¿Cómo encontrar / identificar grandes confirmaciones en el historial de git?

366

Tengo un repositorio git de 300 MB. El tamaño total de mis archivos actualmente desprotegidos es de 2 MB, y el tamaño total del resto del repositorio de git es de 298 MB. Esto es básicamente un repositorio de solo código que no debería tener más de unos pocos MB.

Sospecho que alguien cometió accidentalmente algunos archivos grandes (video, imágenes, etc.), y luego los eliminó ... pero no de git, por lo que el historial todavía contiene archivos grandes inútiles. ¿Cómo puedo encontrar los archivos grandes en el historial de git? Hay más de 400 commits, por lo que ir uno por uno no es práctico.

NOTA : mi pregunta no es acerca de cómo eliminar el archivo , sino cómo encontrarlo en primer lugar.

pantalones
fuente

Respuestas:

143

He encontrado este script muy útil en el pasado para encontrar objetos grandes (y no obvios) en un repositorio de git:


#!/bin/bash
#set -x 

# Shows you the largest objects in your repo's pack file.
# Written for osx.
#
# @see https://stubbisms.wordpress.com/2009/07/10/git-script-to-show-largest-pack-objects-and-trim-your-waist-line/
# @author Antony Stubbs

# set the internal field separator to line break, so that we can iterate easily over the verify-pack output
IFS=$'\n';

# list all objects including their size, sort by size, take top 10
objects=`git verify-pack -v .git/objects/pack/pack-*.idx | grep -v chain | sort -k3nr | head`

echo "All sizes are in kB's. The pack column is the size of the object, compressed, inside the pack file."

output="size,pack,SHA,location"
allObjects=`git rev-list --all --objects`
for y in $objects
do
    # extract the size in bytes
    size=$((`echo $y | cut -f 5 -d ' '`/1024))
    # extract the compressed size in bytes
    compressedSize=$((`echo $y | cut -f 6 -d ' '`/1024))
    # extract the SHA
    sha=`echo $y | cut -f 1 -d ' '`
    # find the objects location in the repository tree
    other=`echo "${allObjects}" | grep $sha`
    #lineBreak=`echo -e "\n"`
    output="${output}\n${size},${compressedSize},${other}"
done

echo -e $output | column -t -s ', '

Eso le dará el nombre del objeto (SHA1sum) del blob, y luego puede usar un script como este:

... para encontrar el commit que apunta a cada uno de esos blobs.

Mark Longair
fuente
31
Esta respuesta fue realmente útil, porque me envió a la publicación anterior. Si bien el guión de la publicación funcionó, lo encontré dolorosamente lento. Así que lo reescribí, y ahora es significativamente más rápido en repositorios grandes. Echa un vistazo: gist.github.com/nk9/b150542ef72abc7974cb
Nick K9
77
Incluya instrucciones completas en sus respuestas y no solo enlaces externos; ¿Qué hacemos cuando stubbisms.wordpress.com inevitablemente cae eh?
ThorSummoner
@ NickK9 curiosamente obtengo resultados diferentes de su script y el otro. Hay un montón de objetos más grandes que el tuyo parece perder. ¿Se me escapa algo?
UpAndAdam
Oh guay! Gracias por hacer mi script más rápido @nick \ k9: D @UpAndAdam, ¿estás diciendo que mi script produjo una salida incorrecta?
Antony Stubbs
1
Estos comentarios hacen que parezca que estamos informando el tamaño en bytes, pero obtengo kilobytes.
Kat
684

🚀 Una carcasa increíblemente rápida de una sola línea 🚀

Este script de shell muestra todos los objetos de blob en el repositorio, ordenados de menor a mayor.

Para mi repositorio de muestra, se ejecutó aproximadamente 100 veces más rápido que los otros que se encuentran aquí.
En mi confiable sistema Athlon II X4, maneja el repositorio Kernel de Linux con sus 5.6 millones de objetos en poco más de un minuto .

El guión base

git rev-list --objects --all \
| git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \
| sed -n 's/^blob //p' \
| sort --numeric-sort --key=2 \
| cut -c 1-12,41- \
| $(command -v gnumfmt || echo numfmt) --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest

