Какой коммит имеет этот BLOB-объект? - PullRequest
129 голосов
/ 22 октября 2008

С учетом хэша большого двоичного объекта, есть ли способ получить список коммитов, которые имеют этот большой двоичный объект в своем дереве?

Ответы [ 7 ]

92 голосов
/ 22 октября 2008

Оба следующих сценария принимают SHA1 большого двоичного объекта в качестве первого аргумента, а после него, необязательно, любые аргументы, которые git log будут понимать. Например. --all для поиска во всех ветвях, а не только в текущей, или -g для поиска в журнале, или что вам еще нравится.

Вот он как скрипт оболочки - короткий и приятный, но медленный:

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

И оптимизированная версия на 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 );
}
14 голосов
/ 16 сентября 2015

К сожалению, скрипты были немного медленными для меня, поэтому мне пришлось немного оптимизировать. К счастью, у меня был не только хеш, но и путь к файлу.

git log --all --pretty=format:%H -- <path> | xargs -n1 -I% sh -c "git ls-tree % -- <path> | grep -q <hash> && echo %"
7 голосов
/ 22 октября 2008

Я подумал, что это будет вообще полезно, поэтому я написал для этого небольшой скрипт на Perl:

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

Я положу это на github, когда вернусь домой этим вечером.

Обновление: похоже, что кто-то уже сделал это . Тот использует ту же общую идею, но детали разные, и реализация на намного короче. Я не знаю, что будет быстрее, но производительность здесь, наверное, не проблема!

Обновление 2: моя реализация на несколько порядков быстрее, особенно для большого репозитория. Это git ls-tree -r действительно причиняет боль.

Обновление 3: я должен отметить, что мои комментарии к производительности выше относятся к реализации, о которой я говорил выше в первом обновлении. Реализация Аристотеля сопоставима с моей. Подробнее в комментариях для любопытных.

6 голосов
/ 29 декабря 2017

С учетом хэша большого двоичного объекта, есть ли способ получить список коммитов, которые имеют этот большой двоичный объект в своем дереве?

С Git 2.16 (Q1 2018), git describe было бы хорошим решением, поскольку его учили копать деревья глубже, чтобы найти <commit-ish>:<path>, который ссылается на данный объект BLOB-объекта.

См. commit 644eb60 , commit 4dbc59a , commit cdaed0c , commit c87b653 , commit ce5b6f9 (16 Ноябрь 2017) и коммит 91904f5 , коммит 2deda00 (02 ноября 2017) Stefan Beller (stefanbeller) .
(Объединено Junio ​​C Hamano - gitster - в коммит 556de1a , 28 декабря 2017 г.)

builtin/describe.c: описать блоб

Иногда пользователям дают хэш объекта, и они хотят идентифицируйте это далее (например: используйте verify-pack, чтобы найти самые большие капли, но что это? или этот очень ТАК вопрос " Какой коммит имеет этот BLOB-объект? ")

При описании коммитов мы пытаемся привязать их к тегам или ссылкам, так как они концептуально на более высоком уровне, чем коммит. И если нет реф или тег, который точно соответствует, нам не повезло.
Поэтому мы используем эвристику, чтобы придумать имя для коммита. Эти имена неоднозначны, могут быть разные теги или ссылки для привязки, и в DAG может быть другой путь для точного достижения фиксации.

При описании капли мы хотим описать каплю из более высокого слоя также, который является кортежем (commit, deep/path) в качестве объектов дерева участие довольно неинтересно.
На один и тот же BLOB-объект может ссылаться несколько коммитов, так как мы решаем, какой коммит использовать?

В этом патче реализован довольно наивный подход к этому: Поскольку нет обратных указателей от блобов на коммиты, в которых встречается блоб, мы начнем с любых доступных советов, перечисляя капли в порядке следования совершить и когда мы нашел BLOB-объект, мы возьмем первый коммит, в котором указан BLOB-объект

Например:

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

сообщает нам, что Makefile, как это было в v0.99, было введено в commit 7672db2 .

