44
45
# 2. Since len(['D', 'E']) > 1, find_lca('D', 'E') => ['A']
48
class DictParentsProvider(object):
50
def __init__(self, ancestry):
51
self.ancestry = ancestry
54
return 'DictParentsProvider(%r)' % self.ancestry
56
@symbol_versioning.deprecated_method(symbol_versioning.one_one)
57
def get_parents(self, revisions):
58
return [self.ancestry.get(r, None) for r in revisions]
60
def get_parent_map(self, keys):
61
"""See _StackedParentsProvider.get_parent_map"""
62
ancestry = self.ancestry
63
return dict((k, ancestry[k]) for k in keys if k in ancestry)
48
66
class _StackedParentsProvider(object):
65
84
If the revision is not present (i.e. a ghost), None is used in place
66
85
of the list of parents.
87
found = self.get_parent_map(revision_ids)
88
return [found.get(r, None) for r in revision_ids]
90
def get_parent_map(self, keys):
91
"""Get a mapping of keys => parents
93
A dictionary is returned with an entry for each key present in this
94
source. If this source doesn't have information about a key, it should
97
[NULL_REVISION] is used as the parent of the first user-committed
98
revision. Its parent list is empty.
100
:param keys: An iterable returning keys to check (eg revision_ids)
101
:return: A dictionary mapping each key to its parents
104
remaining = set(keys)
69
105
for parents_provider in self._parent_providers:
70
pending_revisions = [r for r in revision_ids if r not in found]
71
parent_list = parents_provider.get_parents(pending_revisions)
72
new_found = dict((k, v) for k, v in zip(pending_revisions,
73
parent_list) if v is not None)
106
new_found = parents_provider.get_parent_map(remaining)
74
107
found.update(new_found)
75
if len(found) == len(revision_ids):
108
remaining.difference_update(new_found)
114
class CachingParentsProvider(object):
115
"""A parents provider which will cache the revision => parents in a dict.
117
This is useful for providers that have an expensive lookup.
120
def __init__(self, parent_provider):
121
self._real_provider = parent_provider
122
# Theoretically we could use an LRUCache here
126
return "%s(%r)" % (self.__class__.__name__, self._real_provider)
128
@symbol_versioning.deprecated_method(symbol_versioning.one_one)
129
def get_parents(self, revision_ids):
130
"""See _StackedParentsProvider.get_parents"""
131
found = self.get_parent_map(revision_ids)
77
132
return [found.get(r, None) for r in revision_ids]
134
def get_parent_map(self, keys):
135
"""See _StackedParentsProvider.get_parent_map"""
137
# If the _real_provider doesn't have a key, we cache a value of None,
138
# which we then later use to realize we cannot provide a value for that
145
if value is not None:
146
parent_map[key] = value
151
new_parents = self._real_provider.get_parent_map(needed)
152
cache.update(new_parents)
153
parent_map.update(new_parents)
154
needed.difference_update(new_parents)
155
cache.update(dict.fromkeys(needed, None))
80
159
class Graph(object):
81
160
"""Provide incremental access to revision graphs.
231
311
order if they need it.
233
313
candidate_heads = set(keys)
314
if revision.NULL_REVISION in candidate_heads:
315
# NULL_REVISION is only a head if it is the only entry
316
candidate_heads.remove(revision.NULL_REVISION)
317
if not candidate_heads:
318
return set([revision.NULL_REVISION])
234
319
if len(candidate_heads) < 2:
235
320
return candidate_heads
236
321
searchers = dict((c, self._make_breadth_first_searcher([c]))
311
397
Note that None is not an acceptable substitute for NULL_REVISION.
312
398
in the input for this method.
400
:param count_steps: If True, the return value will be a tuple of
401
(unique_lca, steps) where steps is the number of times that
402
find_lca was run. If False, only unique_lca is returned.
314
404
revisions = [left_revision, right_revision]
316
408
lca = self.find_lca(*revisions)
317
409
if len(lca) == 1:
319
415
if len(lca) == 0:
320
416
raise errors.NoCommonAncestor(left_revision, right_revision)
327
423
An ancestor may sort after a descendant if the relationship is not
328
424
visible in the supplied list of revisions.
330
sorter = tsort.TopoSorter(zip(revisions, self.get_parents(revisions)))
426
sorter = tsort.TopoSorter(self.get_parent_map(revisions))
331
427
return sorter.iter_topo_order()
333
429
def is_ancestor(self, candidate_ancestor, candidate_descendant):
334
430
"""Determine whether a revision is an ancestor of another.
336
432
We answer this using heads() as heads() has the logic to perform the
337
smallest number of parent looksup to determine the ancestral
433
smallest number of parent lookups to determine the ancestral
338
434
relationship between N revisions.
340
436
return set([candidate_descendant]) == self.heads(
405
501
self._start = set(revisions)
406
502
self._search_revisions = None
407
503
self.seen = set(revisions)
408
self._parents_provider = parents_provider
504
self._parents_provider = parents_provider
410
506
def __repr__(self):
411
return ('_BreadthFirstSearcher(self._search_revisions=%r,'
412
' self.seen=%r)' % (self._search_revisions, self.seen))
507
if self._search_revisions is not None:
508
search = 'searching=%r' % (list(self._search_revisions),)
510
search = 'starting=%r' % (list(self._start),)
511
return ('_BreadthFirstSearcher(%s,'
512
' seen=%r)' % (search, list(self.seen)))
415
515
"""Return the next ancestors of this revision.
421
521
self._search_revisions = self._start
423
523
new_search_revisions = set()
424
for parents in self._parents_provider.get_parents(
425
self._search_revisions):
524
parent_map = self._parents_provider.get_parent_map(
525
self._search_revisions)
526
for parents in parent_map.itervalues():
428
527
new_search_revisions.update(p for p in parents if
429
528
p not in self.seen)
430
529
self._search_revisions = new_search_revisions