Cuando ejecute el código anterior, obtendrá una buena salida legible para humanos como esta:

...
0d99bb931299  530KiB path/to/some-image.jpg
2ba44098e28f   12MiB path/to/hires-image.png
bd1741ddce0d   63MiB path/to/some-video-1080p.mp4

Usuarios de macOS : como numfmtno está disponible en macOS, puede omitir la última línea y tratar con tamaños de bytes sin formato o brew install coreutils.

Filtración

Para lograr un mayor filtrado , inserte cualquiera de las siguientes líneas antes de la sortlínea .

Para excluir archivos que están presentesHEAD , inserte la siguiente línea:

| grep -vF --file=<(git ls-tree -r HEAD | awk '{print $3}') \

Para mostrar solo los archivos que exceden el tamaño dado (por ejemplo, 1 MiB = 2 20  B), inserte la siguiente línea:

| awk '$2 >= 2^20' \

Salida para computadoras

Para generar una salida que sea más adecuada para su posterior procesamiento por parte de las computadoras, omita las dos últimas líneas del script base. Hacen todo el formateo. Esto te dejará con algo como esto:

...
0d99bb93129939b72069df14af0d0dbda7eb6dba 542455 path/to/some-image.jpg
2ba44098e28f8f66bac5e21210c2774085d2319b 12446815 path/to/hires-image.png
bd1741ddce0d07b72ccf69ed281e09bf8a2d0b2f 65183843 path/to/some-video-1080p.mp4

Eliminación de archivos

Para la eliminación real del archivo, consulte esta pregunta SO sobre el tema .

raphinesse
fuente
14
¡Esto merece algo más que mi voto a favor! Un agradecimiento especial por proporcionar salida legible por computadora y por humanos.
Michel Jung
2
¡Esto es extremadamente rápido y fácil de usar!
Chin
32
Para usar esto en Mac, necesita brew install coreutilsreemplazarlo cutcon gcuty numfmtcon gnumfmt.
Nick Sweeting
2
Permítanme volver a enfatizar: esto es mucho más rápido que todos los otros listados que he visto.
Sridhar Sarnobat
44
esto hace un increíble alias de git :) git large¿alguien?
anarcat
160

He encontrado una solución única en la página wiki del Departamento de Física de ETH Zurich (cerca del final de esa página). Simplemente haga una git gcpara eliminar la basura obsoleta, y luego

git rev-list --objects --all \
  | grep "$(git verify-pack -v .git/objects/pack/*.idx \
           | sort -k 3 -n \
           | tail -10 \
           | awk '{print$1}')"

le dará los 10 archivos más grandes en el repositorio.

También hay una solución más perezosa ahora disponible, GitExtensions ahora tiene un complemento que hace esto en la interfaz de usuario (y también maneja las reescrituras del historial).

Diálogo 'Encontrar archivos grandes' de GitExtensions

Skolima
fuente
8
Ese one-liner solo funciona si desea obtener el archivo más grande (es decir, use tail -1). Las nuevas líneas se interponen en el camino por algo más grande. Puede usar sed para convertir las nuevas líneas para que grep juegue bien:git rev-list --objects --all | grep -E `git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -10 | awk '{print$1}' | sed ':a;N;$!ba;s/\n/|/g'`
Throctukes
10
grep: a70783fca9bfbec1ade1519a41b6cc4ee36faea0: No existe tal archivo o directorio
Jonathan Allard
1
El enlace wiki trasladó a: readme.phys.ethz.ch/documentation/git_advanced_hints
outsmartin
11
Encontrar GitExtensions es como encontrar la olla de oro y el final del arco iris. ¡Gracias!
ckapilla
3
¿Existe también una extensión que imprima el tamaño de los archivos?
Michael
27

Paso 1 Escriba todos los archivos SHA1 en un archivo de texto:

git rev-list --objects --all | sort -k 2 > allfileshas.txt

Paso 2 Ordena los blobs de mayor a menor y escribe los resultados en el archivo de texto:

git gc && git verify-pack -v .git/objects/pack/pack-*.idx | egrep "^\w+ blob\W+[0-9]+ [0-9]+ [0-9]+$" | sort -k 3 -n -r > bigobjects.txt

