Поиск первого коммита в ветке с помощью GitPython

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

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

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

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

Я читал документацию для Dulwich, но не было ясно, будет ли он делать что-то сильно отличающееся от 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

person Julian Melville    schedule 29.08.2013    source источник


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

Я только что поигрался с 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)
person chlunde    schedule 29.08.2013
comment
Спасибо - я не видел ходячих объектов в Далвиче, и похоже, что я могу сделать это красиво с одним из них. Вскоре опубликую код. - person Julian Melville; 30.08.2013

Что-то вроде этого найдет первый коммит:

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

(Обратите внимание, что это будет использовать память O (n) для больших репозиториев, используйте итератор, чтобы обойти это)

person jelmer    schedule 27.10.2013