Merge ou rebase? O que mudou quando parei de pensar apenas em conflitos
Um breve resumo sobre como merge e rebase afetam o histórico do Git e por que o contexto da feature importa mais do que simplesmente tentar evitar conflitos.
Durante bastante tempo, meu fluxo de trabalho com Git era baseado quase todo em `git pull`. Quando eu precisava atualizar uma feature com a `develop`, fazia um pull, resolvia os conflitos, criava um commit com os ajustes e depois seguia para o Pull Request.
Funcionava, mas era uma forma mecânica de lidar com a situação. Eu sabia como atualizar a branch, mas não pensava muito sobre o que estava acontecendo com o histórico do projeto.
Isso deixou de ser simples quando o fluxo da equipe aumentou: mais pessoas trabalhando ao mesmo tempo significaram mais PRs, mais mudanças entrando na `develop` e mais chances de uma feature chegar ao Pull Request com conflitos. Algumas integrações passaram a consumir mais tempo e exigir novas validações.
Foi aí que comecei a estudar melhor `merge` e `rebase`. A intenção inicial era reduzir os problemas com conflitos, mas percebi que conflito era só uma parte da história.
Merge e rebase não são apenas duas formas de atualizar uma branch. Eles também são duas formas diferentes de organizar o histórico do Git.
---
O problema não começa no Pull Request
Vamos imaginar uma `develop` assim:
A---B---C
Uma feature criada a partir dela evolui em paralelo:
A---B---C---F---G
\
D---E
A feature começou em `C`, mas a `develop` já avançou até `G`. Atualizar a branch, resolver os conflitos e continuar não está necessariamente errado. O problema é fazer isso sem entender qual tipo de histórico está sendo criado.
Em vez de pensar apenas em “como resolvo esse conflito?”, vale perguntar: “qual é a melhor forma de trazer essas mudanças para minha feature?”
---
O merge mantém o histórico como ele aconteceu
Na feature, podemos executar:
git fetch origin
git merge origin/develop
O Git pode criar um commit de merge:
A---B---C---F---G
\ \
D---E---M
Esse commit registra que as duas branches foram integradas naquele ponto. `D`, `E`, `F` e `G` continuam sendo os mesmos commits.
O merge preserva os dois caminhos que existiram em paralelo e deixa claro onde eles foram unidos. O histórico pode ficar mais ramificado, mas essa característica é especialmente importante quando mais de uma pessoa está trabalhando na mesma branch.
---
O rebase reorganiza o histórico
Em uma feature individual, podemos fazer:
git fetch origin
git rebase origin/develop
O Git reaplica os commits da feature depois da versão mais recente da `develop`:
A---B---C---F---G---D'---E'
O histórico fica linear, como se a feature tivesse começado depois de `F` e `G`. Mas `D'` e `E'` são novos commits: as mudanças podem ser as mesmas, porém os hashes mudam. É por isso que dizemos que o rebase reescreve o histórico.
---
A branch é individual ou compartilhada?
Essa é uma das perguntas mais úteis para decidir entre as estratégias: **só uma pessoa está trabalhando nessa feature?**
Se a resposta for sim e a `develop` recebeu mudanças importantes, rebase pode ser uma boa opção. Os conflitos aparecem durante o rebase, antes do momento final do PR.
Mas usar rebase não evita conflitos. Se duas alterações modificarem as mesmas linhas de formas incompatíveis, o conflito continua existindo. O que muda é o momento em que ele aparece e como a integração fica registrada no histórico.
Não é necessário fazer rebase toda vez que a `develop` muda. É melhor considerar o tamanho e a relevância das mudanças, além do tamanho da feature. Git não precisa virar uma sequência de comandos executados automaticamente.
Quando outra pessoa está na mesma feature, o merge ganha importância. Rebasear uma branch compartilhada cria novos commits para uma pessoa enquanto a outra continua referenciando os antigos. Um merge atualiza a branch sem reescrever os commits que já estão sendo utilizados por outras pessoas.
---
Atualizar antes ajuda — e às vezes não é preciso fazer nada
Se a `develop` mudou bastante durante o desenvolvimento, pode fazer sentido integrar essas alterações antes do PR. Não para manter a feature atualizada o tempo todo, mas para descobrir incompatibilidades antes de concentrar muitos conflitos no final.
Por outro lado, se a feature foi criada a partir da `develop`, foi finalizada e nada relevante mudou na base, não existe nada para atualizar. Não é preciso fazer merge ou rebase apenas por hábito.
Uma lógica simples para escolher
- feature individual e mudanças relevantes na `develop`: considero `rebase`; - feature compartilhada: prefiro preservar o histórico e usar `merge` quando for necessário integrar a `develop`; - `develop` não mudou: não faço nenhum dos dois apenas por hábito.
Isso não é uma regra universal. Cada equipe tem estratégias diferentes conforme o modelo de branches, o projeto e as regras do repositório.
No fim, o problema não era só conflito. Merge e rebase ajudam a juntar mudanças, mas fazem isso de formas diferentes: o merge preserva os caminhos que realmente existiram; o rebase reorganiza os commits sobre uma nova base.
Nenhum dos dois impede conflitos e nenhum é automaticamente melhor. O principal aprendizado foi deixar de tratá-los apenas como ferramentas para apagar incêndios no Pull Request e passar a entendê-los como formas diferentes de controlar e organizar o histórico do Git.