Cofnij reset git - twardy z niezatwierdzonymi plikami w obszarze przemieszczania

110

Próbuję odzyskać swoją pracę. Głupio to zrobiłem git reset --hard, ale wcześniej robiłem tylko get add .i nie robiłem git commit. Proszę pomóż! Oto mój dziennik:

MacBookPro:api user$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)

#   modified:   .gitignore
...


MacBookPro:api user$ git reset --hard
HEAD is now at ff546fa added new strucuture for api

Czy git reset --hardw tej sytuacji można cofnąć ?

eistrati
źródło
@MarkLongair awesome man! Właśnie odzyskałeś moją pracę! Napisałem skrypt w Pythonie, aby utworzyć pliki wszystkich danych wyjściowych! Dodam scenariusz jako odpowiedź
Boy
4
Nie „głupio”… ale „naiwnie”… bo właśnie zrobiłem TO SAMO!
Rosdi Kasim
Nadal mogło być głupio ;-)
Duncan McGregor
Tutaj jest świetny artykuł o tym, jak to odwrócić. To zajmie trochę pracy ręcznej.
Jan Swart
@MarkLongair `` `` znajdź .git / objects / -type f -printf '% TY-% Tm-% Td% TT% p \ n' | sortowanie `` działało dla mnie. pojawiają się również daty, zacznij sprawdzać bloby od końca.
ZAky

Odpowiedzi:

175

Powinieneś być w stanie odzyskać wszystkie pliki, które dodałeś do indeksu (np. Tak jak w twojej sytuacji git add .), chociaż może to wymagać trochę pracy. Aby dodać plik do indeksu, git dodaje go do bazy danych obiektów, co oznacza, że ​​można go odzyskać, o ile jeszcze nie nastąpiło czyszczenie pamięci. Oto przykład, jak to zrobić, podany w odpowiedzi Jakuba Narębskiego :

Jednak wypróbowałem to w repozytorium testowym i było kilka problemów - --cachedpowinno być --cachei stwierdziłem, że w rzeczywistości nie utworzyło .git/lost-foundkatalogu. Jednak następujące kroki zadziałały dla mnie:

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)")

Powinno to wyprowadzić wszystkie obiekty w bazie danych obiektów, które nie są osiągalne przez żaden ref, w indeksie ani przez reflog. Wynik będzie wyglądał mniej więcej tak:

unreachable blob 907b308167f0880fb2a5c0e1614bb0c7620f9dc3
unreachable blob 72663d3adcf67548b9e0f0b2eeef62bce3d53e03

... i dla każdego z tych plamek możesz:

git show 907b308

Aby wyświetlić zawartość pliku.


Za dużo produkcji?

Aktualizacja w odpowiedzi na sehe „s komentarz poniżej:

Jeśli zauważysz, że masz wiele zatwierdzeń i drzew wyświetlonych w danych wyjściowych tego polecenia, możesz chcieć usunąć z wyniku wszelkie obiekty, do których istnieją odniesienia z zatwierdzeń bez odniesienia. (Zazwyczaj i tak możesz wrócić do tych zatwierdzeń przez reflog - interesują nas tylko obiekty, które zostały dodane do indeksu, ale nigdy nie można ich znaleźć za pomocą zatwierdzenia).

Najpierw zapisz dane wyjściowe polecenia, używając:

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") > all

Teraz nazwy obiektów tych nieosiągalnych zatwierdzeń można znaleźć za pomocą:

egrep commit all | cut -d ' ' -f 3

Możesz więc znaleźć tylko drzewa i obiekty, które zostały dodane do indeksu, ale w żadnym momencie nie zostały zatwierdzone, za pomocą:

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") \
  $(egrep commit all | cut -d ' ' -f 3)

To ogromnie zmniejsza liczbę obiektów, które musisz wziąć pod uwagę.


Aktualizacja: Philip Oakley poniżej sugeruje inny sposób ograniczenia liczby obiektów do rozważenia, czyli po prostu uwzględnienie ostatnio zmodyfikowanych plików w ramach .git/objects. Możesz je znaleźć za pomocą:

find .git/objects/ -type f -printf '%TY-%Tm-%Td %TT %p\n' | sort

(Znalazłem to findwywołanie tutaj .) Koniec tej listy może wyglądać następująco:

