Encuentra files en git repo sobre x megabytes, que no existen en HEAD

Tengo un repository de Git en el que almaceno cosas aleatorias. Principalmente scripts aleatorios, files de text, sitios web que he diseñado, etc.

Hay algunos files binarys grandes que he eliminado a lo largo del time (generalmente de 1 a 5 MB), que están disponibles para boost el tamaño del repository, que no necesito en el historial de revisiones.

Básicamente quiero poder hacer …

me@host:~$ [magic command or script] aad29819a908cc1c05c3b1102862746ba29bafc0 : example/blah.psd : 3.8MB : 130 days old 6e73ca29c379b71b4ff8c6b6a5df9c7f0f1f5627 : another/big.file : 1.12MB : 214 days old 

..entonces podrá ir a través de cada resultado, verificando si ya no es necesario y luego retirándolo (probablemente usando filter-branch )

Solutions Collecting From Web of "Encuentra files en git repo sobre x megabytes, que no existen en HEAD"

Esta es una adaptación del script git-find-blob que publiqué anteriormente :

 #!/usr/bin/perl use 5.008; use strict; use Memoize; sub usage { die "usage: git-large-blob <size[b|k|m]> [<git-log arguments ...>]\n" } @ARGV or usage(); my ( $max_size, $unit ) = ( shift =~ /^(\d+)([bkm]?)\z/ ) ? ( $1, $2 ) : usage(); my $exp = 10 * ( $unit eq 'b' ? 0 : $unit eq 'k' ? 1 : 2 ); my $cutoff = $max_size * 2**$exp; sub walk_tree { my ( $tree, @path ) = @_; my @subtree; my @r; { open my $ls_tree, '-|', git => 'ls-tree' => -l => $tree or die "Couldn't open pipe to git-ls-tree: $!\n"; while ( <$ls_tree> ) { my ( $type, $sha1, $size, $name ) = /\A[0-7]{6} (\S+) (\S+) +(\S+)\t(.*)/; if ( $type eq 'tree' ) { push @subtree, [ $sha1, $name ]; } elsif ( $type eq 'blob' and $size >= $cutoff ) { push @r, [ $size, @path, $name ]; } } } push @r, walk_tree( $_->[0], @path, $_->[1] ) for @subtree; return @r; } memoize 'walk_tree'; open my $log, '-|', git => log => @ARGV, '--pretty=format:%T %h %cr' or die "Couldn't open pipe to git-log: $!\n"; my %seen; while ( <$log> ) { chomp; my ( $tree, $commit, $age ) = split " ", $_, 3; my $is_header_printed; for ( walk_tree( $tree ) ) { my ( $size, @path ) = @$_; my $path = join '/', @path; next if $seen{ $path }++; print "$commit $age\n" if not $is_header_printed++; print "\t$size\t$path\n"; } } 

Script de ruby ​​más compacto:

 #!/usr/bin/env ruby -w head, treshold = ARGV head ||= 'HEAD' Megabyte = 1000 ** 2 treshold = (treshold || 0.1).to_f * Megabyte big_files = {} IO.popen("git rev-list #{head}", 'r') do |rev_list| rev_list.each_line do |commit| commit.chomp! for object in `git ls-tree -zrl #{commit}`.split("\0") bits, type, sha, size, path = object.split(/\s+/, 5) size = size.to_i big_files[sha] = [path, size, commit] if size >= treshold end end end big_files.each do |sha, (path, size, commit)| where = `git show -s #{commit} --format='%h: %cr'`.chomp puts "%4.1fM\t%s\t(%s)" % [size.to_f / Megabyte, path, where] end 

Uso:

 ruby big_file.rb [rev] [size in MB] $ ruby big_file.rb master 0.3 3.8M example/blah.psd (aad2981: 4 months ago) 1.1M another/big.file (6e73ca2: 2 weeks ago) 

Script de Python para hacer lo mismo (basado en esta publicación ):

 #!/usr/bin/env python import os, sys def getOutput(cmd): return os.popen(cmd).read() if (len(sys.argv) <> 2): print "usage: %s size_in_bytes" % sys.argv[0] else: maxSize = int(sys.argv[1]) revisions = getOutput("git rev-list HEAD").split() bigfiles = set() for revision in revisions: files = getOutput("git ls-tree -zrl %s" % revision).split('\0') for file in files: if file == "": continue splitdata = file.split() commit = splitdata[2] if splitdata[3] == "-": continue size = int(splitdata[3]) path = splitdata[4] if (size > maxSize): bigfiles.add("%10d %s %s" % (size, commit, path)) bigfiles = sorted(bigfiles, reverse=True) for f in bigfiles: print f 

Ouch … ese primer guión (por Aristóteles), es bastante lento. En el repository git.git, buscando files> 100k, mastica la CPU por unos 6 minutos.

También parece tener varios SHA incorrectos impresos; a menudo se imprimirá un SHA que no tiene nada que ver con el nombre de file mencionado en la siguiente línea.

Aquí hay una versión más rápida. El formatting de salida es diferente, pero es muy rápido y, por lo que yo sé, también es correcto.

