¿Qué cometido tiene este blob?

Dado el hash de un blob, ¿hay alguna manera de get una list de confirmaciones que tengan este blob en su tree?

Los dos scripts siguientes toman el SHA1 del blob como primer argumento, y después de éste, opcionalmente, cualquier argumento que git log comprenda. Por ejemplo: todo para search en todas las twigs en lugar de solo en la actual, o -g para search en el reflog, o cualquier otra cosa que te apetezca.

Aquí está como un guión de shell: corto y dulce, pero lento:

 #!/bin/sh obj_name="$1" shift git log "$@" --pretty=format:'%T %h %s' \ | while read tree commit subject ; do if git ls-tree -r $tree | grep -q "$obj_name" ; then echo $commit "$subject" fi done 

Y una versión optimizada en Perl, aún bastante corta pero mucho más rápida:

 #!/usr/bin/perl use 5.008; use strict; use Memoize; my $obj_name; sub check_tree { my ( $tree ) = @_; my @subtree; { open my $ls_tree, '-|', git => 'ls-tree' => $tree or die "Couldn't open pipe to git-ls-tree: $!\n"; while ( <$ls_tree> ) { /\A[0-7]{6} (\S+) (\S+)/ or die "unexpected git-ls-tree output"; return 1 if $2 eq $obj_name; push @subtree, $2 if $1 eq 'tree'; } } check_tree( $_ ) && return 1 for @subtree; return; } memoize 'check_tree'; die "usage: git-find-blob <blob> [<git-log arguments ...>]\n" if not @ARGV; my $obj_short = shift @ARGV; $obj_name = do { local $ENV{'OBJ_NAME'} = $obj_short; `git rev-parse --verify \$OBJ_NAME`; } or die "Couldn't parse $obj_short: $!\n"; chomp $obj_name; open my $log, '-|', git => log => @ARGV, '--pretty=format:%T %h %s' or die "Couldn't open pipe to git-log: $!\n"; while ( <$log> ) { chomp; my ( $tree, $commit, $subject ) = split " ", $_, 3; print "$commit $subject\n" if check_tree( $tree ); } 

Lamentablemente, las secuencias de commands fueron un poco lentas para mí, así que tuve que optimizar un poco. Afortunadamente, no solo tenía el hash sino también la ruta de un file.

 git log --all --pretty=format:%H <path> | xargs -n1 -I% sh -c "git ls-tree % <path> | grep -q <hash> && echo %" 

Pensé que esto sería generalmente útil, así que escribí un pequeño script de perl para hacerlo:

 #!/usr/bin/perl -w use strict; my @commits; my %trees; my $blob; sub blob_in_tree { my $tree = $_[0]; if (defined $trees{$tree}) { return $trees{$tree}; } my $r = 0; open(my $f, "git cat-file -p $tree|") or die $!; while (<$f>) { if (/^\d+ blob (\w+)/ && $1 eq $blob) { $r = 1; } elsif (/^\d+ tree (\w+)/) { $r = blob_in_tree($1); } last if $r; } close($f); $trees{$tree} = $r; return $r; } sub handle_commit { my $commit = $_[0]; open(my $f, "git cat-file commit $commit|") or die $!; my $tree = <$f>; die unless $tree =~ /^tree (\w+)$/; if (blob_in_tree($1)) { print "$commit\n"; } while (1) { my $parent = <$f>; last unless $parent =~ /^parent (\w+)$/; push @commits, $1; } close($f); } if (!@ARGV) { print STDERR "Usage: git-find-blob blob [head ...]\n"; exit 1; } $blob = $ARGV[0]; if (@ARGV > 1) { foreach (@ARGV) { handle_commit($_); } } else { handle_commit("HEAD"); } while (@commits) { handle_commit(pop @commits); } 

Pondré esto en Github cuando llegue a casa esta noche.

Actualización: Parece que alguien ya hizo esto . Ese usa la misma idea general pero los detalles son diferentes y la implementación es mucho más corta. No sé cuál sería más rápido, ¡pero el performance probablemente no sea una preocupación aquí!

Actualización 2: Por lo que vale, mi implementación es mucho más rápida, especialmente para un gran repository. Ese git ls-tree -r realmente duele.

