Git do jeito fácil: altere o histórico com rebase — parte I
Or Kamara
9 de junho de 2020
0 minutos de leituraRecentemente, trabalhei em um recurso grande, com várias mudanças em diferentes partes do código. Para deixar o PR desse recurso mais fácil de revisar, dividi toda a funcionalidade em vários commits. Com o tempo, logo percebi que essa divisão não bastava: havia muitas mudanças a fazer em cada um daqueles commits, seja por causa de problemas que encontrei, seja para atender aos ajustes da revisão de código.
Naquele momento, eu já conhecia o básico do mágico comando interativo git rebase, que você pode usar para “reescrever seu histórico”. Mas as mudanças que eu precisava fazer eram mais complexas do que meu conhecimento básico permitia, então comecei a explorar as opções desse comando.
Para quem ainda não conhece o comando, este é o resultado de executar git rebase -i BRANCH:
Lembro perfeitamente da primeira vez que executei esse comando. Minha reação foi algo como: Tááá, o que está acontecendo aqui? Prometeram uma maneira interativa e fácil de alterar meus commits, mas tudo o que vejo é uma interface supercomplexa… Precisei literalmente dedicar um tempo para ler sobre cada uma daquelas opções, porque, depois de sair dessa tela, eu poderia perder todo o trabalho que tinha feito (#DesenvolvedorComMedo).
Até sair da tela e cancelar tudo foi uma experiência complicada… Precisei pesquisar no Google como fazer isso e descobri que era só apagar todas as linhas de commits para não haver mudanças. Tão fácil (e nada intuitivo). Meu primeiro pensamento foi: “Mas espera, não confio muito em tudo o que dizem no Google. E se eu fizer isso e todo o meu trabalho desaparecer?”
Então, decidi que não havia outra saída e comecei a investigar esses comandos.
Neste artigo, quero explicar alguns problemas que desenvolvedores precisam resolver ao trabalhar em PRs com vários commits. Não vou explicar cada detalhe desses comandos, pois há vários artigos que explicam o que acontece nos bastidores. Em vez disso, vou apresentar alguns cenários que todos nós podemos encontrar ao trabalhar com git e sugerir a solução adequada usando git rebase -i.
Como este é um assunto bastante extenso, vou dividir o artigo em duas partes. Nesta parte, vou explicar os fundamentos do Rebase e apresentar dois cenários comuns. Na segunda parte, vou apresentar mais quatro cenários comuns e algumas conclusões.
Sem mais delongas, vamos à primeira parte!
Rebase em poucas palavras
Vamos começar com o seguinte cenário: você e um amigo estão trabalhando em um novo projeto empolgante. Vocês acabaram de criar o repositório (e fazer o primeiro commit, chamado A) e agora é hora de programar. Para trabalhar com mais eficiência, vocês decidiram dividir as tarefas. Você ficou responsável pelo recurso principal, e seu amigo, pela estrutura do aplicativo.
O ponto de partida dos dois é o mesmo: o commit A. Depois de cinco horas de trabalho intenso, você quer terminar e integrar as mudanças à master. Seu amigo é o primeiro a integrar o trabalho e cria três novos commits a partir de A: B + C + D. Agora é sua vez de integrar na master todo o seu trabalho da FeatureBranch: os commits E + F + G. É assim que fica:
Para testar a funcionalidade que você desenvolveu, precisa usar algumas funções da estrutura do aplicativo, mais especificamente as do commit D. Em outras palavras, você precisa alterar a base da sua branch do commit A para o commit D, como se tivesse criado a branch a partir da versão mais recente:
É exatamente para isso que o git rebase serve: mover uma sequência de commits para uma nova base. Nos bastidores, o git cria novos commits e os aplica à base especificada.
Mais especificamente, neste artigo, vamos falar sobre o modo interativo.
Ao executar o rebase com a opção -i, você pode alterar commits individuais durante o processo básico de rebase — é aí que a mágica começa! No modo interativo, você escolhe o que fazer com cada commit: juntar, dividir, renomear, reordenar e muito mais.
Vamos entender cada uma dessas opções com alguns cenários comuns em que o git rebase pode ser útil.
Cenários comuns (e como lidar com eles!)
Vamos ver alguns cenários comuns em que você pode querer alterar o histórico do seu código e como fazer isso com o Git:
1. Alterar a mensagem de um commit antigo (reword)
Você fez várias mudanças no código e as dividiu perfeitamente em diferentes commits, mas depois percebeu um erro de digitação em uma das mensagens:
Como você provavelmente notou, a mensagem do segundo commit é 3nd commit (ops!) — vamos corrigir isso.
Se o erro estivesse no último commit, poderíamos usar o comando git commit --amend. Mas, como ele está no meio do nosso histórico do git, precisamos fazer algo mais complexo.
Neste caso, vamos usar o comando git rebase -i master (master pode ser outra branch) e escolher a opção reword para o segundo commit (a21bd1d):
Depois de salvar o arquivo, um novo editor será aberto para que você possa editar a mensagem do commit:
Agora, basta alterar a primeira linha para a nova mensagem desejada, feat: 2nd commit, e salvar novamente.
Agora o histórico do git está corrigido:
2. Alterar o conteúdo de um commit antigo (squash, fixup)
Você acabou de receber comentários da revisão de código de um colega e precisa fazer pequenas mudanças: adicionar um novo arquivo e alterar o conteúdo de outro que já existe.
Como você provavelmente sabe, cada commit deve representar um bloco de lógica, não apenas mudanças em arquivos. Uma coisa que devemos evitar é usar uma mensagem como CR fixes ou, no nosso caso, até mesmo really small CR fixes. O ideal é juntar todos os commits para que, no final, cada um contenha todas as mudanças relevantes, incluindo os ajustes da revisão de código.
Como fazer isso? É aí que as opções squash e fixup entram em cena.
Primeiro, vamos criar commits temporários com as mudanças necessárias:
No exemplo acima, criamos dois novos commits:
5641a86— altera o conteúdo do arquivo criado no primeiro commit17a73c1— adiciona um novo arquivo ao segundo commit
Mas agora não queremos manter o histórico do git desse jeito. Queremos juntar 5641a86 com 271c66e e 17a73c1 com e2952e7. Para isso, devemos executar o comando git rebase -i master. Como da última vez, esta é a primeira tela que veremos:
Agora, vamos reordenar as linhas de acordo com a ordem desejada dos commits e usar a opção squash nos commits 5641a86 e 17a73c1:
Com essas mudanças, o git vai processar os commits (de 271c66e a fdb5f71) e incorporar o conteúdo dos commits marcados com squash ao commit anterior. Agora o histórico do git está exatamente como queríamos!
Outra opção que podemos usar em vez de squash é fixup. As duas fazem praticamente a mesma coisa, mas fixup descarta a mensagem de log do commit.
Uma observação importante: às vezes, ao juntar commits, podemos gerar conflitos no código. Quando o git encontra conflitos, você precisa resolvê-los, preparar as mudanças e continuar o rebase executando git rebase --continue. Lembre-se de que você sempre pode executar git rebase --abort se não souber o que fazer. Esse comando interrompe o rebase e restaura o repositório ao estado original, antes de executar git rebase -i.
Para concluir
E é isso para a primeira parte sobre como alterar o histórico do git usando Rebase! Na segunda parte, vamos apresentar mais quatro cenários comuns e sugestões de como lidar com eles.
Comece a jogar Capture the Flag
Aprenda a resolver desafios de Capture the Flag assistindo à gravação sob demanda do nosso workshop virtual introdutório.
