Como abordar uma mesclagem complicada

25

Aqui está o acordo: entrei para uma nova empresa e fui solicitado a terminar o trabalho em uma filial que não é tocada há quase um ano. Enquanto isso, o ramo principal tem crescido a um ritmo constante. Idealmente, eu gostaria de mesclar todas as alterações do ramo mestre no ramo de recursos e continuar o trabalho a partir daí, mas não tenho muita certeza de como abordar isso.

Como executo essa mesclagem com segurança, preservando alterações importantes nos dois lados da ramificação?

Vlad Spreys
fonte
Obrigado a todos pelo feedback incrível. Eu vou dar uma chance ao git-imerge e, se isso for confuso, usarei uma nova abordagem de ramificação!
11115 Vladimir-Speys
Alguém pode explicar por que não podemos usar git cherry-pickaqui?
Santosh Kumar
1. Orações. 2. rebase. 3. teste. 4. mesclar.
AK_ 17/11/2015
1
Eu prefiro rebasear nesta situação, pois ela será confirmada por confirmação. Também permitirá que você esmague o novo recurso antes de disponibilizar o código mesclado. YMMV.
Stephen

Respostas:

27

Em essência, como combinar dois trechos de código (possivelmente não compatíveis) é um problema de desenvolvimento , não um problema de controle de versão . O comando Git merge pode ajudar nesse processo, mas depende da forma do problema.

Comparar as duas versões com a base primeiro faz mais sentido. Isso lhe dará uma idéia da melhor estratégia para levar isso adiante. Sua abordagem pode ser diferente com base na natureza e sobreposição das alterações em cada ramo.

Imagine o cenário ideal: você descobriria que a ramificação principal e a ramificação de recursos apenas modificaram partes mutuamente exclusivas do código, para que você pudesse apenas confirmar todas as alterações e estar pronto.

Certamente, isso quase certamente não será o caso, mas a questão é: a que distância estará desse cenário ideal? ou seja, quão entrelaçadas são as mudanças?

Além disso, qual era a maturidade do antigo ramo de recursos? Estava em bom estado de funcionamento ou não (ou desconhecido)? Quanto do recurso foi concluído?

Se o código relevante na ramificação principal mudou muito no ano passado, ou o recurso não está em um estado muito maduro, posso considerar criar um novo fork da versão mais recente e incorporar manualmente o recurso antigo novamente. Isso permitirá que você adote uma abordagem incremental para fazê-lo funcionar.

Se você fizer uma mescla bagunçada de muito código e ele não funcionar, será muito difícil depurar. Se a ramificação principal mudou muito no ano passado, podem ser necessárias alterações importantes no design do recurso para que ele funcione. Não seria apropriado fazer essas alterações via "resolver conflitos", pois isso exigiria fazer todas as alterações de uma só vez e esperar que funcionasse. Esse problema seria agravado pela possibilidade de erros no ramo parcialmente finalizado antigo.


fonte
+1 "Posso considerar criar um novo fork da versão mais recente e incorporar manualmente o recurso antigo novamente"
mika
1
A primeira pergunta-chave é: o ramo de recurso antigo é construído? Funciona? Caso contrário, será muito difícil testar sua mesclagem.
Moz
22

Na minha experiência limitada com o git, posso dizer que, às vezes, é mais rápido reiniciar o ramo de recursos novamente se o mestre foi longe demais do ponto de desanexação.

Mesclar duas ramificações sem conhecer o histórico por trás do código (já que você acabou de ingressar no projeto) é realmente difícil, e aposto que mesmo um desenvolvedor que acompanhou o projeto desde o início provavelmente cometeria alguns erros na mesclagem.

É claro que isso faz sentido se o ramo do recurso não for enorme, mas você pode simplesmente manter o ramo do recurso antigo aberto, ramificar novamente do mestre e reintroduzir manualmente as alterações que compõem esse recurso. Eu sei que é a abordagem mais manual, mas permite que você tenha controle total em caso de código ausente ou movido.

Emparelhar a programação com um senior nesse caso seria o melhor cenário, ajudando você a conhecer melhor o código.
Pode até ser mais rápido também, se você levar em conta conflitos de mesclagem e tempo de teste!

Eu meio que assumi que pelo menos tentar fazer uma mesclagem é obviamente a melhor coisa a fazer. Se isso falhar ou for muito difícil, tente a escolha da cereja, se isso der errado, siga o caminho manual.

