Нахождение первого коммита на ветке с помощью 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) памяти для больших репозиториев, используйте итератор, чтобы обойти это)