7

My goal is to get the whole diff of the whole branch in git. Provided my branch is named foo/bar, what's the appropriate command (i.e. args to git diff, I assume) to see the whole diff? (this branch received many different merges from other branches).

Edit: Perhaps I was not so clear with the requirement of whole branch diff, but what I meant is taking two commits:

  1. The last commit of a branch.
  2. The parent commit of a branch.

I need those two arguments to be passed to a git diff command. Is there a -shortcut- way to specify both commits?

Luis Masuelli
  • 12,079
  • 10
  • 49
  • 87
  • `git diff branch^..branch` – Pockets Feb 17 '17 at 20:00
  • That command compares the current branch status against the status when the branch started? – Luis Masuelli Feb 17 '17 at 20:00
  • 1
    everything since foo/bar split from master is `git diff master...foo/bar`. If you're after everything since your current branch split from its upstream it's `git diff @{upstream}...` or `git diff @{u}...`. Note the three dots, that's "since the merge base" and it's specific to diff, since yours is a very common request. – jthill Feb 17 '17 at 20:57
  • 1
    There's no such thing as when a branch "starts", given the Git model itself (discounting root commits). That command compares the branch pointer to the parent of the branch pointer. – Pockets Feb 17 '17 at 20:57

2 Answers2

5

diff ... as compared to what?

There is no such thing as a "diff of a branch". There are only diffs of commits.1 Pick any two commits and you can compare them. But a branch ... well, see What exactly do we mean by "branch"? A branch is not two commits. A branch name can select one commit, or you can use a branch name in conjunction with something else to select many commits. But one commit is not enough, and many commits is too many. You must pick exactly two commits.

Note that git diff branch1 branch2 and git diff branch1..branch2 mean exactly the same thing: translate the name branch1 to one specific commit, and translate the name branch2 to one specific commit, and then diff those two specific commits. Again, see What exactly do we mean by "branch"?


1You can also diff a commit against your work-tree, or against your index; and you can diff your index against your work-tree. Besides these, there are specialized forms of diffs to look at just one file, even one outside a Git repository; and you can have Git compute several different diffs, then combine them into a single combined diff. The combined diff, however, is designed to be used only with merge commits, and diffs the final merged result against each parent. It's not useful for viewing many arbitrary commits, only for checking the final result of a merge.


After the edit

You may be able to get what you want—but there isn't really a "parent commit of the branch" either. Consider, for instance, this graph fragment:

...--o--A--o--o--o    <-- br1
         \
          o--B--o     <-- br2
              \
               o--o   <-- feature

The newest commit of each branch is the right-most o, and each commit connects back to an earlier parent commit. (These connections are stored internally as those big ugly SHA-1 hash IDs, with each commit saving its parent IDs. Parents don't know their children, because the children don't exist yet when the parents are made, and once made, no commit can ever be changed. So the children know their parents, but not vice versa.)

What is the "parent" commit of feature? Is it commit A or commit B? (It's probably not any of the remaining commits, which is why I only gave specific one-letter names to those two.) Or:

...--A--o--o--B--o--C--o    <-- devel
      \      /     /
       o----o--o--D--E--F   <-- feat

Here feat has been repeatedly brought into devel, at points B and C. It can be brought in again; if so, that will introduce the changes from E and F with respect to D. But it seems obvious from this graph drawing that feat originally "grew out of" commit A, so is A the parent you want, or is it more related to B or C? If it's not C, you will have a problem, because Git doesn't record A or B independently anywhere (it doesn't record C independently either but we have an easy way to find, well, not C but D; but D is perhaps what you want). Moreover, while it seems obvious that A is the starting point, that's merely because of the way I drew this graph fragment. A different view might make it equally clear that there is an earlier starting point—and in fact, there just isn't a real "starting point" unless you specifically record one (tags are ideal for this purpose).

Merge bases: how to find A or B in the first diagram and D in the second

Git supports—because it must, because git merge needs it—the concept of a merge base. The merge base of two commits is easy(ish) to see if you draw a graph of the commits like we did above. The commits are usually two different branch tip commits (and if not, we just pretend they are). You simply start at each tip, then follow its the connecting lines leftwards / backwards. Eventually, this needs to "meet up" at some common commit. That's the first commit that's on both of these branches, and that commit is the merge base.

Here's that first diagram again:

...--o--A--o--o--o    <-- br1
         \
          o--B--o     <-- br2
              \
               o--o   <-- feature

Starting at the tip of feature and the tip of br1, check to see if you have a common commit yet. No—so look back a bit. Looking back on br1 there's a point where something comes in, at commit A. Can we get to commit A from feature? Yes: we start at the tip of feature and go back two steps to B, then go back two more steps to A. That's the first shared commit—the first commit that's on both br1 and feature. So that's the merge base.

The merge base of br1 and br2 is likewise commit A, and the merge base of br2 and feature is commit B. Hence, given two branch names—or even just two commit IDs—we can find their merge base.

The merge base for the second diagram is trickier. Commit C is a merge commit. This means it has two parent arrows, one to a commit that is only on branch devel, and one to commit D on feat. (Note, by the way, that this means we made commit D before we made commit C. The graph lettering here is a bit misleading! Sorry about that.) This makes commit D reachable from the tip of devel, so that commit D is on both branches—and that, plus its "most close"-ness to the branch tips, makes commit D the merge base of devel and feat.

git merge-base

The command git merge-base A B will find the merge base of the two named branches. Ideally, this will be a single commit ID. (It could be no ID, if the two branches literally have nothing in common—they have unrelated histories; this is rare—or there could be more than one merge base, in which case git merge-base just picks one, somewhat arbitrarily. It's hard to get multiple merge bases, though, so generally you don't have to worry about this.)

Hence, if the goal at this point is to find commit D in the second diagram, and diff that against the tip of feat, you can do:

git merge-base devel feat    # and note the commit ID
git diff <hash> feat

Because this is a relatively common thing to want, git diff has it built in. The syntax for this borrows the three-dot syntax that for every other Git command means something else:

git diff devel...feat

Note the three (not two) dots between the two names. For git diff, and only for git diff, this means "find the merge base between these two branch tip commits, and then diff it against the second commit."

With the first diagram, you can pick br1 or br2 to find either A or B and compare that to the tip of feature:

git diff br1...feature

for instance.

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775
  • Edited the question: I'd like to compare the current status of my branch against the parent commit (i.e. the status where I created the branch from) – Luis Masuelli Feb 17 '17 at 20:00
2

Simply git diff commit_hash1 commit_hash2

where commit_hash1 is the hash of the parent of the first commit on your branch (note that it is not part of your branch), and commit_hash2 is the head of your branch (the commit that your branch is currently pointing to).

Gautam
  • 1,079
  • 14
  • 21