1

For context, from my own feature branch I am using the following commands:

> git fetch
> git show ..origin/master:somefile > ../external_folder/somefile

to ensure that I have the latest copy of a file from origin/master to read from without overwriting my own branch's.

The above works but if I omit the two dots from the git show command, the file I get is the same as my branch's even though a newer one has been pushed to origin/master.

Can someone explain what the difference is? Also, if it makes any difference the file in question is a blob.

edit: I was mistaken:

> git show ..origin/master:somefile > ../external_folder/somefile

produces an empty file and I misinterpreted the diff

2 Answers2

0

You should just use git show origin/master:somefile here, without the two leading dots. The git show command probably should complain about ..origin/master:somefile: print an error message to stderr, and no output at all to stdout.

With the versions of Git I have handy, git show ..<rev>:path produces no output at all (and exits successfully!), even though the given path exists in both commits. So if I were to use your:

git show ..origin/master:somefile > ../external_folder/somefile

I would always end up with an empty ../external_folder/somefile file. I conclude that your Git version must predate Git 1.7.11.3.

Long-ish

The <rev1>..<rev2> syntax mostly1 means select a range of commits, with the range being determined by invoking git rev-list, more or less as if you had run:

git log ^$(git rev-parse rev1) $(git rev-parse rev2)

You can see this effect more directly if you just run git rev-parse yourself:

$ git rev-parse master~2..master
b994622632154fc3b17fb40a38819ad954a5fb88
^41eae3eaa81c5f2b61b2610ed74de15c4291b318

The name master, in this particular Git repository, selects commit b994622632154fc3b17fb40a38819ad954a5fb88. The relative expression master~2 selects commit 41eae3eaa81c5f2b61b2610ed74de15c4291b318.

Omitting one of the two names means pretend I wrote HEAD so since HEAD is currently master, we also get:

git rev-parse master~2..
b994622632154fc3b17fb40a38819ad954a5fb88
^41eae3eaa81c5f2b61b2610ed74de15c4291b318

Commands like git log or git cherry-pick can take a range of commits like this. The set of commits selected by this range expression is:

  • all commits reachable from the non-negated (no ^ prefix) commit, excluding
  • all commits reachable from the negated (^-prefixed) commit.

So if we have:

...--E--F--G--H   <-- master (HEAD)

then master~2..master, or—shorter but identical in meaning—HEAD~2.. selects commits G and H, because master (or HEAD) means commit H while master~2 (or HEAD~2) means commit F. We select all commits up through and including commit H, but then knock out all commits up through and including commit F, leaving just G and H here.

The git show command, however, normally shows just one thing: one commit or one file, for instance. So the notion of giving it a range expression makes no sense (but see below). The command can, however, show individual files, and it does that when the revision expression you supply names a blob object:

$ git rev-parse master:Makefile
3d3a39fc192d5544a411d4cedb3785473b6f1148
$ git cat-file -t 3d3a39fc192d5544a411d4cedb3785473b6f1148
blob

So when git show master:Makefile is given a revision expression that resolves to a single blob object like this, git show shows the contents of that blob object.

Now consider:

$ git rev-parse master~2..master:Makefile
3d3a39fc192d5544a411d4cedb3785473b6f1148
^41eae3eaa81c5f2b61b2610ed74de15c4291b318

While hash IDs are big, ugly, and hard to remember or get right, we can actually see here that what we're giving git show is:

  • the non-negated hash ID of the blob object we'd like to see, plus
  • the negated hash ID of the commit master~2.

As it turns out, commit c5941f1aac071addc1c9b0781c323b588c542420, which first went public in Git 1.7.11.3, modified the git show command to act very much like git log when given a commit range:

git show master~2..master

works much like running:

git rev-list master~2..master | while read commit; do git show $commit; done

There are a few differences. In particular, git log by default does not show a patch for merge commits, but git show by default shows a patch as if you had used git log --cc. (The other difference is less important here: the shell variant, with the while read commit; do ...; done sequence, has to spawn multiple git show commands.)

Note that invoking git rev-list this way produces no output:

$ git rev-list master~2..master:Makefile
$ 

That's because the second (non-negated) hash ID is that of a blob object, and blob objects do not participate in revision walking. So this amounts to select no commits, while excluding all commits reachable from master~2.

The fact that all of this runs and exits quietly, without complaint, is almost certainly a bug. Mixing commit hashes and tag-or-blob hashes in a .. or ... expression should just draw an error from the revision parsing code, I think.


1The reason for the adverb mostly here is that a few commands have special handling. In particular, both git diff and git rebase play special tricks with the two- and/or three-dot syntax: they don't just let git rev-list do the work.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thank you, this clears things up a lot. On taking a second look `git show origin/master:somefile > ../external_folder/somefile` without the dots does work like I needed it to. I must have made a mistake somewhere – Kody Quintana May 26 '20 at 22:38
0

from the git-documentation:

... in these two shorthand notations, you can omit one end and let it default to HEAD. For example, origin.. is a shorthand for origin..HEAD and asks "What did I do since I forked from the origin branch?" Similarly, ..origin is a shorthand for HEAD..origin and asks "What did the origin do since I forked from them?" Note that .. would mean HEAD..HEAD which is an empty range that is both reachable and unreachable from HEAD.

Whole documentation about dotted range notation you can read here

Git substitutes HEAD if one side is missing -> git show ..origin/master becomes git show HEAD..origin/master and look, what did origin do since it forked from.

If you write without double dot, you take the current origin and show the content from this origin.

In the example from the picture above, with ..experiment you will see the stood from the red point. If you write experiment you will see the stood from the blue point.

Picture from here

Have also a look to this question

SwissCodeMen
  • 4,222
  • 8
  • 24
  • 34
  • Thank you, I was confused about giving a range to `git show` with a specific filename because it exited without error, it seems like giving it a range doesn't make any sense in this context. – Kody Quintana May 26 '20 at 22:32