~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/graph.py

  • Committer: Martin Pool
  • Date: 2009-01-13 03:06:36 UTC
  • mfrom: (3932.2.3 1.11)
  • mto: This revision was merged to the branch mainline in revision 3937.
  • Revision ID: mbp@sourcefrog.net-20090113030636-dqx4t8yaaqgdvam5
MergeĀ 1.11rc1

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2011 Canonical Ltd
 
1
# Copyright (C) 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
from __future__ import absolute_import
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
16
 
19
17
import time
20
18
 
21
19
from bzrlib import (
22
20
    debug,
23
21
    errors,
24
 
    osutils,
25
22
    revision,
 
23
    symbol_versioning,
26
24
    trace,
 
25
    tsort,
27
26
    )
28
27
 
29
28
STEP_UNIQUE_SEARCHER_EVERY = 5
60
59
    def __repr__(self):
61
60
        return 'DictParentsProvider(%r)' % self.ancestry
62
61
 
63
 
    # Note: DictParentsProvider does not implement get_cached_parent_map
64
 
    #       Arguably, the data is clearly cached in memory. However, this class
65
 
    #       is mostly used for testing, and it keeps the tests clean to not
66
 
    #       change it.
67
 
 
68
62
    def get_parent_map(self, keys):
69
 
        """See StackedParentsProvider.get_parent_map"""
 
63
        """See _StackedParentsProvider.get_parent_map"""
70
64
        ancestry = self.ancestry
71
 
        return dict([(k, ancestry[k]) for k in keys if k in ancestry])
72
 
 
73
 
 
74
 
class StackedParentsProvider(object):
75
 
    """A parents provider which stacks (or unions) multiple providers.
76
 
 
77
 
    The providers are queries in the order of the provided parent_providers.
78
 
    """
 
65
        return dict((k, ancestry[k]) for k in keys if k in ancestry)
 
66
 
 
67
 
 
68
class _StackedParentsProvider(object):
79
69
 
80
70
    def __init__(self, parent_providers):
81
71
        self._parent_providers = parent_providers
82
72
 
83
73
    def __repr__(self):
84
 
        return "%s(%r)" % (self.__class__.__name__, self._parent_providers)
 
74
        return "_StackedParentsProvider(%r)" % self._parent_providers
85
75
 
86
76
    def get_parent_map(self, keys):
87
77
        """Get a mapping of keys => parents
98
88
        """
99
89
        found = {}
100
90
        remaining = set(keys)
101
 
        # This adds getattr() overhead to each get_parent_map call. However,
102
 
        # this is StackedParentsProvider, which means we're dealing with I/O
103
 
        # (either local indexes, or remote RPCs), so CPU overhead should be
104
 
        # minimal.
105
 
        for parents_provider in self._parent_providers:
106
 
            get_cached = getattr(parents_provider, 'get_cached_parent_map',
107
 
                                 None)
108
 
            if get_cached is None:
109
 
                continue
110
 
            new_found = get_cached(remaining)
111
 
            found.update(new_found)
112
 
            remaining.difference_update(new_found)
113
 
            if not remaining:
114
 
                break
115
 
        if not remaining:
116
 
            return found
117
91
        for parents_provider in self._parent_providers:
118
92
            new_found = parents_provider.get_parent_map(remaining)
119
93
            found.update(new_found)
147
121
            self._get_parent_map = self._real_provider.get_parent_map
148
122
        else:
149
123
            self._get_parent_map = get_parent_map
150
 
        self._cache = None
151
 
        self.enable_cache(True)
 
124
        self._cache = {}
 
125
        self._cache_misses = True
152
126
 
153
127
    def __repr__(self):
154
128
        return "%s(%r)" % (self.__class__.__name__, self._real_provider)
159
133
            raise AssertionError('Cache enabled when already enabled.')
160
134
        self._cache = {}
161
135
        self._cache_misses = cache_misses
162
 
        self.missing_keys = set()
163
136
 
164
137
    def disable_cache(self):
165
138
        """Disable and clear the cache."""
166
139
        self._cache = None
167
 
        self._cache_misses = None
168
 
        self.missing_keys = set()
169
140
 
170
141
    def get_cached_map(self):
171
142
        """Return any cached get_parent_map values."""
172
143
        if self._cache is None:
173
144
            return None
