Jak odzyskać obiekty Git uszkodzone w wyniku awarii dysku twardego?

92

Miałem awarię dysku twardego, która spowodowała uszkodzenie niektórych plików repozytorium Git. Podczas pracy git fsck --fullotrzymuję następujące dane wyjściowe:

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

Mam kopie zapasowe repozytorium, ale jedyna kopia zapasowa zawierająca plik pakietu ma już uszkodzony. Myślę więc, że muszę znaleźć sposób na pobranie pojedynczych obiektów z różnych kopii zapasowych i jakoś poinstruować Git, aby utworzył nowy pakiet z tylko poprawnymi obiektami.

Czy możesz mi podpowiedzieć, jak naprawić moje repozytorium?

chrześcijanin
źródło
2
To właśnie mi się przydarzyło. Nie chcę zepsuć obiektów git ... więc ponownie sklonowałem projekt ze zdalnego repozytorium do nowego folderu, a następnie po prostu skopiuj wszystkie pliki z moich problematycznych repozytoriów (z wyłączeniem .gitfolderu oczywiście) do świeżo sklonowanego repozytorium ... a potem git statusw nowym repozytorium ... git poprawnie wykrywa wszystkie zmiany w moich plikach i mogę ponownie rozpocząć pracę.
Rosdi Kasim

Odpowiedzi:

82

W niektórych poprzednich kopiach zapasowych złe obiekty mogły być spakowane w innych plikach lub mogą być jeszcze luźnymi obiektami. Więc twoje obiekty mogą zostać odzyskane.

Wygląda na to, że w twojej bazie danych jest kilka złych obiektów. Możesz więc zrobić to ręcznie.

Z powodu git hash-object, git mktreea git commit-treenie pisać, ponieważ obiekty znajdują się w opakowaniu, a potem zacznij to robić:

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

(Twoje paczki są przenoszone z repozytorium i ponownie w nim rozpakowywane; tylko dobre obiekty są teraz w bazie danych)

Możesz to zrobić:

git cat-file -t 6c8cae4994b5ec7891ccb1527d30634997a978ee

i sprawdź typ obiektu.

