bash git pre-receive hooks 可以评估传入的提交吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22546393/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-18 09:58:32  来源:igfitidea点击:

Can git pre-receive hooks evaluate the incoming commit?

gitbashgithookspre-commit-hook

提问by spuder

I'm trying to write a server side pre-receive git hook to evaluate commits as they are being pushed.

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

According to the answers here, this should easily be possible by searching git log and filtering out what I want with 'format:'.

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

I've created the following pre-commit script.

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

#!/bin/bash

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

#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 [email protected]"
         echo "git commit --amend --reset-author"
         echo "################################################################"
         exit 1
        fi
    done
  done

I find that when I run git showor git logon the server, the results are for the currentHEAD, whereas I want to query the incomingcommit.

我发现当我运行git showgit log在服务器上时,结果是针对当前HEAD 的,而我想查询传入的提交。

How could I modify this script to run git logor git showon the 'not-yet-recieved' commit?

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

回答by torek

You need to use the SHA-1 IDs being supplied on standard input:

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

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

The "testing code" then needs to look at at least someand maybe all threeitems, depending on the tests to be performed.

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

The value in $oldshawill be 40 0s if the reference name $refnameis being proposed to be created. That is, $refname(typically something like refs/heads/masteror refs/tags/v1.2, but any name in refs/can appear: refs/notes/commits, for instance) does not exist now in the receiving repository, but will exist and will point to $newshaif you allow the change.

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

The value in $newshawill be 40 0s if the reference name $refnameis being proposed to be deleted. That is, $refnamedoes exist now and points to object $oldsha; if you allow the change, that reference-name will be deleted.

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

The values of both will be nonzero if the reference name $refnameis being proposed to be updated, i.e., it currently points to git object $oldsha, and if you allow the change, it will point to new object $newshainstead.

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



If you just run git logor git show, git uses the SHA-1 it finds by running git rev-parse HEAD. In a typical receiving repository, HEADis a symbolic reference pointing to refs/heads/master(the file HEADliterally contains the string ref: refs/heads/master), so you will see the top-most commit on branch master(as you observed).

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

You need to look specifically at any new objects coming in. How do you know which new objects are coming in? That depends on what's happening to the supplied $refname, and possibly otherrefnames as well.

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

If the refname is to be deleted, nothing new is coming in. Whether any underlying git objectswill be deleted (garbage collected) depends on whether that refname is the "last" reference to those objects. For instance, suppose the entire standard input sequence consists of two directives:

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

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

Suppose further that refs/heads/foo(branch foo) points to commit Fin this commit-graph diagram, and tag v1.1points to annotated tag G:

进一步假设refs/heads/foo(branch foo) 指向F此提交图中的提交,并且 tagv1.1指向带注释的 tag G

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

Deleting branch foois "safe" in that no commits will go away because annotated tag Gwill retain them, via the v1.1tag.

删除分支foo是“安全的”,因为没有提交会消失,因为带注释的标签G将通过v1.1标签保留它们。

