Reputation: 1425
Sometimes when I squash a latter commit onto an early commit, I'll get unexpected conflicts.
I have an example repository here with the following structure.
git log --all --decorate --oneline --graph
* 3aa1300 (HEAD -> master, origin/master) Refactor func(x)
* 6b8940e Add func2
* 6ead589 Add import math
* dc55678 Add func
* 9dfc660 add file.py
* c5fd61b Initial commit
I'll be referring to them as letter commits for ease of use:
D 3aa1300 Refactor func(x)
C 6b8940e Add func2
B 6ead589 Add import math
A dc55678 Add func
AA 9dfc660 add file.py
AAA c5fd61b Initial commit
The commit names are fairly self-explanatory, but here's an abbreviated git log -p
:
commit D: Refactor func(x)
import math
-def func(x):
- return(x)
+def func(y):
+ return(y)
def func2(y):
return(y)
commit C: Add func2
def func(x):
return(x)
+
+def func2(y):
+ return(y)
commit B: Add import math
+import math
+
def func(x):
return(x)
commit A: Add func
+def func(x):
+ return(x)
commit AA: add file.py
[Add the file. Only here to have commit to rebase from]
I'm cleaning up the branch, so I want to squish D
into A
. So I perform a git rebase -i HEAD~4
with the following commands:
pick A Add func
squash D Refactor func(x)
pick B Add import math
pick C Add func2
Doing this leaves me with a conflict in file.py
:
<<<<<<< HEAD
def func(x):
return(x)
=======
import math
def func(y):
return(y)
def func2(y):
return(y)
>>>>>>> 3aa1300... Refactor func(x)
Why doesn't the squash just "happen"? Why does the squash bring in all the other irrelevant changes into the conflict?
I assumed that commits essentially just stored the "diff patch" it performed. So D
would only bring over the
-def func(x):
- return(x)
+def func(y):
+ return(y)
and nothing else.
Assuming I'm just not doing the correct thing, how should I take the "fixes" of D
and squash them onto A
?
Upvotes: 1
Views: 43
Reputation: 488519
I assumed that commits stored essentially just stored the "diff patch" it performed.
This is not the case: each commit stores a full snapshot of all files, intact. When cherry-picking a commit to copy it—and rebase does that, even (or especially?) with the squash
action—these snapshots become diffs, through the modified three-way merge process that git cherry-pick
uses. So thinking of them as diffs mostly works. It's the mostly part that becomes a problem here though.
You are actually doing the right thing. Git is being triggered, as it were, by the fact that the diffs in question are from commit C
to commit A
and then commit C
to commit D
, even though commit C
has yet to be copied. If you set merge.conflictStyle
to diff3
, Git will include, where the conflict occurs, the content from the merge-base commit C
. This will let you see that Git is treating commit A
—or rather, the C
-to-A
change-set—as removing func2
, and meanwhile the C
-to-D
change-set modifies func1
in lines that overlap or abut (in this case, I think "abut") this removal.
Upvotes: 1