Git: pre-receive hook 只允许合并而不是直接提交到 master

Git: pre-receive hook to allow only merges and not direct commits into master

我在 git 远程分支上创建 pre-receive 挂钩时遇到了问题,我想做什么。

有什么问题?

不允许直接提交到 master 分支。只允许合并到 master 分支。

解决方案

到目前为止,我的解决方案是检查来自用户的推送是否有变化,而主服务器是否受到影响。但问题是我无法区分更改是直接提交还是合并。

#!/bin/sh
while read oldrefid newrefid refname
do
if [ "$refname" = "refs/heads/master" ]; then
        echo $(git merge-base $oldrefid $newrefid)
        echo "---- Direct commit to master branch is not allowed ----"
        echo "Changes only with a merge from another branch"
        exit 1
fi
done

有谁知道如何检查更改是否为合并?

谢谢!

你在预接收挂钩中得到的是分支的前一个和新的提示,所以你将不得不检查已添加的提交列表,看看是否有任何不是合并:

nonmerges=$(git rev-list --no-merges --first-parent $oldrefid..$newrefid | wc -l)
[ "$nonmerges" -eq 0 ] && exit 0

--first-parent 将输出限制为来自主线的提交,即跳过合并的提交(可通过 second/third/... parent 访问)。

可能的并发症:快进合并(很难与一系列正常提交区分开来)。

这里是简短的回答:看看由以下因素产生的价值:

git rev-list --count --max-parents=1 $oldrefid..$newrefid

您希望它为零。继续阅读解释(和注意事项)。


你的循环有正确的轮廓:

  • 阅读所有参考更新;
  • 对于更新您关心的分支(或其他引用)的那些,执行一些检查。

诀窍在于执行检查。考虑您收到的另外两条信息,即旧的和新的 SHA-1 ID,并且在这些挂钩中,这两个 SHA-1 ID 中的一个(但不是两个)可能都是 0(意思是正在创建或删除 ref。

要坚持更改不是创建或删除,您的测试应确保 SHA-1 均不为全零。 (如果你愿意假设只需要检查删除,你可以只检查新的 SHA-1 是否不是全零。但是如果创建可能发生——只有在 master分支毕竟被删除了,例如,有人登录到服务器接收推送,然后手动删除它——你仍然需要确保旧的 SHA-1 在最终测试中不全为零。显然是这种删除是可能,问题是你要不要写代码来处理这种情况。)

无论如何,最典型的推送只是更新引用。请注意,任何新的提交都已写入存储库(如果您拒绝推送,它们将被垃圾收集),因此此时您的任务是:

  • 查找并验证引用用于命名的任何提交,它将不再命名(这些提交将被非快进推送删除);和
  • 找到并验证引用现在将命名的任何提交,它以前没有命名(这些是将通过推送添加的新提交,无论是否快进:记住新推送可以删除一个或多个提交,同时添加一个或多个提交)。

要找到这两组提交,您应该使用 git rev-list,因为这正是它的工作:生成由某个表达式指定的 SHA-1 列表。您需要的两个表达式是 "all commit IDs find-able from one revision, that are not already find-able from some other ID"。在 git rev-list 术语中,这些是 git rev-list $r1 ^$r21 或等效的 git rev-list $r2..$r1,用于两个修订说明符 $r1$r2.当然,这两个 revspecs 只是提议 push.

的旧 ID 和新 ID

这两个 ID 的顺序决定了哪一组提交 git rev-list 列出了:将被删除的那些——对于快进操作,这组是空的——以及将被添加的那些。


在这个 特殊的 案例中,您的目标不是自己生成这些提交列表(尽管这会起作用),而是 select 一些东西 来自 这些列表。

您可能希望防止提交删除(即,即使执行 push 的用户指定了强制标志,也能强制快进)。在这种情况下,只需验证 "to be removed" 列表是否为空就足够了。你可以通过确保列表实际上是空的,或者——在 shell 脚本中更简单——让 git rev-list 为你计算它们并检查结果数字是否为零来做到这一点。

您绝对希望阻止 不是 合并的添加,但允许合并的添加。在这种情况下,添加 --max-parents=1(也可以拼写为 --no-merges)告诉 git rev-list 抑制具有两个或多个父项的提交,即合并。添加 --count 可以获得满足此 "not a merge because zero or one parent" 约束的提交计数。如果此计数为零,则根据定义,添加的任何提交都必须是合并。

因此:

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 被倒置。