Comment récupérer des objets Git endommagés par une panne de disque dur?

92

J'ai eu une panne de disque dur qui a entraîné l'endommagement de certains fichiers d'un référentiel Git. Lors de l'exécution, git fsck --fullj'obtiens la sortie suivante:

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

J'ai des sauvegardes du référentiel, mais la seule sauvegarde qui inclut le fichier du pack l'a déjà endommagé. Je pense donc que je dois trouver un moyen de récupérer les objets uniques à partir de différentes sauvegardes et demander à Git de produire un nouveau pack avec uniquement les objets corrects.

Pouvez-vous s'il vous plaît me donner des conseils sur la façon de réparer mon référentiel?

Christian
la source
2
Cela m'est juste arrivé. Je ne veux pas gâcher les objets git ... donc re-cloné le projet du référentiel distant dans un nouveau dossier, puis copiez simplement tous les fichiers de mes référentiels problématiques (à l'exclusion du .gitdossier bien sûr) dans le référentiel fraîchement cloné ... et ensuite fait git statusdans le nouveau dépôt ... git détecte correctement toutes les modifications affectées à mes fichiers et je peux recommencer mon travail.
Rosdi Kasim

Réponses:

82

Dans certaines sauvegardes précédentes, vos objets défectueux peuvent avoir été compressés dans différents fichiers ou être encore des objets lâches. Ainsi, vos objets peuvent être récupérés.

Il semble qu'il y ait quelques objets défectueux dans votre base de données. Vous pouvez donc le faire manuellement.

À cause de git hash-object, git mktreeet git commit-treen'écrivez pas les objets car ils se trouvent dans le pack, alors commencez à faire ceci:

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

(Vos packs sont sortis du référentiel et décompressés à nouveau dans celui-ci; seuls les bons objets sont maintenant dans la base de données)

Tu peux faire:

git cat-file -t 6c8cae4994b5ec7891ccb1527d30634997a978ee

et vérifiez le type de l'objet.

Si le type est blob: récupérez le contenu du fichier à partir des sauvegardes précédentes (avec git showou git cat-fileou git unpack-file; alors vous pouvez git hash-object -wréécrire l'objet dans votre référentiel actuel.

Si le type est tree: vous pouvez utiliser git ls-treepour récupérer l'arborescence des sauvegardes précédentes; puis git mktreepour le réécrire dans votre référentiel actuel.

Si le type est commit: la même chose avec git show, git cat-fileet git commit-tree.

Bien sûr, je sauvegarderais votre copie de travail originale avant de commencer ce processus.

Jetez également un œil à Comment récupérer un objet blob corrompu .

Daniel Fanjul
la source
1
Merci, cela m'a sauvé! Je publierai mes étapes exactes sous forme de réponse distincte.
Christian
Juste une correction: la commande pour se termine par "done" et non par "end".
Felipe
.git/objects/pack/
J'essaye
pour moi a; manquait après git unpack-objects -r <$ i
mithrandir
@mithrandir: si vous mettez le 'done' dans la ligne précédente: oui, vous avez besoin d'un point-virgule. Si vous tapez exactement ce que j'ai écrit, vous ne le faites pas.
Daniel Fanjul
38

Banengusk me mettait sur la bonne voie. Pour plus d'informations, je souhaite publier les étapes que j'ai suivies pour corriger la corruption de mon référentiel. J'ai eu la chance de trouver tous les objets nécessaires soit dans des packs plus anciens, soit dans des sauvegardes de référentiel.

# 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!
Christian
la source
3
Ajout à ceci: Si la sauvegarde contient les fichiers manquants dans un pack, la bonne façon d'extraire un blob du pack est 'git cat-file blob <SHA1>> file.dat', et de le remettre dans le fichier endommagé repo, faites 'git hash-object -w file.dat', comme dans la réponse de Daniel.
Emil Styrke
Comment trouvez-vous le dernier pack non corrompu? merci
Romain Ourgorry
18

Essayez d'abord les commandes suivantes (réexécutez si nécessaire):

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

Et puis vous avez encore des problèmes, essayez de:

  • supprimer tous les objets corrompus, par exemple

    fatal: loose object 91c5...51e5 (stored in .git/objects/06/91c5...51e5) is corrupt
    $ rm -v .git/objects/06/91c5...51e5
    
  • supprimer tous les objets vides, par ex.

    error: object file .git/objects/06/91c5...51e5 is empty
    $ find .git/objects/ -size 0 -exec rm -vf "{}" \;
    
  • vérifier un message "lien rompu" en:

    git ls-tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
    

    Cela vous indiquera de quel fichier le blob corrompu provient!

  • pour récupérer le fichier, vous pourriez être vraiment chanceux, et c'est peut-être la version que vous avez déjà extraite dans votre arbre de travail:

    git hash-object -w my-magic-file
    

    encore une fois, et s'il sort le SHA1 manquant (4b945 ..) vous avez maintenant terminé!

  • en supposant que c'était une ancienne version qui était cassée, le moyen le plus simple de le faire est de faire:

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

    et cela vous montrera tout le journal de ce fichier (veuillez vous rendre compte que l'arborescence que vous aviez n'est peut-être pas l'arborescence de niveau supérieur, vous devez donc déterminer vous-même dans quel sous-répertoire il se trouvait), alors vous pouvez maintenant recréer le objet manquant avec objet de hachage à nouveau.

  • pour obtenir une liste de toutes les références avec des commits, des arbres ou des blobs manquants:

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

    Il peut ne pas être possible de supprimer certaines de ces références en utilisant les commandes branch -d ou tag -d normales, car elles mourront si git remarque la corruption. Utilisez donc la commande de plomberie git update-ref -d $ ref à la place. Notez que dans le cas de branches locales, cette commande peut laisser la configuration de branche périmée dans .git / config. Il peut être supprimé manuellement (recherchez la section [branche "$ ref"]).

  • Une fois que toutes les références sont propres, il peut encore y avoir des commits interrompus dans le reflog. Vous pouvez effacer tous les reflogs en utilisant git reflog expire --expire = now --all. Si vous ne voulez pas perdre tous vos reflogs, vous pouvez rechercher les refs individuels pour les reflogs cassés:

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

    (Notez l'option -g ajoutée à git rev-list.) Ensuite, utilisez git reflog expire --expire = now $ ref sur chacun d'entre eux. Lorsque toutes les références et reflogs cassés ont disparu, exécutez git fsck --full afin de vérifier que le référentiel est propre. Les objets pendantes sont ok.


Vous trouverez ci-dessous une utilisation avancée des commandes qui peuvent potentiellement entraîner la perte de vos données dans votre référentiel git si elles ne sont pas utilisées à bon escient, alors faites une sauvegarde avant de causer accidentellement d'autres dommages à votre git. Essayez à vos risques et périls si vous savez ce que vous faites.


Pour extraire la branche actuelle au-dessus de la branche amont après l'extraction:

$ git pull --rebase

Vous pouvez également essayer de retirer une nouvelle branche et de supprimer l'ancienne:

$ git checkout -b new_master origin/master

Pour trouver l'objet corrompu dans git à supprimer, essayez la commande suivante:

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

Pour OSX, utilisez à la sed -Eplace de sed -r.


Une autre idée est de décompresser tous les objets des fichiers de pack pour régénérer tous les objets dans .git / objects, alors essayez d'exécuter les commandes suivantes dans votre référentiel:

$ 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

Si ci-dessus ne vous aide pas, vous pouvez essayer de rsync ou de copier les objets git d'un autre dépôt, par exemple

$ 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

Pour réparer la branche cassée lors de la tentative de paiement, procédez comme suit:

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

Essayez de le supprimer et de procéder à nouveau au paiement en amont:

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

Dans le cas où git vous mettrait dans l'état détaché, vérifiez le masteret fusionnez-y la branche détachée.


Une autre idée est de rebaser le maître existant de manière récursive:

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

Voir également:

Kenorb
la source
2

Voici les étapes que j'ai suivies pour récupérer à partir d'un objet blob corrompu.

1) Identifiez le blob corrompu

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

Le blob corrompu est 241091723c324aed77b2d35f97a05e856b319efd

2) Déplacez le blob corrompu vers un endroit sûr (juste au cas où)

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

3) Obtenir le parent du blob corrompu

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

