Times gastam horas debatendo rebase vs. merge como se fosse questão moral. Não é. São ferramentas com trade-offs diferentes. O problema não é escolher uma. É não entender quando usar cada uma.

O que cada um faz

Merge cria um novo commit que combina o histórico de dois branches. Preserva o histórico exatamente como aconteceu, com todos os commits de feature e o commit de merge.

# merge
* merge branch 'feature' into main
|\
| * add user validation
| * create user model
|/
* main commit

Rebase reaplica os commits de um branch sobre o outro. O histórico fica linear, como se os commits tivessem sido feitos em sequência.

# rebase
* add user validation
* create user model
* main commit

Quando usar merge

Merge é a escolha correta quando:

  • O branch tem múltiplos autores e o histórico de contribuição importa
  • Você quer preservar o contexto de quando as mudanças aconteceram
  • O branch é longo-vivo (ex: release branch que vive semanas)
  • Você está trabalhando com Git Flow ou modelo similar

No GitHub/GitLab, merge via pull request é o padrão. Cria um merge commit que documenta quando o código entrou, quem revisou, e o contexto da mudança.

Quando usar rebase

Rebase é a escolha correta quando:

  • O branch é curto (1-3 commits) e você quer manter o histórico limpo
  • Você precisa atualizar seu branch com mudanças da main sem criar merge commits desnecessários
  • O time prefere um histórico linear para navegação com git log --oneline
  • Você está preparando commits para squash merge
# Atualiza branch com mudanças da main via rebase
git fetch origin
git rebase origin/main

# Se der conflito, resolve e continua
git rebase --continue

A regra de ouro: não rebase branches públicos

Rebase reescreve histórico. Se outro desenvolvedor está trabalhando no mesmo branch, rebase cria um histórico divergente que força force push e causa confusão.

Rebase apenas branches que são só seus. Se o branch é compartilhado, use merge.

Squash merge: o melhor dos dois mundos

Muitos times adotaram squash merge como padrão: os commits da feature são comprimidos em um único commit antes de merge na main.

# No GitHub, configure no repo Settings > General > Pull Requests
# ☑ Allow squash merging

# Ou via CLI:
git checkout main
git merge --squash feature-branch
git commit -m "feat(auth): add JWT refresh token rotation"

Vantagens: histórico da main fica limpo com um commit por feature. Desvantagens: o histórico detalhado da feature (decisões, discussões, tentativas) é perdido.

O fluxo que funciona na prática

# 1. Cria branch a partir da main
git checkout main && git pull
git checkout -b feature/user-validation

# 2. Trabalha, faz commits localmente
git add . && git commit -m "feat: add user model"
git commit -m "feat: add validation logic"

# 3. Antes de abrir PR, rebase na main
git fetch origin
git rebase origin/main

# 4. Resolve conflitos se houver
git rebase --continue

# 5. Abre PR: squash merge no GitHub

Isso dá o melhor dos dois mundos: rebase manter seu branch atualizado sem merge commits, e squash merge manter o histórico da main limpo.

O que não fazer

Não force push em branches compartilhados. Não rebase commits que já foram pushados e que outras pessoas estão usando. Não faça rebase interativo em branches públicos. E não discuta isso no Slack como se fosse religião. Escolha a ferramenta que funciona para o contexto do seu time.