git gc --aggressive vs git repack

88

Estoy buscando formas de reducir el tamaño de un gitrepositorio. La búsqueda me lleva a la git gc --aggressivemayoría de las veces. También he leído que este no es el enfoque preferido.

¿Por qué? ¿De qué debo tener en cuenta si estoy corriendo gc --aggressive?

git repack -a -d --depth=250 --window=250se recomienda más gc --aggressive. ¿Por qué? ¿Cómo se repackreduce el tamaño de un repositorio? Además, no tengo muy claro las banderas --depthy --window.

¿Qué debo elegir entre gcy repack? ¿Cuándo debo usar gcy repack?

Ajith R Nayak
fuente

Respuestas:

76

Hoy en día no hay diferencia: git gc --aggressivefunciona según la sugerencia que hizo Linus en 2007; vea abajo. A partir de la versión 2.11 (cuarto trimestre de 2016), git tiene una profundidad predeterminada de 50. Una ventana de tamaño 250 es buena porque escanea una sección más grande de cada objeto, pero la profundidad a 250 es mala porque hace que cada cadena se refiera a una objects, lo que ralentiza todas las operaciones futuras de git para un uso del disco ligeramente menor.


Antecedentes históricos

Linus sugirió (ver a continuación la publicación completa de la lista de correo) usar git gc --aggressivesolo cuando tenga, en sus palabras, "un paquete realmente malo" o "deltas realmente horriblemente malos", sin embargo, "casi siempre, en otros casos, es realmente un mal cosas que hacer." ¡El resultado puede incluso dejar su repositorio en peores condiciones que cuando comenzó!

El comando que sugiere para hacer esto correctamente después de haber importado "una historia larga y complicada" es

git repack -a -d -f --depth=250 --window=250

Pero esto supone que ya ha eliminado la suciedad no deseada de su historial de repositorio y que ha seguido la lista de verificación para reducir un repositorio que se encuentra en la git filter-branchdocumentación .

git-filter-branch se puede usar para deshacerse de un subconjunto de archivos, generalmente con alguna combinación de --index-filtery --subdirectory-filter. La gente espera que el repositorio resultante sea más pequeño que el original, pero necesita algunos pasos más para hacerlo más pequeño, porque Git se esfuerza por no perder sus objetos hasta que usted se lo indique. Primero asegúrese de que:

  • Realmente eliminó todas las variantes de un nombre de archivo, si un blob se movió durante su vida útil. git log --name-only --follow --all -- filenamepuede ayudarte a encontrar cambios de nombre.

  • Realmente filtró todas las referencias: use --tag-name-filter cat -- --allal llamar git filter-branch.

Entonces hay dos formas de obtener un repositorio más pequeño. Una forma más segura es clonar, que mantiene intacto el original.

  • Clonarlo con git clone file:///path/to/repo. El clon no tendrá los objetos eliminados. Ver git-clone. (¡Tenga en cuenta que la clonación con una ruta simple solo vincula todo!)

Si realmente no desea clonarlo, por cualquier motivo, consulte los siguientes puntos (en este orden). Este es un enfoque muy destructivo, así que haga una copia de seguridad o vuelva a clonarlo. Usted ha sido advertido.

  • Elimine las referencias originales respaldadas por git-filter-branch: digamos

    git for-each-ref --format="%(refname)" refs/original/ |
      xargs -n 1 git update-ref -d
    
  • Caducar todos los reflogs con git reflog expire --expire=now --all.

  • La basura recolecta todos los objetos sin referencia con git gc --prune=now(o si su git gcno es lo suficientemente nuevo para admitir argumentos --prune, use git repack -ad; git pruneen su lugar).


Date: Wed, 5 Dec 2007 22:09:12 -0800 (PST)
From: Linus Torvalds <torvalds at linux-foundation dot org>
To: Daniel Berlin <dberlin at dberlin dot org>
cc: David Miller <davem at davemloft dot net>,
    ismail at pardus dot org dot tr,
    gcc at gcc dot gnu dot org,
    git at vger dot kernel dot org
