(注意:另请参阅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/master 或refs/tags/v1.2,但refs/ 中的任何名称都可以出现:例如refs/notes/commits)现在在接收存储库中不存在,但会存在并将指向如果您允许更改,请发送至 $newsha。
如果建议删除引用名称 $refname,$newsha 中的值将为 40 0s。也就是说,$refname 现在确实存在并且指向对象$oldsha;如果您允许更改,该引用名称将被删除。
如果建议更新引用名称$refname,则两者的值都将为非零,即它当前指向git对象$oldsha,如果您允许更改,它将指向新对象@改为 987654341@。
如果你只是运行 git log 或 git 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 是不安全的:提交 E 和 F 将变得无法访问并被收集。 (这取决于你是否允许这样做。)
另一方面,除了这两个指令之外,stdin 还可能包含第三个指令:
- 创建
refs/heads/foo2 指向提交H,提交H 指向提交G 作为其父[编辑:现在重新阅读本文时,我注意到G 的明显假设是提交对象而不是标记对象。如果我们假设G 是一个提交对象,那么下面的其余部分是正确的,但上面的内容至少有点错误。但是,一般的想法——DAG 受到外部引用的保护——仍然是正确的,这应该是最有意义的。]
在这种情况下,删除 foo 毕竟是安全的,因为新分支 foo2 将保留提交 H,这将保留提交 G。
做一个完整的分析是很棘手的;通常最好只进行允许“安全”操作(无论您决定这些是什么)的分段分析,并强制用户以“安全”方式分段推送更新(首先创建分支foo2,然后才删除分支@例如,987654378@ 作为单独的推送)。
如果您只想查看 新 次提交,那么对于每个参考更新:
- 如果是删除,请允许(或使用其他规则)。
- 如果是创建或修改,请找到之前无法访问的提交对象,并检查这些提交。
在大多数“正常”的预接收挂钩中,您会使用下面列出的方法,但我们为这个特定任务提供了替代方法。
有一种修改的捷径可以处理最常见,通常也是最有趣的情况。假设有人提议将refs/heads/foo 从1234567... 更新为9876543...。可能该范围内的某些对象已经存在,例如,1234567 可能是提交的 ID C,9876543 是提交的 ID E:
A - B - C <-- refs/heads/foo
\
D - E <-- refs/heads/bar
在这种情况下,这将检查对象 D 和 E。如果提交 D 和 E 刚刚上传但还没有 引用,这也是正确的,即建议的更新是添加D 和E,图形当前如下所示:
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”。在这种情况下,我们应该再次查看提交 D 和 E,但没有明显的方法可以了解 D。
仅在某些情况下才有效的非显而易见的方法是找到在所提议的创建下可以访问的对象,而这些对象当前根本无法访问:
git rev-list $newsha --not --all
在这种特殊情况下,这将再次生成 D 和 E 的 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