Ir para o conteúdo

Aurium

Voltar a Blog
Tela cheia

Recuperando um commit em um repositório GIT corrompido

7 de Julho de 2010, 21:00 , por Desconhecido - 0sem comentários ainda | No one following this article yet.
Visualizado 262 vezes

Meu amigo Diguliu (Estagiário da Colivre que trabalha no desenvolvimento do Noosfero) é um cara muito fuçador que recentemente fez o que todo nerd faz: fode de vez algo que estava tentando melhorar. :-)

Diguliu estava melhorando a interface de tarefas do usuário no Noosfero e eu investi algumas horas com ele nessa implementação, mas antes que ele mandasse o patch para o projeto ele teve uma grande idéia: fazer um script para retirar todos os espaços em branco em fim de linha automaticamente no código do noosfero. Naturalmente ele não estava no branch da melhoria das tarefas e mais naturalmente ainda seu script detonou vários objetos em .git/objects/ que registram commits, arquivos e outras entidades de um repositório git.

Com a árvore local do git quebrada nada funcionava, nem mesmo um "git status", então não era possível voltar ao branch das tarefas e recuperar todo aquele trabalho. Diguliu tentou arduamente desfazer a cagada o engano, mas nem a proximidade de Terceiro (grande mestre detentor de conhecimentos sobre a vida, o universo e todas as coisas) lhe levou a solução.

Pausa para tristeza, choro e luto.

Dias depois, ao retornar para Salvador, decidi tentar recuperar a bagaceira o repositório local de Diguliu, armado de paciência e medo de ter que refazer tudo. Avancei! Já era possível executar um "git status", mas (claro) não foi o suficiente.

Neste momento me lembrei de uma genial palestra proferida por Moises (grande mestre detentor de conhecimentos que responderiam todas as perguntas que você jamais terá a idéia de fazer) na sede da Colivre que apenas eu assisti. Naquela oportunidade ele falou que o git guarda cada arquivo, de cada versão, cada diretório e cada commit como objetos dentro de .git e cada objeto referenciava seus correlatos (filhos de um diretório, commit anterior, etc...). Então se eu pudesse achar o objeto do commit das tarefas, de alguma forma acharia as modificações. (sem depender do trabalho anterior de tentar recuperar o repositório) :-D

Achei pela internet que "git-cat-file -t id" poderia me dizer o tipo do objeto. Mas que é o id? Liste .git/objects/ encontrará vários diretórios com 2 caracteres e dentro de cada um desses vários arquivos de 38 caracteres. O id é um hash hexa de 40 caracteres "dir+arquivo".

Para pegar informações em um objeto do tipo commit basta "git-cat-file commit id"

No commit havia a palavra Task, então com essa linha eu achei os commits e listei apenas os que poderiam me interessar:
find .git/objects -type f | while read f; do cod=$(echo $f | sed -r 's/^.*objects.(..).(.*)$/\1\2/'); if [ $(git-cat-file -t $cod) = commit ] && ( git-cat-file commit $cod | grep -iq Task ); then echo COMMIT $cod É TASK; fi; done 2>/dev/null

Escolhi alguns para ver em detalhe a saída de git-cat-file commit id e peguei o último commit.

Agora é a hora da emoção. Me bastaria pegar os arquivos vinculados a esse commit. Nos detalhes dele você vê "tree" que é uma referência para a raiz do repositório contendo as modificações do commit.

$ git cat-file commit 567da3fdeed7811bfbc2ade8512740bdfb725d82
error: wrong index v2 file size in .git/objects/pack/pack-4f679ea236ca3d1f63ad11a4a07bc2d8ccd46b2b.idx
tree 438af284bd6ed59ddf1b61e209f2fa1e854410ff
parent 34593e717c723526ec7eabd8b49f1fd016f309ec
author Rodrigo Souto  1276869876 -0300
committer Rodrigo Souto  1276869876 -0300

Multi Task Processing
  - Base interface.
  - Controller logic.
  - Graciously show/hide functions with the smooth slide of Jquery. ^^
  - (...)
  - "Beautifulnification".

Dentre os objetos em .git/objects/ temos os do tipo "tree" (que na prática são os diretórios) e estes podem ter seu conteúdo (suas referências) listadas com git-ls-tree commit id. Dessa forma eu posso ver outros diretórios e arquivos dentro deste.

Os arquivos são objetos do tipo "blob" e podemos recuperar seu conteúdo com git-cat-file blob id.

Como fazer isso manualmente para toda arvore do Noosfero (ou qualquer outro projeto) seria insuportável, fiz o script cp-git-tree:

id=$1    # The tree id
# $2 is a directory path where tree will be copied
echo ">> Entering on $id"
git-ls-tree $id | while read line; do
  name="$( echo "$line" | cut -f2 )"
  data="$( echo "$line" | cut -f1 )"
  child_type=$( echo $data | cut -d' ' -f2 )
  child_id=$( echo $data | cut -d' ' -f3 )
  if [ $child_type = tree ]; then
    echo ">> Creating \"$2/$name\""
    mkdir "$2/$name"
    cp-git-tree $child_id "$2/$name"
  fi
  if [ $child_type = blob ]; then
    if [ "$(git-cat-file -t $child_id)" = "blob" ] 2>/dev/null; then
      echo ">> Copping \"$name\" ($child_id)"
      git-cat-file blob $child_id > "$2/$name"
    else
      echo ">> \"$name\" ($child_id) is not a real blob"
    fi
  fi
done

E executei provendo a arvore do commit:

cp-git-tree 438af284bd6ed59ddf1b61e209f2fa1e854410ff /tmp/commit-tasks 2>/dev/null

 

O git-cat-file e o git-ls-tree travaram (uma vez cada um) e forram mortos com killall, certamente por terem encontrado objetos corrompidos. Então posso ter perdido uma ou outra modificação (muita coisa foi feita nesse commit), mas recuperei a maior parte do trabalho.

Agora é só revisar (por segurança) os arquivos em /tmp/commit-tasks, mover seu conteúdo para um novo repositório saudável do Noosfero, concluir as implementação e mandar o patch!


Fonte: http://softwarelivre.org/aurium/blog/recuperando-um-commit-em-um-repositorio-git-corrompido

0sem comentários ainda

    Enviar um comentário

    Os campos realçados são obrigatórios.

    Se você é um usuário registrado, pode se identificar e ser reconhecido automaticamente.

    Cancelar