注意:还可以参考理解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
将保留这些提交。 (注释标记对象本身将消失。是否允许此操作由您决定)
但是,同时删除两个不是安全的:提交
E
和
F
将变得无法访问并被收集。 (是否仍然允许此操作由您决定。)
另一方面,stdin可能会包含第三个指令:
创建
refs/heads/foo2
,指向提交
H
,提交
H
指向其父提交
G
[编辑:现在重新阅读时,我注意到惊人的假设是
G
是提交对象而不是标记对象。如果我们假设
G
是提交对象,则下面的内容就正确了,但上面的内容至少有一点错误。但是,整体思路 - 通过具有外部引用来保护DAG - 仍然正确,这应该基本上是有道理的。]
这种情况下,删除
foo
实际上是安全的,因为新分支
foo2
将保留提交
H
,后者将保留提交
G
。
进行完整分析很棘手;通常最好只进行分段分析,以允许“安全”操作(您决定这些操作),并强制用户以“安全”方式逐步推送更新(例如,首先创建分支
foo2
,然后单独推送删除分支
foo
)。
如果您只想查看
新的提交,则对于每个引用更新:
- 如果它是删除,则允许它(或使用其他规则)。
- 如果它是创建或修改,请查找未在先前可访问的提交对象,并检查这些提交。
在大多数“正常”的pre-receive钩子中,您会使用以下方法,但是对于此特定任务,我们有一种替代方案。
修改的快捷方式可以处理最常见且通常最有趣的情况。假设有人建议将refs/heads/foo
从1234567...
更新为9876543...
。可能存在某些范围内的对象已经存在,例如,也许1234567
是提交C
的ID,9876543
是提交E
的ID:
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
,而实际提案是“创建指向E
的refs/heads/bar
”。在这种情况下,我们应该再次查看提交D
和E
,但没有明显的方法知道D
。
不明显的方法(仅适用于某些情况)是找到将在提议创建时可达到的对象,这些对象目前根本不可达:
git rev-list $newsha --not --all
在这种情况下,这将再次生成D
和E
的ID。
现在让我们考虑您的特殊情况,您想查看所有拟添加的提交。以下是一种处理此类情况的方法:
对于所有建议更新:
- 如果这个是删除操作,则我们有一些删除操作。
- 如果这个是创建或更新操作,则我们有一些新的提交; 累积新的SHA值。
如果我们有一些删除操作并且累积了一些SHA值,则拒绝该尝试:它太难了。让用户分离操作。
否则,如果我们没有累积任何SHA值,则我们必须只有删除操作(或者可能什么也没发生-这不应该发生,但是无害的); 允许此操作(退出0)。
否则,我们必须具有一些新的SHA-1值。
使用建议的新SHA作为起点,查找所有可以到达的git对象,排除当前以任何名称到达的所有对象。 这些都是新对象。
对于每个提交,请检查它是否被禁止。 如果是,则拒绝整个操作(即使某些部分可能成功);与之前一样,它太难了,所以让用户从“好的”操作和“坏的”操作中分离出来。
如果我们到达了这一步,则一切正常;允许整个更新。
代码形式如下:
#! /bin/sh
NULL_SHA1="0000000000000000000000000000000000000000"
new_list=
any_deleted=false
while read oldsha newsha refname; do
case $oldsha,$newsha in
*,$NULL_SHA1)
any_deleted=true;;
$NULL_SHA1,*)
new_list="$new_list $newsha";;
*,*)
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
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
git push
可以同时推送许多参考更新。我可以同时推送400个新分支和75个标签。 - torekgit rev-list $newsha --not --all
。太棒了。 - spam_eggs