Rebase --rebase-merges: preservando estrutura de merges ao reescrever
1. O problema do rebase tradicional com merges
1.1. Comportamento padrão do git rebase ao encontrar commits de merge
O git rebase tradicional, quando encontra um commit de merge durante a reescrita do histórico, simplesmente o ignora e lineariza os commits que o compõem. Considere o seguinte cenário:
* Merge branch 'feature' (commit M)
|\
| * Commit C2 (feature)
|/
* Commit B1 (main)
* Commit A1 (main)
Ao executar git rebase main a partir da branch feature, o Git descarta o merge M e aplica apenas C2 sobre main:
* Commit C2 (feature)
* Commit B1 (main)
* Commit A1 (main)
1.2. Perda da topologia do histórico: linearização forçada
A linearização forçada apaga a informação de que C2 foi integrado através de um merge. Isso pode ser aceitável em históricos simples, mas torna-se problemático quando:
- A branch
featurecontém merges internos de sub-branches - O merge resolveu conflitos complexos que precisam ser preservados
- A equipe depende da topologia para entender o fluxo de trabalho
1.3. Conflitos repetidos e reaplicação manual de merges
Sem o --rebase-merges, conflitos resolvidos durante o merge original precisam ser resolvidos novamente. O Git não guarda o resultado da resolução de conflitos do merge — ele apenas armazena o snapshot final. Ao linearizar, cada commit conflitante é reaplicado individualmente, forçando o desenvolvedor a refazer o trabalho manual.
2. Introdução ao --rebase-merges
2.1. Origem da flag e motivação (Git 2.18+)
Introduzida no Git 2.18 (junho de 2018), a flag --rebase-merges foi criada para resolver a limitação histórica do rebase com merges. A motivação veio de equipes que usam Git Flow ou fluxos similares, onde branches de feature frequentemente fazem merges de sub-branches ou de main durante o desenvolvimento.
2.2. Diferença conceitual: reencenar vs. linearizar
Enquanto o rebase tradicional lineariza o histórico (descarta merges), o --rebase-merges reencena a estrutura de merges. Ele refaz cada merge no novo contexto, preservando a topologia original.
2.3. Casos de uso ideais: branches de feature com merges internos
O cenário ideal é quando uma branch de feature faz merges regulares de main para se manter atualizada, ou quando sub-equipes integram trabalho em branches intermediárias antes do merge final.
3. Mecanismo interno: como o Git reencena merges
3.1. O papel do sequencer e da reordenação de commits
O Git utiliza seu sequencer interno (o mesmo do rebase -i) para orquestrar a reaplicação. Em vez de simplesmente aplicar patches, ele gera uma lista de operações que inclui comandos especiais para recriar merges.
3.2. Preservação da estrutura de pais (parents) dos merges
Cada merge reencenado mantém seus dois pais (ou mais, em octopus merges). O Git recalcula qual commit corresponde a cada pai no novo contexto, preservando a relação estrutural.
3.3. Reaplicação de conflitos resolvidos vs. novos conflitos
Se o merge original resolveu conflitos, o Git tenta reaplicar a mesma resolução usando a estratégia recursive com a opção ours para o merge original. Isso nem sempre funciona perfeitamente, mas reduz drasticamente o retrabalho.
4. Sintaxe e opções complementares
4.1. Uso básico: git rebase --rebase-merges <base>
# Rebaseia a branch atual sobre main, preservando merges
git checkout feature
git rebase --rebase-merges main
4.2. Combinando com --onto e --interactive
# Rebaseia apenas commits de feature-branch para nova base
git rebase --rebase-merges --onto new-base old-base feature-branch
# Modo interativo para editar a lista de operações
git rebase --rebase-merges -i main
4.3. A flag --no-rebase-merges para desabilitar seletivamente
Em configurações globais ou por repositório, é possível desabilitar o comportamento:
# Desabilita globalmente (volta ao comportamento antigo)
git config --global rebase.rebaseMerges false
# Uso explícito para desabilitar em um comando específico
git rebase --no-rebase-merges main
5. Trabalhando com o editor interativo (-i)
5.1. Estrutura do TODO list com merges explícitos
Ao usar -i com --rebase-merges, o TODO list inclui comandos especiais:
label onto
# Branch feature
reset [newbase]
pick a1b2c3 Commit A
label branch-a
# Branch sub-feature
reset [newbase]
pick d4e5f6 Commit B
label branch-b
merge -C m1n2o3 branch-a # Merge 'Merge branch-a into sub-feature'
# Merge final
reset [newbase]
pick g7h8i9 Commit C
merge -C m4n5o6 branch-b # Merge 'Merge sub-feature into feature'
5.2. Comandos especiais: label, reset, merge no rebase interativo
label <nome>: marca um ponto no histórico para referência futurareset [<label>]: move o HEAD para um label específicomerge [-C <commit>] <label>: recria um merge, usando a mensagem e resolução de conflitos do commit original (-C) ou permitindo nova mensagem (-c)
5.3. Reordenando e editando merges sem quebrar a árvore
É possível reordenar linhas no TODO list, desde que os labels referenciados existam antes do uso. O Git valida a topologia antes de executar.
6. Cenários práticos e armadilhas comuns
6.1. Rebase de uma branch com múltiplos merges de integração
# Situação: feature fez merge de main duas vezes
* Merge 'Sync with main v2' (M2)
|\
| * main (atualizado)
* | Commit D
* | Merge 'Sync with main v1' (M1)
|\ \
| * | main (anterior)
* | | Commit C
|/ /
* | Commit B
|/
* Commit A
# Após rebase com --rebase-merges
git rebase --rebase-merges main
O Git recria M1 e M2 no topo de main, mantendo a estrutura.
6.2. Conflitos em merges preservados: como resolver de forma eficiente
Quando um merge preservado gera conflitos, o Git pausa e permite resolver como em qualquer merge. Use git merge --continue após resolver. A vantagem é que apenas conflitos novos (devido às mudanças na base) aparecem — conflitos já resolvidos no merge original são reaplicados automaticamente.
6.3. Limitações: merges com conflitos resolvidos manualmente e reaplicação imperfeita
A reaplicação de resoluções de conflitos não é perfeita. Se o merge original envolveu edições manuais complexas (não apenas escolher ours ou theirs), o Git pode não conseguir reaplicar corretamente. Nesses casos, o merge pausa para intervenção manual.
7. Comparação com alternativas
7.1. git rebase tradicional vs. --rebase-merges
| Aspecto | Rebase tradicional | --rebase-merges |
|---|---|---|
| Topologia | Linearizada | Preservada |
| Conflitos de merge | Reaplicados do zero | Reaplicados com resolução anterior |
| Complexidade | Simples | Moderada |
| Histórico | Limpo, mas incompleto | Fiel ao original |
7.2. git merge + git rebase manual vs. abordagem automatizada
Sem --rebase-merges, a alternativa manual seria:
git checkout feature
git merge main
# resolver conflitos
git commit
# repetir para cada merge no histórico
A abordagem automatizada reduz o trabalho repetitivo e o risco de erro humano.
7.3. Quando evitar: histórico linear obrigatório ou merges complexos demais
Evite --rebase-merges quando:
- O time exige histórico estritamente linear (projetos que usam squash merges)
- Os merges originais têm mais de 2 pais (octopus merges) — suporte limitado
- O histórico contém merges com conflitos extremamente complexos que exigem revisão manual completa
8. Boas práticas e fluxo de trabalho recomendado
8.1. Checklist antes de usar --rebase-merges
- [ ] Verificar se a branch contém merges que precisam ser preservados
- [ ] Fazer backup da branch (
git branch backup/nome-da-branch) - [ ] Confirmar que todos os commits estão com mensagens claras
- [ ] Garantir que a base de destino está atualizada
8.2. Verificação pós-rebase: git log --graph e git range-diff
# Visualizar a topologia resultante
git log --graph --oneline --all
# Comparar o antes e depois (requer Git 2.19+)
git range-diff backup/nome-da-branch..HEAD
O range-diff mostra exatamente o que mudou entre a branch original e a reescrita.
8.3. Integração com revisão de código e pull requests
Ao usar --rebase-merges antes de abrir um Pull Request, o histórico preservado ajuda revisores a entenderem a evolução do trabalho. Em plataformas como GitHub, GitLab e Bitbucket, a topologia de merges é exibida corretamente, facilitando a revisão.
Referências
- Git - rebase Documentation — Documentação oficial do Git sobre a flag
--rebase-merges, incluindo sintaxe completa e exemplos. - Git 2.18 Release Notes — Notas de lançamento do Git 2.18 que introduziram o
--rebase-merges, com contexto histórico e motivação. - Rebasing Merges in Git - Atlassian Tutorial — Tutorial da Atlassian explicando o comportamento do rebase com merges e exemplos práticos de uso.
- Preserving Merge Commits During Rebase - Stack Overflow — Discussão técnica no Stack Overflow sobre desafios e soluções para preservar merges durante rebase.
- Git Rebase: The Definitive Guide - Graphite — Guia abrangente sobre rebase no Git, incluindo seção dedicada ao
--rebase-mergese comparação com alternativas. - Understanding Git's Rebase Merges - DEV Community — Artigo técnico na DEV Community que detalha o funcionamento interno do
--rebase-mergescom exemplos visuais. - Git Range-Diff Documentation — Documentação oficial do
git range-diff, ferramenta essencial para verificar o resultado de operações de rebase com merges.