Fundamentos de Programação/02 - Código que Presta/Semana 08 - Git Além do Básico5 min
Rebase vs Merge
- O que cada um faz com o histórico — e qual dos dois cria commits novos?
- Por que nunca rebasear branch que outra pessoa já puxou?
- O que
--ff-onlyprotege? - Histórico linear vale a perda do contexto de "quando isso foi integrado"?
Conceito
Os dois integram trabalho de uma linha de desenvolvimento em outra. A diferença é o que acontece com o histórico.
merge |
rebase |
|
|---|---|---|
| O que faz | cria um commit com dois pais | recria seus commits sobre a nova base |
| Commits novos? | um (o merge commit) | um por commit reaplicado |
| Hash dos seus commits | preservado | muda |
| Histórico resultante | grafo | linha |
| Registra "quando foi integrado"? | sim | não |
Merge registra. Rebase reescreve.
Rebase não move commits — ele cria cópias com o mesmo conteúdo e pai diferente.
Como o hash cobre o pai, a árvore e os metadados, mudar o pai muda o hash. Os commits
originais continuam existindo, órfãos, recuperáveis pelo 4. Reflog até o gc.
Por que nunca rebasear branch que outra pessoa já puxou: ela tem os commits antigos;
você tem cópias novas com o mesmo conteúdo. Quando ela puxar, o git não tem como saber que
são a mesma coisa — ela recebe as duas versões, e o histórico duplica. Resolver isso exige
que ela faça reset --hard para o seu estado, perdendo qualquer trabalho local.
A regra de ouro é simples: rebase só no que é privado. Antes de publicar, reescreva à vontade. Depois de publicar, só merge.
O que --ff-only protege: ele recusa criar um merge commit. Se as branches
divergiram, ele falha em vez de fazer merge — te forçando a decidir consciente entre merge
e rebase, em vez de acumular merge commits acidentais de git pull.
Histórico linear vale a perda de contexto? Depende do que você faz com o histórico:
- linear é mais fácil de ler e faz
bisectandar melhor (5. Bisect) - linear inventa estados que nunca existiram — seus commits reaplicados nunca foram testados naquela base. É a origem do conflito semântico: compila, não conflita, e está quebrado (6. Resolução de Conflitos)
- merge preserva a verdade histórica e produz um grafo que ninguém lê sem ferramenta
Na prática
Verificado num repo de teste: o mesmo commit, antes e depois do rebase.
$ git log --oneline feat # antes
5493ccb add x
$ git rebase master
$ git log --oneline feat # depois
680076a add x # <- hash DIFERENTE, diff idêntico
Mesmo conteúdo (1 file changed, 1 insertion(+)), identidade nova. É isso que "reescrever
o histórico" significa concretamente.
E o --ff-only recusando, com a saída real:
$ git merge --ff-only feat2
hint: Diverging branches can't be fast-forwarded, you need to either:
hint: git merge --no-ff
hint: or:
hint: git rebase
fatal: Not possible to fast-forward, aborting.
Sem o --ff-only, o mesmo comando criaria um merge commit silenciosamente.
Respostas às perguntas-guia
1. O que cada um faz com o histórico — e qual dos dois cria commits novos?
Merge cria um commit (o de merge, com dois pais) e preserva os seus. Rebase cria um commit novo por commit reaplicado e abandona os originais.
Verificado: 5493ccb virou 680076a com o mesmo diff.
2. Por que nunca rebasear branch que outra pessoa já puxou?
Porque ela tem os commits antigos e você tem cópias novas. O git não consegue reconciliar,
e a resolução para ela é destrutiva (reset --hard para o seu estado). Rebase só no que é
privado.
3. O que --ff-only protege?
Protege contra merge commit acidental. Ele falha quando não é possível avançar o ponteiro, em vez de criar um merge. Vale configurar como padrão:
git config --global pull.ff only
4. Histórico linear vale a perda do contexto de "quando isso foi integrado"?
Se você usa o histórico para investigar (bisect, log -S, blame), linear ganha. Se
você usa para auditar integração, merge ganha. E há um custo escondido no linear: commits
reaplicados nunca existiram naquela base, então "compilou em cada commit" deixa de ser
verdade — o que enfraquece exatamente o bisect que motivou a linearidade.
Armadilhas
git pullsem configuração faz merge por padrão e enche o histórico de "Merge branch 'main' of ...". Configurepull.ff onlyoupull.rebase true.- Rebase de branch longa te faz resolver o mesmo conflito repetidamente, uma vez
por commit.
git rereregrava a resolução e reaplica (6. Resolução de Conflitos). --forceapós rebase é obrigatório e perigoso. Use--force-with-lease: ele recusa se alguém empurrou algo que você não viu.- Squash antes de mergear perde a granularidade que o
bisectusa. Squash de 15 commits transforma 15 pontos de busca em 1.
Comandos essenciais
git merge feat # cria merge commit se divergiu
git merge --ff-only feat # RECUSA se não for fast-forward
git merge --no-ff feat # força merge commit mesmo quando poderia avançar
git rebase main # reaplica seus commits sobre main
git rebase --onto main feat~3 feat # move só os 3 últimos
git rebase --abort # desiste, volta ao estado anterior
git rebase --continue # segue após resolver conflito
git push --force-with-lease # empurra reescrita sem atropelar ninguém
git log --graph --oneline --all # vê a topologia de verdade
git config --global pull.ff only # nunca mais merge acidental no pull
git rebase -i (interativo) permite reordenar, juntar e editar commits. É a ferramenta
mais útil da lista e a mais perigosa — só em branch privada.
Relacionado
- 2. Cherry-pick — a outra forma de mover mudanças entre branches
- 4. Reflog — o que recupera os commits que o rebase abandonou
- 6. Resolução de Conflitos — e a inversão de
ours/theirsno rebase - 5. Bisect — por que a forma do histórico importa na investigação
Parte de Semana 08 - Git Além do Básico · 00 - MOC Fundamentos de Programação