When I tried to merge a "source branch" into "target branch", git recognizes two files to be conflicting. But both these files correspond to different file paths in the "source branch" and "target branch" (The file has been moved and modified).
To put this in a more concrete manner, I believe you did:
git checkout tgtbranch
git merge srcbranch
and got:
CONFLICT (rename/delete): orig-filename deleted in HEAD and renamed
to new-filename in A. Version A of new-filename left in tree.
Automatic merge failed; fix conflicts and then commit the result.
(Note that I reproduced this conflict with branches named A
and B
instead of srcbranch
and tgtbranch
respectively. Also, the CONFLICT
message was all on one line. I split it into two for posting purposes only.)
I need to get the path of this conflicting file in the "source branch".
As you can see from the above, this information is available in the CONFLICT
message that git merge
prints.
Unfortunately, this information is not available, at least not directly, anywhere afterward. You must save the CONFLICT
message. (See below for two additional alternatives, if you did not save the CONFLICT
message.)
What is available is what git status
prints:
$ git status --short
UA new-filename
$ git status
On branch B
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
added by them: new-filename
no changes added to commit (use "git add" and/or "git commit -a")
We can also use git ls-files --stage
to see what's in Git's index at this point:
$ git ls-files --stage
100644 4466f6b253fc4b67ec2749befdd29b7c1f573e36 0 README
100644 923932af0c5b6ddc43c116c648cd793759949b66 3 new-filename
Only the new file name exists!
What you can do if you did not save the CONFLICT
line
Method 1: repeat the merge
One option, probably the easiest, is to repeat the merge. Of course, if you're working on resolving the merge, you probably don't want to mess with the current index and your work-tree, which makes this a bit tricky. You can:
- clone the repository, so that you have a new clone with a new index and work-tree, or
- use
git worktree add
, provided you have git worktree
and then do the re-merge in the new clone or work-tree. Use git checkout
to check out the commit to which tgtbranch
points in the original clone (if you re-cloned) or current clone (if you are using git worktree
), then run git merge
to merge the commit to which tgtbranch
points in the original clone (if you re-cloned) or current clone (if you are using git worktree
). That is:
$ git rev-parse tgtbranch
<hash>
$ git rev-parse srcbranch
<hash>
$ cd /tmp && git clone <path> reclone && cd reclone
$ git checkout <hash> && git merge <hash>
if you're using the re-clone procedure, or:
$ git worktree add --detach <path> tgtbranch
$ cd <path> && git merge srcbranch
if you are using the git worktree
approach.
This merge will spit out the CONFLICT
line. Save it! Then remove the new clone or added work-tree—its entire purpose was to obtain the CONFLICT
line. Your problem is now solved: you have the original file name.
Method 2: use git diff
against the merge base
The merge operation detected the rename in tgtbranch
using git diff --find-renames
. The threshold for rename-finding was set to whatever you supplied with your -X find-renames=number
argument, or 50% if you did not specify a number.
This git diff
was a diff between two specific commits: the merge base commit, which Git found automatically, and the tip commit of tgtbranch
or srcbranch
(whichever branch Git "saw" the rename in: one tip commit had a rename, the other had a delete, as compared to the merge base).
Find the merge base commit. There is one complication here, but it will show up immediately if the complication is there. Run:
git merge-base --all srcbranch tgtbranch
For instance, in my case, I get:
$ git merge-base --all A B
3a5c9ae0669e9969f8986810ea03c5283e0ac693
If this prints out just one commit hash, you're good. If it prints out more than one, you probably should go to method #1 instead.
Now that you know that there is a single merge base, you can run two git diff --find-renames
commands. Add the --name-status
option to limit the amount of output. You may also want to add --diff-filter=RD
to show only renamed or deleted files. In my case, I left these options out because the rename or delete is all I did:
$ git diff --find-renames --name-status A...B
D orig-filename
$ git diff --find-renames --name-status B...A
R064 orig-filename new-filename
This shows the renamed/deleted file: the original name was orig-filename
and the new name is new-filename
.
This three-dot syntax, A...B
(or in your case tgtname...srcname
and srcname...tgtname
), has one special meaning exclusive to git diff
. In git diff
—but not in other Git commands1—it means Find a merge base between the two specified commits, and use that as the left side of the diff. Use the right-hand commit specifier as the right side of the diff. Since there is only the one merge base, this diffs the merge base against the commit named by the right hand side argument.
1The special three-dot syntax applies to all the usual Git diff engines, including git diff-tree
as well as the user-facing git diff
.
There is a bug in the way this is implemented in many version of Git, in that if there is more than one merge base, the result is not predictable. So make sure there is only one merge base.
Side note
If we inspect the index file directly, in this unmerged state, we find a REUC
record with the original file name:
$ od -c .git/index
0000000 D I R C \0 \0 \0 002 \0 \0 \0 002 ] 264 256 x
0000020 2 270 ) P ] 264 256 x 2 227 221 220 \0 \0 \0 ~
0000040 \0 320 ! a \0 \0 201 244 \0 \0 003 351 \0 \0 003 351
0000060 \0 \0 \0 W D f 366 262 S 374 K g 354 ' I 276
0000100 375 қ ** | 037 W > 6 \0 006 R E A D M E
0000120 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
0000140 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 201 244
0000160 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 222 9 2 257
0000200 \f [ m 334 C 301 026 306 H 315 y 7 Y 224 233 f
0000220 0 \f n e w - f i l e n a m e \0 \0
0000240 \0 \0 \0 \0 T R E E \0 \0 \0 006 \0 - 1
0000260 0 \n R E U C \0 \0 \0 - o r i g - f
0000300 i l e n a m e \0 1 0 0 6 4 4 \0 0
0000320 \0 0 \0 214 354 + 214 330 360 004 N O 355 \b 243 '
0000340 4 331 225 \n W [ 037 026 216 K } 367 ? 373 354 364
0000360 [ ^ 364 b 001 371 6 , 3 370 320
0000373
The index begins with a DIRC
magic number, as it must, then has ordinary index entries for both of my files (I have just the two as shown in the git ls-files --stage
output). But then it has a TREE
record, which we can see near offset 0000240
, followed by a REUC
record in the very next line. This REUC record is an "undo" record that allows git checkout -m
to reproduce the merge conflict. And, as we can see, the undo record actually stores the original file name.
Git really should let us access this information, perhaps through git ls-files
or—more conveniently—through git status
. Alas, it does not.