Список последних дат фиксации для большого количества файлов, быстро - PullRequest
17 голосов
/ 23 февраля 2012

Я хотел бы перечислить дату последнего принятия для большого количества файлов в репозитории git .

Для конкретности предположим, что я хочу получить даты последней фиксации всех файлов *.txt внутри определенного подкаталога. Всего в хранилище находятся десятки тысяч файлов, а количество соответствующих *.txt файлов составляет несколько сотен. В хранилище уже тысячи коммитов.

Я пробовал три разных подхода.


Решение 1. Этот вопрос дает один ответ, основанный на git log. Однако, если я попытаюсь сделать что-то подобное, это будет очень медленно:

find . -name '*.txt' |
    xargs -n1 git log --format=format:%ai -n1 --all -- '{}'

В моем тестовом случае это заняло несколько минут - слишком медленно для моих целей.


Решение 2. Примерно так будет намного быстрее, меньше одной секунды:

git log --format=format:%ai --name-only .

Однако тогда мне пришлось бы написать сценарий, который обрабатывает вывод. Кроме того, приведенная выше команда выводит лотов информации, которая никогда не нужна: нерелевантные файлы и старые коммиты.


Решение 3. Я также попробовал что-то подобное, чтобы избавиться от ненужных файлов:

git log --format=format:%ai --name-only `find . -name '*.txt'`

Однако оказалось, что это на медленнее , чем решение 2. (Существовало различие во времени выполнения в 3 раза.) Более того, оно по-прежнему печатает старые коммиты, которые больше не нужны.


Вопрос. Я что-то упустил? Есть ли быстрый и удобный подход? Желательно что-то, что работает не только сейчас, но и в будущем, когда у нас будет гораздо больше коммитов?

Ответы [ 4 ]

7 голосов
/ 23 февраля 2012

Попробуйте это.

В git каждый коммит ссылается на объект дерева , который имеет указатели на состояние каждого файла (файлы объекты BLOB-объектов ).

Итак, вы хотите написать программу, которая начинается со списка всех файлов, которые вас интересуют, и начинается с объекта HEAD (фиксация SHA1, полученная с помощью git rev-parse HEAD). Он проверяет, изменены ли какие-либо «интересующие файлы» в этом дереве (дерево, полученное из атрибута «дерево» git cat-file commit [SHA1]) - обратите внимание, вам придется спускаться к поддеревьям для каждого каталога. Если они изменены (имеется в виду хэш SHA1, отличный от того, который был у них в «предыдущей» ревизии), удаляет каждый такой из набора интересов и печатает соответствующую информацию. Затем он продолжается до каждого родителя текущего дерева. Это продолжается до тех пор, пока набор интересов не станет пустым.

Если вы хотите максимальную скорость, вы будете использовать git C API. Если вы не хотите, чтобы это большая скорость, вы можете использовать git cat-file tree [SHA1 hash] (или, проще, git ls-tree [SHA1 hash] [files]), который будет выполнять абсолютный минимальный объем работы для чтения определенного объекта дерева ( это часть сантехнического слоя).

Сомнительно, насколько хорошо это продолжит работать в будущем, но если forward-compat - более серьезная проблема, вы можете подняться на уровень с git cat-file - но, как вы уже обнаружили, git log сравнительно медленный, так как часть фарфора, а не сантехника.

Смотрите здесь , чтобы узнать, как работает объектная модель git.

1 голос
/ 10 апреля 2019

Вот функция Powershell

function Get-GitRevisionDates($Path='.', $Ext='.md')
{
    [array] $log = git --no-pager log --format=format:%ai --name-only $Path

    $date_re = "^\d{4}-\d\d-\d\d \d\d:\d\d:\d\d .\d{4}$"
    [array] $dates = $log | Select-String $date_re | select LineNumber, Line

    $files = $log -notmatch "^$date_re$" | ? { $_.EndsWith($Ext) } | sort -unique

    $res = @()
    foreach ($file in $files) {
        $iFile = $log.IndexOf($file) + 1
        $fDate = $dates | ? LineNumber -lt $iFile | select -Last 1
        $res += [PSCustomObject]@{ File = $file; Date = $fDate.Line }
    }

    $res | sort Date -Desc
}
1 голос
/ 18 июля 2013

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

Я написал некоторый скрипт на Perl для изменения времени доступа, и после некоторых модификаций это версия, которая должна печатать то, что вам нужно:

#!/usr/bin/perl
my $commit = $ARGV[0];

$commit = 'HEAD' unless $commit;

# git a list of access times and files
my @logbook = `git whatchanged --pretty=%ai $commit`;

my %seen;
my $timestamp;
my $filename;
foreach (@logbook) {
    next if /^$/; # skip emtpy lines
    if (/^:/) {
        next unless /.txt$/;
        chomp ($filename = (split /\t/)[1]);
        next if $seen{$filename};
        print "$timestamp $filename\n";
        $seen{$filename} = 1;
    } else {
        chomp ($timestamp = $_);
    }
}

Я использовал git whatchanged вместо git log, чтобы иметь удобный формат с не временными строками, начинающимися с :, поэтому я могу легко отделить строки с файлами от времени последнего изменения.

0 голосов
/ 25 июля 2016

Я немного опоздал на вечеринку, но вот небольшой скрипт Bash , который использует вызов в OP # 2 и выполняет постобработку в awk. (Для моего использования мне не нужно было видеть файлы, которые были удалены на текущую дату, так что есть проверка на существование.)

#!/bin/bash
(
    git ls-files | sed 's/^/+ /'
    git log --format=format:"~ %aI" --name-only .
) | gawk '
/^~/ {date=$2;}
/^+/ {extant[$2] = 1;}
/^[^~+]/ {dates[$1] = date;}
END { for (file in dates) if(extant[file]) print(dates[file], file); }
' | sort
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...