Search code examples
gitgit-branchgit-diffgit-log

git log to show commits different between two branches, including common ancestor


When working on feature branches (e.g., feature) created off of our main (e.g., master) branch, I use the following git command to view a list of commits that have been added to the feature branch that are not on the master branch:

git log --oneline master..HEAD

This outputs a nice short summary of the commits on feature that aren't on master (note, this command is run after checking out the feature branch):

$ git log --oneline master..HEAD
1ca070a (HEAD -> feature) foo the bar
03a1047 baz the wuz
c9e8279 fop the sip
6ee6d6f up the ante
5812200 bop the binky

The problem is, it doesn't show the common ancestor commit on the master branch. What I would like is for the command to include one more line, that's the common ancestor commit, like this:

1ca070a (HEAD -> feature) foo the bar
03a1047 baz the wuz
c9e8279 fop the sip
6ee6d6f up the ante
5812200 bop the binky
fb37d68 (master) fuzzy the wuzzy

I would expect that replacing master with master~1 would include one commit prior to the common ancestor, and therefore include the common ancestor,

git log --oneline master~1..HEAD

but whether I specify master or master~1, the log summary is always the same.

$ git log --oneline master~1..HEAD
1ca070a (HEAD -> feature) foo the bar
03a1047 baz the wuz
c9e8279 fop the sip
6ee6d6f up the ante
5812200 bop the binky

My question is, how can I include the common ancestor commit in the log summary? Note that the common ancestor is not necessarily the tip of the master branch.


Solution

  • I find that this kind of --boundary includes too many commits quite often, but:

    git log --boundary --right-only --oneline master...HEAD
    

    should get you close-ish. Add --graph (and for completeness --decorate, though you obviously have log.decorate set) to get something even more useful. Leave out the --right-only to get the most useful output. Given the --right-only, you can simplify this to --boundary master..HEAD, but read on to see why I suggest not using --right-only, but using --graph.

    What this does is a bit complicated:

    • The three dot syntax master...HEAD requests a symmetric difference. That is, we get all commits that are reachable from master but not from HEAD from the left side of the three dots, and all commits reachable from HEAD but not master on the right side.

    • The --right-only option, if specified, eliminates the left-side commits.

    • The --boundary option adds back, to the set of commits to be visited by the graph walk, those commits that were specifically chosen as not to be shown via the symmetric difference algorithm.

    Consider this graph, drawn with parent commits to the left and children to the right (rather than parents below and children above as git log --graph does):

              C--D--E   <-- master
             /
    ...--A--B
             \
              F--G--H   <-- feature (HEAD)
    

    The uppercase letters here stand in for commit hash IDs. Commits E back to C are reachable from master, and commits H back through F are reachable from HEAD (which is attached to the name feature). Commit B is the merge base of these two sets of commits.

    Here, master...feature selects commits C-D-E on the left and F-G-H on the right. (Note that reversing the names—feature...master—selects the same commits, but now C-D-E would be on the right.) So adding --right-only to this set gets you almost what you want: commits through HEAD, excluding commits reachable from master. That's the same output you'd get from the simpler master..feature.

    Now, if we add --boundary, Git will include commit B as well. That is, both git log --boundary master..feature or git log --boundary --right-only master...feature will show commits B-F-G-H (in the other order).

    With --decorate, when git log shows commit H, it will add the HEAD -> feature decoration. But commit B has no branch name, so it will not be decorated. It will be hard to tell that commit B belongs to both branches.

    Without --right-only and with --boundary and --graph, Git will show commits C-D-E on master and F-G-H on feature, with E and H decorated, and will include boundary commit B. The resulting graph will show pretty clearly that B is the shared merge-base commit.

    When the graph gets complicated, --boundary adds back too many commits. The extra commits will still be displayed reasonably clearly—as clearly as these things get when there are as many graph lines as git log --graph introduces, anyway. You can add --simplify-by-decoration to help eliminate a lot of the visual clutter: the resulting graph is probably readable, and will let you identify the shared commit.