git预接收挂钩可以评估传入的提交吗?

时间:2014-03-20 22:20:56

标签: git bash githooks pre-commit-hook

我尝试编写服务器端预接收git挂钩来评估提交时的提交。

根据answers here,通过搜索git日志并过滤掉我想要的格式,可以轻松实现这一目标:'。

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

#!/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,而我想查询传入< / em>提交。

如何修改此脚本,以便在尚未收到的&#39;上运行git loggit show承诺?

1 个答案:

答案 0 :(得分:17)

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

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

&#34;测试代码&#34;然后需要查看至少一些以及所有三个项目,具体取决于要执行的测试。

如果建议创建参考名称$oldsha0中的值将为40 $refname s。也就是说,$refname(通常类似refs/heads/masterrefs/tags/v1.2,但refs/中的任何名称都可以显示:例如refs/notes/commits现在不存在接收存储库,但将存在,如果允许更改,将指向$newsha

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

如果建议更新引用名称$refname,即它当前指向git对象$oldsha,并且如果允许更改,则两者的值将为非零值,它将指向改为使用新对象$newsha


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

您需要专门查看正在进入的任何新对象。您如何知道有哪些新对象进入?这取决于提供的$refname以及可能其他重新命名的内容。

如果要删除refname,则不会有任何新内容。是否将删除任何基础git 对象(垃圾收集)取决于该refname是否为&#34; last&#34 ;对这些对象的引用。例如,假设整个标准输入序列由两个指令组成:

  • 删除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是&#34;安全&#34;因为带注释的标记G将通过v1.1标记保留它们,所以任何提交都不会消失。

删除标记v1.1是&#34;安全&#34;(ish),因为分支foo将通过refs/heads/foo引用保留它们,因此不会撤消提交。 (带注释的标签对象本身会消失。由您决定是否允许这样做)

但是,删除两者 安全:提交EF将无法访问并将被收集。 (无论如何,这都取决于你。)

另一方面,可能与这两个指令一起,stdin包含第三个指令:

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

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

做一个完整的分析是棘手的;通常可以做一个允许&#34;安全&#34;的分段分析。操作(无论你决定这些是什么),并强迫用户在一个&#34; safe&#34;中分段推送更新。方式(首先创建分支foo2,然后仅删除分支foo作为单独的推送)。


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

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

在大多数&#34;正常&#34;预接收钩子你使用下面概述的方法,但我们有替代这个特定的任务。


这是一种简短的修改方法,可以处理最常见,通常最有趣的案例。假设有人建议将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,并且实际的提案是&#34;创建refs/heads/bar指向E }&#34 ;.在这种情况下,我们应该再次查看提交DE,但没有明显的方法可以了解D

只有在某些情况下才有效的非显而易见的方法是找到在给定建议的情况下可以访问的对象,这些对象目前根本无法访问:

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

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


现在让我们考虑您的具体情况,您希望查看所有要提交的提交。这是处理这个问题的一种方法。

对于所有提议的更新:

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

如果我们有一些删除,我们已经积累了一些SHA,拒绝尝试:​​它太难了。让用户将操作分开。

否则,如果我们没有累积的SHA,我们必须删除(或者根本没有 - 不应该发生,但是无害);允许这个(退出0)。

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

使用建议的新SHA作为起点,找到所有可以访问的git对象,排除当前可以以任何名称访问的所有对象。这些都是新的对象。

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

如果我们走得这么远,一切都很好;允许整个更新。

以代码形式:

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