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

时间:2015-03-16 10:30:56

标签: git git-merge githooks git-commit

我在git远程分支上创建pre-receive挂钩时出了问题,做了我想做的事。

问题是什么?

不应允许直接提交到主分支。只允许合并到主分支中。

解决方案

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

#!/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

有没有人知道,如何检查更改是否合并?

谢谢!

2 个答案:

答案 0 :(得分:7)

这里是简短的回答:看看产生的价值:

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

你希望这个为零。继续阅读解释(和警告)。


你的循环有正确的轮廓:

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

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

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

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

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

要查找这两组提交,您应该使用master,因为这正是它的工作:生成由某个表达式指定的SHA-1列表。这里你想要的两个表达式是#34;所有提交ID都可以从一个修订版中找到,但是还没有从其他ID"中找到它们。在git rev-list个术语中,对于两个修订说明符git rev-listgit rev-list $r1 ^$r2,这些git rev-list $r2..$r1 1 或等效$r1。当然,这两个revspecs只是建议的$r2的旧ID和新ID。

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


在这个特定的情况下,你的目标不是自己生成这些提交列表(虽然这样可行),而是从这些列表中选择

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

您肯定希望阻止合并的添加内容,但允许添加内容。在这种情况下,添加git rev-list(也可以拼写--max-parents=1)告诉--no-merges禁止具有两个或更多父项的提交,即合并。添加git rev-list可以获得满足此要求的提交计数,而不是合并,因为零或一个父级"约束。如果此计数为零,则根据定义添加的任何提交必须合并。

因此:

--count
例如,

足以强制执行此特定约束。


1 几乎但不完全相同,你可以写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 :不同之处在于git rev-list $r1 --not $r2的效果徘徊,所以如果你要添加另一个修订版身份--not $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被反转。

答案 1 :(得分:4)

您在预接收挂钩中获得的是分支的前一个和新的提示,因此您将不得不检查已添加的提交列表,并查看它们中的任何一个是否未合并:

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

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

可能是复杂化:快进合并(很难与一系列正常提交区分开来)。