174
 
        return dict(self._cache)
175
 
 
176
 
    def get_cached_parent_map(self, keys):
177
 
        """Return items from the cache.
178
 
 
179
 
        This returns the same info as get_parent_map, but explicitly does not
180
 
        invoke the supplied ParentsProvider to search for uncached values.
181
 
        """
182
 
        cache = self._cache
183
 
        if cache is None:
184
 
            return {}
185
 
        return dict([(key, cache[key]) for key in keys if key in cache])
 
145
        return dict((k, v) for k, v in self._cache.items()
 
146
                    if v is not None)
186
147
 
187
148
    def get_parent_map(self, keys):
188
 
        """See StackedParentsProvider.get_parent_map."""
189
 
        cache = self._cache
190
 
        if cache is None:
191
 
            cache = self._get_parent_map(keys)
 
149
        """See _StackedParentsProvider.get_parent_map."""
 
150
        # Hack to build up the caching logic.
 
151
        ancestry = self._cache
 
152
        if ancestry is None:
 
153
            # Caching is disabled.
 
154
            missing_revisions = set(keys)
 
155
            ancestry = {}
192
156
        else:
193
 
            needed_revisions = set(key for key in keys if key not in cache)
194
 
            # Do not ask for negatively cached keys
195
 
            needed_revisions.difference_update(self.missing_keys)
196
 
            if needed_revisions:
197
 
                parent_map = self._get_parent_map(needed_revisions)
198
 
                cache.update(parent_map)
199
 
                if self._cache_misses:
200
 
                    for key in needed_revisions:
201
 
                        if key not in parent_map:
202
 
                            self.note_missing_key(key)
203
 
        result = {}
204
 
        for key in keys:
205
 
            value = cache.get(key)
206
 
            if value is not None:
207
 
                result[key] = value
208
 
        return result
209
 
 
210
 
    def note_missing_key(self, key):
211
 
        """Note that key is a missing key."""
212
 
        if self._cache_misses:
213
 
            self.missing_keys.add(key)
214
 
 
215
 
 
216
 
class CallableToParentsProviderAdapter(object):
217
 
    """A parents provider that adapts any callable to the parents provider API.
218
 
 
219
 
    i.e. it accepts calls to self.get_parent_map and relays them to the
220
 
    callable it was constructed with.
221
 
    """
222
 
 
223
 
    def __init__(self, a_callable):
224
 
        self.callable = a_callable
225
 
 
226
 
    def __repr__(self):
227
 
        return "%s(%r)" % (self.__class__.__name__, self.callable)
228
 
 
229
 
    def get_parent_map(self, keys):
230
 
        return self.callable(keys)
 
157
            missing_revisions = set(key for key in keys if key not in ancestry)
 
158
        if missing_revisions:
 
159
            parent_map = self._get_parent_map(missing_revisions)
 
160
            ancestry.update(parent_map)
 
161
            if self._cache_misses:
 
162
                # None is never a valid parents list, so it can be used to
 
163
                # record misses.
 
164
                ancestry.update(dict((k, None) for k in missing_revisions
 
165
                                     if k not in parent_map))
 
166
        present_keys = [k for k in keys if ancestry.get(k) is not None]
 
167
        return dict((k, ancestry[k]) for k in present_keys)
231
168
 
232
169
 
233
170
class Graph(object):
284
221
        common ancestor of all border ancestors, because this shows that it
285
222
        cannot be a descendant of any border ancestor.
286
223
 
287
 
        The scaling of this operation should be proportional to:
288
 
 
 
224
        The scaling of this operation should be proportional to
289
225
        1. The number of uncommon ancestors
290
226
        2. The number of border ancestors
291
227
        3. The length of the shortest path between a border ancestor and an
306
242
        right = searchers[1].seen
307
243
        return (left.difference(right), right.difference(left))
308
244
 
309
 
    def find_descendants(self, old_key, new_key):
310
 
        """Find descendants of old_key that are ancestors of new_key."""
311
 
        child_map = self.get_child_map(self._find_descendant_ancestors(
312
 
            old_key, new_key))
313
 
        graph = Graph(DictParentsProvider(child_map))
314
 
        searcher = graph._make_breadth_first_searcher([old_key])
315
 
        list(searcher)
316
 
        return searcher.seen
