git merge: aplica alterações ao código que foi movido para um arquivo diferente

132

Estou tentando uma manobra de fusão de git bastante robusta agora. Um problema que me deparo é que fiz algumas alterações em algum código da minha ramificação, mas meu colega mudou esse código para um novo arquivo em sua ramificação. Então, quando o fiz git merge my_branch his_branch, o git não percebeu que o código no novo arquivo era o mesmo que o antigo e, portanto, nenhuma das minhas alterações está lá.

Qual é a maneira mais fácil de aplicar minhas alterações novamente ao código nos novos arquivos. Não terei muitos problemas para descobrir quais confirmações precisam ser reaplicadas (posso apenas usar git log --stat). Mas até onde eu sei, não há como o git reaplicar as alterações nos novos arquivos. A coisa mais fácil que estou vendo agora é reaplicar manualmente as alterações, o que não parece uma boa ideia.

Eu sei que o git reconhece blobs, não arquivos, então certamente deve haver uma maneira de dizer a ele: "aplique essa alteração exata no código desse commit, exceto não onde estava, mas onde está agora neste novo arquivo".

asmeurer
fonte
2
Não é exatamente o mesmo, mas aqui é uma pergunta semelhante com boas anwsers que pode aplicar: stackoverflow.com/questions/2701790/...
Mariano Desanze
4
Nenhuma das respostas descreve por que o git não pode executar mesclagens como esta automaticamente. Eu pensei que era para ser inteligente o suficiente para detectar renomeações e executar a mesclagem apropriada automaticamente?
John
Talvez isso não fosse verdade quando a pergunta foi feita, mas as versões modernas do git (estou usando o 1.9.5) podem mesclar alterações nos arquivos renomeados e movidos. Existe até uma --rename-thresholdopção para ajustar a quantidade de semelhança necessária.
Todd Owen
1
@ToddOwen que funcionará se você estiver fazendo uma recuperação de baunilha de uma ramificação upstream, mas você ainda terá problemas se escolher ou retroceder uma série de alterações que podem não incluir a confirmação que renomeia os arquivos .
precisa
Isso é / foi um problema se você fizer uma nova rebase?
hbogert 27/07/19

Respostas:

132

Eu tive um problema semelhante e resolvi-o reorganizando meu trabalho para corresponder à organização do arquivo de destino.

Diga que você modificou original.txtem seu ramo (o localramo), mas no ramo mestre, original.txtfoi copiado para outro, digamos copy.txt. Esta cópia foi feita em um commit que chamamos de commit CP.

Você deseja aplicar todas as alterações locais, confirmações Ae Babaixo feitas no original.txtnovo arquivo copy.txt.

 ---- X -----CP------ (master)
       \ 
        \--A---B--- (local)

Crie um ramo descartável moveno ponto inicial de suas alterações com git branch move X. Ou seja, coloque o movebranch em commit X, aquele antes dos commit que você deseja mesclar; provavelmente, esse é o commit do qual você se ramificou para implementar suas alterações. Como o usuário @digory doo escreveu abaixo, você pode fazer o git merge-base master localpara encontrar X.

 ---- X (move)-----CP----- (master)
       \ 
        \--A---B--- (local)

Nesta ramificação, emita o seguinte comando de renomeação:

git mv original.txt copy.txt

Isso renomeia o arquivo. Observe que copy.txtainda não existia na sua árvore neste momento.
Confirme sua alteração (chamamos de confirmação MV).

        /--MV (move)
       /
 ---- X -----CP----- (master)
       \ 
        \--A---B--- (local)

Agora você pode refazer o seu trabalho sobre move:

git rebase move local

Isso deve funcionar sem problemas, e suas alterações são aplicadas copy.txtna sua filial local.

        /--MV (move)---A'---B'--- (local)
       /
 ---- X -----CP----- (master)

Agora, você não necessariamente deseja ou precisa ter confirmação MVno histórico da ramificação principal, porque a operação de movimentação pode levar a um conflito com a operação de cópia em commit CPna ramificação principal.

Você só precisa refazer seu trabalho novamente, descartando a operação de movimentação, da seguinte maneira:

git rebase move local --onto CP

