James Wright
James Wright

Reputation: 1425

Squashing previous conflicts due to irrelevant "changes"

Sometimes when I squash a latter commit onto an early commit, I'll get unexpected conflicts.

The Setup

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]

The Problem

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)

The Question

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.

The Implicit Question:

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

Answers (1)

torek
torek

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

Related Questions