Jeśli typ to blob: pobierz zawartość pliku z poprzednich kopii zapasowych (za pomocą git showlub git cat-filelub git unpack-file; wtedy możesz git hash-object -wprzepisać obiekt w bieżącym repozytorium.

Jeśli typ to drzewo: możesz użyć git ls-treedo odzyskania drzewa z poprzednich kopii zapasowych; następnie git mktreezapisać go ponownie w bieżącym repozytorium.

Jeśli typ jest popełnić: to samo z git show, git cat-fileigit commit-tree .

Oczywiście wykonałbym kopię zapasową oryginalnej kopii roboczej przed rozpoczęciem tego procesu.

Zobacz też, jak odzyskać uszkodzony obiekt blob .

Daniel Fanjul
źródło
1
Dziękuję, to mnie uratowało! Dokładne kroki podam jako osobną odpowiedź.
Christian
Tylko poprawka: polecenie dla kończy się z „zakończone”, a nie „koniec”.
Felipe
próbuję to zrobić, ale .git/objects/pack/jest pusty
kirill_igum
dla mnie a; brakowało po git unpack-objects -r <$ i
mithrandir
@mithrandir: jeśli umieścisz „gotowe” w poprzednim wierszu: tak, potrzebujesz średnika. Jeśli wpiszesz dokładnie to, co napisałem, nie.
Daniel Fanjul,
38

Banengusk stawia mnie na właściwej drodze. W celu uzyskania dalszych informacji chcę opublikować kroki, które podjąłem, aby naprawić uszkodzenie repozytorium. Miałem szczęście znaleźć wszystkie potrzebne obiekty albo w starszych paczkach, albo w kopiach zapasowych repozytorium.

# 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!
chrześcijanin
źródło
3
Dodając do tego: Jeśli kopia zapasowa zawiera brakujące pliki w pakiecie, właściwym sposobem na usunięcie obiektu blob z pakietu jest „git cat-file blob <SHA1>> file.dat” i przywrócenie go z powrotem do uszkodzonego repo, wykonaj 'git hash-object -w file.dat', jak w odpowiedzi Daniela.
Emil Styrke
Jak znaleźć ostatnią nieuszkodzoną paczkę? dzięki
Romain Ourgorry
18

Najpierw wypróbuj następujące polecenia (w razie potrzeby uruchom ponownie):

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

A potem nadal masz problemy, spróbuj:

  • usuń wszystkie uszkodzone obiekty, np

    fatal: loose object 91c5...51e5 (stored in .git/objects/06/91c5...51e5) is corrupt
    $ rm -v .git/objects/06/91c5...51e5
    
  • usuń wszystkie puste przedmioty, np

    error: object file .git/objects/06/91c5...51e5 is empty
    $ find .git/objects/ -size 0 -exec rm -vf "{}" \;
    
  • sprawdź wiadomość „uszkodzony link”:

    git ls-tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
    

    Dzięki temu dowiesz się, z jakiego pliku pochodzi uszkodzony obiekt blob!

  • aby odzyskać plik, możesz mieć naprawdę szczęście i może to być wersja, którą już wypisałeś w swoim drzewie roboczym:

    git hash-object -w my-magic-file
    

    ponownie, a jeśli wyprowadza brakujący SHA1 (4b945 ..), wszystko gotowe!

  • zakładając, że zepsuta była jakaś starsza wersja, najłatwiej to zrobić:

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

    a to pokaże ci cały dziennik dla tego pliku (pamiętaj, że drzewo, które miałeś, może nie być drzewem najwyższego poziomu, więc musisz samodzielnie dowiedzieć się, w którym podkatalogu się znajduje), a następnie możesz teraz odtworzyć ponownie brakuje obiektu z hash-object.

  • aby uzyskać listę wszystkich referencji z brakującymi zatwierdzeniami, drzewami lub blobami:

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

    Może nie być możliwe usunięcie niektórych z tych referencji za pomocą zwykłych poleceń branch -d lub tag -d, ponieważ zginą, jeśli git zauważy uszkodzenie. Zamiast tego użyj polecenia hydraulicznego git update-ref -d $ ref. Zauważ, że w przypadku lokalnych gałęzi, to polecenie może pozostawić nieaktualną konfigurację gałęzi w .git / config. Można go usunąć ręcznie (poszukaj sekcji [branch "$ ref"]).

  • Po tym, jak wszyscy sędziowie są czyste, w reflogu nadal mogą być zepsute zatwierdzenia. Możesz wyczyścić wszystkie reflogi za pomocą git reflog expire --expire = now --all. Jeśli nie chcesz stracić wszystkich swoich reflogów, możesz przeszukać poszczególne referencje pod kątem uszkodzonych reflogów:

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

    (Zwróć uwagę na dodaną opcję -g do git rev-list.) Następnie użyj git reflog expire --expire = now $ ref na każdym z nich. Kiedy wszystkie zepsute referencje i reflogs znikną, uruchom git fsck --full, aby sprawdzić, czy repozytorium jest czyste. Wiszące obiekty są w porządku.


Poniżej możesz znaleźć zaawansowane użycie poleceń, które potencjalnie mogą spowodować utratę danych w repozytorium git, jeśli nie są używane mądrze, więc zrób kopię zapasową, zanim przypadkowo wyrządzisz dalsze szkody swojemu gitowi. Spróbuj na własne ryzyko, jeśli wiesz, co robisz.


Aby przeciągnąć bieżącą gałąź na gałąź upstream po pobraniu:

$ git pull --rebase

Możesz także spróbować wyewidencjonować nowy oddział i usunąć stary:

$ git checkout -b new_master origin/master

Aby znaleźć uszkodzony obiekt w git do usunięcia, wypróbuj następujące polecenie:

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

W przypadku OSX użyj sed -Ezamiast sed -r.


Innym pomysłem jest rozpakowanie wszystkich obiektów z plików paczek w celu ponownego wygenerowania wszystkich obiektów w .git / objects, więc spróbuj uruchomić następujące polecenia w swoim repozytorium:

$ 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

Jeśli powyższe nie pomoże, możesz spróbować rsync lub skopiować obiekty git z innego repozytorium, np

$ 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

Aby naprawić uszkodzoną gałąź podczas próby płatności w następujący sposób:

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

Spróbuj go usunąć i ponownie wyewidencjonuj z upstream:

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

W przypadku, gdy git wprowadzi cię w stan odłączony, wyewidencjonuj masteri połącz z nim odłączoną gałąź.


Innym pomysłem jest rekurencyjne przebudowanie istniejącego wzorca:

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

Zobacz też:

kenorb
źródło
2

Oto kroki, które wykonałem, aby odzyskać dane z uszkodzonego obiektu typu blob.

1) Zidentyfikuj uszkodzony obiekt BLOB

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

