Quale commit ha questo blob?

Dato l’hash di un blob, c’è un modo per ottenere un elenco di commit che hanno questo blob nel loro albero?

Entrambi i seguenti script prendono SHA1 del blob come primo argomento, e dopo di esso, opzionalmente, qualsiasi argomento che il git log comprenderà. Ad esempio: tutto per cercare in tutti i rami invece che solo quello corrente, o -g per cercare nel reflog, o qualsiasi altra cosa vogliate.

Qui è come uno script di shell – breve e dolce, ma lento:

 #!/bin/sh obj_name="$1" shift git log "[email protected]" --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 

E una versione ottimizzata in Perl, ancora piuttosto breve ma molto più veloce:

 #!/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  []\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 ); } 

Purtroppo le sceneggiature sono state un po ‘lente per me, quindi ho dovuto ottimizzare un po’. Fortunatamente non avevo solo l’hash ma anche il percorso di un file.

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

Ho pensato che sarebbe stata una cosa utile in generale, quindi ho scritto un piccolo script perl per farlo:

 #!/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 ([email protected]) { 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); } 

Questa sera metterò su Github quando torno a casa.

Aggiornamento: sembra che qualcuno l’abbia già fatto . Quello usa la stessa idea generale ma i dettagli sono diversi e l’implementazione è molto più breve. Non so quale sarebbe più veloce ma le prestazioni non sono probabilmente una preoccupazione qui!

Aggiornamento 2: per quello che vale, la mia implementazione è più veloce di ordini di grandezza, soprattutto per un repository di grandi dimensioni. Quel git ls-tree -r fa davvero male.

Aggiornamento 3: Devo notare che i miei commenti sulle prestazioni sopra riportati si applicano all’implementazione che ho collegato sopra nel primo aggiornamento. L’implementazione di Aristotele si comporta in modo simile al mio. Maggiori dettagli nei commenti per coloro che sono curiosi.

Mentre la domanda originale non lo richiede, penso che sia utile anche controllare l’area di staging per vedere se un blob è referenziato. Ho modificato lo script bash originale per fare ciò e ho trovato cosa stava facendo riferimento a un blob corrotto nel mio 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 "[email protected]" --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 

Ecco i dettagli di una sceneggiatura che ho lucidato come risposta a una domanda simile , e qui puoi vederla in azione:

screenshot di git-ls-dir corre http://sofit.miximages.com/git/git-ls-dir.png

Quindi … Avevo bisogno di trovare tutti i file oltre un determinato limite in un repository di dimensioni superiori a 8 GB, con oltre 108.000 revisioni. Ho adattato il perl script di Aristotele insieme a uno script ruby che ho scritto per raggiungere questa soluzione completa.

Innanzitutto, git gc : fai questo per assicurarti che tutti gli oggetti siano nei file pack: non eseguiamo la scansione degli oggetti non nei file pack.

Successivo Eseguire questo script per individuare tutti i BLOB su byte CUTOFF_SIZE. Cattura l’output in un file come “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 

Successivamente, modifica il file per rimuovere eventuali blob che non aspetti ei bit INPUT_THREAD in alto. una volta che hai solo linee per gli sha1 che vuoi trovare, esegui il seguente script in questo modo:

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

Dove lo script git-find-blob è sotto.

 #!/usr/bin/perl # taken from: http://stackoverflow.com/questions/223678/which-commit-has-this-blob # and modified by Carl Myers  to scan multiple blobs at once # Also, modified to keep the discovered 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   ... -- []\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; } 

L’output sarà simile a questo:

   path/to/file.txt path/to/file2.txt ...   

E così via. Verranno elencati tutti i commit che contengono un grande file nella sua struttura. se si grep fuori le righe che iniziano con una scheda, e uniq che, si avrà un elenco di tutti i percorsi che è ansible filtrare-ramo per rimuovere, o si può fare qualcosa di più complicato.

Vorrei ribadire: questo processo è andato a buon fine, su un repo da 10 GB con 108.000 commit. Ci sono voluti molto più tempo di quanto avevo previsto quando si eseguiva un gran numero di BLOB, però, per oltre 10 ore, dovrò vedere se il bit di memoria sta funzionando …

Dato l’hash di un blob, c’è un modo per ottenere un elenco di commit che hanno questo blob nel loro albero?

Con Git 2.16 (Q1 2018), git describe sarebbe una buona soluzione, dal momento che è stato insegnato a scavare più in profondità per trovare un : che si riferisce a un dato object blob.

Vedi commit 644eb60 , commit 4dbc59a , commit cdaed0c , commit c87b653 , commit ce5b6f9 (16 nov 2017) e commit 91904f5 , commit 2deda00 (02 nov 2017) di Stefan Beller ( stefanbeller ) .
(Fuso da Junio ​​C Hamano – gitster – in commit 556de1a , 28 dic 2017)

