Нахождение первого коммита на ветке с помощью GitPython

Я пишу ловушку git post-receive с использованием Python и Git-Python, которая собирает информацию о коммитах, содержащихся в push, а затем обновляет наш трекер ошибок и IM с помощью сводки. У меня возникли проблемы в случае, когда толчок создает ветку (т.е. fromrev параметр для пост-получения - все нули), а также охватывает несколько коммитов в этой ветви. Я иду список родителей назад от torev commit, но я не могу понять, как определить, какой коммит первый в ветке, т.е. когда перестать искать.

В командной строке я могу сделать

git rev-list this-branch ^not-that-branch ^master

который даст мне точно список коммитов в this-branchи других нет. Я пытался повторить это с помощью Commit.iter_parents метод, который задокументирован, принимает те же параметры, что и git-rev-list, но ему не нравятся позиционные параметры, насколько я вижу, и я не могу найти набор ключевых параметров, которые работают.

Я читал документацию по Далвичу, но не было ясно, будет ли он отличаться от Git-Python.

Мой (упрощенный) код выглядит следующим образом. Когда push запускает новую ветку, он в данный момент просматривает только первый коммит, а затем останавливается:

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

3 ответа

Используя чистый 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)

И если вы действительно хотите исключить все коммиты во всех других ветвях, вы можете обернуть этот первый цикл for в другой цикл for repo.branches:

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: 2-й подход показывает первый коммит, который не появляется ни в одной другой ветке, который не обязательно является первым коммитом в этой ветке. Если feat_b ответвляется от feat_a, который исходит от master, тогда будет показан первый коммит на feat_a после того, как feat_b был разветвлен: остальные коммиты feat_a уже находятся на feat_b.
  • Предостережение 2: git rev-list, и оба эти решения работают только до тех пор, пока ветвь еще не объединена с master. Вы буквально просите его перечислить все коммиты в этой ветке, но не в другой.
  • Примечание: 2-й подход излишний и требует больше времени для завершения. Лучше всего ограничить другие ветви списком известных веток слияния, если у вас есть больше, чем просто мастер.

Я просто играл с Далвичем, может быть, есть гораздо лучший способ сделать это (с помощью встроенного Уокера?). Предполагая, что есть только одна новая ветвь (или несколько новых ветвей, не имеющих ничего общего):

#!/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) памяти для больших репозиториев, используйте итератор, чтобы обойти это)

Другие вопросы по тегам