Paso 3a Combine ambos archivos de texto para obtener información sobre el nombre del archivo / sha1 / tamaño:

for SHA in `cut -f 1 -d\  < bigobjects.txt`; do
echo $(grep $SHA bigobjects.txt) $(grep $SHA allfileshas.txt) | awk '{print $1,$3,$7}' >> bigtosmall.txt
done;

Paso 3b Si tiene nombres de archivos o rutas que contienen espacios, pruebe esta variación del Paso 3a. Se utiliza en cutlugar de awkobtener las columnas deseadas incl. espacios desde la columna 7 hasta el final de la línea:

for SHA in `cut -f 1 -d\  < bigobjects.txt`; do
echo $(grep $SHA bigobjects.txt) $(grep $SHA allfileshas.txt) | cut -d ' ' -f'1,3,7-' >> bigtosmall.txt
done;

Ahora puede mirar el archivo bigtosmall.txt para decidir qué archivos desea eliminar de su historial de Git.

Paso 4 Para realizar la eliminación (tenga en cuenta que esta parte es lenta ya que examinará cada confirmación en su historial en busca de datos sobre el archivo que identificó):

git filter-branch --tree-filter 'rm -f myLargeFile.log' HEAD

Fuente

Los pasos 1-3a se copiaron de Buscar y purgar archivos grandes del historial de Git

EDITAR

El artículo se eliminó en algún momento de la segunda mitad de 2017, pero aún se puede acceder a una copia archivada utilizando Wayback Machine .

Friederbluemle
fuente
66
Un trazador de líneas para hacer lo mismo:git gc && join -e ERROR -a 2 -j 1 -o 2.1,2.3,1.2 --check-order <( git rev-list --objects --all | sort -k 1 ) <( git verify-pack -v .git/objects/pack/pack-*.idx | gawk '( NF == 5 && $2 == "blob" ){print}' | sort -k1 ) | sort -k2gr
Iwan Aucamp
1
@Iwan, gracias por la frase! No maneja los nombres de archivo con espacios en ellos, esto parece: join -t' ' -e ERROR -a 2 -j 1 -o 2.1,2.3,1.2 --check-order <( git rev-list --objects --all | sed 's/[[:space:]]/\t/' | sort -k 1 ) <( git verify-pack -v .git/objects/pack/pack-*.idx | gawk '( NF == 5 && $2 == "blob" ){print}' | sort -k1 | sed 's/[[:space:]]\+/\t/g' ) | sort -k2gr | less. Tenga en cuenta que debe ingresar el carácter TAB real después join -t'con CTRL + V <TAB> por geekbraindump.blogspot.ru/2009/04/unix-join-with-tabs.html
Nickolay
2
@Nickolay con bash $'\t'debería darte una pestaña. echo -n $'\t' | xxd -ps->09
Iwan Aucamp
1
@IwanAucamp: aún mejor, ¡gracias por la sugerencia! (Lástima que no puedo editar el comentario anterior ... oh, bueno.)
Nickolay
1
@ Sridhar-Sarnobat ¡El artículo fue guardado por Wayback Machine! :) web.archive.org/web/20170621125743/http://www.naleid.com/blog/…
friederbluemle
18

Deberías usar BFG Repo-Cleaner .

Según el sitio web:

El BFG es una alternativa más simple y más rápida a git-filter-branch para limpiar los datos incorrectos de su historial de repositorio de Git:

  • Eliminar Crazy Big Files
  • Eliminar contraseñas, credenciales y otros datos privados

El procedimiento clásico para reducir el tamaño de un repositorio sería:

git clone --mirror git://example.com/some-big-repo.git
java -jar bfg.jar --strip-biggest-blobs 500 some-big-repo.git
cd some-big-repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push
Warren Seine
fuente
44
BFG Repo-Cleaner es muy bueno. Se aligera rápidamente y funciona de manera muy confiable.
fschmitt
30
Sin embargo, esto no le dice cómo enumerar todos los archivos más grandes.
Andi Jay
55
El problema con esto es que no puedes VER cuáles son los archivos grandes sin eliminarlos. No me siento cómodo haciendo esto sin una ejecución en seco primero que simplemente enumera los archivos grandes.
Sridhar Sarnobat
¿Qué --strip-biggest-blobs 500hacer?
2540625
git rechazará los cambios que realice esta herramienta.
Christopher
9

