paint-brush
Reescrevendo a história do Git com confiança: um guiapor@omerosenbaum
2,019 leituras
2,019 leituras

Reescrevendo a história do Git com confiança: um guia

por Omer Rosenbaum18m2023/04/27
Read on Terminal Reader

Muito longo; Para ler

Git é um sistema para gravar instantâneos de um sistema de arquivos no tempo. Um repositório Git tem três “estados” ou “árvores”: o índice, a área de preparação e a árvore de trabalho. Um diretório de trabalho (ectrory) é qualquer diretório em nosso sistema de arquivos que tenha um repositório Git associado a ele.
featured image - Reescrevendo a história do Git com confiança: um guia
Omer Rosenbaum HackerNoon profile picture

Como desenvolvedor, você trabalha com Git o tempo todo.


Você já chegou a um ponto em que disse: "Uh-oh, o que eu acabei de fazer?"

Quando as coisas dão errado com o Git, muitos engenheiros se sentem impotentes (fonte: XKCD)


Este post lhe dará as ferramentas para reescrever a história com confiança.

Notas antes de começar

  1. Eu também dei uma palestra ao vivo cobrindo o conteúdo deste post. Se você preferir um vídeo (ou deseja assisti-lo junto com a leitura) — você pode encontrá-lo .


  2. Estou trabalhando em um livro sobre Git! Você está interessado em ler as versões iniciais e fornecer feedback? Envie-me um e-mail: [email protected]

Gravando alterações no Git

Antes de entender como desfazer as coisas no Git, você deve primeiro entender como registramos as alterações no Git. Se você já conhece todos os termos, sinta-se à vontade para pular esta parte.


É muito útil pensar no Git como um sistema para gravar instantâneos de um sistema de arquivos no tempo. Considerando um repositório Git, ele possui três “estados” ou “árvores”:

