git merge: apply changes to code that moved to a different file
Solution 1:
I had a similar issue, and I resolved it by rebasing my work to match the target file organization. This works because git keep tracks of files content, so by rebasing on top of a rename, the changes are applied as necessary.
More precisely, let's say that you modified original.txt
on your branch (the local
branch), but on the master branch, original.txt
has been copied to another one, say copy.txt
.
This copy has been done in a commit that we name commit CP
.
You want to apply all your local changes, commits A
and B
below, that were made on original.txt
, to the new file copy.txt
.
---- X -----CP------ (master)
\
`--A---B--- (local)
Create a throwaway branch move
at the starting point of your changes with git branch move X
. That is to say, put the move
branch at commit X
, the one before the commits you want to merge; most likely, this is the commit from which you branched out to implement your changes. As user @digory doo wrote below, you can do git merge-base master local
to find X
.
---- X (move)-----CP----- (master)
\
`--A---B--- (local)
On this branch, issue the following renaming command:
git mv original.txt copy.txt
This renames the file. Note that copy.txt
did not yet exist in your tree at this point.
Commit your change (we name this commit MV
).
,--MV (move)
/
---- X -----CP----- (master)
\
`--A---B--- (local)
You can now rebase your work on top of move
:
git rebase move local
This should work without problem, and your changes are applied to copy.txt
in your local branch.
,--MV (move)---A'---B'--- (local)
/
---- X -----CP----- (master)
Now, you do not necessarily want or need to have commit MV
in your main branch's history, because the move operation may lead to a conflict with the copy operation at commit CP
in the main branch.
You only have to rebase your work again, discarding the move operation, as follows:
git rebase move local --onto CP
... where CP
is the commit where copy.txt
was introduced in the other branch.
This rebases all the changes on copy.txt
on top of the CP
commit.
Now, your local
branch is exactly as if you always modified copy.txt
and not original.txt
, and you can continue merging with others.
,--A''---B''-- (local)
/
-----X-------CP----- (master)
It is important that the changes are applied on CP
or otherwise copy.txt
would not exist and the changes would be applied back on original.txt
.
Hope this is clear. This answer comes late, but this may be useful to someone else.
Solution 2:
You can always use git diff
(or git format-patch
) to generate the patch, then go manually edit the filenames in the patch, and apply it with git apply
(or git am
).
Short of this, the only way it's going to work automatically is if git's rename detection can figure out that the old and new files are the same thing - which it sounds like they aren't really in your case, just a chunk of them. It's true that git uses blobs, not files, but a blob is just the contents of an entire file, without the filename and metadata attached. So if you have a chunk of code moved between two files, they aren't really the same blob - the rest of the blob's content is different, just the chunk in common.