Entenda as diferenças entre os tipos de merge no Git: Fast-forward X Three-way

rsantanarj

Rodrigo Santana

Posted on December 29, 2021

Entenda as diferenças entre os tipos de merge no Git: Fast-forward X Three-way

Pré-requisitos

Como todo sistema de controle de versão (VCS) o Git dispõe de recursos de criação e união de branches. Para mesclar uma branch na outra, você pode considerar merge ou rebase. Nesse artigo será apresentado o que é um merge, as estratégias envolvidas fast-forward e three-way e o que são conflitos e quando podem ocorrer.

O que é um merge?

Merge é uma opção muito comum e útil em repositórios que possuem mais de uma branch além da principal. Se é necessário fundir uma branch em outra, o merge pode ser uma opção.

Vale lembrar que uma branch no Git, é um ponteiro para um commit, que será a última versão de tal ramificação. Por exemplo, considere a sequência, a qual inicialmente existe um commit inicial "first commit" na branch principal.

Merge etapa 1

Em seguida, um novo commit "setup" é realizado.

Merge etapa 2

Após isso, um contribuidor João cria uma nova branch "login" a partir do commit setup. Isso significa que neste momento ambas as branches apontam para a mesma versão do repositório e que possuem o mesmo histórico, porém são dois ramos totalmente independentes. Qualquer nova versão em uma branch não afetará a outra e vice-versa. Observe o resultado de um novo commit "creating login feature" na branch "login".

Merge etapa 3

Note que o rótulo da branch login passou a referenciar este último commit, mas o rótulo da branch master ainda está apontando para o commit "setup". Ou seja, a branch login está a um commit à frente em relação a branch master.

E se João desejar integrar os commits feitos na branch login na branch master, como fazer isso? Uma das opções, é através de merge.

Merge fast-forward

João decide integrar a branch login na branch master. Para isso ele retorna para a branch master com o comando abaixo:

git checkout master

Fast-forward etapa 1

Em seguida executa o comando:

git merge login

Fast-forward etapa 2

Note que nessa situação, a única tarefa necessária que o Git precisou realizar foi mover o rótulo da branch master para o commit "creating login feature". Por isso, o nome dessa estratégia é fast-forward (em português avanço rápido), pois é simplesmente uma alteração de ponteiro.

Merge three-way

Agora considere que antes de João integrar a branch login na branch master, uma outra contribuidora Maria promoveu um novo commit "creating user feature" na branch master.

Three-way etapa 1

Nesse caso, uma tentativa de merge não terá o mesmo resultado do exemplo anterior. Isso porque um commit foi realizado na branch master após a criação da branch login. Então não basta mover o ponteiro, há duas versões que precisam ser combinadas, que são o "creating user feature", commit que só existe na branch master e "creating login feature", commit que só existe na branch login. Veja a seguir o resultado de um merge nessa situação.

Three-way etapa 2

Observe que foi gerado um novo commit "Merge". Tal commit foi gerado de forma automática pelo Git, que uniu as alterações envolvidas nos dois ramos em uma nova versão do repositório. Veja também que este novo commit possui dois pais e o histórico do repositório não é mais linear. A estrutura que representa o histórico linear ou bifurcado no Git pode ser definida como DAG (Directed Acyclic Graph ou Grafo Acíclico Dirigido).

Conflitos

No exemplo anterior, ao realizar um merge com a estratégia three-way, o Git conseguiu gerar um commit de merge automaticamente, pois as alterações que precisavam ser combinadas não conflitavam. Entretanto, as duas branches poderiam ter alterado as mesmas linhas de um mesmo arquivo e nesse caso, o Git não pode determinar automaticamente o que está correto e é necessário decisões humanas.

Os conflitos afetam apenas o contribuidor que conduz a mesclagem, o resto da equipe não tem conhecimento do conflito. O Git marcará o arquivo como conflitante e interromperá o processo de fusão. É responsabilidade desse contribuidor resolver o conflito.

Por exemplo, imagine que Joao além de ter criado um novo recurso login, tenha alterado a linha 4 de um arquivo global do sistema. Maria também, além de ter criado o novo recurso de usuários, alterou a linha 4 desse mesmo arquivo. Qual alteração deve prevalecer no commit de merge, a alteração de Maria? Ou a alteração de João? O Git não pode determinar isso de forma automática, João que realizou a mesclagem que irá avaliar. Talvez a alteração de João seja melhor, como ele também pode considerar que a alteração de Maria faça mais sentido ou ainda, a alteração de ambos se complete.

Vale destacar que conflitos só surgem em mesclagens com a estratégia three-way. Merge fast-forward nunca irá gerar um conflito, pois não há histórico divergente a ser combinado. É somente mudança de ponteiro.

No fast-forward

Considere novamente a seguinte situação:

No fast-forward etapa 1

Neste caso, quando foi executado um merge da branch login em direção a branch master, o Git assumiu a estratégia fast-forward e moveu o rótulo da branch master para o commit "creating login feature". Porém, é possível forçar a utilização de uma estratégia three-way, fazendo que o Git crie um commit de merge. Para isso basta incluir a opção --no-ff (no fast-forward) no comando merge:

git merge --no-ff login

No fast-forward etapa 2

Note que esta era uma situação típica de fast-forward, porém com a opção --no-ff o Git foi forçado a gerar um commit de merge. Isso pode ser útil, ocasionalmente, caso você precise marcar pontos de mesclagem no histórico. Você só deve ter em mente que um histórico com muitos commits de merge pode torná-lo confuso e de difícil compreensão. Já em situações que o merge seria pela estratégia three-way, você não pode forçar um fast-forward.

Merge sem checkout, é possível?

Nos exemplos vistos, antes de uma ação de merge, foi realizado um checkout na branch master. Em todos os casos o objetivo era um só, incluir os commits exclusivos da branch login na branch master, que era a branch corrente com o checkout.

Neste contexto, uma pergunta que pode surgir é sobre a possibilidade de fazer uma mesclagem entre duas branches, que não são a branch corrente. A resposta para esta dúvida pode ser respondida pelo texto a seguir, retirado diretamente da fonte:

"...Você não pode mesclar uma branch B na branch A sem fazer o checkout de A primeiro, caso isso resulte em uma mesclagem sem fast-forward (three-way). Isso ocorre porque uma cópia de trabalho é necessária para resolver quaisquer conflitos em potencial. No entanto, no caso de mesclagens fast-forward, isso é possível , porque tais mesclagens nunca podem resultar em conflitos, por definição..."

Conclusão

Como vimos, um merge pode ser executado de duas formas: por estratégia fast-forward ou three-way. A primeira, quando possível, será a melhor opção pois mantém um histórico linear e está livre de resolução de conflitos, pois envolve somente alteração de referência. Já a segunda, além de gerar um histórico bifurcado, com commits adicionais de mesclagem, ainda está suscetível a resolução de conflitos. Esses commits adicionais podem prejudicar a leitura do histórico. Ao menos que você utilize um --no-ff, o Git irá definir a estratégia de merge de acordo o histórico das duas branches envolvidas na mesclagem.

Quer aprender mais sobre esta ferramenta essencial para desenvolvedores de software? Aprenda no mais novo curso de Git - Básico ao avançado

💖 💪 🙅 🚩
rsantanarj
Rodrigo Santana

Posted on December 29, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related