使用GitPython查找分支上的第一个提交记录

6
我正在使用Python和Git-Python编写git post-receive钩子,它可以收集有关推送中提交的信息,然后更新我们的bug跟踪器和即时通讯工具并提供一个摘要。但当一次推送创建了一个新分支(即post-receive的fromrev参数为全零),且该分支跨越多个提交时,我遇到了麻烦。我在从torev提交开始向父提交列表反向遍历,但是我无法确定哪个提交是该分支中的第一个提交,也就是何时停止查找。
在命令行上,我可以执行以下操作:
git rev-list this-branch ^not-that-branch ^master

我希望能够准确地获取this-branch中的提交列表,而不包括其他分支的提交。我已经尝试使用Commit.iter_parents方法来复制这个功能,但是该方法似乎不支持位置参数,我也找不到一组可用的关键字参数。

我阅读了Dulwich的文档,但并不清楚它是否与Git-Python有很大的不同。

我的(简化后的)代码如下。当推送开始一个新分支时,它目前只查看第一个提交,然后停止:

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 log master..branch --oneline | tail -1,只需要弄清楚如何将其转换为代码即可 :P - starking
3个回答

5

使用纯Git-Python库也可以完成此操作。我没有找到一种可以一次性识别一组kwargs的方法。但是,可以简单地构建主分支的一组shas,然后在要检查的分支上使用iter_commits函数以找到第一个未出现在父级中的提交记录:

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)

如果你确实想要确保排除所有其他分支上的提交,那么你可以在repo.branches上再包裹一个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分支。

2

我刚刚尝试了dulwich,也许有更好的方法来完成这个任务(使用内置的walker?)。假设只有一个新分支(或多个没有共同点的新分支):

#!/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)

1
谢谢 - 我之前没有看到dulwich中的walker对象,看起来我可以用其中一个很好地完成它。稍后会发布一些代码。 - Julian Melville

1

类似这样的代码会找到第一个提交:

x = Repo('.')
print list(x.get_walker(include=[x.head()]))[-1].commit

(注意,对于大型仓库,这将使用O(n)内存,请使用迭代器绕过此问题)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接