O comando git rebase tem uma reputação de ser o vodu mágico do Git do qual os iniciantes devem ficar longe, mas ele, na verdade, pode facilitar muito a vida para uma equipe de desenvolvimento quando usado com cuidado. Neste artigo, vamos comparar o git rebase com o comando git merge relacionado e identificar todas as oportunidades potenciais para incorporar o rebase ao fluxo de trabalho típico do Git.

Visão geral conceitual

A primeira coisa a entender sobre o git rebase é que ele resolve o mesmo problema que git merge. Esses dois comandos são projetados para integrar as alterações de uma ramificação em outra—eles só fazem isso de maneiras muito diferentes.

Considere o que acontece quando você começa a trabalhar em um novo recurso em uma ramificação dedicada e, em seguida, outro membro da equipe atualiza a ramificação mestre com novas confirmações. Isso resulta em um histórico bifurcado, que deve ser familiar a todos que já usaram o Git como ferramenta de colaboração.

A forked commit history

Agora, digamos que as novas confirmações em mestre sejam relevantes para o recurso no qual você está trabalhando. Para incorporar as novas confirmações à sua ramificação de recurso, você tem duas opções: mesclagem ou rebase.

A opção Mesclar

A opção mais fácil é mesclar a ramificação mestre na ramificação de recurso usando algo parecido com o seguinte:

git checkout feature
git merge master

Ou você pode condensar isso em uma linha de comando:

git merge feature master

Isso cria uma nova “confirmação de mesclagem” na ramificação de recurso que se une aos históricos de ambas as ramificações, dando a você uma estrutura de ramificação semelhante ao seguinte:

Merging master into the feature branch

A mesclagem é boa porque é uma operação não destrutiva. As ramificações existentes não são alteradas de maneira nenhuma. Isso evita todas as possíveis armadilhas de rebase (discutidas abaixo).

Por outro lado, isso também significa que a ramificação de recurso terá uma confirmação de mesclagem estranha toda vez que for necessário incorporar as alterações de upstream. Se mestre for muito ativo, isso pode poluir um pouco o histórico da ramificação de recurso. Embora seja possível mitigar esse problema com opções avançadas de git log, pode ficar difícil para outros desenvolvedores entenderem o histórico do projeto.

A opção Rebase

Como uma alternativa à mesclagem, você pode fazer o rebase da ramificação de recurso na ramificação mestre usando os comandos a seguir:

git checkout feature
git rebase master

Isso move toda a ramificação de recurso para começar na ponta da ramificação mestre, incorporando efetivamente todas as novas confirmações em mestre. Mas em vez de usar uma confirmação de mesclagem, o rebase reescreve o histórico do projeto criando confirmações totalmente novas para cada confirmação na ramificação original.

Rebasing the feature branch onto master

O maior benefício do rebase é que você obtém um histórico de projeto muito mais limpo. primeiro, ele elimina as confirmações de mesclagem desnecessárias exigidas por git merge. Segundo, como é possível ver no diagrama acima, o rebase também resulta em um histórico de projeto perfeitamente linear—você pode seguir a ponta de recurso sempre até o início do projeto sem nenhuma bifurcação. Isso facilita a navegação em seu projeto com comandos como git log, git bisect e gitk.

Porém, há duas desvantagens para este histórico de confirmação intacto: segurança e rastreabilidade. Se você não seguir a Regra de ouro do rebase, reescrever o histórico do projeto pode ser potencialmente catastrófico para o fluxo de trabalho de colaboração. E, menos importante, o rebase perde o contexto fornecido por uma confirmação de mesclagem—não é possível ver quando as alterações de upstream foram incorporadas ao recurso.

Rebase interativo

O rebase interativo dá a você a oportunidade de alterar as confirmações conforme elas são movidas para a nova ramificação. Isso é ainda mais potente do que o rebase automatizado, uma vez que oferece controle completo sobre o histórico de confirmação da ramificação. Geralmente, isso é usado para limpar um histórico bagunçado, antes de mesclar a ramificação de um recurso para mestre.

Para iniciar uma sessão interativa de rebase, passe a opção i para o comando git rebase:

git checkout feature
git rebase -i master

Isso vai abrir um editor de texto, listando todas as confirmações que estão prestes a serem movidas:

pick 33d5b7a Mensagem para confirmação #1
pick 9480b3d Mensagem para confirmação #2
pick 5c67e61 Mensagem para confirmação #3

Essa listagem define exatamente qual será o aspecto da ramificação depois da execução do rebase. Ao alterar o comando pick e/ou reordenar as entradas, é possível fazer com que o histórico da ramificação seja parecido com o que você quiser. Por exemplo, se a 2ª confirmação corrigir um problema pequeno na 1ª confirmação, você poderá condensá-las em uma única confirmação com o comando fixup:

