/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz

« back to all changes in this revision

Viewing changes to graph.py

  • Committer: Robert Collins
  • Date: 2006-04-03 02:31:25 UTC
  • mto: (36.1.4 bzrk.jaq)
  • mto: This revision was merged to the branch mainline in revision 38.
  • Revision ID: robertc@robertcollins.net-20060403023125-1bc8678463e9cd36
Some more tweaking on the graph stuff - reducing duplicate effort and leveraging bzrlib more.

Show diffs side-by-side

added added

removed removed

Lines of Context:
31
31
        self.message = self.revision_id
32
32
 
33
33
 
 
34
class RevisionProxy(object):
 
35
    """A revision proxy object.
 
36
 
 
37
    This will demand load the revision it represents when the committer or
 
38
    message attributes are accessed in order to populate them. It is 
 
39
    constructed with the revision id and parent_ids list and a repository
 
40
    object to request the revision from when needed.
 
41
    """
 
42
 
 
43
    def __init__(self, revid, parent_ids, repository):
 
44
        self.revision_id = revid
 
45
        self.parent_ids = parent_ids
 
46
        self._repository = repository
 
47
        self._revision = None
 
48
 
 
49
    def _get_attribute_getter(attr):
 
50
        def get_attribute(self):
 
51
            if self._revision is None:
 
52
                self._load()
 
53
            return getattr(self._revision, attr)
 
54
        return get_attribute
 
55
    committer = property(_get_attribute_getter('committer'))
 
56
    message = property(_get_attribute_getter('message'))
 
57
    properties = property(_get_attribute_getter('properties'))
 
58
    timestamp = property(_get_attribute_getter('timestamp'))
 
59
    timezone = property(_get_attribute_getter('timezone'))
 
60
 
 
61
    def _load(self):
 
62
        """Load the revision object."""
 
63
        self._revision = self._repository.get_revision(self.revision_id)
 
64
 
 
65
 
34
66
class DistanceMethod(object):
35
67
 
36
68
    def __init__(self, branch, start):
43
75
        self.colours = { start: 0 }
44
76
        self.last_colour = 0
45
77
        self.direct_parent_of = {}
 
78
        self.graph = {}
46
79
 
47
80
    def fill_caches(self):
48
81
        # FIXME: look at using repository.get_revision_graph_with_ghosts - RBC.
49
 
        branch = self.branch
50
 
        revisions = self.revisions
51
 
        todo = set([self.start])
52
 
        while todo:
53
 
            revid = todo.pop()
54
 
            try:
55
 
                revision = branch.repository.get_revision(revid)
56
 
            except NoSuchRevision:
57
 
                revision = DummyRevision(revid)
58
 
            self.cache_revision(revid, revision)
59
 
            for parent_id in revision.parent_ids:
60
 
                if parent_id not in revisions:
61
 
                    todo.add(parent_id)
 
82
        graph = self.branch.repository.get_revision_graph_with_ghosts([self.start])
 
83
        for revid in graph.ghosts:
 
84
            self.cache_revision(DummyRevision(revid))
 
85
        for revid, parents in graph.get_ancestors().items():
 
86
            self.cache_revision(RevisionProxy(revid, parents, self.branch.repository))
62
87
 
63
 
    def cache_revision(self, revid, revision):
 
88
    def cache_revision(self, revision):
64
89
        "Set the caches for a newly retrieved revision."""
 
90
        revid = revision.revision_id
65
91
        # Build a revision cache
66
92
        self.revisions[revid] = revision
67
 
        # Build a children dictionnary
 
93
        # Build a children dictionary
68
94
        for parent_id in revision.parent_ids:
69
95
            self.children_of_id.setdefault(parent_id, set()).add(revision)
70
96
        # Build a parents dictionnary, where redundant parents will be removed,
71
97
        # and that will be passed along tothe rest of program.
72
 
        if len(revision.parent_ids) == len(set(revision.parent_ids)):
73
 
            self.parent_ids_of[revision] = list(revision.parent_ids)
74
 
        else:
75
 
            # Remove duplicate parents
 
98
        if len(revision.parent_ids) != len(set(revision.parent_ids)):
 
99
            # fix the parent_ids list.
76
100
            parent_ids = []
77
101
            parent_ids_set = set()
78
102
            for parent_id in revision.parent_ids:
80
104
                    continue
81
105
                parent_ids.append(parent_id)
82
106
                parent_ids_set.add(parent_id)
83
 
            self.parent_ids_of[revision] = parent_ids
 
107
            revision.parent_ids = parent_ids
 
108
        self.parent_ids_of[revision] = list(revision.parent_ids)
 
109
        self.graph[revid] = revision.parent_ids
84
110
 
85
111
    def make_children_map(self):
86
112
        revisions = self.revisions
87
113
        return dict((revisions[revid], c)
88
114
                    for (revid, c) in self.children_of_id.iteritems())
89
115
 
90
 
    def first_ancestry_traversal(self):
91
 
        # make a revision graph:
92
 
        self.graph = {}
93
 
        distances = {}
94
 
        todo = [self.start]
95
 
        revisions = self.revisions
96
 
        children_of_id = self.children_of_id
97
 
        while todo:
98
 
            revid = todo.pop(0)
99
 
            self.graph[revid] = self.revisions[revid].parent_ids
