Desenvolvimento

8 mai, 2012

Dica Git da semana: Reflogs

Publicidade

A dica Git desta semana é sobre como recuperar o trabalho com o reflog.

Reflogs

O reflog do Git é muito diferente do reflog do Hg de mesmo nome. Reflogs do Hg são equivalentes aos arquivos ,v do CVS. (Na verdade, eles são RCS, mas não se preocupe com isso agora…)

O reflog do Git é uma lista de hashes, que representam onde você esteve durante commits. Cada vez que um branch é atualizado para apontar para uma nova referência, uma entrada é escrita no reflog para dizer onde você estava. Uma vez que o branch é atualizado sempre que você comita, o reflog do Git tem um belo efeito de armazenar o histórico do seu desenvolvedor local.

Além disso, o ponteiro no reflog aponta para o commit, que por sua vez aponta para um objeto dentro da árvore, o que representa uma estrutura de diretórios do tipo de pastas e arquivos. Assim, embora o reflog esteja ativo, você pode voltar e ver quais as mudanças que você fez – e até mesmo recuperar arquivos específicos de versões anteriores do commit. Com Git, você nunca perde nada, mesmo se tiver feito filter-branch para reescrever a história, você é apenas uma entrada reflog longe de conseguir tudo de volta.

Para ver sobre o que é o reflog, execute git reflog a partir de um repositório Git ativo. Deve se parecer com isto:

9bdbd83 HEAD@{0}: commit: Adding  build script
86a7a39 HEAD@{1}: commit (amend): Updating commit message
325e0af HEAD@{2}: commit (amend): Example Project (fix typo)
06bf85e HEAD@{3}: commit (initial): Example project

O primeiro número equivale ao primeiro número de hash do commit no ponto em que a alteração foi feita. Mesmo que eles não representem a história linear (você verá alguns (amend) listados lá), eles são a sequência de ações tomadas no repositório local, na ordem em que foram feitas.

O segundo é o estado de HEAD, juntamente com o número de alterações. Nesse caso, temos HEAD@{0}, o que significa que onde a HEAD está agora; HEAD@{1} é onde a HEAD estava anteriormente, e assim por diante.

A parte final é o tipo, se é um commit ou um amended commit, e o assunto do commit. Isso é frequentemente útil para recordar que o código era, especialmente se não é parte da história linear. Ele também contém outras operações, tais como checkout, merge e reset:

abec02f HEAD@{0}: merge foo: Merge made by recursive.
9bdbd83 HEAD@{1}: 9bdbd83: updating HEAD
2d90ece HEAD@{2}: merge foo: Fast-forward
9bdbd83 HEAD@{3}: checkout: moving from foo to master
2d90ece HEAD@{4}: commit: hello
9bdbd83 HEAD@{5}: checkout: moving from master to foo

Referências de reflog

A maioria dos comandos do git aceita um número de referências diferentes para apontar para um commit. Por exemplo, você pode executar git checkout master, git checkout abec02f e git checkout mytag. No entanto, você também pode verificar referências por reflog também.

No exemplo acima, podemos executar git checkout 290ece, ou podemos nos referir a ele como git checkout HEAD@{2}. Desde que não tenham comitado qualquer outra coisa (que iria alterar o reflog), essas duas variações têm o mesmo efeito.

Você pode usar isso para implementar uma forma rústica de desfazer:

git config --global alias.undo  "reset HEAD@{1}"

Isso fará com que você reverta para a ação anterior (se era um commit ou não).

Referências de reflog revisitados

Embora nós já estarmos usando HEAD aqui, reflogs são mais gerais do que apenas HEAD. A representação geral é name@{}qualifier.

Na verdade, stashes (abordado na semana passada) são uma forma específica de reflog, cujo nome é stash. Não só isso, mas outros branches podem ser consultados por seu reflog também.

Todos os reflogs de branch são armazenados sob .git/logs/refs/heads/. (Há também um sob .git/logs/heads, bem como .git/logs/refs/stash, caso você já tenha usado um stash antes.)

Também podemos identificar reflogs para um branch específico:

$ git reflog show master
abec02f master@{0}: merge foo: Merge made by recursive.
9bdbd83 master@{1}: 9bdbd83: updating HEAD
2d90ece master@{2}: merge foo: Fast-forward
9bdbd83 master@{3}: commit: hello
$ git reflog show foo
2d90ece foo@{0}: commit: hello
9bdbd83 foo@{1}: branch: Created from HEAD

Uma vez que todas essas são referências git válidas, podemos realizar diffs contra eles, por exemplo, git diff foo@{0} foo@{1}.

Times reflogs

Uma vez que cada reflog tem um tempo implícito associado a ele, você pode filtrar, não só pela história, mas também pelo tempo. Várias formas de suporte incluem:

  • 1.minute.ago (1.minuto.atrás)
  • 1.hour.ago (1.hora.atrás)
  • 1.day.ago (1.dia.atrás)
  • Yesterday (ontem)
  • 1.week.ago (1.semana.atrás)
  • 1.month.ago (1.mês.atrás)
  • 1.year.ago (1.ano.atrás)
  • 2011-05-17.09:00:00

As formas de plural também são aceitas (por exemplo, 2.semanas.atrás), bem como combinações (eg 1.dia.2.horas.atrás).

O formato da hora é mais útil se você quiser voltar ao estado de um branch como de uma hora atrás, ou quer ver que diferenças foram feitas na última hora (por exemplo, git diff @{1.hour.ago}). Observe que, se um branch está faltando, então ele assume o branch atual (assim @{1.hour.ago} refere-se a master@{1.hour.ago} se no branch master).

Resumo

O Git nunca perde nada, mesmo se você executar filter-branching ou commit amending. O commit, tree, e os conteúdos são ainda armazenados no repositório, e o reflog ainda mantém ponteiros para commits prévios.

Cada vez que você atualizar um branch, ele armazena o conjunto de revisões passadas em um reflog, que é tanto armazenado contra HEAD e contra um branch particular (incluindo um reflog especial para stashes).

Os reflogs ficam até expirarem (o que pode ser feito com o comando git reflog expire). O padrão para unreachable commit é de 30 dias (ou o valor de configuração gc.reflogExpireUnreachable) ou, para reachable commit, 90 dias (ou o valor de configuração gc.reflogExpire).

?

Texto original disponível em http://alblue.bandlem.com/2011/05/git-tip-of-week-reflogs.html