repositorio - git push tag
Encuentra archivos en git repo sobre x megabytes, que no existen en HEAD (10)
Tengo un repositorio de Git en el que almaceno cosas aleatorias. Principalmente scripts aleatorios, archivos de texto, sitios web que he diseñado, etc.
Hay algunos archivos binarios grandes que he eliminado a lo largo del tiempo (generalmente de 1 a 5 MB), que están disponibles para aumentar el tamaño del repositorio, 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
)
Desea utilizar BFG Repo-Cleaner , una alternativa más rápida y sencilla a git-filter-branch
específicamente diseñada para eliminar archivos grandes de repositorios Git.
Descargue el jar BFG (requiere Java 6 o superior) y ejecute este comando:
$ java -jar bfg.jar --strip-blobs-bigger-than 1M my-repo.git
Cualquier archivo de más de 1 M de tamaño (que no esté en su última confirmación) se eliminará del historial de su repositorio 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 contraseñas, credenciales y otros datos privados
Descripción completa: soy el autor de BFG Repo-Cleaner.
Ouch ... ese primer guión (por Aristóteles), es bastante lento. En el repositorio git.git, buscando archivos> 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 archivo mencionado en la siguiente línea.
Aquí hay una versión más rápida. El formato de salida es diferente, pero es muy rápido y, por lo que yo sé, también es correcto.
El programa 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";
El guión de Aristote te mostrará lo que quieres. También necesita saber que los archivos eliminados seguirán ocupando espacio en el repositorio.
De forma predeterminada, Git mantiene los cambios durante alrededor 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, archivos de texto, sitios web" y archivos binarios. Git realiza un seguimiento de los cambios en el contenido, en particular el historial de cambios coordinados entre muchos archivos de texto, y lo hace de manera muy eficiente y efectiva. Como su pregunta lo ilustra, Git no tiene buenas herramientas para rastrear cambios de archivos individuales. Y no rastrea los cambios en los binarios, por lo que cualquier revisión almacena otra copia completa en el repositorio.
Por supuesto, este uso de Git es una manera perfectamente buena de familiarizarse con la forma en que funciona.
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.
#!/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
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";
}
}
Mi simplificación de python de https://.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
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
Este bash "one-liner" muestra todos los objetos blob en el repositorio que tienen más de 10 MiB y no están presentes en HEAD
ordenados de menor a mayor.
Es muy rápido , fácil de copiar 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 obtener más información, incluido un formato de salida más adecuado para el procesamiento adicional de scripts, vea mi respuesta original en una pregunta similar.
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)