Skip to main content

Git sem complicação: alterando o histórico com rebase — parte II

Escrito por
Headshot of Or Kamara

Or Kamara

17 de junho de 2020

0 minutos de leitura

Bem-vindo de volta à segunda parte sobre como trabalhar com Git sem complicação!

No artigo anterior, falamos sobre os conceitos básicos do rebase e duas situações comuns que podem surgir ao trabalhar com Git. Você encontra a primeira parte aqui. Nesta parte, vamos abordar mais quatro situações comuns e algumas conclusões.

Vamos direto ao ponto!

Situações comuns (e como lidar com elas!) — parte II

Vamos retomar de onde paramos da última vez, com algumas situações comuns em que você pode querer alterar o histórico do seu código e como fazer isso usando Git:

1. Dividir um commit antigo em dois (edit)

Digamos que você acabou de perceber que cometeu um erro ao juntar 2 commits (veja a parte I para mais detalhes) e quer desfazer o squash de 17a73c1 em e2952e7, para que o novo arquivo fique em um commit diferente. A solução é executar todos os commits até o ponto específico que você quer editar, parar, fazer as alterações e continuar. Ou seja, use a edit opção.

Vamos começar executando git rebase -i master novamente, mas desta vez com a opção edit no commit que queremos dividir (o segundo commit, `6871683`):

pick 682c27f feat: 1st commit
edit 6871683 feat: 2nd commit
pick 1b2f5b1 feat: 3rd commit

Ao sair da tela do rebase, veremos:

Stopped at 6871683...  feat: 2nd commit
You can amend the commit now, with

  git commit --amend '-S'

Once you are satisfied with your changes, run

  git rebase --continue

Basicamente, o Git executou o primeiro e o segundo commits, parou e agora a bola está com a gente. Podemos fazer todas as alterações que quisermos!

Este é o grafo do Git neste momento:

* 6871683 (HEAD) feat: 2nd commit* 682c27f feat: 1st commit
* 24aa86a (master) Init: Git History

Como mencionamos antes, queremos dividir o segundo commit em dois commits diferentes, um para cada arquivo. O primeiro passo é desfazer as alterações do segundo commit executando git reset --soft HEAD^. O comando reset altera o commit para o qual a branch HEAD está apontando. Com a opção --soft, mantemos o índice e o diretório de trabalho, enquanto todos os arquivos alterados entre o HEAD original e o commit ficam preparados para commit.

Este é o novo status do Git. Como você pode ver, os dois arquivos estão em stage:

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
 modified:   b.txt
 new file:   b2.txt

Agora, vamos tirar b2.txt da lista de arquivos em stage executando git restore --staged b2.txt:

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
 modified:   b.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)
 b2.txt

Com apenas b.txt em stage, podemos fazer o commit da primeira parte do segundo commit. Depois, colocamos b2.txt em stage e fazemos o commit da segunda parte:

❯ git commit -m "feat: 2nd commit - part 1"
❯ git add b2.txt
❯ git commit -m "feat: 2nd commit - part 2"

Agora, nosso grafo do Git está assim:

* 96127c6 feat: 2nd commit - part 2
* 93260e5 feat: 2nd commit - part 1
* 9882b2f feat: 1st commit
* 24aa86a (master) Init: Git History

O último passo é continuar o rebase e executar os commits restantes com:

git rebase --continue. O grafo final do Git deve ficar assim:

* 09fe03e (HEAD -> feat/git-history) feat: 3rd commit
* 96127c6 feat: 2nd commit - part 2
* 93260e5 feat: 2nd commit - part 1
* 9882b2f feat: 1st commit
* 24aa86a (master) Init: Git History

2. Modificar todos os arquivos em um commit específico (edit)

Você provavelmente conhece a próxima situação: enviou todo o seu código, perfeitamente dividido em vários commits, mas depois percebeu que esqueceu de executar o linter. Como explicamos antes, a solução não é criar outro commit chamado fix linting, mas corrigir o lint em cada commit.

Se a alteração for pequena, podemos usar a opção squash. Mas, se houver várias alterações em vários commits, o melhor é executar o script de lint um a um em cada commit (ou nos commits específicos que queremos corrigir). Para isso, podemos usar novamente a opção edit (como fizemos no exemplo anterior).

Vamos executar git commit rebase -i novamente, usando a opção edit no primeiro commit (um commit com vários arquivos):

edit 9882b2f feat: 1st commit
pick 93260e5 feat: 2nd commit - part 1
pick 96127c6 feat: 2nd commit - part 2
pick 09fe03e feat: 3rd commit

Como antes, o Git para depois de executar o primeiro commit:

Stopped at 9882b2f...  feat: 1st commit
You can amend the commit now, with

  git commit --amend '-S'

Once you are satisfied with your changes, run

  git rebase --continue

Agora podemos executar nosso linter (por exemplo, npm run lint) e verificar o status do repositório:

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
 modified:   a.txt
 modified:   a1.txt
 modified:   a2.txt
 modified:   a3.txt
 modified:   a4.txt
 modified:   a5.txt

Como você pode ver, o script do linter alterou o conteúdo de 6 arquivos (a*.txt). Agora, basta incorporar as alterações atuais (colocando-as em stage e fazendo o commit) e continuar o rebase:

❯ git add a*
❯ git commit --amend --no-edit
❯ git rebase --continue
Successfully rebased and updated refs/heads/feat/git-history.

E o grafo do Git deve ficar assim:

* 8f404fe (HEAD -> feat/git-history) feat: 3rd commit
* 7e37b6c feat: 2nd commit - part 2
* f71ad8e feat: 2nd commit - part 1
* b50f18f feat: 1st commit
* 24aa86a (master) Init: Git History

3. Garantir que os testes passem em cada commit (exec)

Ao dividir seu código em vários commits, lembre-se de que cada commit deve ser independente. Em outras palavras, depois de juntar vários commits em uma das branches principais, você deve poder voltar a cada um deles e esperar que o código compile e que todos os testes passem.

Então, como validar isso antes de juntar os commits?

Vamos executar os testes manualmente em cada commit! Brincadeira, é claro — devemos automatizar isso :) Para isso, podemos usar a exec opção.

Essa opção nos ajuda a executar scripts de shell como parte do processo de rebase. Para isso, basta adicionar novas linhas com o comando exec onde quisermos. Por exemplo, vamos executar novamente git rebase -i master :

pick b50f18f feat: 1st commit
pick f71ad8e feat: 2nd commit - part 1
pick 7e37b6c feat: 2nd commit - part 2
pick 8f404fe feat: 3rd commit

Agora vem a parte divertida: executar os testes (rodando o script ./run_tests.sh) após cada commit:

pick b50f18f feat: 1st commit
exec ./run_tests.sh
pick f71ad8e feat: 2nd commit
exec ./run_tests.sh
pick 7e37b6c feat: 2nd commit - part 2
exec ./run_tests.sh
pick 8f404fe feat: 3rd commit
exec ./run_tests.sh

E este é o resultado ao sair da janela do rebase:

❯ git rebase -i master
Executing: ./run_tests.sh
All tests passed!
Executing: ./run_tests.sh
5 tests failed :( 108 tests passed!
Executing: ./run_tests.sh
All tests passed!
Executing: ./run_tests.sh
All tests passed!
Successfully rebased and updated refs/heads/feat/git-history.

Como você pode ver, o Git executou o script de testes depois de cada commit. Assim, agora sabemos que algo no commit feat: 2nd commit faz os testes falharem. Também é possível adicionar um único comando exec depois de cada commit executando:

$ git rebase -i --exec "./run_tests.sh"

Por último (na verdade, primeiro): como cancelar

Esta não é exatamente uma situação, mas sim um alerta: o comando rebase pode ser perigoso!Usá-lo sem entender os efeitos dos comandos pode comprometer seu trabalho. Por isso, começo com duas recomendações:

  • Antes de começar a mexer nos commits, sempre faça um backup adequado do seu repositório Git. O jeito mais simples, para mim, é enviar todas as alterações para uma branch remota. Se algo der errado, posso voltar ao código original (usando git reset).

  • Tenha certeza de que entende os comandos. Em vez de usá-los pela primeira vez no código real, teste-os antes em um repositório Git temporário.

Lembre-se de que você sempre pode interromper o processo de rebase e voltar ao estado original:

  • Na tela inicial do rebase, basta apagar todas as linhas dos commits e nada será alterado.

  • No meio do processo de rebase, se por algum motivo você se perder e não souber o que fazer, pode executar git rebase --abort. Esse comando interrompe o rebase e restaura o repositório ao estado original (antes de executar git rebase -i).

Para resumir

Como vimos nos últimos exemplos, git rebase é um comando poderoso, especialmente no modo interativo. Há inúmeros casos em que podemos usá-lo, mas espero ter ajudado você a entender quando cada uma das opções é útil.

O principal que gostaria que você levasse deste artigo é que deve usar git rebase com inteligência e confiança. Sei que, no começo, pode dar um pouco de medo, e talvez você queira alguém segurando sua mão na hora de apertar Enter, mas tudo bem. Com a prática, isso vai se tornar parte da rotina. Além disso, tenho certeza de que existem muitos outros cenários específicos para o seu caso em que esse comando pode ser útil.

Aproveite!

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.

Publicado em:

Leia mais

Blog

Verão nos estádios: a turnê Fan Zone do Snyk Connect

A turnê Fan Zone da Snyk levou workshops de segurança de IA, networking e competição amistosa a 8 cidades e 3 sessões virtuais. Os participantes desenvolveram habilidades, trocaram ideias e evoluíram juntos.

Blog

Snyk VulnBench JS 1.0: LLMs conseguem encontrar os mesmos bugs duas vezes?

Snyk VulnBench JS 1.0: 300 varreduras repetidas mostram que as descobertas de segurança dos LLMs variam a cada execução, enquanto SAST e modelos detectam diferentes lacunas de vulnerabilidade.

feature insights context
Blog

Construindo a segurança de IA com nossos clientes: 5 lições do programa de parceiros de design do Evo

Conheça 5 lições importantes do programa de parceiros de design do Evo, da Snyk. Descubra como a descoberta de IA, a inteligência de risco e a automação de políticas ajudam as equipes a proteger a IA generativa e controlar a proliferação de IA em grande escala.