Executando verificação de segurança...
9

Quase perdi todo o meu trabalho… até o Git reflog salvar o dia! 💻

Eu havia concluído uma tarefa importante no projeto e, achando que tudo estava certo, apaguei minha branch local e remota. Acreditei que minhas mudanças já estavam no ambiente correto.

30 minutos depois, fui conferir e… nada! Minhas mudanças simplesmente não estavam lá! 😱 Bateu o desespero!

Felizmente, após uma breve pesquisa, descobri o Git reflog. Ele me mostrou todo o histórico das ações que eu tinha feito, com uma chave 🔑(hash SHA1) para cada etapa.

Usando essa chave, recriei a branch e recuperei todo o meu progresso!

Segue aqui +- meu passo a passo:

Executei a solução nessa ordem:

  1. git reflog (lista um historico de ações chave(sha1) e valor(contexto))

  2. git branch nomeDaMinhaBranch </sha1EscolhidoDaLista/>

Carregando publicação patrocinada...
7

Só pra complementar e dar mais detalhes sobre o reflog.


O Git mantém um registro chamado "reference logs" (também chamado de "reflogs"), que gravam quando a "ponta" de um branch (basicamente, o commit para o qual ele aponta) é alterado no seu repositório local.

Para exemplificar, vamos começar com um repositório vazio, e depois criamos alguns commits (para deixar mais curto, omiti algumas saídas dos comandos):

$ mkdir project
$ cd project/
$ git init

# criar o primeiro e segundo commits
$ echo first >> file.txt && git add . && git commit -m"first"
$ echo second >> file.txt && git commit -am"second"

# ver histórico
$ git log --oneline
899ed6c (HEAD -> master) second
d142056 first

Agora podemos rodar git reflog e ver os reflogs:

$ git reflog
899ed6c (HEAD -> master) HEAD@{0}: commit: second
d142056 HEAD@{1}: commit (initial): first

Basicamente, este histórico mostra como o HEAD mudou (do mais recente para o mais antigo). Também podemos ver a notação HEAD@{N}, que específica o enésimo valor anterior do HEAD (ou seja, HEAD@{0} é valor atual, HEAD@{1} é o anterior, etc).

Essas referências inclusive podem ser usadas em qualquer comando que espera um commit/referência como argumento. Por exemplo, git log HEAD@{1}, git diff HEAD@{1}, git checkout HEAD@{1}, etc. HEAD@{N} usa os reflogs para procurar pelo commit que corresponde ao enésimo valor anterior do HEAD, e funciona como se você colocasse o hash do commit diretamente.

Vale notar que por isso, HEAD@{0} é o mesmo que HEAD.

Os reflogs também gravam quando vc muda para outro branch:

# criar um branch e mudar para ele
$ git checkout -b somebranch # ou "git switch -c somebranch" para Git >= 2.23.0
$ git reflog
899ed6c (HEAD -> somebranch, master) HEAD@{0}: checkout: moving from master to somebranch
899ed6c (HEAD -> somebranch, master) HEAD@{1}: commit: second
d142056 HEAD@{2}: commit (initial): first

# adicionar commit no branch
$ echo branch >> file.txt && git commit -am"branch"
$ git reflog
d0e4769 (HEAD -> somebranch) HEAD@{0}: commit: branch
899ed6c (master) HEAD@{1}: checkout: moving from master to somebranch
899ed6c (master) HEAD@{2}: commit: second
d142056 HEAD@{3}: commit (initial): first

$ git log --oneline 
d0e4769 (HEAD -> somebranch) branch
899ed6c second
d142056 first

Note como ele grava a mudança do branch master para somebranch. Vale notar também que todas as referências HEAD@{N} também mudam (ex: HEAD@{0} apontava para o commit 899ed6c, agora aponta para d0e4769). Os números são atualizados a todo o momento, sendo que {0} é sempre o mais recente.

Continuando com o exemplo, digamos que outra pessoa atualizou o repositório remoto e eu quero puxar essas alterações:

$ git checkout master  # ou "git switch master" para Git >= 2.23.0
$ git pull origin master # obter as atualizações do repositório remoto
$ git reflog
0ff62c8 (HEAD -> master, origin/master) HEAD@{0}: pull origin master: Fast-forward
899ed6c HEAD@{1}: checkout: moving from somebranch to master
d0e4769 (origin/somebranch, somebranch) HEAD@{2}: commit: branch
899ed6c HEAD@{3}: checkout: moving from master to somebranch
899ed6c HEAD@{4}: commit: second
d142056 HEAD@{5}: commit (initial): first

