Qual commit tem esse blob?

Dado o hash de um blob, existe uma maneira de obter uma lista de commits com esse blob em sua tree?

Ambos os scripts a seguir tomam o SHA1 do blob como o primeiro argumento, e depois dele, opcionalmente, qualquer argumento que o git log entenderá. Por exemplo --all para procurar em todos os ramos em vez de apenas o atual, ou -g para pesquisar no reflog, ou o que mais você gosta.

Aqui está como um script de shell – curto e doce, mas 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 

E uma versão otimizada em Perl, ainda bem curta, mas muito mais 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  []\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 ); } 

Infelizmente os scripts foram um pouco lentos para mim, então eu tive que otimizar um pouco. Felizmente eu não tinha apenas o hash, mas também o caminho de um arquivo.

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

Eu pensei que isso seria uma coisa geralmente útil, então escrevi um pequeno script perl para fazer isso:

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

Vou colocar isso no github quando chegar em casa esta noite.

Atualização: parece que alguém já fez isso . Aquele usa a mesma ideia geral, mas os detalhes são diferentes e a implementação é muito mais curta. Eu não sei o que seria mais rápido, mas o desempenho provavelmente não é uma preocupação aqui!

Atualização 2: Por que vale a pena, minha implementação é muito mais rápida, especialmente para um grande repository. Aquele git ls-tree -r realmente dói.

Atualização 3: devo observar que meus comentários de desempenho acima se aplicam à implementação que eu relacionei acima na primeira atualização. A implementação de Aristóteles tem um desempenho comparável ao meu. Mais detalhes nos comentários para quem está curioso.

Enquanto a pergunta original não pede, eu acho que é útil verificar também a área de teste para ver se um blob é referenciado. Eu modifiquei o script bash original para fazer isso e descobri o que estava referenciando um blob corrompido em meu 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 

Aqui estão os detalhes de um script que eu elaborei como a resposta para uma pergunta semelhante , e aqui você pode vê-lo em ação:

screenshot do git-ls-dir é executado em http://sofpt.miximages.com/git/git-ls-dir.png

Então, eu precisava encontrar todos os arquivos em um limite de 8GB, com mais de 108.000 revisões. Eu adaptei o script perl de Aristóteles junto com um script ruby ​​que escrevi para chegar a essa solução completa.

Primeiro, git gc – faça isso para garantir que todos os objects estejam em arquivos de pacotes – não varremos objects que não estão em arquivos de pacotes.

Próximo Execute este script para localizar todos os blobs em bytes CUTOFF_SIZE. Capture a saída para um arquivo 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 

Em seguida, edite o arquivo para remover os blobs que você não espera e os bits INPUT_THREAD no topo. uma vez que você tenha apenas linhas para os sha1s que deseja encontrar, execute o seguinte script como este:

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

Onde o script git-find-blob está abaixo.

 #!/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; } 

A saída ficará assim:

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

E assim por diante. Todo commit que contém um arquivo grande em sua tree será listado. Se você grep as linhas que começam com uma aba e uniq isso, você terá uma lista de todos os caminhos que você pode filtrar-ramificar para remover, ou você pode fazer algo mais complicado.

Deixe-me reiterar: esse processo foi executado com sucesso, em um repository de 10 GB com 108.000 confirmações. Demorou muito mais do que eu previa quando estava correndo em um grande número de bolhas, mais de 10 horas, eu vou ter que ver se o bit de memorização está funcionando …

Dado o hash de um blob, existe uma maneira de obter uma lista de commits com esse blob em sua tree?

Com o Git 2.16 (Q1 2018), o git describe seria uma boa solução, já que foi ensinado a cavar trees mais fundo para encontrar um : que se refere a um dado object blob.

Veja commit 644eb60 , commit 4dbc59a , commited cdaed0c , commitc87b653 , commit ce5b6f9 (16 Nov 2017), e commit 91904f5 , commit 2deda00 (02 Nov 2017) por Stefan Beller ( stefanbeller ) .
(Mesclado por Junio ​​C Hamano – gitster – em commit 556de1a , 28 de dez de 2017)

builtin/describe.c : descreve um blob

