Étant donné le hachage d'un blob, existe-t-il un moyen d'obtenir une liste des validations qui ont ce blob dans leur arbre?
Les deux scripts suivants prennent le SHA1 du blob comme premier argument, puis, éventuellement, tous les arguments qui git log
comprendra. Par exemple. --all
pour rechercher dans toutes les branches au lieu de la seule, ou -g
pour rechercher dans le reflog, ou tout ce que vous voulez.
Le voici 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 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 );
}
Malheureusement, les scripts étaient un peu lents pour moi, j'ai donc dû optimiser un peu. Heureusement, j'avais non seulement le hachage mais aussi le chemin d'un fichier.
git log --all --pretty=format:%H -- <path> | xargs -n1 -I% sh -c "git ls-tree % -- <path> | grep -q <hash> && 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 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);
}
Je mettrai ça sur github quand je rentrerai ce soir.
Mise à jour: il ressemble à 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 l'implémentation est beaucoup plus courte. Je ne sais pas lequel serait le plus rapide mais les performances ne sont probablement pas un problème ici!
Mise à jour 2: pour ce que ça vaut, mon implémentation est des ordres de grandeur plus rapide, surtout pour un grand référentiel. Cette git ls-tree -r
fait vraiment mal.
Mise à jour 3: je dois noter que mes commentaires sur les performances ci-dessus s'appliquent à l'implémentation que j'ai liée ci-dessus dans la première mise à jour. implémentation d'Aristote fonctionne de manière comparable à la mienne. Plus de détails dans les commentaires pour les curieux.
Étant donné le hachage d'un blob, existe-t-il un moyen d'obtenir une liste des validations qui ont ce blob dans leur arbre?
Avec Git 2.16 (T1 2018), git describe
serait une bonne solution, car il a été appris à creuser plus profondément les arbres pour trouver un <commit-ish>:<path>
qui fait référence à un objet blob donné.
Voir commit 644eb6 , commit 4dbc59a , commit cdaed0c , commit c87b65 , commit ce5b6f9 (16 nov 2017), et commit 91904f5 , commit 2deda (02 nov 2017) par Stefan Beller (stefanbeller
) .
(Fusionné par Junio C Hamano - gitster
- in commit 556de1a , 28 décembre 2017)
builtin/describe.c
: décrire un blobParfois, les utilisateurs reçoivent un hachage d'un objet et ils veulent l'identifier davantage (ex.: Utiliser
verify-pack
pour trouver les plus gros blobs, mais quels sont-ils? ou très SO question " Quel commit a ce blob? ")Lors de la description des validations, nous essayons de les ancrer aux balises ou aux références, car celles-ci sont conceptuellement à un niveau supérieur à la validation. 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 la validation. Ces noms sont ambigus, il peut y avoir différentes balises ou références à ancrer et il peut y avoir un chemin différent dans le DAG à parcourir pour arriver précisément à la validation.Lors de la description d'un blob, nous voulons également décrire le blob d'une couche supérieure, qui est un tuple de
(commit, deep/path)
car les objets arborescents impliqués sont plutôt inintéressants.
Le même blob peut être référencé par plusieurs validations, alors comment décider quel commit utiliser?Ce correctif met en œuvre une approche plutôt naïve à ce sujet: Comme il n'y a pas de pointeurs de retour des blobs vers les commits dans lesquels le blob se produit, nous allons commencer à marcher à partir de tous les conseils disponibles, en listant les blobs dans l'ordre du commit et une fois que nous avons trouvé le blob, nous prendrons le premier commit qui a répertorié le blob .
Par exemple:
git describe --tags v0.99:Makefile conversion-901-g7672db20c2:Makefile
nous indique le
Makefile
tel qu'il était dansv0.99
a é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 le git describe
man page ajoute aux objectifs de cette commande:
Au lieu de simplement décrire un commit à l'aide de la balise la plus récente accessible depuis celui-ci,
git describe
donnera réellement à un objet un nom lisible par l'homme basé sur une référence disponible lorsqu'il est utilisé commegit describe <blob>
.Si l'objet donné fait référence à un blob, il sera décrit comme
<commit-ish>:<path>
, de sorte que le blob se trouve à<path>
dans le<commit-ish>
, qui décrit lui-même le premier commit dans lequel ce blob se produit dans une marche de révision inverse de HEAD.
Mais:
BOGUES
Les objets arborescents ainsi que les objets tag ne pointant pas sur les validations ne peuvent pas être décrits .
Lors de la description des blobs, les balises légères pointant sur les blobs sont ignorées, mais le blob est toujours décrit comme<committ-ish>:<path>
malgré la légèreté de la balise.
Bien que la question d'origine ne le demande pas, je pense qu'il est utile de vérifier également la zone de transit pour voir si un blob est référencé. J'ai modifié le script bash d'origine pour ce faire et j'ai 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
Donc ... je devais trouver tous les fichiers dépassant une limite donnée dans un dépôt 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.
Première, git gc
- faites cela pour vous assurer que tous les objets sont dans des fichiers pack - nous n'analysons pas les objets qui ne sont pas dans les fichiers pack.
Exécutez ce script pour localiser tous les objets blob sur les octets CUTOFF_SIZE. Capturez 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, modifiez le fichier pour supprimer tous les blobs que vous n'attendez pas et les bits INPUT_THREAD en haut. une fois que vous n'avez que des lignes pour les sha1 que vous voulez 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 git-find-blob
le script est ci-dessous.
#!/usr/bin/Perl
# taken from: http://stackoverflow.com/questions/223678/which-commit-has-this-blob
# and modified by Carl Myers <[email protected]> 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 <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 sortie ressemblera à ceci:
<hash prefix> <oneline log message>
path/to/file.txt
path/to/file2.txt
...
<hash prefix2> <oneline log msg...>
Etc. Chaque commit contenant un gros fichier dans son arborescence sera répertorié. si vous grep
sortez les lignes qui commencent par un onglet et uniq
cela, vous aurez une liste de tous les chemins que vous pouvez filtrer pour supprimer, ou vous pouvez faire quelque chose de plus compliqué.
Permettez-moi de répéter: ce processus s'est déroulé avec succès, sur un dépôt de 10 Go avec 108 000 commits. Cela a pris beaucoup plus de temps que prévu lors de l'exécution sur un grand nombre d'objets blob, mais sur 10 heures, je devrai voir si le bit de mémorisation fonctionne ...
En plus de git describe
, que je mentionne dans ma réponse précédente , git log
et git diff
bénéficie désormais également du "--find-object=<object-id>
"option pour limiter les résultats aux modifications qui impliquent l'objet nommé.
C'est dans Git 2.16.x/2.17 (Q1 2018)
Voir commit 4d8c51a , commit 5e50525 , commit 15af58c , commit cf63051 , commit c1ddc46 , commit 929ed7 (04 janvier 2018) par Stefan Beller (stefanbeller
) .
(Fusionné par Junio C Hamano - gitster
- in commit c0d75f , 23 janvier 2018)
diffcore
: ajoutez une option de pioche pour trouver un blob spécifiqueParfois, les utilisateurs reçoivent un hachage d'un objet et ils veulent l'identifier davantage (ex.: Utilisez Verify-pack pour trouver les plus gros blobs, mais quels sont-ils? Ou cette question de débordement de pile " Quel commit a ce blob ? ")
On pourrait être tenté d'étendre
git-describe
pour fonctionner également avec des objets blob, tels quegit describe <blob-id>
donne une description comme ':'.
C'était implémenté ici ; comme en témoigne le nombre de réponses (> 110), il s'avère que c'est difficile de bien faire les choses.
La partie la plus difficile à obtenir est de choisir le bon "commit-ish" car cela pourrait être le commit qui a (ré) introduit le blob ou le blob qui a supprimé le blob; la goutte peut exister dans différentes branches.Junio a fait allusion à une approche différente pour résoudre ce problème, que ce correctif implémente.
Apprenez à la machineriediff
un autre indicateur pour restreindre 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
livré avec2.0
est apparu dansv1.9.2-471-g47fbfded53
et env2.0.0-rc1-5-gb2feb6430b
.
.