¿Por qué es tan grande mi repositorio git?

141

145M = .git / objetos / paquete /

Escribí un script para sumar los tamaños de las diferencias de cada commit y el commit antes de ir hacia atrás desde la punta de cada rama. Obtengo 129 MB, que es sin compresión y sin tener en cuenta los mismos archivos en todas las ramas e historial común entre las ramas.

Git tiene en cuenta todas esas cosas, por lo que esperaría un repositorio mucho más pequeño. Entonces, ¿por qué es tan grande .git?

Hice:

git fsck --full
git gc --prune=today --aggressive
git repack

Para responder sobre cuántos archivos / commits, tengo 19 sucursales, aproximadamente 40 archivos en cada una. 287 commits, encontrados usando:

git log --oneline --all|wc -l

No debería tomar 10 de megabytes almacenar información sobre esto.

Ian Kelling
fuente
55
Linus recomienda lo siguiente sobre gc agresivo. ¿Hace una diferencia significativa? git repack -a -d --depth = 250 --window = 250
Greg Bacon
gracias gbacon, pero no hay diferencia.
Ian Kelling
Eso es porque te falta el -f. metalinguist.wordpress.com/2007/12/06/…
spuder
git repack -a -dredujo mi repositorio de 956MB a 250MB . ¡Gran éxito! ¡Gracias!
xanderiel

Respuestas:

68

Recientemente saqué el repositorio remoto incorrecto en el local ( git remote add ...y git remote update). Después de eliminar las referencias remotas no deseadas, las ramas y las etiquetas, todavía tenía 1,4 GB (!) De espacio desperdiciado en mi repositorio. Solo pude deshacerme de esto clonándolo con git clone file:///path/to/repository. Tenga en cuenta que esto file://hace una gran diferencia al clonar un repositorio local: solo se copian los objetos a los que se hace referencia, no toda la estructura del directorio.

Editar: Aquí está el único revestimiento de Ian para recrear todas las ramas en el nuevo repositorio:

d1=#original repo
d2=#new repo (must already exist)
cd $d1
for b in $(git branch | cut -c 3-)
do
    git checkout $b
    x=$(git rev-parse HEAD)
    cd $d2
    git checkout -b $b $x
    cd $d1
done
pgs
fuente
1
Guau. GRACIAS. .git = 15M ahora !! después de la clonación, aquí hay un pequeño trazador de líneas para preservar sus ramas anteriores. d1 = # repositorio original; d2 = # nuevo repositorio; cd $ d1; para b en $ (git branch | cut -c 3-); hacer git checkout $ b; x = $ (git rev-parse HEAD); cd $ d2; pago git -b $ b $ x; cd $ d1; hecho
Ian Kelling
Si marca esto, puede agregar el liner 1 a su respuesta para que tenga el formato de código.
Ian Kelling
1
Agregué tontamente un montón de archivos de video a mi repositorio y tuve que restablecer --soft HEAD ^ y volver a comprometerme. El directorio .git / objects fue enorme después de eso, y esta fue la única forma de que volviera a funcionar. Sin embargo, no me gustó la forma en que el revestimiento cambió los nombres de mis sucursales (mostraba origen / nombre de sucursal en lugar de solo nombre de sucursal). Así que fui un paso más allá y ejecuté una cirugía incompleta: eliminé el directorio .git / objects del original y puse el del clon. Eso funcionó, dejando intactas todas las ramas, referencias, etc. originales, y todo parece funcionar (cruzar los dedos).
Jack Senechal
1
gracias por el consejo sobre el archivo: // clone, eso fue el truco para mí
adam.wulf
3
@vonbrand si enlaza con un archivo y elimina el archivo original, no sucede nada, excepto que un contador de referencia se reduce de 2 a 1. Solo si ese contador se reduce a 0, el espacio se libera para otros archivos en fs. Por lo tanto, no, incluso si los archivos estuvieran vinculados, no pasaría nada si se elimina el original.
stefreak
157

Algunos scripts que uso:

git-fatfiles

git rev-list --all --objects | \
    sed -n $(git rev-list --objects --all | \
    cut -f1 -d' ' | \
    git cat-file --batch-check | \
    grep blob | \
    sort -n -k 3 | \
    tail -n40 | \
    while read hash type size; do 
         echo -n "-e s/$hash/$size/p ";
    done) | \
    sort -n -k1