builtin/describe.c : descrive un blob

A volte gli utenti ricevono un hash di un object e vogliono identificarlo ulteriormente (es .: Usa verify-pack per trovare i blob più grandi, ma cosa sono questi? O questa stessa domanda SO ” Quale commit ha questo blob? “)

Quando descriviamo commit, proviamo ad ancorarli a tag o refs, poiché questi sono concettualmente su un livello superiore rispetto al commit. E se non ci sono riferimenti o tag che corrispondono esattamente, siamo sfortunati.
Quindi impieghiamo un’euristica per creare un nome per il commit. Questi nomi sono ambigui, potrebbero esserci tag o riferimenti diversi da ancorare a, e potrebbe esserci un percorso diverso nel DAG per viaggiare per arrivare esattamente al commit.

Quando descriviamo un blob, vogliamo descrivere anche il blob da un livello superiore, che è una tupla di (commit, deep/path) poiché gli oggetti dell’albero coinvolti sono piuttosto poco interessanti.
Lo stesso blob può essere referenziato da più commit, quindi come decidiamo quale commit utilizzare?

Questa patch implementa un approccio piuttosto ingenuo su questo: dato che non ci sono back pointers da blob a commit in cui avviene il blob, inizieremo a camminare da eventuali suggerimenti disponibili, elencando i BLOB in ordine del commit e una volta trovato il blob blob, prenderemo il primo commit che ha elencato il blob.

Per esempio:

 git describe --tags v0.99:Makefile conversion-901-g7672db20c2:Makefile 

ci dice che il Makefile com’era nella v0.99 stato introdotto nel commit 7672db2 .

La camminata viene eseguita in ordine inverso per mostrare l’introduzione di un blob piuttosto che la sua ultima occorrenza.

Ciò significa che la git describe pagina man aggiunta agli scopi di questo comando:
Invece di descrivere semplicemente un commit usando il tag più recente raggiungibile da esso, git describe darà effettivamente a un object un nome leggibile dall’uomo basato su un riferimento disponibile quando usato come git describe .

Se l’object indicato fa riferimento a un blob, verrà descritto come : , in modo tale che il blob possa essere trovato in in , che a sua volta descrive il primo commit in che questo blob si verifica in una revisione inversa a piedi da HEAD.

Ma:

BUGS

Gli oggetti dell’albero e gli oggetti tag che non puntano ai commit non possono essere descritti .
Quando si descrivono i BLOB, i tag leggeri che puntano sui BLOB vengono ignorati, ma il BLOB è ancora descritto come : nonostante il tag leggero sia favorevole.

Oltre a git describe , che menziono nella mia risposta precedente , git log e git diff ora beneficiano anche --find-object=--find-object= ” per limitare i risultati alle modifiche che coinvolgono l’object denominato .
Questo è in Git 2.16.x / 2.17 (Q1 2018)

Vedi commit 4d8c51a , commit 5e50525 , commit 15af58c , commit cf63051 , commit c1ddc46 , commit 929ed70 (04 gen 2018) di Stefan Beller ( stefanbeller ) .
(Fuso da Junio ​​C Hamano – gitster – in commit c0d75f0 , 23 gen 2018)

diffcore : aggiungi un’opzione diffcore per trovare un blob specifico

A volte gli utenti ricevono un hash di un object e vogliono identificarlo ulteriormente (es .: Usa verify-pack per trovare i blob più grandi, ma che cosa sono questi? O questa domanda di Overflow dello stack ” Quale commit ha questo blob? “)

Si potrebbe essere tentati di estendere git-describe anche a funzionare con blob, in modo che git describe dia una descrizione come ‘:’.
Questo è stato implementato qui ; visto dal numero di risposte (> 110), risulta che questo è difficile da ottenere.
La parte difficile da ottenere è scegliere il commit-ish corretto in quanto potrebbe essere il commit che ha (re) introdotto il blob o il blob che ha rimosso il blob; il blob potrebbe esistere in diversi rami.

Junio ​​ha suggerito un approccio diverso per risolvere questo problema, che questa patch implementa.
Insegna alla macchina diff un’altra bandiera per limitare l’informazione a ciò che viene mostrato.
Per esempio:

 $ ./git log --oneline --find-object=v2.0.0:Makefile b2feb64 Revert the whole "ask curl-config" topic for now 47fbfde i18n: only extract comments marked with "TRANSLATORS:" 

osserviamo che il Makefile fornito con 2.0 è apparso in v1.9.2-471-g47fbfded53 e in v2.0.0-rc1-5-gb2feb6430b .
Il motivo per cui questi commit si verificano entrambi prima della v2.0.0 sono le unioni malvagie che non si trovano usando questo nuovo meccanismo.