317
 
 
318
 
    def _find_descendant_ancestors(self, old_key, new_key):
319
 
        """Find ancestors of new_key that may be descendants of old_key."""
320
 
        stop = self._make_breadth_first_searcher([old_key])
321
 
        descendants = self._make_breadth_first_searcher([new_key])
322
 
        for revisions in descendants:
323
 
            old_stop = stop.seen.intersection(revisions)
324
 
            descendants.stop_searching_any(old_stop)
325
 
            seen_stop = descendants.find_seen_ancestors(stop.step())
326
 
            descendants.stop_searching_any(seen_stop)
327
 
        return descendants.seen.difference(stop.seen)
328
 
 
329
 
    def get_child_map(self, keys):
330
 
        """Get a mapping from parents to children of the specified keys.
331
 
 
332
 
        This is simply the inversion of get_parent_map.  Only supplied keys
333
 
        will be discovered as children.
334
 
        :return: a dict of key:child_list for keys.
335
 
        """
336
 
        parent_map = self._parents_provider.get_parent_map(keys)
337
 
        parent_child = {}
338
 
        for child, parents in sorted(parent_map.items()):
339
 
            for parent in parents:
340
 
                parent_child.setdefault(parent, []).append(child)
341
 
        return parent_child
342
 
 
343
245
    def find_distance_to_null(self, target_revision_id, known_revision_ids):
344
246
        """Find the left-hand distance to the NULL_REVISION.
345
247
 
393
295
        # get there.
394
296
        return known_revnos[cur_tip] + num_steps
395
297
 
396
 
    def find_lefthand_distances(self, keys):
397
 
        """Find the distance to null for all the keys in keys.
398
 
 
399
 
        :param keys: keys to lookup.
400
 
        :return: A dict key->distance for all of keys.
401
 
        """
402
 
        # Optimisable by concurrent searching, but a random spread should get
403
 
        # some sort of hit rate.
404
 
        result = {}
405
 
        known_revnos = []
406
 
        ghosts = []
407
 
        for key in keys:
408
 
            try:
409
 
                known_revnos.append(
410
 
                    (key, self.find_distance_to_null(key, known_revnos)))
411
 
            except errors.GhostRevisionsHaveNoRevno:
412
 
                ghosts.append(key)
413
 
        for key in ghosts:
414
 
            known_revnos.append((key, -1))
415
 
        return dict(known_revnos)
416
 
 
417
298
    def find_unique_ancestors(self, unique_revision, common_revisions):
418
299
        """Find the unique ancestors for a revision versus others.
419
300
 
423
304
 
424
305
        :param unique_revision: The revision_id whose ancestry we are
425
306
            interested in.
426
 
            (XXX: Would this API be better if we allowed multiple revisions on
427
 
            to be searched here?)
 
307
            XXX: Would this API be better if we allowed multiple revisions on
 
308
                 to be searched here?
428
309
        :param common_revisions: Revision_ids of ancestries to exclude.
429
310
        :return: A set of revisions in the ancestry of unique_revision
430
311
        """
670
551
    def _refine_unique_nodes(self, unique_searcher, all_unique_searcher,
671
552
                             unique_tip_searchers, common_searcher):
672
553
        """Steps 5-8 of find_unique_ancestors.
673
 
 
 
554
        
674
555
        This function returns when common_searcher has stopped searching for
675
556
        more nodes.
676
557
        """
719
600
                                 all_unique_searcher._iterations)
720
601
            unique_tip_searchers = next_unique_searchers
721
602
 
 
603
    @symbol_versioning.deprecated_method(symbol_versioning.one_one)
 
604
    def get_parents(self, revisions):
 
605
        """Find revision ids of the parents of a list of revisions
 
606
 
 
607
        A list is returned of the same length as the input.  Each entry
 
608
        is a list of parent ids for the corresponding input revision.
 
609
 
 
610
        [NULL_REVISION] is used as the parent of the first user-committed
 
611
        revision.  Its parent list is empty.
 
612
 
 
613
        If the revision is not present (i.e. a ghost), None is used in place
 
614
        of the list of parents.
 
615
 
 
616
        Deprecated in bzr 1.2 - please see get_parent_map.
 