Le hachage parent est 0716831e1a6c8d3e6b2b541d21c4748cc0ce7180 .

4) Obtenez le nom de fichier correspondant à l'objet blob corrompu

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

Trouvez ce fichier particulier dans une sauvegarde ou dans le référentiel git en amont (dans mon cas, il s'agit de dump.tar.gz ). Puis copiez-le quelque part dans votre référentiel local.

5) Ajouter un fichier précédemment corrompu dans la base de données d'objets git

git hash-object -w dump.tar.gz

6) Célébrez!

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
la source
Cela n'a pas fonctionné pour moi. L'étape 4 a abouti git ls-tree 9504a07fb803edfdf0c1dd99c5d561274af87982 error: Could not read 19505205fd1f219993da9b75846fff3cf432152d, et j'ai aussi essayé à nouveau sans l'étape 2, et cela a abouti àgit ls-tree 9504a07fb803edfdf0c1dd99c5d561274af87982 error: inflate: data stream error (invalid stored block lengths) fatal: failed to read object 19505205fd1f219993da9b75846fff3cf432152d: Invalid argument
Ryan
1

Git checkout peut en fait sélectionner des fichiers individuels à partir d'une révision. Donnez-lui simplement le hachage de validation et le nom du fichier. Plus d'informations détaillées ici.

Je suppose que le moyen le plus simple de résoudre ce problème en toute sécurité est de revenir à la dernière sauvegarde non validée, puis de sélectionner sélectivement les fichiers non corrompus à partir de nouveaux commits. Bonne chance!

Tim Lin
la source
1

Voici deux fonctions qui peuvent vous aider si votre sauvegarde est corrompue ou si vous avez également quelques sauvegardes partiellement corrompues (cela peut se produire si vous sauvegardez les objets corrompus).

Exécutez les deux dans le dépôt que vous essayez de récupérer.

Avertissement standard: à n'utiliser que si vous êtes vraiment désespéré et que vous avez sauvegardé votre dépôt (corrompu). Cela pourrait ne rien résoudre, mais au moins devrait mettre en évidence le niveau de corruption.

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

et

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
la source
0

J'ai résolu ce problème pour ajouter des changements comme git add -A et git commit à nouveau.

Dmitriy S
la source