Який комітет має цю крапку?


149

З огляду на хеш блобу, чи є спосіб отримати список комітетів, які мають цю крапку у своєму дереві?


2
"Hash blob" - це те, що повертається git hash-objectабо sha1("blob " + filesize + "\0" + data), а не просто sha1sum вмісту краплі.
Іван Гамільтон

1
Спочатку я вважав, що це питання відповідає моєму питанню, але, здається, це не так. Я хочу знати один зробити який першим ввів цей згусток в сховище.
Джессі Глік

Якщо ви знаєте шлях до файлів, ви можете використовувати його git log --follow filepath(і скористатися цим, щоб прискорити рішення Аристотеля, якщо хочете).
Заз

ProTip ™: Введіть один із сценаріїв версії ~/.binі назвіть його git-find-object. Потім ви можете використовувати його git find-object.
Заз

1
Примітка. З Git 2.16 (I квартал 2018 року) ви могли б подумати просто git describe <hash>: Дивіться мою відповідь нижче .
VonC

Відповіді:


107

Обидва наступні сценарії приймають 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 );
}

8
FYI, ви повинні використовувати повну SHA краплі. Префікс, навіть якщо він унікальний, не працюватиме. Щоб отримати повний SHA з префікса, ви можете використовуватиgit rev-parse --verify $theprefix
John Douthat

1
Дякую @JohnDouthat за цей коментар. Ось як включити це у вищезазначений сценарій (вибачте за вкладене у коментарях): my $blob_arg = shift; open my $rev_parse, '-|', git => 'rev-parse' => '--verify', $blob_arg or die "Couldn't open pipe to git-rev-parse: $!\n"; my $obj_name = <$rev_parse>; chomp $obj_name; close $rev_parse or die "Couldn't expand passed blob.\n"; $obj_name eq $blob_arg or print "(full blob is $obj_name)\n";
Інго Каркат

Може бути помилка у верхньому скрипті оболонки. Цикл while виконується лише в тому випадку, якщо є більше рядків для читання, і з будь-якої причини журнал git не ставить остаточну кришку в кінці. Мені довелося додати стрічковий канал і проігнорувати порожні рядки. obj_name="$1" shift git log --all --pretty=format:'%T %h %s %n' -- "$@" | while read tree commit cdate subject ; do if [ -z $tree ] ; then continue fi if git ls-tree -r $tree | grep -q "$obj_name" ; then echo "$cdate $commit $@ $subject" fi done
Mixologic

7
Це знаходить коміти в поточній гілці, якщо ви не передасте --allяк додатковий аргумент. (Знаходження всіх комітетів, що виконуються на весь рівень, важливо у випадках, як видалення великого файлу з історії репо ).
peterflynn

1
Порада: передайте прапор -g до сценарію оболонки (після ідентифікатора об’єкта), щоб переглянути рефлог.
Bram Schoenmakers

24

На жаль, сценарії були для мене трохи повільними, тому мені довелося трохи оптимізувати. На щастя, у мене був не тільки хеш, але й шлях файлу.

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

1
Відмінна відповідь, тому що це так просто. Просто зробивши обґрунтовану припущення про те, що шлях відомий. Однак слід знати, що він повертає команду, де шлях було змінено на заданий хеш.
Unapiedra

1
Якщо ви хочете, щоб найновіша фіксація, що містить <hash>вказану <path>, буде видалено <path>аргумент з git logволі. Перший повернутий результат - розшук.
Unapiedra

10

З огляду на хеш блобу, чи є спосіб отримати список комітетів, які мають цю крапку у своєму дереві?

Завдяки Git 2.16 (Q1 2018) git describeбуло б хорошим рішенням, оскільки його вчили копати дерева глибше, щоб знайти <commit-ish>:<path>те, що відноситься до даного об’єкту краплі.

