Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

git log master ^master produces non-empty output

Tags:

git

I execute git log master ^master and expect an empty output, because I list all commits that are present in master and simultaneously exclude them.

However I see all log messages as if I just ran git log master. I wonder why this happens.

My master is fully up-to-date with remote (git pull -> Already up to date.)

I made a couple tests and got the correct (empty) output with some of commands:

git version 2.50.0

git log master ^master -> wrong (WHY?)

git log master ^origin/master -> correct
git log origin/master ^master -> wrong
git log origin/master ^origin/master -> correct

git log master ^refs/heads/master -> correct
git log refs/heads/master ^master -> wrong
git log refs/heads/master ^refs/heads/master -> correct

git log origin/master ^refs/heads/master -> correct (proves that branches are in-sync)
git log refs/heads/master ^origin/master -> correct
git log master..master -> correct

UPD: Thanks to the guys in the comments, I figured out that the problem was in my Zsh shell. Everything works fine with Bash, so the question is: what does Zsh do that breaks in some cases, but works in others? I assume it's some kind of globbing.

like image 358
Alexey Nurmukhametov Avatar asked Dec 19 '25 15:12

Alexey Nurmukhametov


1 Answers

To understand how this works on a deeper level, we need to go to first, to understand how git handles the design logic

: https://github.com/git/git/blob/master/revision.c#L3898

git log master ^master

First argument (master):

  • Git parses the ref master as a positive inclusion.

  • It resolves to a commit object (e.g., refs/heads/master).

  • This object is added to revs->pending with no special flags.

  • During prepare_revision_walk(), handle_commit() processes it:

    • The commit is marked with the SEEN flag.

    • It's added to the traversal queue.

Second argument (^master):

  • Git sees ^master as a negative revision (an exclusion).

  • It resolves to the same commit object as before.

  • This time, it is added to revs->pending with flags:

    • UNINTERESTING | BOTTOM
  • During prepare_revision_walk() , handle_commit() is called again:

    https://github.com/git/git/blob/master/revision.c#L3898

    • But the commit already has the SEEN flag from the earlier inclusion.

    • Git skips reprocessing it due to:

      if (commit->object.flags & SEEN)
          return 0; // Already handled
      
      

      As a result, the commit never gets marked as UNINTERESTING, and is not excluded from output.

      The problem is not that Git doesn’t understand you want to exclude master — it’s that the commit was already marked as SEEN during inclusion, so when Git sees it again during exclusion, it skips updating its flags.

      So:

      • First argument (master) includes the commit

      • Second argument (^master) tries to exclude it — but fails to apply UNINTERESTING

      • Therefore, the commit still appears in the log output


like image 79
Daniel Raphael Avatar answered Dec 21 '25 05:12

Daniel Raphael