Actualización 3: debo señalar que mis comentarios de performance anteriores se aplican a la implementación que he vinculado anteriormente en la primera Actualización. La implementación de Aristóteles funciona de manera comparable a la mía. Más detalles en los comentarios para aquellos que son curiosos.

Si bien la pregunta original no lo solicita, creo que es útil también verificar el área de ensayo para ver si se hace reference a un blob. Modifiqué el script bash original para hacer esto y encontré lo que hacía reference a un blob corrupto en mi repository:

 #!/bin/sh obj_name="$1" shift git ls-files --stage \ | if grep -q "$obj_name"; then echo Found in staging area. Run git ls-files --stage to see. fi git log "$@" --pretty=format:'%T %h %s' \ | while read tree commit subject ; do if git ls-tree -r $tree | grep -q "$obj_name" ; then echo $commit "$subject" fi done 

Estos son los detalles de un guión que pulí como la respuesta a una pregunta similar , y aquí puedes verlo en acción:

La captura de pantalla de git-ls-dir se ejecuta en http://adamspiers.org/computing/git-ls-dir.png

Entonces … Necesitaba encontrar todos los files por encima de un límite determinado en un repository de más de 8GB de tamaño, con más de 108,000 revisiones. Adapte el script de perl de Aristóteles junto con un script de Ruby que escribí para llegar a esta solución completa.

En primer lugar, git gc : haga esto para asegurarse de que todos los objects se encuentren en files de package; no escaneamos objects que no estén en los files del package.

Siguiente Ejecute este script para ubicar todos los blobs en CUTOFF_SIZE bytes. Capture la salida en un file como "large-blobs.log"

 #!/usr/bin/env ruby require 'log4r' # The output of git verify-pack -v is: # SHA1 type size size-in-packfile offset-in-packfile depth base-SHA1 # # GIT_PACKS_RELATIVE_PATH=File.join('.git', 'objects', 'pack', '*.pack') # 10MB cutoff CUTOFF_SIZE=1024*1024*10 #CUTOFF_SIZE=1024 begin include Log4r log = Logger.new 'git-find-large-objects' log.level = INFO log.outputters = Outputter.stdout git_dir = %x[ git rev-parse --show-toplevel ].chomp if git_dir.empty? log.fatal "ERROR: must be run in a git repository" exit 1 end log.debug "Git Dir: '#{git_dir}'" pack_files = Dir[File.join(git_dir, GIT_PACKS_RELATIVE_PATH)] log.debug "Git Packs: #{pack_files.to_s}" # For details on this IO, see http://stackoverflow.com/questions/1154846/continuously-read-from-stdout-of-external-process-in-ruby # # Short version is, git verify-pack flushes buffers only on line endings, so # this works, if it didn't, then we could get partial lines and be sad. types = { :blob => 1, :tree => 1, :commit => 1, } total_count = 0 counted_objects = 0 large_objects = [] IO.popen("git verify-pack -v -- #{pack_files.join(" ")}") do |pipe| pipe.each do |line| # The output of git verify-pack -v is: # SHA1 type size size-in-packfile offset-in-packfile depth base-SHA1 data = line.chomp.split(' ') # types are blob, tree, or commit # we ignore other lines by looking for that next unless types[data[1].to_sym] == 1 log.info "INPUT_THREAD: Processing object #{data[0]} type #{data[1]} size #{data[2]}" hash = { :sha1 => data[0], :type => data[1], :size => data[2].to_i, } total_count += hash[:size] counted_objects += 1 if hash[:size] > CUTOFF_SIZE large_objects.push hash end end end log.info "Input complete" log.info "Counted #{counted_objects} totalling #{total_count} bytes." log.info "Sorting" large_objects.sort! { |a,b| b[:size] <=> a[:size] } log.info "Sorting complete" large_objects.each do |obj| log.info "#{obj[:sha1]} #{obj[:type]} #{obj[:size]}" end exit 0 end 

A continuación, edite el file para eliminar cualquier blobs que no espere y los bits INPUT_THREAD en la parte superior. una vez que tenga solo líneas para los sha1s que desea encontrar, ejecute el siguiente script de esta manera:

 cat edited-large-files.log | cut -d' ' -f4 | xargs git-find-blob | tee large-file-paths.log 

