Quel commit a ce blob?

Étant donné le hash d’un blob, y a-t-il un moyen d’obtenir une liste des commits qui ont ce blob dans leur arbre?

Les deux scripts suivants prennent le blob SHA1 comme premier argument, et ensuite, éventuellement, tous les arguments que git log comprendra. Par exemple --all pour rechercher dans toutes les twigs au lieu de la seule en cours, ou -g pour rechercher dans le renvoi, ou ce que vous voulez.

Ici, c’est comme un script shell – court et doux, mais lent:

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

Et une version optimisée en Perl, encore assez courte mais beaucoup plus rapide:

 #!/usr/bin/perl use 5.008; use ssortingct; 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 ); } 

Malheureusement, les scripts étaient un peu lents pour moi, alors j’ai dû optimiser un peu. Heureusement, j’avais non seulement le hash mais aussi le chemin d’un fichier.

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

Je pensais que ce serait une chose généralement utile à avoir, alors j’ai écrit un petit script Perl pour le faire:

 #!/usr/bin/perl -w use ssortingct; 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); } 

Je vais mettre ça sur github quand je rentre à la maison ce soir.

Mise à jour: On dirait que quelqu’un l’a déjà fait . Celui-ci utilise la même idée générale mais les détails sont différents et la mise en œuvre est beaucoup plus courte. Je ne sais pas ce qui serait plus rapide mais la performance n’est probablement pas un problème ici!

Mise à jour 2: Pour ce que cela vaut, mon implémentation est beaucoup plus rapide, en particulier pour un grand référentiel. Ce git ls-tree -r fait vraiment mal.

Mise à jour 3: je devrais noter que mes commentaires de performance ci-dessus s’appliquent à l’implémentation que j’ai liée ci-dessus dans la première mise à jour. La mise en œuvre d’Aristote est comparable à la mienne. Plus de détails dans les commentaires pour ceux qui sont curieux.

Bien que la question initiale ne le demande pas, je pense qu’il est utile de vérifier également la zone de transit pour voir si une goutte est référencée. J’ai modifié le script bash original pour le faire et trouvé ce qui faisait référence à un blob corrompu dans mon référentiel:

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

Voici les détails d’un script que j’ai peaufiné comme réponse à une question similaire , et vous pouvez le voir ici en action:

Capture d’écran de git-ls-dir s’exécute http://soffr.miximages.com/git/git-ls-dir.png

Donc … J’ai eu besoin de trouver tous les fichiers dépassant une limite donnée dans un repository de plus de 8 Go, avec plus de 108 000 révisions. J’ai adapté le script Perl d’Aristote avec un script Ruby que j’ai écrit pour atteindre cette solution complète.

Tout d’abord, git gc – faites ceci pour vous assurer que tous les objects sont dans des fichiers packs – nous n’analysons pas les objects non dans les fichiers pack.

Suivant Exécutez ce script pour localiser tous les objects blob sur les octets CUTOFF_SIZE. Capture la sortie dans un fichier comme “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 

Ensuite, éditez le fichier pour supprimer les blobs que vous n’attendez pas et les bits INPUT_THREAD en haut. Une fois que vous avez uniquement des lignes pour les sha1 que vous souhaitez trouver, exécutez le script suivant comme ceci:

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

Où le script git-find-blob est ci-dessous.

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

La sortie ressemblera à ceci:

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

Etc. Chaque commit contenant un gros fichier dans son arborescence sera répertorié. Si vous lancez les lignes qui commencent par un onglet, et uniq that, vous aurez une liste de tous les chemins que vous pouvez filtrer pour les supprimer, ou vous pouvez faire quelque chose de plus compliqué.

Permettez-moi de répéter: ce processus a été exécuté avec succès, sur un repository de 10 Go avec 108 000 commits. Il a fallu beaucoup plus de temps que prévu pour un grand nombre de blobs, mais en 10 heures, je devrai vérifier si le bit de mémorisation fonctionne …

Étant donné le hash d’un blob, y a-t-il un moyen d’obtenir une liste des commits qui ont ce blob dans leur arbre?

Avec Git 2.16 (Q1 2018), git describe serait une bonne solution, car il a été appris à creuser des arbres plus profonds pour trouver un : qui fait référence à un object blob donné.

Voir commit 644eb60 , commettre 4dbc59a , commettre cdaed0c , valider c87b653 , valider ce5b6f9 (16 novembre 2017) et valider 91904f5 , valider 2deda00 (02 nov. 2017) par Stefan Beller ( stefanbeller ) .
(Fusionné par Junio ​​C Hamano – gitster – dans commit 556de1a , 28 déc 2017)

