Fast-forward vs Three-way Merge

1. Conceitos Fundamentais sobre Merge no Git

O merge (fusão) é uma operação fundamental no Git que permite integrar alterações de diferentes branches em um único branch unificado. Ele existe porque o desenvolvimento paralelo é uma realidade em projetos de software: diferentes pessoas ou equipes trabalham em funcionalidades distintas, correções de bugs ou experimentos ao mesmo tempo. Sem o merge, seria impossível consolidar essas contribuições de forma organizada.

Para que um merge seja possível, o Git precisa encontrar um ancestral comum entre os dois branches — o commit a partir do qual ambos divergiram. Esse ancestral serve como base para calcular as diferenças. O merge pode ser automático (quando não há conflitos de código) ou manual (quando o Git não consegue resolver automaticamente alterações concorrentes no mesmo arquivo, exigindo intervenção do desenvolvedor).

A diferença fundamental entre os dois tipos de merge que exploraremos aqui está no formato do histórico resultante e nas condições que os desencadeiam.

2. Fast-forward Merge: O Caminho Retilíneo

O fast-forward merge ocorre quando o branch de destino (ex: main) não teve nenhum novo commit desde que o branch de origem (ex: feature) foi criado. Nesse cenário, o Git simplesmente move o ponteiro do branch de destino para frente, apontando para o commit mais recente do branch de origem. O resultado é um histórico linear, sem qualquer commit de merge.

Exemplo prático

Suponha que você criou um branch feature a partir de main, fez dois commits e, enquanto isso, ninguém mexeu em main:

# Estado inicial
main: A --- B
feature: A --- B --- C --- D

# Comando
git checkout main
git merge feature

# Resultado (fast-forward)
main: A --- B --- C --- D
feature: A --- B --- C --- D

O Git moveu main para o commit D. Não há commit extra. Esse é o comportamento padrão quando você executa git merge sem flags.

3. Three-way Merge: O Caminho com Junção

O three-way merge acontece quando os dois branches divergiram — ou seja, ambos receberam novos commits após o ponto de divergência. O Git precisa criar um commit de merge especial, que possui dois pais, representando a junção das duas linhas de desenvolvimento.

Exemplo prático

Considere que main recebeu um commit E enquanto você trabalhava em feature:

# Estado inicial
main: A --- B --- E
feature: A --- B --- C --- D

# Comando
git checkout main
git merge feature

# Resultado (three-way, com commit de merge M)
main: A --- B --- E --- M
                    \         /
feature:              C --- D

O commit M tem dois pais: E (o último de main) e D (o último de feature). Esse nó de junção é fundamental para rastrear de onde vieram as alterações.

4. Comparação Visual: Fast-forward vs Three-way

Fast-forward (histórico linear)

* commit D (HEAD -> main, feature)
* commit C
* commit B
* commit A

Visualmente, é uma linha reta. Fácil de ler, mas perde a informação de que houve um branch separado.

Three-way (histórico ramificado)

*   commit M (HEAD -> main)
|\
| * commit D (feature)
| * commit C
* | commit E
|/
* commit B
* commit A

O commit M com dois pais revela claramente onde houve uma integração. A desvantagem é que o gráfico fica mais complexo.

Vantagens do fast-forward: simplicidade, histórico limpo, facilidade para navegar.
Vantagens do three-way: rastreabilidade explícita de branches e merges, útil para auditoria.

5. Quando o Git Escolhe Automaticamente Cada Tipo

O Git decide o tipo de merge baseado no ancestral comum e no estado atual dos branches:

  • Cenário fast-forward: Se o branch base (main) não contém commits que não estejam no branch de origem (feature), ou seja, main é um ancestral direto de feature. O Git move o ponteiro.
  • Cenário three-way: Se ambos os branches possuem commits que o outro não tem. O Git precisa criar um commit de merge com dois pais.
# Fast-forward: main é ancestral de feature
main: A --- B
feature: A --- B --- C

# Three-way: ambos divergiram
main: A --- B --- D
feature: A --- B --- C

6. Forçando Comportamentos com Flags

O Git oferece flags para controlar o comportamento do merge:

--ff-only

Força que o merge só ocorra se for possível fazer fast-forward. Caso contrário, o comando aborta com erro:

git merge --ff-only feature
# Se não for fast-forward: fatal: Not possible to fast-forward, aborting.

Útil em workflows que prezam por histórico linear (como Git Rebase).

--no-ff

Força a criação de um commit de merge, mesmo quando o fast-forward seria possível:

git checkout main
git merge --no-ff feature

# Mesmo que main estivesse em A e feature em C, o resultado seria:
main: A --- M
        \   /
feature:  C

Isso preserva a informação de que houve um branch.

--squash

Uma alternativa que não gera merge nem mantém o histórico completo do branch de origem:

git checkout main
git merge --squash feature
git commit -m "Implementa feature X"

Todos os commits do branch feature são achatados em um único commit, sem vínculo com o branch original.

7. Boas Práticas e Cenários Reais

Quando preferir fast-forward

  • Projetos pequenos ou individuais: O histórico linear simplifica a navegação.
  • Branches de curta duração: Quando uma feature é concluída rapidamente, sem commits concorrentes em main.
  • Workflows baseados em rebase: Como o Git Flow simplificado, onde se usa git rebase antes do merge.

Quando preferir three-way

  • Equipes grandes: A rastreabilidade é importante para saber quem integrou o quê e quando.
  • Branches de longa duração: Features que levam semanas precisam de um commit de merge explícito.
  • Auditoria e compliance: O commit de merge documenta a integração oficial.

Estratégias híbridas

Muitas equipes adotam uma abordagem mista: usam git merge --no-ff ao finalizar branches de feature, mas permitem fast-forward para branches de correção rápida (hotfix). Outra prática comum é usar git merge --ff-only em branches de release, garantindo que nunca haja merges acidentais.

# Exemplo de workflow híbrido
# Feature branch: sempre --no-ff
git checkout main
git merge --no-ff feature-x

# Hotfix: fast-forward se possível
git checkout main
git merge --ff-only hotfix-urgente

A escolha entre fast-forward e three-way merge não é técnica, mas sim de estratégia de equipe. Ambos são válidos; o importante é documentar a convenção adotada e usá-la consistentemente.

Referências