Subject: Re: Git and GCC
In-Reply-To: <[email protected]>
Message-ID: <[email protected]>
References: <[email protected]>
            <[email protected]>
            <[email protected]>
            <[email protected]>
            <[email protected]>

El jueves 6 de diciembre de 2007, Daniel Berlin escribió:

En realidad, resulta que git-gc --aggressivehace esta tontería empacar archivos a veces, independientemente de si ha convertido de un repositorio SVN o no.

Absolutamente. git --aggressivees mayormente tonto. Realmente solo es útil para el caso de "Sé que tengo un paquete realmente malo y quiero deshacerme de todas las malas decisiones de empaque que he tomado".

Para explicar esto, vale la pena explicar (probablemente lo sepas, pero déjame repasar los conceptos básicos de todos modos) cómo funcionan las cadenas delta de git y cómo son tan diferentes de la mayoría de los otros sistemas.

En otros SCM, una cadena delta generalmente es fija. Puede ser "hacia adelante" o "hacia atrás" y puede evolucionar un poco a medida que trabaja con el repositorio, pero generalmente es una cadena de cambios en un solo archivo representado como algún tipo de entidad SCM única. En CVS, obviamente es el *,varchivo, y muchos otros sistemas hacen cosas bastante similares.

Git también hace cadenas delta, pero las hace mucho más "sueltas". No existe una entidad fija. Los deltas se generan contra cualquier otra versión aleatoria que git considere un buen candidato delta (con varias heurísticas bastante exitosas), y no hay absolutamente ninguna regla de agrupación estricta.

En general, esto es algo muy bueno. Es bueno por varias razones conceptuales ( es decir , git internamente nunca necesita preocuparse por toda la cadena de revisión, realmente no piensa en términos de deltas), pero también es genial porque deshacerse de las inflexibles reglas delta significa que git no tiene ningún problema al fusionar dos archivos, por ejemplo, simplemente no hay *,v“archivos de revisión” arbitrarios que tengan algún significado oculto.

También significa que la elección de deltas es una cuestión mucho más abierta. Si limita la cadena delta a un solo archivo, realmente no tiene muchas opciones sobre qué hacer con los deltas, pero en git, realmente puede ser un problema totalmente diferente.

Y aquí es donde --aggressiveentra en juego lo realmente mal llamado . Si bien git generalmente intenta reutilizar la información delta (porque es una buena idea, y no desperdicia el tiempo de la CPU para encontrar todos los deltas buenos que encontramos antes), a veces quiero decir "comencemos de nuevo, con una pizarra en blanco, ignoremos toda la información delta anterior e intente generar un nuevo conjunto de deltas".

Así --aggressiveque no se trata realmente de ser agresivo, sino de perder el tiempo de la CPU volviendo a tomar una decisión que ya tomamos antes.

A veces eso es bueno. Algunas herramientas de importación en particular podrían generar deltas realmente horriblemente malos. Cualquier cosa que use git fast-import, por ejemplo, probablemente no tenga un gran diseño delta, por lo que podría valer la pena decir "Quiero comenzar desde cero".

Pero casi siempre, en otros casos, es realmente algo realmente malo. Va a desperdiciar tiempo de CPU, y especialmente si realmente ha hecho un buen trabajo en deltaing antes, el resultado final no va a reutilizar todos esos buenos deltas que ya encontró, por lo que en realidad terminará con mucho peor resultado final también!

Enviaré un parche a Junio ​​para eliminar la git gc --aggressive documentación. Puede ser útil, pero generalmente es útil solo cuando realmente comprende a un nivel muy profundo lo que está haciendo, y esa documentación no lo ayuda a hacerlo.

Generalmente, hacer incrementos git gces el enfoque correcto y mejor que hacerlo git gc --aggressive. Va a reutilizar los deltas antiguos, y cuando no se puedan encontrar esos deltas antiguos (¡la razón para hacer GC incremental en primer lugar!), Creará otros nuevos.

