Como recuperar objetos Git danificados por falha do disco rígido?

92

Tive uma falha no disco rígido que resultou em alguns arquivos de um repositório Git sendo danificados. Ao executar git fsck --full, obtenho o seguinte resultado:

error: .git/objects/pack/pack-6863e0a0e4b4ded6090fac5d12eba6ca7346b19c.pack SHA1 checksum mismatch
error: index CRC mismatch for object 6c8cae4994b5ec7891ccb1527d30634997a978ee from .git/objects/pack/pack-6863e0a0e4b4ded6090fac5d12eba6ca7346b19c.pack at offset 97824129
error: inflate: data stream error (invalid code lengths set)
error: cannot unpack 6c8cae4994b5ec7891ccb1527d30634997a978ee from .git/objects/pack/pack-6863e0a0e4b4ded6090fac5d12eba6ca7346b19c.pack at offset 97824129
error: inflate: data stream error (invalid stored block lengths)
error: failed to read object 0dcf6723cc69cc7f91d4a7432d0f1a1f05e77eaa at offset 276988017 from .git/objects/pack/pack-6863e0a0e4b4ded6090fac5d12eba6ca7346b19c.pack
fatal: object 0dcf6723cc69cc7f91d4a7432d0f1a1f05e77eaa is corrupted

Tenho backups do repositório, mas o único backup que inclui o arquivo do pacote já está danificado. Portanto, acho que tenho que descobrir uma maneira de recuperar os objetos únicos de backups diferentes e de alguma forma instruir o Git a produzir um novo pacote com apenas objetos corretos.

Você pode me dar dicas de como consertar meu repositório?

cristão
fonte
2
Isto somente aconteceu para mim. Não quero bagunçar os objetos git ... então, clonar novamente o projeto do repositório remoto para uma nova pasta e, em seguida, apenas copiar todos os arquivos dos meus repositórios problemáticos (excluindo a .gitpasta, é claro) para o repo recém-clonado ... e o fez git statusno novo repo ... git detecta corretamente todas as alterações afetadas em meus arquivos e posso começar meu trabalho novamente.
Rosdi Kasim de

Respostas:

82

Em alguns backups anteriores, seus objetos danificados podem ter sido empacotados em arquivos diferentes ou ainda podem ser objetos soltos. Assim, seus objetos podem ser recuperados.

Parece que há alguns objetos ruins em seu banco de dados. Então você pode fazer isso de forma manual.

Por causa de git hash-object, git mktreee git commit-treenão escrever os objetos porque eles são encontrados no pacote, em seguida, começar a fazer isso:

