我正在使用Python和git Python编写一个git post receive hook,它收集推送中包含的提交信息,然后用摘要更新我们的bug跟踪器和IM。在推送创建一个分支(即,要发布接收的fromrev
参数全部为零)并且在该分支上跨多个提交的情况下,我遇到了麻烦。我正在从torev
提交向后遍历父级列表,但我不知道如何判断哪个提交是分支中的第一个提交,即何时停止查看。
在命令行上,我可以进行
git rev-list this-branch ^not-that-branch ^master
这将准确地给出this-branch
中的提交列表,而不是其他列表。我曾尝试使用Commit.iter_parents
方法来复制这一点,该方法记录了与git-rev-list相同的参数,但据我所见,它不喜欢位置参数,而且我找不到一组有效的关键字参数。
我读了Dulwich的文档,但不清楚它是否会与GitPython有很大不同。
我的(简化的)代码如下所示。当推送启动一个新分支时,它当前只查看第一个提交,然后停止:
import git
repo = git.Repo('.')
for line in input:
(fromrev, torev, refname) = line.rstrip().split(' ')
commit = repo.commit(torev)
maxdepth = 25 # just so we don't go too far back in the tree
if fromrev == ('0' * 40):
maxdepth = 1
depth = 0
while depth < maxdepth:
if commit.hexsha == fromrev:
# Reached the start of the push
break
print '{sha} by {name}: {msg}'.format(
sha = commit.hexsha[:7], user = commit.author.name, commit.summary)
commit = commit.parents[0]
depth += 1
使用纯Git Python也可以做到这一点。我也没有找到一种方法来识别一组可以一次性完成的夸尔格。但是,可以简单地构建主分支的一组sha,然后在要检查的分支上使用iter_commits,以便找到第一个没有出现在父分支中的sha:
from git import *
repo_path = '.'
repo = Repo(repo_path)
parent_branch = repo.branches.master
examine_branch = repo.branches.test_feature_branch
other_shas = set()
for parent_commit in repo.iter_commits(rev=parent_branch):
other_shas.add(parent_commit.hexsha)
for commit in repo.iter_commits(rev=examine_branch):
if commit.hexsha not in other_shas:
first_commit = commit
print '%s by %s: %s' % (first_commit.hexsha[:7],
first_commit.author.name, first_commit.summary)
如果你真的想确保排除所有其他分支上的所有提交,你可以将第一个for循环包装在另一个for循环中
other_shas = set()
for branch in repo.branches:
if branch != examine_branch:
for commit in repo.iter_commits(rev=branch):
other_shas.add(commit.hexsha)
- 注意1:第二种方法显示了没有出现在任何其他分支上的第一次提交,这不一定是该分支上的首次提交。如果feat_b是从feat_a分支出来的,而feat_a来自master,那么这将显示在feat_b分支出来之后feat_a上的第一个提交:feat_a的其余提交已经在feat_b上了
- 注意2:git-rev-list和这两个解决方案只有在分支还没有合并回master的情况下才有效。你实际上是要求它列出这个分支上的所有提交,而不是另一个分支上的
- 备注:第二种方法过于夸张,需要更多的时间才能完成。一个更好的方法是将其他分支限制在已知合并分支的列表中,如果您拥有的不仅仅是master
我刚和dulwich玩过,也许有更好的方法(用内置助行器?)。假设只有一个新分支(或多个没有共同点的新分支):
#!/usr/bin/env python
import sys
from dulwich.repo import Repo
from dulwich.objects import ZERO_SHA
def walk(repo, sha, shas, callback=None, depth=100):
if not sha in shas and depth > 0:
shas.add(sha)
if callback:
callback(sha)
for parent in repo.commit(sha).parents:
walk(repo, parent, shas, callback, depth - 1)
def reachable_from_other_branches(repo, this_branch):
shas = set()
for branch in repo.refs.keys():
if branch.startswith("refs/heads") and branch != this_branch:
walk(repo, repo.refs[branch], shas)
return shas
def branch_commits(repo, fromrev, torev, branchname):
if fromrev == ZERO_SHA:
ends = reachable_from_other_branches(repo, branchname)
else:
ends = set([fromrev])
def print_callback(sha):
commit = repo.commit(sha)
msg = commit.message.split("n")[0]
print('{sha} by {author}: {msg}'
.format(sha=sha[:7], author=commit.author, msg=msg))
print(branchname)
walk(repo, torev, ends, print_callback)
repo = Repo(".")
for line in sys.stdin:
fromrev, torev, refname = line.rstrip().split(' ')
branch_commits(repo, fromrev, torev, refname)
类似的东西会找到第一个提交:
x = Repo('.')
print list(x.get_walker(include=[x.head()]))[-1].commit
(注意,对于大型存储库,这将使用O(n)内存,使用迭代器来绕过这一点)