Git Rebase ou Merge? Escolha a estratégia certa para seu time
1. Visão geral
git merge e git rebase são usados para integrar mudanças entre dois branches, mas criam históricos de commit muito diferentes. Escolher a estratégia errada deixa o log confuso, difícil de revisar, ou pior — danifica o histórico de seus colegas. Este artigo ajuda você a entender o mecanismo de cada comando através de exemplos práticos, saber quando fazer merge, quando fazer rebase e evitar os erros mais comuns ao usar rebase em um time.
2. Contexto do exemplo
Suponha que você está desenvolvendo na branch feature/payment a partir de main. Enquanto você programa, um colega já enviou alguns commits para main.
git log --oneline --graph --all
* c3f9a1d (main) Adiciona PaymentValidator
* 7b2e4aa (main) Corrige bug OrderService
| * 9d1a0ef (feature/payment) Adiciona PaymentService.processPayment()
| * 4f3c2bb (feature/payment) Inicializa PaymentService
|/
* a1b2c3d Commit base comum
Os dois branches se separaram a partir de a1b2c3d. Agora você quer colocar feature/payment no mesmo nível que main antes de criar um pull request. Existem duas formas: merge ou rebase.
3. Git Merge: preserva o histórico, cria merge commit
3.1. Como usar
git checkout feature/payment
git merge main
Este comando cria um novo merge commit que tem dois pais: o último commit de feature/payment e o último commit de main.
* e5f6a7b (HEAD -> feature/payment) Merge branch 'main' into feature/payment
|\
| * c3f9a1d (main) Adiciona PaymentValidator
| * 7b2e4aa (main) Corrige bug OrderService
* | 9d1a0ef Adiciona PaymentService.processPayment()
* | 4f3c2bb Inicializa PaymentService
|/
* a1b2c3d Commit base comum
3.2. Características
- O histórico original de ambos os branches é preservado 100% — nenhum commit é modificado.
- Um novo merge commit é adicionado registrando o momento em que os dois branches se encontram.
- Completamente seguro com branches já enviados — ninguém é afetado negativamente.
Nota:
git mergenão reescreve o SHA de nenhum commit já existente. Esta é a razão pela qual é sempre seguro usar em branches compartilhados.
4. Git Rebase: reescreve o histórico, cria um branch linear
4.1. Como usar
git checkout feature/payment
git rebase main
Rebase pega cada commit em feature/payment a partir do ponto de divergência (a1b2c3d), e os “reproduz” (replay) no topo de main. O resultado são commits completamente novos com SHA diferente.
* 2a9e1cc (HEAD -> feature/payment) Adiciona PaymentService.processPayment()
* d8b4f0a Inicializa PaymentService
* c3f9a1d (main) Adiciona PaymentValidator
* 7b2e4aa (main) Corrige bug OrderService
* a1b2c3d Commit base comum
O histórico agora é uma linha reta (linear history), sem nenhum merge commit.
4.2. Características
9d1a0efe4f3c2bbnão existem mais — substituídos por2a9e1cced8b4f0acom SHA novo.- Log limpo, fácil de ler, fácil de usar
git bisect. - Perigoso se o branch já foi puxado por outras pessoas, pois eles mantêm os commits antigos que “desapareceram”.
5. Resolvendo conflitos: a maior diferença durante a operação
Com merge, conflitos (se houver) precisam ser resolvidos apenas uma vez no merge commit:
git merge main
# CONFLICT (content): Merge conflict in PaymentService.java
# ... edita o arquivo, resolve ...
git add PaymentService.java
git commit
Com rebase, se houver vários commits, conflitos podem ocorrer em cada commit durante o replay:
git rebase main
# CONFLICT no commit 4f3c2bb
# edita o arquivo, resolve, depois:
git add PaymentService.java
git rebase --continue
# se o próximo commit também tiver conflito, repete o processo
Se você quiser parar e voltar ao estado anterior ao rebase:
git rebase --abort
Nota: Fazer rebase de um branch com 10 commits às vezes significa resolver conflitos 10 vezes — uma razão pela qual fazer
squashantes de rebase (veja a seção 7) é muito útil.
6. Regra de Ouro: nunca faça rebase de branches compartilhados
Esta é a regra mais importante ao usar rebase.
# Branch main já foi puxado por 5 outras pessoas
git checkout main
git rebase feature/old-stuff # ❌ EXTREMAMENTE PERIGOSO
git push --force origin main
Porque rebase cria novos SHA para todos os commits, após fazer push --force, qualquer pessoa que tenha puxado o main antigo terá um histórico divergido com a remote. O pull seguinte deles falhará ou criará uma série de commits duplicados.
error: failed to push some refs to 'origin'
hint: Updates were rejected because the tip of your current branch is behind
Se for absolutamente necessário fazer force-push após rebase, use --force-with-lease em vez de --force para evitar sobrescrever commits de outras pessoas:
git push --force-with-lease origin feature/payment
--force-with-lease verificará se a remote não recebeu nenhum novo commit desde seu último fetch — se recebeu, o comando falha em vez de sobrescrever.
Regra de Ouro: faça rebase apenas em branches pessoais, não compartilhados ou que apenas você está trabalhando. Nunca faça rebase em main, develop ou qualquer branch compartilhado.
7. Interactive rebase: limpe o histórico antes de fazer merge
Uma aplicação muito prática de rebase é limpar commits “inúteis” em seu branch pessoal antes de criar um pull request.
git checkout feature/payment
git rebase -i HEAD~3
Um editor abre:
pick 4f3c2bb Inicializa PaymentService
pick 9d1a0ef Adiciona PaymentService.processPayment()
pick 1c8f3aa fix typo
Mude as duas últimas linhas para squash para mesclá-las no primeiro commit:
pick 4f3c2bb Inicializa PaymentService
squash 9d1a0ef Adiciona PaymentService.processPayment()
squash 1c8f3aa fix typo
Salve, e o Git pedirá uma nova mensagem de commit. Resultado: 3 commits menores viram 1 commit limpo e significativo — muito útil antes de abrir uma PR para o revisor não ter que ler commits como “fix typo”, “wip”, “ah esqueci de importar”.
8. Estratégia prática: combine ambos
A maioria dos times eficientes não escolhe absolutamente um dos dois, mas combina conforme o contexto:
| Situação | Use |
|---|---|
Atualizar branch pessoal com o main mais novo |
rebase |
| Limpar commits menores antes de abrir PR | rebase -i (squash) |
Fazer merge de um feature branch revisado em main |
merge (geralmente via PR, pode usar squash-merge) |
| Branch que várias pessoas estão usando/puxando | merge, nunca rebase |
| Precisa manter registro de quando a integração aconteceu para auditoria | merge |
Fluxo comum na prática:
# 1. Antes de abrir PR: rebase em main para atualizar, evitar conflitos depois
git checkout feature/payment
git rebase main
# 2. Limpe commits se necessário
git rebase -i main
# 3. Envie o branch pessoal (seguro pois apenas você usa)
git push --force-with-lease origin feature/payment
# 4. Faça merge em main via PR (merge commit ou squash-merge, não rebase main)
Desta forma, o log de feature/payment fica limpo, linear, fácil de revisar na PR, enquanto main permanece seguro pois recebe apenas merge commits (ou squash commits) de PRs aprovadas.
9. Squash merge: solução intermediária
Muitas plataformas (GitHub, GitLab) têm botão “Squash and merge” — mescla todos os commits da PR em um único commit e faz merge em main.
git checkout main
git merge --squash feature/payment
git commit -m "Adiciona funcionalidade de processamento de pagamento (#42)"
Resultado: main ganha exatamente um novo commit, sem merge commit, e o histórico de feature/payment (na remote) permanece inalterado se você não deletar o branch. Esta é a opção mais comum para times que usam fluxo de PR, pois deixa o log de main limpo, evitando reescrever histórico.
10. Erros comuns
- Fazer rebase e depois push normal (sem
--force): Git reportará erro pois o histórico divergiu. Não usegit pullnormal para “consertar” — criará merge commits indesejados, destruindo o histórico linear que você acabou de criar com rebase. - Esquecer que o branch em rebase é compartilhado: sempre verifique
git log origin/<branch>para ver se outras pessoas já usam aqueles commits antigos. - Fazer rebase, esquecer de
--continueapós resolver conflito: o estado de rebase fica “suspenso” —git statusavisará que você está no meio de um rebase. - Usar
mergecontinuamente em branch pessoal deixa o log cheio de pequenos merge commits, difícil de ler — use rebase para atualizar em vez de fazer merge reverso demainpara sua branch várias vezes. - Fazer squash em excesso: mesclar muitas mudanças não relacionadas em um commit torna
git bisectegit revertparcial inúteis.
11. Conclusão
merge é seguro, preserva o histórico verdadeiro, ideal para branches compartilhados. rebase cria histórico limpo e linear, mas reescreve SHA — só funciona em branches pessoais não compartilhados. Na prática, você não precisa escolher um lado absoluto: faça rebase para atualizar e limpar seu branch pessoal, e use merge (ou squash-merge) para integrar em main via PR. Lembre-se da Regra de Ouro — nunca rebase branches que outras pessoas usam — e você evitará a maioria dos problemas de Git em um time.