Assume we have two example with its commits (line 1, line 2...
is the content of a foo
file, which is the only file in working directory):
Example 1:
line 1 line 1
line 2 line 22
line 3 line 3
C0---------C1 [master]
\
C2 [test]
line 1
line 222
line 3
Example 2:
line 1 line 1
line 2 line 22
line 3 line 3
C0---------C1 [master]
\
C2--------C3 [test]
line 1 line 1
line 22 line 222
line 3 line 3
I ran git checkout test
and git rebase master
on each of those examples, and I saw that Example 1 has a conflict, while Example 2 doesn't have any.
In Example 1, when reapplying C2
, I guess Git do a 3-way merge on C2
, C1
and C0
, which detect that "line 222"
on C2
different with "line 22"
on C1
and different with "line 2"
on base (C0
), so Git check that as a conflict.
In Example 2, the patch of C2
is already has in master
so Git skip it. But when reapplying C3
, if Git does a 3-way merge on C3
, C1
and C0
, which detect that "line 222"
on C3
different with "line 22"
on C1
and different with "line 2"
on base (C0
), then Git should check that as a conflict, but in real Git doesn't.
So in real, how does Git rebasing check a reapplying commit as a conflict?
Each commit is copied by (or as if by) git cherry-pick
. A cherry-pick is a merge whose merge base is the parent of the commit being picked, with the --ours
version being the HEAD
commit and the --theirs
version being the commit being picked. So, given:
C0--C1 <-- master
\
C2--C3 <-- test
and:
git checkout test && git rebase master
Git will first copy C2
to C2'
, then copy C3
to C3'
.
When copying C2
, the merge base is C0
(parent of C2
), --ours
is C1
(tip of master
), and --theirs
is C2
(being copied). After the copy is finished we have this:
C2' <-- HEAD
/
C0--C1 <-- master
\
C2--C3 <-- test
Next, Git cherry-picks C3
. The merge base is therefore C2
(parent of C3
), --ours
is C2'
, and --theirs
is C3
.
There is a merge conflict if the difference (git diff
) from base to ours, vs the difference from base to theirs, touches "the same lines" of the same file. "Same" in this case includes one line after the line touched. So, compare the contents of C2
(base) to each of C2'
and C3
, to see which lines changed in ours (C2
vs C2'
) and theirs (C2
vs C3
). If we've taken "their" change to line 2 when producing C2'
, the difference from C2
to C2'
is empty, and therefore the merge is trivially resolved by taking the file from C3
.