Див. Виконувати 644eb60 , виконувати 4dbc59a , здійснювати cdaed0c , фіксувати c87b653 , фіксувати ce5b6f9 (16 листопада 2017 р.) Та виконувати 91904f5 , виконувати 2deda00 (02 листопада 2017 р.) Стефаном Беллером ( stefanbeller) .
(Об'єднав Хуніо С Хамано - gitster- у комітеті 556de1a , 28 грудня 2017 р.)

builtin/describe.c: опишіть краплі

Іноді користувачі отримують хеш-об’єкт, і вони хочуть його ідентифікувати далі (напр .: Використовуйте verify-packдля пошуку найбільших крапок, але що це? Або це дуже ТАКЕ питання "У якій комісії є ця крапка? ")

Описуючи комісії, ми намагаємося прив’язати їх до тегів або рефлексу, оскільки вони концептуально на більш високому рівні, ніж коміти. І якщо немає посилання або тегів, які б точно відповідали, нам не пощастило.
Таким чином, ми використовуємо евристику, щоб скласти назву для комітету. Ці назви неоднозначні, можуть бути різні теги або відмінні знаки для прив’язки, а в DAG може бути інший шлях для проходження, щоб точно прийти до комісії.

Описуючи краплі, ми хочемо також описати крапку з більш високого шару, що є кордоном, (commit, deep/path)оскільки задіяні дерева об'єкти є досить нецікавими.
На одну і ту ж крапку можна посилатися декількома комісіями, тож як ми вирішуємо, яку команду використовувати?

Цей патч реалізує досить наївний підхід до цього: Оскільки немає зворотних покажчиків від крапок на коміти, в яких виникає крапля, ми почнемо переходити з будь-яких доступних підказок, перераховуючи крапки в порядку виконання комісії і як тільки ми знайдемо блоб, ми візьмемо перше зобов’язання, яке перерахувало крапку .

Наприклад:

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

повідомляє нам, Makefileяк це v0.99було введено в команді 7672db2 .

Ходіння виконується у зворотному порядку, щоб показати введення краплі, а не останнє його виникнення.

Це означає, що git describeдовідкова сторінка додає до цілей цієї команди:

Замість того, щоб просто описати команду, використовуючи останній тег, доступний з нього, git describeвін фактично дасть об'єкту зрозуміле для людини ім'я на основі доступного посилання при використанні як git describe <blob>.

Якщо даний об'єкт посилається на крапку, він буде описаний як <commit-ish>:<path>такий, що крапка може бути знайдена у <path>в <commit-ish>, який сам описує першу фіксацію, в якій ця крапка відбувається в ході зворотного перегляду від HEAD.

Але:

БУГИ

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


1
Добре використовувати разом із git rev-list --objects --all | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | awk '/^blob/ {print substr($0,6)}' | sort --numeric-sort --key=2 -r | head -n 20, що повертає вам топ-20 найбільших крапель. Тоді ви можете передати ідентифікатор blob з наведеного вище виводу git describe. Працював як шарм! Дякую!
Олександр Погребняк

7

Я думав, що це взагалі корисна річ, тому я написав невеликий сценарій 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: Я повинен зазначити, що мої коментарі щодо ефективності вище стосуються реалізації, яку я пов’язував вище в першому Оновлення. Реалізація Аристотеля працює порівняно з моєю. Детальніше в коментарях для тих, хто цікавиться.


Хм, як це може бути , що набагато швидше? Ви все одно ходите по дереву, чи не так? Яку роботу робить git-ls-tree, яку ви уникаєте? (Примітка. Grep під заставу на першому поєдинку, SIGPIPE'ing git-ls-tree.) Коли я спробував це, мені довелося Ctrl-C ваш сценарій через 30 секунд; міна була зроблена в 4.
Арістотель Пагальцис

1
Мій скрипт кешує результати підрядів у хеші% дерев, тому не потрібно тримати пошукові піддеревки, які не змінилися.
Грег Хьюгілл

Насправді я намагався виконати, що я знайшов у github, до якого я пов’язаний. У деяких випадках ваше швидше, але дуже залежить від того, чи шукаєте ви файл на початку чи в кінці списку ls-дерева. Зараз у моєму сховищі є 9574 файли.
Грег Хьюгілл

Також мені здається, що деякі нелінійні історії проектів можуть змусити мій сценарій зробити набагато більше роботи, ніж потрібно (це можна виправити). Це може бути причиною того, що бігти до вас потрібно було багато часу. Мій репозиторій - це дзеркало git-svn сховища Subversion, тому воно добре лінійне.
Грег Х'югілл

Замість розбору котячих файлів, щоб отримати дерево, просто зробітьgit rev-parse $commit^{}
jthill

6

Хоча оригінальне запитання не задається, я вважаю, що корисно також перевірити місце постановки, щоб побачити, чи є посилання на крапку. Я змінив оригінальний сценарій bash, щоб зробити це, і виявив, що посилається на корумповану крапку в моєму сховищі:

#!/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
Я просто хотів би дати кредит, коли це завдячує: дякую корупції оперативної пам’яті за те, що викликав у мене BSOD і змусив мене вручну відремонтувати мою репу.
Маріо

4

Отже ... мені потрібно було знайти всі файли за заданим лімітом у репо-форматі розміром понад 8 ГБ, з понад 108 000 версій. Я адаптував сценарій perl Арістотеля разом з рубіновим сценарієм, який я написав, щоб досягти цього повного рішення.

По-перше, git gc- зробіть це для того, щоб усі об'єкти знаходилися в пакетних файлах - ми не скануємо об'єкти не в файлах пакету.

Далі Запустіть цей сценарій, щоб знайти всі краплі в байтах на 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: http://stackoverflow.com/questions/223678/which-commit-has-this-blob
# 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це, ви будете мати список усіх шляхів, які ви можете відфільтрувати до гілки, або ви можете зробити щось складніше.

Дозвольте ще раз зазначити: цей процес пройшов успішно, на РЕПО 10 ГБ із 108 000 комітів. Це зайняло набагато більше часу, ніж я передбачив, коли я працював на великій кількості крапок, хоча протягом 10 годин мені доведеться перевірити, чи працює біт запам'ятовування ...


1
Як відповідь Аристотеля вище, це знаходить тільки коммітов на поточній гілці , якщо не передати додаткові аргументи: -- --all. (Знаходження всіх комітетів на всій території є важливим у випадках, коли ретельно видалити великий файл з історії репо ).
peterflynn

4

Окрім того git describe, що я згадую у своїй попередній відповіді , git logа git diffтепер виграє також можливість " --find-object=<object-id>" обмежити висновки змінами, які стосуються названого об'єкта.
Тобто в Git 2.16.x / 2.17 (Q1 2018)

Див. Зробити комісію 4d8c51a , зробити 5e50525 , зробити 15af58c , зробити cf63051 , зробити c1ddc46 , скоїти 929ed70 (04 січня 2018 р.) Стефаном Беллером ( stefanbeller) .
(Об’єднав Хуніо С Хамано - gitster- у комітеті c0d75f0 , 23 січня 2018 р.)

diffcore: додайте варіант підбирання, щоб знайти певну крапку

Іноді користувачі отримують хеш-об’єкт, і вони хочуть його ідентифікувати далі (напр .: Використовуйте verify-pack для пошуку найбільших крапок, але що це? Або це запитання щодо переповнення стека "У якій комісії є ця крапка? ")

Можна спокуситись поширитись git-describeі на роботу з краплями, що git describe <blob-id>дає опис як::.
Це було реалізовано тут ; як видно з великої кількості відповідей (> 110), виявляється, що це складно правильно.
Важкою частиною вибору правильно є вибір правильного «фікш-іш», оскільки це може бути зобов’язання, яке (повторно) запровадило крапку або крапку, яка видалила крапку; крапля могла існувати в різних галузях.

Хуніо натякнув на інший підхід до вирішення цієї проблеми, який реалізує цей патч.
Навчіть машину 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, є злісні злиття, які не знайдені за допомогою цього нового механізму.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.