mv .git/objects/pack/* <somewhere>
for i in <somewhere>/*.pack; do
  git unpack-objects -r < $i
done
rm <somewhere>/*

(Seus pacotes são retirados do repositório e desempacotados novamente nele; apenas os objetos bons estão agora no banco de dados)

Você pode fazer:

git cat-file -t 6c8cae4994b5ec7891ccb1527d30634997a978ee

e verifique o tipo do objeto.

Se o tipo for blob: recupere o conteúdo do arquivo de backups anteriores (com git showou git cat-fileou git unpack-file; então você pode git hash-object -wreescrever o objeto em seu repositório atual.

Se o tipo for árvore: você pode usar git ls-treepara recuperar a árvore de backups anteriores; em seguida, git mktreepara escrevê-lo novamente em seu repositório atual.

Se o tipo for commit: o mesmo com git show, git cat-filee git commit-tree.

Claro, eu faria backup de sua cópia de trabalho original antes de iniciar este processo.

Além disso, dê uma olhada em Como recuperar objetos Blob corrompidos .

Daniel Fanjul
fonte
1
Obrigado, isso me salvou! Vou postar minhas etapas exatas como uma resposta separada.
Christian
Apenas uma correção: o comando para termina com "pronto" e não com "fim".
Felipe
estou tentando fazer isso, mas .git/objects/pack/está vazio
kirill_igum
para mim um; estava faltando após git unpack-objects -r <$ i
mithrandir
@mithrandir: se você colocar 'feito' na linha anterior: sim, você precisa de um ponto e vírgula. Se você digitar exatamente o que escrevi, não o fará.
Daniel Fanjul
38

Banengusk estava me colocando no caminho certo. Para referência futura, quero postar as etapas que executei para corrigir a corrupção do meu repositório. Tive a sorte de encontrar todos os objetos necessários em pacotes mais antigos ou em backups de repositório.

# Unpack last non-corrupted pack
$ mv .git/objects/pack .git/objects/pack.old
$ git unpack-objects -r < .git/objects/pack.old/pack-012066c998b2d171913aeb5bf0719fd4655fa7d0.pack
$ git log
fatal: bad object HEAD

$ cat .git/HEAD 
ref: refs/heads/master

$ ls .git/refs/heads/

$ cat .git/packed-refs 
# pack-refs with: peeled 
aa268a069add6d71e162c4e2455c1b690079c8c1 refs/heads/master

$ git fsck --full 
error: HEAD: invalid sha1 pointer aa268a069add6d71e162c4e2455c1b690079c8c1
error: refs/heads/master does not point to a valid object!
missing blob 75405ef0e6f66e48c1ff836786ff110efa33a919
missing blob 27c4611ffbc3c32712a395910a96052a3de67c9b
dangling tree 30473f109d87f4bcde612a2b9a204c3e322cb0dc

# Copy HEAD object from backup of repository
$ cp repobackup/.git/objects/aa/268a069add6d71e162c4e2455c1b690079c8c1 .git/objects/aa
# Now copy all missing objects from backup of repository and run "git fsck --full" afterwards
# Repeat until git fsck --full only reports dangling objects

# Now garbage collect repo
$ git gc
warning: reflog of 'HEAD' references pruned commits
warning: reflog of 'refs/heads/master' references pruned commits
Counting objects: 3992, done.
Delta compression using 2 threads.
fatal: object bf1c4953c0ea4a045bf0975a916b53d247e7ca94 inconsistent object length (6093 vs 415232)
error: failed to run repack

# Check reflogs...
$ git reflog

# ...then clean
$ git reflog expire --expire=0 --all

# Now garbage collect again
$ git gc       
Counting objects: 3992, done.
Delta compression using 2 threads.
Compressing objects: 100% (3970/3970), done.
Writing objects: 100% (3992/3992), done.
Total 3992 (delta 2060), reused 0 (delta 0)
Removing duplicate objects: 100% (256/256), done.
# Done!
cristão
fonte
3
Adicionando a isso: se o backup tiver os arquivos ausentes em um pacote, a maneira adequada de retirar um blob do pacote é 'git cat-file blob <SHA1>> file.dat' e colocá-lo de volta no repo, faça 'git hash-object -w file.dat', como na resposta de Daniel.
Emil Styrke
Como você encontra o último pacote não corrompido? obrigado
Romain Ourgorry
18

Tente os seguintes comandos primeiro (execute novamente se necessário):

$ git fsck --full
$ git gc
$ git gc --prune=today
$ git fetch --all
$ git pull --rebase

E então você ainda tem problemas, tente pode:

  • remova todos os objetos corrompidos, por exemplo

    fatal: loose object 91c5...51e5 (stored in .git/objects/06/91c5...51e5) is corrupt
    $ rm -v .git/objects/06/91c5...51e5
    
  • remova todos os objetos vazios, por exemplo

    error: object file .git/objects/06/91c5...51e5 is empty
    $ find .git/objects/ -size 0 -exec rm -vf "{}" \;
    
  • verifique uma mensagem de "link quebrado" ao:

    git ls-tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
    

    Isso informa de qual arquivo veio o blob corrompido!

  • para recuperar o arquivo, você pode ter muita sorte, e pode ser a versão que você já verificou em sua árvore de trabalho:

    git hash-object -w my-magic-file
    

    novamente, e se ele mostrar o SHA1 ausente (4b945 ..), está tudo pronto!

  • presumindo que era alguma versão mais antiga que estava quebrada, a maneira mais fácil de fazer isso é:

    git log --raw --all --full-history -- subdirectory/my-magic-file
    

    e isso irá mostrar-lhe todo o log desse arquivo (por favor, perceba que a árvore que você tinha pode não ser a árvore de nível superior, então você precisa descobrir em qual subdiretório ele estava sozinho), então agora você pode recriar o objeto ausente com objeto hash novamente.

  • para obter uma lista de todas as referências com commits, árvores ou blobs ausentes:

    $ git for-each-ref --format='%(refname)' | while read ref; do git rev-list --objects $ref >/dev/null || echo "in $ref"; done
    

    Pode não ser possível remover alguns desses refs usando os comandos branch -d ou tag -d regulares, já que eles morrerão se o git perceber a corrupção. Portanto, use o comando de encanamento git update-ref -d $ ref. Note que no caso de branches locais, este comando pode deixar a configuração de branch obsoleta para trás em .git / config. Ele pode ser excluído manualmente (procure a seção [branch "$ ref"]).

  • Depois que todos os refs estiverem limpos, ainda pode haver commits quebrados no reflog. Você pode limpar todos os reflogs usando git reflog expire --expire = now --all. Se você não quiser perder todos os seus reflogs, você pode pesquisar os refs individuais por reflogs quebrados:

    $ (echo HEAD; git for-each-ref --format='%(refname)') | while read ref; do git rev-list -g --objects $ref >/dev/null || echo "in $ref"; done
    

    (Observe a opção -g adicionada ao git rev-list.) Então, use git reflog expire --expire = now $ ref em cada um deles. Quando todos os refs e reflogs quebrados desaparecerem, execute git fsck --full para verificar se o repositório está limpo. Objetos pendentes estão ok.


Abaixo você pode encontrar o uso avançado de comandos que potencialmente podem causar perda de seus dados em seu repositório git se não forem usados ​​com sabedoria, então faça um backup antes de acidentalmente causar mais danos ao seu git. Experimente por sua própria conta e risco se você sabe o que está fazendo.


Para puxar o branch atual em cima do branch upstream após a busca:

$ git pull --rebase

Você também pode tentar verificar o novo branch e excluir o antigo:

$ git checkout -b new_master origin/master

Para encontrar o objeto corrompido no git para remoção, tente o seguinte comando:

while [ true ]; do f=`git fsck --full 2>&1|awk '{print $3}'|sed -r 's/(^..)(.*)/objects\/\1\/\2/'`; if [ ! -f "$f" ]; then break; fi; echo delete $f; rm -f "$f"; done

Para OSX, use em sed -Evez de sed -r.


Outra ideia é descompactar todos os objetos de arquivos de pacote para regenerar todos os objetos dentro de .git / objects, então tente executar os seguintes comandos dentro de seu repositório:

$ cp -fr .git/objects/pack .git/objects/pack.bak
$ for i in .git/objects/pack.bak/*.pack; do git unpack-objects -r < $i; done
$ rm -frv .git/objects/pack.bak

Se acima não ajudar, você pode tentar rsync ou copiar os objetos git de outro repo, por exemplo

$ rsync -varu git_server:/path/to/git/.git local_git_repo/
$ rsync -varu /local/path/to/other-working/git/.git local_git_repo/
$ cp -frv ../other_repo/.git/objects .git/objects

Para consertar o branch quebrado ao tentar finalizar a compra da seguinte maneira:

$ git checkout -f master
fatal: unable to read tree 5ace24d474a9535ddd5e6a6c6a1ef480aecf2625

Tente removê-lo e check-out do upstream novamente:

$ git branch -D master
$ git checkout -b master github/master

No caso de o git colocar você no estado desanexado, verifique o mastere mescle nele o branch desanexado.


Outra ideia é realocar o mestre existente recursivamente:

$ git reset HEAD --hard
$ git rebase -s recursive -X theirs origin/master

Veja também:

Kenorb
fonte
2

Aqui estão as etapas que segui para recuperar de um objeto blob corrompido.

1) Identificar blob corrompido

git fsck --full
  error: inflate: data stream error (incorrect data check)
  error: sha1 mismatch 241091723c324aed77b2d35f97a05e856b319efd
  error: 241091723c324aed77b2d35f97a05e856b319efd: object corrupt or missing
  ...

O blob corrompido é 241091723c324aed77b2d35f97a05e856b319efd

2) Mova o blob corrompido para um lugar seguro (por precaução)

mv .git/objects/24/1091723c324aed77b2d35f97a05e856b319efd ../24/

3) Obtenha o pai do blob corrompido

git fsck --full
  Checking object directories: 100% (256/256), done.
  Checking objects: 100% (70321/70321), done.
  broken link from    tree 0716831e1a6c8d3e6b2b541d21c4748cc0ce7180
              to    blob 241091723c324aed77b2d35f97a05e856b319efd

O hash pai é 0716831e1a6c8d3e6b2b541d21c4748cc0ce7180 .

4) Obtenha o nome do arquivo correspondente ao blob corrompido

git ls-tree 0716831e1a6c8d3e6b2b541d21c4748cc0ce7180
  ...
  100644 blob 241091723c324aed77b2d35f97a05e856b319efd    dump.tar.gz
  ...

Encontre esse arquivo específico em um backup ou no repositório git upstream (no meu caso, é dump.tar.gz ). Em seguida, copie-o em algum lugar dentro do seu repositório local.

5) Adicionar arquivo corrompido anteriormente no banco de dados de objetos git

git hash-object -w dump.tar.gz

6) Comemore!

git gc
  Counting objects: 75197, done.
  Compressing objects: 100% (21805/21805), done.
  Writing objects: 100% (75197/75197), done.
  Total 75197 (delta 52999), reused 69857 (delta 49296)
Jonathan Maim
fonte
Isso não funcionou para mim. A Etapa 4 resultou em git ls-tree 9504a07fb803edfdf0c1dd99c5d561274af87982 error: Could not read 19505205fd1f219993da9b75846fff3cf432152d, e eu também tentei de novo sem a Etapa 2, e isso resultou emgit ls-tree 9504a07fb803edfdf0c1dd99c5d561274af87982 error: inflate: data stream error (invalid stored block lengths) fatal: failed to read object 19505205fd1f219993da9b75846fff3cf432152d: Invalid argument
Ryan
1

O checkout do Git pode selecionar arquivos individuais de uma revisão. Basta fornecer o hash de confirmação e o nome do arquivo. Informações mais detalhadas aqui.

Eu acho que a maneira mais fácil de consertar isso com segurança é reverter para o backup não confirmado mais recente e selecionar seletivamente os arquivos não corrompidos dos commits mais recentes. Boa sorte!

Tim Lin
fonte
1

Aqui estão duas funções que podem ajudar se seu backup estiver corrompido ou se você tiver alguns backups parcialmente corrompidos (isso pode acontecer se você fizer backup de objetos corrompidos).

Execute ambos no repositório que você está tentando recuperar.

Aviso padrão: use apenas se você estiver realmente desesperado e tiver feito backup de seu repositório (corrompido). Isso pode não resolver nada, mas pelo menos deve destacar o nível de corrupção.

fsck_rm_corrupted() {
    corrupted='a'
    while [ "$corrupted" ]; do
        corrupted=$(                                  \
        git fsck --full --no-dangling 2>&1 >/dev/null \
            | grep 'stored in'                          \
            | sed -r 's:.*(\.git/.*)\).*:\1:'           \
        )
        echo "$corrupted"
        rm -f "$corrupted"
    done
}

if [ -z "$1" ]  || [ ! -d "$1" ]; then
    echo "'$1' is not a directory. Please provide the directory of the git repo"
    exit 1
fi

pushd "$1" >/dev/null
fsck_rm_corrupted
popd >/dev/null

e

unpack_rm_corrupted() {
    corrupted='a'
    while [ "$corrupted" ]; do
        corrupted=$(                                  \
        git unpack-objects -r < "$1" 2>&1 >/dev/null \
            | grep 'stored in'                          \
            | sed -r 's:.*(\.git/.*)\).*:\1:'           \
        )
        echo "$corrupted"
        rm -f "$corrupted"
    done
}

if [ -z "$1" ]  || [ ! -d "$1" ]; then
    echo "'$1' is not a directory. Please provide the directory of the git repo"
    exit 1
fi

for p in $1/objects/pack/pack-*.pack; do
    echo "$p"
    unpack_rm_corrupted "$p"
done
go2null
fonte
0

Resolvi esse problema para adicionar algumas alterações como git add -A e git commit novamente.

Dmitriy S
fonte