如何检索git存储库中所有文件的最后修改日期

Eri*_*ier 81 git

我知道如何在 git 存储库中检索单个文件的最后修改日期:

git log -1 --format="%ad" -- path/to/file
Run Code Online (Sandbox Code Playgroud)

是否有一种简单有效的方法可以对存储库中当前存在的所有文件执行相同操作?

And*_* M. 93

一个简单的答案是遍历每个文件并显示其修改时间,即:

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

这将产生如下输出:

Fri Dec 23 19:01:01 2011 +0000 Config
Fri Dec 23 19:01:01 2011 +0000 Makefile
Run Code Online (Sandbox Code Playgroud)

显然,您可以控制它,因为此时它只是一个 bash 脚本——所以请随意根据您的心意定制!

  • 如果您想要可排序的时间戳而不是人类可读的日期,我建议使用 --format="%ai"。 (13认同)
  • 我希望有一个选项可以在一次 git log 运行中获得组合输出,但是您的答案比我想到的使用 find 的答案要好。我不知道 git-ls-tree,它的优点是只列出存储在存储库中的文件,跳过 .git 文件夹并忽略文件。谢谢。 (2认同)
  • 由于“HEAD”只是一个引用,您可以使用任何您想要的引用,无论是标签、分支、提交哈希等。 (2认同)
  • 正如@ThorSummoner 所说,日期使用 %ai 格式,然后通过管道对结果进行排序:`git ls-tree -r --name-only HEAD | 读取文件名时;做 echo "$(git log -1 --format="%ai" -- $filename) $filename"; 完成 | 排序` (2认同)

Wil*_*ken 31

这种方法也适用于包含空格的文件名:

git ls-files -z | xargs -0 -n1 -I{} -- git log -1 --format="%ai {}" {}
Run Code Online (Sandbox Code Playgroud)

示例输出:

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
...
Run Code Online (Sandbox Code Playgroud)

输出可以通过添加| sort到末尾按修改时间戳排序:

git ls-files -z | xargs -0 -n1 -I{} -- git log -1 --format="%ai {}" {} | sort
Run Code Online (Sandbox Code Playgroud)

  • 这有效,但需要很长时间...... (3认同)

Mik*_*nen 16

这是另一种方法:

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

对先前给出的答案的更改:

  • 正确处理文件名中的空格。
  • 使用ls-tree而不是ls-filesand 可以与裸存储库一起使用。
  • 以 ISO 8601 类似格式打印所有零偏移 (UTC) 时间。 | sort这还允许通过附加到命令来对接近夏令时更改(或来自不同时区的提交)的时间进行正确排序。
  • 不需要使用子shell,因此性能应该尽可能好。

请注意,这不能正确处理带有该%字符的文件名。请参阅下面的更详细的命令来正确处理文件名中的所有字符。

请注意,这个命令仍然非常慢,因为 Git 并不真正存储我们正在查找的信息。从技术上讲,这会遍历所有文件,从整个项目历史记录中过滤对任何给定文件的所有更改,获取最新提交并打印其作者时间戳。因此,显示的时间与更改每个文件的最后一次提交相匹配。如果文件在原始提交时在磁盘上具有不同的时间戳,则它不会存储在 Git 存储库中的任何位置,因此如果没有外部数据源,就无法恢复它。

该脚本发出的时间戳只是与提交时间匹配的模拟版本,而不是文件具有的真实时间戳,因为 Git 不将文件时间戳视为数据。这是因为 Git 的这一部分是由 Linus Torvalds 设计的,他坚信磁盘上的文件时间戳应该与它在磁盘上修改的时间相匹配,而不是该文件在历史上被修改时在其他人的磁盘上的时间戳。Git 仅存储已进行的提交的一个时间戳和该提交包含在 DAG 中那一刻的另一个时间戳。如果提交作者和将提交应用于版本历史的人是两个不同的人,这些可能会有所不同,这在 Linux 内核开发中经常发生。(还要考虑这样一个事实,即您可以使用索引/暂存区域仅提交每个文件中选定的行。在这种情况下,理论上甚至不存在“文件时间戳”的概念,因为提交的版本与任何文件都不匹配在磁盘上。)

如果要将文件系统修改时间设置为每个文件的最后一个作者提交时间,您可以执行以下操作来处理文件名中的特殊字符(添加| 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"
Run Code Online (Sandbox Code Playgroud)

尽管这比上面的命令复杂得多,但该命令的性能应该与第一个命令大致相同,因为性能受到搜索每个文件的最后修改时间而不是实际设置修改时间的限制。请注意,这会将时间转换为 UTC,使用 null 分隔的文件,并在设置时间时使用 UTC 时区为文件系统上的每个文件重置正确的时间戳。

如果输出顺序并不严格重要,您可以通过添加标志来将 Git 扩展到所有 CPU,使命令看起来像 ,从而提高此命令-P $(nproc)xargs性能...TZ=UTC xargs -0n1 -P $(nproc) git...

如果您更喜欢提交者时间而不是作者日期,请在上面的命令行中使用%cd而不是。%ad

  • 在某些情况下,“读时”Bash 是可以的。对于这个特定的用例,xargs 实际上可能具有与 while read 相同的性能,但 xargs 允许正确处理带有嵌入换行符的文件名。此外,xargs 允许使用 -P 标志在多个 CPU 上同时运行命令。 (3认同)

小智 7

这是对 Andrew M. 的回答的一个小调整。(我无法评论他的回答。)

将第一个 $filename 用双引号括起来,以支持带有嵌入空格的文件名。

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

示例输出:

Tue Jun 21 11:38:43 2016 -0600 subdir/this is a filename with spaces.txt
Run Code Online (Sandbox Code Playgroud)

我很欣赏 Andrew 的解决方案(基于ls-tree)适用于裸存储库!(对于使用 ls-files 的解决方案,情况并非如此。)


小智 7

如果您尝试在大型存储库上设置文件修改时间,请查看Git Tools。它\xe2\x80\x99s已经是一个包了。

\n
sudo apt install git-restore-mtime\ncd repo\ngit restore-mtime\n
Run Code Online (Sandbox Code Playgroud)\n

它使用git whatschanged而不是在大型git log存储库上更快。

\n