100
 
            for child in children_of_id[revid]:
101
 
                if child.revision_id not in distances:
102
 
                    todo.append(revid)
103
 
                    break
104
 
            else:
105
 
                distances[revid] = len(distances)
106
 
                for parent_id in revisions[revid].parent_ids:
107
 
                    if parent_id not in todo:
108
 
                        todo.insert(0, parent_id)
109
 
        # Topologically sorted revids, with the most recent revisions first.
110
 
        # A revision occurs only after all of its children.
111
 
        self.distances = distances
112
 
        self.merge_sorted = merge_sort(self.graph, self.start)
113
 
        return sorted(distances, key=distances.get)
114
 
 
115
116
    def remove_redundant_parents(self, sorted_revids):
116
117
        children_of_id = self.children_of_id
117
118
        revisions = self.revisions
247
248
            self.colours[revid] = self.last_colour = self.last_colour + 1
248
249
 
249
250
    def choose_colour_many_children(self, revision, the_children):
250
 
        distances = self.distances
 
251
        """Colour revision revision."""
251
252
        revid = revision.revision_id
252
253
        direct_parent_of = self.direct_parent_of
253
254
        # multiple children, get the colour of the last displayed child
261
262
            if direct_parent == revision:
262
263
                self.colours[revid] = self.colours[child.revision_id]
263
264
                break
264
 
            if direct_parent is None:
265
 
                available[child] = distances[child.revision_id]
 
265
            # FIXME: Colouring based on whats been displayed MUST be done with 
 
266
            # knowledge of the revisions being output.
 
267
            # until the refactoring to fold graph() into this more compactly is
 
268
            # done, I've disabled this reuse. RBC 20060403
 
269
            # if direct_parent is None:
 
270
            #     available[child] = distances[child.revision_id] 
 
271
            #   .. it will be something like available[child] =  \
 
272
            #  revs[child.revision_id][0] - which is the sequence number
266
273
        else:
267
274
            if available:
268
275
                sorted_children = sorted(available, key=available.get)
286
293
    """
287
294
    distance = DistanceMethod(branch, start)
288
295
    distance.fill_caches()
289
 
    sorted_revids = distance.first_ancestry_traversal()
290
296
    if robust:
291
297
        print 'robust filtering'
292
 
        distance.remove_redundant_parents(sorted_revids)
 
298
        distance.remove_redundant_parents(self.graph.keys())
 
299
    distance.merge_sorted = merge_sort(distance.graph, distance.start)
293
300
    children = distance.make_children_map()
294
 
    sorted_revids = []
295
301
    
296
302
    for seq, revid, merge_depth, end_of_merge in distance.merge_sorted:
297
 
        sorted_revids.append(revid)
298
303
        distance.choose_colour(revid)
299
304
 
300
305
    if maxnum is not None:
301
 
        del sorted_revids[maxnum:]
302
306
        print 'FIXME: maxnum disabled.'
303
307
 
304
308
    revisions = distance.revisions
306
310
    parent_ids_of = distance.parent_ids_of
307
311
    return (revisions, colours, children, parent_ids_of, distance.merge_sorted)
308
312
 
309
 
def graph(revisions, colours, parent_ids, merge_sorted):
 
313
def graph(revisions, colours, merge_sorted):
310
314
    """Produce a directed graph of a bzr branch.
311
315
 
312
316
    For each revision it then yields a tuple of (revision, node, lines).
417
421
                    drawn_parents.add(parent_id)
418
422
                else:
419
423
                    # this is the last revision in a 'merge', show where it came from
420
 
                    if len(parent_ids[revisions[revid]]) > 1:
 
424
                    if len(revisions[revid].parent_ids) > 1:
421
425
                        # having > 1
422
426
                        # parents means this commit was a merge, and being
423
427
                        # the end point of a merge group means that all
433
437
                        # it would show up as a merge into this making
434
438
                        # this not the end of a merge-line.
435
439
                        lowest = len(merge_sorted)
436
 
                        for parent_id in parent_ids[revisions[revid]]:
 
440
                        for parent_id in revisions[revid].parent_ids:
437
441
                            if revs[parent_id][0] < lowest:
438
442
                                lowest = revs[parent_id][0]
439
443
                        assert lowest != len(merge_sorted)
440
444
                        draw_line(h_idx, len(new_hanging), merge_sorted[lowest][1])
441
445
                        drawn_parents.add(merge_sorted[lowest][1])
442
 
                    elif len(parent_ids[revisions[revid]]) == 1:
 
446
                    elif len(revisions[revid].parent_ids) == 1:
443
447
                        # only one parent, must show this link to be useful.
444
 
                        parent_id = parent_ids[revisions[revid]][0]
 
448
                        parent_id = revisions[revid].parent_ids[0]
445
449
                        draw_line(h_idx, len(new_hanging), parent_id)
446
450
                        drawn_parents.add(parent_id)
447
451
                
454
458
                # hangs to keep the graph small.
455
459
                # RBC: we do not draw lines to parents that were already merged
456
460
                # unless its the last revision in a merge group.
457
 
                for parent_id in parent_ids[revisions[revid]]:
 
461
                for parent_id in revisions[revid].parent_ids:
458
462
                    if parent_id in drawn_parents:
459
463
                        continue
460
464
                    parent_seq = revs[parent_id][0]