617
        """
 
618
        parents = self.get_parent_map(revisions)
 
619
        return [parents.get(r, None) for r in revisions]
 
620
 
722
621
    def get_parent_map(self, revisions):
723
622
        """Get a map of key:parent_list for revisions.
724
623
 
944
843
                stop.add(parent_id)
945
844
        return found
946
845
 
947
 
    def find_lefthand_merger(self, merged_key, tip_key):
948
 
        """Find the first lefthand ancestor of tip_key that merged merged_key.
949
 
 
950
 
        We do this by first finding the descendants of merged_key, then
951
 
        walking through the lefthand ancestry of tip_key until we find a key
952
 
        that doesn't descend from merged_key.  Its child is the key that
953
 
        merged merged_key.
954
 
 
955
 
        :return: The first lefthand ancestor of tip_key to merge merged_key.
956
 
            merged_key if it is a lefthand ancestor of tip_key.
957
 
            None if no ancestor of tip_key merged merged_key.
958
 
        """
959
 
        descendants = self.find_descendants(merged_key, tip_key)
960
 
        candidate_iterator = self.iter_lefthand_ancestry(tip_key)
961
 
        last_candidate = None
962
 
        for candidate in candidate_iterator:
963
 
            if candidate not in descendants:
964
 
                return last_candidate
965
 
            last_candidate = candidate
966
 
 
967
846
    def find_unique_lca(self, left_revision, right_revision,
968
847
                        count_steps=False):
969
848
        """Find a unique LCA.
1021
900
                yield (ghost, None)
1022
901
            pending = next_pending
1023
902
 
1024
 
    def iter_lefthand_ancestry(self, start_key, stop_keys=None):
1025
 
        if stop_keys is None:
1026
 
            stop_keys = ()
1027
 
        next_key = start_key
1028
 
        def get_parents(key):
1029
 
            try:
1030
 
                return self._parents_provider.get_parent_map([key])[key]
1031
 
            except KeyError:
1032
 
                raise errors.RevisionNotPresent(next_key, self)
1033
 
        while True:
1034
 
            if next_key in stop_keys:
1035
 
                return
1036
 
            parents = get_parents(next_key)
1037
 
            yield next_key
1038
 
            if len(parents) == 0:
1039
 
                return
1040
 
            else:
1041
 
                next_key = parents[0]
1042
 
 
1043
903
    def iter_topo_order(self, revisions):
1044
904
        """Iterate through the input revisions in topological order.
1045
905
 
1047
907
        An ancestor may sort after a descendant if the relationship is not
1048
908
        visible in the supplied list of revisions.
1049
909
        """
1050
 
        from bzrlib import tsort
1051
910
        sorter = tsort.TopoSorter(self.get_parent_map(revisions))
1052
911
        return sorter.iter_topo_order()
1053
912
 
1061
920
        return set([candidate_descendant]) == self.heads(
1062
921
            [candidate_ancestor, candidate_descendant])
1063
922
 
1064
 
    def is_between(self, revid, lower_bound_revid, upper_bound_revid):
1065
 
        """Determine whether a revision is between two others.
1066
 
 
1067
 
        returns true if and only if:
1068
 
        lower_bound_revid <= revid <= upper_bound_revid
1069
 
        """
1070
 
        return ((upper_bound_revid is None or
1071
 
                    self.is_ancestor(revid, upper_bound_revid)) and
1072
 
               (lower_bound_revid is None or
1073
 
                    self.is_ancestor(lower_bound_revid, revid)))
1074
 
 
1075
923
    def _search_for_extra_common(self, common, searchers):
1076
924
        """Make sure that unique nodes are genuinely unique.
1077
925
 
1348
1196
        return ('_BreadthFirstSearcher(iterations=%d, %s,'
1349
1197
                ' seen=%r)' % (self._iterations, search, list(self.seen)))
1350
1198
 
1351
 
    def get_state(self):
1352
 
        """Get the current state of this searcher.
1353
 
 
1354
 
        :return: Tuple with started keys, excludes and included keys
 
1199
    def get_result(self):
 
1200
        """Get a SearchResult for the current state of this searcher.
 
1201
        
 
1202
        :return: A SearchResult for this search so far. The SearchResult is
 
1203
            static - the search can be advanced and the search result will not
 
1204
            be invalidated or altered.
