[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