Git merge of moved files
I forked a repo and changed the file A. Now file A in the upstream repo has moved to a sub directory. I am trying to merge the upstream repo to my fork.
Git thinks that file A was deleted in its original location and a new one was created in the new sub directory (This could also be because, of the way they moved the file upstream).
I want to get my modifications into this file in the new location with least possible repercussions, w.r.t history of commits and stuff. Can anyone please help me out with ways to achieve this?
2 Solutions collect form web for “Git merge of moved files”
Git always detects renames “after the fact”, by comparing the two trees in question (or one tree and the index, for cases that don’t include
git merge itself). (Linus Torvalds considers this a feature; see this SO question for instance.)
In any case,
git merge will run git’s internal diff with merge detection enabled and the default similarity-index of 50%, unless you configure it otherwise.1 Similarly,
git diff has some defaults which are also configurable. If you run
git diff --find-renames -M50% manually between your merge-base and the upstream, you’re probably fine (but see footnote 1 about configuring).
If git does not detect the rename, you may need to adjust the rename detection thresholds and/or increase the number of files that git should consider. The first of these is the
rename-threshold value in the
-X options (
rename-threshold first appeared in git 1.7.4). See the documentation for details.
1You can set
merge.renameLimit to the number of files to consider in terms of rename detection. If you do not set it, the current default is 1000 files (but the default has changed over time). Also, if you do not set it, merge uses
diff.renameLimit, so you can set just the second of these, and have both diff and merge use both values.
The way file rename detection works is a bit complex, but simple enough to describe by example. Suppose that git is comparing commit
12345 with commit
67890, and in
12345 there are files with pathnames
D; but in
67890 there are pathnames
D. This means path
A is gone but new path
B/gronk has appeared. Git will then remember such paths (up to the rename limit value), and will compare the contents of
12345:A with the contents of
67890:B/gronk. If the files are “sufficiently similar”, git will declare that
12345:A was renamed to
I’m not sure precisely how git decides that a file is 50%, or 75%, or whatever, similar/different. I have seen that similarity index is based on “chunks” rather than lines (even though the usual diff output is line-oriented), though.
You need to use
git mv and not just
mv to move the file.
git takes a snapshot of the content it does not care about the filename (which is stored in an idx file as metadata).
If you simply move the file, git will not “understand” that you want to move it and will treat it as a new file.
You need to revert your changes and then use:
git mv <old path> <new path>
Now in git status you will see the move of the file and not the deletion of one and the creation of a new one.