... onde CPestá o commit onde copy.txtfoi introduzido no outro ramo. Isso refaz todas as alterações na copy.txtparte superior da CPconfirmação. Agora, seu localramo é exatamente como se você sempre modificasse copy.txte não original.txt, e você pode continuar se fundindo com os outros.

                /--A''---B''-- (local)
               /
 -----X-------CP----- (master)

É importante que as mudanças sejam aplicadas CPou copy.txtnão existiriam e as mudanças seriam aplicadas novamente original.txt.

Espero que isso esteja claro. Essa resposta chega tarde, mas pode ser útil para outra pessoa.

coredump
fonte
2
Isso dá muito trabalho, mas acho que deve funcionar em princípio. Eu acho que você terá mais sorte com mesclagem do que com rebase.
asmeurer
7
Essa solução envolve apenas comandos git básicos, ao contrário da edição de patches ou do uso patch(o que também envolve potencialmente muito trabalho), e é por isso que pensei que poderia ser interessante mostrá-lo. Além disso, observe que levei mais tempo para escrever a resposta do que realmente aplicar as alterações, no meu caso. Em qual etapa você recomendaria usando uma mesclagem? e porque? A única diferença que vejo é que, ao rebater, faço um commit temporário que é descartado posteriormente (commit MV), o que não é possível apenas com mesclagens.
Coredump1 de
1
Com o rebase, você tem uma chance maior de lidar com conflitos de mesclagem, porque lida com cada confirmação, enquanto que com uma mesclagem você lida com tudo de uma só vez, o que significa que algumas mudanças que poderiam ter acontecido com uma rebase seriam inexistentes com uma mesclagem. O que eu faria é mesclar e, em seguida, mover manualmente o arquivo mesclado. Talvez eu tenha entendido mal a ideia da sua resposta.
precisa saber é o seguinte
4
Eu adicionei algumas árvores ASCII para esclarecer a abordagem. Quanto à mesclagem versus rebase nesse caso: o que eu quero fazer é pegar todas as alterações em 'original.txt' no meu ramo e aplicá-las em 'copy.txt' no ramo mestre, porque, por algum motivo, 'original. txt 'foi copiado (e não movido) para' copy.txt 'em algum momento. Após essa cópia, 'original.txt' também pode ter evoluído na ramificação principal. Se eu mesclasse diretamente, minhas alterações locais no original.txt seriam aplicadas ao original.txt modificado na ramificação principal, o que seria difícil de mesclar. Saudações.
Coredump #
1
De qualquer forma, porém, acredito que essa solução funcionará (embora, felizmente, eu não tenha uma situação para testá-la agora), por enquanto, vou marcá-la como resposta.
asmeurer
31

Você sempre pode usar git diff(ou git format-patch) para gerar o patch, editar manualmente os nomes dos arquivos no patch e aplicá-lo com git apply(ou git am).

Além disso, a única maneira de funcionar automaticamente é se a detecção de renomeação do git puder descobrir que os arquivos novos e antigos são a mesma coisa - o que parece que eles não estão realmente no seu caso, apenas uma parte deles. É verdade que o git usa blobs, não arquivos, mas um blob é apenas o conteúdo de um arquivo inteiro, sem o nome do arquivo e os metadados anexados. Portanto, se você tiver um pedaço de código movido entre dois arquivos, eles não são realmente o mesmo blob - o restante do conteúdo do blob é diferente, apenas o pedaço em comum.

Cascabel
fonte
1
Bem, é a melhor resposta até agora. Eu não conseguia descobrir como fazer o git format-patchtrabalho para um commit. Se assim for git format-patch SHA1, ele gera um monte de arquivos de correção para todo o histórico. Mas acho que git show SHA1 > diff.patchtambém funcionará.
asmeurer
1
@asmeurer: use a -1opção O modo normal de operação para o formato de patch é um intervalo de revisões, assim origin/master..master, você pode preparar facilmente uma série de patches.
Cascabel
1
Na verdade, outra nota. git applye git amsão muito exigentes, porque desejam os mesmos números de linha. Mas atualmente estou obtendo sucesso com o patchcomando UNIX .
asmeurer
24