Ходьба выполняется в обратном порядке, чтобы показать введение капля, а не ее последнее вхождение.

Это означает, что git describe справочная страница добавляет к целям этой команды:

Вместо простого описания коммита с использованием самого последнего тега, доступного из него, git describe на самом деле даст объекту удобочитаемое имя на основе доступного ссылки при использовании в качестве git describe <blob>.

Если данный объект ссылается на BLOB-объект, он будет описан как <commit-ish>:<path>, так что BLOB-объект можно найти в <path> в <commit-ish>, который сам описывает первый коммит, в котором этот BLOB-объект встречается в обратный пересмотр ходьбы от HEAD.

Но:

ОШИБКА

Объекты дерева, а также объекты тегов, не указывающие на коммиты, не могут быть описаны .
При описании BLOB-объектов легкие теги, указывающие на BLOB-объекты, игнорируются, но BLOB-объект все еще описывается как <committ-ish>:<path>, несмотря на то, что облегченный тег является благоприятным.

6 голосов
/ 17 марта 2012

Хотя исходный вопрос не требует его, я думаю, что полезно также проверить область подготовки, чтобы увидеть, есть ли ссылка на BLOB-объект. Я изменил исходный скрипт bash, чтобы сделать это, и нашел то, что ссылалось на поврежденный 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 "$@" --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
3 голосов
/ 05 октября 2012

Итак ... Мне нужно было найти все файлы с заданным лимитом в репо размером более 8 ГБ с более чем 108 000 ревизий. Я адаптировал Perl-скрипт Аристотеля вместе со сценарием ruby, который написал, чтобы достичь полного решения.

Сначала git gc - сделайте это, чтобы убедиться, что все объекты находятся в пакетных файлах - мы не проверяем объекты не в пакетных файлах.

Далее Запустите этот скрипт, чтобы найти все BLOB-объекты в байтах 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, которые вы хотите найти, запустите следующий скрипт, например:

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

Где скрипт git-find-blob ниже.

#!/usr/bin/perl

# taken from: /179224/kakoi-kommit-imeet-etot-blob-obekt
# and modified by Carl Myers <cmyers@cmyers.org> 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, у вас будет список всех путей, которые вы можете удалить с помощью filter-branch, или вы можете сделать что-то более сложное.

Позвольте мне повторить: этот процесс прошел успешно, на репо 10 ГБ со 108 000 коммитов. Это заняло гораздо больше времени, чем я ожидал, при работе с большим количеством больших двоичных объектов, хотя через 10 часов мне нужно будет проверить, работает ли бит запоминания ...

2 голосов
/ 02 февраля 2018

В дополнение к git describe, о котором я упоминал в моем предыдущем ответе, , git log и git diff теперь также получает выгоду от опции "--find-object=<object-id>", чтобы ограничить результаты изменениями, которые задействовать названный объект.
Это в Git 2.16.x / 2.17 (Q1 2018)

См. commit 4d8c51a , commit 5e50525 , commit 15af58c , commit cf63051 , commit c1ddc46 , коммит 929ed70 (4 января 2018 г.) Stefan Beller (stefanbeller) .
(Объединено с Junio ​​C Hamano - gitster - в коммит c0d75f0 , 23 января 2018 г.)

diffcore: добавить опцию кирки, чтобы найти конкретный шарик

Иногда пользователям дают хэш объекта, и они хотят идентифицируйте это далее (например: используйте verify-pack, чтобы найти самые большие капли, но что это? или этот вопрос переполнения стека " Какой коммит имеет этот BLOB-объект? ")

Можно было бы испытать желание расширить git-describe, чтобы также работать с каплями, такой, что git describe <blob-id> дает описание как ':'
. Это было реализовано здесь ; как видно по явному число ответов (> 110), оказывается, это сложно сделать правильно.
Трудно понять, как правильно выбрать 'commit-ish' может быть совершить, что (повторно) представил 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, злая слияния, которые не найдены с использованием этого нового механизма.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...