Search code examples
gitgithubgit-log

Git log between two tags showing commits older than tags?


I am using git log tag1..tag2 to see the commits made between tag1 and tag2 (where tag1 is the older tag, and tag2 is the newer). I expected to see the commits that were made between tag1 and tag2, but I am also seeing commits that were made before tag1's date. Why could this be?


Solution

  • You're running into the fact that commit1..commit2 doesn't mean what people first think it means. In fact, this notation is just shorthand for commit2 ^commit1. It means: find commits reachable from commit2, excluding commit commit1 and commits reachable from commit1.

    This "reachable from" notion is a bit tricky. I like to think of it as "paint commits red or green temporarily". Red means stop, and green means go, as usual. We start with the latest commit and work backwards. Red paint overrides green paint: a stopped-out commit stays stopped-out, even if something else would make it go.

    Consider a commit graph like this one, where new commits are always added on the right:

                      tag:v2.0
                         |
                         v
                    J1--J2   <-- release/2
                   /
    ...--G--H--I--J--...   <-- mainline
             \
              H1--H2--H3--H4--H5   <-- release/1
                   ^           ^
                   |           |
                tag:v1.0    tag:v1.1
    

    Here, we have released versions 1.0, 1.1, and 2.0 (tags with v), which correspond to commits H2, H5, and J2 respectively. The name mainline points to some commit after J, so if we use the name mainline as green/go, we get all its commits down to J, and continuing backwards to I, H, G, and so on. We never "go right again" though, so we never paint any of the extra Jn or Hn commits green. Similarly, if we pick H2 to paint red, we paint H2, then H1, then H, and G, and so on all the way back to the left, but we never move rightward, so we don't touch I or anything further right.

    Looking at commits v1.0..v1.1 you will see commits H5, then H4, then H3. They were painted green (by starting from v1.1 aka H5), and never painted red (by starting from v1.0 aka H2). Looking at v1.1..v2.0 you will see J2, then J1, then J, then I, then H. That's all fine, and is probably what you expect.

    But this graph is very linear. Suppose we have some fork-and-merge behavior in the graph:

              G--H
             /    \
    ...--E--F      K--L   <-- branchname
             \    /
              I--J
    

    and we pick commit L to start from, and J to stop at. We paint J red, and I and F and E and so on are also all red. Then we paint L green, and K green, and H green, and G green. F is already red (or will be overridden with red if we do the red after the green). The final list of commits, then, is L, K, H, and G, even though in some sense G is probably earlier than J. The trick is that G is not reachable from J. Git isn't going by dates at all: it's going by reachability in the commit graph.

    See also my answer to List commits between 2 commit hashes in git, and more generally, Think like (a) Git.