git merge e git rebase fazem o mesmo trabalho: trazer as mudanças de um branch para dentro de outro. O histórico que sobra é que difere, e é esse histórico que decide qual comando serve ao seu caso.

O que cada comando faz

Merge escreve um commit com dois pais. Esse commit registra o momento em que dois caminhos se encontraram, e cada commit da feature sobrevive como foi escrito.

git checkout feature
git merge main

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

Rebase repassa seus commits por cima do destino. Ele descarta os originais e grava cópias com o mesmo conteúdo e pais novos. Os hashes mudam, então qualquer referência antiga àqueles commits (um branch, uma tag, um link no PR) passa a apontar para um objeto que ninguém mais tem.

git checkout feature
git rebase main

* add user validation
* create user model
* main commit

Quando o merge resolve melhor

  • O branch tem vários autores e o histórico de quem contribuiu com o quê precisa ficar registrado.
  • O branch vive semanas, como um release branch, e você quer preservar a ordem real dos acontecimentos.
  • O time segue Git Flow e o commit de merge vira o ponto de encontro entre release e main.

No pull request, o commit de merge carrega metadado: quem aprovou, o número do PR, a data. A página Configuring pull request merges do GitHub lista os três formatos aceitos e o que cada um grava no histórico.

Quando o rebase resolve melhor

  • Sua branch tem de um a três commits e a main andou. Você quer acompanhar a main sem encher a linha de commits de merge.
  • Você prepara a mensagem final antes de um squash merge.
  • O time navega o histórico com git log --oneline e usa git bisect para achar regressão. Uma linha reta encurta as duas buscas.
git fetch origin
git rebase origin/main

# deu conflito: resolve nos arquivos e continua
git add .
git rebase --continue

Histórico público não se reescreve

O Pro Git abre um bloco chamado "Do not rebase public history" com a regra que não admite exceção: se outro desenvolvedor já puxou os commits que você reescreveu, o rebase obriga um force push e parte o histórico em dois. Cada um passa a trabalhar em cima de uma versão diferente do mesmo commit, e o próximo git pull mistura as duas.

Reescreva commits que só você puxou. Branch que outras pessoas leem recebe merge. Quando um rebase dá errado, git reflog guarda os commits descartados por alguns dias e git rebase --abort volta ao ponto de partida.

Squash merge

Squash merge comprime todos os commits da feature em um só antes de entrar na main. A main vira uma linha com um commit por feature, e a discussão do PR guarda o detalhe que a mensagem do commit perde.

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

Quem adota squash abre mão da sequência de tentativas dentro da branch. Em troca, o git log da main lê como um diário de mudanças. O arranjo que funciona na maioria dos times: commits pequenos na branch, squash na hora do merge.

O fluxo completo

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

# 2. commits locais enquanto você trabalha
git add . && git commit -m "feat: add user model"
git commit -m "feat: add validation logic"

# 3. antes do PR, atualiza sobre a main
git fetch origin
git rebase origin/main

# 4. conflito: resolve e continua
git rebase --continue

# 5. push e PR, com squash merge habilitado no repositório

O rebase mantém a branch em dia até o PR abrir. O squash merge mantém a main legível depois que ele entra. Os dois comandos ocupam momentos distintos do fluxo.

Referências