在更新挂钩中查看新分支上的提交

时间:2013-09-10 15:37:19

标签: git hook githooks

我写了一个更新挂钩(服务器端),它检查所有提交消息(检查是否存在问题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”的情况下,用户想要删除branch =>没问题。
在推入现有分支的情况下,我们得到提交的SHA-1 => OK
但是当用户创建分支时,我不知道如何获得SHA-1 ......

你有什么想法吗?

1 个答案:

答案 0 :(得分:13)

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

在pre-receive和update中,你会得到三个参数(不同的顺序和不同的方法,参数vs stdin;但是相同的三个参数,最后,相同的" deal"因为它是)。两个是旧的和新的sha1,第三个是参考名称。我们称他们为oldrevnewrev(正如您所做的)和第三个refname

完成脚本后,返回值0允许git更新refname,非零返回禁止它。也就是说,脚本是通过提议调用的:"我(现在正在运行的git更新操作)建议对某些标签进行更改"。对于更新挂钩,您可以单独获取每个标签,每个返回值允许或禁止一次更改;对于预接收挂钩,您可以批量获取它们,每行一个,标准输入,并且您的返回值允许或不允许整体更改。 (如果您拒绝预先接收的更改,则不会进行任何更新。在预先接收确认之后,或者不存在更新时,更新一次只能获得一次。)

如果refname"refs/heads/"开头,则为分支名称。其他可能性包括"refs/tags/" and "refs/notes/",尽管注释引用相对较新。大多数重命名将指向提交对象,除了标签经常(但不总是)指向带标注的标签对象。

所以这是第一个绊脚石: refname可能不是分支。确保将您的逻辑应用于标签(可能是笔记),或单独处理(以适用者为准)。

如果旧的和新的sha1都是"非空" (不是"0" * 40),建议是移动标签。它曾经命名为oldrev,现在它(如果你允许的话)将命名为newrev

这是第二个绊脚石:当标签移动时,无法保证旧版本和新版本完全相关。注意&#34 ;无义"来自oldrev..newrev的结果,在这种情况下会发生。您可以(或可能不会,取决于您正在做的事情)确认oldrevnewrev的祖先。 (见git merge-base --is-ancestor。)

当新的sha1为null时,建议是删除标签,这非常简单(每个人似乎本能地得到这个:-))。

当旧sha1为null时,建议是设置新标签。这是第三个绊脚石:此标签以前不存在。这告诉你什么,你想要考虑哪些提交(如果有的话)""的一部分。新标签。标签仅命名一个提交,并且由某人解释它们,在某些未来的点上,该标签是什么"意味着"。

作为一个例子,假设我有一份你的回购(前面我做了git clone),我被允许git push回复它。我决定:gosh,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引用了一个提交对象,我创建了一个指向它的新的轻量级标记;如果它是带注释的标签,我已为注释标签创建了一个名称(可能是#34;另一个名称)。

假设5555555引用了一个提交对象,我实际上创建了一个新的分支,但它的历史记录是什么?#34 ;?在这种情况下,它可能根本没有,我可能只是在"中间添加了标签"一些现有的分支。 (但也许不是:也许我在master现在指向的地方添加了它,并且在我的master结束后,我将在一瞬间将origin/master重新回到push。 )

最常见的答案似乎是"新分支命名从newrev开始但尚未通过任何其他分支名称的父母命名的任何提交#34;。有一种方法可以找到此类提交的列表。以sh形式(请参阅下面的注释):

git rev-list $newrev --not \
    $(git for-each-ref refs/heads/ --format='%(refname)')

在这种情况下,由于您处于预接收或更新挂钩状态,新的refname实际上还没有出生,所以没有必要将其排除,而是对{{3}进行评论}表明有时它可能,在这种情况下(再次在sh):

git rev-list $newrev --not \
    $(git for-each-ref refs/heads/ --format='%(refname)' |
    grep -v ^$newref\$)

会做到这一点。但是这里还有另一个潜在的绊脚石,你无法在更新钩子中做任何事情:如果推送创建了多个分支,结果列表可能依赖于多个新分支名称和/或创建顺序。在收件后挂钩中,您可以找到所有新的分支创作,并且:

  • 拒绝,如果有多个,或
  • 根据需要向--not添加更多git rev-list个参数。

如果您使用后者,请注意在同一修订版中创建两个或多个新分支标签的情况:它们各自指的是所有其他分支标签'提交。

最后的绊脚石(很少被击中):post-receive钩子中的,列出修订号和引用名称的输入流来自管道,只能读取一次。如果你想要多次读取它,你必须将它保存到一个临时文件中(以便你可以寻找偏移0,或者关闭并重新打开它)。

最后几点说明:

  • 我建议做:

    NULL_SHA1 = "0" * 40

    早期在python代码中,然后使用rev == NULL_SHA1作为测试。如果不出意外,它可以很容易地看到正好有四十0 s,而且重点是检查" null sha1"。

    Git可能会转向使用SHA3-256,现在是this answer。 (这对Git来说并不是致命的,但是表明计算能力已经发展到可能依赖于它的情况可能是不明智的。)目前尚不清楚这将如何影响钩子,但你现在可能想要匹配任意数量的0,只要它们所有零,使用:

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

    (或re.search('^0+$', ...)如果您因某种原因选择re.search。您可以将其预编译为nullhash = re.compile('^0+$'),然后使用nullhash.matchnullhash.search(如前所述,只有在使用常规search而不是左锚定match)。

  • subprocess.Popenshell=False一起使用可以提高效率(保存启动" sh")和安全(重新命名不是问题,请参阅{{1 },但只是一般规则)。

  • 直接使用git check-ref-format,而不是格式为git rev-list的{​​{1}}(并仔细研究log的手册页;它与最常见的。)

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