2011-08-22 11:43:43.0234896770 .git/objects/b2/1700b09c0bc0fc848f67dd751a9e4ea5b4133b
2011-09-13 07:36:37.5868133260 .git/objects/de/629830603289ef159268f443da79968360913a

W takim przypadku możesz zobaczyć te obiekty za pomocą:

git show b21700b09c0bc0fc848f67dd751a9e4ea5b4133b
git show de629830603289ef159268f443da79968360913a

(Zauważ, że musisz usunąć /na końcu ścieżki, aby uzyskać nazwę obiektu.)

Mark Longair
źródło
zanim opublikowałem swoją odpowiedź, rozważałem włączenie dokładnie tych kroków. Jednak próbując tego z jednym z moich lokalnych repozytoriów, otrzymałem setki nieosiągalnych pozycji i nie mogłem znaleźć odpowiedniego podejścia, powiedzmy, do posortowania ich według daty przesłania. To byłoby niesamowite
zobaczcie
3
Czy nie można spojrzeć na znaczniki czasu obiektów dla najnowszych obiektów, które są późniejsze niż ostatnie zatwierdzenie? albo coś mi brakuje.
Philip Oakley
1
@Philip Oakley: dzięki za sugestię, dodałem sugestię znalezienia ostatnio zmodyfikowanych obiektów w bazie danych obiektów.
Mark Longair,
2
Koleś, to jest niesamowite! To właśnie uratowało moje @ $$. Nauczyłem się trochę gitów. Lepiej uważaj na to, co dodajesz do indeksu ...
bowsersenior
2
nie ukraść tu nikomu wapiennego światła. Ale to tylko uwaga, ponieważ właśnie napotkałem ten sam problem i pomyślałem, że straciłem całą swoją pracę. Dla tych, którzy używają IDE , IDE zwykle mają rozwiązanie do odzyskiwania jednym kliknięciem. W przypadku PHPstorm musiałem tylko kliknąć prawym przyciskiem myszy katalog i wybrać pokaż lokalną historię, a następnie powrócić do ostatniego poprawnego stanu.
Shalom Sam,
58

Właśnie zrobiłem git reset --hardi straciłem jedno zatwierdzenie. Ale znałem wartość skrótu zatwierdzenia, więc mogłem git cherry-pick COMMIT_HASHgo przywrócić.

Zrobiłem to w ciągu kilku minut od utraty zatwierdzenia, więc może to zadziałać dla niektórych z was.

Richard Saunders
źródło
5
Dziękuję dziękuję dziękuję Udało mi się odzyskać dni pracy dzięki tej odpowiedzi
Dace
21
Prawdopodobnie warto wspomnieć, że możesz zobaczyć te skróty, używając git reflognp. git reset --hard-> git reflog(patrząc na hash HEAD @ {1}) i wreszciegit cherry-pick COMMIT_HASH
Javier López
2
Chłopaki, którzy to czytają, uważajcie, że działa to tylko dla pojedynczego skrótu zatwierdzenia, jeśli było więcej niż jedno zatwierdzenie, nie rozwiąże to od razu!
Ain Tohvri,
4
Możesz faktycznie zresetować „do przodu”. Więc jeśli zresetowałeś wiele zatwierdzeń, wykonanie kolejnego 'git reset --hard <commit>' powinno przywrócić cały łańcuch zatwierdzeń do tego momentu. (biorąc pod uwagę, że nie mają jeszcze GC)
akimsko
6
One-liner do odzyskiwania utraconych z zatwierdzeń git reset --hard(zakładając, że nie mają niczego innego po tej komendzie zrobione) jestgit reset --hard @{1}
Ajedi32
13

Dzięki Markowi Longairowi odzyskałem swoje rzeczy!

Najpierw zapisałem wszystkie skróty do pliku:

git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") > allhashes

następnie umieszczam je wszystkie (usuwając `` nieosiągalny blob '') na liście i umieszczam wszystkie dane w nowych plikach ... musisz wybrać swoje pliki i zmienić ich nazwy, których potrzebujesz ... ale potrzebowałem tylko kilku pliki ... mam nadzieję, że to komuś pomoże ...

commits = ["c2520e04839c05505ef17f985a49ffd42809f",
    "41901be74651829d97f29934f190055ae4e93",
    "50f078c937f07b508a1a73d3566a822927a57",
    "51077d43a3ed6333c8a3616412c9b3b0fb6d4",
    "56e290dc0aaa20e64702357b340d397213cb",
    "5b731d988cfb24500842ec5df84d3e1950c87",
    "9c438e09cf759bf84e109a2f0c18520",
    ...
    ]