GavinoGrifoni
fonte
2
Definitivamente não é a abordagem correta.
Andy
12
não, esta é a abordagem correta - se você tem um ramo antigo e não conhece o código, tentar mesclar não será muito bem-sucedido e será muito arriscado. Determinar as alterações que foram originalmente feitas, investigar se elas são relevantes para o novo código e aplicá-las para fazer sentido é a maneira de fazer isso. Essa é uma abordagem manual, mas nessas circunstâncias, a única segura a ser adotada. Obviamente, eu ainda tentaria mesclar primeiro, apenas para ver o que acontece, e verificaria o log para ver quanta alteração ocorreu na ramificação também - poderia ser trivial.
Gbjbaanb
10
@ DavidPacker: Eu não acho que GavianoGrifoni sugere jogar todo o trabalho ao mar. Ele sugere transferir as alterações do ramo antigo manualmente para a linha de desenvolvimento atual, passo a passo. Isso jogará fora a velha história , não mais.
Doc Brown
3
@DavidPacker 1º: o ramo está desatualizado, 2º: o responsável pela conclusão não conhece o código. Dados esses 2 fatores, uma reaplicação manual é a única maneira realista de abordar a tarefa. Ninguém está sugerindo uma cópia-cola simples da revisão de ponta do antigo ramo.
Gbjbaanb
5
@DavidPacker: conflitos de mesclagem podem se tornar maus - se você precisar resolver 500 deles de uma só vez antes de obter o programa em um estado compilável e testável novamente. Esse é o tipo de situação que o OP está esperando aqui. Se você acha que é possível usar o git de maneira eficiente para evitar essa situação de "tudo ou nada", por que você não edita sua resposta e diz ao OP como isso pode ser feito?
Doc Brown
16

O git-imerge foi projetado exatamente para esse fim. É uma ferramenta git que fornece um método para mesclagem incremental . Ao mesclar de forma incremental, você só precisa lidar com as colisões entre duas versões, nunca mais. Além disso, um número muito maior de mesclagens pode ser realizado automaticamente, pois os conjuntos de alterações individuais são menores.

Joe
fonte
6

Tentar mesclar a cabeça da linha principal em um ramo obsoleto de um ano pode ser um exercício de frustrações e aprofundar o dente na mesa com a testa.

A linha principal não chegou ao local de uma só vez ao longo dos meses. Também teve desenvolvimento e lançamentos. Tentar atualizar tudo em uma mesclagem monolítica pode ser esmagador.

Em vez disso, comece mesclando a partir do primeiro recurso novamente para a linha principal após a divisão de ramificação antiga. Faça essa mesclagem funcionar. Em seguida, o próximo recurso será mesclado. E assim por diante. Muitos desses recursos são mesclados sem conflito. Ainda é importante garantir que a funcionalidade atual da ramificação obsoleta permaneça compatível com a direção da linha principal.

Você pode ramificar da cabeça do ramo obsoleto para o papel de mesclar em outras alterações. Trata-se de garantir que o commit e o histórico quando alguém olha para trás sejam claros e comuniquem qual é o papel e a política de cada ramo. O ramo obsoleto era um ramo de recurso. O que você está trabalhando é um ramo de acumulação e reconciliação.

Muito disso será mais fácil se o recurso antigo ou as ramificações de lançamento ainda existirem e forem facilmente acessíveis (alguns lugares têm uma política de limpar os nomes das ramificações mais antigas que em alguma data, para que a lista de ramificações não seja excessiva. )

O importante em tudo isso é garantir que você teste e corrija após mesclar com êxito cada parte do histórico da linha principal no lugar. Mesmo que algo possa se fundir sem conflitos, isso significa apenas que o código não entrou em conflito. Se a maneira como o recurso obsoleto foi acessado foi descontinuada ou removida, pode ser necessário corrigir após a mesclagem bem-sucedida.

Como um aparte, isso também funciona para outros sistemas de controle de versão. Ocasionalmente, precisei mesclar um grupo específico de commits svn em um ramo (cherry cherry) para um recurso, corrigir o branch para trabalhar com esse recurso e mesclar o próximo grupo de commits svn em vez de apenas fazer um svn atacado mesclar.

Embora se possa fazer uma escolha de git aqui, e permitir trazer confirmações específicas , isso tem algumas desvantagens que podem complicar o processo. A seleção de cereja não mostrará informações sobre o commit que você selecionou (você pode anexá-lo à mensagem de confirmação). Isso dificulta o rastreamento dos commits na história.

Além disso, significa que você não reproduzirá efetivamente o mestre no ramo obsoleto - estará escolhendo recursos possivelmente incompletos - e esses recursos poderão ser reproduzidos fora de ordem.

A principal razão pela qual você deve mesclar as confirmações históricas para dominar o ramo obsoleto é poder manter o, vamos chamá-lo de "histórico futuro" do ramo obsoleto em um estado em que você possa raciocinar. Você pode ver claramente as mesclagens do histórico na ramificação obsoleta e as correções para reintegrar a funcionalidade. Os recursos estão sendo adicionados na mesma ordem em que deveriam ser dominados. E quando você terminar, e finalmente fazer a mesclagem do chefe do mestre para o ramo obsoleto, você sabe que tudo foi mesclado e não está perdendo nenhum comprometimento.


fonte
+1 Esta é uma solução alternativa potencial interessante que usa mesclagem para incorporar grandes alterações. No entanto, posso ver uma desvantagem: suponha que você tenha as versões principais ABCDE e deseje incorporar a ramificação de recurso A1 no E. Pode haver muito esforço desperdiçado na fusão do código em BC e D. Por exemplo, e se D a E fosse uma grande mudança de design que tornou irrelevantes as alterações incrementais em B, C e D? Além disso, novamente depende da maturidade do recurso em primeiro lugar. Uma abordagem potencial útil, mas sua adequação precisa ser considerada antes de iniciar.
1

