git rebase

Este documento vai servir como uma discussão aprofundada do comando git rebase. O comando rebase também foi abordado nas páginas configurar um repositório e reescrever o histórico. Esta página vai ter uma abordagem mais aprofundada sobre a configuração e a execução do git rebase. Casos de uso e armadilhas comuns do rebase vão ser tratados aqui.

Rebase é um dos dois utilitários do Git que se especializam em integrar alterações da ramificação para outra. O outro utilitário de integração de alterações é o git merge. A mesclagem (merge) é uma alteração de registro de avanço. Como outra opção, o rebase tem recursos poderosos para reescrever o histórico. Para saber mais sobre as diferenças entre mesclagem e rebase, acesse o guia mesclagem x rebase. O rebase tem 2 modos principais: os modos "manual" e "interativo". Vamos falar sobre os diferentes modos de rebase com mais informações abaixo.

O que é o git rebase?

Rebasing é o processo de mover ou combinar uma sequência de commits para um novo commit base. O rebasing é mais útil e melhor visualizado no contexto do fluxo de trabalho de ramificação de recurso. O processo geral pode ser visualizado da seguinte forma:

Tutorial Git: Git rebase

A partir da perspectiva de conteúdo, o rebase é o processo de alterar a base da ramificação do commit para outra, fazendo parecer como se você criou a ramificação a partir de um commit diferente. De um jeito intrínseco, o Git realiza isso criando novos commits e aplicando-os à base especificada. É muito importante entender que, mesmo que a ramificação pareça a mesma, ela é composta de commits novos completos.

Uso

O principal motivo para fazer o rebase é manter um histórico de projeto linear. Por exemplo, considere uma situação em que a branch principal progrediu desde que você começou a trabalhar em uma ramificação de recurso. Você quer colocar as atualizações mais recentes da branch principal na ramificação de recurso, no entanto você também quer o histórico da ramificação limpo, para que pareça que você esteve trabalhando com a branch principal mais recente. Isto te dá o benefício posterior da mesclagem limpa da ramificação de recurso de volta para a branch principal. Por que é interessante manter um "histórico limpo"? Os benefícios de ter um histórico limpo se tornam tangíveis ao realizar operações do Git para investigar a introdução da regressão. Um cenário mais realista seria:

  1. Um bug foi identificado no branch principal. Um recurso que estava funcionando bem agora está com falha.
  2. Um desenvolvedor examina o histórico do branch principal usando git log, por causa do "histórico limpo", o desenvolvedor é capaz de analisar logo o histórico do projeto.
  3. O desenvolvedor agora não consegue identificar quando o bug foi introduzido usando git log, então, ele executa git bisect.
  4. Como o histórico do git está limpo, o git bisect tem um conjunto refinado de commits para comparar ao observar a regressão. O desenvolvedor encontra logo o commit que introduziu o bug e consegue agir de acordo.

Saiba mais sobre git log e git bisect nas páginas de uso individuais.

Você tem duas opções para integrar seu recurso na branch principal: mesclar com objetividade ou fazer o rebase e mesclar depois disso. A primeira opção resulta em uma mesclagem de 3 vias e um commit de mesclagem, enquanto a segunda resulta em uma mesclagem de avanço rápido e um histórico linear ideal. O seguinte diagrama demonstra como fazer o rebase na branch principal facilita uma mesclagem de avanço rápido.

Git rebase: Ramificar para dentro da branch principal

O rebase é um método comum para integrar alterações de upstream no repositório local. Colocar alterações de upstream com o Git merge resulta no commit de mesclagem supérfluo toda vez que você queira ver como o projeto progrediu. Por outro lado, o rebase é como dizer "Eu quero que minhas alterações sejam baseadas no que todo mundo já fez."

Não faça o rebase no histórico público

Conforme já discutido em reescrever histórico, você nunca deve fazer o rebase em commits após eles serem colocados no repositório público. O rebase faria substituição dos antigos commits por novos commits e seria como se aquela parte do histórico do projeto tivesse desaparecido do nada.

Git Rebase Standard x Git Rebase Interactive

