Cómo eliminar blobs sin referencia de mi repositorio de git

124

Tengo un repositorio de GitHub que tenía dos ramas: master y release.

La rama de lanzamiento contenía archivos de distribución binarios que contribuían a un tamaño de repositorio muy grande (> 250 MB), así que decidí limpiar las cosas.

Primero eliminé la rama de lanzamiento remoto, a través de git push origin :release

Luego eliminé la rama de lanzamiento local. Primero lo intenté git branch -d release, pero git dijo "error: la rama 'release' no es un antepasado de tu HEAD actual". lo cual es cierto, entonces lo hice git branch -D releasepara forzar su eliminación.

Pero el tamaño de mi repositorio, tanto localmente como en GitHub, seguía siendo enorme. Entonces revisé la lista habitual de comandos de git, como git gc --prune=today --aggressive, sin suerte.

Siguiendo las instrucciones de Charles Bailey en SO 1029969 pude obtener una lista de SHA1 para las manchas más grandes. Luego utilicé el script de SO 460331 para encontrar los blobs ... y los cinco más grandes no existen, aunque se encuentran blobs más pequeños, así que sé que el script está funcionando.

Creo que estos blogs son los binarios de la rama de lanzamiento, y de alguna manera se quedaron después de eliminar esa rama. ¿Cuál es la forma correcta de deshacerse de ellos?

kkrugler
fuente
¿Qué versión de Git estás usando? ¿Y probaste stackoverflow.com/questions/1106529/… ?
VonC
git versión 1.6.2.3 Probé gc y prune con varios argumentos. No había intentado repack -a -d -l, solo lo ejecuté, sin cambios.
kkrugler
2
Nueva información: un clon nuevo de GitHub ya no tiene los blobs sin referencia y se redujo a "solo" 84 MB de 250 MB.
kkrugler

Respuestas:

219

... y sin más preámbulos, puedo presentarles este útil comando, "git-gc-all", garantizado para eliminar toda su basura git hasta que puedan aparecer variables de configuración adicionales:

git -c gc.reflogExpire=0 -c gc.reflogExpireUnreachable=0 -c gc.rerereresolved=0 -c gc.rerereunresolved=0 -c gc.pruneExpire=now gc

Es posible que también necesites ejecutar algo como esto primero, ¡oh, cielos, git es complicado!

