Git预接收钩子可以评估传入的提交吗?

9
我想编写一个服务器端的pre-receive git hook,以便在提交被推送时对其进行评估。
根据这里的答案,可以通过搜索git log并使用“format:”过滤出所需内容,这应该很容易实现。
我已创建了以下pre-commit脚本。
#!/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时,结果是针对当前HEAD的,而我想查询即将到来的提交。
我该如何修改这个脚本以在“尚未接收到”的提交上运行git loggit show
1个回答

20

注意:还可以参考理解git rev-list,以了解下面代码的工作原理。

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

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

“测试代码”需要查看至少一些,也可能是全部三个项目,具体取决于要执行的测试。
如果正在建议创建引用名称$refname,则$oldsha中的值将为40个0。也就是说,在接收存储库中,$refname(通常类似于refs/heads/master或refs/tags/v1.2,但任何名称都可以出现:例如refs/notes/commits)现在不存在,但如果允许更改,则会存在并指向$newsha。
如果正在建议删除引用名称$refname,则$oldsha中的值将为40个0。也就是说,$refname现在存在并指向对象$oldsha;如果允许更改,则将删除该引用名称。
如果引用名称$refname正在建议更新,则两者的值都不为零,即它当前指向git对象$oldsha,并且如果您允许更改,则它将指向新对象$newsha。
如果只运行git log或git show,则git使用通过运行git rev-parse HEAD找到的SHA-1。在典型的接收存储库中,HEAD是一个符号引用,指向refs/heads/master(文件HEAD实际上包含字符串ref: refs/heads/master),因此您将看到分支master上最顶部的提交(正如您所观察到的)。
您需要特别查看任何新对象的进入情况。如何知道哪些新对象正在进入?这取决于正在发生的$refname以及可能的其他引用名称。
如果要删除refname,则没有任何新内容进入。是否删除(垃圾回收)任何基础git对象取决于该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是“安全的”,因为通过标记v1.1,提交将被保留在注释标记G中。
删除标记v1.1是“安全的”(有点),因为通过引用refs/heads/foo,分支foo将保留这些提交。 (注释标记对象本身将消失。是否允许此操作由您决定)
但是,同时删除两个不是安全的:提交EF将变得无法访问并被收集。 (是否仍然允许此操作由您决定。)
另一方面,stdin可能会包含第三个指令:
创建refs/heads/foo2,指向提交H,提交H指向其父提交G [编辑:现在重新阅读时,我注意到惊人的假设是G是提交对象而不是标记对象。如果我们假设G是提交对象,则下面的内容就正确了,但上面的内容至少有一点错误。但是,整体思路 - 通过具有外部引用来保护DAG - 仍然正确,这应该基本上是有道理的。]
这种情况下,删除foo实际上是安全的,因为新分支foo2将保留提交H,后者将保留提交G
进行完整分析很棘手;通常最好只进行分段分析,以允许“安全”操作(您决定这些操作),并强制用户以“安全”方式逐步推送更新(例如,首先创建分支foo2,然后单独推送删除分支foo)。
如果您只想查看新的提交,则对于每个引用更新:
- 如果它是删除,则允许它(或使用其他规则)。 - 如果它是创建或修改,请查找未在先前可访问的提交对象,并检查这些提交。
在大多数“正常”的pre-receive钩子中,您会使用以下方法,但是对于此特定任务,我们有一种替代方案。

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

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,而实际提案是“创建指向Erefs/heads/bar”。在这种情况下,我们应该再次查看提交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是从哪里来的?我在我的pre-receive脚本中添加了以下调试,所有以下内容都为0或null https://gist.github.com/spuder/9795078 - spuder
哦,你把这些回显行添加到哪里了?旧的和新的SHA-1以及refname仅在第一个“while”循环内有效。我将每个新的sha1累加到$new_list中,并在循环退出后使用它。 - torek
是的。原因在于git push可以同时推送许多参考更新。我可以同时推送400个新分支和75个标签。 - torek
2
再次感谢torek,我扩展了您的答案以验证电子邮件地址并检查名称数组。我在此处发布了带有归属的内容,https://github.com/spuder/git-hooks/blob/master/pre-commit - spuder
3
长话短说,但其中有一个非常有价值的提示:git rev-list $newsha --not --all。太棒了。 - spam_eggs
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接