Aqui está uma solução de mesclagem de encontrar um conflito de mesclagem com renomear e editar e resolvê-lo com o mergetool, reconhecendo os 3 arquivos de origem de mesclagem corretos.

  • Após uma mesclagem falhar devido ao 'arquivo excluído' que você percebe que foi renomeado e editado:

    1. Você aborta a mesclagem.
    2. Confirme arquivos renomeados em sua filial.
    3. E mesclar novamente.

Passo a passo:

Crie um arquivo.txt:

$ git init
Initialized empty Git repository in /tmp/git-rename-and-modify-test/.git/

$ echo "A file." > file.txt
$ git add file.txt
$ git commit -am "file.txt added."
[master (root-commit) 401b10d] file.txt added.
 1 file changed, 1 insertion(+)
 create mode 100644 file.txt

Crie uma ramificação onde você editará mais tarde:

$ git branch branch-with-edits
Branch branch-with-edits set up to track local branch master.

Crie a renomeação e edite no mestre:

$ git mv file.txt renamed-and-edited.txt
$ echo "edits on master" >> renamed-and-edited.txt 
$ git commit -am "file.txt + edits -> renamed-and-edited.txt."
[master def790f] file.txt + edits -> renamed-and-edited.txt.
 2 files changed, 2 insertions(+), 1 deletion(-)
 delete mode 100644 file.txt
 create mode 100644 renamed-and-edited.txt

Troque para ramificar e edite também:

$ git checkout branch-with-edits 
Switched to branch 'branch-with-edits'
Your branch is behind 'master' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)
$ 
$ echo "edits on branch" >> file.txt 
$ git commit -am "file.txt edited on branch."
[branch-with-edits 2c4760e] file.txt edited on branch.
 1 file changed, 1 insertion(+)

Tentativa de mesclar mestre:

$ git merge master
CONFLICT (modify/delete): file.txt deleted in master and modified in HEAD. Version HEAD of file.txt left in tree.
Automatic merge failed; fix conflicts and then commit the result.

Observe que o conflito é difícil de resolver - e que os arquivos foram renomeados. Abortar, imite o renomear:

$ git merge --abort
$ git mv file.txt renamed-and-edited.txt
$ git commit -am "Preparing for merge; Human noticed renames files were edited."
[branch-with-edits ca506da] Preparing for merge; Human noticed renames files were edited.
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename file.txt => renamed-and-edited.txt (100%)

Tente mesclar novamente:

$ git merge master
Auto-merging renamed-and-edited.txt
CONFLICT (add/add): Merge conflict in renamed-and-edited.txt
Recorded preimage for 'renamed-and-edited.txt'
Automatic merge failed; fix conflicts and then commit the result.

Ótimo! A mesclagem resulta em um conflito 'normal' que pode ser resolvido com o mergetool:

$ git mergetool
Merging:
renamed-and-edited.txt

Normal merge conflict for 'renamed-and-edited.txt':
  {local}: created file
  {remote}: created file
$ git commit 
Recorded resolution for 'renamed-and-edited.txt'.
[branch-with-edits 2264483] Merge branch 'master' into branch-with-edits
Vincent Scheib
fonte
Interessante. Vou ter que tentar isso na próxima vez que me deparar com esse problema.
asmeurer
1
Nessa solução, parece-me que o primeiro passo, a mesclagem abortada, é útil apenas para descobrir quais arquivos foram renomeados e editados remotamente. Se você os conhece com antecedência. Você pode pular essa etapa e, basicamente, a solução é simplesmente renomear os arquivos manualmente localmente e depois mesclar e resolver os conflitos como de costume.
1
Obrigado. Minha situação era que eu mudei B.txt -> C.txte A.txt -> B.txtcom o git mv, e o git não pôde combinar automaticamente os conflitos de mesclagem corretamente (estava obtendo conflitos de mesclagem entre o antigo B.txte o novo B.txt). Usando esse método, os conflitos de mesclagem estão agora entre os arquivos corretos.
CIB
Isso funciona apenas quando o arquivo inteiro é movido, mas o git geralmente deve detectar essa situação automaticamente. A situação complicada é quando apenas parte de um arquivo é movida.
Robin Green