Annuler git reset --hard avec des fichiers non validés dans la zone de préparation

110

J'essaye de récupérer mon travail. Je l'ai fait bêtement git reset --hard, mais avant cela, je ne faisais que et je ne get add .faisais pas git commit. Veuillez aider! Voici mon journal:

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

Est-il possible d'annuler git reset --harddans cette situation?

eistrati
la source
@MarkLongair homme génial! Vous venez de récupérer mon travail! J'ai écrit un script Python pour créer des fichiers de toutes les sorties! J'ajouterai le script comme réponse
Garçon
4
Pas «bêtement»… mais «naïvement»… parce que je viens de faire LA MÊME!
Rosdi Kasim
Peut-être encore bêtement ;-)
Duncan McGregor
Voici un excellent article sur la façon d'inverser certains de ces problèmes. Cela va demander du travail manuel.
Jan Swart
@MarkLongair `` `` trouver .git / objets / -type f -printf '% TY-% Tm-% Td% TT% p \ n' | trier `` a fonctionné pour moi. les dates apparaissent également, commencez à vérifier les blobs à partir de la fin.
ZAky

Réponses:

175

Vous devriez pouvoir récupérer tous les fichiers que vous avez ajoutés à l'index (par exemple, comme dans votre situation, avec git add .) bien que cela puisse être un peu de travail. Afin d'ajouter un fichier à l'index, git l'ajoute à la base de données d'objets, ce qui signifie qu'il peut être récupéré tant que le garbage collection n'a pas encore eu lieu. Il y a un exemple de la façon de faire cela donné dans la réponse de Jakub Narębski ici:

Cependant, j'ai essayé cela sur un référentiel de test, et il y a eu quelques problèmes - --cacheddevrait être --cache, et j'ai trouvé que cela ne créait pas réellement le .git/lost-foundrépertoire. Cependant, les étapes suivantes ont fonctionné pour moi:

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

Cela devrait afficher tous les objets de la base de données d'objets qui ne sont accessibles par aucune référence, dans l'index ou via le reflog. La sortie ressemblera à ceci:

unreachable blob 907b308167f0880fb2a5c0e1614bb0c7620f9dc3
unreachable blob 72663d3adcf67548b9e0f0b2eeef62bce3d53e03

... et pour chacun de ces blobs, vous pouvez faire:

git show 907b308

Pour sortir le contenu du fichier.


Trop de sortie?

Mise à jour en réponse au commentaire de sehe ci-dessous:

Si vous constatez que de nombreuses validations et arborescences sont répertoriées dans la sortie de cette commande, vous souhaiterez peut-être supprimer de la sortie tous les objets référencés à partir de validations non référencées. (En règle générale, vous pouvez revenir à ces commits via le reflog de toute façon - nous sommes simplement intéressés par les objets qui ont été ajoutés à l'index mais qui ne peuvent jamais être trouvés via un commit.)

Tout d'abord, enregistrez la sortie de la commande, avec:

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

Maintenant, les noms d'objet de ces commits inaccessibles peuvent être trouvés avec:

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

Ainsi, vous pouvez trouver uniquement les arbres et objets qui ont été ajoutés à l'index, mais non validés à aucun moment, avec:

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

Cela réduit énormément le nombre d'objets que vous devrez considérer.


Mise à jour: Philip Oakley ci-dessous suggère une autre façon de réduire le nombre d'objets à prendre en compte, qui consiste à ne considérer que les fichiers les plus récemment modifiés sous .git/objects. Vous pouvez les trouver avec:

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

(J'ai trouvé cette findinvocation ici .) La fin de cette liste pourrait ressembler à:

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

Dans ce cas, vous pouvez voir ces objets avec:

git show b21700b09c0bc0fc848f67dd751a9e4ea5b4133b
git show de629830603289ef159268f443da79968360913a

(Notez que vous devez supprimer le /à la fin du chemin pour obtenir le nom de l'objet.)

Mark Longair
la source
avant de publier ma réponse, j'ai envisagé d'inclure exactement ces étapes. Cependant, en essayant cela avec l'un de mes dépôts locaux, j'ai obtenu des centaines de produits inaccessibles et je n'ai pas pu trouver une approche adéquate pour, par exemple, les trier par date de soumission. Maintenant , ce serait génial
sehe
3
N'est-il pas possible de consulter les horodatages des objets pour les objets les plus récents qui sont postérieurs à votre dernier commit? ou est-ce que je manque quelque chose.
Philip Oakley
1
@Philip Oakley: merci pour cette suggestion, j'ai ajouté la suggestion de trouver les objets les plus récemment modifiés dans la base de données d'objets.
Mark Longair
2
Mec, c'est génial! Cela a sauvé mon @ $$ tout à l'heure. Et j'ai appris un peu de science git. Mieux vaut regarder ce que vous ajoutez à l'index ...
bowsersenior
2
ne pas voler la lumière de chaux de personne ici. Mais juste pour une note puisque je viens de rencontrer le même problème et que je pensais avoir perdu tout mon travail. Pour ceux qui utilisent un IDE , les IDE ont généralement une solution de récupération en un clic. En cas de PHPstorm, je devais juste cliquer avec le bouton droit sur le répertoire et sélectionner Afficher l'historique local, puis revenir au dernier état valide.
Shalom Sam
58

J'ai juste fait un git reset --hardet perdu un commit. Mais je connaissais le hachage de commit, donc j'ai pu le faire git cherry-pick COMMIT_HASHpour le restaurer.

Je l'ai fait quelques minutes après avoir perdu le commit, cela peut donc fonctionner pour certains d'entre vous.

Richard Saunders
la source
5
Merci merci merci Géré pour récupérer une journée de travail grâce à cette réponse
Dace
21
Cela vaut probablement la peine de mentionner que vous pouvez voir ces hachages en utilisant git reflog, par exemple git reset --hard-> git reflog(en regardant le hachage HEAD @ {1}) et enfingit cherry-pick COMMIT_HASH
Javier López
2
Les gars qui lisent ceci, sachez que cela ne fonctionne que pour un seul hachage de commit, s'il y avait plus d'un seul commit, cela ne résoudra pas le problème à la fois!
Ain Tohvri
4
Vous pouvez réellement réinitialiser "en avant". Donc, si vous avez réinitialisé plusieurs validations, faire un autre 'git reset --hard <commit>' devrait restaurer toute la chaîne de validation jusqu'à ce point. (étant donné qu'ils ne sont pas encore GC)
akimsko
6
Le one-liner pour récupérer les commits perdus avec git reset --hard(en supposant que vous n'ayez rien fait d'autre après cette commande) estgit reset --hard @{1}
Ajedi32
13

Grâce à Mark Longair, j'ai récupéré mes affaires!

J'ai d'abord enregistré tous les hachages dans un fichier:

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

Ensuite, je les mets tous (en supprimant le `` blob inaccessible '') dans une liste et je mets les données dans de nouveaux fichiers ... vous devez choisir vos fichiers et les renommer à nouveau dont vous avez besoin ... mais je n'en avais besoin que de quelques-uns fichiers..espérons que cela aide quelqu'un ...

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
Garçon
la source
1
Oui! C'est exactement ce dont j'avais besoin. J'aime aussi le script Python pour recréer tous les fichiers. Les autres réponses m'ont rendu nerveux à l'idée de perdre mes données avec le ramasse-miettes, donc le vidage des fichiers est une victoire pour moi :)
Eric Olson
1
J'ai écrit ce script basé sur cette réponse. Fonctionne hors de la boîte: github.com/pendashteh/git-recover-index
Alexar
1
Je trouve plus facile d'automatiser comme ceci: 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. Les fichiers finiront danslost/*.blob
Matija Nalis
6

La solution de @ Ajedi32 dans les commentaires a fonctionné pour moi exactement dans cette situation.

git reset --hard @{1}

Notez que toutes ces solutions reposent sur le fait qu'il n'y a pas de git gc, et certaines d'entre elles peuvent en causer un, donc je compresserais le contenu de votre répertoire .git avant d'essayer quoi que ce soit afin que vous ayez un instantané auquel revenir si vous ne le faites pas ne travaille pas pour toi.

Duncan McGregor
la source
J'avais un certain nombre de fichiers non validés. Ensuite, j'ai validé 1 fichier, et j'ai fait un git reset --hard, qui a gâché mon dépôt d'une manière inconnue. @Duncan, que fait le @ {1}? et de quel commentaire parlez-vous? Est-ce la réinitialisation d'un git reset?
alpha_989
Je pense que @ {1} est une référence relative au dernier mais un commit, mais je ne suis pas un expert, je ne fais que rapporter ce qui a fonctionné pour moi
Duncan McGregor
3

Ran dans le même problème, mais n'avait pas ajouté les modifications à l'index. Donc, toutes les commandes ci-dessus ne m'ont pas apporté les modifications souhaitées.

Après toutes les réponses élaborées ci-dessus, c'est un indice naïf, mais cela sauvera peut-être quelqu'un qui n'y a pas pensé en premier, comme je l'ai fait.

En désespoir de cause, j'ai essayé d'appuyer sur CTRL-Z dans mon éditeur (LightTable), une fois dans chaque onglet ouvert - cela a heureusement récupéré le fichier dans cet onglet, à son dernier état avant le git reset --hard. HTH.

Olange
la source
1
Exactement la même situation, a manqué d'ajouter l'index et fait une réinitialisation matérielle et aucune des solutions n'a fonctionné tout au-dessus. Celui-ci était un geste SMART, merci d'avoir fait Ctrl + Z et sauvé ma journée. Mon éditeur: SublimeText. Un de mon côté!
Vivek
1

C'est probablement évident pour les professionnels git, mais je voulais le mettre en place car dans ma recherche effrénée, je n'ai pas vu cela évoqué.

J'ai mis en scène certains fichiers et fait un git reset --hard , un peu paniqué, puis j'ai remarqué que mon statut montrait tous mes fichiers toujours en cours ainsi que toutes leurs suppressions non mises en scène.

À ce stade, vous pouvez valider ces modifications par étapes, tant que vous ne procédez pas à leurs suppressions. Après cela, il vous suffit de trouver le courage de fairegit reset --hard une fois de plus, ce qui vous ramènera aux changements que vous aviez mis en scène et que vous venez de commettre.

Encore une fois, ce n'est probablement rien de nouveau pour la plupart, mais j'espère que puisque cela m'a aidé et que je n'ai rien trouvé suggérant cela, cela pourrait aider quelqu'un d'autre.

ddoty
la source
1

Dieu, je me suis tiré les cheveux jusqu'à ce que je tombe sur cette question et ses réponses. Je pense que la réponse correcte et succincte à la question posée n'est disponible que si vous rassemblez deux des commentaires ci-dessus, donc ici, tout est au même endroit:

  1. Comme mentionné par chilicuil, exécutez git reflogpour identifier là-dedans le hachage de validation auquel vous souhaitez revenir

  2. Comme mentionné par akimsko, vous ne voudrez probablement PAS faire un choix à moins d'avoir perdu un seul commit, vous devriez donc exécuter git reset --hard <hash-commit-you-want>

Remarque pour les utilisateurs d'egit Eclipse: je n'ai pas trouvé de moyen de faire ces étapes dans Eclipse avec egit. Fermer Eclipse, exécuter les commandes ci-dessus à partir d'une fenêtre de terminal, puis rouvrir Eclipse a très bien fonctionné pour moi.

Lolo
la source
0

Les solutions ci-dessus peuvent fonctionner, cependant, il existe des moyens plus simples de récupérer à partir de cela au lieu de passer par gitune annulation complexe. Je suppose que la plupart des réinitialisations git se produisent sur un petit nombre de fichiers, et si vous utilisez déjà VIM, c'est peut-être la solution la plus rapide. La mise en garde est que vous devez déjà utiliser ViM'sl'annulation persistante, que vous devriez utiliser de toute façon, car elle vous offre la possibilité d'annuler un nombre illimité de modifications.

Voici les étapes:

  1. Dans vim appuyez sur :et tapez la commandeset undodir . Si vous avez persistent undoactivé votre .vimrc, il affichera un résultat similaire à undodir=~/.vim_runtime/temp_dirs/undodir.

  2. Dans votre repo, utilisez git logpour connaître la dernière date / heure à laquelle vous avez effectué le dernier commit

  3. Dans votre shell, accédez à votre undodirutilisation cd ~/.vim_runtime/temp_dirs/undodir.

  4. Dans ce répertoire, utilisez cette commande pour trouver tous les fichiers que vous avez modifiés depuis le dernier 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/*"

    Ici "2018-03-20 11:24:44" est la date et l'heure du dernier commit. Si la date à laquelle vous avez effectué le git reset --hardest "2018-03-22", utilisez "2018-03-22", puis "2018-03-23". Ceci est dû à une bizarrerie de find, où la limite inférieure est inclusive et la limite supérieure est exclusive. https://unix.stackexchange.com/a/70404/242983

  5. Ensuite, allez dans chacun des fichiers, ouvrez-les dans vim et faites un "20m plus tôt". Vous pouvez trouver plus de détails sur «plus tôt» en utilisant «h plus tôt». Ici earlier 20msignifie revenir à l'état du fichier 20 minutes en arrière, en supposant que vous avez fait le git hard --reset, 20 minutes en arrière. Répétez cette opération sur tous les fichiers crachés par la findcommande. Je suis sûr que quelqu'un peut écrire un script qui combinera ces choses.

alpha_989
la source
0

J'utilise IntelliJ et j'ai simplement pu parcourir chaque fichier et faire:

Edit -> reload from disk

Heureusement, je venais juste de faire un git statusjuste avant d'effacer mes modifications de travail, donc je savais exactement ce que je devais recharger.

Forrest
la source
0

Se souvenir de votre hiérarchie de fichiers et utiliser la technique de Mark Longair avec la modification de Phil Oakley donne des résultats spectaculaires.

Essentiellement, si vous avez au moins ajouté les fichiers au référentiel mais ne l'avez pas validé, vous pouvez restaurer en utilisant de manière interactive git show, inspecter le journal et utiliser la redirection shell pour créer chaque fichier (en vous souvenant de votre chemin d'intérêt).

HTH!

ainthu
la source
0

Si vous avez récemment examiné un, git diffil existe un autre moyen de récupérer des incidents comme celui-ci, même si vous n'avez pas encore mis en scène les modifications: si la sortie de git diffest toujours dans la mémoire tampon de votre console, vous pouvez simplement faire défiler vers le haut, copier-coller le diff dans un fichier et utiliser l' patchoutil pour appliquer la diff à votre arbre: patch -p0 < file. Cette approche m'a sauvé plusieurs fois.

Confusion
la source