Encontrar el primer commit en una twig con GitPython

Estoy escribiendo un gancho post-recepción de git usando Python y Git-Python que reúne información sobre los commits contenidos en un push, luego actualiza nuestro rastreador de errores y postría instantánea con un resumen. Estoy teniendo problemas en el caso en que un push crea una twig (es decir, el parámetro fromrev a post-receive es todo ceros) y también abarca varias confirmaciones en esa twig. Estoy caminando por la list de padres hacia atrás desde el compromiso de torev , pero no puedo encontrar la manera de decir qué compromiso es el primero en la twig, es decir, cuándo dejar de search.

En la línea de command, puedo hacer

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

que me dará exactamente la list de commits en this-branch , y no otros. Intenté replicar esto usando el método Commit.iter_parents , que está documentado para tomar los mismos parameters que git-rev-list, pero no me gustan los parameters de position, por lo que puedo ver, y no puedo encontrar un set de parameters de palabras key que funcionan.

Leí el doco para Dulwich pero no estaba claro si haría algo muy diferente a Git-Python.

Mi código (simplificado) se ve así. Cuando un push inicia una nueva twig, actualmente solo mira el primer commit y luego se detiene:

 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 

Acabo de jugar con dulwich, tal vez hay una manera mucho mejor de hacer esto (¿con un andador incorporado?). Asumiendo que solo hay una nueva twig (o múltiples twigs nuevas sin nada en común):

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

Usando Git-Python puro, también se puede hacer. No he encontrado una manera de identificar un set de kwargs que lo hiciera de una vez tampoco. Pero uno puede simplemente build un set de shas de la twig principal, luego usar iter_commits en la twig a ser examinada para encontrar la primera que no aparece en el padre:

 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) 

Y si realmente quiere asegurarse de excluir todas las confirmaciones en todas las demás twigs, puede ajustar ese primer for-loop en otro for-loop sobre 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) 
  • Advertencia 1: el segundo enfoque muestra el primer compromiso que no aparece en ninguna otra twig, que no es necesariamente la primera confirmación en esta twig. Si feat_b se bifurca de feat_a, que proviene de master, esto mostrará el primer commit en feat_a después de que feat_b haya sido ramificado: el rest de los commit_a's commits ya están en feat_b.
  • Advertencia 2: git rev-list y ambas soluciones solo funcionan siempre que la twig no se haya fusionado de nuevo al maestro. Literalmente le pides que liste todos los commits en esta twig pero no en la otra.
  • Observación: el segundo enfoque es excesivo y toma un poco más de time para completarse. Un mejor enfoque es limitar las otras twigs a una list de twigs de combinación conocidas, en caso de que tenga algo más que dominar.

Algo como esto encontrará el primer compromiso:

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

(Tenga en count que esto usará memory O (n) para repositorys grandes, use un iterador para evitar eso)