Search code examples
gitgit-revision

What is the command for "show me all logs from HEAD to ec9cb4106"?


I need to see either (1) log messages from HEAD to ec9cb4106 or (2) rollup diff from HEAD to ec9cb4106. The revision selection page does not appear to discuss it, and I can't get git to accept the obvious choices:

$ git log HEAD .. ec9cb4106
fatal: ec9cb4106: no such path in the working tree.
Use 'git <command> -- <path>...' to specify paths that do not exist locally.

$ git diff HEAD .. ec9cb4106
fatal: ec9cb4106: no such path in the working tree.
Use 'git <command> -- <path>...' to specify paths that do not exist locally.

I'm trying the X .. Y because that 's what git displays when it summarizes an update and it can be fed to git diff.

How do I see either (1) the log messages in the range, or (2) the rollup diff of the range?


Solution

  • Formatted as answer (and with more care about order): you want git log ec9cb4106..HEAD here (as per comments), i.e., include commits reachable from HEAD but exclude items reachable from ec9cb4106 (including ec9cb4106 itself).

    Note that there are no spaces before or after the two dots.

    The two-dot notation is shorthand for inclusion-and-exclusion.

    In general, commands like git log and git rev-list select a commit "with ancestry", as it were: take the given commit and find its parent(s), the parents' parents, and so on, and visit every such commit. This can be a very large set of commits, so we want some way to prune off uninteresting parts of the commit graph.

    For this, git log and git rev-list can do set operations. In general, naming multiple commits gets you a union. For instance, consider a graph like this:

         C--D   <-- branch1
        /
    A--B
        \
         E--F   <-- branch2
    

    Naming branch1 selects commit D, which—through the back-pointers—reaches commits C, B, and A, and hence shows you those four commits.

    Naming branch2 selects commit F, which reaches commits E, B, and A, and hence shows you those four commits.

    Naming both branches selects both D and F, so you see all six commits. Note that you do not see B and A twice: the selection is done first, collecting up all the commits to show, and then the commits are sorted and displayed (once each).

    To remove some part of the graph you can tell the revision-walker to start at some commit and remove that commit and all its ancestors. The external syntax for this is a prefix hat ^:

    git log branch1 ^branch2
    

    This says "select everything reachable from branch1, then discard everything reachable from branch2". In this case, that would select D, C, B, and A; then discard F, E, B, and A. F and E are not in the original set, but this is not a problem: you can subtract away commits that are not there. Hence there's no need for the negated commit(s) to actually be in the set of ancestors of the positively-selected commit(s).

    Git offers a rather ridiculously large set of syntaxes for these, so besides ^X Y, you can write --not X --not Y. The reason for two --nots is that --not persists: --not X Y means ^X ^Y. The second --not cancels out the first.

    The two-dot syntax, X..Y, is shorthand for ^X Y (or equivalently (Y ^X). Except for --not persisting, order is irrelevant when using the longer syntax, but important with the two-dot syntax.

    Note that git diff treats the two-dot syntax specially. It does not select a range of commits there. (The C source for git diff checks for the literal string .. relatively early, and removes it if it's present, before passing the arguments on to the revision parsing code.)