【问题标题】:Can git pre-receive hooks evaluate the incoming commit?git pre-receive 钩子可以评估传入的提交吗?
【发布时间】:2014-04-28 02:10:12
【问题描述】:

我正在尝试编写一个服务器端 pre-receive git 挂钩来评估提交时的提交。

根据answers here,这应该很容易通过搜索 git log 并使用“格式:”过滤掉我想要的内容来实现。

我创建了以下预提交脚本。

#!/bin/bash

set -x #for debugging, TODO: remove
echo "parameters are" $@
echo "1 is " $1

#List of banned users
bannedusers=( root )

author_name=$(git show --pretty=oneline --pretty=format:%an | head -n1)
author_email=$(git show --pretty=oneline --pretty=format:%ae | head -n1)

committer_name=$(git show --pretty=oneline --pretty=format:%cn | head -n1)
committer_email=$(git show --pretty=oneline --pretty=format:%ce | head -n1)

commit_users=( "${author_name}" "${committer_name}" )


  for acommituser in "${commit_users[@]}"
  do
    :
    echo $acommituser #for debugging, TODO: remove
    for abanneduser in "${bannedusers[@]}"
    do
      :
        echo $abanneduser #for debugging, TODO: remove
        if [[ $abanneduser == $acommituser ]]; then
         echo "################################################################"
         echo "Commits from $abanneduser are not allowed"
         echo "git config user.name bob builder --replace-all"
         echo "git config user.email bob@aol.com"
         echo "git commit --amend --reset-author"
         echo "################################################################"
         exit 1
        fi
    done
  done

我发现当我在服务器上运行git showgit log 时,结果是current HEAD,而我想查询incoming 提交.

如何修改此脚本以在“尚未收到”提交时运行 git loggit show

