Annulla git reset --hard con file non salvati nell'area di staging


110

Sto cercando di recuperare il mio lavoro. L'ho fatto stupidamente git reset --hard, ma prima l'ho fatto solo get add .e non l'ho fatto git commit. Per favore aiuto! Ecco il mio registro:

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

È possibile annullare git reset --hardin questa situazione?


@MarkLongair uomo fantastico! Hai appena riavuto il mio lavoro! Ho scritto uno script Python per creare file di tutto l'output! Aggiungerò il copione come risposta
Ragazzo

4
Non "stupidamente" .. ma "ingenuamente" ... perché ho appena fatto LO STESSO!
Rosdi Kasim

Potrebbe ancora essere stupidamente ;-)
Duncan McGregor

Ecco un ottimo articolo su come invertire parte di questo. Ci vorrà del lavoro manuale.
Jan Swart

@MarkLongair `` `trova .git / objects / -type f -printf '% TY-% Tm-% Td% TT% p \ n' | sort `` `` ha funzionato per me. vengono visualizzate anche le date, inizia a controllare i blob dalla fine.
ZAky

Risposte:


175

Dovresti essere in grado di recuperare tutti i file che hai aggiunto all'indice (ad esempio, come nella tua situazione, con git add .) anche se potrebbe essere un po 'di lavoro. Per aggiungere un file all'indice, git lo aggiunge al database degli oggetti, il che significa che può essere ripristinato a condizione che la raccolta dei rifiuti non sia ancora avvenuta. C'è un esempio di come farlo nella risposta di Jakub Narębski qui:

Tuttavia, l'ho provato su un repository di prova, e c'erano un paio di problemi - --cacheddovrebbe essere --cache, e ho scoperto che in realtà non creava la .git/lost-founddirectory. Tuttavia, i seguenti passaggi hanno funzionato per me:

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

Questo dovrebbe produrre tutti gli oggetti nel database degli oggetti che non sono raggiungibili da alcun riferimento, nell'indice o tramite il reflog. L'output sarà simile a questo:

unreachable blob 907b308167f0880fb2a5c0e1614bb0c7620f9dc3
unreachable blob 72663d3adcf67548b9e0f0b2eeef62bce3d53e03

... e per ciascuno di quei blob, puoi fare:

git show 907b308

Per visualizzare il contenuto del file.


Troppa potenza?

Aggiornamento in risposta a sehe 's commento qui sotto:

Se si scopre di avere molti commit e alberi elencati nell'output di quel comando, è possibile rimuovere dall'output qualsiasi oggetto a cui si fa riferimento da commit non referenziati. (In genere puoi comunque tornare a questi commit tramite il reflog: siamo interessati solo agli oggetti che sono stati aggiunti all'indice ma che non possono essere trovati tramite un commit.)

Per prima cosa, salva l'output del comando, con:

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

Ora i nomi degli oggetti di quei commit irraggiungibili possono essere trovati con:

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

Quindi puoi trovare solo gli alberi e gli oggetti che sono stati aggiunti all'indice, ma non sottoposti a commit in qualsiasi momento, con:

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

Ciò riduce enormemente il numero di oggetti che dovrai considerare.


Aggiornamento: Philip Oakley di seguito suggerisce un altro modo per ridurre il numero di oggetti da considerare, ovvero considerare solo i file modificati più di recente in .git/objects. Puoi trovarli con:

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

(Ho scoperto che findinvocazione qui .) La fine di quella lista potrebbe essere simile:

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

In tal caso puoi vedere quegli oggetti con:

git show b21700b09c0bc0fc848f67dd751a9e4ea5b4133b
git show de629830603289ef159268f443da79968360913a

(Nota che devi rimuovere /alla fine del percorso per ottenere il nome dell'oggetto.)


prima di pubblicare la mia risposta ho considerato di includere esattamente questi passaggi. Tuttavia, provando con uno dei miei repository locali, ho ottenuto centinaia di irraggiungibili e non sono riuscito a trovare un approccio adeguato per, ad esempio, ordinarli per data di invio. Sarebbe fantastico
guarda l'

3
Non è possibile dare un'occhiata ai timestamp degli oggetti per gli oggetti più recenti che sono successivi al tuo ultimo commit? Oppure mi sfugge qualcosa.
Philip Oakley,

1
@Philip Oakley: grazie per il suggerimento, ho aggiunto il suggerimento di trovare gli oggetti modificati più di recente nel database degli oggetti.
Mark Longair

2
Amico, è fantastico! Questo ha salvato il mio @ $$ proprio ora. E ho imparato un po 'di scienza geniale. Meglio guardare cosa aggiungi all'indice ...
bowsersenior

2
per non rubare la luce a nessuno qui. Ma solo come nota dato che mi sono imbattuto nello stesso problema e ho pensato di aver perso tutto il mio lavoro. Per coloro che utilizzano un IDE , gli IDE di solito hanno una soluzione di ripristino con un clic. In caso di PHPstorm, dovevo semplicemente fare clic con il pulsante destro del mouse sulla directory e selezionare mostra cronologia locale, quindi tornare all'ultimo stato valido.
Shalom Sam

58

Ho appena fatto un git reset --harde perso un impegno. Ma conoscevo l'hash del commit, quindi sono stato in grado git cherry-pick COMMIT_HASHdi ripristinarlo.

L'ho fatto pochi minuti dopo aver perso il commit, quindi potrebbe funzionare per alcuni di voi.


5
Grazie grazie grazie Riuscito a recuperare una giornata di lavoro grazie a questa risposta
Dace

21
Probabilmente vale la pena menzionare che puoi vedere quegli hash usando git reflog, ad esempio git reset --hard-> git reflog(guardando l'hash HEAD @ {1}) e infinegit cherry-pick COMMIT_HASH
Javier López

2
Ragazzi che leggono questo, fate attenzione che funziona solo per un singolo hash di commit, se ci fosse più di un singolo commit, non risolverà il problema in una volta!
Ain Tohvri

4
Puoi effettivamente resettare "avanti". Quindi, se il ripristino ha superato più commit, eseguire un altro "git reset --hard <commit>" dovrebbe ripristinare l'intera catena di commit fino a quel punto. (dato che non hanno ancora ottenuto il GC)
akimsko

6
L'unica git reset --hardriga per recuperare i commit persi con (supponendo che tu non abbia fatto nient'altro dopo quel comando) ègit reset --hard @{1}
Ajedi32

13

Grazie a Mark Longair ho riavuto la mia roba!

Per prima cosa ho salvato tutti gli hash in un file:

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

poi li metto tutti (rimuovendo la cosa 'blob irraggiungibile') in un elenco e metto i dati tutti in nuovi file ... devi scegliere i tuoi file e rinominarli di nuovo che ti servono ... ma ne avevo solo bisogno file..spero che questo aiuti qualcuno ...

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

1
Sì! Questo è esattamente ciò di cui avevo bisogno. Mi piace anche lo script Python per ricreare tutti i file. Le altre risposte mi hanno reso nervoso all'idea di perdere i miei dati con la raccolta dei rifiuti, quindi scaricare i file è una vittoria per me :)
Eric Olson

1
Ho scritto questo script sulla base di questa risposta. Funziona
immediatamente

1
Trovo più facile per automatizzare in questo modo: 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. I file finiranno inlost/*.blob
Matija Nalis

6

La soluzione di @ Ajedi32 nei commenti ha funzionato per me esattamente in questa situazione.

git reset --hard @{1}

Nota che tutte queste soluzioni si basano sul fatto che non ci sia git gc, e alcune di esse potrebbero causarne uno, quindi comprimerei il contenuto della tua directory .git prima di provare qualsiasi cosa in modo da avere un'istantanea a cui tornare se uno non lo fa lavoro per te.


Ho avuto un numero di file non registrati. Quindi ho eseguito il commit di 1 file e ne ho fatto uno git reset --hard, che ha incasinato il mio repository in modo sconosciuto. @ Duncan, cosa fa @ {1}? ea quale commento ti riferisci? Sta resettando un git reset?
alpha_989

Penso che @ {1} sia un riferimento relativo al penultimo commit, ma non sono un esperto, sto solo segnalando cosa ha funzionato per me
Duncan McGregor

3

Ho riscontrato lo stesso problema, ma non ho aggiunto le modifiche all'indice. Quindi tutti i comandi sopra non mi hanno riportato le modifiche desiderate.

Dopo tutte le risposte elaborate sopra, questo è un suggerimento ingenuo, ma potrebbe essere che salverà qualcuno che non ci ha pensato per primo, come ho fatto io.

In preda alla disperazione, ho provato a premere CTRL-Z nel mio editor (LightTable), una volta in ogni scheda aperta - questo fortunatamente ha ripristinato il file in quella scheda, al suo stato più recente prima del git reset --hard. HTH.


1
Esattamente la stessa situazione, non è stato possibile aggiungere l'indice e ha eseguito un hard reset e nessuna delle soluzioni ha funzionato sopra. Questa è stata una mossa SMART, grazie ho fatto Ctrl + Z e ho salvato la mia giornata. Il mio editore: SublimeText. Uno dal mio fianco!
Vivek

1

Questo è probabilmente ovvio per i professionisti di git là fuori, ma volevo metterlo su perché nella mia ricerca frenetica non ho visto questo sollevato.

Ho messo in scena alcuni file, e ho fatto un git reset --hard, sono andato un po 'fuori di testa, e poi ho notato che il mio stato mostrava tutti i miei file ancora messi in scena e tutte le loro eliminazioni non organizzate.

A questo punto è possibile eseguire il commit di tali modifiche in fasi, a condizione che non ne vengano eliminate le eliminazioni. Dopo questo, devi solo trovare il coraggio di farlogit reset --hard una volta, il che ti riporterà a quei cambiamenti che avevi messo in scena e ora appena commesso.

Ancora una volta, questo probabilmente non è nuovo per la maggior parte, ma spero che dal momento che mi ha aiutato e non ho trovato nulla che lo suggerisca, potrebbe aiutare qualcun altro.


1

Dio, mi sono tirato i capelli finché non mi sono imbattuto in questa domanda e nelle sue risposte. Credo che la risposta corretta e concisa alla domanda posta sia disponibile solo se metti insieme due dei commenti sopra, quindi qui è tutto in un unico posto:

  1. Come accennato da chilicuil, corri git reflogper identificare lì l'hash del commit a cui vuoi tornare

  2. Come accennato da akimsko, probabilmente NON vorrai scegliere a meno che tu non abbia perso solo un commit, quindi dovresti quindi eseguire git reset --hard <hash-commit-you-want>

Nota per gli utenti di egit Eclipse: non sono riuscito a trovare un modo per eseguire questi passaggi all'interno di Eclipse con egit. La chiusura di Eclipse, l'esecuzione dei comandi sopra da una finestra di terminale e quindi la riapertura di Eclipse ha funzionato bene per me.


0

Le soluzioni di cui sopra possono funzionare, tuttavia, ci sono modi più semplici per recuperare da questo invece di passare attraverso l' gitannullamento complesso. Immagino che la maggior parte dei ripristini git avvenga su un numero limitato di file e, se utilizzi già VIM, questa potrebbe essere la soluzione più efficiente in termini di tempo. L'avvertenza è che devi già utilizzare l' ViM'sannullamento persistente, che dovresti utilizzare in entrambi i modi, perché ti fornisce la possibilità di annullare un numero illimitato di modifiche.

Ecco i passaggi:

  1. In vim premere :e digitare il comando set undodir. Se hai persistent undoacceso nel tuo .vimrc, mostrerà un risultato simile a undodir=~/.vim_runtime/temp_dirs/undodir.

  2. Nel tuo repository usa git logper scoprire l'ultima data / ora in cui hai effettuato l'ultimo commit

  3. Nella tua shell vai al tuo undodirusing cd ~/.vim_runtime/temp_dirs/undodir.

  4. In questa directory usa questo comando per trovare tutti i file che hai cambiato dall'ultimo commit

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

    Qui "2018-03-20 11:24:44" è la data e l'ora dell'ultimo commit. Se la data in cui è stato eseguito git reset --hardè "2018-03-22", utilizzare "2018-03-22", quindi utilizzare "2018-03-23". Ciò è dovuto a una stranezza di trova, in cui il limite inferiore è inclusivo e il limite superiore è esclusivo. https://unix.stackexchange.com/a/70404/242983

  5. Quindi vai a ciascuno dei file aprili in vim e fai un "20m precedente". È possibile trovare maggiori dettagli su "prima" utilizzando "h precedente". Qui earlier 20msignifica tornare allo stato del file 20 minuti indietro, supponendo che tu abbia fatto git hard --reset20 minuti indietro. Ripeti l'operazione su tutti i file che sono stati sputati dal findcomando. Sono sicuro che qualcuno possa scrivere una sceneggiatura che combini queste cose.


0

Sto usando IntelliJ e sono stato in grado di passare semplicemente attraverso ogni file e fare:

Edit -> reload from disk

Fortunatamente, avevo appena fatto un git statusdiritto prima di cancellare le mie modifiche di lavoro, quindi sapevo esattamente cosa dovevo ricaricare.


0

Ricordando la gerarchia dei file e utilizzando la tecnica di Mark Longair con la modifica di Phil Oakley si ottengono risultati straordinari.

In sostanza, se hai almeno aggiunto i file al repository ma non lo hai eseguito il commit, puoi ripristinarlo utilizzando interattivamente git show , ispezionare il registro e utilizzare il reindirizzamento della shell per creare ogni file (ricordando il percorso di interesse).

HTH!


0

Se hai recentemente rivisto un, git diffc'è un altro modo per recuperare da incidenti come questo, anche quando non hai ancora messo in scena le modifiche: se l'output di git diffè ancora nel buffer della tua console, puoi semplicemente scorrere verso l'alto, copiare e incollare il diff in un file e utilizzare il patchtool per applicare il diff per il vostro albero: patch -p0 < file. Questo approccio mi ha salvato alcune volte.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.