Suppose there are 2 branches x1 and x2 both based on master. git diff x1..x2 shows the diff between x1 and x2, but git format-patch x1..x2 -1 --stdout shows the diff between master and x2. Is there a way to let format-patch to show the diff between x1 and x2?
For example:
git branch x1 master
git branch x2 master
git switch x1
echo a >> README
git commit -a -m x1 # one extra line at x1
git switch x2
echo a >> README
echo a >> README
git commit -a -m x2 # 2 extra lines at x2
git diff x1..x2 # show one line betwen x1 and x2
git format-patch -1 --stdout x1..x2 # show 2 lines between master and x2
If I understand correctly, it seems git format-patch only emits real commits (could be stashed) but it will not try to calculate interdiff.
Why is “git format-patch” between two commits different from “git diff”?
The interpretation of .. is different for the two commands. git diff is looking for two commits, git format-patch is looking for one or more patch series.
They're both trying to be helpful, to turn what you told them into something they can work with.
git diff effectively ignores the two dots. It's looking for two commits, you gave it two commits, those are the two commits. This is perhaps less baffling when you consider that it also has its own (quite useful, and sensible) interpretation of the three-dot syntax.
git format-patch is looking for patch series, so it uses the more common dots interpretation shared among all git rev-list-driven commmands.
TL;DR: if you're looking for something like an interdiff, consider git range-diff.
If I understand correctly, it seems git format-patch only emits real commits (could be stashed) but it will not try to calculate interdiff.
This is correct, but may be coming from some wrong assumptions.
In particular, git format-patch is a method of turning a commit—or a whole chain of commits—into something that will survive email. Whoever gets the patch can use git am to create an equivalent commit, preserving most of the metadata and the git patch-id.1
Since this is aimed at preserving a chain of commits, format-patch looks specifically at a single chain of commits. The x1..x2 syntax you used is allowed, but when giving it a single revision, that's treated as a since specifier—that is, git format-patch X means git format-patch X..HEAD.
Note that in this kind of revision range—A..B for any valid specifiers A and B; see the gitrevisions documentation for more detail—commit B acts as a positive reference and is normally included in the selected commits, but A is a negative reference, as in ^A, and is always excluded. Hence if A and B name the same commit, B winds up being excluded too. However, you can exclude a commit that normally wouldn't appear anyway. In particular, you have created this situation:
I <-- x1
/
...--H <-- master
\
J <-- x2
such that the commits selected by x1..x2 are always just commit J, and the commits selected by x2..x1 are always just commit I—the same set you'd get with master..x2 and master..x1 respectively.
(Note: the -number option to git format-patch forces a change in behavior when given a single commit specifier, instead of a range. It doesn't seem to have any effect when using a range specifier.)
The git diff command, however, does not obey these rules. Normally, a range A..B means all commits reachable from B, excluding all commits reachable from A, which is what we saw above with the master/x1/x2 fork. But git diff A..B just means git diff A B. Git simply extracts commit A to use as the left side, and extracts commit B to use as the right side, and compares them. There is no interdiff here either, nor any attempt to reach back to the common commit on master.
The git diff command has a special interpretation for the three-dot range syntax too: git diff A...B means find a merge base between A and B, then compare that to commit B. This is also not any kind of interdiff—git diff just doesn't do that at all.
1The committer and committer-date are, by default, new when using git am. However, you can also add --committer-date-is-author-date. If you do this and you set things up so that your committer name matches the original committer name, and apply it to the right starting commit, you'll even get a commit that has the same hash ID, provided the original commit has the same author and committer dates. That is, with a little effort, sometimes an emailed patch can result in a bit-for-bit identical commit, as if you had used git bundle or similar.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With