Liste et suppression des commits Git qui ne sont sous aucune branche (pendantes?)

146

J'ai un référentiel Git avec beaucoup de commits qui ne sont sous aucune branche particulière, je peux git showles faire, mais quand j'essaye de lister les branches qui les contiennent, il ne rapporte rien.

Je pensais que c'était le problème des commits / arborescence pendantes (à la suite de la branche -D), j'ai donc élagué le dépôt, mais je vois toujours le même comportement après cela:

$ git fetch origin

$ git fsck --unreachable
$ git fsck

Pas de sortie, rien de balançant (non?). Mais le commit existe

$ git show 793db7f272ba4bbdd1e32f14410a52a412667042
commit 793db7f272ba4bbdd1e32f14410a52a412667042
Author: ...

et il n'est accessible via aucune succursale car

$ git branch --contains 793db7f272ba4bbdd1e32f14410a52a412667042

ne donne aucune sortie.

Quel est exactement l'état de ce commit? Comment puis-je lister tous les commits dans un état similaire? Comment puis-je supprimer des commits comme ceux-ci?

Samer Buna
la source
duplication possible de Comment supprimer les commits de fusion erronés?
Josh Lee

Réponses:

75

Pas de sortie, rien de balançant (non?)

Notez que les commits référencés dans votre reflog sont considérés comme atteignables.

Quel est exactement l'état de ce commit? Comment puis-je lister tous les commits avec un état similaire

Passez --no-reflogsà convaincre git fsckde vous les montrer.

Comment puis-je supprimer des commits comme ceux-ci?

Une fois vos entrées reflog expirées, ces objets seront également nettoyés par git gc.

Expiration est régie par les gc.pruneexpire, gc.reflogexpireet les gc.reflogexpireunreachableparamètres. Cf. git help config.

Les valeurs par défaut sont toutes assez raisonnables.

Aristote Pagaltzis
la source
2
vous dites donc que les reflows pour les commits pendantes seront supprimés automatiquement après un certain temps?
MoralCode
2
En gros: oui - sauf que la question est un peu confuse. Je dis que toutes les entrées reflog sont supprimées automatiquement après un certain temps, mais vous pouvez changer cela via les paramètres de configuration. Et comme un commit n'est appelé « balançant» que lorsque rien ne le pointe - y compris les entrées de reflog -, les «reflogs pour les commits pendantes» ne sont pas une chose. Ce seraient des «reflogs pour les commits inaccessibles ».
Aristotle Pagaltzis
«Ce seraient des« reflogs pour des commits inaccessibles ».» Mais vous avez dit que "les commits mentionnés dans votre reflog sont considérés comme atteignables". Alors, comment "reflogs pour les commits inaccessibles" peut-il être une chose? Je suis tellement confus.
LarsH
1
Ouais, je n'étais pas cohérent. Normalement, les gens ne pensent pas au reflog, et quand ils disent «inaccessible», cela implique «d'une référence». Même le git help glossarydéfinit de cette façon… alors que sa définition de «accessible» n'est pas restreinte de cette façon, ils sont donc contradictoires. Drôle - donc ce que j'ai dit est en fait cohérent avec la confusion dans gitglossary… Ce ne sont pas les concepts qui prêtent à confusion, cependant, juste la terminologie. Le fait est que les commits «pendantes» sont ceux sur lesquels rien d' autre ne pointe. Est-ce que cela aiderait si je dis "reflogs pour des commits autrement inaccessibles"…?
Aristotle Pagaltzis
Tout cela est très déroutant. Faisons simple. Lorsque mastervous êtes sur une branche , vous le faites git commitet obtenez un commit 000001. Ensuite, vous faites git commit --amend, ce qui vous donne un engagement 000002. Il n'y a plus de balises ou de branches pointant vers 000001, et vous ne pouvez pas le voir dans votre journal sans l' --reflogoption, mais si vous le souhaitez, vous pouvez toujours y accéder avec git checkout 000001. Maintenant, la question est: est-ce 000001qu'un commit suspendu , ou un commit inaccessible , ou ni l'un ni l'autre, ou les deux?
chharvey
264