Por otro lado, es definitivamente cierto que una "importación inicial de una historia larga y complicada" es un punto en el que puede valer la pena dedicar mucho tiempo a encontrar los deltas realmente buenos . Entonces, todos los usuarios posteriores (¡siempre que no lo git gc --aggressivedeshagan!) Obtendrán la ventaja de ese evento único. Entonces, especialmente para proyectos grandes con una larga historia, probablemente valga la pena hacer un trabajo adicional, diciéndole al código de búsqueda delta que se vuelva loco.

Entonces, el equivalente de git gc --aggressive, pero hecho correctamente , es hacer (durante la noche) algo como

git repack -a -d --depth=250 --window=250

donde esa profundidad se trata de cuán profundas pueden ser las cadenas delta (hazlas más largas para la historia antigua, vale la pena el espacio), y la cuestión de la ventana se trata de qué tan grande es la ventana de objeto que queremos que escanee cada candidato delta.

Y aquí, es posible que desee agregar la -fbandera (que es "eliminar todos los deltas antiguos", ya que ahora está tratando de asegurarse de que este realmente encuentre buenos candidatos.

Y luego tomará una eternidad y un día ( es decir , una cosa de "hazlo de la noche a la mañana"). Pero el resultado final es que todos los usuarios posteriores a ese repositorio obtendrán paquetes mucho mejores, sin tener que gastar ningún esfuerzo en ellos.

          Linus
Greg Bacon
fuente
2
Tu comentario sobre la profundidad es un poco confuso. Al principio me iba a quejar de que estás completamente equivocado, que agresivo puede acelerar enormemente un repositorio de git. Después de hacer una recolección de basura agresiva, un repositorio ENORME que tardó cinco minutos en hacer un estado de git reducido a segundos. Pero luego me di cuenta de que no te referías a que el GC agresivo ralentizaba el repositorio, sino solo a un tamaño de profundidad extremadamente grande.
user6856
57

¿Cuándo debo usar gc & repack?

Como mencioné en " La recolección de basura de Git no parece funcionar completamente ", a git gc --aggressiveno es suficiente ni siquiera suficiente por sí solo.
Y, como explico a continuación , a menudo no es necesario.

La combinación más efectiva sería agregar git repack, pero también git prune:

git gc
git repack -Ad      # kills in-pack garbage
git prune           # kills loose garbage

Nota: Git 2.11 (Q4 2016) establecerá la gc aggressiveprofundidad predeterminada en 50

Consulte la confirmación 07e7dbf (11 de agosto de 2016) de Jeff King ( peff) .
(Combinado por Junio ​​C Hamano - gitster- en el compromiso 0952ca8 , 21 de septiembre de 2016)

gc: profundidad agresiva predeterminada a 50

" git gc --aggressive" se utiliza para limitar la longitud de la cadena delta a 250, que es demasiado profunda para ahorrar espacio adicional y es perjudicial para el rendimiento en tiempo de ejecución.
El límite se ha reducido a 50.

El resumen es: el valor predeterminado actual de 250 no ahorra mucho espacio y cuesta CPU. No es una buena compensación.

La --aggressivebandera " " git-gchace tres cosas:

  1. use " -f" para descartar los deltas existentes y volver a calcular desde cero
  2. use "--window = 250" para buscar más detenidamente los deltas
  3. use "--depth = 250" para hacer cadenas delta más largas

Los elementos (1) y (2) son buenas combinaciones para un reempaquetado "agresivo".
Le piden al reempaquetado que haga más trabajo de cálculo con la esperanza de obtener un paquete mejor. Usted paga los costos durante el reembalaje y otras operaciones solo ven el beneficio.

El ítem (3) no es tan claro.
Permitir cadenas más largas significa menos restricciones en los deltas, lo que significa potencialmente encontrar mejores y ahorrar algo de espacio.
Pero también significa que las operaciones que acceden a los deltas deben seguir cadenas más largas, lo que afecta su desempeño.
Así que es una compensación, y no está claro que la compensación sea siquiera buena.

(Ver compromiso para estudio )

Puede ver que el ahorro de CPU para operaciones regulares mejora a medida que disminuimos la profundidad.
Pero también podemos ver que el ahorro de espacio no es tan grande a medida que aumenta la profundidad. Ahorrar un 5-10% entre el 10 y el 50 probablemente valga la pena la compensación de la CPU. Ahorrar un 1% para pasar de 50 a 100, u otro 0,5% para pasar de 100 a 250 probablemente no lo sea.


Hablando de ahorro de CPU, " git repack" aprendí a aceptar la --threads=<n>opción y pasarla a pack-objects.

Consulte la confirmación 40bcf31 (26 de abril de 2017) de Junio ​​C Hamano ( gitster) .
(Combinado por Junio ​​C Hamano - gitster- en el compromiso 31fb6f4 , 29 de mayo de 2017)

reempaquetar: aceptar --threads=<n>y pasarlo apack-objects

Ya lo hacemos por --window=<n>y --depth=<n>; esto ayudará cuando el usuario quiera forzar --threads=1pruebas reproducibles sin verse afectado por las carreras de múltiples hilos.

VonC
fuente
3
Mencioné el hilo de Linus en el enlace "La recolección de basura de Git no parece funcionar completamente"
VonC
1
¡Gracias por esta actualización moderna! Todas las demás respuestas aquí son antiguas. Ahora podemos ver que git gc --aggressivese ha solucionado dos veces: primero, hacer lo que Linus sugirió en 2007 como un "mejor método de empaque". Y luego en Git 2.11 para evitar la profundidad de objeto excesiva que Linus había sugerido pero que resultó ser dañina (ralentiza todas las operaciones futuras de Git y no ahorró ningún espacio del que valga la pena hablar).
gw0
git gc, seguido de git repack -Ad y git prune aumentan el tamaño de mi repositorio ... ¿por qué?
devops
@devops No estoy seguro: ¿qué versión de Git estás usando? Puede hacer una nueva pregunta para eso (con más detalles como el sistema operativo, el tamaño general de su repositorio, ...)
VonC
man git-repackdice para -d: `También ejecuta git prune-pack para eliminar archivos de objetos sueltos redundantes. '¿O git prunetambién hace eso? man git-prunedice In most cases, users should run git gc, which calls git prune., entonces, ¿para qué sirve después git gc? ¿No sería mejor o suficiente usar solo git repack -Ad && git gc?
Jakob
14

El problema git gc --aggressivees que el nombre de la opción y la documentación son engañosos.

Como explica el propio Linus en este correo , lo que git gc --aggressivebásicamente hace es esto:

Si bien git generalmente intenta reutilizar la información delta (porque es una buena idea y no desperdicia el tiempo de la CPU al volver a encontrar todos los deltas buenos que encontramos anteriormente), a veces quieres decir "empecemos de nuevo, con un pizarra en blanco e ignore toda la información delta anterior e intente generar un nuevo conjunto de deltas ".

Por lo general, no es necesario volver a calcular los deltas en git, ya que git determina que estos deltas son muy flexibles. Solo tiene sentido si sabes que tienes deltas muy, muy malos. Como explica Linus, principalmente las herramientas que utilizan git fast-importcaen en esta categoría.

La mayoría de las veces, git hace un buen trabajo determinando deltas útiles y el uso git gc --aggressivete dejará con deltas que son potencialmente incluso peores y pierden mucho tiempo de CPU.


Linus termina su correo con la conclusión de que git repackcon un gran --depthy --windowes la mejor opción en la mayor parte del tiempo; especialmente después de importar un proyecto grande y querer asegurarse de que git encuentre buenos deltas.

Entonces, el equivalente de git gc --aggressive, pero hecho correctamente , es hacer (durante la noche) algo como

git repack -a -d --depth=250 --window=250

donde esa profundidad se trata de cuán profundas pueden ser las cadenas delta (hazlas más largas para la historia antigua, vale la pena el espacio), y la cuestión de la ventana se trata de qué tan grande es la ventana de objeto que queremos que escanee cada candidato delta.

Y aquí, es posible que desee agregar la -fbandera (que es "eliminar todos los deltas antiguos", ya que ahora está tratando de asegurarse de que este realmente encuentre buenos candidatos.

Sascha lobo
fuente
8

Precaución. No lo ejecute git gc --agressivecon un repositorio que no esté sincronizado con el remoto si no tiene copias de seguridad.

Esta operación recrea los deltas desde cero y podría provocar la pérdida de datos si se interrumpe correctamente.

Para mi computadora de 8GB, el gc agresivo se quedó sin memoria en el repositorio de 1Gb con 10k pequeñas confirmaciones. Cuando OOM killer terminó el proceso de git, me dejó con un repositorio casi vacío, solo sobrevivieron el árbol de trabajo y pocos deltas.

Por supuesto, no era la única copia del repositorio, así que simplemente lo recreé y lo saqué del control remoto (la búsqueda no funcionó en el repositorio roto y se bloqueó en el paso 'resolver deltas' varias veces, intenté hacerlo), pero si su repositorio es Repositorio local de un solo desarrollador sin ningún tipo de control remoto: primero haga una copia de seguridad.

Puntero sabio
fuente
5

Nota: tenga cuidado con el uso git gc --aggressive, como aclara Git 2.22 (Q2 2019).

Ver confirmar 0044f77 , confirmar daecbf2 , confirmar 7384504 , confirmar 22d4e3b , confirmar 080a448 , confirmar 54d56f5 , confirmar d257e0f , confirmar b6a8d09 (07 de abril de 2019) y confirmar fc559fb , confirmar cf9cd77 , confirmar b11e856 (22 de marzo de 2019) por Ærvar Bjarmajöson (Ærvar Bjarmajöson avar) .
(Combinado por Junio ​​C Hamano - gitster- en el compromiso ac70c53 , 25 de abril de 2019)

gc docs: restar importancia a la utilidad de --aggressive

Los gc --aggressivedocumentos " " existentes no llegan a recomendar a los usuarios que lo ejecuten con regularidad.
Personalmente, he hablado con muchos usuarios que han tomado estos documentos como un consejo para usar esta opción y, por lo general , es (en su mayoría) una pérdida de tiempo .

Así que aclaremos lo que realmente hace y dejemos que el usuario saque sus propias conclusiones.

Aclaremos también "Los efectos [...] son ​​persistentes" para parafrasear una breve versión de la explicación de Jeff King .

Eso significa que la documentación de git-gc ahora incluye :

AGRESIVO

Cuando --aggressivese proporcione la opción, git-repackse invocará con la -fbandera, que a su vez pasará --no-reuse-deltaa git-pack-objects .
Esto eliminará los deltas existentes y los volverá a calcular, a expensas de dedicar mucho más tiempo al reempaquetado.

Los efectos de esto son en su mayoría persistentes, por ejemplo, cuando los paquetes y los objetos sueltos se fusionan entre sí, los deltas existentes en ese paquete pueden reutilizarse, pero también hay varios casos en los que podemos elegir un delta subóptimo de uno más nuevo. empacar en su lugar.

Además, el suministro --aggressiveajustará las opciones --depthy --windowpasadas a git-repack.
Consulte la configuración de gc.aggressiveDepthy a gc.aggressiveWindowcontinuación.
Al usar un tamaño de ventana más grande, es más probable que encontremos deltas más óptimos.

Probablemente no valga la pena utilizar esta opción en un repositorio determinado sin ejecutar evaluaciones comparativas de rendimiento personalizadas en él .
Lleva mucho más tiempo y la optimización de espacio / delta resultante puede valer la pena o no. No usar esto en absoluto es la compensación correcta para la mayoría de los usuarios y sus repositorios.

Y ( comete 080a448 ):

gcdocs: observe cómo los --aggressiveimpactos --windowy--depth

Desde 07e7dbf ( gc: profundidad agresiva predeterminada a 50, 2016-08-11, Git v2.10.1) usamos de manera algo confusa la misma profundidad --aggressiveque usamos por defecto.

Como se señaló en ese compromiso que tiene sentido, estuvo mal hacer más profundidad el valor predeterminado para "agresivo", y así ahorrar espacio en disco a expensas del rendimiento en tiempo de ejecución, que suele ser lo opuesto a alguien a quien le gustaría "gc agresivo" quiere.

VonC
fuente