我写了一个更新挂钩(服务器端),它检查所有提交消息(检查是否存在问题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 ......
你有什么想法吗?
答案 0 :(得分:13)
在我回答之前,请注意一些提醒。有几个"绊脚石"在他们写钩子时让人们接受。你击中了我的第三个"在下面的列表中。
在pre-receive和update中,你会得到三个参数(不同的顺序和不同的方法,参数vs stdin;但是相同的三个参数,最后,相同的" deal"因为它是)。两个是旧的和新的sha1,第三个是参考名称。我们称他们为oldrev
和newrev
(正如您所做的)和第三个refname
。
完成脚本后,返回值0
允许git更新refname
,非零返回禁止它。也就是说,脚本是通过提议调用的:"我(现在正在运行的git更新操作)建议对某些标签进行更改"。对于更新挂钩,您可以单独获取每个标签,每个返回值允许或禁止一次更改;对于预接收挂钩,您可以批量获取它们,每行一个,标准输入,并且您的返回值允许或不允许整体更改。 (如果您拒绝预先接收的更改,则不会进行任何更新。在预先接收确认之后,或者不存在更新时,更新一次只能获得一次。)
如果refname
以"refs/heads/"
开头,则为分支名称。其他可能性包括"refs/tags/" and "refs/notes/"
,尽管注释引用相对较新。大多数重命名将指向提交对象,除了标签经常(但不总是)指向带标注的标签对象。
所以这是第一个绊脚石: refname可能不是分支。确保将您的逻辑应用于标签(可能是笔记),或单独处理(以适用者为准)。
如果旧的和新的sha1都是"非空" (不是"0" * 40
),建议是移动标签。它曾经命名为oldrev
,现在它(如果你允许的话)将命名为newrev
。
这是第二个绊脚石:当标签移动时,无法保证旧版本和新版本完全相关。注意&#34 ;无义"来自oldrev..newrev
的结果,在这种情况下会发生。您可以(或可能不会,取决于您正在做的事情)确认oldrev
是newrev
的祖先。 (见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.match
或nullhash.search
(如前所述,只有在使用常规search
而不是左锚定match
)。
将subprocess.Popen
与shell=False
一起使用可以提高效率(保存启动" sh")和安全(重新命名不是问题,请参阅{{1 },但只是一般规则)。
直接使用git check-ref-format
,而不是格式为git rev-list
的{{1}}(并仔细研究log
的手册页;它与最常见的。)
保留%H
和/或git rev-list
前缀:refs/heads/
对这些前缀感到满意,并确保您获得正确的参考。例如,如果标签和都有一个名为refs/tags/
的分支,那么你会得到哪一个? (你得到了标签 - 但为什么不使用全名,而不必记住它?)