Pour supprimer tous les commits en suspens et ceux accessibles à partir des reflogs, procédez comme suit:

git reflog expire --expire-unreachable=now --all
git gc --prune=now

Mais assurez-vous que c'est ce que vous voulez. Je vous recommande de lire les pages de manuel mais voici l'essentiel:

git gcsupprime les objets inaccessibles (commits, arborescences, blobs (fichiers)). Un objet est inaccessible s'il ne fait pas partie de l'histoire d'une branche. En fait, c'est un peu plus compliqué:

git gc fait d'autres choses mais elles ne sont pas pertinentes ici et ne sont pas dangereuses.

Les objets inaccessibles de moins de deux semaines ne sont pas supprimés, nous utilisons donc --prune=nowce qui signifie «supprimer les objets inaccessibles qui ont été créés auparavant».

Les objets peuvent également être atteints via le reflog. Alors que les succursales enregistrent l'historique de certains projets, les reflogs enregistrent l'histoire de ces succursales. Si vous modifiez, réinitialisez, etc., les validations sont supprimées de l'historique de la branche mais git les garde au cas où vous vous rendriez compte que vous avez fait une erreur. Les Reflogs sont un moyen pratique de savoir quelles opérations destructives (et autres) ont été effectuées sur une branche (ou HEAD), ce qui facilite l'annulation d'une opération destructive.

Nous devons donc également supprimer les reflogs pour supprimer tout ce qui n'est pas accessible depuis une branche. Nous le faisons en expirant les --allreflogs. Encore une fois git garde un peu des reflogs pour protéger les utilisateurs que nous avons à nouveau pour lui dire de ne pas le faire: --expire-unreachable=now.

Comme j'utilise principalement le reflog pour récupérer des opérations destructives, j'utilise généralement à la --expire=nowplace, ce qui zappe complètement les reflogs.

Tarse
la source
1
Je vous dis quelles commandes utiliser qui ne sont pas évidentes - gc ne devrait-il pas suffire? Si vous n'avez jamais utilisé git-reflog auparavant, vous ne le saurez pas. Alors maintenant que vous savez quelles commandes vous devez utiliser, vous devriez rechercher les options mentionnées dans leurs pages de manuel. Bien sûr, je pourrais plutôt copier ces informations à partir de là ...
Tarsius
1
Eh bien en fait, je dis exactement ce qu'il fait: "supprimer tous les commits pendants et ceux accessibles des reflogs". Si vous ne savez pas ce que sont les reflogs: lisez à nouveau le manuel.
tarsius
7
Bien que la réponse donnée puisse être correcte, @ erikb85 a raison de souligner qu'il n'y avait aucune information sur ce qu'on vous disait de faire. Le suivi avec RTFM est encore moins utile. Oui, nous devrions tous lire toute la documentation. Dans certains cas, peut-être que la personne qui effectue la recherche n'a pas assez de documentation pour savoir ce qui se passe. Donc, un peu d'éducation sur ce que font les commandes serait utile pour tous ceux qui trouveront cette réponse plus tard.
Lee Saferite
@LeeSaferite j'espère que vous êtes tous heureux maintenant :-)
tarsius
12
git reflog expire --expire-unreachable=now --alllaisse tomber toutes vos cachettes!
Vsevolod Golovanov
22

J'ai eu le même problème, toujours après avoir suivi tous les conseils de ce fil:

git reflog expire --expire-unreachable=now --all
git gc --prune=now
git fsck --unreachable --no-reflogs   # no output
git branch -a --contains <commit>     # no output
git show <commit>                     # still shows up

Si ce n'est pas un reflog et pas une branche, ... ça doit être un tag !

git tag                             # showed several old tags created before the cleanup

J'ai supprimé les balises avec git tag -d <tagname>et refait le nettoyage, et les anciens commits avaient disparu.