...
89076 images/screenshots/properties.png
103472 images/screenshots/signals.png
9434202 video/parasite-intro.avi

Si desea más líneas, consulte también la versión de Perl en una respuesta contigua: https://stackoverflow.com/a/45366030/266720

git-erradicar (para video/parasite.avi):

git filter-branch -f  --index-filter \
    'git rm --force --cached --ignore-unmatch video/parasite-intro.avi' \
     -- --all
rm -Rf .git/refs/original && \
    git reflog expire --expire=now --all && \
    git gc --aggressive && \
    git prune

Nota: el segundo script está diseñado para eliminar la información de Git por completo (incluida toda la información de los registros). Usar con precaución.

Vi.
fuente
2
Finalmente ... Irónicamente, vi esta respuesta anteriormente en mi búsqueda, pero parecía demasiado complicada ... después de intentar otras cosas, ¡esta comenzó a tener sentido y listo!
msanteler
@msanteler, el git-fatfilesscript anterior ( ) surgió cuando hice la pregunta sobre IRC (Freenode / # git). Guardé la mejor versión en un archivo, luego la publiqué como respuesta aquí. (Aunque no puedo el autor original en los registros de IRC).
Vi.
Esto funciona muy bien inicialmente. Pero cuando busco o extraigo del control remoto nuevamente, solo copia todos los archivos grandes de nuevo en el archivo. ¿Cómo evito eso?
pir
1
@felbo, entonces el problema probablemente no esté solo en su repositorio local, sino también en otros repositorios. Tal vez necesite hacer el procedimiento en todas partes, o forzar a todos a abandonar las ramas originales y cambiar a ramas reescritas. No es fácil en un gran equipo y necesita cooperación entre los desarrolladores y / o la intervención del gerente. A veces, solo dejar la piedra de carga adentro puede ser una mejor opción.
Vi.
1
Esta función es excelente, pero es inimaginablemente lenta. Ni siquiera puede terminar en mi computadora si elimino el límite de 40 líneas. Para su información, acabo de agregar una respuesta con una versión más eficiente de esta función. Compruébelo si desea usar esta lógica en un repositorio grande, o si desea ver los tamaños sumados por archivo o por carpeta.
piojo
66

git gcya lo hace, por git repacklo que no tiene sentido volver a embalar manualmente a menos que le vaya a pasar algunas opciones especiales.

El primer paso es ver si la mayoría del espacio es (como normalmente sería el caso) su base de datos de objetos.

git count-objects -v

Esto debería proporcionar un informe de cuántos objetos desempaquetados hay en su repositorio, cuánto espacio ocupan, cuántos archivos de paquete tiene y cuánto espacio ocupan.

Idealmente, después de un reempaquetado, no tendría objetos desempaquetados y un archivo de paquete, pero es perfectamente normal tener algunos objetos que no estén directamente referenciados por las ramas actuales todavía presentes y desempacados.

Si tiene un solo paquete grande y desea saber qué ocupa el espacio, puede enumerar los objetos que componen el paquete junto con la forma en que se almacenan.

git verify-pack -v .git/objects/pack/pack-*.idx

Tenga en cuenta que verify-packtoma un archivo de índice y no el archivo del paquete en sí. Esto proporciona un informe de cada objeto en el paquete, su tamaño real y su tamaño empaquetado, así como información sobre si ha sido 'deltificado' y, de ser así, el origen de la cadena delta.

Para ver si hay objetos inusualmente grandes en su repositorio, puede ordenar la salida numéricamente en la tercera de la cuarta columna (por ejemplo | sort -k3n).

A partir de esta salida, podrá ver el contenido de cualquier objeto utilizando el git showcomando, aunque no es posible ver exactamente en qué parte del historial de confirmación del repositorio se hace referencia al objeto. Si necesita hacer esto, intente algo de esta pregunta .

CB Bailey
fuente
1
Esto encontró grandes objetos grandes. La respuesta aceptada se deshizo de ellos.
Ian Kelling
2
La diferencia entre git gc y git repack según linus torvalds. metalinguist.wordpress.com/2007/12/06/…
spuder
31

Solo para su información, la razón más importante por la que puede terminar con objetos no deseados que se guardan es que git mantiene un registro.

El reflog está ahí para salvar su trasero cuando elimina accidentalmente su rama maestra o de alguna manera daña catastróficamente su repositorio.

La forma más fácil de solucionar esto es truncar tus reflogs antes de comprimir (solo asegúrate de que nunca quieras volver a ninguno de los commits en el reflog).

git gc --prune=now --aggressive
git repack

Esto es diferente de git gc --prune=todayque expira todo el reflog inmediatamente.

John Gietzen
fuente
1
¡Este lo hizo por mí! Pasé de unos 5 gb a 32 mb.
Hawkee
Esta respuesta parecía más fácil de hacer, pero desafortunadamente no funcionó para mí. En mi caso, estaba trabajando en un repositorio clonado. ¿Esa es la razón?
Mert
13

Si desea encontrar qué archivos están ocupando espacio en su repositorio git, ejecute

git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -5

Luego, extraiga la referencia de blob que ocupa más espacio (la última línea) y verifique el nombre de archivo que ocupa tanto espacio

git rev-list --objects --all | grep <reference>

Esto incluso podría ser un archivo que eliminó git rm, pero git lo recuerda porque todavía hay referencias a él, como etiquetas, controles remotos y reflog.

Una vez que sepa de qué archivo desea deshacerse, le recomiendo usar git forget-blob

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

Es fácil de usar, solo hazlo

git forget-blob file-to-forget

Esto eliminará todas las referencias de git, eliminará el blob de cada confirmación en el historial y ejecutará la recolección de elementos no utilizados para liberar espacio.

nachoparker
fuente
7

El script git-fatfiles de la respuesta de Vi es encantador si quieres ver el tamaño de todos tus blobs, pero es tan lento que es inutilizable. Eliminé el límite de salida de 40 líneas e intenté usar toda la RAM de mi computadora en lugar de terminar. Así que lo reescribí: esto es miles de veces más rápido, ha agregado características (opcional) y se eliminó algún error extraño: la versión anterior daría recuentos inexactos si suma la salida para ver el espacio total utilizado por un archivo.

#!/usr/bin/perl
use warnings;
use strict;
use IPC::Open2;
use v5.14;

# Try to get the "format_bytes" function:
my $canFormat = eval {
    require Number::Bytes::Human;
    Number::Bytes::Human->import('format_bytes');
    1;
};
my $format_bytes;
if ($canFormat) {
    $format_bytes = \&format_bytes;
}
else {
    $format_bytes = sub { return shift; };
}

# parse arguments:
my ($directories, $sum);
{
    my $arg = $ARGV[0] // "";
    if ($arg eq "--sum" || $arg eq "-s") {
        $sum = 1;
    }
    elsif ($arg eq "--directories" || $arg eq "-d") {
        $directories = 1;
        $sum = 1;
    }
    elsif ($arg) {
        print "Usage: $0 [ --sum, -s | --directories, -d ]\n";
        exit 1;
    } 
}

# the format is [hash, file]
my %revList = map { (split(' ', $_))[0 => 1]; } qx(git rev-list --all --objects);
my $pid = open2(my $childOut, my $childIn, "git cat-file --batch-check");

# The format is (hash => size)
my %hashSizes = map {
    print $childIn $_ . "\n";
    my @blobData = split(' ', <$childOut>);
    if ($blobData[1] eq 'blob') {
        # [hash, size]
        $blobData[0] => $blobData[2];
    }
    else {
        ();
    }
} keys %revList;
close($childIn);
waitpid($pid, 0);

# Need to filter because some aren't files--there are useless directories in this list.
# Format is name => size.
my %fileSizes =
    map { exists($hashSizes{$_}) ? ($revList{$_} => $hashSizes{$_}) : () } keys %revList;


my @sortedSizes;
if ($sum) {
    my %fileSizeSums;
    if ($directories) {
        while (my ($name, $size) = each %fileSizes) {
            # strip off the trailing part of the filename:
            $fileSizeSums{$name =~ s|/[^/]*$||r} += $size;
        }
    }
    else {
        while (my ($name, $size) = each %fileSizes) {
            $fileSizeSums{$name} += $size;
        }
    }

    @sortedSizes = map { [$_, $fileSizeSums{$_}] }
        sort { $fileSizeSums{$a} <=> $fileSizeSums{$b} } keys %fileSizeSums;
}
else {
    # Print the space taken by each file/blob, sorted by size
    @sortedSizes = map { [$_, $fileSizes{$_}] }
        sort { $fileSizes{$a} <=> $fileSizes{$b} } keys %fileSizes;

}

for my $fileSize (@sortedSizes) {
    printf "%s\t%s\n", $format_bytes->($fileSize->[1]), $fileSize->[0];
}

Nombre este git-fatfiles.pl y ejecútelo. Para ver el espacio en disco utilizado por todas las revisiones de un archivo, use la --sumopción Para ver lo mismo, pero para los archivos dentro de cada directorio, use la --directoriesopción Si instala el módulo Number :: Bytes :: Human cpan (ejecute "cpan Number :: Bytes :: Human"), los tamaños se formatearán: "21M /path/to/file.mp4".

piojo
fuente
4

¿Está seguro de que solo cuenta los archivos .pack y no los archivos .idx? Están en el mismo directorio que los archivos .pack, pero no tienen ninguno de los datos del repositorio (como indica la extensión, no son más que índices para el paquete correspondiente; de ​​hecho, si conoce el comando correcto, puede recrearlos fácilmente desde el archivo de paquete, y git lo hace al clonar, ya que solo se transfiere un archivo de paquete usando el protocolo git nativo).

Como muestra representativa, eché un vistazo a mi clon local del repositorio linux-2.6:

$ du -c *.pack
505888  total

$ du -c *.idx
34300   total

Lo que indica que una expansión de alrededor del 7% debería ser común.

También están los archivos afuera objects/; en mi experiencia personal, de ellos indexy gitk.cachetienden a ser los más grandes (un total de 11M en mi clon del repositorio linux-2.6).

CesarB
fuente
3

Otros objetos git almacenados en .gitincluyen árboles, commits y etiquetas. Los commits y las etiquetas son pequeños, pero los árboles pueden crecer mucho, especialmente si tiene una gran cantidad de archivos pequeños en su repositorio. ¿Cuántos archivos y cuántas confirmaciones tienes?

Greg Hewgill
fuente
Buena pregunta. 19 sucursales con aproximadamente 40 archivos en cada una. git count-objects -v dice "in-pack: 1570". No estoy seguro exactamente qué significa eso o cómo contar cuántas confirmaciones tengo. Unos cientos, supongo.
Ian Kelling
Ok, no parece que esa sea la respuesta entonces. Unos pocos cientos serán insignificantes en comparación con 145 MB.
Greg Hewgill
2

¿Intentaste usar git repack ?

baudtack
fuente
Buena pregunta. Lo hice, también tuve la impresión de que git gc hace eso también?
Ian Kelling
Lo hace con git gc --auto No estoy seguro de lo que usaste.
baudtack
2

Antes de hacer git filter-branch y 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 refrenados por estas etiquetas, por lo tanto, 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-filter & git gc y luego empujar master a un nuevo repositorio desnudo. El nuevo repositorio desnudo tendrá el árbol limpio.

v_abhi_v
fuente
1

Esto puede suceder si agrega una gran porción de archivos accidentalmente y los organiza, no necesariamente los confirma. Esto puede ocurrir en una railsaplicación cuando se ejecuta bundle install --deploymenty luego accidentalmente git add .entonces ver todos los archivos añadido bajo vendor/bundleque los unstage pero ya se metió en la historia de Git, por lo que tiene que aplicar la respuesta de Vi y el cambio video/parasite-intro.avipor la vendor/bundlecontinuación, ejecutar el segundo comando que ofrece.

Puedes ver la diferencia con la git count-objects -vque en mi caso antes de aplicar el script tenía un paquete de tamaño: de 52K y después de aplicarlo era de 3.8K.

juliangonzalez
fuente
1

Vale la pena revisar el stacktrace.log. Básicamente es un registro de errores para el seguimiento de confirmaciones que fallaron. Recientemente descubrí que mi stacktrace.log tiene 65.5GB y mi aplicación tiene 66.7GB.

Nes
fuente