Uszkodzony obiekt BLOB to 241091723c324aed77b2d35f97a05e856b319efd

2) Przenieś uszkodzoną kroplę w bezpieczne miejsce (na wszelki wypadek)

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

3) Zdobądź rodzica uszkodzonego obiektu blob

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

Hash nadrzędny to 0716831e1a6c8d3e6b2b541d21c4748cc0ce7180 .

4) Pobierz nazwę pliku odpowiadającą uszkodzonemu obiektowi BLOB

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

Znajdź ten konkretny plik w kopii zapasowej lub w głównym repozytorium git (w moim przypadku jest to dump.tar.gz ). Następnie skopiuj go gdzieś w swoim lokalnym repozytorium.

5) Dodaj wcześniej uszkodzony plik do bazy danych obiektów git

git hash-object -w dump.tar.gz

6) Świętuj!

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
źródło
To nie zadziałało dla mnie. Krok 4 zaowocował git ls-tree 9504a07fb803edfdf0c1dd99c5d561274af87982 error: Could not read 19505205fd1f219993da9b75846fff3cf432152d, a ja również spróbowałem tego wszystkiego jeszcze raz bez kroku 2, a to zaowocowałogit ls-tree 9504a07fb803edfdf0c1dd99c5d561274af87982 error: inflate: data stream error (invalid stored block lengths) fatal: failed to read object 19505205fd1f219993da9b75846fff3cf432152d: Invalid argument
Ryan
1

Git Checkout może faktycznie wybrać pojedyncze pliki z wersji. Po prostu nadaj mu skrót zatwierdzenia i nazwę pliku. Więcej szczegółowych informacji tutaj.

Myślę, że najłatwiejszym sposobem na bezpieczne rozwiązanie tego problemu jest przywrócenie najnowszej niezatwierdzonej kopii zapasowej, a następnie selektywne wybranie nieuszkodzonych plików z nowszych zatwierdzeń. Powodzenia!

Tim Lin
źródło
1

Oto dwie funkcje, które mogą pomóc, jeśli kopia zapasowa jest uszkodzona lub masz kilka częściowo uszkodzonych kopii zapasowych (może się to zdarzyć, jeśli utworzysz kopię zapasową uszkodzonych obiektów).

Uruchom oba w repozytorium, które próbujesz odzyskać.

Standardowe ostrzeżenie: używaj tylko wtedy, gdy jesteś naprawdę zdesperowany i utworzyłeś kopię zapasową (uszkodzonego) repozytorium. To może niczego nie rozwiązać, ale powinno przynajmniej podkreślić poziom korupcji.

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

i

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
źródło
0

Rozwiązałem ten problem, dodając zmianę, taką jak git add -A i ponownie git commit.

Dmitriy S
źródło