获取git修订中每个文件最后修改时间的最有效方法是什么?

2 投票
3 回答
109 浏览
提问于 2025-04-14 15:29

我想要通过编程的方式列出某个版本中每个文件的名称和最后修改时间。每次对每个文件运行 git log,就像这里提到的那样,速度非常慢。有没有更快的方法来实现这个目标呢?

在一个比较复杂的代码库上运行下面的脚本(SDL)在我这台机器上花了59秒。

#!/usr/bin/env python

import datetime
import subprocess
import time

commit = "HEAD"

start = time.time()

file_names = subprocess.check_output(["git", "ls-tree", "--name-only", "-r", commit], text=True).strip().split("\n")

print(f"[{time.time() - start:.4f}] git ls-tree finished")

file_times = list(datetime.datetime.fromisoformat(subprocess.check_output(["git", "log", "-1", "--pretty=format:%cI", commit, "--", name], text=True).strip()) for name in file_names)

print(f"[{time.time() - start:.4f}] git info finished")

3 个回答

1

我之前写了一个Python脚本,里面用了很多次git命令。后来我加了一个功能,如果有的话就用pygit2来代替git命令。结果发现,使用git命令的速度非常慢。这并不是因为git本身慢,而是因为每次调用git命令时,系统需要创建和销毁进程,而且还要多次打开同一个仓库,这样就导致了很大的延迟。因此,我建议大家尽量用pygit2来完成这个任务。

我的项目可以作为参考:https://github.com/eantoranz/git-duplicate

虽然你可能不能完全按照你的需求使用这个项目,但你可以用pygit2和二进制的git来运行它(有个选项可以不使用pygit2),这样你就能看到性能上的差别。我可以简单总结一下这个差别:完全没有可比性

另外,正如我在评论中提到的,你可以考虑换个思路,先进行一次日志调用,然后对每个提交运行git ls-tree -r <some-commit>来检查哪些内容发生了变化(随着你查看历史记录,待处理的内容列表应该会变得更短)……这样你就能获取到相关提交的信息。

1

根据@jthill的建议,我写了下面这个Python脚本:

#!/usr/bin/env python

import datetime
import subprocess
import time

commit = "HEAD"

start = time.time()

commit_paths = set(subprocess.check_output(["git", "ls-tree", "--name-only", commit], text=True).strip().split())

print(f"[{time.time() - start:.4f}] git ls-tree finished")

path_times = {}

git_log_out = subprocess.check_output(["git", "log", "--name-status", '--pretty=time=%cI'], text=True).splitlines(keepends=False)
current_time = None
for line in git_log_out:
  if not line:
    continue
  if line.startswith("time="):
    current_time = datetime.datetime.fromisoformat(line.removeprefix("time="))
    continue
  mod_type, paths = line.split(maxsplit=1)
  assert current_time is not None
  for path in paths.split():
    if path in commit_paths and path not in path_times:
      path_times[path] = current_time

print(f"[{time.time() - start:.4f}] git log finished")

在我的电脑上运行大约需要0.85秒。

6

基本的想法是对 git log --name-status 的结果进行后处理,添加你想要的每次提交的信息,然后查找你感兴趣的名字第一次出现的地方。下面是一个包含所有内容的版本:

 git log --name-status --pretty=%ci | awk -F$'\t' '
         NF==1 { stamp=$0; next }
         !seen[$2]++ { print stamp,$0 }
' | sort -t$'\t' -k2,2

就像往常一样,根据个人口味调整。你是在用机械硬盘吗?我在用便宜的SSD进行SDL默认检出时,耗时0.548秒,这样快了超过一百倍。不过,这样做的好处是,它在历史记录中走过的路径少了1500多倍,所以这是一个优势。

撰写回答