Reputation: 1351
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
Reputation: 488103
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.
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
.
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/file2
2 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.
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).
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.--pretty
format chosen.git diff
between the (single) parent and the commit itself, and show that output too. If the commit is a merge, show nothing.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