from subprocess import call
filename = "file"
i = 1
for c in commits:
    f = open(filename + str(i),"wb")
    call(["git", "show", c],stdout=f)
    i+=1
Chłopak
źródło
1
Tak! Właśnie tego potrzebowałem. Podoba mi się również skrypt w Pythonie do odtwarzania wszystkich plików. Pozostałe odpowiedzi mnie zdenerwowały o utracie moich danych ze zbierania śmieci, więc dumping plików jest korzystna dla mnie :)
Eric Olson
1
Napisałem ten skrypt w oparciu o tę odpowiedź. Działa po wyjęciu z pudełka: github.com/pendashteh/git-recover-index
Alexar
1
I łatwiej zautomatyzować to tak: mkdir lost; git fsck --cache --unreachable $(git for-each-ref --format="%(objectname)") | grep -Po '\s\S{40}$' | xargs -i echo "git show {} > lost/{}.blob" | sh. Pliki skończą się nalost/*.blob
Matija Nalis
6

Rozwiązanie @ Ajedi32 w komentarzach działało dla mnie dokładnie w tej sytuacji.

git reset --hard @{1}

Zauważ, że wszystkie te rozwiązania polegają na tym, że nie ma git gc, a niektóre z nich mogą powodować jeden z nich, więc spakowałbym zawartość twojego katalogu .git przed wypróbowaniem czegokolwiek, abyś miał migawkę, do której możesz wrócić, jeśli nie. nie działa dla ciebie.

Duncan McGregor
źródło
Miałem wiele niezaakceptowanych plików. Następnie zatwierdziłem 1 plik i zrobiłem git reset --hard, co zepsuło moje repozytorium w nieznany sposób. @Duncan, co robi @ {1}? i do którego komentarza się odnosisz? Czy to resetuje git reset?
alpha_989
Myślę, że @ {1} jest względnym odniesieniem do przedostatniego zatwierdzenia, ale nie jestem ekspertem, po prostu opisuję, co zadziałało
Duncan McGregor
3

Wpadłem na ten sam problem, ale nie dodałem zmian do indeksu. Więc wszystkie powyższe polecenia nie przywróciły pożądanych zmian.

Po wszystkich powyższych wyszukanych odpowiedziach jest to naiwna wskazówka, ale może uratuje kogoś, kto nie pomyślał o tym pierwszy, tak jak ja.

Zrozpaczony, próbowałem nacisnąć CTRL-Z w moim edytorze (LightTable), raz w każdej otwartej karcie - to na szczęście przywróciło plik na tej karcie do jego najnowszego stanu przed git reset --hard. HTH.

olange
źródło
1
Dokładnie ta sama sytuacja, brakowało dodania indeksu i twardego resetu, a żadne z powyższych rozwiązań nie działało. Ten był mądrym posunięciem, dzięki Ctrl + Z i uratowałem mój dzień. Mój redaktor: SublimeText. Jeden z mojej strony!
Vivek
1

Jest to prawdopodobnie oczywiste dla specjalistów od gitów, ale chciałem to wyjaśnić, ponieważ w moich gorączkowych poszukiwaniach nie widziałem tego.

Wystawiłem kilka plików i git reset --hardtrochę się wystraszyłem, a potem zauważyłem, że mój status pokazał, że wszystkie moje pliki są nadal w fazie przejściowej, a wszystkie ich usunięcia nie znajdują się na etapie.

W tym momencie możesz zatwierdzić te etapowe zmiany, o ile nie przygotujesz ich usuwania. Po tym musisz tylko zebrać się na odwagęgit reset --hard jeden raz, co spowoduje powrót do tych zmian, które zainscenizowałeś, a teraz właśnie dokonałeś.

Ponownie, prawdopodobnie dla większości nie jest to nic nowego, ale mam nadzieję, że skoro mi pomogło i nie znalazłem nic, co by to sugerowało, może pomóc komuś innemu.

ddoty
źródło
1

Boże, szarpałem się za włosy, dopóki nie natrafiłem na to pytanie i odpowiedzi. Uważam, że poprawna i zwięzła odpowiedź na zadane pytanie jest dostępna tylko wtedy, gdy połączysz dwa z powyższych komentarzy, więc tutaj wszystko jest w jednym miejscu:

  1. Jak wspomniano w chilicuil, uruchom tam, git reflogaby zidentyfikować skrót zatwierdzenia, do którego chcesz wrócić

  2. Jak wspomniał akimsko, prawdopodobnie NIE będziesz chciał wybierać wiśni, chyba że straciłeś tylko jedno zatwierdzenie, więc powinieneś wtedy uciekać git reset --hard <hash-commit-you-want>

Uwaga dla użytkowników egit Eclipse: nie mogłem znaleźć sposobu na wykonanie tych kroków w Eclipse z egit. Zamknięcie Eclipse, uruchomienie powyższych poleceń z okna terminala, a następnie ponowne otwarcie Eclipse działało dobrze dla mnie.

Lolo
źródło
0

Powyższe rozwiązania mogą działać, jednak istnieją prostsze sposoby na naprawienie tego problemu zamiast przechodzenia przez gitzłożone cofanie. Domyślam się, że większość resetów git ma miejsce na niewielkiej liczbie plików, a jeśli już używasz VIM, może to być najbardziej efektywne czasowo rozwiązanie. Zastrzeżenie polega na tym, że już musisz używać ViM'strwałego cofania, którego powinieneś używać w obu przypadkach, ponieważ zapewnia to możliwość cofnięcia nieograniczonej liczby zmian.

Oto kroki:

  1. W vimie naciśnij :i wpisz polecenie set undodir. Jeśli persistent undowłączyłeś w swoim .vimrc, pokaże wynik podobny do undodir=~/.vim_runtime/temp_dirs/undodir.

  2. W swoim repozytorium użyj, git logaby znaleźć datę / godzinę ostatniego zatwierdzenia

  3. W swojej powłoce przejdź do undodirusing cd ~/.vim_runtime/temp_dirs/undodir.

  4. W tym katalogu użyj tego polecenia, aby znaleźć wszystkie pliki, które zmieniłeś od ostatniego zatwierdzenia

    find . -newermt "2018-03-20 11:24:44" \! -newermt "2018-03-23" \( -type f -regextype posix-extended -regex '.*' \) \-not -path "*/env/*" -not -path "*/.git/*"

    Tutaj „2018-03-20 11:24:44” to data i godzina ostatniego zatwierdzenia. Jeśli data wykonania git reset --hardto „2018-03-22”, użyj „2018-03-22”, a następnie „2018-03-23”. Dzieje się tak z powodu dziwactwa znalezienia, w którym dolna granica jest włącznie, a górna jest wyłączna. https://unix.stackexchange.com/a/70404/242983

  5. Następnie przejdź do każdego z plików, otwórz je w vimie i zrób „wcześniej 20m”. Więcej informacji na temat „wcześniej” można znaleźć, używając „h wcześniej”. Tutaj earlier 20moznacza powrót do stanu pliku 20 minut wstecz, zakładając, że zrobiłeś git hard --reset, 20 minut wstecz. Powtórz to dla wszystkich plików, które zostały wyplute z findpolecenia. Jestem pewien, że ktoś może napisać skrypt, który będzie łączył te rzeczy.

alpha_989
źródło
0

Używam IntelliJ i mogłem po prostu przejść przez każdy plik i wykonać:

Edit -> reload from disk

Na szczęście zrobiłem to git statustuż przed wymazaniem zmian roboczych, więc wiedziałem dokładnie, co muszę ponownie załadować.

Forrest
źródło
0

Zapamiętanie hierarchii plików i użycie techniki Marka Longaira z modyfikacją Phila Oakleya daje dramatyczne rezultaty.

Zasadniczo, jeśli przynajmniej dodałeś pliki do repozytorium, ale go nie zatwierdziłeś, możesz je przywrócić, używając interaktywnego git showdziennika, przejrzyj dziennik i użyj przekierowania powłoki, aby utworzyć każdy plik (zapamiętując ścieżkę zainteresowania).

HTH!

ainthu
źródło
0

Jeśli niedawno przeglądałeś, git diffto istnieje inny sposób na odzyskanie po takich incydentach, nawet jeśli jeszcze nie wprowadziłeś zmian: jeśli dane wyjściowe git diffnadal znajdują się w buforze konsoli, możesz po prostu przewinąć w górę, skopiować i wkleić diff do pliku i użyć patchnarzędzia do stosowania diff do drzewa: patch -p0 < file. Takie podejście kilka razy mnie uratowało.

Dezorientacja
źródło