在 git "update" 挂钩中获取每个分支的提交

Get commits per branch in git "update" hook

考虑下一个创建所需 git 日志的伪代码:

git checkout master
git checkout -b 123-test-branch-1
git commit -m "#123 b1 c1"
git commit -m "#123 b1 c2"
git push
git checkout master
git checkout -b 456-test-branch-2
git commit -m "#456 b2 c1"
git commit -m "#456 b2 c2"
git push
git checkout 123-test-branch-1
git merge 456-test-branch-2
git commit -m "#123 b1 c3"
git push

在现实世界中,我在远程 git 存储库中的 update 挂钩验证分支名称和提交消息格式。分支名称和提交消息必须包含问题编号,例如,在 123-test-branch-1#123 b1 c1 中,问题编号是 123。推送分支时,挂钩从分支和提交消息中提取问题编号并进行比较。如果它们不相等,钩子将错误退出。

当我推送只有 "own" 提交的分支时,这很好用。但是,上面的 git 日志示例,推送分支 123-test-branch-1 有来自合并分支 456-test-branch-2 的提交,因此 hook 尝试将来自两个分支的所有提交仅与推送分支 123-test-branch-1 进行比较并退出错误,因为来自 456-test-branch-2 的提交有问题编号 456,而预期 123

为了接收提交,我使用 git log --pretty=%s ${oldRef}..${newRef},其中 oldRefnewRef 是 "update" 钩子参数。

所以,我的问题是如何解决这个问题。以某种方式对每个分支进行分组提交,或者从现在推送的分支中过滤提交(但是如果 456-test-branch-2 是本地分支并且从未推送并且从未验证过,钩子可能会跳过无效提交),或者其他东西。

更新挂钩没有获得足够的信息:它无法获得传入哈希 ID 的“全局视图”。 pre- 或 post-receive 钩子确实,1,因此确实获得了足够的信息——至少对于某些目的。

最大的问题在于新分支的创建。例如,假设更新正在传递名称 refs/heads/arefs/heads/b,其中两个名称都是新的(它们的旧哈希是空哈希),并且 refs/heads/a 指向提交 N2refs/heads/b 指向此图形片段中的提交 N3

                 N2   <-- A
                /
...--O--O--O--N1
                \
                 N3   <-- B

其中所有 O 提交都是“旧的”(例如,之前可以从现有分支或标记名称访问)并且 N 提交是“新的”,因为永远无法访问之前,因此被列为:

git rev-list refs/heads/a refs/heads/b --not \
    $(git for-each-ref --format '%(refname) |
        egrep -v '^(refs/heads/a|refs/heads/b)$')

很明显,这三个 N 提交是“新的”,但是您应该将 N1 分配给哪个分支?

对此没有唯一的正确答案。毕竟,提交 N1 在两个分支上。

无论如何,如果您更关心合并提交——例如:

...O1--O2--N1--N2   <-- A
              /
...-O3--O4--N3    <-- B

—您可能想要使用 --first-parent 遍历。在这里我们可以相信,基于这两个分支名称更新(AO2 移动到 N2BO4 移动到 N3)—N2 的第一个父级是 N1(有可能,但很难,相反),所以跟随 --first-parents 将“分配”提交 N1A 而不是 B。同样,如果您是从更新挂钩而不是预接收挂钩或 post-接收挂钩执行此操作,那可能是您能做的最好的,因为您没有获得 both 的信息AB建议更新


1一个 post-receive 挂钩在删除所有锁后是 运行,因此它与可能更新引用名称的其他操作竞争。预接收挂钩获取所有建议的更新,因此在引用名称更新周围有一个大锁,所以从某种意义上说,在那里做这项工作显然更安全。

缺点是预接收挂钩 运行 持有大锁,所以它做的任何“慢”事情都会禁止并行。