git rebase

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.

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

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 main 
git checkout -b feature_branch main
# 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

  • 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  main
        \
         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 main 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  main
     \
      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.

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

Pronto(a) para aprender Git?

Tente este tutorial interativo.

Comece agora mesmo