Listado y eliminación de confirmaciones de Git que no están bajo ninguna rama (¿colgando?)

146

Tengo un repositorio de Git con muchas confirmaciones que no están bajo ninguna rama en particular, puedo git showhacerlo, pero cuando trato de enumerar las ramas que las contienen, no informa nada.

Pensé que este es el problema del árbol / commits colgantes (como resultado de la rama -D), así que eliminé el repositorio, pero aún veo el mismo comportamiento después de eso:

$ git fetch origin

$ git fsck --unreachable
$ git fsck

Sin salida, nada colgando (¿verdad?). Pero el compromiso existe

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

y no es accesible a través de ninguna rama como

$ git branch --contains 793db7f272ba4bbdd1e32f14410a52a412667042

no da salida

¿Cuál es exactamente el estado de ese compromiso? ¿Cómo puedo enumerar todas las confirmaciones en un estado similar? ¿Cómo puedo eliminar confirmaciones como esas?

Samer Buna
fuente

Respuestas:

75

Sin salida, nada colgando (¿verdad?)

Tenga en cuenta que las confirmaciones a las que se hace referencia desde su reflog se consideran accesibles.

¿Cuál es exactamente el estado de ese compromiso? ¿Cómo puedo enumerar todas las confirmaciones con un estado similar

Pase --no-reflogspara convencer git fsckde mostrárselos.

¿Cómo puedo eliminar confirmaciones como esas?

Una vez que sus entradas de reflog hayan caducado, esos objetos también serán limpiados por git gc.

Caducidad está regulada por las gc.pruneexpire, gc.reflogexpirey gc.reflogexpireunreachablelos ajustes. Cf. git help config.

Los valores predeterminados son todos bastante razonables.

Aristóteles Pagaltzis
fuente
2
Entonces, ¿básicamente estás diciendo que los reflujos para las confirmaciones pendientes se eliminarán después de un tiempo automáticamente?
MoralCode
2
Básicamente: sí, excepto que la pregunta es un poco confusa. Estoy diciendo que todas las entradas de reflog se eliminan automáticamente después de un tiempo, pero puede cambiar eso a través de la configuración. Y debido a que un commit solo se llama colgar cuando no tiene nada que lo señale, incluidas las entradas de reflog, "reflogs para colgar commits" no son una cosa. Serían "reflogs para commits inalcanzables ".
Aristóteles Pagaltzis
Serían "reflogs para commits inalcanzables". Pero usted dijo que "los compromisos a los que se hace referencia desde su reflog se consideran accesibles". Entonces, ¿cómo pueden ser "reflogs para confirmaciones inalcanzables" algo? Estoy tan confundida.
LarsH
1
Sí, no fui consistente. Normalmente, las personas no piensan en el registro, y cuando dicen "inalcanzable" implica "de un árbitro". Incluso lo git help glossarydefine de esa manera ... mientras que su definición de "accesible" no se reduce de esa manera, por lo que son contradictorias. Es curioso, así que lo que dije es en realidad consistente con la confusión en gitglossary... No son los conceptos lo que confunden, sin embargo, solo la terminología. El punto es que los commits "colgantes" son aquellos a los que nada más apunta. ¿Ayudaría si digo "reflogs para commits que de otro modo serían inalcanzables" ...?
Aristóteles Pagaltzis
Todo esto es muy confuso. Hagámoslo simple. Cuando masterestás en la sucursal , lo haces git commity obtienes un compromiso 000001. Entonces lo haces git commit --amend, lo que te da compromiso 000002. Ya no hay etiquetas o ramas que apuntan 000001, y no puede verlo en su registro sin la --reflogopción, pero si lo desea, aún puede hacerlo git checkout 000001. Ahora la pregunta es, ¿es 000001un commit colgante , o un commit inalcanzable , o ninguno, o ambos?
chharvey
264

Para eliminar todos los commits colgantes y aquellos accesibles desde los reflogs, haga esto:

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

Pero asegúrese de que esto es lo que quiere. Le recomiendo que lea las páginas del manual, pero aquí está la esencia:

git gcelimina objetos inalcanzables (commits, árboles, blobs (archivos)). Un objeto es inalcanzable si no es parte del historial de alguna rama. En realidad es un poco más complicado:

git gc hace algunas otras cosas pero no son relevantes aquí y no son peligrosas.

Los objetos inalcanzables que tienen menos de dos semanas no se eliminan, por lo que utilizamos lo --prune=nowque significa "eliminar objetos inalcanzables que se crearon antes".

Los objetos también se pueden alcanzar a través del registro. Mientras las sucursales registran el historial de algún proyecto, los registros registran el historial de estas sucursales. Si corrige, restablece, etc., las confirmaciones se eliminan del historial de la rama, pero git las mantiene en caso de que se dé cuenta de que cometió un error. Los registros son una forma conveniente de averiguar qué operaciones destructivas (y otras) se realizaron en una rama (o CABEZA), lo que facilita deshacer una operación destructiva.

Por lo tanto, también tenemos que eliminar los reflogs para eliminar todo lo que no sea accesible desde una rama. Lo hacemos expirando los --allregistros. Una vez más Git guarda un poco de las reflogs a proteger a los usuarios por lo que de nuevo tenemos que decir que no hacerlo: --expire-unreachable=now.

Como utilizo principalmente el reflog para recuperarme de las operaciones destructivas, generalmente lo uso --expire=now, lo que elimina los reflogs por completo.

