Home > Enterprise >  What does git diff <commit> <commit> ... <commit> actually do?
What does git diff <commit> <commit> ... <commit> actually do?

Time:07-01

I have a somehwat complicated multi commit cherry-pick and I was thinking that I just wanted to see what the entire changes would look like in its entirety. So I tried to do git diff A B C D E F G H I J K. It looks like it did what I asked for. I looked up what that actually does in the docs, but I'm a little unsure if that is what I'm asking for as it says (emphasis mine):

git diff [<options>] <commit> <commit>…​ <commit> [--] [<path>…​]

This form is to view the results of a merge commit. The first listed must be the merge itself; the remaining two or more commits should be its parents. A convenient way to produce the desired set of revisions is to use the ^@ suffix. For instance, if master names a merge commit, git diff master master^@ gives the same combined diff as git show master.

Does this actually do the equivalent of showing what the changes are if all of the commits were done, excluding other changes that are not relevant to those changes? (There are changes that were done in between some of these changes)

The topology of the git history is:

* L - (11 months ago)
* K - (11 months ago)
*   l - (11 months ago)
|\
| * m - (11 months ago)
* | n - (11 months ago)
|/
* o - (11 months ago)
*   p - (11 months ago)
|\
| * q - (11 months ago)
| * J - (11 months ago)
* | r - (11 months ago)
|/
*   s - (11 months ago)
|\
| *   t - (11 months ago)
| |\
| * | I - (11 months ago)
| * | H - (11 months ago)
| * | G - (11 months ago)
| * | F - (11 months ago)
| * |   u - (12 months ago)
| |\ \
| * | | E - (12 months ago)
| * | | D - (12 months ago)
| * | | C - (12 months ago)
| * | | B - (12 months ago)
* | | | v - (11 months ago)
| |_|/
|/| |
* | | w - (11 months ago)
...
* x - (1 year ago)
*   y - (1 year ago) 
|\
* | A - (1 year ago) 
* | z - (1 year ago) 

An excerpt of the diff shows:

  -----                var sw = new Stopwatch();
  -----                sw.Start();
                       var result = SystemGetTheatreInformationAsync(localTMSClientManager);
  -----
                       //var sw = new Stopwatch();
                       //sw.Start();
       ----            var result = SystemGetTheatreInformationAsync(localTMSClientManager);
       ----

  ---------            if (!result.Wait(TimeSpan.FromSeconds(45)))
                       if (!result.Wait(TimeSpan.FromSeconds(90)))    // Have seen this take up to 56 seconds
                       {
                           log.Warn($"{MethodBase.GetCurrentMethod().Name} took too long to execute (timeout exceeded).");
                       }
                       else
                       {
  -----                    sw.Stop();

and what is curious are the leading s and -s which I'm interpreting as additions and removals at individual commits, but what are the spaces signifying? If they are signifying no change, then I would have expected it to show maybe at the beginning, but wouldn't have expected to see them at the middle or end.

Is this command showing the diff as if it was merged as one or what exactly is it showing?

CodePudding user response:

TL;DR: "Combined diffs"

When you invoke git diff this way—by naming three or more commits on the command line—you're invoking the same internal machinery that git show uses for showing a merge commit. This depends on the fact that each commit stores a single Git "tree" object. A merge commit has two or more parent commits, each of which also has a tree object, so when git show is handed a merge commit hash ID, it has three or more tree objects to compare, while the basic difference-engine algorithm can only take two at a time. It therefore does ... something, and knowing what that "something" is, is useful, every time you see one of these things. Git calls this something a combined diff. You don't have to memorize the details of combined diffs: just look them up in the manuals. Do, however, remember that the Git documentation splits up two key facts about combined diffs:

  • Combined diffs omit some files entirely.
  • Combined diffs can omit some diff hunks entirely.

When reading the manual pages, remember to search for both sections about combined diffs.

Note that you get combined diffs for a plain, no-arguments-provided git diff command when you're in the middle of an incomplete merge operation, too. However, in this case, the sources for the to-be-diffed files are your working tree and Git's index, rather than multiple commits. This answer does not cover these details.

Short-ish

Git adds special git diff logic in an attempt that (in my opinion at least) works sort-of-OK for some common cases where we want to see why a merge commit has the tree it has, and not some other tree we might have expected. This attempt has some flaws, and since merge-ort became the default merge strategy (in Git 2.34) and it has some new features, there may someday be a better way to do this, but for now, git show of a merge can sometimes help you figure out what happened. The mechanism it uses is to run git diff n times, where n is the number of parents of the merge commit, then combine parts of the results, and discard other parts of the results, to form a combined diff. This makes sense for showing a merge. It makes less sense for showing a non-merge commit (and no sense for your original cherry-pick purpose).

The files that get omitted entirely from a combined diff are those where the merge commit's version of the file exactly matches the version in at least one of the parent commits. The general idea here is that whoever did the merge must have thought that one of the two parents had the right code. The flaw in this general idea is that perhaps the person who made the merge was an idiot.

  • Related