jakub.g
la source
Il y a déjà une réponse sur les balises ( stackoverflow.com/a/37335660/450127 ), et il ne semble pas que cela ajoute quelque chose de nouveau. Cela ne devrait-il pas être supprimé au profit de la réponse précédente?
Ian Dunn
En fait, j'ai en quelque sorte oublié cette réponse. Bien que 4 personnes aient trouvé ma réponse utile, alors peut-être que ce n'est pas si inutile? J'ai également regroupé toutes les possibilités en une seule réponse concise.
jakub.g
1
Même si elle est dupliquée, cette page peut apparaître dans Google Result, et elle aide immédiatement les personnes confrontées au même problème, mieux que de simplement rediriger les gens encore et encore vers des liens qui peuvent avoir la bonne réponse.
Alexandre T.
14
git branch --contains 793db7f272ba4bbdd1e32f14410a52a412667042

a probablement juste besoin d'être

git branch -a --contains 793db7f272ba4bbdd1e32f14410a52a412667042

pour signaler également les succursales à partir de télécommandes

sehe
la source
merci, maintenant j'ai trouvé mes télécommandes / origine / prochaine qui détient toujours ce commit. comment l'enlever? git push -d origin nextn'aide pas.
iRaS
1
@iRaS de
sehe
merci - le a git fetch --prunefait l'affaire. mais dans toutes les réponses, il me manque une vérification des balises qui font référence à ce commit. Je ne sais toujours pas comment vérifier les balises avec un commit (j'ai tout supprimé).
iRaS
Mais ... cela signifie-t-il que les commits qui ne sont accessibles qu'à partir de branches distantes (et pas de succursales locales) sont considérés comme joignables, et git fsck --unreachablecommuniquent donc réellement sur le réseau avec la télécommande afin de savoir quels commits sont accessibles?
LarsH
1
Réponse à ma propre question ... oui, les commits qui ne sont accessibles qu'à partir de succursales distantes (et pas de succursales locales) sont considérés comme joignables; mais git fsck --unreachablen'a pas besoin de communiquer sur le réseau avec la télécommande pour savoir quelles branches distantes contiennent quels commits. Les informations de la branche distante sont stockées localement, sous par exemple .git/refs/remotes/origin(ou dans packed-refs).
LarsH
8

J'ai eu un problème similaire. J'ai couru git branch --contains <commit>, et il n'a renvoyé aucune sortie comme dans la question.

Mais même après avoir couru

git reflog expire --expire-unreachable=now --all
git gc --prune=now

mon commit était toujours accessible en utilisant git show <commit>. Cela était dû au fait que l'un des commits de sa "branche" détachée / suspendue était balisé. J'ai supprimé la balise, exécuté à nouveau les commandes ci-dessus et j'étais en or. git show <commit>retourné fatal: bad object <commit>- exactement ce dont j'avais besoin. Espérons que cela aide quelqu'un d'autre qui était aussi coincé que moi.

Andrew Larsson
la source
comment avez-vous supprimé le tag?
bapors du
@bapors Répertoriez toutes les balises, recherchez celle qui fait référence au commit en question, puis supprimez-la. stackoverflow.com/questions/5480258/…
Andrew Larsson
4

J'ai accidentellement rencontré la même situation et j'ai trouvé que mes cachettes contenaient une référence au commit inaccessible, et donc le commit présumé inaccessible était accessible à partir des cachettes.

C'est ce que j'ai fait pour le rendre vraiment inaccessible.

git stash clear
git reflog expire --expire-unreachable=now --all
git fsck --unreachable
git gc --prune=now
Lei Zhao
la source
2

git gc --prune=<date>par défaut, élaguer les objets datant de plus de deux semaines. Vous pouvez définir une date plus récente. Mais, les commandes git qui créent des objets libres exécutent généralement git gc --auto (qui élague les objets libres si leur nombre dépasse la valeur de la variable de configuration gc.auto).

Voulez-vous vraiment supprimer ces validations? Le paramètre par défaut de gc.auto garantira que les objets en vrac ne prennent pas une quantité déraisonnable de mémoire, et stocker les objets en vrac pendant un certain temps est généralement une bonne idée. De cette façon, si vous réalisez demain que votre branche supprimée contenait un commit dont vous aviez besoin, vous pouvez le récupérer.

dublev
la source