1355
1205
        """
1356
1206
        if self._returning == 'next':
1357
1207
            # We have to know the current nodes children to be able to list the
1358
1208
            # exclude keys for them. However, while we could have a second
1359
1209
            # look-ahead result buffer and shuffle things around, this method
1360
1210
            # is typically only called once per search - when memoising the
1361
 
            # results of the search.
 
1211
            # results of the search. 
1362
1212
            found, ghosts, next, parents = self._do_query(self._next_query)
1363
1213
            # pretend we didn't query: perhaps we should tweak _do_query to be
1364
1214
            # entirely stateless?
1368
1218
            next_query = self._next_query
1369
1219
        excludes = self._stopped_keys.union(next_query)
1370
1220
        included_keys = self.seen.difference(excludes)
1371
 
        return self._started_keys, excludes, included_keys
1372
 
 
1373
 
    def _get_result(self):
1374
 
        """Get a SearchResult for the current state of this searcher.
1375
 
 
1376
 
        :return: A SearchResult for this search so far. The SearchResult is
1377
 
            static - the search can be advanced and the search result will not
1378
 
            be invalidated or altered.
1379
 
        """
1380
 
        from bzrlib.vf_search import SearchResult
1381
 
        (started_keys, excludes, included_keys) = self.get_state()
1382
 
        return SearchResult(started_keys, excludes, len(included_keys),
 
1221
        return SearchResult(self._started_keys, excludes, len(included_keys),
1383
1222
            included_keys)
1384
1223
 
1385
1224
    def step(self):
1416
1255
 
1417
1256
    def next_with_ghosts(self):
1418
1257
        """Return the next found ancestors, with ghosts split out.
1419
 
 
 
1258
        
1420
1259
        Ancestors are returned in the order they are seen in a breadth-first
1421
1260
        traversal.  No ancestor will be returned more than once. Ancestors are
1422
1261
        returned only after asking for their parents, which allows us to detect
1462
1301
        parents_of_found = set()
1463
1302
        # revisions may contain nodes that point to other nodes in revisions:
1464
1303
        # we want to filter them out.
1465
 
        seen = self.seen
1466
 
        seen.update(revisions)
 
1304
        self.seen.update(revisions)
1467
1305
        parent_map = self._parents_provider.get_parent_map(revisions)
1468
1306
        found_revisions.update(parent_map)
1469
1307
        for rev_id, parents in parent_map.iteritems():
1470
1308
            if parents is None:
1471
1309
                continue
1472
 
            new_found_parents = [p for p in parents if p not in seen]
 
1310
            new_found_parents = [p for p in parents if p not in self.seen]
1473
1311
            if new_found_parents:
1474
1312
                # Calling set.update() with an empty generator is actually
1475
1313
                # rather expensive.
1482
1320
 
1483
1321
    def find_seen_ancestors(self, revisions):
1484
1322
        """Find ancestors of these revisions that have already been seen.
1485
 
 
 
1323
        
1486
1324
        This function generally makes the assumption that querying for the
1487
1325
        parents of a node that has already been queried is reasonably cheap.
1488
1326
        (eg, not a round trip to a remote host).
1544
1382
                self._current_ghosts.intersection(revisions))
1545
1383
            self._current_present.difference_update(stopped)
1546
1384
            self._current_ghosts.difference_update(stopped)
1547
 
            # stopping 'x' should stop returning parents of 'x', but
 
1385
            # stopping 'x' should stop returning parents of 'x', but 
1548
1386
            # not if 'y' always references those same parents
1549
1387
            stop_rev_references = {}
1550
1388
            for rev in stopped_present:
1566
1404
                    stop_parents.add(rev_id)
1567
1405
            self._next_query.difference_update(stop_parents)
1568
1406
        self._stopped_keys.update(stopped)
1569
 
        self._stopped_keys.update(revisions)
 
1407
        self._stopped_keys.update(revisions - set([revision.NULL_REVISION]))
1570
1408
        return stopped
1571
1409
 
1572
1410
    def start_searching(self, revisions):
1594
1432
            return revs, ghosts
1595
1433
 
1596
1434
 
1597
 
def invert_parent_map(parent_map):
1598
 
    """Given a map from child => parents, create a map of parent=>children"""
1599
 
    child_map = {}
1600
 
    for child, parents in parent_map.iteritems():
1601
 
        for p in parents:
1602
 
            # Any given parent is likely to have only a small handful
1603
 
            # of children, many will have only one. So we avoid mem overhead of
1604
 
            # a list, in exchange for extra copying of tuples
1605
 
            if p not in child_map:
1606
 
                child_map[p] = (child,)
1607
 
            else:
1608
 
                child_map[p] = child_map[p] + (child,)
1609
 
    return child_map
 
1435
class SearchResult(object):
 
1436
    """The result of a breadth first search.
 
