我知道如何检索 Git 存储库中单个文件的最后修改日期:
git log -1 --format="%ad" -- path/to/file
是否有一种简单有效的方法可以对存储库中当前存在的所有文件执行相同的操作?
答案1
一个简单的答案是遍历每个文件并显示其修改时间,即:
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 脚本 - 因此请随意根据自己的心意进行定制!
答案2
此方法也适用于包含空格的文件名:
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
答案3
这是另一种方法:
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
并且同样可以与裸存储库一起使用。 | sort
以 ISO 8601 格式打印所有零偏移(UTC)时间。通过附加到命令,这还可以对接近夏令时的时间(或来自不同时区的提交)进行正确排序。- 不需要使用子壳,因此性能应该尽可能好。
请注意,这不能正确处理带有%
字符的文件名。请参阅下文以了解更详细的命令,以正确处理文件名中的所有字符。
请注意,此命令仍然非常慢,因为 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"
尽管这比上面的命令复杂得多,但该命令的性能应该与第一个命令大致相同,因为性能受限于搜索每个文件的最后修改时间,而不是实际设置修改时间。请注意,这会将时间转换为 UTC,使用以空分隔的文件,并在设置时间时使用 UTC 时区为文件系统上的每个文件重置正确的时间戳。
如果输出的顺序不是十分重要,您可以通过添加标志来将 Git 扩展到所有 CPU,从而使命令看起来像这样,从而提高此命令-P $(nproc)
的xargs
性能...TZ=UTC xargs -0n1 -P $(nproc) git...
。
如果您更喜欢提交时间而不是作者日期,请在上面的命令行中使用%cd
而不是。%ad
答案4
这是Andrew M. 的回答(我无法对他的回答发表评论。)
将第一个 $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
我很欣赏 Andrew 的解决方案(基于ls 树) 适用于裸存储库!(使用的解决方案并非如此ls 文件。