Skip to content
This page has been auto-translated and may contain errors.View in English

Mesclando branches e resolvendo conflitos ​

docs.scrimba.com

Seu branch de feature está pronto. O toggle funciona, você já commitou as mudanças, e agora você quer que esse trabalho fique na main, onde vive o código do resto da equipe. Na maior parte do tempo, trazer esse trabalho de volta é rápido e silencioso: o Git compara os dois branches, não encontra nada que se sobreponha e incorpora o trabalho sem nenhuma etapa extra. Às vezes, porém, você e um colega de equipe alteraram exatamente as mesmas linhas em branches diferentes, e o Git não consegue adivinhar qual versão você quer manter. Essa situação aparece em quase todo projeto com mais de uma pessoa commitando nele. É uma parte normal e comum do trabalho com branches, e significa que o Git encontrou duas ideias diferentes para as mesmas linhas e quer que você decida qual delas prevalece.

Trazendo um branch de volta com git merge ​

Digamos que você criou o toggle de Fahrenheit em um branch próprio, add-fahrenheit-toggle, e a main não avançou desde que você criou esse branch a partir dela. Mude para a main e execute git merge com o nome do branch que você quer trazer:

bash
$ git switch main
$ git merge add-fahrenheit-toggle
Updating 4f2a891..8c3d1a0
Fast-forward
 src/index.js | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

git merge <branch> sempre mescla o branch indicado no branch em que você está no momento, então mudar para a main primeiro faz diferença. O branch em que você está é sempre o que recebe a mesclagem. Depois que isso é executado, a main passa a ter todos os commits de add-fahrenheit-toggle. O branch de feature em si permanece intacto: você pode continuar trabalhando nele ou apagá-lo agora que o trabalho também vive na main.

Aquela linha Fast-forward na saída vale a pena ser lida com atenção. Ela significa que a main não tinha ganhado novos commits desde que você criou o branch, então o Git não precisou combinar nada: ele apenas avançou o rótulo da main para apontar para o mesmo commit que add-fahrenheit-toggle já apontava. Um fast-forward merge é o Git simplesmente atualizando um rótulo para onde ele já precisava estar, nada mais.

Se a main tivesse recebido outros commits enquanto você trabalhava, digamos que um colega de equipe mesclou uma correção de bug nesse meio-tempo, o Git não consegue mais apenas avançar o rótulo, porque a main e o seu branch seguiram caminhos diferentes desde que se separaram. Em vez disso, ele cria um novo commit que amarra as duas históricas, chamado de merge commit:

bash
$ git switch main
$ git merge add-fahrenheit-toggle
Merge made by the 'ort' strategy.
 src/index.js | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

git log --graph --oneline depois de uma mesclagem assim mostra a bifurcação e a junção: duas linhas de histórico correndo lado a lado, se encontrando de volta no merge commit. Nenhum dos dois resultados exige nada extra de você. O Git decide qual se aplica com base em se os branches divergiram ou não, e de qualquer forma sua feature agora é parte da main.

Os dois resultados acima se baseiam na mesma ideia por trás: o 3-way merge. Para combinar dois branches, o Git compara mais do que apenas as duas pontas deles. Primeiro ele encontra o commit que os dois branches têm em comum como ponto de partida, o merge base, depois olha para três snapshots: a base e a ponta de cada branch. Comparar cada ponta com a base compartilhada diz ao Git exatamente quais linhas cada lado alterou, e é isso que permite que ele combine dois conjuntos de mudanças automaticamente em vez de perguntar a você a cada vez.

Um fast-forward é o caso em que um desses três snapshots acaba sendo redundante: a base e uma das pontas são o mesmo commit, então não há nada a combinar daquele lado, e o Git apenas move o rótulo sem criar nada novo. Um merge commit é o caso geral, em que os dois lados mudaram algo desde a base, e o Git registra um novo snapshot com dois pais, cada um apontando de volta para o histórico de um dos branches. Todo conflito de merge que você encontrar na próxima seção vem dessa mesma comparação de 3 vias descobrindo que os dois lados tocaram as mesmas linhas, sem nenhuma forma de combiná-las por conta própria.

JunoTrazendo um branch de volta com git mergegit merge branch-name traz os commits daquele branch para o branch em que você está no momento, então mude para a main primeiro. A maioria das mesclagens acontece silenciosamente em segundo plano e você segue com o seu dia. Nada muda no branch de feature em si; você pode continuar usando ele ou apagá-lo depois que a main já tiver o trabalho.
JunoTrazendo um branch de volta com git merge Um fast-forward significa que a main não tinha se movido, então o Git só deslizou o rótulo para frente. Quando a main recebe outros commits nesse meio-tempo, a mesclagem cria de fato um merge commit com dois pais. De qualquer forma o comando é o mesmo, git merge branch-name; o Git decide qual tipo de mesclagem se aplica.
JunoTrazendo um branch de volta com git merge Um 3-way merge compara o commit base compartilhado com a ponta de cada branch para descobrir o que mudou de cada lado, e é isso que torna a combinação de dois branches automática na maior parte do tempo. Fast-forward é o caso especial em que a base e uma das pontas já coincidem. Eu ainda me pego parando para entender qual commit conta como base em um grafo mais confuso, então vale desenhar se você não tiver certeza.

Como é um conflito de merge ​

Um conflito acontece quando as mesmas linhas mudam nos dois lados de uma mesclagem, e o Git não tem como saber qual versão você quer. Digamos que um colega de equipe mesclou um spinner de carregamento na main que alterou uma função em src/index.js, e o seu branch add-fahrenheit-toggle alterou a mesma função para adicionar o toggle. Agora a mesclagem para na metade em vez de terminar:

bash
$ git switch main
$ git merge add-fahrenheit-toggle
Auto-merging src/index.js
CONFLICT (content): Merge conflict in src/index.js
Automatic merge failed; fix conflicts and then commit the result.

Isso é um conflito de merge. Abra o arquivo e você vai encontrar o Git marcando exatamente onde as duas versões discordam:

<<<<<<< HEAD
  showLoadingSpinner(true);
=======
  const tempF = celsiusToFahrenheit(tempC);
>>>>>>> add-fahrenheit-toggle

<<<<<<< HEAD marca o início do que está atualmente no seu branch (main, nesse caso). ======= divide as duas versões. >>>>>>> add-fahrenheit-toggle marca o fim da versão do branch que está sendo trazido. Tudo entre os marcadores é o mesmo punhado de linhas, escrito de duas formas diferentes.

Um conflito significa que o Git precisa do seu julgamento; nada está quebrado. O Git encontrou duas mudanças nas mesmas linhas e não tem como adivinhar qual você quer, então ele pausa e pede para você decidir. Edite o arquivo até que ele fique do jeito que você quer, mantendo uma versão, a outra, as duas, ou algo novo que combine as duas, e então apague as linhas dos marcadores:

  showLoadingSpinner(true);
  const tempF = celsiusToFahrenheit(tempC);

Depois que o arquivo estiver do jeito certo, adicione ao stage e faça o commit dele exatamente como faria com qualquer outra mudança:

bash
$ git add src/index.js
$ git commit

Executar git commit sem mensagem aqui abre seu editor com uma mensagem que o Git já preparou, descrevendo a mesclagem. Salve e feche, e a mesclagem estará completa.

Enquanto um conflito está aberto, git status é a primeira coisa que vale a pena checar. Ele lista todos os arquivos que ainda precisam de atenção em "Unmerged paths", então em uma mesclagem maior, com vários arquivos em conflito, você sabe exatamente quantos ainda faltam corrigir:

bash
$ git status
On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
	both modified:   src/index.js

git add avisa o Git que o conflito de um arquivo foi resolvido. Ele não verifica se você realmente removeu os marcadores, então leia o arquivo antes de colocá-lo no stage. Se a mesclagem parecer mais confusa do que você esperava, ou você escolheu o branch errado, git merge --abort desfaz tudo por completo e devolve seu diretório de trabalho ao estado de antes de você executar git merge, sem nada pela metade.

Conflitos não são exclusivos do git merge. Rebase é a outra forma de atualizar uma linha de histórico com base em outra, e ele passa pelo mesmo tipo de choque, mas com um resultado diferente depois de resolvido. Um merge amarra dois históricos com um merge commit que tem dois pais, preservando o fato de que um branch existiu e exatamente quando ele se reuniu de volta. Um rebase, em vez disso, reaplica os commits do seu branch, um por um, sobre a ponta do outro branch, produzindo uma linha reta de histórico sem merge commit e sem nenhum vestígio de que os dois já divergiram:

bash
$ git switch add-fahrenheit-toggle
$ git rebase main

O trade-off é real. Um histórico linear vindo de um rebase fica limpo no git log, um commit após o outro, o que algumas equipes preferem por dar uma história mais organizada. Um merge preserva a forma real do que aconteceu, incluindo o ponto exato em que um branch de feature se reuniu à main, o que algumas equipes preferem pela precisão. Nenhuma das duas escolhas está errada; escolha uma convenção por equipe e mantenha a consistência.

Uma regra vale independentemente da convenção escolhida: nunca faça rebase de um branch que outras pessoas já puxaram ou sobre o qual já construíram trabalho. O rebase reescreve commits em novos commits com novos IDs, e se o histórico de um branch compartilhado muda debaixo dos pés de um colaborador, a cópia local dele e a versão remota reescrita deixam de coincidir, forçando uma correção manual do lado dele. Faça rebase livremente no seu próprio branch antes que qualquer outra pessoa o tenha puxado. Depois que ele for compartilhado, use merge.

JunoComo é um conflito de merge Um conflito de merge significa que dois branches alteraram as mesmas linhas, e o Git precisa que você escolha o resultado. Abra o arquivo, procure por <<<<<<<, =======, e >>>>>>>, edite até ficar do jeito que você quer e depois apague essas linhas de marcadores. Termine com git add e git commit. Meu primeiro conflito pareceu uma emergência; no fim, era só o Git me fazendo uma pergunta bem comum.
JunoComo é um conflito de mergegit status lista todos os arquivos não resolvidos em unmerged paths, útil no momento em que um conflito toca mais de um arquivo. git add marca um arquivo como resolvido sem checar seu trabalho, então leia antes de colocá-lo no stage. Se uma mesclagem sair do controle, git merge --abort te leva de volta a um estado limpo, sem nada pela metade.
JunoComo é um conflito de merge O rebase encontra os mesmos choques que o merge; ele reaplica commits sobre uma nova base em vez de amarrar históricos, trocando uma forma preservada do branch por uma linha reta. Guarde essa troca só para branches que apenas você está usando. No momento em que um branch é compartilhado, reescrever seus commits com rebase complica a vida de quem já o puxou, então use merge nesse caso.

Juntando tudo de volta ​

Mesclar é o que faz o trabalho com branches valer a pena: você experimenta com segurança na sua própria linha de histórico, tema coberto em Branches, e depois incorpora esse trabalho de volta à main quando ele estiver pronto, conflitos e tudo. No GitHub, a mesma operação geralmente acontece por meio de um pull request em vez de um git merge local na sua máquina; o capítulo fluxo de pull request cobre como propor, revisar e mesclar uma mudança em um projeto compartilhado. Se uma mesclagem algum dia acabar em um lugar que você não pretendia depois de já ter commitado, desfazendo coisas cobre como recuar com segurança.