在更新挂钩中查看新分支上的提交 [英] View commits on a new branch in the update hook

查看:152
本文介绍了在更新挂钩中查看新分支上的提交的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

#这里是我的问题,我想要提交SHA- 1 :-)

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



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



你有什么想法吗?

解决方案

回答,让我们注意一些提醒。有几个绊脚石让人们在写钩子的时候得到人们的帮助。您在下面的列表中打了我的第三。



在预接收和更新中,给出三个参数(以不同的顺序并通过不同的方法,参数vs stdin;但最后,同样的三个参数与之相同的交易)。两个是旧的和新的sha1,第三个是参考名称。我们称它们为 oldrev newrev (就像您一样),第三个 refname

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

如果 refname refs / heads /开头,则它是一个分支名称。其他可能性包括refs / tags /和refs / notes /,但注释引用相对较新。大多数refnames将指向提交对象,除了标签经常(但并非总是)指向带注释标记的对象。



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



如果旧的和新的sha1都是非空的(不是0* 40 ),建议是移动标签。它用于命名 oldrev ,现在它会(如果允许的话)名称 newrev



这是第二个绊脚石:当标签移动时,不能保证旧版本和新版本完全相关。注意无意义结果 oldrev..newrev ,在这种情况下发生。您可能(或者可能不会,取决于您在做什么)要验证 oldrev newrev 。 (见 git merge-base --is-ancestor 。)



当新的sha1为空时,是删除标签,这是非常简单的(每个人似乎本能地这样做:-))。

当旧的sha1为空时,提案将设置一个新的标签。这是第三个绊脚石:此标签以前不存在。这不会告诉您任何关于哪些提交(如果有的话),您希望将其视为新标签的一部分。标签只提供一个一个提交,这取决于有人在未来的某个时刻解释它们的含义。

举一个例子,假设我有一个你的repo的副本(我之前做了一个 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引用了一个提交对象,我已经创建了一个新的轻量级标签指向它;如果它是一个带注释的标签,我为注释标签创建了一个名称(可能是另一个名称)。 假设 5555555 引用一个提交对象,实际上我创建了一个新的分支,但它的历史是什么?在这种情况下,它可能根本没有,我可能只是在某个现有分支的中间添加了标签。 (但也许不是:也许我现在添加了 master 点,我将把 master 倒回到 origin / master 之后,我的 push 完成。)



最常见的答案似乎是新分支命名从newrev开始的任何提交,但尚未由任何其他分支名称或通过其父代命名。有一种方法可以找到这样的提交列表。在 sh 表单中(见下面的注释):

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

在这种情况下,由于您处于预接收或更新挂钩中,因此新的refname实际上还没有出现,所以不需要排除它,但对这个答案的评论表明,有时它可能会在这种情况下(也是在sh中):

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

会做的伎俩。但是这里还有另一个潜在的障碍,你不能在更新钩子中做任何事情:如果一个推送创建多个分支,结果列表可能取决于多个新的分支名称和/或顺序。在接收后的挂钩中,您可以找到所有新的分支创建,并且:


  • 如果存在

  • - 参数添加到 git rev-list c $ c>根据需要。



如果您使用后者,请注意在创建两个或多个新分支标签的情况相同的版本:他们会分别引用所有其他人的提交。



最后的绊脚石(很少碰到):输入流列表的修订版本号和引用名称来自管道,并且只能读取一次。如果要多次读取它,必须将其保存到临时文件(以便您可以查找bac k $抵消0,或关闭并重新打开它)。

几个最后的笔记:

    $

    NULL_SHA1 =0* 40

    $ c

    在Python代码中,然后使用 rev == NULL_SHA1 作为测试。如果没有其他的东西,它可以很容易地看到有四十个 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 nullhash.search (和以前一样,前缀帽子只有在您使用一般搜索时才需要)比左锚点匹配)。 使用 subprocess.Popen shell = False 以提高效率(节省sh)和安全性(与refname无关,请参阅 git check-ref-format ,但只是一般规则)。 使用 git rev-list ,而不是 log 格式%H 页面为 git rev-list ;它与大多数的钩子都非常相关) refs / heads / 和/或 refs / tags / 前缀: git rev-list 对这些前缀感到满意,并且它们用于确保您获得正确的参考。例如,如果同时存在一个名为 master 的分支,您会得到哪一个分支? (你得到标签 - 但为什么不使用全名,而不必记住它?)


I wrote an update hook (server-side) which checks all commit messages (To check if there is an Issue Id)

