我们在一个大型团队(>100 名开发人员)的工作中使用 git,我正在编写不同的脚本来向管理层提供 git 统计信息。
管理层想知道的统计数据之一是提交实际推送到存储库的时间。他们并不真正关心作者日期或提交者日期,因为重要的是提交何时被推送并被 CI 服务器拾取。所以我必须实现像“推送日期”这样的东西。只是为了完整性(不是为了给自己做广告:))这是我的博文描述了细节。 基本上,当提交实际推送到远程存储库时,我使用自定义 git 注释来存储详细信息。
让我们考虑一个简单的任务:提供 A(不包括)和 B(包括)之间的所有提交的列表,并输出提交哈希、提交消息和推送日期
我可以做这样的事情:
git log A..B --notes=push-date --format=<begin>%H<separator>%s<separator>%N<end>
然后相应地解析事物。无论如何,这确实很慢。而且我也不喜欢进行字符串解析,我更喜欢强类型方法。
因此,为了解决性能问题并摆脱解析,我决定使用 LibGit2Sharp 库。
好吧,如果我们不碰笔记,它的工作速度会非常快,但一旦我尝试检索笔记,它就会变得非常非常慢:
# PowerShell script
$pushDateNote = $commit.Notes | Where-Object -FilterScript { $_.Namespace -eq "push-date" }
$pushDate = [DateTime]::Parse($pushDateNote.Message)
为了进行比较,如果我不包含注释,大约 2 秒内返回 200 次提交的结果。如果我添加笔记,时间会增加到 2 分钟。
我已经检查过这里的瓶颈是提交的搜索注释。看来git本身没有提交和注释之间的映射,所以它需要一直查找所有注释。 我刚刚检查了存储库中有 188921 个提交,因此最有可能的情况会变得更糟。所以我的解决方案根本不可扩展。
所以我的问题是:我做错了吗?也许 git 不是有效存储自己的元数据的正确工具?我现在正在考虑将所有元数据移动到外部数据库(例如 MSSQL)中。但我宁愿把所有东西都放在一处。或者,我想将提交和推送日期之间的整个映射序列化为一次提交中的注释
例如使用魔法哈希
4b825dc642cb6eb9a060e54bf8d69288fbee4904(git的半秘密空树对象可靠吗?为什么没有符号名?)
git notes add 4b825dc642cb6eb9a060e54bf8d69288fbee4904 -m serialized-data
$serializedData = git notes show 4b825dc642cb6eb9a060e54bf8d69288fbee4904
这将有助于仅检索一次数据,因此不会出现查找问题。但它会增加序列化-反序列化数据的额外开销,这对我来说看起来不太合适。
请分享您的想法。
Commit
对象访问注释使得 libgit2 在循环的每次迭代中访问注释树。更有效的方法是:
首先,加载您感兴趣的提交列表(显然您已经在这样做了)
push-date
仅一次并最终在这两个列表之间执行连接
:从内存角度来看,这会增加一些更多的压力,但应该会更快。
这可以在 C# 中使用以下代码完成:using (var repo = new Repository("your_repo_path"))
{
var notes = repo.Notes["push-date"];
var commits = repo.Commits.QueryBy(
new CommitFilter {Since = "1234567", Until = "89abcde"});
var pairs = from commit in commits
from note in notes
where note.TargetObjectId == commit.Id
select new {Commit = commit, Note = note};
foreach (var pair in pairs)
{
Debug.Write(pair.Commit.Sha + " : " + pair.Note);
}
}
这将输出在
push-date
命名空间中具有关联注释的提交。
注意
:如果您使用 QueryBy
语法来检索提交列表,请注意指定为
Until
的提交将从列表中排除(例如:如 git log A...B 中所示) 为了还显示在
push-date
命名空间中没有关联注释的提交,您可以使用以下 linq 查询:
var pairs2 = from commit in commits
join note in notes on commit.Id equals note.TargetObjectId into gj
from subnote in gj.DefaultIfEmpty()
select new { Commit = commit, Note = subnote };