Às vezes, os usuários recebem um hash de um object e querem identificá-lo ainda mais (ex .: Use o verify-pack para localizar os maiores blobs, mas o que são estes? Ou esta pergunta SO ” Qual commit possui esse blob? “)

Ao descrever confirmações, tentamos ancorá-las em tags ou refs, pois elas estão conceitualmente em um nível superior ao commit. E se não houver ref ou tag que corresponda exatamente, estamos sem sorte.
Por isso, empregamos uma heurística para criar um nome para o commit. Esses nomes são ambíguos, pode haver tags ou referências diferentes para ancorar e pode haver um caminho diferente no DAG para viajar para chegar ao commit com precisão.

Ao descrever um blob, queremos também descrever o blob de uma camada superior, que é uma tupla de (commit, deep/path) pois os objects da tree envolvidos são bastante desinteressantes.
O mesmo blob pode ser referenciado por múltiplos commits, então como nós decidimos qual commit usar?

Este patch implementa uma abordagem bastante ingênua sobre isso: Como não há pointers de blobs a commits nos quais o blob ocorre, vamos começar a partir de qualquer dica disponível, listando os blobs na ordem do commit e assim que encontrarmos o blob, vamos dar o primeiro commit que listou o blob.

Por exemplo:

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

nos diz que o Makefile como estava na v0.99 foi introduzido no commit 7672db2 .

A caminhada é realizada em ordem inversa para mostrar a introdução de um blob em vez de sua última ocorrência.

Isso significa que a página man git describe man é adicionada aos propósitos deste comando:
Em vez de simplesmente descrever um commit usando a tag mais recente que pode ser acessada a partir dele, o git describe dará um nome legível a um object baseado em uma referência disponível quando usado como git describe .

Se o object dado se referir a um blob, ele será descrito como : , de modo que o blob possa ser encontrado em no , que descreve o primeiro commit em em que esse blob ocorre em uma revisão reversa da HEAD.

Mas:

INSETOS

Objetos de tree, bem como objects de tag que não apontam para commits, não podem ser descritos .
Ao descrever blobs, os tags leves apontando para blobs são ignorados, mas o blob ainda é descrito como : apesar da tag light ser favorável.

Além do git describe , que mencionei na minha resposta anterior , git log e git diff agora também se beneficiam da opção ” --find-object= ” para limitar as descobertas a mudanças que envolvem o object nomeado .
Isso está no Git 2.16.x / 2.17 (Q1 2018)

Veja commit 4d8c51a , commit 5e50525 , commit 15af58c , commit cf63051 , commit c1ddc46 , commit 929ed70 (04 Jan 2018) por Stefan Beller ( stefanbeller ) .
(Mesclado por Junio ​​C Hamano – gitster – em commit c0d75f0 , 23 jan 2018)

diffcore : adicione uma opção diffcore para encontrar um blob específico

Às vezes, os usuários recebem um hash de um object e querem identificá-lo ainda mais (ex .: usar o pacote de verificação para localizar os maiores blobs, mas o que são esses? Ou essa pergunta de estouro de pilha ” Qual confirmação tem esse blob? “)

Pode-se ficar tentado a estender o git-describe para também trabalhar com blobs, de tal forma que git describe fornece uma descrição como ‘:’.
Isso foi implementado aqui ; como visto pelo grande número de respostas (> 110), acontece que é difícil acertar.
A parte difícil de acertar é escolher o ‘commit-ish’ correto, já que pode ser o commit que (re) introduziu o blob ou o blob que removeu o blob; o blob poderia existir em diferentes ramos.

Junio ​​sugeriu uma abordagem diferente para resolver esse problema, implementado por esse patch.
Ensine a máquina de diff outra bandeira para restringir a informação ao que é mostrado.
Por exemplo:

 $ ./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:" 

Observamos que o Makefile como fornecido com o 2.0 foi publicado no v1.9.2-471-g47fbfded53 e no v2.0.0-rc1-5-gb2feb6430b .
A razão pela qual esses commits ocorrem antes da v2.0.0 são fusões malignas que não são encontradas usando esse novo mecanismo.