As três “árvores” de um repositório Git (fonte: https://youtu.be/ozA1V00GIT8)


Normalmente, quando trabalhamos em nosso código-fonte, trabalhamos a partir de um diretório de trabalho . Um diretório de trabalho (ectrory) (ou árvore de trabalho ) é qualquer diretório em nosso sistema de arquivos que tenha um repositório associado a ele.


Ele contém as pastas e arquivos do nosso projeto e também um diretório chamado .git . Descrevi o conteúdo da pasta .git com mais detalhes em uma postagem anterior .


Depois de fazer algumas alterações, você pode querer gravá-las em seu repositório . Um repositório (resumindo: repo ) é uma coleção de commits , cada um dos quais é um arquivo de como era a árvore de trabalho do projeto em uma data passada, seja na sua máquina ou na de outra pessoa.


Um repositório também inclui outras coisas além de nossos arquivos de código, como HEAD , branches, etc.


No meio, temos o índice ou a área de preparação ; esses dois termos são intercambiáveis. Quando checkout uma ramificação, o Git preenche o índice com todo o conteúdo do arquivo que foi verificado pela última vez em nosso diretório de trabalho e como eles se pareciam quando foram originalmente verificados.


Quando usamos git commit , o commit é criado com base no estado do índice .


Assim, o índice, ou a área de preparação, é o seu playground para o próximo commit. Você pode trabalhar e fazer o que quiser com o índice , adicionar arquivos a ele, remover coisas dele e, somente quando estiver pronto, vá em frente e se comprometa com o repositório.


Hora de colocar a mão na massa 🙌🏻


Use git init para inicializar um novo repositório. Escreva algum texto em um arquivo chamado 1.txt :

Iniciando um novo repositório e criando o primeiro arquivo nele (fonte: https://youtu.be/ozA1V00GIT8)


Dos três estados de árvore descritos acima, onde está 1.txt agora?


Na árvore de trabalho, como ainda não foi introduzido no índice.

O arquivo `1.txt` agora faz parte apenas do diretório de trabalho (fonte: https://youtu.be/ozA1V00GIT8)


Para organizá- lo, para adicioná-lo ao índice, use git add 1.txt .

O uso de `git add` organiza o arquivo para que agora também esteja no índice (fonte: https://youtu.be/ozA1V00GIT8)


Agora, podemos usar git commit para confirmar nossas alterações no repositório.

Usar `git commit` cria um objeto commit no repositório (fonte: https://youtu.be/ozA1V00GIT8)


Você criou um novo objeto commit, que inclui um ponteiro para uma árvore que descreve toda a árvore de trabalho. Neste caso, será apenas 1.txt dentro da pasta raiz. Além de um ponteiro para a árvore, o objeto commit inclui metadados, como carimbos de data/hora e informações do autor.


Para obter mais informações sobre os objetos no Git (como commits e árvores), confira meu post anterior .


(Sim, “check-out”, trocadilho intencional 😇)


O Git também nos informa o valor SHA-1 desse objeto de confirmação. No meu caso, foi c49f4ba (que são apenas os 7 primeiros caracteres do valor SHA-1, para economizar espaço).


Se você executar este comando em sua máquina, obterá um valor SHA-1 diferente, pois é um autor diferente; além disso, você criaria o commit em um carimbo de data/hora diferente.


Quando inicializamos o repositório, o Git cria uma nova ramificação (chamada main por padrão). E uma ramificação no Git é apenas uma referência nomeada para um commit . Portanto, por padrão, você tem apenas a ramificação main . O que acontece se você tiver várias filiais? Como o Git sabe qual branch é o branch ativo?


O Git tem outro ponteiro chamado HEAD , que aponta (geralmente) para um branch, que então aponta para um commit. Por falar nisso, sob o capô, HEAD é apenas um arquivo. Inclui o nome da filial com alguns prefixos.


Hora de introduzir mais mudanças no repositório!


Agora, quero criar outro. Então, vamos criar um novo arquivo e adicioná-lo ao índice, como antes:

O arquivo `2.txt` está no diretório de trabalho e o índice após a preparação com `git add` (fonte: https://youtu.be/ozA1V00GIT8)


Agora, é hora de usar git commit . É importante ressaltar que git commit faz duas coisas:


Primeiro, ele cria um objeto commit, então há um objeto dentro do banco de dados interno do Git com um valor SHA-1 correspondente. Este novo objeto commit também aponta para o commit pai. Esse é o commit para o qual HEAD estava apontando quando você escreveu o comando git commit .

Um novo objeto commit foi criado, a princípio — `main` ainda aponta para o commit anterior (fonte: https://youtu.be/ozA1V00GIT8)


Em segundo lugar, git commit move o ponteiro do branch ativo — em nosso caso, seria main , para apontar para o objeto commit recém-criado.

`git commit` também atualiza o branch ativo para apontar para o objeto commit recém-criado (fonte: https://youtu.be/ozA1V00GIT8)


Desfazendo as Alterações

Para reescrever a história, vamos começar desfazendo o processo de introdução de um commit. Para isso, vamos conhecer o comando git reset , uma ferramenta superpoderosa.

git reset --soft

Portanto, o último passo que você fez antes foi git commit , o que na verdade significa duas coisas — Git criou um objeto commit e moveu main , o branch ativo. Para desfazer esta etapa, use o comando git reset --soft HEAD~1 .


A sintaxe HEAD~1 refere-se ao primeiro pai de HEAD . Se eu tivesse mais de um commit no grafo de commit, diga “Commit 3” apontando para “Commit 2”, que por sua vez está apontando para “Commit 1”.


E diga que HEAD estava apontando para “Commit 3”. Você poderia usar HEAD~1 para se referir a “Commit 2”, e HEAD~2 se referiria a “Commit 1”.


Então, de volta ao comando: git reset --soft HEAD~1


Este comando pede ao Git para alterar qualquer HEAD que esteja apontando. (Nota: Nos diagramas abaixo, eu uso *HEAD para “qualquer coisa que HEAD esteja apontando”). Em nosso exemplo, HEAD está apontando para main . Portanto, o Git apenas mudará o ponteiro de main para apontar para HEAD~1 . Ou seja, main apontará para “Commit 1”.


No entanto, esse comando não afetou o estado do índice ou da árvore de trabalho. Portanto, se você usar git status , verá que 2.txt está preparado, assim como antes de executar git commit .

Redefinindo `main` para “Commit 1” (fonte: https://youtu.be/ozA1V00GIT8)


E o git log? Ele começará em HEAD , vá para main e depois para “Commit 1”. Observe que isso significa que “Commit 2” não está mais acessível em nosso histórico.


Isso significa que o objeto de confirmação de “Commit 2” foi excluído? 🤔


Não, não foi deletado. Ele ainda reside no banco de dados interno de objetos do Git.


Se você enviar o histórico atual agora, usando git push , o Git não enviará “Commit 2” para o servidor remoto, mas o objeto commit ainda existe em sua cópia local do repositório.


Agora, faça commit novamente — e use a mensagem de commit de “Commit 2.1” para diferenciar esse novo objeto do “Commit 2” original:

Criando um novo commit (fonte: https://youtu.be/ozA1V00GIT8)


Por que “Commit 2” e “Commit 2.1” são diferentes? Mesmo se usássemos a mesma mensagem de commit, e mesmo que eles apontem para o mesmo objeto árvore (da pasta raiz composta por 1.txt e 2.txt ), eles ainda possuem timestamps diferentes, pois foram criados em momentos diferentes.


No desenho acima, mantive o “Commit 2” para lembrar que ele ainda existe no banco de dados de objetos internos do Git. Ambos “Commit 2” e “Commit 2.1” agora apontam para “Commit 1”, mas apenas “Commit 2.1” é acessível a partir HEAD .

Git Reset --Misto

É hora de voltar ainda mais e desfazer ainda mais. Desta vez, use git reset --mixed HEAD~1 (observação: --mixed é a opção padrão para git reset ).


Este comando começa da mesma forma que git reset --soft HEAD~1 . O que significa que ele pega o ponteiro de qualquer HEAD que esteja apontando agora, que é o branch main , e o define como HEAD~1 , em nosso exemplo — “Commit 1”.

A primeira etapa de `git reset --mixed` é a mesma de `git reset --soft` (fonte: https://youtu.be/ozA1V00GIT8)


Em seguida, o Git vai além, desfazendo efetivamente as alterações que fizemos no índice. Ou seja, alterando o índice para que coincida com o HEAD atual, o novo HEAD após defini-lo na primeira etapa.


Se executarmos git reset --mixed HEAD~1 , isso significa que HEAD seria definido como HEAD~1 (“Commit 1”) e, em seguida, Git corresponderia o índice ao estado de “Commit 1” — nesse caso, significa que 2.txt não fará mais parte do índice.

A segunda etapa do `git reset --mixed` é combinar o índice com o novo `HEAD` (fonte: https://youtu.be/ozA1V00GIT8)


É hora de criar um novo commit com o estado do original “Commit 2”. Desta vez, precisamos preparar 2.txt novamente antes de criá-lo:

Criando “Commit 2.2” (fonte: https://youtu.be/ozA1V00GIT8)


Git Reset --Hard

Vá em frente, desfaça ainda mais!


Vá em frente e execute git reset --hard HEAD~1


Novamente, o Git começa com o estágio --soft , configurando qualquer HEAD para o qual esteja apontando ( main ), para HEAD~1 (“Commit 1”).

O primeiro passo de `git reset --hard` é o mesmo que `git reset --soft` (fonte: https://youtu.be/ozA1V00GIT8)


Até agora tudo bem.


Em seguida, passando para o estágio --mixed , combinando o índice com HEAD . Ou seja, o Git desfaz a preparação de 2.txt .

A segunda etapa de `git reset --hard` é a mesma de `git reset --mixed` (fonte: https://youtu.be/ozA1V00GIT8)


É hora da etapa --hard onde o Git vai ainda mais longe e combina o diretório de trabalho com o estágio do índice. Neste caso, significa remover 2.txt também do diretório de trabalho.

A terceira etapa de `git reset --hard` corresponde ao estado do diretório de trabalho com o do índice (fonte: https://youtu.be/ozA1V00GIT8)


(**Observação: neste caso específico, o arquivo não foi rastreado, portanto não será excluído do sistema de arquivos; não é realmente importante para entender git reset ).


Portanto, para introduzir uma alteração no Git, você tem três etapas. Você altera o diretório de trabalho, o índice ou a área de preparação e, em seguida, confirma um novo instantâneo com essas alterações. Para desfazer essas alterações:


  • Se usarmos git reset --soft , desfazemos a etapa de confirmação.


  • Se usarmos git reset --mixed , também desfazemos a etapa de preparação.


  • Se usarmos git reset --hard , desfazemos as alterações no diretório de trabalho.

Cenários da vida real!

Cenário 1

Então, em um cenário da vida real, escreva “Eu amo o Git” em um arquivo ( love.txt ), pois todos nós amamos o Git 😍. Vá em frente, stage e confirme isso também:

Criando “Commit 2.3” (fonte: https://youtu.be/ozA1V00GIT8)


Opa!


Na verdade, eu não queria que você o cometesse.


O que eu realmente queria que você fizesse era escrever mais algumas palavras de amor neste arquivo antes de enviá-lo.

O que você pode fazer?


Bem, uma maneira de superar isso seria usar git reset --mixed HEAD~1 , desfazendo efetivamente as ações de commit e staging que você realizou:

Desfazendo as etapas de preparação e confirmação (fonte: https://youtu.be/ozA1V00GIT8)


Portanto, main aponta para “Commit 1” novamente e love.txt não faz mais parte do índice. No entanto, o arquivo permanece no diretório de trabalho. Agora você pode ir em frente e adicionar mais conteúdo a ele:

Adicionando mais letras de amor (fonte: https://youtu.be/ozA1V00GIT8)


Vá em frente, prepare e confirme seu arquivo:

Criando o novo commit com o estado desejado (fonte: https://youtu.be/ozA1V00GIT8)


Muito bem 👏🏻


Você tem esse histórico claro e agradável de “Commit 2.4” apontando para “Commit 1”.


Agora temos uma nova ferramenta em nossa caixa de ferramentas, git reset 💪🏻

git reset agora está em nossa caixa de ferramentas (fonte: https://youtu.be/ozA1V00GIT8)


Essa ferramenta é super, super útil e você pode realizar quase tudo com ela. Nem sempre é a ferramenta mais conveniente de usar, mas é capaz de resolver quase todos os cenários de reescrita da história se você usá-la com cuidado.


Para iniciantes, recomendo usar apenas git reset para quase todas as vezes que você quiser desfazer no Git. Depois de se sentir confortável com isso, é hora de passar para outras ferramentas.

Cenário #2

Consideremos outro caso.


Crie um novo arquivo chamado new.txt ; palco e cometer:

Criando `new.txt` e “Commit 3” (fonte: https://youtu.be/ozA1V00GIT8)


Ops. Na verdade, isso é um erro. Você estava em main e eu queria que você criasse este commit em um branch de recursos. meu mal 😇


Existem duas ferramentas mais importantes que eu quero que você tire deste post. O segundo é git reset . A primeira e muito mais importante é colocar no quadro branco o estado atual versus o estado em que você deseja estar.


Para este cenário, o estado atual e o estado desejado têm a seguinte aparência:

Cenário 2: estados atuais x estados desejados (fonte: https://youtu.be/ozA1V00GIT8)


Você notará três mudanças:


  1. main aponta para “Commit 3” (o azul) no estado atual, mas para “Commit 2.4” no estado desejado.


  2. A ramificação feature não existe no estado atual, mas existe e aponta para “Commit 3” no estado desejado.


  3. HEAD aponta para o main no estado atual e para feature no estado desejado.


Se você pode desenhar isso e sabe como usar git reset , você pode definitivamente sair dessa situação.


Então, novamente, a coisa mais importante é respirar e extrair isso.


Observando o desenho acima, como passamos do estado atual ao desejado?


Claro que existem algumas formas diferentes, mas vou apresentar apenas uma opção para cada cenário. Sinta-se à vontade para brincar com outras opções também.


Você pode começar usando git reset --soft HEAD~1 . Isso definiria main para apontar para o commit anterior, “Commit 2.4”:

Alterando `main`; “O commit 3 está desfocado porque ainda está lá, apenas inacessível (fonte: https://youtu.be/ozA1V00GIT8)


Olhando novamente para o diagrama atual versus desejado, você pode ver que precisa de um novo branch, certo? Você pode usar git switch -c feature para ele ou git checkout -b feature (que faz a mesma coisa):

Criando branch `feature` (fonte: https://youtu.be/ozA1V00GIT8)


Este comando também atualiza HEAD para apontar para a nova ramificação.


Como você usou git reset --soft , você não alterou o índice, então ele tem exatamente o estado que você deseja confirmar - que conveniente! Você pode simplesmente se comprometer com o ramo feature :

Comprometendo-se com o branch `feature` (fonte: https://youtu.be/ozA1V00GIT8)


E você chegou ao estado desejado 🎉

Cenário #3

Pronto para aplicar seu conhecimento a casos adicionais?


Adicione algumas alterações a love.txt e também crie um novo arquivo chamado cool.txt . Organize-os e comprometa-se:

Criando “Commit 4” (fonte: https://youtu.be/ozA1V00GIT8)


Oh, opa, na verdade eu queria que você criasse dois commits separados, um para cada mudança 🤦🏻

Quer experimentar este você mesmo?


Você pode desfazer as etapas de commit e staging:

Desfaça commit e staging usando `git reset --mixed HEAD~1` (fonte: https://youtu.be/ozA1V00GIT8)


Após esse comando, o índice não inclui mais essas duas alterações, mas ambas ainda estão em seu sistema de arquivos. Portanto, agora, se você apenas colocar em stage love.txt , poderá confirmá-lo separadamente e, em seguida, fazer o mesmo para cool.txt :

Compromisso separado (fonte: https://youtu.be/ozA1V00GIT8)


Legal 😎

Cenário #4

Crie um novo arquivo ( new_file.txt ) com algum texto e adicione algum texto a love.txt . Organize ambas as alterações e confirme-as:

Um novo commit (fonte: https://youtu.be/ozA1V00GIT8)


Opa 🙈🙈


Então, desta vez, eu queria que fosse em outro branch, mas não em um novo branch, mas em um branch já existente.


Então o que você pode fazer?


Vou te dar uma dica. A resposta é muito curta e muito fácil. O que fazemos primeiro?


Não, não reset . Nos desenhamos. Essa é a primeira coisa a fazer, pois tornaria tudo muito mais fácil. Então este é o estado atual:

O novo commit em `main` aparece em azul (fonte: https://youtu.be/ozA1V00GIT8)


E o estado desejado?

Queremos que o commit “azul” esteja em outro branch `existente` (fonte: https://youtu.be/ozA1V00GIT8)


Como você sai do estado atual para o estado desejado, o que seria mais fácil?


Então, uma maneira seria usar git reset como você fez antes, mas há outra maneira que eu gostaria que você tentasse.


Primeiro, mova HEAD para apontar para a ramificação existing :

Alterne para a ramificação `existente` (fonte: https://youtu.be/ozA1V00GIT8)


Intuitivamente, o que você quer fazer é pegar as alterações introduzidas no commit azul e aplicá-las (“copiar e colar”) no topo da ramificação existing . E o Git tem uma ferramenta só para isso.


Para pedir ao Git para fazer as alterações introduzidas entre este commit e seu commit pai e apenas aplicar essas alterações no branch ativo, você pode usar git cherry-pick . Este comando pega as mudanças introduzidas na revisão especificada e as aplica ao commit ativo.


Ele também cria um novo objeto commit e atualiza a ramificação ativa para apontar para esse novo objeto.

Usando `git cherry-pick` (fonte: https://youtu.be/ozA1V00GIT8)


No exemplo acima, eu especifiquei o identificador SHA-1 do commit criado, mas você também pode usar git cherry-pick main , já que o commit cujas alterações estamos aplicando é aquele para o qual main está apontando.


Mas não queremos que essas alterações existam no ramo main . git cherry-pick apenas aplicou as alterações ao branch existing . Como você pode removê-los do main ?


Uma maneira seria switch para main e, em seguida, usar git reset --hard HEAD~1 :

Redefinindo `principal` (fonte: https://youtu.be/ozA1V00GIT8)


Você fez isso! 💪🏻


Observe que git cherry-pick realmente calcula a diferença entre o commit especificado e seu pai e, em seguida, aplica-os ao commit ativo. Isso significa que, às vezes, o Git não conseguirá aplicar essas alterações, pois pode ocorrer um conflito, mas isso é assunto para outro post.


Além disso, observe que você pode pedir ao Git para cherry-pick as alterações introduzidas em qualquer commit, não apenas nos commits referenciados por um branch.


Adquirimos uma nova ferramenta, então temos git reset e git cherry-pick em nosso currículo.

Cenário #5

Ok, então outro dia, outro repo, outro problema.


Crie um commit:

Outro commit (fonte: https://youtu.be/ozA1V00GIT8)


E push -o para o servidor remoto:

(fonte: https://youtu.be/ozA1V00GIT8)


Hum, opa 😓…


Acabei de notar uma coisa. Há um erro de digitação aí. Eu escrevi This is more tezt em vez de This is more text . Opa. Então, qual é o grande problema agora? Eu push ed, o que significa que outra pessoa já pode ter pull essas alterações.


Se eu substituir essas alterações usando git reset , como fizemos até agora, teremos histórias diferentes e o inferno pode explodir. Você pode reescrever sua própria cópia do repositório o quanto quiser até que você o push .


Depois de push a alteração, você precisa ter certeza de que ninguém mais buscou essas alterações se for reescrever o histórico.


Como alternativa, você pode usar outra ferramenta chamada git revert . Este comando pega o commit que você está fornecendo e calcula o Diff de seu commit pai, assim como git cherry-pick , mas desta vez, ele calcula as alterações inversas.


Portanto, se no commit especificado você adicionar uma linha, o inverso excluirá a linha e vice-versa.

Usando `git revert` para desfazer as alterações (fonte: https://youtu.be/ozA1V00GIT8)


git revert criou um novo objeto commit, o que significa que é uma adição ao histórico. Ao usar git revert , você não reescreveu o histórico. Você admitiu seu erro passado, e este commit é um reconhecimento de que você cometeu um erro e agora o corrigiu.


Alguns diriam que é a forma mais madura. Alguns diriam que não é um histórico tão limpo quanto você obteria se usasse git reset para reescrever o commit anterior. Mas esta é uma forma de evitar reescrever a história.


Agora você pode corrigir o erro de digitação e confirmar novamente:

Refazendo as alterações (fonte: https://youtu.be/ozA1V00GIT8)


Sua caixa de ferramentas agora está carregada com uma nova ferramenta brilhante, revert :

Nossa caixa de ferramentas (fonte: https://youtu.be/ozA1V00GIT8)


Cenário #6

Faça algum trabalho, escreva algum código e adicione-o a love.txt . Organize esta alteração e confirme-a:

Outro commit (fonte: https://youtu.be/ozA1V00GIT8)


Fiz o mesmo na minha máquina e usei a tecla de seta Up no teclado para rolar de volta para os comandos anteriores e, em seguida, pressionei Enter e… Uau.


Opa.

Acabei de `git reset -- hard`? (fonte: https://youtu.be/ozA1V00GIT8)


Acabei de usar git reset --hard ? 😨


O que realmente aconteceu? Git moveu o ponteiro para HEAD~1 , então o último commit, com todo o meu precioso trabalho, não pode ser acessado a partir do histórico atual. O Git também removeu todas as alterações da área de teste e, em seguida, combinou o diretório de trabalho com o estado da área de teste.


Ou seja, tudo condiz com esse estado onde meu trabalho está... desaparecido.


Hora de surtar. Pirando.

Mas, realmente, há uma razão para surtar? Na verdade não... Somos pessoas relaxadas. O que nós fazemos? Bem, intuitivamente, o commit realmente acabou? Não porque não? Ele ainda existe dentro do banco de dados interno do Git.


Se eu soubesse onde fica, saberia o valor SHA-1 que identifica esse commit, poderíamos restaurá-lo. Eu poderia até desfazer o desfazer e reset para este commit.


Portanto, a única coisa que realmente preciso aqui é o SHA-1 do commit “excluído”.


Então a pergunta é, como faço para encontrá-lo? git log seria útil?


Bem, na verdade não. git log iria para HEAD , que aponta para main , que aponta para o commit pai do commit que estamos procurando. Em seguida, git log rastrearia a cadeia pai, que não inclui o commit com meu precioso trabalho.

`git log` não ajuda neste caso (fonte: https://youtu.be/ozA1V00GIT8)


Felizmente, as pessoas muito inteligentes que criaram o Git também criaram um plano de backup para nós, chamado de reflog .


Enquanto você trabalha com o Git, sempre que você altera HEAD , o que pode ser feito usando git reset , mas também outros comandos como git switch ou git checkout , o Git adiciona uma entrada ao reflog .


`git reflog` nos mostra onde `HEAD` estava (fonte: https://youtu.be/ozA1V00GIT8)


Encontramos nosso commit! É aquele que começa com 0fb929e .


Também podemos nos relacionar com ele por seu “apelido” — HEAD@{1} . Assim como Git usa HEAD~1 para chegar ao primeiro pai de HEAD , e HEAD~2 para se referir ao segundo pai de HEAD e assim por diante, Git usa HEAD@{1} para se referir ao primeiro reflog pai de HEAD , onde HEAD apontou na etapa anterior.


Também podemos pedir git rev-parse para nos mostrar seu valor:

(fonte: https://youtu.be/ozA1V00GIT8)


Outra maneira de visualizar o reflog é usando git log -g , que pede git log para realmente considerar o reflog :

A saída de `git log -g` (fonte: https://youtu.be/ozA1V00GIT8)


Vemos acima que o reflog , assim como HEAD , aponta para main , que aponta para “Commit 2”. Mas o pai dessa entrada no reflog aponta para “Commit 3”.


Então, para voltar ao “Commit 3”, você pode simplesmente usar git reset --hard HEAD@{1} (ou o valor SHA-1 de “Commit 3”):

(fonte: https://youtu.be/ozA1V00GIT8)


E agora, se git log :

Nossa história está de volta!!! (fonte: https://youtu.be/ozA1V00GIT8)


Nós salvamos o dia! 🎉👏🏻


O que aconteceria se eu usasse esse comando novamente? E executou git commit --reset HEAD@{1} ? Git definiria HEAD para onde HEAD estava apontando antes da última reset , significando “Commit 2”. Podemos continuar o dia todo:

(fonte: https://youtu.be/ozA1V00GIT8)


Olhando para nossa caixa de ferramentas agora, ela está repleta de ferramentas que podem ajudá-lo a resolver muitos casos em que as coisas dão errado no Git:

Nossa caixa de ferramentas é bastante extensa! (fonte: https://youtu.be/ozA1V00GIT8)


Com essas ferramentas, agora você entende melhor como o Git funciona. Existem mais ferramentas que permitem reescrever o histórico especificamente, git rebase ), mas você já aprendeu muito neste post. Em postagens futuras, vou mergulhar no git rebase também.


A ferramenta mais importante, ainda mais importante do que as cinco ferramentas listadas nesta caixa de ferramentas, é colocar no quadro branco a situação atual versus a desejada. Acredite em mim, isso fará com que cada situação pareça menos assustadora e a solução mais clara.

Saiba mais sobre Git

Eu também dei uma palestra ao vivo cobrindo o conteúdo deste post. Se você preferir um vídeo (ou deseja assisti-lo junto com a leitura) — você pode encontrá-lo .


Em geral, meu canal do youtube cobre muitos aspectos do Git e seus componentes internos; você é bem-vindo Confira (trocadilho intencional 😇)

Sobre o autor

Omer Rosenbaum é o CTO e co-fundador da nadar , uma ferramenta de desenvolvimento que ajuda os desenvolvedores e suas equipes a gerenciar o conhecimento sobre sua base de código com documentação interna atualizada. Omer é o fundador da Check Point Security Academy e foi o líder de segurança cibernética da ITC, uma organização educacional que treina profissionais talentosos para desenvolver carreiras em tecnologia.


Omer tem mestrado em Linguística pela Universidade de Tel Aviv e é o criador do Breve canal do YouTube .


Publicado pela primeira vez aqui