Deleting tag v1.1is "safe"(ish) in that no commits will go away because branch foowill retain them, via the refs/heads/fooreference. (The annotated tag object itself will go away. It's up to you whether to allow this)

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

However, deleting bothis notsafe: commits Eand Fwill become unreachable and will be collected. (It's up to you whether to allow this anyway.)

但是,删除这两个不是安全的:提交EF将无法访问,将被收集。(这取决于您是否允许这样做。)

On the other hand, it's possible that along with those two directives, stdin contains a third directive:

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

  • create refs/heads/foo2pointing to commit H, with commit Hpointing to commit Gas its parent [Edit: on re-reading this now, I notice the glaring assumption that Gis a commit object rather than a tag object. If we assume Gis a commit object the rest of the below is correct, but the above becomes at least a little wrong. However, the general idea—that the DAG is protected by having external references—is still right, and this should mostly make sense.]
  • createrefs/heads/foo2指向 commit H,commitH指向 commitG作为其父级 [编辑:现在重新阅读本文时,我注意到明显的假设G是提交对象而不是标记对象。如果我们假设G是一个提交对象,那么下面的其余部分是正确的,但上面的内容至少有点错误。然而,一般的想法——DAG 受外部引用保护——仍然是正确的,这应该是有道理的。]

in which case deletion of foois safe after all, as the new branch foo2will retain commit Hwhich will retain commit G.

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

Doing a complete analysis is tricky; it's often better to just do a piecewise analysis that allows "safe" operations (whatever you decide these are), and force users to push updates piecewise in a "safe" manner (create branch foo2first, and only then delete branch fooas a separate push, for instance).

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



If you only want to look at newcommits, then, for each reference update:

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

  • If it's a delete, allow it (or use other rules).
  • If it's a create or a modification, find commit objects it makes reachable that were not reachable before, and examine those commits.
  • 如果是删除,请允许(或使用其他规则)。
  • 如果是创建或修改,请查找它使之前无法访问的提交对象可访问,并检查这些提交。

In most "normal" pre-receive hooks you'd use the methods outlined below, but we have an alternative for this particular task.

在大多数“正常”预接收钩子中,您将使用下面概述的方法,但我们有针对此特定任务的替代方法。



There's a short-cut method for modifications that handles the most common, and usually most interesting, cases. Suppose someone proposes updating refs/heads/foofrom 1234567...to 9876543.... It's possible that some objects in the range already existed, e.g., perhaps 1234567is the ID of commit Cand 9876543is the ID of commit E:

有一种处理最常见、通常也是最有趣的情况的修改的捷径方法。假设有人提议refs/heads/foo从更新1234567...9876543...。这有可能是在范围内的一些对象已经存在,例如,可能1234567是提交的ID C,并9876543为提交的ID E

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

in which case this will examine objects D and E. This is also true if commits Dand Ehave just been uploaded but have noreferences yet, i.e., the proposed update is to add Dand Eand the graph currently looks like this:

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

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

In either case, a simple:

在任何一种情况下,一个简单的:

git rev-list $oldsha..$newsha

produces the object IDs you should look at.

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

For new references, there's no short-cut. For instance, suppose we have the same five commits shown above, with the same refs/heads/foobut no refs/heads/bar, and the actual proposal is "create refs/heads/barpointing to E". In this case, we should again look at commits Dand E, but there's no obvious way to know about D.

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

The non-obvious way, which only works in some cases, is to find objects that will be reachable given the proposed creation, that are not currently reachable at all:

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

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

In this particular case, this will again produce the IDs for Dand E.

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



Now let's consider your particular case, where you want to look at all commits that are being proposed-to-be-added. Here's a way to handle this one.

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

For all proposed updates:

对于所有建议的更新:

  • If this one is a delete, we have some deletes.
  • If this one is a create or update, we have some new commits; accumulate the new SHA.
  • 如果这是一个删除,我们有一些删除。
  • 如果这是一个创建或更新,我们有一些新的提交;累积新的 SHA。

If we have some deletes andwe have accumulated some SHAs, reject the attempt: it's too hard. Make the user separate out the operations.

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

Otherwise, if we have no accumulated SHAs, we must just have deletes (or maybe nothing at all—should not happen, but harmless); allow this (exit 0).

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

Otherwise we must have some new SHA-1 values.

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

Using the proposed new SHAs as starting points, find all git objects that would be reachable, excluding all objects that are currently reachable under any name. These are all the new objects.

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

For each one that is a commit, examine it to see if it's forbidden. If so, reject the entire operation (even if some parts could succeed); as before, it's too hard to figure out, so make the user separate out the "good" operations from the "bad" ones.

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

If we get this far, everything is OK; permit the entire update.

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

In code form:

以代码形式:

#! /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 [ "" = root ]; then
        echo "################################################################"
        echo "Commits from  are not allowed"
        echo ... rest of message ...
        exit 1
     fi
}
check_commit() {
    check_banned "$(git log -1 --pretty=format:%an )"
    check_banned "$(git log -1 --pretty=format:%cn )"
}


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