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 merge nã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

  • 9d1a0ef e 4f3c2bb não existem mais — substituídos por 2a9e1cc e d8b4f0a com 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 squash antes 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 use git pull normal 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 --continue após resolver conflito: o estado de rebase fica “suspenso” — git status avisará que você está no meio de um rebase.
  • Usar merge continuamente 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 de main para sua branch várias vezes.
  • Fazer squash em excesso: mesclar muitas mudanças não relacionadas em um commit torna git bisect e git revert parcial 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.

gitrebasemergeversion-controlworkflow