下面是简短的回答:查看以下产生的值:
git rev-list --count --max-parents=1 $oldrefid..$newrefid
您希望它为零。继续阅读解释(和注意事项)。
你的循环有正确的轮廓:
- 阅读所有参考更新;
- 对于那些更新您关心的分支(或其他参考)的人,请执行一些检查。
诀窍在于执行检查。考虑您收到的另外两条信息,即旧的和新的 SHA-1 ID,在这些钩子中,这两个 SHA-1 ID 中的一个(但不是两者)可能都是0s(意味着 ref正在创建或删除)。
要坚持更改不是创建或删除,您的测试应确保 SHA-1 都不是全零。 (如果您愿意假设只需要检查删除,您可以检查新的 SHA-1 是否不是全零。但是如果可能发生创建 - 只有在 master 分支以某种方式获取时才会出现这种情况毕竟删除了,例如,有人登录到服务器接收推送,然后手动删除它——你仍然需要确保旧的 SHA-1 在最终测试中不是全零。显然这种删除是可能,问题是你要不要写代码来处理这种情况。)
无论如何,最典型的推送只是更新引用。请注意,任何新提交都已写入存储库(如果您拒绝推送,它们将被垃圾收集),因此您此时的任务是:
- 查找并验证引用用于命名的任何提交,它将不再命名(这些提交将被非快进推送删除);和
- 查找并验证引用现在将命名的任何提交,它以前没有命名(这些是将通过推送添加的新提交,无论是否快进:记住新推送可以删除一个或多个提交,同时添加一个或多个提交)。
要查找这两组提交,您应该使用git rev-list,因为这正是它的工作:生成由某个表达式指定的 SHA-1 列表。您在此处需要的两个表达式是“所有提交 ID 都可以从一个修订版中找到,而这些提交 ID 还不能从其他 ID 中找到”。在git rev-list 术语中,它们是git rev-list $r1 ^$r2,1 或等效的git rev-list $r2..$r1,用于两个修订说明符$r1 和$r2。当然,这两个 revspec 只是提议的 push 的旧 ID 和新 ID。
这两个 ID 的顺序决定了git rev-list 列出的提交集:将被删除的提交(该集对于快进操作是空的)以及将被添加的提交。
在这种特定 情况下,您的目标不是自己生成这些提交列表(尽管这会起作用),而是从这些列表中 选择一些东西。
您可能希望防止提交删除(即,即使执行push 的用户指定了强制标志,也要强制执行快进)。在这种情况下,只需验证“待删除”列表为空就足够了。您可以通过确保列表实际上是空的来做到这一点,或者(在 shell 脚本中更简单)让 git rev-list 为您计算它们并检查结果数字是否为零。
您肯定希望阻止不是合并的添加,但允许添加。在这种情况下,添加--max-parents=1(也可以拼写为--no-merges)告诉git rev-list 抑制具有两个或多个父级的提交,即合并。添加--count 可以获得满足此“不是合并,因为零个或一个父级”约束的提交计数。如果此计数为零,则添加的任何提交都必须根据定义进行合并。
因此:
n=$(git rev-list --count --max-parents=1 $oldrefid..$newrefid)
if [ $n -gt 0 ]; then
echo "disallowed: push adds $n non-merge commit(s)" 1>&2
exit 1
fi
例如,足以强制执行此特定约束。
1几乎但不完全等价,您可以写git rev-list $r1 --not $r2:不同之处在于--not 的效果会持续存在,因此如果您要添加另一个修订ID $r3 --not 将适用于 r3。也就是说,git rev-list A ^B C 表示yes-A, not-B, yes-C,但A --not B C 表示yes-A, not-B, not-C。请注意,在rev-list 语法中,B..A 表示A ^B,即恰好B 被反转。