builtin/describe.c : décrivez un blob

Parfois, les utilisateurs se voient atsortingbuer un hachage d’object et veulent l’identifier davantage (ex .: utiliser verify-pack pour trouver les plus gros blobs, mais qu’est-ce que c’est? Ou cette question très SO ” Quel commit a ce blob? “)

Lors de la description des commits, nous essayons de les ancrer à des balises ou à des refs, car ceux-ci sont conceptuellement à un niveau plus élevé que le commit. Et s’il n’y a pas de référence ou de tag qui correspond exactement, nous n’avons pas de chance.
Nous utilisons donc une heuristique pour créer un nom pour le commit. Ces noms sont ambigus, il peut y avoir différentes balises ou références à ancrer, et il peut y avoir différents chemins dans le DAG pour arriver à la validation avec précision.

Lors de la description d’un blob, nous voulons également décrire le blob d’un calque supérieur, qui est un tuple de (commit, deep/path) car les objects d’arbre impliqués sont plutôt inintéressants.
Le même blob peut être référencé par plusieurs commits, alors comment déciderons-nous de nous engager?

Ce patch implémente une approche plutôt naïve: comme il n’y a pas de pointeur de retour des blobs vers les commits dans lesquels le blob se produit, nous commencerons à suivre les conseils disponibles, en listant les blobs dans l’ordre et après avoir trouvé le blob, nous allons prendre le premier commit qui a listé le blob.

Par exemple:

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

nous dit que le Makefile tel qu’il était en v0.99 été introduit dans commit 7672db2 .

La marche est effectuée dans l’ordre inverse pour montrer l’introduction d’une goutte plutôt que sa dernière occurrence.

Cela signifie que la page de manuel git describe ajoute aux objectives de cette commande:
Au lieu de simplement décrire un commit en utilisant la balise la plus récente accessible, git describe donnera en fait à un object un nom lisible par un humain basé sur une référence disponible lorsqu’il est utilisé comme git describe .

Si l’object donné fait référence à un object blob, il sera décrit comme : , de sorte que le blob puisse être trouvé dans dans le , qui décrit lui-même le premier commit dans que ce blob se produit dans une révision inverse à pied de HEAD.

Mais:

BOGUES

Les objects d’arborescence ainsi que les objects tag ne pointant pas vers les commits ne peuvent pas être décrits .
Lors de la description des blobs, les balises légères pointant vers les blobs sont ignorées, mais le blob est toujours décrit comme étant : bien que la balise légère soit favorable.

En plus de git describe , que je mentionne dans ma réponse précédente , git log et git diff bénéficient désormais également de l’ --find-object=--find-object= ” pour limiter les résultats aux modifications impliquant l’object nommé .
C’est dans Git 2.16.x / 2.17 (T1 2018)

Voir commit 4d8c51a , commit 5e50525 , commit 15af58c , commit cf63051 , commit c1ddc46 , commit 929ed70 (le 4 janvier 2018) par Stefan Beller ( stefanbeller ) .
(Fusion par Junio ​​C Hamano – gitster – dans commit c0d75f0 , 23 janvier 2018)

diffcore : ajoutez une option de pioche pour trouver un blob spécifique

Parfois, les utilisateurs se voient atsortingbuer un hachage d’object et veulent l’identifier davantage (ex.: Utilisez verify-pack pour trouver les plus gros blobs, mais qu’est-ce que c’est? Ou cette question Stack Overflow « Quel commit a ce blob?

On pourrait être tenté d’étendre git-describe pour qu’il fonctionne également avec les blobs, de telle sorte que git describe donne une description en tant que ‘:’.
Cela a été mis en œuvre ici ; comme le montre le nombre de réponses (> 110), il s’avère que c’est difficile à faire.
Le plus difficile est de choisir le bon «commit-ish», car cela pourrait être le commit qui (réintroduit) le blob ou le blob qui a supprimé le blob; le blob pourrait exister dans différentes twigs.

Junio ​​a fait allusion à une approche différente de la résolution de ce problème, que ce patch implémente.
Apprenez aux autres appareils de diff à limiter les informations à ce qui est affiché.
Par exemple:

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

Nous observons que le Makefile fourni avec la version 2.0 est apparu dans la version v1.9.2-471-g47fbfded53 et dans la version v2.0.0-rc1-5-gb2feb6430b .
La raison pour laquelle ces commits se produisent avant la version 2.0.0 sont des fusions maléfiques qui ne sont pas trouvées avec ce nouveau mécanisme.