tarsius
fuente
1
Te digo qué comandos usar que no son obvios: ¿no debería ser suficiente gc? Si nunca usaste git-reflog antes, no lo sabrás. Entonces, ahora que sabe qué comandos tiene que usar, debe buscar las opciones mencionadas en sus páginas de manual. Por supuesto, podría copiar esa información desde allí ...
tarsius
1
Bueno, en realidad digo exactamente lo que hace: "eliminar todos los commits colgantes y aquellos accesibles desde los reflogs". Si no sabe qué son los registros de registros: vuelva a leer el manual.
tarsius
77
Si bien la respuesta dada puede ser correcta, @ erikb85 es correcta al señalar que no hubo educación sobre lo que le dijeron que hiciera. Seguir con RTFM es aún menos útil. Sí, todos deberíamos leer toda la documentación. En algunos casos, tal vez la persona que realiza la búsqueda no asimila la documentación lo suficiente como para saber lo que está sucediendo. Por lo tanto, un poco de educación sobre lo que están haciendo los comandos sería útil para todos los que encuentren esta respuesta más adelante.
Lee Saferite
@LeeSaferite espero que estén todos felices ahora :-)
tarsius
12
git reflog expire --expire-unreachable=now --alldeja caer todos tus escondites!
Vsevolod Golovanov
22

Tuve el mismo problema, aún después de seguir todos los consejos de este hilo:

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 no es un reflog y no una rama, ¡debe ser una etiqueta !

git tag                             # showed several old tags created before the cleanup

Eliminé las etiquetas git tag -d <tagname>y rehice la limpieza, y las viejas confirmaciones desaparecieron.

jakub.g
fuente
Ya hay una respuesta sobre las etiquetas ( stackoverflow.com/a/37335660/450127 ), y no parece que esto agregue nada nuevo. ¿No debería eliminarse esto a favor de la respuesta anterior?
Ian Dunn
De hecho, de alguna manera pasé por alto esa respuesta. Aunque 4 personas encontraron útil mi respuesta, ¿quizás no sea tan inútil? También agrupé todas las posibilidades en una respuesta concisa.
jakub.g
1
Incluso si está duplicada, esta página puede aparecer en Google Result e inmediatamente ayuda a las personas con el mismo problema, mejor que simplemente redireccionar a las personas una y otra vez a enlaces que pueden tener la respuesta correcta.
Alexandre
14
git branch --contains 793db7f272ba4bbdd1e32f14410a52a412667042

probablemente solo necesita ser

git branch -a --contains 793db7f272ba4bbdd1e32f14410a52a412667042

para informar también sobre sucursales desde controles remotos

sehe
fuente
gracias, ahora encontré mis controles remotos / origen / siguiente que aún contiene este compromiso. como quitarlo git push -d origin nextno ayuda
iRaS
gracias, git fetch --prunehicieron el truco. pero en todas las respuestas me falta una comprobación de etiquetas que hacen referencia a esta confirmación. Todavía no sé cómo verificar las etiquetas con una confirmación (eliminé todas).
iRaS
Pero ... ¿esto significa que los compromisos a los que solo se puede acceder desde sucursales remotas (y no a las sucursales locales) se consideran accesibles y, por git fsck --unreachablelo tanto, en realidad se está comunicando a través de la red con el control remoto para descubrir qué compromisos son accesibles?
LarsH
1
Respondí mi propia pregunta ... sí, los commits a los que solo se puede acceder desde sucursales remotas (y no a sucursales locales) se consideran accesibles; pero git fsck --unreachableno necesita comunicarse a través de la red con el control remoto para averiguar qué ramas remotas contienen qué confirmaciones. La información de la sucursal remota se almacena localmente, por ejemplo, en .git/refs/remotes/origin(o en packed-refs).
LarsH
8

Tuve un problema similar Corrí git branch --contains <commit>, y no devolvió ningún resultado al igual que en la pregunta.

Pero incluso después de correr

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

mi commit todavía estaba accesible usando git show <commit>. Esto se debió a que uno de los commits en su "rama" separada / colgada fue etiquetado. Quité la etiqueta, ejecuté los comandos anteriores nuevamente, y estaba dorado. git show <commit>regresé fatal: bad object <commit>, exactamente lo que necesitaba. Espero que esto ayude a alguien más que estaba tan atrapado como yo.

Andrew Larsson
fuente
¿Cómo eliminaste la etiqueta?
bapors
@bapors Enumere todas las etiquetas, encuentre la que haga referencia a la confirmación en cuestión y luego elimínela. stackoverflow.com/questions/5480258/…
Andrew Larsson
4

Accidentalmente llegué a la misma situación y descubrí que mis escondites contienen referencias al compromiso inalcanzable, y por lo tanto, el supuesto compromiso inalcanzable era accesible desde los escondites.

Esto fue lo que hice para hacerlo verdaderamente inalcanzable.

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

git gc --prune=<date>El valor predeterminado es podar objetos anteriores a hace dos semanas. Podrías establecer una fecha más reciente. Pero, los comandos git que crean objetos sueltos generalmente ejecutarán git gc --auto (que elimina objetos sueltos si su número excede el valor de la variable de configuración gc.auto).

¿Estás seguro de que deseas eliminar estas confirmaciones? La configuración predeterminada de gc.auto asegurará que los objetos sueltos no ocupen una cantidad irrazonable de memoria, y generalmente es una buena idea almacenar objetos sueltos durante cierto tiempo. De esa manera, si se da cuenta mañana de que su rama eliminada contenía una confirmación que necesitaba, puede recuperarla.

dublev
fuente