I don't understand the reason for the merge conflicts.
The problem appears to be that Git has mis-paired the renamed files.
In fact the problematic file was renamed and the merge conflicts I get are on an old filename. I performed many commits over the past days making updates to the file and renaming it a few times. All were done in my branch. Each time I made updates I committed and pushed it to my branch.
This is about git merge
, but it applies to git rebase
as well
When Git is asked to merge two branch tips—or indeed any pair of commits (HEAD
, the current branch, and some other branch tip commit or any other commit at all), Git will first use git merge-base --all
to identify the merge base(s) between these two tip commits:
...--o--B--o--o--...--H <-- branchA (HEAD)
\
o--o--...--T <-- branchB
It does not matter how many times the file was renamed, nor on which branch(es). What matters is what Git sees when it runs:
git diff --find-renames B H
git diff --find-renames B T
Any file that Git identifies as "the same" in commits B
and H
, or "the same" in commit B
and T
, is decreed to be "the same file", regardless of its actual path name in commits B
, H
, and T
.
Whatever changes Git sees between B
and H
are "our" changes (--ours
). Whatever changes Git sees between B
and T
are "their" changes (--theirs
). Git now tries to combine these changes. Where they overlap, Git complains of a merge conflict.
If Git is mis-identifying some path in B
with the same or a different path in H
or T
, the diff between the version of the file that (Git thinks) is in B
vs the version of that same file (with maybe different path name) is in H
or T
will not be a "good diff", and will clash badly with the other diff. This will produce the merge conflicts you are seeing.
The possible solutions are:
- Rename the file again in a new commit added to
H
and/or T
so that Git doesn't mis-identify the files.
- Use the
-X find-renames=<value>
extended option to git merge
to change the similarity index, to affect which files Git thinks are "the same".
- Allow the merge conflict to happen, but then just construct the correct result by wiping out Git's attempt to merge the file(s), substituting in correct hand-merged file(s) instead.
Note that if some file in B
exists with the same name in H
or T
, Git will (currently) always pair those two files up, even if they're not related. For instance, suppose commit B
had a file named polish
, referring to the country Poland, and both commits H
and T
rename this to polish-as-in-the-country
while one commit, either H
or T
, creates a new file named polish
that refers to shoe polish. Git will identify B
's polish
(country) with the new polish
(shoe) because these are the same name. Plain git diff
can be told to break this association, but git merge
cannot.
Renaming the shoe-polish file to polish-as-in-the-shoes
will cause Git not to see a polish
in both B
and the one tip commit. Now Git will search for a "best match", and find instead polish-as-in-the-country
each time, and know that the same rename was performed in both branches.
A rebase is (like) repeated cherry-picks, and each cherry-pick is a merge
Running git rebase <upstream>
or git rebase --onto <target> <upstream>
tells Git to copy a series of commits. The commits to copy are (essentially) as the documentation says those shown by git log <upstream>..HEAD
.1 The commit after which they get copied is the one given by <target>
, or is <upstream>
if you do not specify a <target>
.
In any case, once Git has the list of commits to copy, it copies them as if by running git cherry-pick
on each one. (Depending on your particular git rebase
command, this may actually run git cherry-pick
, or it may simulate it through git format-patch ... | git am -3
. I have not yet constructed a good example to illustrate when these produce different results—but if you do get a merge conflict, it's because git am -3
fell back to three-way merge, which has the same effect as using git cherry-pick
.)
Annoyingly, while you can control merge results with git merge
(by adding new commits, or supplying -X
options), you have little to no control over each cherry-pick during a rebase. The merge base of any cherry-pick is the parent commit of the commit being picked. The --theirs
commit is the commit being cherry-picked, and the --ours
or HEAD
commit is the commit being built-upon. If the rebase is just starting, that's the <target>
you gave, otherwise it's the commit most recently successfully copied.
In any case, at this point you're down to the one option: merge the file correctly by hand. (Well, that or terminate the rebase attempt entirely, with git rebase --abort
. You could then use git cherry-pick
yourself, repeatedly, and add -X
arguments if appropriate.)
1The documentation has a white lie here. It's almost those commits that Git copies; it is actually those listed by git rev-list --cherry-pick --right-only --no-merges <upstream>...HEAD
. This is the same list, except that it:
- omits any merge commits, and
- omits any commit that has the same
git patch-id
in the upstream target.
Moreover, as is actually documented, --fork-point
changes the starting point from the given <upstream>
argument in some cases, so as to omit even more commits. See Git rebase - commit select in fork-point mode.