【问题标题】:Git: pre-receive hook to allow only merges and not direct commits into masterGit:pre-receive hook 只允许合并而不是直接提交到 master
【发布时间】:2015-05-18 10:25:25
【问题描述】:

我在 git 远程分支上创建 pre-receive 挂钩时遇到问题,按照我的意愿行事。

有什么问题?

不应允许直接提交到主分支。只允许合并到 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

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

谢谢!

【问题讨论】:

    标签: git git-merge githooks git-commit


    【解决方案1】:

    下面是简短的回答:查看以下产生的值:

    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 被反转。

    【讨论】:

      【解决方案2】:

      你在 pre-receive 钩子中得到的是分支的前一个和新的提示,所以你将不得不检查添加的提交列表,并查看其中是否有任何未合并:

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

      --first-parent 将输出限制为来自主行的提交,即跳过已合并的提交(可通过第二个/第三个/...父级访问)。

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

      【讨论】:

      • “快进合并(很难与一系列正常提交区分开来)”AFAIK 无法区分一系列提交和快进合并。这只是我们合并到相关提交的分支的更新。
      • @Zeeker 您可以通过检查另一个分支上是否存在相同的提交来猜测快进合并。在某些情况下,这可能是充分的证据。不过,它远非防错。
      • 我明白了,但这有点牵强。至少在本地,reflog 可以使一个更聪明,但这显然不包括我们推送到的任何远程。
      • "fast-forward-ness" 实际上是标签移动的一个属性:一个或多个合并提交的典型推送通常本身就是一个快进操作(因此在没有--force 的情况下允许) .
      • @MarcelBalzer 不是真的。 FF 合并与正常的一系列提交没有区别。如果您对工作流程有某些限制,则可以将一系列提交检测为来自另一个分支的 FF 合并,但这很麻烦,而且我个人更喜欢在这种情况下拒绝除真正的合并提交之外的任何内容。
      猜你喜欢
      • 2017-09-08
      • 1970-01-01
      • 2016-09-19
      • 1970-01-01
      • 1970-01-01
      • 2012-12-06
      • 2017-07-14
      • 1970-01-01
      • 2010-11-02
      相关资源
      最近更新 更多