What configuration/option is required for Git to apply changes to all copies when merging?

2

Git (at least as of 2.11.1) does not track copies/renames/moves in the repository. However, it can be told to analyze commits afterwards but it seems this only works correctly with log, not merge commands. When working with branches, it may be necessary to apply changes which have been made to an original file to all copies, so it would make sense that a merge would be able to analyze all commits between two revisions in the same way log is capable of doing it. Unfortunately, I cannot find the correct parameters/configuration options to make this work.

See this example:

# prepare repository
git init git-copies
cd git-copies
git config --local --add log.follow true

# add initial file
echo first >initial.txt
git add initial.txt
git commit -m 'initial'

# create new branch
git checkout -b copies

# commit first copy
cp initial.txt copy1.txt
git add copy1.txt
git commit -m 'copy 1'

# commit second copy
cp initial.txt copy2.txt
git add copy2.txt
git commit -m 'copy 2'

# rename first copy
mv copy1.txt copy1-renamed.txt
git add copy1-renamed.txt copy1.txt
# at this point git status would show an informational client-local status "renamed" (not recorded in repo AFAIK)
git commit -m 'renamed copy 1'

# create further copies of previously copied files
cp copy1-renamed.txt copy1-renamed-copy1.txt 
cp copy1-renamed.txt copy1-renamed-copy2.txt
cp copy2.txt copy2-copy1.txt
cp copy2.txt copy2-copy2.txt
git add copy1-renamed-copy1.txt copy1-renamed-copy2.txt copy2-copy1.txt copy2-copy2.txt
git commit -m 'copies on second level'

# modify each second level second copy
echo 'altered 1' >copy1-renamed-copy2.txt
echo 'altered 2' >copy2-copy2.txt
git add copy1-renamed-copy2.txt copy2-copy2.txt
git commit -m 'altered second level second copies'

# rename the initial file (omit for some extra fun, see below)
mv initial.txt initial-renamed.txt
git add initial.txt initial-renamed.txt
git commit -m 'renamed initial file'

# create a new copy of the renamed initial file (omit for some extra fun, see below)
cp initial-renamed.txt initial-renamed-copy.txt
git add initial-renamed-copy.txt
git commit -m 'copied renamed initial file'

# switch back to master branch and alter initial file
git checkout master
echo changed >initial.txt
git add initial.txt
git commit -m 'changed initial file'

# switch to copies branch again
git checkout copies

Check what git recognized as copies:

for file in *.txt; do echo " -- $file"; git --no-pager log --oneline $file; echo; done

  -- copy1-renamed-copy1.txt
71ad85b copies on second level
c9266f9 renamed copy 1
d3a0006 copy 1
a7ff313 initial

  -- copy1-renamed-copy2.txt
f5e9a0e altered second level second copies
71ad85b copies on second level
c9266f9 renamed copy 1
d3a0006 copy 1
a7ff313 initial

  -- copy1-renamed.txt
c9266f9 renamed copy 1
d3a0006 copy 1
a7ff313 initial

  -- copy2-copy1.txt
71ad85b copies on second level
c9266f9 renamed copy 1
d3a0006 copy 1
a7ff313 initial

  -- copy2-copy2.txt
f5e9a0e altered second level second copies
71ad85b copies on second level
c9266f9 renamed copy 1
d3a0006 copy 1
a7ff313 initial

  -- copy2.txt
f66887f copy 2
d3a0006 copy 1
a7ff313 initial

  -- initial-renamed-copy.txt
68a2e29 copied renamed initial file
71ad85b copies on second level
c9266f9 renamed copy 1
d3a0006 copy 1
a7ff313 initial

  -- initial-renamed.txt
0d89d9b renamed initial file
a7ff313 initial

While Git fails to distinguish verbatim copies between first and second level from the initial file (as is expected) it confuses initial-renamed-copy.txt with being a copy of a second level file it does indeed recognize that all files have the initially committed file as a common ancestor. So basically, I would expect a merge to apply changes to all files and issueing a merge conflict on the second-level copies we modified later on.

Let's merge:

git merge --no-commit master
git status --short

Result: M copy1-renamed-copy1.txt

Huh? That's unexpected somehow... Let's reset and try again with some options which (as I understand the documentation) should be able to help:

git reset --hard
git merge --no-commit -s recursive -X patience -X find-renames master
git status --short

Result: M copy1-renamed-copy1.txt

Ehm... Let's try to set renameLimit config parameters to some high values, maybe that's the issue?

git config --local --add diff.renameLimit 999999
git config --local --add merge.renameLimit 999999
git reset --hard
git merge --no-commit -s recursive -X patience -X find-renames master
git status --short

Result: M copy1-renamed-copy1.txt

Sadly not...

By the way, renaming initial.txt seems to have confused Git a lot. If you omit the two commits I marked above, we will still only get the changes applied to one file but at least the change will be applied to initial.txt. It appears that it will just pick one file at random (but reproduceable).

What am I doing wrong?

Energiequant

Posted 2017-02-20T19:01:13.213

Reputation: 121

No answers