Si solo desea tener una lista de archivos grandes, me gustaría proporcionarle la siguiente línea:

join -o "1.1 1.2 2.3" <(git rev-list --objects --all | sort) <(git verify-pack -v objects/pack/*.idx | sort -k3 -n | tail -5 | sort) | sort -k3 -n

Cuyo resultado será:

commit       file name                                  size in bytes

72e1e6d20... db/players.sql 818314
ea20b964a... app/assets/images/background_final2.png 6739212
f8344b9b5... data_test/pg_xlog/000000010000000000000001 1625545
1ecc2395c... data_development/pg_xlog/000000010000000000000001 16777216
bc83d216d... app/assets/images/background_1forfinal.psd 95533848

La última entrada en la lista apunta al archivo más grande en su historial de git.

Puede usar esta salida para asegurarse de que no está eliminando cosas con BFG que hubiera necesitado en su historial.

schmijos
fuente
2
¡¡Increíble!! Sin embargo, debe tener en cuenta que necesita clonar el repositorio con las opciones --mirror antes de ejecutar este comando.
Andi Jay
Tengo curiosidad, ¿para qué son los 1.1, 1.2, 2.3números?
ympostor
Los números son una lista que <filenumber>.<field>especifica el orden de la combinación. Ver man.cx/join para más información.
schmijos
6

Si está en Windows, aquí hay un script de PowerShell que imprimirá los 10 archivos más grandes en su repositorio:

$revision_objects = git rev-list --objects --all;
$files = $revision_objects.Split() | Where-Object {$_.Length -gt 0 -and $(Test-Path -Path $_ -PathType Leaf) };
$files | Get-Item -Force | select fullname, length | sort -Descending -Property Length | select -First 10
Julia Schwarz
fuente
1
Esto produce una respuesta diferente a @raphinesse, faltando un montón de los archivos más grandes en mi repositorio. Además, cuando un archivo grande tiene muchas modificaciones, solo se informa el tamaño más grande.
kristianp
Este script no para mí, con el error: You cannot call a method on a null-valued expression. At line: 2 char: 1. Sin embargo, esta respuesta funcionó: stackoverflow.com/a/57793716/2441655 (también es más corta)
Venryx
4

Tratar git ls-files | xargs du -hs --threshold=1M.

Usamos el siguiente comando en nuestra canalización de CI, se detiene si encuentra archivos grandes en el repositorio de git:

test $(git ls-files | xargs du -hs --threshold=1M 2>/dev/null | tee /dev/stderr | wc -l) -gt 0 && { echo; echo "Aborting due to big files in the git repository."; exit 1; } || true
Vojtech Vitek
fuente
2

No pude hacer uso de la respuesta más popular porque el --batch-check cambio de línea de comandos a Git 1.8.3 (que tengo que usar) no acepta ningún argumento. Los pasos siguientes se han probado en CentOS 6.5 con Bash 4.1.2

Conceptos clave

En Git, el término blob implica el contenido de un archivo. Tenga en cuenta que una confirmación puede cambiar el contenido de un archivo o nombre de ruta. Por lo tanto, el mismo archivo podría referirse a un blob diferente dependiendo de la confirmación. Un determinado archivo podría ser el más grande en la jerarquía de directorios en un commit, mientras que no en otro. Por lo tanto, la cuestión de encontrar confirmaciones grandes en lugar de archivos grandes coloca los asuntos en la perspectiva correcta.

Para el impaciente

El comando para imprimir la lista de blobs en orden descendente de tamaño es:

git cat-file --batch-check < <(git rev-list --all --objects  | \
awk '{print $1}')  | grep blob  | sort -n -r -k 3

Salida de muestra:

3a51a45e12d4aedcad53d3a0d4cf42079c62958e blob 305971200
7c357f2c2a7b33f939f9b7125b155adbd7890be2 blob 289163620

Para eliminar dichos blobs, use BFG Repo Cleaner , como se menciona en otras respuestas. Dado un archivo blobs.txtque solo contiene los hash de blob, por ejemplo:

3a51a45e12d4aedcad53d3a0d4cf42079c62958e
7c357f2c2a7b33f939f9b7125b155adbd7890be2

Hacer:

java -jar bfg.jar -bi blobs.txt <repo_dir>

La pregunta es sobre encontrar los commits, que es más trabajo que encontrar blobs. Para saber, sigue leyendo.

Más trabajo

Dado un hash de confirmación, un comando que imprime hash de todos los objetos asociados con él, incluidos los blobs, es:

git ls-tree -r --full-tree <commit_hash>

Entonces, si tenemos tales salidas disponibles para todos los commits en el repositorio, entonces dado un hash de blob, el montón de commits son los que coinciden con cualquiera de las salidas. Esta idea está codificada en el siguiente script:

#!/bin/bash
DB_DIR='trees-db'

find_commit() {
    cd ${DB_DIR}
    for f in *; do
        if grep -q $1 ${f}; then
            echo ${f}
        fi
    done
    cd - > /dev/null
}

create_db() {
    local tfile='/tmp/commits.txt'
    mkdir -p ${DB_DIR} && cd ${DB_DIR}
    git rev-list --all > ${tfile}

    while read commit_hash; do
        if [[ ! -e ${commit_hash} ]]; then
            git ls-tree -r --full-tree ${commit_hash} > ${commit_hash}
        fi
    done < ${tfile}
    cd - > /dev/null
    rm -f ${tfile}
}

create_db

while read id; do
    find_commit ${id};
done

Si los contenidos se guardan en un archivo llamado find-commits.sh, una invocación típica será la siguiente:

cat blobs.txt | find-commits.sh

Como anteriormente, el archivo blobs.txtenumera hash de blob, uno por línea. loscreate_db() función guarda un caché de todos los listados de confirmación en un subdirectorio en el directorio actual.

Algunas estadísticas de mis experimentos en un sistema con dos procesadores Intel (R) Xeon (R) CPU E5-2620 2.00GHz presentados por el sistema operativo como 24 núcleos virtuales:

  • Número total de confirmaciones en el repositorio = casi 11,000
  • Velocidad de creación de archivos = 126 archivos / s. El script crea un solo archivo por confirmación. Esto ocurre solo cuando el caché se está creando por primera vez.
  • Sobrecarga de creación de caché = 87 s.
  • Velocidad de búsqueda promedio = 522 commits / s. La optimización de caché resultó en una reducción del 80% en el tiempo de ejecución.

Tenga en cuenta que el script tiene un solo subproceso. Por lo tanto, solo se usaría un núcleo a la vez.

pdp
fuente
2

Solución Powershell para Windows Git, encuentre los archivos más grandes:

git ls-tree -r -t -l --full-name HEAD | Where-Object {
 $_ -match '(.+)\s+(.+)\s+(.+)\s+(\d+)\s+(.*)'
 } | ForEach-Object {
 New-Object -Type PSObject -Property @{
     'col1'        = $matches[1]
     'col2'      = $matches[2]
     'col3' = $matches[3]
     'Size'      = [int]$matches[4]
     'path'     = $matches[5]
 }
 } | sort -Property Size -Top 10 -Descending
Aaron
fuente
0

¿Cómo puedo rastrear los archivos grandes en el historial de git?

Comience analizando, validando y seleccionando la causa raíz. Úselo git-repo-analysispara ayudar.

También puede encontrar algo de valor en los informes detallados generados por BFG Repo-Cleaner , que se pueden ejecutar muy rápidamente clonando en una gota de Océano Digital utilizando su rendimiento de red de 10MiB / s.

Josh Habdas
fuente
Creo que tiene una buena respuesta general en la sugerencia de BFG, pero la estropea al no dar ningún detalle y luego sugiere utilizar un servicio de terceros diferente (también sin ninguna explicación). ¿Puedes limpiar esto para proporcionar un ejemplo de línea de comandos de este uso de BFG?
Phord
0

Me topé con esto por la misma razón que cualquier otra persona. Pero los guiones citados no funcionaron para mí. Hice uno que es más un híbrido de los que he visto y ahora vive aquí: https://gitlab.com/inorton/git-size-calc

IanNorton
fuente