What does the `..` mean in git branch reference?

With git log (and all the other Git commands that take a similar argument set), it’s a specification of how to find a range of revisions, yes. Remember that in Git’s general world, that means some subgraph of the revision graph— to most people, it generally means just a range of revisions in a list. (And if you don’t do much if any branching, it simplifies to that in Git too).

The revision specification contains a set of positive references (starting points) and negative references (stopping points) and additional filters (limit number of revisions, grep commit text, etc.). Git starts with the positive references and goes back through the revision history, stopping when it encounters revisions that are reachable from the negative references (not necessarily just when it reaches one of the negative references themselves).

It’s perhaps rather confusing that there are various shorthand notations that have evolved, that aim to make this all easier to use and yet somehow also manage to confuse- I had to spend quite a while figuring out just what “master..maint”, “maint..master”, etc. meant and when to use which.

When you just say “origin/master”, then that means “origin/master” is a positive reference and there are no negative references. So Git starts at origin/master and walks back through all the revisions available– you get the complete history of origin/master.

“origin/master..” is shorthand for “origin/master..HEAD” which looks kind of like it means “from origin/master up to HEAD”. Which it does, effectively. It can be rewritten as “HEAD ^origin/master” or “HEAD –not origin/master”. In this case, HEAD is a positive reference and “origin/master” is a negative reference. So Git starts at HEAD and walks back through the graph until it encounters a revision that is reachable from origin/master. It is likely that it will encounter origin/master itself, in fact. Note that all the references are inclusive– the positive references themselves are output and the negative references aren’t (unless you give –boundary, and then they’re flagged). That means that “origin/master..HEAD” outputs nothing if HEAD and origin/master are the same revision.

So if you have made a couple of local commits on top of the upstream version you have this kind of situation:

steve@monolith:~/src/git <master>$ git log --pretty=oneline --abbrev-commit --decorate -n 4
ea3107d (refs/heads/master) Add another dummy comment
869c260 Add dummy comment
6345d7a (refs/remotes/origin/master, refs/remotes/origin/HEAD) Merge branch 'maint'
be427d7 allow -t abbreviation for --track in git branch

And now “git log origin/master..” means git will start at HEAD (ea3107d), which isn’t reachable from origin/master, so it prints that. Then it walks back to HEAD’s parent (869c260), which still isn’t, so prints that. Then the next parent is 6345d7a, which is origin/master so it stops.

Note that “git log ..origin/master” does the opposite– tries to walk back from origin/master to HEAD. In this case, it won’t print anything. But if I checked out “origin/maint”, it would print the revisions on origin/master that were not on origin/maint: so in general, try to think of “A..B” as “revisions in B that are not in A”, and remember that omitting A or B means “HEAD”.

Just for extra super duper confusion, there is also a notation “A…B”. So remember to count the number of dots! In the case of A and B being in a line of revisions, there is no real difference. But what “A…B” means is the revisions in either A or B that are not in any of the merge bases for A and B. So if A and B are on divergent branches, it shows all the commits made on either since they diverged.

The “long form” for a revision range (“B –not A”) allows you to specify things like “all revisions on local branches that are not on any remote-tracking branch” (“–branches –not –remotes”). This argument list is parsed by many Git commands (“git rev-list” being the core one), including gitk. So you can do “gitk –branches –not –remotes” to see your local changes graphically.

And finally for mega-bonus confusion, commands like “git diff” accept the same sort of shorthand syntax, but it doesn’t mean (quite) the same thing. git diff actually takes two revisions and compares them, which is not the same as a range– remember that a revision range in Git is a subgraph, not just a list. “git diff A..B” is equivalent to “git diff A B”. “git diff A…B” means “show the changes in B since it diverged from A”. Confusing? Just a bit: for example, “git log A…B” and “git log B…A” mean the same thing, but “git diff A…B” and “git diff B…A” don’t.

Leave a Comment