# HG changeset patch
# User Peer Sommerlund <[email protected]>
# Date 1251705048 -7200
logview: Add branch_grapher
Add a revision grapher that shows branches instead of
ancestory. The multiple merge pattern will show as two
swimlanes, instead of a long curtain. This also means that
it is not always possible to show the correct parent of a
changeset, although the correct branch will be visible.
diff -r 74b2963abd9b -r 52ad7538a583 hggtk/logview/revgraph.py
--- a/hggtk/logview/revgraph.py
+++ b/hggtk/logview/revgraph.py
@@ -145,6 +145,189 @@
curr_rev -= 1
+class BranchGrapher:
+ """Incremental branch grapher
+
+ This generator function produces a graph that uses loose ends to keep
+ focus on branches. All revisions in the range are shown, but not all edges.
+ The function identifies branches and may use loose ends if an edge links
+ two branches.
+ """
+
+ def __init__(self, repo, start_rev, stop_rev):
+ assert start_rev >= stop_rev
+ self.repo = repo
+
+ #
+ # Iterator content
+ # Variables that keep track of the iterator
+ #
+
+ # The current revision to process
+ self.curr_rev = start_rev
+
+ # Process all revs from start_rev to and including stop_rev
+ self.stop_rev = stop_rev
+
+ # List current branches. For each branch the next rev is listed
+ # The order of branches determine their columns
+ self.curr_branches = [start_rev]
+
+ # For each branch, tell the next version that will show up
+ self.next_in_branch = {}
+
+ #
+ # Graph variables
+ # These hold information related to the graph. Most are computed
lazily.
+ #
+
+ # Map rev to next rev in branch = first parent of rev.
+ # The parent of last rev in a branch is undefined,
+ # even if the revsion has a parent rev.
+ self.parent_of = {}
+
+ # Map rev to newest rev in branch. This identifies the branch that the
rev
+ # is part of
+ self.branch4rev = {}
+
+ # Last revision in branch
+ self.branch_tail = {}
+
+ # Map branch-id (head-rev of branch) to color
+ self.color4branch = {}
+
+ # Next colour used. for branches
+ self.nextcolor = 0
+
+ def _get_parents(self, rev):
+ return [x for x in self.repo.changelog.parentrevs(rev) if x != nullrev]
+
+ def _covered_rev(self, rev):
+ """True if rev is inside the revision range for the iterator"""
+ return self.stop_rev <= rev
+
+ def _new_branch(self, branch_head):
+ """Mark all unknown revisions in range that are direct ancestors
+ of branch_head as part of the same branch. Stops when stop_rev
+ is passed or a known revision is found"""
+ assert self._covered_rev(branch_head)
+ assert not branch_head in self.branch4rev
+ self.color4branch[branch_head] = self.nextcolor
+ self.nextcolor += 1
+ self.next_in_branch[branch_head] = branch_head
+ rev = branch_head
+ while not rev in self.branch4rev:
+ # TODO consider lazy evaluation here
+ if not self._covered_rev(rev):
+ # rev is outside visible range, so we don't know tail location
+ self.branch_tail[branch_head] = 0 # Prev revs wasn't tail
+ return
+ self.branch4rev[rev] = branch_head
+ self.branch_tail[branch_head] = rev
+ parents = self._get_parents(rev)
+ if not parents:
+ # All revisions have been exhausted (rev = 0)
+ self.parent_of[rev] = None
+ return
+ self.parent_of[rev] = parents[0]
+ rev = parents[0]
+
+ def _get_rev_branch(self, rev):
+ """Find revision branch or create a new branch"""
+ branch = self.branch4rev.get(rev)
+ if branch is None:
+ branch = rev
+ self._new_branch(branch)
+ assert rev in self.branch4rev
+ assert branch in self.branch_tail
+ return branch
+
+ def _compute_next_branches(self):
+ """Compute next row of branches"""
+ next_branches = self.curr_branches[:]
+ # Find branch used by current revision
+ curr_branch = self._get_rev_branch(self.curr_rev)
+ self.next_in_branch[curr_branch] = self.parent_of[self.curr_rev]
+ branch_index = self.curr_branches.index(curr_branch)
+ # Insert branches if parents point to new branches
+ new_parents = 0
+ for parent in self._get_parents(self.curr_rev):
+ branch = self._get_rev_branch(parent)
+ if not branch in next_branches:
+ new_parents += 1
+ next_branches.insert(branch_index + new_parents, branch)
+ # Delete branch if last revision
+ if self.curr_rev == self.branch_tail[curr_branch]:
+ del next_branches[branch_index]
+ # Return result
+ return next_branches
+
+ def _rev_color(self, rev):
+ """Map a revision to a color"""
+ return self.color4branch[self.branch4rev[rev]]
+
+ def _compute_lines(self, parents, next_branches):
+ # Compute lines (from CUR to NEXT branch row)
+ lines = []
+ curr_rev_branch = self.branch4rev[self.curr_rev]
+ for curr_column, branch in enumerate(self.curr_branches):
+ if branch == curr_rev_branch:
+ # Add lines from current branch to parents
+ for par_rev in parents:
+ par_branch = self._get_rev_branch(par_rev)
+ color = self.color4branch[par_branch]
+ next_column = next_branches.index(par_branch)
+ line_type = type_PLAIN
+ if par_rev != self.next_in_branch.get(par_branch):
+ line_type = type_LOOSE_LOW
+ lines.append( (curr_column, next_column, color, line_type)
)
+ else:
+ # Continue unrelated branch
+ color = self.color4branch[branch]
+ next_column = next_branches.index(branch)
+ lines.append( (curr_column, next_column, color, type_PLAIN) )
+ return lines
+
+ def more(self):
+ return self.curr_rev >= self.stop_rev
+
+ def next(self):
+ """Perform one iteration of the branch grapher"""
+
+ # Compute revision (on CUR branch row)
+ rev = self.curr_rev
+ rev_branch = self._get_rev_branch(rev)
+ if rev_branch not in self.curr_branches:
+ # New head
+ self.curr_branches.append(rev_branch)
+
+ # Compute parents (indicates the branches on NEXT branch row that
curr_rev links to)
+ parents = self._get_parents(rev)
+ # BUG: __get_parents is not defined - why?
+
+ # Compute lines (from CUR to NEXT branch row)
+ next_branches = self._compute_next_branches()
+ lines = self._compute_lines(parents, next_branches)
+
+ # Compute node info (for CUR branch row)
+ rev_column = self.curr_branches.index(rev_branch)
+ rev_color = self.color4branch[rev_branch]
+ node = (rev_column, rev_color)
+
+ # Next loop
+ self.curr_branches = next_branches
+ self.curr_rev -= 1
+
+ # Return result
+ # TODO: Refactor parents field away - it is apparently not used
anywhere
+ return (rev, node, lines, parents)
+
+def branch_grapher(repo, start_rev, stop_rev):
+ grapher = BranchGrapher(repo, start_rev, stop_rev)
+ while grapher.more():
+ yield grapher.next()
+
+
def filelog_grapher(repo, path):
'''
Graph the ancestry of a single file (log). Deletions show
------------------------------------------------------------------------------
Let Crystal Reports handle the reporting - Free Crystal Reports 2008 30-Day
trial. Simplify your report design, integration and deployment - and focus on
what you do best, core application coding. Discover what's new with
Crystal Reports now. http://p.sf.net/sfu/bobj-july
_______________________________________________
Tortoisehg-develop mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/tortoisehg-develop