O Git (pelo menos a partir de 2.11.1) não rastreia cópias / renomeações / movimentações no repositório. No entanto, ele pode ser avisado para analisar commits depois, mas parece que isso só funciona corretamente com comandos log, e não merge. Ao trabalhar com ramificações, pode ser necessário aplicar as alterações feitas a um arquivo original em todas as cópias, portanto, faria sentido que uma mesclagem pudesse analisar todas as confirmações entre duas revisões da mesma maneira que o log é capaz de fazer. Fazendo. Infelizmente, não consigo encontrar os parâmetros corretos / opções de configuração para fazer isso funcionar.
Veja este exemplo:
# 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
Verifique o que o git reconheceu como cópias:
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
Enquanto o Git não consegue distinguir cópias íntegras entre o primeiro e o segundo nível do arquivo inicial (como é esperado) confunde initial-renamed-copy.txt
com ser uma cópia de um arquivo de segundo nível ele realmente reconhece que todos os arquivos têm o arquivo inicialmente confirmado como um ancestral comum. Então, basicamente, eu esperaria uma mesclagem para aplicar alterações a todos os arquivos e emitir um conflito de mesclagem nas cópias de segundo nível que modificamos mais tarde.
Vamos mesclar:
git merge --no-commit master
git status --short
Resultado: M copy1-renamed-copy1.txt
Huh? Isso é inesperado de alguma forma ... Vamos redefinir e tentar novamente com algumas opções que (como eu entendo a documentação ) devem ser capaz de ajudar:
git reset --hard
git merge --no-commit -s recursive -X patience -X find-renames master
git status --short
Resultado: M copy1-renamed-copy1.txt
Ehm ... Vamos tentar configurar os parâmetros de configuração renameLimit para alguns valores altos, talvez esse seja o problema?
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
Resultado: M copy1-renamed-copy1.txt
Infelizmente não ...
A propósito, renomear initial.txt
parece ter confundido muito o Git. Se você omitir os dois commits que marquei acima, continuaremos recebendo apenas as alterações aplicadas a um arquivo, mas pelo menos a alteração será aplicada a initial.txt
. Parece que ele selecionará apenas um arquivo aleatoriamente (mas reproduzível).
O que estou fazendo de errado?