user3808269
user3808269

Reputation: 1351

Why Do I See No Output With Git Log But I Do See This With Git Diff?

Team,

I type: git log branchA..branchB -- /dir1/dir2

When typing this "git log" command nothing is output. However, when I type this "git diff" command many files are output.

git diff branchA..branchB -- /dir1/dir2

Does anyone know why there is this extreme difference? I have read the documentation but this does not give me an obvious answer unfortunately and i have to venture onto the open seas of the Internet.

Seems like if there is output with the "git diff" command then this means files were changed between the branches (branchA and branchB). If there are changes between these branches as evidenced by the "git diff" command then there should be something, anything, in the "git log" command.

Why does the "git log" command not return anything but the "git diff" command does return a lot of output on file and code changes?

Upvotes: 1

Views: 650

Answers (1)

torek
torek

Reputation: 488103

TL;DR

First, check whether there are any commits in the branchA..branchB range, using git rev-list for instance (add --count if you don't want to see the raw hash IDs). Then, if there are commits, consider adding --full-history and maybe -m to your git log command.

Long

There is a lot to unpack here. (There might be a lot less if you pointed to a specific repository and two specific branch names within that one specific repository.)

First, git log and git diff are pretty different beasts. But it is worth noting that git log can run git diff, more or less; git diff cannot run git log. Nonetheless, git diff can do things git log can't do. Second, the two-dot notation has multiple different meanings. What it means to git log is very different from what it means to git diff. In one sense, it doesn't mean anything at all to git diff as you can always replace:

git diff X..Y

with:

git diff X Y

That is, the two dots here merely serve to separate X from Y, which you could also do just by giving them as separate arguments. That's not true for git log, though. That makes git diff quite a bit simpler, so let's start with the git diff.

Diffing two commits

You have run (in effect anyway):

git diff branchA branchB -- /file1/file2

This form of git diff, given two commit-specifiers and a path,1 has the effect of checking out each of the two specific commits and comparing them. Without the path limiter, you'd get complete instructions on what set of operations would change the first commit into the second one: add files A and B, delete files C and D, modify files E through H, etc. Using the path specifier /file1/file22 eliminates from the output any instructions that affect files whose name does not start with (or equal) this string. (The leading slash here is just relative to the top level of your repository.)


1Some Git commands take pathspec arguments. The git log and git diff commands do not. If you don't use complicated stuff, there's nothing to worry about here. If you start using :/: or :(glob) and other tricky pathspec options, just note that they're not available here.

2Note that your OS probably demands that file1/file2 name either a file named file2 within a directory / folder file1, or a directory / folder named file2 within one named file1. To Git, these are just strings—there's no "folder-ness" involved. Still, it's probably better to say /dir1/dir2 here.


Using git log

There is a lot to know about git log; let's start with the two-dot notation. In this case your two items are branchA and branchB. Git will—at least with git log, and also with many other commands but not git diff—first treat this as selecting all commits reachable from the tip of branchB, minus any and all commits reachable from the tip of branchA. This uses the phrase reachable from, which deserves an entire web tutorial: Think Like (a) Git. (Go read it!)

As knittl notes in a comment, this list can be empty, even if the two names designate different commits. In this case, git log will print nothing, as we'll see. To find the list of hash IDs—without seeing anything else—use git rev-list:

git rev-list branchA..branchB

The rev-list command is a major Git work-horse that a lot of Git's commands use, either by running it, or by having it built in to themselves. For instance, all of cherry-pick, revert, rebase, log, shortlog, and push use git rev-list internally. A git fetch also uses it, but runs the rev-list command on the other Git, rather than on yours.

In any case, git log now has, in effect, a list of commits to walk. It's not at all obvious how many commits are in this list. If the list had just one commit in it, your git log and git diff would do the same thing—produce the same output—so we may safely assume that the list has either no commits in it, or at least two commits in it. If it's empty, but the commits are different, your output is already explained.

If not, things get more complicated, but it's likely that the list of commits has at least one merge commit in it. If we assume that it does have at least one merge in it, that also gets us an explanation. What Git will now do is walk the list of commits, in some order—and if you hadn't added -- /file1/file2 it would now do something simple to explain:

  • Find a commit that is to be walked (internally, these actually come off a priority queue, as part of this build-a-list-and-simultaneously-walk-it).

    • Find its parent. If this commit is a merge, find all parents. This is really part of the rev-list action: git log and git rev-list are built from a single source and share most of their code, and rev-list and log both walk the commit graph using this shared walking-code.
    • Print the log message from the commit, plus any leading stuff like the commit hash, based on the --pretty format chosen.
    • If the commit is not a merge, run git diff between the (single) parent and the commit itself, and show that output too. If the commit is a merge, show nothing.
    • Put the parent(s) on the queue of commits to walk, and go back to the top.

So you'd see a git diff for every non-merge commit in the list that git log / git rev-list find by enumerating "commits reachable from branchB minus commits reachable from branchA".

Now, you would think that adding -- /file1/file2 would affect each of these diffs so that you see only those changed files ... and that's true. But it does more that that: it turns on History Simplification.

With History Simplification turned on, git log (and git rev-list) deliberately cut off parts of the reachability graph. In particular, at any merge, Git goes through an elaborate computation of what the documentation calls TREESAME. This computation amounts to stripping each commit down to just those files within the list of files and/or directories you specified on the command line, and comparing the contents of each such file.

When one "leg" of a merge is TREESAME on all files, after this stripping of other files, and the other isn't TREESAME, Git discards the not-TREESAME leg, including all the commits that are reachable through that leg of the merge. If the merge is an octopus merge, and at least one parent has the TREESAME-after-stripping property, Git picks one at more or less random and follows that one, discarding all the other legs. (If all legs are different, Git follows all legs.)

Then, since the merge commit is a merge commit, git log, by default, prints nothing. Even if git log weren't skipping diff-listings for this,3 it has probably just picked some TREESAME parent, so that the diff listing would be empty anyway. That's what TREESAME implies: after stripping away all the files you didn't select, the remaining ones were the same from the merge commit to its chosen parent.

Having chosen the TREESAME parent, Git continues down this track. If branchA identifies some commit that's in the discarded leg, you can—I think—now see why the output from this git log might be empty: there may well be no changes to the selected files in any commits from here on back to wherever branchA rejoins with commits reachable from the tip of branchB.

If it is in fact History Simplification that has discarded the interesting commits, adding --full-history to your git log command will show you the commits you wanted to see. Of course, it's possible that any changes you were looking for were introduced by an "evil merge", so you might also need the -m option, which forces git log to show a full diff between a merge commit and each of its parents.


3You can tell git log to print diff listings at merges, using either -c, --cc, or -m, each of which has a different effect.

Upvotes: 3

Related Questions