Учитывая хэш из blob, есть ли способ получить список коммитов, которые имеют этот blob в своем дереве?
Какой фиксатор имеет этот blob?
Ответ 1
Оба из следующих сценариев берут blobs SHA1 в качестве первого аргумента, а после него, необязательно, любые аргументы, которые git log
будут понимать, Например. --all
для поиска во всех ветвях, а не только для текущего, или -g
для поиска в рефлоге или любого другого, что вам нравится.
Здесь это как оболочка script - короткая и сладкая, но медленная:
#!/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
И оптимизированная версия в Perl, но довольно короткая, но намного быстрее:
#!/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 );
}
Ответ 2
К сожалению, скрипты для меня были немного медленными, поэтому мне пришлось немного оптимизировать. К счастью, у меня был не только хэш, но и путь к файлу.
git log --all --pretty=format:%H <path> | xargs -n1 -I% sh -c "git ls-tree % <path> | grep -q <hash> && echo %"
Ответ 3
Я думал, что это будет вообще полезно, поэтому я написал немного perl script, чтобы сделать это:
#!/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);
}
Я положу это на github, когда вернусь домой сегодня вечером.
Обновление: похоже, что кто-то уже сделал это. В этом случае используется одна и та же общая идея, но детали разные, а реализация намного короче. Я не знаю, что будет быстрее, но производительность, вероятно, не вызывает беспокойства!
Обновление 2: для чего это стоит, моя реализация на порядок быстрее, особенно для большого репозитория. Это git ls-tree -r
действительно больно.
Обновление 3: я должен отметить, что мои комментарии по эффективности выше применимы к реализации, связанной выше в первом обновлении. Реализация Aristotle работает по сравнению с моей. Более подробно в комментариях для тех, кто интересуется.
Ответ 4
В то время как исходный вопрос не запрашивает его, я думаю, что полезно также проверить промежуточную область, чтобы увидеть, ссылается ли blob. Я изменил оригинальный bash script, чтобы сделать это, и нашел, что ссылается на поврежденный blob в моем репозитории:
#!/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
Ответ 5
Вот детали script, которые я отполировал в качестве ответа на аналогичный вопрос, и здесь вы можете увидеть его в действии:
снимок экрана git -ls-dir работает http://adamspiers.org/computing/git-ls-dir.png
Ответ 6
Итак... Мне нужно было найти все файлы с заданным лимитом в репо размером более 8 ГБ с более чем 108 000 ревизий. Я адаптировал Aristotle perl script вместе с рубином script, который я написал, чтобы достичь этого полного решения.
Во-первых, git gc
- сделать это, чтобы все объекты находились в packfiles - мы не сканируем объекты не в файлах пакета.
Далее Запустите этот script, чтобы найти все капли по байтам CUTOFF_SIZE. Захват вывода в файл, например "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
Затем отредактируйте файл, чтобы удалить любые капли, которые вы не ожидаете, и биты INPUT_THREAD вверху. если у вас есть только строки для sha1, которые вы хотите найти, запустите следующий script следующим образом:
cat edited-large-files.log | cut -d' ' -f4 | xargs git-find-blob | tee large-file-paths.log
Где git-find-blob
script ниже.
#!/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;
}
Результат будет выглядеть так:
<hash prefix> <oneline log message>
path/to/file.txt
path/to/file2.txt
...
<hash prefix2> <oneline log msg...>
И так далее. Будут перечислены все фиксации, содержащие большой файл в дереве. если вы grep
выделите строки, начинающиеся с вкладки, и uniq
, то у вас будет список всех путей, которые вы можете удалить, чтобы удалить фильтр, или вы можете сделать что-то более сложное.
Позвольте мне повторить: этот процесс прошел успешно, на ретрансляции 10GB с 108 000 коммитов. Это заняло гораздо больше времени, чем я предсказывал при работе на большом количестве капель, хотя более 10 часов мне придется посмотреть, работает ли бит запоминания...
Ответ 7
Учитывая хэш из blob, есть ли способ получить список коммитов, которые имеют этот blob в своем дереве?
С Git 2.16 (Q1 2018) git describe
было бы хорошим решением, поскольку учили копать деревья глубже найдите <commit-ish>:<path>
, который ссылается на данный объект blob.
См. commit 644eb60, зафиксировать 4dbc59a, commit cdaed0c, commit c87b653, commit ce5b6f9 (16 ноября 2017 г.) и commit 91904f5, commit 2deda00 (02 ноября 2017 г.) Стефан Беллер (stefanbeller
).
(слияние Junio C Hamano - gitster
- в commit 556de1a, 28 декабря 2017 г.)
builtin/describe.c
: описать blobИногда пользователям предоставляется хэш объекта, и они хотят идентифицируйте его далее (например: Используйте
verify-pack
, чтобы найти самые большие капли, но что это? или этот очень SO вопрос Которое имеет этот blob?")При описании коммитов мы пытаемся привязать их к тегам или ссылкам, поскольку эти концептуально на более высоком уровне, чем фиксация. И если нет ссылки или тег, который соответствует точно, нам не повезло.
Поэтому мы используем эвристику, чтобы составить имя для фиксации. Эти имена неоднозначны, могут быть разные теги или ссылки на привязку, и может быть другой путь в DAG для перемещения, чтобы точно выполнить фиксацию.При описании blob мы хотим описать blob с более высокого уровня также, который является кортежем
(commit, deep/path)
в качестве объектов дерева вовлечены довольно неинтересно.
На тот же blob можно ссылаться на несколько коммитов, так как мы решаем, какую фиксацию использовать?Этот патч реализует довольно наивный подход к этому: поскольку нет никаких обратных указателей от blobs до коммитов, в которых происходит blob, мы начнем с любых доступных советов, указав blobs в порядке фиксации и один раз мы нашел blob, мы возьмем первую фиксацию, которая указала blob.
Например:
git describe --tags v0.99:Makefile conversion-901-g7672db20c2:Makefile
сообщает нам, что
Makefile
, как это было вv0.99
, введено в commit 7672db2.Ходьба выполняется в обратном порядке, чтобы показать введение blob, а не его последнее появление.
Это означает, что git describe
man page добавляет цели этой команды:
Вместо того, чтобы просто описывать фиксацию, используя самый последний тег, доступный из нее, git describe
фактически предоставит объекту человеко-читаемое имя на основе доступного ref при использовании в качестве git describe <blob>
.
Если данный объект относится к блобу, он будет описан как
<commit-ish>:<path>
, так что blob можно найти в<path>
в<commit-ish>
, который сам описывает первое коммит, в котором этот blob происходит в обратная ревизия ходьбы от HEAD.
Но:
ОШИБКИ
Объекты дерева, а также объекты тегов, не указывающие на фиксации, не могут быть описаны.
Когда описываются капли, легкие теги, указывающие на капли, игнорируются, но капля все еще описывается как<committ-ish>:<path>
, несмотря на то, что легкий ярлык является благоприятным.
Ответ 8
В дополнение к git describe
, о котором я упоминал в своем предыдущем ответе, git log
и git diff
теперь также выигрывает от "--find-object=<object-id>
" возможность ограничить результаты изменениями, связанными с указанным объектом.
То есть в Git 2.16.x/2.17 (Q1 2018)
См. commit 4d8c51a, зафиксировать 5e50525, commit 15af58c, совершить cf63051, commit c1ddc46, commit 929ed70 (04 января 2018 г.) Стефан Беллер (stefanbeller
).
(объединено Junio C Hamano - gitster
- в commit c0d75f0, 23 января 2018 г.
diffcore
: добавьте параметр кирки, чтобы найти конкретный blobИногда пользователям предоставляется хэш объекта, и они хотят идентифицировать его далее (например: Использовать проверочный пакет, чтобы найти самые большие капли, но что это? или этот вопрос "Какой фиксатор имеет этот blob?" )
Возможно, у вас возникнет соблазн расширить
git-describe
, чтобы работать с blobs, такой, чтоgit describe <blob-id>
дает описание как ':'
. Это было реализовано здесь; как видно количество ответов ( > 110), оказывается, это сложно сделать правильно.
Трудная часть для правильного выбора - это выбор правильного "commit-ish", как может быть фиксацией, которая (повторно) ввела blob или blob, что удалил blob; blob может существовать в разных ветвях.Юнио намекнул на другой подход к решению этой проблемы, который патч-инструмент.
Научите оборудованиюdiff
еще один флаг, чтобы ограничить информацию тем, что показано.
Например:$ ./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:"
заметим, что
Makefile
, поставляемый с2.0
, появился вv1.9.2-471-g47fbfded53
и вv2.0.0-rc1-5-gb2feb6430b
.
Причина, по которой эти коммиты происходят до v2.0.0, - это зло слияния, которые не найдены с использованием этого нового механизма.