31
31
self.message = self.revision_id
34
class RevisionProxy(object):
35
"""A revision proxy object.
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.
43
def __init__(self, revid, parent_ids, repository):
44
self.revision_id = revid
45
self.parent_ids = parent_ids
46
self._repository = repository
49
def _get_attribute_getter(attr):
50
def get_attribute(self):
51
if self._revision is None:
53
return getattr(self._revision, attr)
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'))
62
"""Load the revision object."""
63
self._revision = self._repository.get_revision(self.revision_id)
34
66
class DistanceMethod(object):
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 = {}
47
80
def fill_caches(self):
48
81
# FIXME: look at using repository.get_revision_graph_with_ghosts - RBC.
50
revisions = self.revisions
51
todo = set([self.start])
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:
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))
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)
75
# Remove duplicate parents
98
if len(revision.parent_ids) != len(set(revision.parent_ids)):
99
# fix the parent_ids list.
77
101
parent_ids_set = set()
78
102
for parent_id in revision.parent_ids:
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
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())
90
def first_ancestry_traversal(self):
91
# make a revision graph:
95
revisions = self.revisions
96
children_of_id = self.children_of_id
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:
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)
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
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]
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
268
275
sorted_children = sorted(available, key=available.get)
287
294
distance = DistanceMethod(branch, start)
288
295
distance.fill_caches()
289
sorted_revids = distance.first_ancestry_traversal()
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()
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)
300
305
if maxnum is not None:
301
del sorted_revids[maxnum:]
302
306
print 'FIXME: maxnum disabled.'
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)
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.
312
316
For each revision it then yields a tuple of (revision, node, lines).
417
421
drawn_parents.add(parent_id)
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:
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)
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:
460
464
parent_seq = revs[parent_id][0]