【问题讨论】:

    标签: git bash githooks pre-commit-hook


    【解决方案1】:

    (注意:另请参阅Understanding git rev-list,了解以下代码的工作原理。)

    您需要使用标准输入中提供的 SHA-1 ID:

    while read oldsha newsha refname; do
        ... testing code goes here ...
    done
    

    “测试代码”然后需要查看至少一些甚至可能所有三个项目,具体取决于要执行的测试。

    如果建议创建引用名称 $refname$oldsha 中的值将为 40 0s。也就是说,$refname(通常类似于refs/heads/masterrefs/tags/v1.2,但refs/ 中的任何名称都可以出现:例如refs/notes/commits)现在在接收存储库中不存在,但会存在并将指向如果您允许更改,请发送至 $newsha

    如果建议删除引用名称 $refname$newsha 中的值将为 40 0s。也就是说,$refname 现在确实存在并且指向对象$oldsha;如果您允许更改,该引用名称将被删除。

    如果建议更新引用名称$refname,则两者的值都将为非零,即它当前指向git对象$oldsha,如果您允许更改,它将指向新对象@改为 987654341@。


    如果你只是运行 git loggit show,git 会使用它通过运行 git rev-parse HEAD 找到的 SHA-1。在典型的接收存储库中,HEAD 是指向refs/heads/master 的符号引用(文件HEAD 字面上包含字符串ref: refs/heads/master),因此您将看到分支master 上的最高提交(当您观察到)。

    您需要专门查看任何进入的新对象。您如何知道哪些新对象进入?这取决于所提供的$refname 发生了什么,可能还有其他 refnames。

    如果要删除 refname,则不会出现任何新内容。是否会删除任何底层 git objects(垃圾收集)取决于该 refname 是否是对这些对象的“最后”引用.例如,假设整个标准输入序列由两个指令组成:

    • 删除refs/heads/foo
    • 删除refs/tags/v1.1

    进一步假设refs/heads/foo(分支foo)在此提交图表中指向提交F,标签v1.1指向带注释的标签G

    A - B - C - D   <-- refs/heads/master
          \
            E - F   <-- refs/heads/foo
                 \
                  G <-- refs/tags/v1.1
    

    删除分支foo 是“安全的”,因为注释标记G 将通过v1.1 标记保留它们。

    删除标签v1.1 是“安全的”(ish),因为分支foo 将通过refs/heads/foo 引用保留它们,因此不会消失任何提交。 (带注释的标签对象本身会消失,是否允许由你决定)

    但是,删除 both安全的:提交 EF 将变得无法访问并被收集。 (这取决于你是否允许这样做。)

    另一方面,除了这两个指令之外,stdin 还可能包含第三个指令:

    • 创建refs/heads/foo2 指向提交H,提交H 指向提交G 作为其父[编辑:现在重新阅读本文时,我注意到G 的明显假设是提交对象而不是标记对象。如果我们假设G 是一个提交对象,那么下面的其余部分是正确的,但上面的内容至少有点错误。但是,一般的想法——DAG 受到外部引用的保护——仍然是正确的,这应该是最有意义的。]

    在这种情况下,删除 foo 毕竟是安全的,因为新分支 foo2 将保留提交 H,这将保留提交 G

    做一个完整的分析是很棘手的;通常最好只进行允许“安全”操作(无论您决定这些是什么)的分段分析,并强制用户以“安全”方式分段推送更新(首先创建分支foo2,然后才删除分支@例如,987654378@ 作为单独的推送)。


    如果您只想查看 次提交,那么对于每个参考更新:

    • 如果是删除,请允许(或使用其他规则)。
    • 如果是创建或修改,请找到之前无法访问的提交对象,并检查这些提交。

    在大多数“正常”的预接收挂钩中,您会使用下面列出的方法,但我们为这个特定任务提供了替代方法。


    有一种修改的捷径可以处理最常见,通常也是最有趣的情况。假设有人提议将refs/heads/foo1234567... 更新为9876543...。可能该范围内的某些对象已经存在,例如,1234567 可能是提交的 ID C9876543 是提交的 ID E

    A - B - C           <-- refs/heads/foo
              \
                D - E   <-- refs/heads/bar
    

    在这种情况下,这将检查对象 D 和 E。如果提交 DE 刚刚上传但还没有 引用,这也是正确的,即建议的更新是添加DE,图形当前如下所示:

    A - B - C           <-- refs/heads/foo
              \
                D - E   [no reference yet]
    

    无论哪种情况,一个简单的:

    git rev-list $oldsha..$newsha
    

    生成您应该查看的对象 ID。

    对于新的参考资料,没有捷径可走。例如,假设我们有上面显示的相同的五个提交,相同的refs/heads/foo 但没有refs/heads/bar,实际的提议是“创建refs/heads/bar 指向E”。在这种情况下,我们应该再次查看提交 DE,但没有明显的方法可以了解 D

    仅在某些情况下才有效的非显而易见的方法是找到在所提议的创建下可以访问的对象,而这些对象当前根本无法访问:

    git rev-list $newsha --not --all
    

    在这种特殊情况下,这将再次生成 DE 的 ID。


    现在让我们考虑一下您的特殊情况,您希望查看所有提议添加的提交。这是处理这个问题的一种方法。

    对于所有建议的更新:

    • 如果这是一个删除,我们有一些删除。
    • 如果这是一个创建或更新,我们有一些新的提交;积累新的 SHA。

    如果我们有一些删除并且我们已经积累了一些 SHA,请拒绝尝试:​​这太难了。让用户分离出操作。

    否则,如果我们没有累积的 SHA,我们只能进行删除(或者可能什么都没有——不应该发生,但无害);允许这个(退出 0)。

    否则我们必须有一些新的 SHA-1 值。

    使用提议的新 SHA 作为起点,查找所有可访问的 git 对象,不包括当前以任何名称可访问的所有对象。这些都是新对象。

    对于每一个提交,检查它是否被禁止。如果是,则拒绝整个操作(即使某些部分可能成功);和以前一样,很难弄清楚,所以让用户将“好”操作与“坏”操作分开。

    如果我们走到这一步,一切都会好起来的;允许整个更新。

    代码形式:

    #! /bin/sh
    # (untested)
    NULL_SHA1="0000000000000000000000000000000000000000" # 40 0's
    new_list=
    any_deleted=false
    while read oldsha newsha refname; do
        case $oldsha,$newsha in
        *,$NULL_SHA1) # it's a delete
            any_deleted=true;;
        $NULL_SHA1,*) # it's a create
            new_list="$new_list $newsha";;
        *,*) # it's an update
            new_list="$new_list $newsha";;
        esac
    done
    $any_deleted && [ -n "$new_list" ] && {
        echo 'error: you are deleting some refs and creating/updating others'
        echo 'please split your push into separate operations'
        exit 1
    }
    [ -z "$new_list" ] && exit 0
    
    # look at all new objects, and verify them
    # let's write the verifier function, including a check_banned function...
    check_banned() {
        if [ "$1" = root ]; then
            echo "################################################################"
            echo "Commits from $1 are not allowed"
            echo ... rest of message ...
            exit 1
         fi
    }
    check_commit() {
        check_banned "$(git log -1 --pretty=format:%an $1)"
        check_banned "$(git log -1 --pretty=format:%cn $1)"
    }
    
    
    git rev-list $new_list --not --all |
    while read sha1; do
        objtype=$(git cat-file -t $sha1)
        case $objtype in
        commit) check_commit $sha1;;
        esac
    done
    

    【讨论】:

    • 也许我应该提出一个新问题,但是 $oldsha 来自哪里?我在我的预接收脚本中添加了以下调试,以下都是 0 或 null gist.github.com/spuder/9795078
    • 假设我没有搞砸脚本:oldsha 应该由while read oldsha newsha refname 行设置。所以它来自标准输入,当 git 运行外观时由 git 提供。
    • 我添加了我的代码作为要点。您为我提供的代码运行良好,但我很困惑 $oldsha $refname 和 $newsha 为何都可以为空,但您的脚本仍然有效。
    • 再次感谢 torek,我扩展了您的答案以验证电子邮件地址并检查一系列名称。我把它贴在这里并注明出处,github.com/spuder/git-hooks/blob/master/pre-commit
    • 长答案——但里面有一个非常有价值的提示:git rev-list $newsha --not --all。太棒了。
    猜你喜欢
    • 2017-12-30
    • 1970-01-01
    • 1970-01-01
    • 2013-06-26
    • 1970-01-01
    • 2017-01-18
    • 2021-09-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多