Donde el script git-find-blob está debajo.

 #!/usr/bin/perl # taken from: http://stackoverflow.com/questions/223678/which-commit-has-this-blob # and modified by Carl Myers <cmyers@cmyers.org> to scan multiple blobs at once # Also, modified to keep the discovenetworking filenames # vi: ft=perl use 5.008; use strict; use Memoize; use Data::Dumper; my $BLOBS = {}; MAIN: { memoize 'check_tree'; die "usage: git-find-blob <blob1> <blob2> ... -- [<git-log arguments ...>]\n" if not @ARGV; while ( @ARGV && $ARGV[0] ne '--' ) { my $arg = $ARGV[0]; #print "Processing argument $arg\n"; open my $rev_parse, '-|', git => 'rev-parse' => '--verify', $arg or die "Couldn't open pipe to git-rev-parse: $!\n"; my $obj_name = <$rev_parse>; close $rev_parse or die "Couldn't expand passed blob.\n"; chomp $obj_name; #$obj_name eq $ARGV[0] or print "($ARGV[0] expands to $obj_name)\n"; print "($arg expands to $obj_name)\n"; $BLOBS->{$obj_name} = $arg; shift @ARGV; } shift @ARGV; # drop the -- if present #print "BLOBS: " . Dumper($BLOBS) . "\n"; foreach my $blob ( keys %{$BLOBS} ) { #print "Printing results for blob $blob:\n"; open my $log, '-|', git => log => @ARGV, '--pretty=format:%T %h %s' or die "Couldn't open pipe to git-log: $!\n"; while ( <$log> ) { chomp; my ( $tree, $commit, $subject ) = split " ", $_, 3; #print "Checking tree $tree\n"; my $results = check_tree( $tree ); #print "RESULTS: " . Dumper($results); if (%{$results}) { print "$commit $subject\n"; foreach my $blob ( keys %{$results} ) { print "\t" . (join ", ", @{$results->{$blob}}) . "\n"; } } } } } sub check_tree { my ( $tree ) = @_; #print "Calculating hits for tree $tree\n"; my @subtree; # results = { BLOB => [ FILENAME1 ] } my $results = {}; { open my $ls_tree, '-|', git => 'ls-tree' => $tree or die "Couldn't open pipe to git-ls-tree: $!\n"; # example git ls-tree output: # 100644 blob 15d408e386400ee58e8695417fbe0f858f3ed424 filaname.txt while ( <$ls_tree> ) { /\A[0-7]{6} (\S+) (\S+)\s+(.*)/ or die "unexpected git-ls-tree output"; #print "Scanning line '$_' tree $2 file $3\n"; foreach my $blob ( keys %{$BLOBS} ) { if ( $2 eq $blob ) { print "Found $blob in $tree:$3\n"; push @{$results->{$blob}}, $3; } } push @subtree, [$2, $3] if $1 eq 'tree'; } } foreach my $st ( @subtree ) { # $st->[0] is tree, $st->[1] is dirname my $st_result = check_tree( $st->[0] ); foreach my $blob ( keys %{$st_result} ) { foreach my $filename ( @{$st_result->{$blob}} ) { my $path = $st->[1] . '/' . $filename; #print "Generating subdir path $path\n"; push @{$results->{$blob}}, $path; } } } #print "Returning results for tree $tree: " . Dumper($results) . "\n\n"; return $results; } 

La salida se verá así:

 <hash prefix> <oneline log message> path/to/file.txt path/to/file2.txt ... <hash prefix2> <oneline log msg...> 

Y así. Se enumerará cada confirmación que contenga un file grande en su tree. si grep las líneas que comienzan con una pestaña y uniq eso, tendrá una list de todas las routes que puede filtrar-branch para eliminar, o puede hacer algo más complicado.

Permítanme reiterar: este process se ejecutó con éxito, en un repository de 10 GB con 108,000 confirmaciones. Tomó mucho más time de lo que pnetworkingije cuando corría en una gran cantidad de blobs, sin embargo, durante 10 horas, tendré que ver si el bit de memorización está funcionando …