1437
 
 
1438
    A SearchResult provides the ability to reconstruct the search or access a
 
1439
    set of the keys the search found.
 
1440
    """
 
1441
 
 
1442
    def __init__(self, start_keys, exclude_keys, key_count, keys):
 
1443
        """Create a SearchResult.
 
1444
 
 
1445
        :param start_keys: The keys the search started at.
 
1446
        :param exclude_keys: The keys the search excludes.
 
1447
        :param key_count: The total number of keys (from start to but not
 
1448
            including exclude).
 
1449
        :param keys: The keys the search found. Note that in future we may get
 
1450
            a SearchResult from a smart server, in which case the keys list is
 
1451
            not necessarily immediately available.
 
1452
        """
 
1453
        self._recipe = (start_keys, exclude_keys, key_count)
 
1454
        self._keys = frozenset(keys)
 
1455
 
 
1456
    def get_recipe(self):
 
1457
        """Return a recipe that can be used to replay this search.
 
1458
        
 
1459
        The recipe allows reconstruction of the same results at a later date
 
1460
        without knowing all the found keys. The essential elements are a list
 
1461
        of keys to start and and to stop at. In order to give reproducible
 
1462
        results when ghosts are encountered by a search they are automatically
 
1463
        added to the exclude list (or else ghost filling may alter the
 
1464
        results).
 
1465
 
 
1466
        :return: A tuple (start_keys_set, exclude_keys_set, revision_count). To
 
1467
            recreate the results of this search, create a breadth first
 
1468
            searcher on the same graph starting at start_keys. Then call next()
 
1469
            (or next_with_ghosts()) repeatedly, and on every result, call
 
1470
            stop_searching_any on any keys from the exclude_keys set. The
 
1471
            revision_count value acts as a trivial cross-check - the found
 
1472
            revisions of the new search should have as many elements as
 
1473
            revision_count. If it does not, then additional revisions have been
 
1474
            ghosted since the search was executed the first time and the second
 
1475
            time.
 
1476
        """
 
1477
        return self._recipe
 
1478
 
 
1479
    def get_keys(self):
 
1480
        """Return the keys found in this search.
 
1481
 
 
1482
        :return: A set of keys.
 
1483
        """
 
1484
        return self._keys
1610
1485
 
1611
1486
 
1612
1487
def collapse_linear_regions(parent_map):
1680
1555
            removed.add(node)
1681
1556
 
1682
1557
    return result
1683
 
 
1684
 
 
1685
 
class GraphThunkIdsToKeys(object):
1686
 
    """Forwards calls about 'ids' to be about keys internally."""
1687
 
 
1688
 
    def __init__(self, graph):
1689
 
        self._graph = graph
1690
 
 
1691
 
    def topo_sort(self):
1692
 
        return [r for (r,) in self._graph.topo_sort()]
1693
 
 
1694
 
    def heads(self, ids):
1695
 
        """See Graph.heads()"""
1696
 
        as_keys = [(i,) for i in ids]
1697
 
        head_keys = self._graph.heads(as_keys)
1698
 
        return set([h[0] for h in head_keys])
1699
 
 
1700
 
    def merge_sort(self, tip_revision):
1701
 
        nodes = self._graph.merge_sort((tip_revision,))
1702
 
        for node in nodes:
1703
 
            node.key = node.key[0]
1704
 
        return nodes
1705
 
 
1706
 
    def add_node(self, revision, parents):
1707
 
        self._graph.add_node((revision,), [(p,) for p in parents])
1708
 
 
1709
 
 
1710
 
_counters = [0,0,0,0,0,0,0]
1711
 
try:
1712
 
    from bzrlib._known_graph_pyx import KnownGraph
1713
 
except ImportError, e:
1714
 
    osutils.failed_to_load_extension(e)
1715
 
    from bzrlib._known_graph_py import KnownGraph