There is an extract of my python code (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('#')
[...]

So, here, in case of newrev_type = "delete", the user wants to delete a branch => No problem. In case of pushing in an existing branch, we get the SHA-1 of the commits => OK But when the user creates a branch, I don't know how to get the SHA-1...

Do you have any ideas?

解决方案

Before I answer, let's note some reminders. There are several "stumbling blocks" that get people when they write hooks. You have hit my "third" in the list below.

In both pre-receive and update, you are given three arguments (in different orders and through different methods, arguments vs stdin; but the same three arguments, in the end, with the same "deal" as it were). Two are old and new sha1 and the third is the reference name. Let's call them oldrev and newrev (as you did) and the third refname.

When you finish your script, a return value of 0 allows git to update refname, and a nonzero return forbids it. That is, the script is called with a proposal: "I (the git update operation now running) propose to make a change in some label(s)". For the update hook you get each label individually, and each return value allows or disallows one change; for the pre-receive hook, you get them all in a batch, one per line, on standard input, and your return value allows or disallows the change as a whole. (If you reject the change in pre-receive, no updates will happen. After pre-receive OKs them, or is absent, update gets a chance one at a time.)

If the refname starts with "refs/heads/", it is a branch name. Other possibilities include "refs/tags/" and "refs/notes/" although note references are relatively new. Most refnames will point to commit objects, except that tags often (but not always) point to annotated-tag objects.

So here's the first stumbling block: the refname might not be a branch. Make sure that it's OK to apply your logic to tags (and maybe notes), or handle them separately (whichever is appropriate).

If the old and new sha1 are both "non-null" (not "0" * 40), the proposal is to move the label. It used to name oldrev and now it will (if you allow it) name newrev.

Here's the second stumbling block: when a label moves, there's no guarantee that the old revision and new revision are related at all. Watch out for "nonsense" results from oldrev..newrev, which occur in that case. You may (or may not, depending on what you're doing) want to verify that oldrev is an ancestor of newrev. (See git merge-base --is-ancestor.)

When the new sha1 is null, the proposal is to remove the label, which is pretty straightforward (everyone seems to get this right instinctively :-) ).

When the old sha1 is null, the proposal is to set a new label. Here's the third stumbling block: That label did not exist before. That tells you nothing about which commit(s), if any, you want to consider to be "part of" the new label. Labels only name one commit and it's up to someone interpreting them, at some future point, what that label "means".

As an example, suppose I have a copy of your repo (I did a git clone earlier) and am allowed to git push back to it. I decide: gosh, rev 1234567 should have a tag, and ref 5555555 should have a branch label:

git tag new-tag 1234567
git branch new-branch 5555555
git push --tags origin refs/heads/new-branch:refs/heads/new-branch

If 1234567 refers to a commit object, I have created a new lightweight tag pointing to that; if it's an annotated tag, I've made a name (probably "another" name) for the annotated tag.

Assuming 5555555 refers to a commit object, I have in fact created a new branch, but what is its "history"? In this case, it probably has none at all, I probably just added the label "in the middle of" some existing branch. (But maybe not: maybe I added it where my master now points, and I am going to rewind master back to origin/master in a moment, after my push finishes.)

The most common answer seems to be "the new branch names any commit starting from newrev but not already named by, or through the parents of, any other branch-name". There is a way to find a list of such commits. In sh form (see notes below):

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

In this case, since you're in a pre-receive or update hook, the new refname has not actually been born yet, so it should not be necessary to exclude it, but a comment on this answer suggests that sometimes it might, in which case (again in sh):

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

would do the trick. But there's another potential stumbling block here, which you can't do anything about in an update hook: if a push is creating more than one branch, the resulting list could depend on the multiple new branch names and/or the order of their creation. In a post-receive hook, you can find all new branch creations, and:

  • reject if there is more than one, or
  • add more --not arguments to git rev-list as needed.

If you do the latter, beware of the case of creating two or more new branch labels at the same revision: they'll each refer to all of the others' commits.

A final stumbling block (rarely hit): in the post-receive hook, the input stream listing revision numbers and reference names is coming from a pipe, and can only be read once. If you want to read it multiple times, you must save it to a temporary file (so that you can seek back to offset 0, or close and re-open it).

A few final notes:

  • I'd recommend doing:

    NULL_SHA1 = "0" * 40

    earlier in the python code, and then using rev == NULL_SHA1 as the test. If nothing else, it makes it easy to see that there are exactly forty 0s, and that the point is to check for a "null sha1".

    Git may move to using SHA3-256, now that SHA-1 has been broken by example. (This is not fatal to Git, but shows that compute power has advanced to the point that it's perhaps unwise to keep depending on it.) It's not clear how this will affect hooks, but you might now want to match against any number of 0s as long as they are all zeros, using:

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

    (or re.search('^0+$', ...) if you prefer re.search for some reason). You can pre-compile this as nullhash = re.compile('^0+$') and then use nullhash.match or nullhash.search (as before, the prefix hat is only required if you are using the general search rather than the left-anchored match).

  • Use subprocess.Popen with shell=False for a little bit more efficiency (save firing up "sh") and safety (not a problem with refnames, see git check-ref-format, but just a general rule).

  • Use git rev-list directly, rather than log with format %H (and study the manual page for git rev-list closely; it's highly relevant to most hooks).

  • Leave in the refs/heads/ and/or refs/tags/ prefixes: git rev-list is happy with these prefixes, and they serve to make sure that you get the right reference. For instance, if there are both a tag and a branch named master, which one do you get? (You get the tag—but why not use the full name, and not have to remember that?)

这篇关于在更新挂钩中查看新分支上的提交的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