El progtwig es un poco más largo, pero mucho es verborrea.

 #!/usr/bin/perl use 5.10.0; use strict; use warnings; use File::Temp qw(tempdir); END { chdir( $ENV{HOME} ); } my $tempdir = tempdir( "git-files_tempdir.XXXXXXXXXX", TMPDIR => 1, CLEANUP => 1 ); my $min = shift; $min =~ /^\d+$/ or die "need a number"; # ---------------------------------------------------------------------- my @refs =qw(HEAD); @refs = @ARGV if @ARGV; # first, find blob SHAs and names (no sizes here) open( my $objects, "-|", "git", "rev-list", "--objects", @refs) or die "rev-list: $!"; open( my $blobfile, ">", "$tempdir/blobs" ) or die "blobs out: $!"; my ( $blob, $name ); my %name; my %size; while (<$objects>) { next unless / ./; # no commits or top level trees ( $blob, $name ) = split; $name{$blob} = $name; say $blobfile $blob; } close($blobfile); # next, use cat-file --batch-check on the blob SHAs to get sizes open( my $sizes, "-|", "< $tempdir/blobs git cat-file --batch-check | grep blob" ) or die "cat-file: $!"; my ( $dummy, $size ); while (<$sizes>) { ( $blob, $dummy, $size ) = split; next if $size < $min; $size{ $name{$blob} } = $size if ( $size{ $name{$blob} } || 0 ) < $size; } my @names_by_size = sort { $size{$b} <=> $size{$a} } keys %size; say " The size shown is the largest that file has ever attained. But note that it may not be that big at the commit shown, which is merely the most recent commit affecting that file. "; # finally, for each name being printed, find when it was last updated on each # branch that we're concerned about and print stuff out for my $name (@names_by_size) { say "$size{$name}\t$name"; for my $r (@refs) { system("git --no-pager log -1 --format='%x09%h%x09%x09%ar%x09$r' $r -- $name"); } print "\n"; } print "\n"; 

Desea utilizar BFG Repo-Cleaner , una alternativa más rápida y sencilla a git-filter-branch específicamente diseñada para eliminar files grandes de repositorys Git.

Descargue el jar BFG (requiere Java 6 o superior) y ejecute este command:

 $ java -jar bfg.jar --strip-blobs-bigger-than 1M my-repo.git 

Cualquier file de más de 1 M de tamaño (que no esté en su última confirmación) se eliminará del historial de su repository Git. A continuación, puede usar git gc para limpiar los datos muertos:

 $ git gc --prune=now --aggressive 

El BFG suele ser 10-50 veces más rápido que ejecutar git-filter-branch y las opciones se adaptan a estos dos casos de uso comunes:

  • Eliminando Crazy Big Files
  • Eliminar passwords, cnetworkingenciales y otros datos privados

Descripción completa: soy el autor de BFG Repo-Cleaner.

El guión de Aristote te mostrará lo que quieres. También necesita saber que los files eliminados seguirán ocupando espacio en el repository.

De forma pnetworkingeterminada, Git mantiene los cambios durante alnetworkingedor de 30 días antes de que puedan ser recolectados. Si quieres eliminarlos ahora:

 $ git reflog expire --expire=1.minute refs/heads/master # all deletions up to 1 minute ago available to be garbage-collected $ git fsck --unreachable # lists all the blobs(file contents) that will be garbage-collected $ git prune $ git gc 

Un comentario al margen: aunque soy un gran admirador de Git, Git no aporta ninguna ventaja al almacenar su colección de "scripts aleatorios, files de text, sitios web" y files binarys. Git realiza un seguimiento de los cambios en el contenido, en particular el historial de cambios coordinados entre muchos files de text, y lo hace de manera muy eficiente y efectiva. Como su pregunta lo ilustra, Git no tiene buenas herramientas para rastrear cambios de files individuales. Y no rastrea los cambios en los binarys, por lo que cualquier revisión almacena otra copy completa en el repository.

Por supuesto, este uso de Git es una manera perfectamente buena de familiarizarse con la forma en que funciona.

 #!/bin/bash if [ "$#" != 1 ] then echo 'git large.sh [size]' exit fi declare -A big_files big_files=() echo printing results while read commit do while read bits type sha size path do if [ "$size" -gt "$1" ] then big_files[$sha]="$sha $size $path" fi done < <(git ls-tree --abbrev -rl $commit) done < <(git rev-list HEAD) for file in "${big_files[@]}" do read sha size path <<< "$file" if git ls-tree -r HEAD | grep -q $sha then echo $file fi done 

Fuente

Mi simplificación de python de https://stackoverflow.com/a/10099633/131881

 #!/usr/bin/env python import os, sys bigfiles = [] for revision in os.popen('git rev-list HEAD'): for f in os.popen('git ls-tree -zrl %s' % revision).read().split('\0'): if f: mode, type, commit, size, path = f.split(None, 4) if int(size) > int(sys.argv[1]): bigfiles.append((int(size), commit, path)) for f in sorted(set(bigfiles)): print f 

Este bash "one-liner" muestra todos los objects blob en el repository que tienen más de 10 MiB y no están presentes en HEAD orderados de menor a mayor.

Es muy rápido , fácil de copyr y pegar, y solo requiere las utilidades estándar de GNU.

 git rev-list --objects --all \ | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' \ | awk -v min_mb=10 '/^blob/ && $3 >= min_mb*2^20 {print substr($0,6)}' \ | grep -vF "$(git ls-tree -r HEAD | awk '{print $3}')" \ | sort --numeric-sort --key=2 \ | cut --complement --characters=13-40 \ | numfmt --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest 

Esto generará resultados como este:

 2ba44098e28f 12MiB path/to/hires-image.png bd1741ddce0d 63MiB path/to/some-video-1080p.mp4 

Para get más información, incluido un formatting de salida más adecuado para el procesamiento adicional de scripts, vea mi respuesta original en una pregunta similar.

Un poco tarde para la fiesta, pero git-fat tiene esta funcionalidad incorporada.

Simplemente instálalo con pip y ejecuta git fat -a find 100000 donde el número al final está en Bytes.