Git rebase interactive é quando o git rebase aceita um argumento -- i. O "i" representa "Interactive." Sem nenhum argumento, o comando é executado no modo padrão. Em ambos os casos, vamos supor que a gente criou uma ramificação de recurso separada.

# Create a feature branch based off of master 
git checkout -b feature_branch master 
# Edit files 
git commit -a -m "Adds new feature" 

O Git rebase no modo padrão pega com agilidade os commits na ramificação de trabalho atual e os aplica no cabeçalho da ramificação passada.

git rebase <base>

Assim vai ser feito o rebase automático da ramificação atual para a , que pode ser qualquer tipo de referência de commit (por exemplo, ID, nome de ramificação, marcador ou referência relativa ao HEAD).

Executar o git rebase com a bandeira -i dá início a uma sessão de rebasing interativa. Em vez de mover todos os commits para a nova base sem conhecimento, o rebasing interativo oferece a oportunidade de alterar os commits individuais no processo. Assim você pode limpar o histórico removendo, dividindo e alterando uma série existente de commits. É como uma versão aprimorada do Git commit --amend.

git rebase --interactive <base>

Assim vai ser feito o rebase da ramificação atual para , mas usando uma sessão de rebasing interativa. Essa ação abre um editor onde você pode inserir comandos (descritos abaixo) para cada commit que vai passar pelo rebasing. Estes comandos determinam como os commits individuais vão ser transferidos para a nova base. Também é possível reordenar a lista de commits para alterar a ordem dos próprios commits. Uma vez que você especificou os comandos para cada commit no rebase, o Git vai começar a reproduzir os commits aplicando os comandos de rebase. Os comandos de edição de rebasing são os seguintes:




pick 2231360 some old commit
pick ee2adc2 Adds new feature


# Rebase 2cf755d..ee2adc2 onto 2cf755d (9 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit

Comandos de rebase adicionais

Conforme especificado na página reescrever histórico, o rebase pode ser usado para alterar commits múltiplos e mais antigos, bem como arquivos com commit e múltiplas mensagens. Embora estas sejam as aplicações mais comuns, o git rebase também tem mais opções de comando que podem ser úteis em aplicações mais complexas.

  • git rebase -- d significa que durante a reprodução o commit vai ser descartado do bloco de commit combinado final.
  • git rebase -- p deixa o commit como está. Ele não vai modificar a mensagem ou conteúdo do commit e ainda vai ser um commit individual no histórico de ramificações.
  • git rebase -- x durante a reprodução, executa um script do shell da linha de comandos em cada commit marcado. Um exemplo útil seria executar o conjunto de teste da base de código em commits específicos, o que poderia ajudar a identificar as regressões durante um rebase.

Recapitulando

O rebasing interativo lhe oferece controle completo sobre como o histórico do projeto se parece. Isto dá muita liberdade aos desenvolvedores, pois permite a eles fazer o commit de um histórico "bagunçado" enquanto eles estão focados em escrever código e, então, voltar e limpar mais tarde.

A maioria dos desenvolvedores gosta de utilizar um rebase interativo para polir uma ramificação de recurso antes de mesclar na base de código principal. Isto dá a eles a oportunidade de fazer squash em commits insignificantes, excluir commits obsoletos e se certificar de que todo o restante está em ordem antes de confirmar no histórico "oficial" do projeto. Para as demais pessoas, vai parecer que todo o recurso foi desenvolvido em uma única série de commits bem planejados.

O verdadeiro poder do rebasing interativo pode ser visto no histórico da branch principal resultante. Para as demais pessoas, é como se você fosse um desenvolvedor incrível, que implementou o novo recurso com a quantidade perfeita de commits de primeira. É assim que o rebasing interativo pode manter o histórico do projeto limpo e significativo.

Opções de configuração

Existem algumas propriedades de rebase que podem ser definidas usando o git config. Estas opções vão alterar a aparência do resultado do git rebase.

  • rebase.stat: um booleano que é definido como falso por padrão. A opção alterna a exibição do conteúdo diffstat visual que mostra o que mudou desde o último debase.
  • rebase.autoSquash: um valor booleano que alterna o comportamento do --autosquash.
  • rebase.missingCommitsCheck: Pode ser definido para diversos valores que mudam o comportamento do rebase em relação a commits ausentes.
