[Pkg-bazaar-commits] ./bzr-gtk/unstable r38: [merge] lifeless graphing and performance improvements
David Allouche
david.allouche at canonical.com
Fri Apr 10 07:15:51 UTC 2009
------------------------------------------------------------
revno: 38
committer: David Allouche <david.allouche at canonical.com>
branch nick: ddaa
timestamp: Mon 2006-05-08 14:36:08 +0200
message:
[merge] lifeless graphing and performance improvements
modified:
__init__.py
branchwin.py
bzrkapp.py
graph.py
------------------------------------------------------------
revno: 37.1.1
committer: Robert Collins <robertc at robertcollins.net>
branch nick: trunk
timestamp: Wed 2006-03-29 19:15:10 +1100
message:
Retab branchwin.py
modified:
branchwin.py
------------------------------------------------------------
revno: 37.1.2
committer: Robert Collins <robertc at robertcollins.net>
branch nick: trunk
timestamp: Thu 2006-03-30 04:40:50 +1100
message:
Make revision sorting and linking use merge_sorted from latest bzr.dev. This
gives a much nicer layout with features such as 'fully merged branches' not
being inappropriately shown as active - rather then point the branch is
resurrected has its merge from mainline linked. This leads to a narrow,
more understandable view, and much less cross-hatch cases - perhaps none in
fact.
modified:
__init__.py
branchwin.py
bzrkapp.py
graph.py
------------------------------------------------------------
revno: 37.1.3
committer: Robert Collins <robertc at robertcollins.net>
branch nick: trunk
timestamp: Mon 2006-04-03 12:31:25 +1000
message:
Some more tweaking on the graph stuff - reducing duplicate effort and leveraging bzrlib more.
modified:
branchwin.py
graph.py
-------------- next part --------------
=== modified file '__init__.py'
--- a/__init__.py 2006-03-10 05:08:53 +0000
+++ b/__init__.py 2006-03-29 17:40:50 +0000
@@ -33,19 +33,15 @@
The --robust option enables removal of redundant parents. It is sometimes
useful on branches that contain corrupt revisions.
-
- The --accurate option enables a more expensive sorting algorithm to keep
- revisions on the same branch close.
"""
takes_options = [
"revision",
Option('robust', "ignore redundant parents"),
- Option('accurate', "sort revisions more carefully"),
Option('maxnum', "maximum number of revisions to display", int, 'count')]
takes_args = [ "location?" ]
aliases = [ "visualize", "vis", "viz" ]
- def run(self, location=".", revision=None, robust=False, accurate=False,
+ def run(self, location=".", revision=None, robust=False,
maxnum=None):
(branch, path) = Branch.open_containing(location)
branch.lock_read()
@@ -61,7 +57,7 @@
from bzrkapp import BzrkApp
app = BzrkApp()
- app.show(branch, revid, robust, accurate, maxnum)
+ app.show(branch, revid, robust, maxnum)
finally:
branch.repository.unlock()
branch.unlock()
=== modified file 'branchwin.py'
--- a/branchwin.py 2005-12-16 21:53:33 +0000
+++ b/branchwin.py 2006-04-03 02:31:25 +0000
@@ -238,7 +238,7 @@
return vbox
- def set_branch(self, branch, start, robust, accurate, maxnum):
+ def set_branch(self, branch, start, robust, maxnum):
"""Set the branch and start position for this window.
Creates a new TreeModel and populates it with information about
@@ -257,16 +257,18 @@
index = 0
last_lines = []
- (revids, self.revisions, colours, self.children, self.parent_ids) \
- = distances(branch, start, robust, accurate, maxnum)
+ (self.revisions, colours, self.children, self.parent_ids, merge_sorted) \
+ = distances(branch, start, robust, maxnum)
for revision, node, lines in graph(
- revids, self.revisions, colours, self.parent_ids):
+ self.revisions, colours, merge_sorted):
+ # FIXME: at this point we should be able to show the graph order and
+ # lines with no message or commit data - and then incrementally fill
+ # the timestamp, committer etc data as desired.
message = revision.message.split("\n")[0]
if revision.committer is not None:
timestamp = format_date(revision.timestamp, revision.timezone)
else:
timestamp = None
-
self.model.append([ revision, node, last_lines, lines,
message, revision.committer, timestamp ])
self.index[revision] = index
@@ -286,23 +288,23 @@
self.fwd_button.set_sensitive(len(self.children[revision]) > 0)
if revision.committer is not None:
- branchnick = ""
+ branchnick = ""
committer = revision.committer
timestamp = format_date(revision.timestamp, revision.timezone)
message = revision.message
- try:
- branchnick = revision.properties['branch-nick']
- except KeyError:
- pass
+ try:
+ branchnick = revision.properties['branch-nick']
+ except KeyError:
+ pass
else:
committer = ""
timestamp = ""
message = ""
- branchnick = ""
+ branchnick = ""
self.revid_label.set_text(revision.revision_id)
- self.branchnick_label.set_text(branchnick)
+ self.branchnick_label.set_text(branchnick)
self.committer_label.set_text(committer)
self.timestamp_label.set_text(timestamp)
=== modified file 'bzrkapp.py'
--- a/bzrkapp.py 2005-12-03 13:13:08 +0000
+++ b/bzrkapp.py 2006-03-29 17:40:50 +0000
@@ -27,10 +27,10 @@
the last window is closed.
"""
- def show(self, branch, start, robust, accurate, maxnum):
+ def show(self, branch, start, robust, maxnum):
"""Open a new window to show the given branch."""
window = BranchWindow(self)
- window.set_branch(branch, start, robust, accurate, maxnum)
+ window.set_branch(branch, start, robust, maxnum)
window.connect("destroy", self._destroy_cb)
window.show()
=== modified file 'graph.py'
--- a/graph.py 2006-03-26 20:30:07 +0000
+++ b/graph.py 2006-04-03 02:31:25 +0000
@@ -12,6 +12,7 @@
from bzrlib.errors import NoSuchRevision
+from bzrlib.tsort import merge_sort
class DummyRevision(object):
@@ -30,6 +31,38 @@
self.message = self.revision_id
+class RevisionProxy(object):
+ """A revision proxy object.
+
+ This will demand load the revision it represents when the committer or
+ message attributes are accessed in order to populate them. It is
+ constructed with the revision id and parent_ids list and a repository
+ object to request the revision from when needed.
+ """
+
+ def __init__(self, revid, parent_ids, repository):
+ self.revision_id = revid
+ self.parent_ids = parent_ids
+ self._repository = repository
+ self._revision = None
+
+ def _get_attribute_getter(attr):
+ def get_attribute(self):
+ if self._revision is None:
+ self._load()
+ return getattr(self._revision, attr)
+ return get_attribute
+ committer = property(_get_attribute_getter('committer'))
+ message = property(_get_attribute_getter('message'))
+ properties = property(_get_attribute_getter('properties'))
+ timestamp = property(_get_attribute_getter('timestamp'))
+ timezone = property(_get_attribute_getter('timezone'))
+
+ def _load(self):
+ """Load the revision object."""
+ self._revision = self._repository.get_revision(self.revision_id)
+
+
class DistanceMethod(object):
def __init__(self, branch, start):
@@ -42,35 +75,28 @@
self.colours = { start: 0 }
self.last_colour = 0
self.direct_parent_of = {}
+ self.graph = {}
def fill_caches(self):
- branch = self.branch
- revisions = self.revisions
- todo = set([self.start])
- while todo:
- revid = todo.pop()
- try:
- revision = branch.repository.get_revision(revid)
- except NoSuchRevision:
- revision = DummyRevision(revid)
- self.cache_revision(revid, revision)
- for parent_id in revision.parent_ids:
- if parent_id not in revisions:
- todo.add(parent_id)
+ # FIXME: look at using repository.get_revision_graph_with_ghosts - RBC.
+ graph = self.branch.repository.get_revision_graph_with_ghosts([self.start])
+ for revid in graph.ghosts:
+ self.cache_revision(DummyRevision(revid))
+ for revid, parents in graph.get_ancestors().items():
+ self.cache_revision(RevisionProxy(revid, parents, self.branch.repository))
- def cache_revision(self, revid, revision):
+ def cache_revision(self, revision):
"Set the caches for a newly retrieved revision."""
+ revid = revision.revision_id
# Build a revision cache
self.revisions[revid] = revision
- # Build a children dictionnary
+ # Build a children dictionary
for parent_id in revision.parent_ids:
self.children_of_id.setdefault(parent_id, set()).add(revision)
# Build a parents dictionnary, where redundant parents will be removed,
# and that will be passed along tothe rest of program.
- if len(revision.parent_ids) == len(set(revision.parent_ids)):
- self.parent_ids_of[revision] = list(revision.parent_ids)
- else:
- # Remove duplicate parents
+ if len(revision.parent_ids) != len(set(revision.parent_ids)):
+ # fix the parent_ids list.
parent_ids = []
parent_ids_set = set()
for parent_id in revision.parent_ids:
@@ -78,34 +104,15 @@
continue
parent_ids.append(parent_id)
parent_ids_set.add(parent_id)
- self.parent_ids_of[revision] = parent_ids
+ revision.parent_ids = parent_ids
+ self.parent_ids_of[revision] = list(revision.parent_ids)
+ self.graph[revid] = revision.parent_ids
def make_children_map(self):
revisions = self.revisions
return dict((revisions[revid], c)
for (revid, c) in self.children_of_id.iteritems())
- def first_ancestry_traversal(self):
- distances = {}
- todo = [self.start]
- revisions = self.revisions
- children_of_id = self.children_of_id
- while todo:
- revid = todo.pop(0)
- for child in children_of_id[revid]:
- if child.revision_id not in distances:
- todo.append(revid)
- break
- else:
- distances[revid] = len(distances)
- for parent_id in revisions[revid].parent_ids:
- if parent_id not in todo:
- todo.insert(0, parent_id)
- # Topologically sorted revids, with the most recent revisions first.
- # A revision occurs only after all of its children.
- self.distances = distances
- return sorted(distances, key=distances.get)
-
def remove_redundant_parents(self, sorted_revids):
children_of_id = self.children_of_id
revisions = self.revisions
@@ -241,7 +248,7 @@
self.colours[revid] = self.last_colour = self.last_colour + 1
def choose_colour_many_children(self, revision, the_children):
- distances = self.distances
+ """Colour revision revision."""
revid = revision.revision_id
direct_parent_of = self.direct_parent_of
# multiple children, get the colour of the last displayed child
@@ -255,8 +262,14 @@
if direct_parent == revision:
self.colours[revid] = self.colours[child.revision_id]
break
- if direct_parent is None:
- available[child] = distances[child.revision_id]
+ # FIXME: Colouring based on whats been displayed MUST be done with
+ # knowledge of the revisions being output.
+ # until the refactoring to fold graph() into this more compactly is
+ # done, I've disabled this reuse. RBC 20060403
+ # if direct_parent is None:
+ # available[child] = distances[child.revision_id]
+ # .. it will be something like available[child] = \
+ # revs[child.revision_id][0] - which is the sequence number
else:
if available:
sorted_children = sorted(available, key=available.get)
@@ -269,7 +282,7 @@
self.colours[revid] = self.last_colour = self.last_colour + 1
-def distances(branch, start, robust, accurate, maxnum):
+def distances(branch, start, robust, maxnum):
"""Sort the revisions.
Traverses the branch revision tree starting at start and produces an
@@ -280,26 +293,24 @@
"""
distance = DistanceMethod(branch, start)
distance.fill_caches()
- sorted_revids = distance.first_ancestry_traversal()
if robust:
print 'robust filtering'
- distance.remove_redundant_parents(sorted_revids)
+ distance.remove_redundant_parents(self.graph.keys())
+ distance.merge_sorted = merge_sort(distance.graph, distance.start)
children = distance.make_children_map()
- if accurate:
- print 'accurate sorting'
- sorted_revids = distance.sort_revisions(sorted_revids, maxnum)
- for revid in sorted_revids:
+
+ for seq, revid, merge_depth, end_of_merge in distance.merge_sorted:
distance.choose_colour(revid)
if maxnum is not None:
- del sorted_revids[maxnum:]
+ print 'FIXME: maxnum disabled.'
revisions = distance.revisions
colours = distance.colours
parent_ids_of = distance.parent_ids_of
- return (sorted_revids, revisions, colours, children, parent_ids_of)
+ return (revisions, colours, children, parent_ids_of, distance.merge_sorted)
-def graph(revids, revisions, colours, parent_ids):
+def graph(revisions, colours, merge_sorted):
"""Produce a directed graph of a bzr branch.
For each revision it then yields a tuple of (revision, node, lines).
@@ -321,39 +332,160 @@
It's up to you how to actually draw the nodes and lines (straight,
curved, kinked, etc.) and to pick the actual colours for each index.
"""
- hanging = revids[:1]
- for revid in revids:
+ if not len(merge_sorted):
+ return
+ # split merge_sorted into a map:
+ revs = {}
+ # FIXME: get a hint on this from the merge_sorted data rather than
+ # calculating it ourselves
+ # mapping from rev_id to the sequence number of the next lowest rev
+ next_lower_rev = {}
+ # mapping from rev_id to next-in-branch-revid - may be None for end
+ # of branch
+ next_branch_revid = {}
+ # the stack we are in in the sorted data for determining which
+ # next_lower_rev to set. It is a stack which has one list at each
+ # depth - the ids at that depth that need the same id allocated.
+ current_stack = [[]]
+ for seq, revid, indent, end_merge in merge_sorted:
+ revs[revid] = (seq, indent, end_merge)
+ if indent == len(current_stack):
+ # new merge group starts
+ current_stack.append([revid])
+ elif indent == len(current_stack) - 1:
+ # part of the current merge group
+ current_stack[-1].append(revid)
+ else:
+ # end of a merge group
+ while current_stack[-1]:
+ stack_rev_id = current_stack[-1].pop()
+ # record the next lower rev for this rev:
+ next_lower_rev[stack_rev_id] = seq
+ # if this followed a non-end-merge rev in this group note that
+ if len(current_stack[-1]):
+ if not revs[current_stack[-1][-1]][2]:
+ next_branch_revid[current_stack[-1][-1]] = stack_rev_id
+ current_stack.pop()
+ # append to the now-current merge group
+ current_stack[-1].append(revid)
+ # assign a value to all the depth 0 revisions
+ while current_stack[-1]:
+ stack_rev_id = current_stack[-1].pop()
+ # record the next lower rev for this rev:
+ next_lower_rev[stack_rev_id] = len(merge_sorted)
+ # if this followed a non-end-merge rev in this group note that
+ if len(current_stack[-1]):
+ if not revs[current_stack[-1][-1]][2]:
+ next_branch_revid[current_stack[-1][-1]] = stack_rev_id
+
+ # a list of the current revisions we are drawing lines TO indicating
+ # the sequence of their lines on the screen.
+ # i.e. [A, B, C] means that the line to A, to B, and to C are in
+ # (respectively), 0, 1, 2 on the screen.
+ hanging = [merge_sorted[0][1]]
+ for seq, revid, indent, end_merge in merge_sorted:
+ # a list of the lines to draw: their position in the
+ # previous row, their position in this row, and the colour
+ # (which is the colour they are routing to).
lines = []
- node = None
new_hanging = []
+
for h_idx, hang in enumerate(hanging):
+ # one of these will be the current lines node:
+ # we are drawing a line. h_idx
if hang == revid:
- # We've matched a hanging revision, so need to output a node
- # at this point
+ # we have found the current lines node
node = (h_idx, colours[revid])
+ # note that we might have done the main parent
+ drawn_parents = set()
+
+ def draw_line(from_idx, to_idx, revision_id):
+ try:
+ n_idx = new_hanging.index(revision_id)
+ except ValueError:
+ # force this to be vertical at the place this rev was
+ # drawn.
+ new_hanging.insert(to_idx, revision_id)
+ n_idx = to_idx
+ lines.append((from_idx, n_idx, colours[revision_id]))
+
+
+ # we want to draw a line to the next commit on 'this' branch
+ if not end_merge:
+ # drop this line first.
+ parent_id = next_branch_revid[revid]
+ draw_line(h_idx, h_idx, parent_id)
+ # we have drawn this parent
+ drawn_parents.add(parent_id)
+ else:
+ # this is the last revision in a 'merge', show where it came from
+ if len(revisions[revid].parent_ids) > 1:
+ # having > 1
+ # parents means this commit was a merge, and being
+ # the end point of a merge group means that all
+ # the parent revisions were merged into branches
+ # to the left of this before this was committed
+ # - so we want to show this as a new branch from
+ # those revisions.
+ # to do this, we show the parent with the lowest
+ # sequence number, which is the one that this
+ # branch 'spawned from', and no others.
+ # If this sounds like a problem, remember that:
+ # if the parent was not already in our mainline
+ # it would show up as a merge into this making
+ # this not the end of a merge-line.
+ lowest = len(merge_sorted)
+ for parent_id in revisions[revid].parent_ids:
+ if revs[parent_id][0] < lowest:
+ lowest = revs[parent_id][0]
+ assert lowest != len(merge_sorted)
+ draw_line(h_idx, len(new_hanging), merge_sorted[lowest][1])
+ drawn_parents.add(merge_sorted[lowest][1])
+ elif len(revisions[revid].parent_ids) == 1:
+ # only one parent, must show this link to be useful.
+ parent_id = revisions[revid].parent_ids[0]
+ draw_line(h_idx, len(new_hanging), parent_id)
+ drawn_parents.add(parent_id)
+
+ # what do we want to draw lines to from here:
+ # each parent IF its relevant.
+ #
# Now we need to hang its parents, we put them at the point
# the old column was so anything to the right of this has
# to move outwards to make room. We also try and collapse
# hangs to keep the graph small.
- for parent_id in parent_ids[revisions[revid]]:
- try:
- n_idx = new_hanging.index(parent_id)
- except ValueError:
- n_idx = len(new_hanging)
- new_hanging.append(parent_id)
- lines.append((h_idx, n_idx, colours[parent_id]))
+ # RBC: we do not draw lines to parents that were already merged
+ # unless its the last revision in a merge group.
+ for parent_id in revisions[revid].parent_ids:
+ if parent_id in drawn_parents:
+ continue
+ parent_seq = revs[parent_id][0]
+ parent_depth = revs[parent_id][1]
+ if parent_depth == indent + 1:
+ # the parent was a merge into this branch
+ # determine if it was already merged into the mainline
+ # via a different merge:
+ # if all revisions between us and parent_seq have a
+ # indent greater than there are no revisions with a lower indent than
+ # us.
+ # we do not use 'parent_depth < indent' because that would allow
+ # un-uniqueified merges to show up, and merge_sorted should take
+ # care of that for us (but does not trim the values)
+ if parent_seq < next_lower_rev[revid]:
+ draw_line(h_idx, len(new_hanging), parent_id)
+ elif parent_depth == indent and parent_seq == seq + 1:
+ # part of this branch
+ draw_line(h_idx, len(new_hanging), parent_id)
else:
- # Revision keeps on hanging, adjust for any change in the
- # graph shape and try to collapse hangs to keep the graph
- # small.
- try:
- n_idx = new_hanging.index(hang)
- except ValueError:
- n_idx = len(new_hanging)
- new_hanging.append(hang)
- lines.append((h_idx, n_idx, colours[hang]))
+ # draw a line from the previous position of this line to the
+ # new position.
+ # h_idx is the old position.
+ # new_indent is the new position.
+ draw_line(h_idx, len(new_hanging), hang)
+ # we've calculated the row, assign new_hanging to hanging to setup for
+ # the next row
hanging = new_hanging
yield (revisions[revid], node, lines)
More information about the Pkg-bazaar-commits
mailing list