Git Rebase vs Merge: Quando Usar Cada Um (e o Erro que Quebra o Histórico de Todo Projeto)
← Voltar para Codeshort

Git Rebase vs Merge: Quando Usar Cada Um (e o Erro que Quebra o Histórico de Todo Projeto)

Merge preserva contexto, rebase reescreve o passado. Usar o errado no momento errado gera conflitos impossíveis, push --force acidentais e histórico que não conta mais nada. Guia definitivo com tabela de decisão.

DC
Dev Code Software
03 de abril de 2026·9 min de leitura

Índice


De acordo com pesquisas recorrentes de desenvolvedores, Git é a ferramenta mais usada na profissão — e git push --force em branch compartilhada é um dos erros mais comuns em times que adotaram rebase sem estabelecer convenção. O problema quase nunca é não saber o que merge e rebase fazem. É saber superficialmente e aplicar no contexto errado.

Você já abriu o git log e viu um emaranhado de merges cruzados, commits "fix", "fix2", "agora vai" intercalados com branches que não fazem sentido juntas. Ou o oposto: um histórico linear impecável que apagou todo rastro de quando aquela feature crítica foi desenvolvida em paralelo com a correção urgente de produção. Os dois cenários têm a mesma causa: usar merge e rebase sem entender o que cada um preserva — e o que cada um apaga para sempre.

O que você vai encontrar aqui:

  • O mecanismo real de merge e rebase — com diagramas de como o histórico muda em cada caso
  • O erro clássico que gera push --force acidental em branch compartilhada e como evitá-lo
  • Os casos de uso corretos para merge, rebase e merge --squash
  • Como conflitos funcionam diferente nas duas operações — e quando cada abordagem é mais pragmática
  • Tabela de decisão, configurações globais recomendadas e checklist antes de integrar qualquer branch

O que merge e rebase realmente fazem com o histórico

merge: une dois históricos sem mentir sobre o passado

git merge cria um commit de merge com dois pais — um ponto no histórico que documenta exatamente onde duas linhas de desenvolvimento foram unidas.

        A---B---C  feature
       /         \
D---E---F----------G  main

O commit G tem dois pais: C e F. Daqui a seis meses, quando alguém precisar entender por que aquela decisão foi tomada naquele momento, o contexto vai estar lá: essas duas linhas existiram em paralelo e foram unidas neste ponto específico.

rebase: reescreve o passado para parecer que tudo foi linear

git rebase descarta os commits originais e recria cada um deles em cima de outro ponto base. O conteúdo é o mesmo; a identidade — hash, pai, posição no histórico — não.

Antes do rebase:
      A---B---C  feature
     /
D---E---F  main

Depois de: git checkout feature && git rebase main
              A'--B'--C'  feature
             /
D---E---F  main

A', B' e C' são commits novos. O histórico agora diz que sua feature foi desenvolvida depois do estado atual da main — o que nunca foi verdade. O passado foi reescrito.


O erro que quebra o trabalho de toda a equipe

Fazer rebase em uma branch que outras pessoas estão usando.

git checkout feature/pagamento
git rebase main

git push origin feature/pagamento
# error: failed to push some refs
# hint: Updates were rejected because the tip of your current branch is behind

