Как получить дату последней модификации всех файлов в репозитории git

Я знаю, как получить дату последнего изменения одного файла в репозитории git:

git log -1 --format="%ad" -- path/to/file

Есть ли простой и эффективный способ сделать то же самое для всех файлов, присутствующих в настоящее время в хранилище?

4 ответа

Решение

Простой ответ - перебирать каждый файл и отображать время его модификации, а именно:

git ls-tree -r --name-only HEAD | while read filename; do
  echo "$(git log -1 --format="%ad" -- $filename) $filename"
done

Это даст результат примерно так:

Fri Dec 23 19:01:01 2011 +0000 Config
Fri Dec 23 19:01:01 2011 +0000 Makefile

Очевидно, что вы можете контролировать это, так как это всего лишь сценарий bash на данный момент - так что не стесняйтесь настраивать по своему вкусу!

Этот подход также работает с именами файлов, которые содержат пробелы:

git ls-files -z | xargs -0 -n1 -I{} -- git log -1 --format="%ai {}" {}

Пример вывода:

2015-11-03 10:51:16 -0500 .gitignore
2016-03-30 11:50:05 -0400 .htaccess
2015-02-18 12:20:26 -0500 .travis.yml
2016-04-29 09:19:24 +0800 2016-01-13-Atlanta.md
2016-04-29 09:29:10 +0800 2016-03-03-Elmherst.md
2016-04-29 09:41:20 +0800 2016-03-03-Milford.md
2016-04-29 08:15:19 +0800 2016-03-06-Clayton.md
2016-04-29 01:20:01 +0800 2016-03-14-Richmond.md
2016-04-29 09:49:06 +0800 3/8/2016-Clayton.md
2015-08-26 16:19:56 -0400 404.htm
2016-03-31 11:54:19 -0400 _algorithms/acls-bradycardia-algorithm.htm
2015-12-23 17:03:51 -0500 _algorithms/acls-pulseless-arrest-algorithm-asystole.htm
2016-04-11 15:00:42 -0400 _algorithms/acls-pulseless-arrest-algorithm-pea.htm
2016-03-31 11:54:19 -0400 _algorithms/acls-secondary-survey.htm
2016-03-31 11:54:19 -0400 _algorithms/acls-suspected-stroke-algorithm.htm
2016-03-31 11:54:19 -0400 _algorithms/acls-tachycardia-algorithm-stable.htm
...

Выходные данные могут быть отсортированы по метке времени изменения, добавив | sort к концу:

git ls-files -z | xargs -0 -n1 -I{} -- git log -1 --format="%ai {}" {} | sort

Вот еще один ответ:

git ls-tree -r --name-only HEAD -z | TZ=UTC xargs -0n1 -I_ git --no-pager log -1 --date=iso-local --format="%ad _" -- _

Изменения в ранее данных ответах:

  • Правильно обрабатывает пробелы в именах файлов.
  • Использует ls-tree вместо ls-files и как таковой может использоваться с голыми репозиториями.
  • Печатает все время с нулевым смещением (UTC) в формате, подобном ISO 8601. Это позволяет правильно сортировать время, близкое к переходу на летнее время (или фиксации из разных часовых поясов), добавляя | sort к команде.
  • Не требует использования подоболочек, поэтому производительность должна быть максимально хорошей.

Обратите внимание, что это неправильно обрабатывает имена файлов с %персонаж. См. Ниже более подробную команду для правильной обработки всех символов в именах файлов.

Обратите внимание, что эта команда все еще очень медленная, потому что Git на самом деле не хранит информацию, которую мы ищем. Технически это перебирает все файлы, фильтрует все изменения любого заданного файла из всей истории проекта, берет последний коммит и печатает временную метку его автора. В результате отображаемое время соответствует последней фиксации, изменившей каждый файл. Если в момент совершения исходной фиксации файл имел другую метку времени на диске, он никогда не хранился нигде в репозитории git, и поэтому его невозможно восстановить без внешнего источника данных.

Если вы хотите установить время модификации файловой системы на время последней фиксации автором каждого файла, вы можете сделать что-то вроде этого, чтобы иметь дело со специальными символами в именах файлов (добавьте | bash для автоматического выполнения всех выданных команд):

git ls-tree -r --name-only HEAD -z | TZ=UTC xargs -0n1 git --no-pager log -1 --date=iso-local --name-only -z --format="format:%ad" | perl -npe "INIT {\$/ = \"\\0\"} s@^(.*? .*?) .*?\n(.*)\$@\$date=\$1; \$name=\$2; \$name =~ s/'/'\"'\"'/sg; \"TZ=UTC touch -m --date '\$date' '\$name';\n\"@se"

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

Если порядок вывода строго не важен, вы можете улучшить производительность этой команды, добавив -P $(nproc) к xargs флаги для масштабирования Git на все процессоры, чтобы команда выглядела как ...TZ=UTC xargs -0n1 -P $(nproc) git....

Если вы предпочитаете время коммиттера вместо даты автора, используйте %cd вместо %ad в командной строке выше.

Если вы пытаетесь установить время модификации файла в большом репо, посмотрите https://github.com/MestreLion/git-tools. Это уже пакет.

sudo apt install git-restore-mtime
cd repo
git restore-mtime

оно использует git whatschangedа не git log, который намного быстрее на больших репо

Это небольшой трюк с ответом Эндрю М. (Я не смог прокомментировать его ответ.)

Оберните первое $filename в двойные кавычки, чтобы поддерживать имена файлов со встроенными пробелами.

git ls-tree -r --name-only HEAD | while read filename; do
    echo "$(git log -1 --format="%ad" -- "$filename") $filename"
done

Образец вывода:

Tue Jun 21 11:38:43 2016 -0600 subdir/this is a filename with spaces.txt

Я ценю, что решение Эндрю (основанное на ls-tree) работает с голыми репозиториями! (Это не относится к решениям, использующим ls-файлы.)

Для тех из нас, кто использует Windows и PowerShell, ответ Эндрю М, с машиночитаемой настройкой:

git ls-tree -r --name-only HEAD | ForEach-Object { "$(git log -1 --format="%ai" -- "$_")`t$_" }

Пример вывода:

2019-05-07 12:00:37 -0500   .editorconfig
2016-07-13 14:03:49 -0500   .gitattributes
2019-05-07 12:00:37 -0500   .gitignore
2018-02-03 22:01:17 -0600   .mailmap

Вот версия ответа Эндрю М для рыбных раковин, для тех, кто использует рыбу.

git ls-tree -r --name-only HEAD | while read -l filename
    printf '%s %s\n' (git log -1 --format="%ai" -- $filename) $filename
end

Я храню это как функцию рыбы для легкого доступа.

Другие вопросы по тегам