git remote rm origin
rm -rf .git/refs/original/ .git/refs/remotes/ .git/*_HEAD .git/logs/
git for-each-ref --format="%(refname)" refs/original/ | xargs -n1 --no-run-if-empty git update-ref -d

Es posible que también deba eliminar algunas etiquetas, gracias Zitrax:

git tag | xargs git tag -d

Puse todo esto en un script: git-gc-all-ferocious .

Sam Watkins
fuente
1
Interesante. Una buena alternativa a mi respuesta más general. +1
VonC
10
Esto merece más votos a favor. Finalmente se deshizo de muchos objetos git que otros métodos conservarían. ¡Gracias!
Jean-Philippe Pellet
1
Voto a favor. Vaya, no sé lo que acabo de hacer, pero parece limpiar mucho. ¿Puede explicarnos qué hace? Tengo la sensación de que me ha aclarado todo objects. ¿Cuáles son esos y por qué son (aparentemente) irrelevantes?
Redsandro
2
@Redsandro, según tengo entendido, esos comandos "git rm origin", "rm" y "git update-ref -d" eliminan las referencias a las confirmaciones antiguas para los controles remotos y demás, lo que podría estar impidiendo la recolección de basura. Las opciones para "git gc" le dicen que no se aferre a varias confirmaciones antiguas, de lo contrario, las mantendrá por un tiempo. Por ejemplo, gc.rerereresolved es para "registros de fusión conflictiva que resolvió antes", que se mantienen de forma predeterminada durante 60 días. Esas opciones están en la página de manual de git-gc. No soy un experto en git y no sé exactamente qué hacen todas estas cosas. Los encontré en las páginas de manual y en grepping .git para las referencias de confirmación.
Sam Watkins
1
Un objeto git es un archivo comprimido o un árbol o una confirmación en su repositorio git, incluidas las cosas antiguas del historial. git gc borra los objetos innecesarios. Mantiene los objetos que aún son necesarios para su repositorio actual y su historial.
Sam Watkins
81

Como se describe aquí , si desea eliminar permanentemente todo lo que se hace referencia solo a través de reflog , simplemente use

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

git reflog expire --expire-unreachable=now --allelimina todas las referencias de confirmaciones inalcanzables en reflog.

git gc --prune=now elimina las propias confirmaciones.

Atención : Solo el uso git gc --prune=nowno funcionará ya que esas confirmaciones todavía se hacen referencia en el reflog. Por lo tanto, borrar el reflog es obligatorio. También tenga en cuenta que si lo usa rereretiene referencias adicionales no borradas por estos comandos. Consulte git help rererepara obtener más detalles. Además, cualquier confirmación a la que hagan referencia las ramas o etiquetas locales o remotas no se eliminará porque git las considera datos valiosos.

jiasli
fuente
14
Funcionó, pero de alguna manera perdí mis escondites guardados en el proceso (nada importante en mi caso, solo una advertencia para los demás)
Amro
1
¿por qué no agresivo?
JoelFan
3
Creo que esta respuesta necesita una advertencia clara, preferiblemente en la parte superior. Mi sugerencia de edición fue rechazada, porque supongo que debería sugerirla al autor en un comentario. Acepte esta edición stackoverflow.com/review/suggested-edits/26023988 o agregue una advertencia a su manera. Además, esto deja caer todos tus alijos . ¡Eso también debería estar incluido en la advertencia!
Iñigo
Probé con la versión 2.17 de git y los comandos anteriores no eliminarán las confirmaciones almacenadas. ¿Está seguro de que no ejecutó ningún comando adicional?
Mikko Rantalainen
1
git fetch --prunereducir aún más el tamaño debido a la eliminación de blobs locales.
Héctorpal
33

Como se menciona en esta respuesta SO , ¡en git gcrealidad puede aumentar el tamaño del repositorio!

Ver también este hilo

Ahora git tiene un mecanismo de seguridad para no eliminar objetos sin referencia de inmediato cuando se ejecuta ' git gc'.
De forma predeterminada, los objetos sin referencia se guardan durante un período de 2 semanas. Esto es para facilitarle la recuperación de ramas o confirmaciones borradas accidentalmente, o para evitar una carrera en la que un objeto recién creado en el proceso de ser pero aún no referenciado podría ser eliminado por un git gcproceso que se ejecuta en paralelo.

Entonces, para dar ese período de gracia a los objetos empaquetados pero sin referencia, el proceso de reempaquetado empuja esos objetos sin referencia fuera del paquete a su forma suelta para que puedan envejecer y eventualmente podar.
Sin embargo, los objetos que quedan sin referencia no suelen ser tantos. Tener 404855 objetos sin referencia es bastante, y enviar esos objetos en primer lugar a través de un clon es estúpido y una completa pérdida de ancho de banda de la red.

De todos modos ... Para resolver su problema, simplemente necesita ejecutar ' git gc' con el --prune=nowargumento para deshabilitar ese período de gracia y deshacerse de esos objetos sin referencia de inmediato (seguro solo si no se están realizando otras actividades de git al mismo tiempo, lo que debería ser fácil de asegurar en una estación de trabajo).

Y por cierto, usando ' git gc --aggressive' con una versión posterior de git (o ' git repack -a -f -d --window=250 --depth=250')

El mismo hilo menciona :

 git config pack.deltaCacheSize 1

Eso limita el tamaño de la caché delta a un byte (desactivándolo efectivamente) en lugar del valor predeterminado de 0, lo que significa ilimitado. Con eso, puedo volver a empaquetar ese repositorio usando el git repackcomando anterior en un sistema x86-64 con 4GB de RAM y usando 4 subprocesos (este es un núcleo cuádruple). Sin embargo, el uso de memoria residente crece a casi 3.3GB.

Si su máquina es SMP y no tiene suficiente RAM, puede reducir la cantidad de subprocesos a solo uno:

git config pack.threads 1

Además, puede limitar aún más el uso de memoria con --window-memory argumentto ' git repack'.
Por ejemplo, el uso --window-memory=128Mdebe mantener un límite superior razonable en el uso de la memoria de búsqueda delta, aunque esto puede resultar en una coincidencia delta menos óptima si el repositorio contiene muchos archivos grandes.


En el frente de la rama de filtro, puede considerar (con cautela) este script

#!/bin/bash
set -o errexit

# Author: David Underhill
# Script to permanently delete files/folders from your git repository.  To use 
# it, cd to your repository's root and then run the script with a list of paths
# you want to delete, e.g., git-delete-history path1 path2

if [ $# -eq 0 ]; then
    exit 0
fi

# make sure we're at the root of git repo
if [ ! -d .git ]; then
    echo "Error: must run this script from the root of a git repository"
    exit 1
fi

# remove all paths passed as arguments from the history of the repo
files=$@
git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch $files" HEAD

# remove the temporary history git-filter-branch otherwise leaves behind for a long time
rm -rf .git/refs/original/ && git reflog expire --all &&  git gc --aggressive --prune
VonC
fuente
stackoverflow.com/questions/359424/… también es un buen comienzo para el filter-branchuso de comandos.
VonC
Hola, VonC: NI había probado git gc prune = ahora sin suerte. Realmente parece un error de git, ya que terminé con blobs sin referencia localmente después de la eliminación de una rama, pero estos no están ahí con un clon nuevo del repositorio de GitHub ... así que es solo un problema de repositorio local. Pero tengo archivos adicionales que quiero borrar, por lo que el script al que hizo referencia anteriormente es excelente, ¡gracias!
kkrugler
19

git gc --prune=now, o nivel bajo git prune --expire now.

Jakub Narębski
fuente
12

Cada vez que tu HEAD se mueve, git rastrea esto en el reflog. Si eliminó las confirmaciones, todavía tiene "confirmaciones colgantes" porque todavía se hace referencia a ellasreflog durante ~ 30 días. Esta es la red de seguridad cuando elimina confirmaciones por accidente.

Puede usar el git reflogcomando eliminar confirmaciones específicas, reempaquetar, etc., o simplemente el comando de alto nivel:

git gc --prune=now
vdboor
fuente
5

Puede utilizar git forget-blob.

El uso es bastante sencillo git forget-blob file-to-forget. Puedes obtener más información aquí

https://ownyourbits.com/2017/01/18/completely-remove-a-file-from-a-git-repository-with-git-forget-blob/

Desaparecerá de todas las confirmaciones en su historial, reflog, etiquetas, etc.

Me encuentro con el mismo problema de vez en cuando, y cada vez que tengo que volver a esta publicación y a otras, es por eso que automaticé el proceso.

Créditos a contribuyentes como Sam Watkins

nachoparker
fuente
2

Intente usar git-filter-branch ; no elimina las manchas grandes, pero puede eliminar los archivos grandes que especifique de todo el repositorio. Para mí, reduce el tamaño del repositorio de cientos MB a 12 MB.

W55tKQbuRu28Q4xv
fuente
6
Ahora que es un comando aterrador :) Tendré que intentarlo cuando mi git-fu se sienta más fuerte.
kkrugler
puedes decir eso de nuevo. Siempre desconfío de cualquier comando que manipule el historial de un repositorio. Las cosas tienden a ir muy mal cuando varias personas están presionando y tirando de ese repositorio y, de repente, un montón de objetos que git espera no están allí.
Jonathan Dumaine
1

A veces, la razón por la que "gc" no sirve de mucho es que hay un cambio de base sin terminar o un alijo basado en una confirmación anterior.

StellarVortex
fuente
O la confirmación anterior es referenciada por HEAD, ORIG_HEAD, FETCH_HEAD, reflog o alguna otra cosa que git automáticamente sigue tratando de asegurarse de que nunca pierda nada valioso. Si realmente quieres perder todos esos, tienes que hacer un esfuerzo adicional para hacerlo.
Mikko Rantalainen
1

Para agregar otro consejo, no olvide usar git remote prune para eliminar las ramas obsoletas de sus controles remotos antes de usar git gc

puedes verlos con git branch -a

A menudo es útil cuando se obtiene de github y repositorios bifurcados ...

Tanguy
fuente
1

Antes de hacer git filter-branchy git gc, debe revisar las etiquetas que están presentes en su repositorio. Cualquier sistema real que tenga etiquetado automático para cosas como la integración continua y las implementaciones hará que los objetos no deseados aún sean referenciados por estas etiquetas, por lo tantogc lo no puede eliminarlos y aún se preguntará por qué el tamaño del repositorio sigue siendo tan grande.

La mejor manera de deshacerse de todas las cosas no deseadas es ejecutar git-filterygit gc y luego empujar master a un nuevo repositorio desnudo. El nuevo repositorio desnudo tendrá el árbol limpiado.

v_abhi_v
fuente