Duas formas de integrar mudanças
Quando você trabalha com branches do Git, chega o momento em que precisa trazer as mudanças de uma branch para outra. Git oferece duas estratégias principais para isso: merge e rebase. Ambas alcançam o mesmo resultado final -- sua branch tem todas as mudanças mais recentes -- mas fazem isso de maneiras fundamentalmente diferentes, e escolher a certa importa para o histórico do seu projeto e o workflow da sua equipe.
Como o Merge funciona
Um merge pega duas branches e as combina criando um novo merge commit. Esse merge commit tem dois pais: a ponta da sua branch atual e a ponta da branch que você está integrando.
git checkout feature/login
git merge main
Depois disso, sua branch de funcionalidade contém todas as mudanças de main, unidas por um merge commit. O histórico de ambas as branches é preservado exatamente como aconteceu. Se você olhar o grafo de commits, verá as duas linhas de desenvolvimento convergindo no ponto de merge.
As características principais do merge:
- Não destrutivo. Nenhum commit existente é alterado. O histórico é exatamente o que aconteceu.
- Cria um merge commit. Este commit extra une os dois históricos.
- Preserva o contexto. Você sempre pode ver quando uma branch foi criada e quando foi integrada de volta.
Merge fast-forward
Se sua branch não divergiu do alvo (ou seja, não há novos commits no alvo desde que você criou sua branch), o Git realiza um merge fast-forward por padrão. Ele simplesmente move o ponteiro da branch para frente. Nenhum merge commit é criado porque não é necessário. Você pode forçar um merge commit mesmo neste caso com:
git merge --no-ff feature/login
Isso é útil quando você quer preservar o fato de que o trabalho foi feito em uma branch separada, mesmo que o merge fosse trivial.
Como o Rebase funciona
Rebase adota uma abordagem diferente. Em vez de criar um merge commit, ele reproduz seus commits no topo de outra branch. O resultado é um histórico linear que parece como se você tivesse iniciado seu trabalho a partir do ponto mais recente da branch alvo.
git checkout feature/login
git rebase main
O que acontece por trás dos panos:
- Git encontra o ancestral comum das duas branches.
- Remove temporariamente os commits da sua branch.
- Move o ponteiro da sua branch para a ponta de main.
- Reproduz seus commits um por um no topo.
O histórico resultante é perfeitamente linear. Sem merge commit, sem linhas se bifurcando e convergindo no grafo. Parece como se você tivesse escrito todo o seu código depois das últimas mudanças em main, mesmo que não seja o que realmente aconteceu.
As características principais do rebase:
- Cria um histórico linear. O grafo de commits é limpo e fácil de ler.
- Reescreve os hashes dos commits. Os commits reproduzidos recebem novos hashes SHA, mesmo que seu conteúdo seja o mesmo.
- Sem merge commit. A branch se integra de forma limpa sem commits extras.
Quando usar Merge
Merge é a escolha mais segura e direta em várias situações:
- Branches compartilhadas. Quando várias pessoas trabalham na mesma branch, merge preserva o histórico de todos sem reescrever commits dos quais outros dependem.
- Preservar o histórico é importante. Em projetos onde você precisa rastrear exatamente quando e como as mudanças foram integradas, merge fornece um registro completo e inalterado.
- Integração de branches de longa duração. Ao fazer merge de uma branch de release em main, ou ao combinar duas branches que divergiram significativamente, um merge commit marca claramente o ponto de integração.
- Você quer simplicidade. Merge é conceitualmente mais fácil. Há menos coisas que podem dar errado, especialmente para desenvolvedores que são novos no Git.
Quando usar Rebase
Rebase se destaca quando você quer um histórico de projeto limpo e linear:
- Atualizar uma branch de funcionalidade. Antes de fazer merge da sua funcionalidade em main, fazer rebase sobre o main mais recente dá a você um conjunto limpo de commits que se aplicam diretamente sem merge commit.
- Manter um histórico limpo. Um histórico linear é mais fácil de navegar com
git log, mais fácil de fazer bisect ao caçar bugs, e mais fácil de revisar em pull requests. - Limpeza local. Se você fez pequenos commits incrementais enquanto trabalhava, pode usar rebase para organizar antes de compartilhar sua branch.
A regra de ouro do Rebase
Nunca faça rebase de commits que foram pushados para uma branch compartilhada.
Esta é a regra mais importante a lembrar. Como rebase reescreve os hashes dos commits, qualquer pessoa que tenha baseado seu trabalho nos commits originais terá problemas. O histórico deles não corresponde mais ao histórico rebaseado, levando a commits duplicados, conflitos e confusão.
A regra é simples: se outras pessoas podem ter feito pull dos seus commits, não faça rebase. Use rebase apenas nas suas branches locais, ou em branches onde você é o único contribuidor.
Se você acidentalmente fizer rebase de uma branch compartilhada, os outros desenvolvedores da equipe precisarão lidar com o histórico divergente. Isso tipicamente significa fazer force-push (que sobrescreve o remoto) e fazer com que todos resetem suas cópias locais. É disruptivo e evitável.
Rebase interativo: reescrevendo o histórico com precisão
O rebase interativo (git rebase -i) vai além de simplesmente mover commits. Ele permite modificar o histórico de commits em si:
git rebase -i HEAD~5
Isso abre uma lista dos últimos 5 commits, onde você pode:
- pick -- manter o commit como está
- reword -- alterar a mensagem do commit
- edit -- pausar para modificar o commit
- squash -- combinar com o commit anterior
- fixup -- como squash, mas descartando a mensagem do commit
- drop -- remover o commit completamente
- reorder -- alterar a ordem reorganizando as linhas
O rebase interativo é poderoso para limpar uma branch desorganizada antes do merge. Você pode fazer squash de commits de "corrigir typo" em seu pai, reformular mensagens pouco claras e reordenar commits para que contem uma história lógica.
Um workflow prático
Muitas equipes usam uma abordagem combinada que tira o melhor das duas estratégias:
- Criar uma branch de funcionalidade a partir de main.
- Durante o trabalho, fazer rebase periodicamente da sua branch de funcionalidade sobre main para se manter atualizado.
- Antes de abrir uma pull request, usar rebase interativo para limpar seus commits.
- Fazer merge da branch de funcionalidade em main (frequentemente com um merge commit para marcar o ponto de integração).
Isso dá a você um histórico limpo e linear na branch de funcionalidade e um ponto de merge claro em main. É um meio-termo pragmático que funciona bem para a maioria das equipes.
Rebase e Merge no GitSquid
GitSquid suporta tanto merge quanto rebase diretamente pela interface. Você pode fazer merge de branches pelo menu de contexto em qualquer label de branch no grafo de commits. Para rebase, GitSquid inclui um editor visual de rebase interativo que permite reordenar, squash, fixup, reword e drop commits arrastando e clicando em vez de editar um arquivo de texto no terminal. Isso torna o rebase interativo acessível mesmo que você ache a versão de linha de comando intimidante.
Resumo
| Merge | Rebase | |
|---|---|---|
| Histórico | Não linear, preserva branches | Linear, limpo |
| Hashes dos commits | Inalterados | Reescritos |
| Merge commit | Sim (exceto fast-forward) | Não |
| Seguro para branches compartilhadas | Sim | Não |
| Melhor para | Integrar trabalho compartilhado | Limpar branches de funcionalidade |
Nem merge nem rebase é universalmente melhor. São ferramentas para situações diferentes. Entenda o que cada um faz com seu histórico, siga a regra de ouro do rebase e escolha o que se encaixa no contexto. Seu eu futuro lendo git log vai agradecer.