git merge
uma ferramenta integral para combinar ramos. No entanto, não é o único que o Git oferece.



Como alternativa ao cenário acima, você pode combinar os ramos com o git rebase
comando. Em vez de amarrar as ramificações com uma confirmação de mesclagem, o rebase move toda a ramificação do recurso para a ponta de master
como mostrado abaixo.



Isso serve para o mesmo propósito que git merge
, integrando commits de diferentes branches. Mas há duas razões pelas quais podemos optar por um rebase em vez de uma mesclagem:
- Isso resulta em um histórico de projeto linear.
- Isso nos dá a oportunidade de limpar os commits locais.
Neste tutorial, exploraremos esses dois casos de uso comuns de git rebase
. Infelizmente, os benefícios de git rebase
venha em uma troca. Quando usado incorretamente, pode ser uma das operações mais perigosas que você pode realizar em um repositório Git. Portanto, também examinaremos cuidadosamente os perigos do rebase.
Pré-requisitos
Este tutorial pressupõe que você esteja familiarizado com os comandos básicos do Git e os fluxos de trabalho de colaboração. Você deve se sentir confortável em testar e enviar snapshots, desenvolver recursos em branches isolados, mesclar branches e enviar/puxar branches de/para repositórios remotos.
1. Rebase para uma história linear
O primeiro caso de uso que exploraremos envolve um histórico de projeto divergente. Considere um repositório onde sua ramificação de produção avançou enquanto você estava desenvolvendo um recurso:



Para rebasear o feature
ramo para o master
branch, você executaria os seguintes comandos:
1 |
git checkout feature |
2 |
git rebase master |
Isso transplanta o feature
ramo de sua localização atual até a ponta do master
filial:



Há dois cenários em que você gostaria de fazer isso. Primeiro, se o recurso dependia dos novos commits em master
, agora teria acesso a eles. Em segundo lugar, se o recurso estivesse completo, agora seria configurado para uma mesclagem de avanço rápido em master
. Em ambos os casos, o rebase resulta em um histórico linear, enquanto git merge
resultaria em confirmações de mesclagem desnecessárias.
Por exemplo, considere o que aconteceria se você integrasse os commits upstream com uma mesclagem em vez de um rebase:
1 |
git checkout feature |
2 |
git merge master |
Isso nos daria uma confirmação de mesclagem extra no feature
filial. Além do mais, isso aconteceria toda vez que você quisesse incorporar upstream commits em seu recurso. Eventualmente, o histórico do seu projeto estaria repleto de confirmações de mesclagem sem sentido.



Esse mesmo benefício pode ser visto ao se fundir na outra direção. Sem rebase, integrando o acabado feature
ramo em master
requer uma confirmação de mesclagem. Embora este seja realmente um commit de mesclagem significativo (no sentido de que representa um recurso concluído), o histórico resultante está cheio de bifurcações:



Quando você rebase antes da fusão, o Git pode avançar rapidamente master
para a ponta de feature
. Você encontrará uma história linear de como seu projeto progrediu no git log
output—os commits em feature
são cuidadosamente agrupados em cima dos commits em master
. Este não é necessariamente o caso quando as ramificações são vinculadas com uma confirmação de mesclagem.



Resolução de Conflitos
Quando você corre git rebase
, Git pega cada commit no ramo e os move, um por um, para a nova base. Se algum desses commits alterar a(s) mesma(s) linha(s) de código que os commits upstream, isso resultará em um conflito.



O git merge
O comando permite resolver todos os conflitos da ramificação no final da mesclagem, que é um dos propósitos principais de uma confirmação de mesclagem. No entanto, funciona de maneira um pouco diferente quando você está rebaseando. Os conflitos são resolvidos por confirmação. Então se git rebase
encontrar um conflito, ele interromperá o procedimento de rebase e exibirá uma mensagem de aviso:
1 |
Auto-merging readme.txt |
2 |
CONFLICT (content): Merge conflict in readme.txt |
3 |
Failed to merge in the changes.
|
4 |
.... |
5 |
When you have resolved this problem, run "git rebase --continue". |
6 |
If you prefer to skip this patch, run "git rebase --skip" instead.
|
7 |
To check out the original branch and stop rebasing, run "git rebase --abort". |
Visualmente, é assim que o histórico do seu projeto se parece quando git rebase
encontra um conflito:



Os conflitos podem ser inspecionados executando git status
. A saída é muito semelhante a um conflito de mesclagem:
1 |
Unmerged paths: |
2 |
(use "git reset HEAD |
3 |
(use "git add |
4 |
|
5 |
both modified: readme.txt |
6 |
|
7 |
no changes added to commit (use "git add" and/or "git commit -a") |
Para resolver o conflito, abra o arquivo em conflito (readme.txt no exemplo acima), localize as linhas afetadas e edite-as manualmente para obter o resultado desejado. Em seguida, diga ao Git que o conflito foi resolvido ao preparar o arquivo:
Observe que esta é exatamente a mesma maneira que você marca um git merge
conflito como resolvido. Mas lembre-se de que você está no meio de um rebase — você não quer esquecer o resto dos commits que precisam ser movidos. A última etapa é dizer ao Git para terminar o rebase com o --continue
opção:
Isso moverá o restante dos commits, um por um, e se algum outro conflito surgir, você terá que repetir esse processo novamente.
Se você não deseja resolver o conflito, pode optar pelo --skip
ou --abort
bandeiras. O último é particularmente útil se você não tem ideia do que está acontecendo e só quer voltar para a segurança.
1 |
# Ignore the commit that caused the conflict
|
2 |
git rebase --skip
|
3 |
|
4 |
# Abort the entire rebase and go back to the drawing board
|
5 |
git rebase --abort
|
2. Rebase para limpar commits locais
Até agora, só usamos git rebase
para mover galhos, mas é muito mais poderoso do que isso. Ao passar o -i
sinalizador, você pode iniciar uma sessão de rebase interativa. O rebase interativo permite definir com precisão como cada confirmação será movida para a nova base. Isso lhe dá a oportunidade de limpar o histórico de um recurso antes de compartilhá-lo com outros desenvolvedores.
Por exemplo, digamos que você terminou de trabalhar em seu feature
ramo e você está pronto para integrá-lo em master
. Para iniciar uma sessão de rebase interativa, execute o seguinte comando:
1 |
git checkout feature |
2 |
git rebase -i master
|
Isso abrirá um editor contendo todos os commits em feature
que estão prestes a ser movidos:
1 |
pick 5c43c2b [Description for oldest commit] |
2 |
pick b8f3240 [Description for 2nd oldest commit] |
3 |
pick c069f4a [Description for most recent commit] |
Esta listagem define o que feature
branch vai ficar depois do rebase. Cada linha representa um commit e o pick
O comando antes de cada hash de confirmação define o que acontecerá com ele durante o rebase. Observe que os commits são listados do mais antigo para o mais recente. Ao alterar esta listagem, você obtém controle total sobre o histórico do seu projeto.
Se você quiser alterar a ordem dos commits, basta reordenar as linhas. Se você quiser alterar a mensagem de um commit, use o reword
comando. Se você deseja combinar dois commits, altere o pick
comando para squash
. Isso rolará todas as alterações nesse commit para o que está acima dele. Por exemplo, se você esmagou o segundo commit na listagem acima, o feature
branch ficaria assim depois de salvar e fechar o editor:



O edit
comando é particularmente poderoso. Quando atingir o commit especificado, o Git pausará o procedimento de rebase, como quando encontra um conflito. Isso lhe dá a oportunidade de alterar o conteúdo do commit com git commit --amend
ou até adicionar mais commits com o padrão git add
/git commit
comandos. Quaisquer novos commits que você adicionar farão parte do novo branch.
O rebase interativo pode ter um impacto profundo em seu fluxo de trabalho de desenvolvimento. Em vez de se preocupar em dividir suas alterações em confirmações encapsuladas, você pode se concentrar em escrever seu código. Se você acabou confirmando o que deveria ser uma única alteração em quatro instantâneos separados, isso não é um problema – reescreva o histórico com git rebase -i
e esmagá-los todos em um commit significativo.
3. Perigos do Rebase
Agora que você tem uma compreensão git rebase
, podemos falar sobre quando não usá-lo. Internamente, o rebase não move os commits para um novo branch. Em vez disso, ele cria novos commits que contêm as alterações desejadas. Com isso em mente, o rebasing é melhor visualizado da seguinte forma:



Após o rebase, os commits em feature
terá diferentes hashes de confirmação. Isso significa que não apenas reposicionamos uma ramificação – literalmente reescrevemos a história do nosso projeto. Este é um efeito colateral muito importante da git rebase
.
Quando você está trabalhando sozinho em um projeto, reescrever a história não é grande coisa. No entanto, assim que você começar a trabalhar em um ambiente colaborativo, isso pode se tornar muito perigoso. Se você reescrever commits que outros desenvolvedores estão usando (por exemplo, commits no master
branch), parecerá que esses commits desapareceram na próxima vez que tentarem puxar seu trabalho. Isso resulta em um cenário confuso do qual é difícil se recuperar.
Com isso em mente, você nunca deve rebasear commits que foram enviados para um repositório público, a menos que você tenha certeza de que ninguém baseou seu trabalho neles.
Conclusão
Este tutorial apresentou os dois casos de uso mais comuns de git rebase
. Conversamos muito sobre como mover ramificações, mas lembre-se de que o rebase é realmente sobre o controle do histórico do projeto. O poder de reescrever confirmações após o fato libera você para se concentrar em suas tarefas de desenvolvimento, em vez de dividir seu trabalho em instantâneos isolados.
Observe que o rebase é uma adição totalmente opcional à sua caixa de ferramentas do Git. Você ainda pode fazer tudo o que precisa com o velho git merge
comandos. Na verdade, isso é mais seguro, pois evita a possibilidade de reescrever a história pública. No entanto, se você entender os riscos, git rebase
pode ser uma maneira muito mais limpa de integrar branches em comparação com merge commits.