# -*- coding: utf-8 -*- # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ Modified mercurial DAG graph functions that re-uses VCS structure It allows to have a shared codebase for DAG generation for hg and git repos """ nullrev = -1 def _first_known_ancestors(parentrev_func, minrev, knownrevs, head): """ Return the apparent parents of the head revision in a filtered DAG. This is like calling plain parentrev_func, except that if the parent has been filtered (isn't in knownrevs), recurse on its parents. For ancestors that are unknown in knownrevs, the revision number is negated. (This means that a Mercurial revision can have more than 2 "parents".) parentrev_func defines the full DAG. knownrevs contains the subset we care about. minrev is lower bound on knownrevs. """ pending = set([head]) seen = set() ancestors = set() while pending: r = pending.pop() if r < minrev: if r > nullrev: # ignore -1 returned from parentrev_func ancestors.add(-head) # fake unique unknown parent for this rev elif r in knownrevs: ancestors.add(r) elif r not in seen: pending.update(parentrev_func(r)) seen.add(r) return ancestors def graph_data(repo, revs): """Return a DAG with colored edge information for revs For each DAG node this function emits tuples:: ((col, color), [(col, nextcol, color)]) with the following new elements: - Tuple (col, color) with column and color index for the current node - A list of tuples indicating the edges between the current node and its parents. """ dag = _dagwalker(repo, revs) return list(_colored(repo, dag)) def _dagwalker(repo, revs): """Iterate over revs, yielding revs (highest first) and parents to show in the graph.""" if not revs: return if repo.alias == 'hg': parentrev_func = repo._repo.changelog.parentrevs elif repo.alias == 'git': def parentrev_func(rev): return [x.revision for x in repo[rev].parents] minrev = revs[-1] # assuming sorted with highest revision numbers first knownrevs = set(revs) acache = {} for rev in revs: parents = set(parentrev_func(rev)) - set([nullrev]) dagparents = parents & knownrevs # Calculate indirect parents for p in parents - dagparents: ancestors = acache.get(p) if ancestors is None: ancestors = acache[p] = _first_known_ancestors(parentrev_func, minrev, knownrevs, p) dagparents.update(ancestors) yield (rev, sorted(dagparents)) def _colored(repo, dag): """annotates a DAG with colored edge information For each DAG node this function emits tuples:: ((col, color), [(col, nextcol, color)]) with the following new elements: - Tuple (col, color) with column and color index for the current node - A list of tuples indicating the edges between the current node and its parents. """ branch_cache = {} def branch(rev): """Return branch for rev, using cache for efficiency. For Mercurial, always return the named branch name (which may be 'default'). For Git, return a branch name for branch heads, otherwise None.""" if rev not in branch_cache: branch_cache[rev] = repo[rev].branch return branch_cache[rev] row = [] # the ancestor revision that each column is tracking colors = {} # color number for revisions - set by descendants obs = {} # obsolete flag for revisions - set by descendants newcolor = 1 # the next available color for (rev, dagparents) in dag: # Compute row and nextrow if rev not in row: row.append(rev) # new head colors[rev] = newcolor obs[rev] = int(repo[rev].obsolete) newcolor += 1 col = row.index(rev) addparents = [p for p in dagparents if p not in row] # Add unknown parents to nextrow tmprow = row[:] tmprow[col:col + 1] = reversed(addparents) # highest revs first (to the right), dead ends last (to the left) # Filter old fake parents but keep new ones nextrow = [] for r in tmprow: if r >= 0 or r in dagparents: nextrow.append(r) else: colors.pop(r) obs.pop(r) # Let color and obs for this rev be "inherited" by the first "parent" color = colors.pop(rev) if addparents: b = branch(rev) searching = True for p in reversed(addparents): obs[p] = int(repo[p].obsolete) if p >= 0 else 0 if searching and branch(abs(p)) in [b, None]: # This is the first parent on the same branch - inherit the color colors[p] = color searching = False # make sure we don't give the color away twice else: colors[p] = newcolor newcolor += 1 # Add edges to the graph edges = [] for ecol, ep in enumerate(row): if ep in nextrow: edges.append((ecol, nextrow.index(ep), colors[ep], obs[ep])) elif ep == rev: for p in dagparents: edges.append((ecol, nextrow.index(p), colors[p], obs[p])) # Yield and move on closing = int(repo[rev].closesbranch) obsolete = int(repo[rev].obsolete) bumped = int(repo[rev].bumped) divergent = int(repo[rev].divergent) extinct = int(repo[rev].extinct) unstable = int(repo[rev].unstable) yield ((col, color), edges, closing, obsolete, bumped, divergent, extinct, unstable) row = nextrow