warn Imprime uma saída de aviso no modo interativo que informa sobre commits removidos

error

Interrompe o rebase e imprime mensagens de aviso de commits removidos

ignore

Configurado por padrão, ignora quaisquer avisos de commits ausentes
  • rebase.instructionFormat: Uma cadeia de formato git log que vai ser usada para formatar a exibição do rebase interativo

Aplicação avançada de rebase

O argumento da linha de comandos --onto pode ser passado ao git rebase. No modo --onto do git rebase, o comando é expandido para:

 git rebase --onto <newbase> <oldbase>

O comando --onto habilita uma forma mais poderosa de rebase, que permite a passagem de refs específicas para as pontas do rebase.
Vamos supor que temos um repositório de exemplo, com ramificações da seguinte forma:


   o---o---o---o---o  master
        \
         o---o---o---o---o  featureA
              \
               o---o---o  featureB

featureB está baseado no featureA; no entanto, é possível notar que o featureB não depende de nenhuma das alterações no featureA e pode ser ramificado com facilidade com o branch principal.

 git rebase --onto master featureA featureB

featureA is the < oldbase >. master becomes the < newbase > and featureB is reference for what HEAD of the < newbase > will point to. The results are then:

 
                      o---o---o  featureB
                     /
    o---o---o---o---o  master
     \
      o---o---o---o---o  featureA
                           

Entender os perigos do rebase

Uma ressalva a ser considerada ao trabalhar com o Git Rebase é que os conflitos de mesclagem podem se tornar mais frequentes durante um fluxo de trabalho de rebase. É o que ocorre caso você tenha uma ramificação antiga que se desviou do branch principal. Em determinadas ocasiões você vai querer fazer o rebase contra o branch principal e nesse momento ele vai poder conter muitos novos commits que vão conflitar com as alterações da ramificação. Esses casos podem ser resolvidos com facilidade fazendo o rebase da ramificação com frequência contra o branch principal e fazendo commits mais frequentes. Os argumentos da linha de comando --continue e --abort podem ser passados para o git rebase para avançar ou reiniciar o rebase ao lidar com conflitos.

Uma ressalva de rebase mais séria é a perda de commits ao reescrever o histórico interativo. Executar o rebase no modo interativo e executar subcomandos como squash ou drop vai remover os commits do log imediato da ramificação. À primeira vista, pode parecer que os commits desapareceram de vez. Usando o git reflog, esses commits podem ser restaurados e todo o rebase pode ser desfeito. Para mais informações sobre como usar o git reflog para encontrar commits perdidos, acesse a página de documentação do Git reflog.

O Git Rebase em si não é tão perigoso. Os casos de verdadeiro perigo surgem ao executar rebases interativos com reescrita de histórico e colocar os resultados em uma ramificação remota que é compartilhada por outros usuários. Este padrão deve ser evitado, pois ele tem o potencial de substituir o trabalho remoto de outros usuários quando eles fizerem o pull.

Recuperação a partir do rebase upstream

Caso outro usuário tenha feito o rebase e forçou o envio para a ramificação que você está fazendo o commit, um git pull vai então substituir quaisquer commits baseados naquela ramificação anterior com a ponta que foi forçada. Por sorte, usando o git reflog você pode obter o reflog da ramificação remota. No reflog da ramificação remota, você pode encontrar uma ref anterior ao rebase. Você pode então fazer o rebase da ramificação contra esta ref remota, usando a opção --onto, conforme discutido acima na seção Aplicação avançada de rebase.

Resumo

Neste artigo, foi abordado o uso do git rebase. Casos de uso básicos e avançados, bem como exemplos mais avançados foram abordados. Alguns dos pontos principais de discussão são:

  • git rebase padrão vs. modos interativos
  • opções de configuração do git rebase
  • git rebase --onto
  • commits perdidos do git rebase

A gente viu o uso do git rebase com outras ferramentas como git reflog, git fetch e git push. Visite as páginas correspondentes para mais informações.

Pronto(a) para aprender Git?

Tente este tutorial interativo.

Comece agora mesmo