1

我写了一个更新钩子(服务器端),它检查所有提交消息(检查是否有问题 ID)

有一个我的 python 代码(update.py)的摘录:

[...]
if newrev == "0000000000000000000000000000000000000000":
  newrev_type = "delete"
elif oldrev == "0000000000000000000000000000000000000000":  
  newrev_type = "create"

  # HERE IS MY QUESTION, I want to get the commits SHA-1 :-)

else:
  POPnewrev_type = os.popen("git cat-file -t " + newrev)
  newrev_type = POPnewrev_type.read()[0:-1]
  # get the SHA-1
  POPanalyzelog = os.popen("git log " + oldrev + ".." + newrev + " --pretty=#%H")
  analyzelog = POPanalyzelog.read().split('#')
[...]

所以,在这里,如果 newrev_type = "delete",用户想要删除一个分支 => 没问题。
在推送现有分支的情况下,我们得到提交的 SHA-1 => OK
但是当用户创建分支时,我不知道如何获取 SHA-1 ...

你有什么想法?

4

1 回答 1

13

在我回答之前,让我们注意一些提醒。人们在编写钩子时有几个“绊脚石”。你在下面的列表中击中了我的“第三个”。

在预接收和更新中,您都获得了三个参数(以不同的顺序和通过不同的方法,参数 vs 标准输入;但最终,相同的三个参数具有相同的“交易”)。两个是新旧 sha1,第三个是参考名称。让我们称它们为oldrevand 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注意在这种情况下发生 的“废话”结果。您可能(也可能不会,取决于您在做什么)想要验证它oldrevnewrev. (见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/masterpush

最常见的答案似乎是“新分支命名任何从 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 个0s,关键是检查“null sha1”。

    Git 可能会转而使用 SHA3-256,因为SHA-1 已被示例破坏。(这对 Git 来说不是致命的,但表明计算能力已经发展到继续依赖它可能是不明智的程度。)目前尚不清楚这将如何影响钩子,但您现在可能想要匹配任意数量的0s只要它们都是,使用:

    re.match('0+$', hash)
    

    (或者re.search('^0+$', ...)如果您re.search出于某种原因更喜欢)。您可以将其预编译为nullhash = re.compile('^0+$')然后使用nullhash.matchor (和以前一样,只有在使用 general而不是 left-anchorednullhash.search时才需要前缀 hat )。searchmatch

  • 使用subprocess.Popenwithshell=False可以提高效率(节省启动“sh”)和安全性(引用名不是问题,请参阅git check-ref-format,但只是一般规则)。

  • 直接使用git rev-list,而不是log使用格式%H(并仔细研究手册页git rev-list;它与大多数钩子高度相关)。

  • 保留refs/heads/和/或refs/tags/前缀:git rev-list对这些前缀感到满意,它们有助于确保您获得正确的参考。例如,如果同时有一个标签一个名为 的分支master,你会得到哪一个?(你得到了标签——但为什么不使用全名,而不必记住呢?)

于 2013-09-10T18:09:25.557 回答