pick 33d5b7a Mensagem para confirmação #1
fixup 9480b3d Mensagem para confirmação #2
pick 5c67e61 Mensagem para confirmação #3

Quando você salva e fecha o arquivo, o Git vai executar o rebase de acordo com suas instruções, resultando em um histórico de projeto parecido com o seguinte:

Squashing a commit with an interactive rebase

Eliminar confirmações insignificantes como esta deixa o histórico do recurso muito mais fácil de entender. Isso é algo que o git merge simplesmente não pode fazer.

A regra de ouro da troca de base

Depois que você entende o que é o rebase, o mais importante a aprender é quando não fazê-lo. A regra de ouro de git rebase é nunca usá-lo em ramificações públicas.

Por exemplo, pense no que poderia acontecer se você tivesse feito rebase em mestre na ramificação do seu recurso:

Rebasing the master branch

O rebase move todas as confirmações em mestre para a ponta do recurso. O problema é que isso só aconteceu no seu repositório. Todos os outros desenvolvedores ainda estão trabalhando com o mestre original. Como o rebase resulta em confirmações totalmente novas, o Git vai pensar que o histórico da sua ramificação mestre divergiu do de todas as outras pessoas.

A única forma de sincronizar as duas ramificações mestre é mesclá-las, resultando em uma confirmação de mesclagem extra e dois conjuntos de confirmações que contêm as mesmas alterações (as originais e aquelas da sua ramificação com rebase feito). Nem é preciso dizer, esta é uma situação muito confusa.

Então, antes de executar o git rebase, sempre pergunte a si mesmo: “Mais alguém está olhando para esta ramificação?” Se a resposta for sim, tire suas mãos do teclado e comece a pensar sobre uma forma não destrutiva de fazer suas alterações (por exemplo, o comando git revert). Caso contrário, você está seguro para reescrever o histórico como quiser.

Forçar a colocação

Se você tentar colocar a ramificação mestre com rebase feito de volta em um repositório remoto, o Git não vai deixar você fazer isso porque entra em conflito com a ramificação mestre remota. Mas você pode forçar o push passando o sinalizador --force, da seguinte maneira:

# Tenha muito cuidado com este comando!
git push --force

Isso substitui a ramificação mestre remota para corresponder àquela com rebase feito do seu repositório e tornar as coisas muito confusas para o restante da equipe. Então, tome muito cuidado para usar este comando apenas quando souber exatamente o que você está fazendo.

Uma das únicas vezes que você deve forçar a colocação é quando executou uma limpeza local depois de ter enviado uma ramificação de recurso privado para um repositório remoto (por exemplo, para fins de backup). Isso é como dizer: “Opa, eu realmente não queria colocar essa versão original da ramificação de recurso. Use a atual no lugar dela”. Novamente, é importante que ninguém esteja trabalhando fora das confirmações da versão original da ramificação de recurso.

Passo a passo do fluxo de trabalho

O rebase pode ser incorporado ao seu fluxo de trabalho existente do Git na medida em que isso for confortável para sua equipe. Nesta seção, vamos dar uma olhada nos benefícios que o rebase pode oferecer nos vários estágios de desenvolvimento de um recurso.

A primeira etapa em qualquer fluxo de trabalho que utilize o git rebase é criar uma ramificação dedicada para cada recurso. Isso dá a você a estrutura de ramificação necessária para utilizar o rebase com segurança:

Developing a feature in a dedicated branch

Limpeza local

Uma das melhores maneiras de incorporar o rebase em seu fluxo de trabalho é limpar os recursos locais, em andamento. Ao executar periodicamente um rebase interativo, você pode assegurar que cada confirmação no recurso seja focada e útil. Isso permite escrever seu código sem se preocupar em quebrá-lo em confirmações isoladas—é possível corrigi-lo depois do fato.

Ao chamar git rebase, você tem duas opções para a nova base: a ramificação pai do recurso (por exemplo, mestre) ou uma confirmação anterior em seu recurso. Vimos um exemplo da primeira opção na seção Rebase interativo. A última opção é boa quando é necessário corrigir apenas as últimas confirmações. Por exemplo, o comando a seguir começa um rebase interativo apenas das 3 últimas confirmações.

git checkout feature
git rebase -i HEAD~3

Ao especificar HEAD~3 como a nova base, você não está realmente movendo a ramificação—está apenas reescrevendo de forma interativa as 3 confirmações que a seguem. Note que isso não vai incorporar as alterações de upstream na ramificação do recurso.

Rebasing onto Head~3

Se você quiser reescrever o recurso inteiro usando este método, o comando git merge-base pode ser útil para encontrar a base original da ramificação de recurso. O seguinte retorna o ID de confirmação da base original, que pode, então, ser passado para git rebase:

git merge-base feature master