PS: eu pulei as partes em que configurei o remote e adicionei o commit 0ff62c8 nele.

Enfim, o exemplo foi para mostrar que git pull também atualiza o HEAD, e portanto esta mudança é gravada nos reflogs.

Como podemos ver, o reflog é uma maneira geral de saber como o HEAD mudou no seu repositório local (ou seja, se vc clonar novamente o remoto, cada clone terá seu próprio reflog).


Outras formas

Vale lembrar que o reflog, apesar de ser uma solução geral, não é a única forma de recuperar informações sobre "branches perdidos". Comandos específicos podem ter informações adicionais que podem ser úteis.

Por exemplo, o git pull que fizemos acima também criou duas referências (FETCH_HEAD e ORIG_HEAD):

$ git log FETCH_HEAD --oneline -1
0ff62c8 (HEAD -> master, origin/master) another

$ git log ORIG_HEAD --oneline -1
899ed6c second

Segundo a descrição da documentação (em tradução livre):

  • FETCH_HEAD: branch que foi puxado do repositório remoto na última vez que git fetch foi executado. Obs: ao executar git pull, o git fetch também é executado, por isso esta referência foi criada.
  • ORIG_HEAD: é criado por comandos que movem o HEAD de uma maneira que o Git considera "drástica" (git am, git merge, git rebase, git reset), e contém a posição do HEAD antes da operação, para que vc possa facilmente voltar para onde o branch estava antes do comando rodar. Obs: após o fetch, git pull faz um merge ou rebase (dependendo da configuração ou opções da linha de comando), por isso esta referência foi criada.

Portanto, FETCH_HEAD aponta para o branch remoto que foi puxado do repositório remoto (neste caso, origin/master, e corresponde ao commit 0ff62c8), e ORIG_HEAD aponta para onde o HEAD antes de fazer o merge com 0ff62c8. Podemos ver isso no histórico:

$ git log --oneline
0ff62c8 (HEAD -> master, origin/master) another
899ed6c second
d142056 first

Na mesma documentação são citadas outras referências especiais, como o MERGE_HEAD, que grava os commits que estão sendo "mergeados" para o seu branch. Geralmente ele só existe durante o merge, e é apagado depois que ele termina. Serve portanto para vc olhar durante um conflito de merge, por exemplo.

Estas referências especiais são úteis para obter mais detalhes sobre a situação do HEAD durante operações específicas. Mas de maneira geral, a solução genérica é verificar os reflogs.


Curiosidades

A documentação também descreve outras maneiras de obter posições anteriores do HEAD (todas usando o reflog por debaixo dos panos). Por exemplo, HEAD@{yesterday}, HEAD@{5.minutes.ago} ou HEAD@{2024-08-21T08:30:00} para buscar onde o HEAD estava em um instante específico no passado.

E vale lembrar que esta notação não é exclusiva do HEAD, pois funciona com qualquer outro branch. Ou seja, dá para fazer coisas como master@{yesterday} (para onde o master apontava ontem) ou somebranch@{2} (o segundo valor mais recente do branch somebranch).

E se a referência for omitida, será usado o branch atual. Por exemplo, se o branch atual é o master, então @{yesterday} será equivalente a master@{yesterday}. Mas atenção: isso não necessariamente é o mesmo que HEAD@{yesterday}, porque o HEAD poderia estar apontando para um branch diferente naquele instante em particular.

Como curiosidade, @ é um atalho para o HEAD.

Atenção: se usar números negativos, é completamente diferente. @{-N} significa "o enésimo branch que fiz checkout antes do atual". E vc não pode colocar uma referência antes, ou seja, HEAD@{-1} não funciona. Números negativos sempre se referem ao branch que fiz checkout anteriormente. Como curiosidade, git checkout - é um atalho para git checkout @{-1} (desde a versão 1.6.2).


Por fim, vale lembrar mais uma vez que o reflog é armazenado localmente, e não é compartilhado com nenhum repositório remoto.

Mesmo se vc clonar o mesmo repositório remoto em outra pasta da mesma máquina, cada clone terá seu próprio reflog. Ou seja, HEAD@{N} não será necessariamente o mesmo em cada um dos clones.

Por fim, se vc usar git-worktree, cada worktree também terá seu próprio reflog.