在我回答之前,让我们注意一些提醒。人们在编写钩子时有几个“绊脚石”。你在下面的列表中击中了我的“第三个”。
在预接收和更新中,您都获得了三个参数(以不同的顺序和通过不同的方法,参数 vs 标准输入;但最终,相同的三个参数具有相同的“交易”)。两个是新旧 sha1,第三个是参考名称。让我们称它们为oldrev
and newrev
(就像你所做的那样)和第三个refname
。
当你完成你的脚本时,返回值0
允许 git 更新refname
,非零返回禁止它。也就是说,脚本被调用时提出了一个提议:“我(现在正在运行的 git 更新操作)提议对某些标签进行更改”。对于更新挂钩,您可以单独获取每个标签,并且每个返回值允许或禁止一项更改;对于 pre-receive 挂钩,您可以在标准输入上批量获取它们,每行一个,并且您的返回值允许或禁止整体更改。(如果您在 pre-receive 中拒绝更改,则不会发生更新。在 pre-receive OK 或不存在后,更新一次有机会。)
如果以refname
开头"refs/heads/"
,则它是一个分支名称。其他可能性包括"refs/tags/" and "refs/notes/"
尽管注释引用相对较新。大多数 refnames 将指向提交对象,除了标签经常(但不总是)指向带注释的标签对象。
所以这是第一个绊脚石:refname 可能不是一个分支。 确保可以将您的逻辑应用于标签(可能还有注释),或单独处理它们(以适当者为准)。
如果旧 sha1 和新 sha1 都是“非空”(不是"0" * 40
),则建议移动标签。它曾经命名oldrev
,现在它将(如果你允许的话)命名newrev
。
这是第二个绊脚石:当标签移动时,不能保证旧修订版和新修订版完全相关。oldrev..newrev
注意在这种情况下发生 的“废话”结果。您可能(也可能不会,取决于您在做什么)想要验证它oldrev
是newrev
. (见git merge-base --is-ancestor
。)
当新的 sha1 为空时,建议是删除标签,这非常简单(每个人似乎都本能地理解了这一点 :-))。
当旧的sha1为空时,建议设置一个新的标签。这是第三个绊脚石:这个标签以前不存在。这不会告诉您哪些提交(如果有)是您想要考虑成为新标签的“一部分”。标签只命名一个提交,并且在将来的某个时候,该标签的“含义”取决于解释它们的人。
举个例子,假设我有一份你的 repo 的副本(我之前做过git clone
)并且被允许git push
回到它。我决定:天哪,rev 1234567 应该有一个标签,而 ref 5555555 应该有一个分支标签:
git tag new-tag 1234567
git branch new-branch 5555555
git push --tags origin refs/heads/new-branch:refs/heads/new-branch
如果 1234567 指的是一个提交对象,我创建了一个新的轻量级标签指向它;如果它是一个带注释的标签,我已经为带注释的标签起了一个名字(可能是“另一个”名字)。
假设5555555
指的是一个提交对象,我其实已经创建了一个新的分支,但它的“历史”是什么?在这种情况下,它可能根本没有,我可能只是在某个现有分支的“中间”添加了标签。(但也许不是:也许我将它添加到我master
现在指向的位置,我将在完成后立即倒master
回。)origin/master
push
最常见的答案似乎是“新分支命名任何从 newrev 开始但尚未由任何其他分支名称或通过其父级命名的提交”。有一种方法可以找到此类提交的列表。形式sh
(见下面的注释):
git rev-list $newrev --not \
$(git for-each-ref refs/heads/ --format='%(refname)')
在这种情况下,由于您处于 pre-receive 或 update 钩子中,因此新的 refname 实际上还没有诞生,因此没有必要排除它,但是对此答案的评论表明,有时它可能会在哪种情况(再次在 sh 中):
git rev-list $newrev --not \
$(git for-each-ref refs/heads/ --format='%(refname)' |
grep -v ^$newref\$)
会成功的。但是这里还有另一个潜在的绊脚石,你不能在更新钩子中做任何事情:如果推送创建了多个分支,则结果列表可能取决于多个新分支名称和/或它们的创建顺序. 在 post-receive 挂钩中,您可以找到所有新的分支创建,并且:
- 如果有多个则拒绝,或
- 根据需要添加更多
--not
参数git rev-list
。
如果您执行后者,请注意在同一修订版中创建两个或多个新分支标签的情况:它们每个都会引用所有其他人的提交。
最后一个绊脚石(很少遇到):在 post-receive 挂钩中,列出修订号和参考名称的输入流来自管道,并且只能读取一次。 如果你想多次读取它,你必须将它保存到一个临时文件中(这样你就可以回溯到偏移量 0,或者关闭并重新打开它)。
最后的几点说明:
我建议这样做:
NULL_SHA1 = "0" * 40
在python代码的前面,然后rev == NULL_SHA1
用作测试。如果不出意外,很容易看出正好有 40 个0
s,关键是检查“null sha1”。
Git 可能会转而使用 SHA3-256,因为SHA-1 已被示例破坏。(这对 Git 来说不是致命的,但表明计算能力已经发展到继续依赖它可能是不明智的程度。)目前尚不清楚这将如何影响钩子,但您现在可能想要匹配任意数量的0
s只要它们都是零,使用:
re.match('0+$', hash)
(或者re.search('^0+$', ...)
如果您re.search
出于某种原因更喜欢)。您可以将其预编译为nullhash = re.compile('^0+$')
然后使用nullhash.match
or (和以前一样,只有在使用 general而不是 left-anchorednullhash.search
时才需要前缀 hat )。search
match
使用subprocess.Popen
withshell=False
可以提高效率(节省启动“sh”)和安全性(引用名不是问题,请参阅git check-ref-format
,但只是一般规则)。
直接使用git rev-list
,而不是log
使用格式%H
(并仔细研究手册页git rev-list
;它与大多数钩子高度相关)。
保留refs/heads/
和/或refs/tags/
前缀:git rev-list
对这些前缀感到满意,它们有助于确保您获得正确的参考。例如,如果同时有一个标签和一个名为 的分支master
,你会得到哪一个?(你得到了标签——但为什么不使用全名,而不必记住呢?)