防止用户推送到已删除的分支

Prevent users from pushing to deleted branch

考虑这个测试脚本。

#!/bin/sh -x

rm -rf test clone*

# create a test repo containing one branch called "branch"
git init test
cd test
echo foo > foo
git add foo
git commit -am "initial commit"
git branch branch

# create three clones of the repo
cd ..
git clone test clone1
git clone test clone2
git clone test clone3

# push a commit on the branch in clone1
cd clone1
git checkout branch
echo bar > foo
git commit -am "bar"
git push origin branch

# from clone2, delete the branch in the origin
cd ../clone2
git push --delete origin branch

# from clone3, push a commit on the branch
cd ../clone3
git checkout branch
echo baz > foo
git commit -am "baz"
git push origin branch

此脚本创建了一个包含三个克隆的测试仓库。克隆 1 在分支上推送提交;克隆 2 推送删除分支;克隆 3 在分支的陈旧版本上推送提交。

如果克隆 2 没有删除该分支,则克隆 3 在尝试推送该分支时会出错。例如如果您注释掉 "clone2" 行,克隆 3 的推送将被正确拒绝。

但是在克隆 2 删除分支后,没有什么可以阻止克隆 3 推送该分支的陈旧副本; Clone 3 用户可能甚至不知道该分支已被删除,或者该分支根本就过时了。

我明白为什么会这样:删除 git 个分支会将它们从历史记录中删除。从 git 的角度来看,克隆 3 正在创建一个全新的分支,恰好不包括克隆 1 的已删除提交。

但这不是我想要的。我希望 Clone 3 在尝试推送到已删除的分支时收到某种错误或警告消息,上面写着 "Hey, buddy! That branch was deleted! You'll need to --force the push if you want to recreate it."

这可能吗?

您无法完全达到 --force+ 标志,但您 可以 获得 pre-receiveupdate 挂钩完成推送的裸存储库。

在任何一种脚本中,您都会被告知要更新哪些参考资料 is/are。分支只是一个引用,其全名以 refs/heads/ 开头(名称的其余部分是分支名称,其中可能包含更多斜杠)。您可以检查这是否在 "forbidden" 个名称的 table 中,如果是,则拒绝推送。

(其他名称以 refs/tags/ 开头,如果它们是标签,refs/notes/ 如果它们是注释,等等。)

pre-receiveupdate 挂钩之间的主要区别在于,前者会一次性获得 所有 提议的参考更新列表,在其标准输入上,而后者被给予建议参考,一次更新一个。可以说 "allow" 或 "deny",但是因为 pre-receive 挂钩在所有更新中只运行一次,它的 allow/deny 是全有或全无:你检查所有更新一次并且届时确定是否允许所有这些。 update 挂钩针对每个引用运行一次,并且可以在允许其他更新的同时拒绝某些单独的更新。

两个挂钩每次更新都会收到三个项目:名称、以前的(又名旧的)SHA-1 和提议的新 SHA-1。在这两种情况下,如果旧 SHA-1 为全零,则会创建一个引用名称;如果新的 SHA-1 为全零,它将被删除;并且它存在但正在从一个提交1 移动到另一个如果两个 SHA-1 都不是特殊的全零。

两个钩子都没有得到任何指示这是否是强制推送。这意味着如果你想允许某种方法来故意重新创建这些禁止分支之一,你必须想出一些替代方法。

一种可能性是让某些 "blessed" 用户:那些(至少在理论上)知道他们在做什么的人被允许重新创建这样一个分支。 (我相信这通常是首选解决方案。)

另一种(相当笨拙的)方法是使用多个挂钩。在预接收挂钩中,如果要更新的引用之一具有某些特殊名称(例如 "refs/force"),则允许重新创建分支;然后,在更新挂钩中,拒绝特定的特殊名称,或使用 post-接收挂钩删除 "force" ref.

可以让pre-receive hook allow all,然后update hook deny some:结果只是允许那些没有被单独拒绝的。用户看到的输出可能有点 scary/confusing,但是:他们会收到一条拒绝消息,并且必须意识到这只是针对某些 特定的 ref 更新,而不是针对整体推送。

您必须将 "deleted" 分支列表存储在某处(可能在由挂钩维护的文件中,并在删除分支时更新)。

注意,如果你用gitolite,它内置了很多这种控件。它接管了一些钩子,并且要求每个人都以特定用户的身份进行推送,所以操作有点与我上面描述的方式使用原始钩子不同。


1或标记或其他对象,但分支名称应始终指向提交。标签名称通常指向提交(轻量级标签)或标签对象(注释标签)。可以将引用点直接指向 blob 或树对象,但分支名称和标记名称都不应该这样做。