Etapa 1. Aprenda sobre o código, analise sua arquitetura e as alterações que foram feitas nos dois ramos desde o último ancestral comum.

Etapa 2. Se o recurso parecer amplamente independente e abordar principalmente áreas diferentes de código, mesclar, corrigir conflitos, testar, corrigir etc. Caso contrário, vá para a Etapa 3

Etapa 3. Analise as áreas de conflito, entenda o impacto funcional e os motivos em detalhes. Pode haver facilmente conflitos nos requisitos de negócios que vêm à tona aqui. Discuta com os BAs, outros desenvolvedores, conforme apropriado. Experimente a complexidade envolvida na resolução da interferência.

Etapa 4. À luz do acima exposto, decida se deseja mesclar / colher de cerejeira / até colar e colar apenas as partes que não conflitam e reescrevem as peças conflitantes, OU se reescreve todo o recurso do zero .

Brad Thomas
fonte
0

1. Alterne para a ramificação que é usada como uma ramificação principal de desenvolvedor / versão.

Este é o ramo que contém as alterações mais recentes no sistema. Pode ser master, core, dev, isso depende da empresa. No seu caso, provavelmente é masterdiretamente.

git checkout master
git pull

Puxe para garantir que você tenha a versão mais recente do principal ramo de desenvolvimento adquirido.

2. Faça o checkout e puxe o ramo que contém o trabalho que você deve concluir.

Você puxa para garantir que realmente tenha o conteúdo mais recente da ramificação. Ao fazer o check-out diretamente, sem criá-lo localmente primeiro, você garante que não haja o novo conteúdo master(ou o principal ramo de desenvolvimento, respectivamente) nele.

git checkout <name of the obsolete branch>
git pull origin <name of the obsolete branch>

3. Mesclar o ramo principal de desenvolvimento ao ramo obsoleto.

Antes de executar o comando a seguir, verifique se você está digitando git branchou git statusse está na ramificação obsoleta.

git merge master

O git mergecomando tentará mesclar o conteúdo da ramificação especificada, neste casomaster , para a ramificação em que você está atualmente.

A ênfase será tentada . Pode haver conflitos de mesclagem, que precisarão ser resolvidos somente por você e você.

4. Corrija os conflitos de mesclagem, confirme e envie a correção de conflitos

Depois de corrigir o conflito de mesclagem em todos os arquivos onde houver, prepare, confirme e envie a resolução de conflitos para origin.

git add .
git commit -m "fixed the merge conflict from the past year to update the branch"
git push

Você geralmente pode ligar git add . para preparar todos os arquivos para confirmação. Ao lidar com conflitos de mesclagem, você deseja que todos os arquivos necessários sejam atualizados.

Nota adicional

Resolver conflitos de mesclagem pode ser um trabalho tedioso. Especialmente se você é novo em uma empresa. Talvez você ainda não tenha o conhecimento adequado para resolver todos os conflitos de mesclagem.

Tome seu tempo para inspecionar cuidadosamente todos os conflitos que ocorreram e resolvê-los adequadamente, antes de continuar seu trabalho.


Pode acontecer, então, você começa a trabalhar em uma ramificação de um ano, mescla o estado de desenvolvimento atual e não terá nenhum conflito de mesclagem.

Isso acontece quando, embora o sistema tenha mudado muito no ano, ninguém tocou nos arquivos que foram realmente alterados no ramo de um ano.

Andy
fonte
6
# 4 é potencialmente o problema. Se houve muitas alterações no último ano, podem ser necessárias alterações importantes no recurso antigo. Fazer isso por mesclagem exige que você faça grandes alterações no código de uma só vez e espere que funcione, o que não é uma boa prática de desenvolvimento. Além disso, quem sabe qual era o estado do recurso inacabado? Você pode acabar com uma enorme bagunça de código que não funciona, e quem sabe se foi devido a problemas com o original ou a alterações feitas?
David, desde que essa abordagem padrão funcione, tudo bem, e o OP deve tentar isso primeiro. Mas existe definitivamente o risco de haver muitos conflitos de mesclagem na situação descrita para lidar com eles dessa maneira "tudo ou nada".
Doc Brown
@ dan1111 A existência de conflitos de mesclagem é completamente boa, e de fato passar por eles é o caminho a percorrer. Como a filial permaneceu intocada por um ano, você pode ter certeza de que não é nada importante e não afetará grande parte do sistema. Portanto, mesmo que a filial esteja um ano atrasada, você pode obter de 2 a nenhum conflito de mesclagem.
Andy
4
A suposição de que essa ramificação não é importante é injustificada. Poderia ter sido uma mudança fundamental no design que foi abandonada e agora está sendo retomada. Poderia ser qualquer coisa. Você está certo de que pode ser uma questão simples e pode haver poucos ou nenhum conflito - nesse caso, sua resposta estaria correta. Mas essa não é a única possibilidade.
@ dan1111 Se alguém não tiver tocado em um local de recurso em uma ramificação separada por um ano, isso não refletirá muito nas alterações do sistema. Isso vem da minha própria experiência com ramos obsoletos (com mais de 6 meses).
Andy