Neste ponto, dois devs da equipe já têm os commits originais (A, B, C) nas máquinas locais. Você acabou de criar commits novos (A', B', C') com hashes completamente diferentes no remoto. O que acontece a seguir é quase inevitável:

git push --force origin feature/pagamento

O --force reescreve o histórico remoto. O próximo git pull dos seus colegas vai encontrar commits que o Git não consegue reconciliar com o que eles têm localmente. O resultado é um conflito estrutural — não de código, mas de histórico — que exige resolução manual e coloca todos em modo de apagamento de incêndio.

Se você precisar fazer push após um rebase legítimo em branch pessoal, use --force-with-lease no lugar de --force. Ele verifica se o remoto foi atualizado por outra pessoa desde seu último fetch antes de sobrescrever:

git push --force-with-lease origin feature/minha-feature

Regra sem exceção: nunca faça rebase de uma branch que outras pessoas estão usando. Rebase é para branches locais ou branches pessoais que só você controla.


Quando usar merge

Integrando feature branch finalizada na branch principal

git checkout main
git merge feature/novo-checkout

O commit de merge documenta quando e onde aquela feature entrou. Em projetos com vários devs, esse contexto tem valor real — você consegue ver no git log --graph que o trabalho no checkout e no módulo de relatórios aconteceram em paralelo, e que o checkout entrou antes.

git log --oneline --graph

* 4a2f1c3 Merge branch 'feature/novo-checkout'
|\
| * 9e3d8b1 Adiciona validação de CPF no checkout
| * 2c7a4f0 Implementa cálculo de frete
* | 8b1d3e2 Fix: relatório de vendas com filtro de data
|/
* 5f2e9a1 Refatora módulo de pagamento

Pull requests e code review

Em times que usam PRs, merge é o padrão correto. O commit de merge marca o momento exato da integração — quem aprovou, quando entrou, qual era o estado da main naquele momento. GitHub e GitLab associam todos os commits do PR via esse merge commit. Com rebase, essa rastreabilidade desaparece: os commits ficam no histórico, mas o vínculo com o PR some.

Hotfixes em produção

Em correções críticas, contexto é tudo. O merge commit documenta que aquela mudança foi uma resposta a um problema específico, num momento específico, com o estado exato da branch de produção naquele instante. Rebase apagaria esse contexto.


Quando usar rebase

Atualizar sua branch com mudanças da main antes de abrir PR

Sua feature branch ficou dois dias sem atualização. A main evoluiu com commits de outros devs. Você quer incorporar essas mudanças antes de abrir o PR sem criar um merge commit desnecessário na sua branch:

git checkout feature/minha-feature
git rebase main

Agora sua branch está posicionada como se tivesse sido criada a partir do estado atual da main. O PR vai mostrar apenas os seus commits — sem ruído de merge, sem histórico confuso para o revisor.

Limpar o histórico antes do merge — rebase interativo

Você desenvolveu uma feature em seis commits, sendo três deles "wip", "fix typo" e "teste rápido". Antes de mergear, faz sentido condensar em commits que contam uma história coerente:

git rebase -i HEAD~6

O editor abre com os seis commits listados. Você decide o que fazer com cada um:

pick 2c7a4f0 Implementa cálculo de frete
squash 9e3d8b1 wip
squash 4a2f1c3 fix typo
pick 8b1d3e2 Adiciona validação de CPF
squash 5f2e9a1 ajuste no regex
pick 3d1b8c0 Adiciona testes do checkout

squash funde o commit com o anterior. O resultado: três commits limpos, cada um com uma mensagem que descreve o que realmente aconteceu — não o processo bagunçado de desenvolvimento.

* 3d1b8c0 Adiciona testes do checkout
* 8b1d3e2 Adiciona validação de CPF no checkout
* 2c7a4f0 Implementa cálculo de frete

Além de squash, o rebase interativo oferece mais opções:

  • reword — reescreve apenas a mensagem do commit, sem alterar o conteúdo
  • edit — pausa o rebase para que você modifique o commit manualmente (útil para dividir um commit grande em dois)
  • drop — remove o commit completamente do histórico

Como os conflitos funcionam diferente em cada operação

No merge, você resolve o conflito uma única vez. O commit de merge documenta a resolução e o Git segue em frente.

No rebase, você pode precisar resolver conflitos em cada commit que está sendo reescrito. Se você tem oito commits e o arquivo conflitante foi modificado em três deles, você resolve três vezes — uma por vez.

git rebase main

# CONFLICT (content): Merge conflict in src/checkout.ts
# error: could not apply 9e3d8b1...

git add src/checkout.ts
git rebase --continue

git rebase --abort

O --abort cancela o rebase inteiro e restaura o estado anterior — útil quando os conflitos estão complexos demais ou você se perdeu no meio do processo.

Resolver por commit não é necessariamente ruim: cada resolução fica registrada no commit correto, deixando o histórico mais preciso. Mas se a branch divergiu muito da main, merge tende a ser mais pragmático e menos desgastante do que resolver conflitos em série.


merge --squash: o meio-termo que pouca gente usa corretamente

Existe uma terceira opção que combina características das duas abordagens:

git checkout main
git merge --squash feature/minha-feature
git commit -m "feat: implementa checkout com validação de CPF"

--squash pega todos os commits da feature, combina as mudanças em staging e permite criar um único commit novo na main. Não cria commit de merge. Não preserva o histórico individual dos commits da feature.

Quando faz sentido: features pequenas e autocontidas, onde o histórico interno (wip, fix, ajuste) não tem valor e você quer uma linha do tempo limpa na main sem precisar fazer rebase interativo na branch.

O custo que ninguém menciona: a branch de origem fica desconectada do ponto de vista do Git. Se você tentar mergear a mesma branch novamente depois, o Git não vai reconhecer que o conteúdo já está na main e vai tratar tudo como novo — gerando conflitos ou duplicações inesperadas. Após um merge --squash, delete a branch de origem.


Tabela de decisão e configurações

Quando usar cada operação

SituaçãoOperação recomendada
Integrar feature branch finalizada na mainmerge
Atualizar feature branch com a main (branch só sua)rebase
Limpar commits antes de abrir PRrebase -i
Branch compartilhada com outros devsmerge — nunca rebase
Feature pequena, histórico interno sem valormerge --squash
Hotfix em produçãomerge
Repositório open source com convenção linearrebase antes do PR
Você não tem certezamerge — é reversível

Configurações que valem ativar agora

Por padrão, git pull faz merge. Em branches pessoais, rebase é mais limpo — e você pode definir isso globalmente:

git config --global pull.rebase true

git config --global merge.ff false

A primeira linha define rebase como padrão no pull. A segunda força a criação de um merge commit mesmo quando o fast-forward seria possível — garantindo que a integração sempre fique documentada no histórico, sem merges silenciosos que parecem um simples avanço de ponteiro.

Checklist antes de integrar qualquer branch

ItemO que verificar
Branch é só sua?Se outros devs usam a branch, merge. Nunca rebase.
Commits estão limpos?Mensagens descritivas, sem "wip" ou "fix2"? Se não, rebase -i antes.
Branch está atualizada com a main?git fetch origin && git rebase origin/main se for branch pessoal.
PR vai para revisão?Merge é o padrão — o commit de merge preserva o vínculo com o PR.
Feature é pequena e autocontida?merge --squash como alternativa ao rebase interativo.
Push após rebase foi necessário?Usou --force-with-lease, não --force?
Convenção está documentada?O time sabe qual abordagem usar em cada caso?

FAQ

Devo usar rebase ou merge para atualizar minha branch local com a main?

Se a branch é só sua — ninguém mais fez push nela — rebase é a escolha mais limpa. Sua branch vai parecer que foi criada a partir do estado atual da main, e o PR vai mostrar apenas os seus commits sem commits de merge intermediários. Se outros devs também trabalham na branch, use merge. A regra é simples: rebase só reescreve o passado que só você controla.

O que fazer depois de um push --force acidental em branch compartilhada?

Primeiro, avise o time imediatamente para ninguém fazer pull até o problema ser resolvido. Depois, use git reflog para encontrar o hash do commit anterior ao rebase e restaure o remoto:

git reflog

git reset --hard abc1234
git push --force-with-lease origin feature/branch-afetada

Quem já havia feito pull dos commits antigos vai precisar fazer git fetch e git reset --hard origin/feature/branch-afetada para sincronizar com o remoto restaurado. O reflog guarda o histórico local por 90 dias por padrão — é o seguro de vida do Git.

git pull --rebase é seguro para usar no dia a dia?

Sim, com uma condição: a branch precisa ser só sua. git pull --rebase é equivalente a fazer fetch e rebase local — mantém o histórico linear sem commits de merge de pull. Se você trabalha sozinho em uma feature branch, é a opção mais limpa. Se outros devs fazem push na mesma branch, use git pull padrão (merge) para evitar conflitos de histórico.

Qual a diferença prática entre merge --squash e rebase -i com squash?

Os dois resultam em commits limpos na main, mas o mecanismo é diferente. merge --squash opera na branch de destino — você faz o squash no momento do merge, sem tocar na branch de origem. rebase -i opera na branch de origem — você limpa os commits antes de mergear, e a branch de origem fica com o histórico reescrito. Se você quer preservar a branch de origem intacta depois do merge, merge --squash é mais conveniente. Se quer que o PR mostre os commits já limpos durante o review, rebase -i antes de abrir o PR é o caminho.

Como padronizar a convenção para o time inteiro?

A convenção precisa estar em dois lugares: no CONTRIBUTING.md do repositório e nas configurações de merge das branches protegidas. No GitHub, você pode restringir o método de integração nas configurações da branch main — forçando "Squash and merge" ou "Rebase and merge" como único método disponível. Isso elimina a decisão individual e padroniza o histórico. Para branches de feature, documente no CONTRIBUTING.md se o time adota rebase antes do PR ou aceita branches com merge commits internos.


Próximos passos

Se o seu time usa Git sem convenção definida, esse é o ponto com maior retorno imediato. A falta de combinação sobre quando usar merge e rebase é a causa número um de git push --force acidental em branches compartilhadas — e histórico bagunçado não se conserta retroativamente sem dor.

O primeiro passo prático: rode git rebase -i HEAD~5 em uma branch de teste com commits ruins. Experimente squash, reword, edit e drop. Em dez minutos você vai entender rebase de forma mais concreta do que qualquer leitura passiva. Depois, documente a convenção do time antes que o projeto cresça.