Este uso do rebase interativo é uma excelente maneira de introduzir o git rebase no seu fluxo de trabalho, pois ele afeta somente ramificações locais. A única coisa que outros desenvolvedores vão ver é o produto acabado, que deve ser um histórico de ramificação de recurso limpo e fácil de seguir.

Mas, novamente, isso funciona apenas para ramificações de recursos privadas. Se você estiver colaborando com outros desenvolvedores por meio da mesma ramificação de recurso, esta ramificação será pública e você não terá permissão para reescrever seu histórico.

Não há nenhuma alternativa de git merge para limpar confirmações locais com um rebase interativo.

Como incorporar alterações de upstream em um recurso

Na seção Visão geral conceitual, vimos como uma ramificação de recurso pode incorporar alterações de upstream de mestre usando git merge ou git rebase. A mesclagem é uma opção segura que preserva o histórico inteiro do seu repositório, enquanto o rebase cria um histórico linear movendo a ramificação do recurso para a ponta de mestre.

Este uso de git rebase é semelhante a uma limpeza local (e pode ser realizado simultaneamente), mas no processo incorpora essas confirmações de upstream de mestre.

Lembre-se de que é perfeitamente legal fazer o rebase em uma ramificação remota em vez de em mestre. Isso pode acontecer ao colaborar no mesmo recurso com outro desenvolvedor e você precisa incorporar suas alterações em seu repositório.

Por exemplo, se você e outro desenvolvedor chamado John adicionaram confirmações à ramificação de recurso, o aspecto do seu repositório pode ser semelhante ao seguinte depois de procurar a ramificação de recurso remota do repositório de John:

Collaborating on the same feature branch

Você pode resolver essa bifurcação exatamente da mesma maneira que integra alterações de upstream de mestre: mescle o recurso local com john/recurso ou faça o rebase do recurso local na ponta de john/recurso.

Merging vs. rebasing onto a remote branch

Note que esse rebase não viola a Regra de outro de rebase porque somente suas confirmações de recurso locais estão sendo movidas—tudo antes disso está intocado. Isso é como dizer: “adicione minhas alterações ao que John já fez”. Na maioria das circunstâncias, isso é mais intuitivo do que sincronizar com a ramificação remota por meio de uma confirmação de mesclagem.

Por padrão, o comando git pull executa uma mesclagem, mas você pode forçá-lo a integrar a ramificação remota a um rebase passando para ele a opção --rebase.

Como revisar um recurso com uma solicitação pull

Se usar solicitações pull como parte do seu processo de revisão de código, você precisa evitar usar git rebase depois de criar a solicitação pull. Assim que você fizer a solicitação pull, outros desenvolvedores estarão olhando suas confirmações, o que significa que é uma ramificação pública. Reescrever seu histórico fará com que seja impossível para o Git e para seus colegas de equipe rastrear qualquer confirmação de sequência adicionada ao recurso.

Qualquer alteração de outros desenvolvedores precisa ser incorporada com git merge em vez de git rebase.

Por este motivo, geralmente é uma boa ideia limpar seu código com um rebase interativo antes de enviar sua solicitação pull.

Como integrar um recurso aprovado

Depois que um recurso é aprovado pela sua equipe, você tem a opção de fazer o rebase dele para a ponta da ramificação mestre antes de usar git merge para integrar o recurso à base de código principal.

Esta é uma situação semelhante a incorporar alterações de upstream a uma ramificação de recurso, mas como você não tem permissão para reescrever confirmações na ramificação mestre, tem que eventualmente usar git merge para integrar o recurso. No entanto, ao executar um rebase antes da mesclagem, há a certeza de que a mesclagem vai ser de avanço rápido, resultando em um histórico perfeitamente linear. Isso também faz com que seja possível suprimir qualquer confirmação de sequência adicionada durante uma solicitação pull.

Integrating a feature into master with and without a rebase

Se você não estiver totalmente confortável com git rebase, pode sempre executar o rebase em uma ramificação temporária. Dessa forma, se você acidentalmente bagunçar o histórico do seu recurso, pode verificar a ramificação original e tentar novamente. Por exemplo:

git checkout feature
git checkout -b temporary-branch
git rebase -i master
# [Clean up the history]
git checkout master
git merge temporary-branch

Resumo

E isso é tudo o que você realmente precisa saber para começar o rebase de suas ramificações. Se preferir um histórico limpo, linear, livre de confirmações de mesclagem desnecessárias, você deve usar git rebase em vez de git merge ao integrar alterações de outra ramificação.

Por outro lado, se quiser preservar o histórico completo do seu projeto e evitar o risco de reescrever confirmações públicas, você pode inserir git merge. Qualquer uma das opções é perfeitamente válida, mas pelo menos agora você tem a opção de aproveitar os benefícios de git rebase.