/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/graph.py

  • Committer: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2011 Canonical Ltd
 
1
# Copyright (C) 2007-2010 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
23
23
    revision,
24
24
    trace,
25
25
    )
 
26
from bzrlib.symbol_versioning import deprecated_function, deprecated_in
26
27
 
27
28
STEP_UNIQUE_SEARCHER_EVERY = 5
28
29
 
58
59
    def __repr__(self):
59
60
        return 'DictParentsProvider(%r)' % self.ancestry
60
61
 
61
 
    # Note: DictParentsProvider does not implement get_cached_parent_map
62
 
    #       Arguably, the data is clearly cached in memory. However, this class
63
 
    #       is mostly used for testing, and it keeps the tests clean to not
64
 
    #       change it.
65
 
 
66
62
    def get_parent_map(self, keys):
67
63
        """See StackedParentsProvider.get_parent_map"""
68
64
        ancestry = self.ancestry
69
 
        return dict([(k, ancestry[k]) for k in keys if k in ancestry])
 
65
        return dict((k, ancestry[k]) for k in keys if k in ancestry)
70
66
 
 
67
@deprecated_function(deprecated_in((1, 16, 0)))
 
68
def _StackedParentsProvider(*args, **kwargs):
 
69
    return StackedParentsProvider(*args, **kwargs)
71
70
 
72
71
class StackedParentsProvider(object):
73
72
    """A parents provider which stacks (or unions) multiple providers.
74
 
 
 
73
    
75
74
    The providers are queries in the order of the provided parent_providers.
76
75
    """
77
 
 
 
76
    
78
77
    def __init__(self, parent_providers):
79
78
        self._parent_providers = parent_providers
80
79
 
96
95
        """
97
96
        found = {}
98
97
        remaining = set(keys)
99
 
        # This adds getattr() overhead to each get_parent_map call. However,
100
 
        # this is StackedParentsProvider, which means we're dealing with I/O
101
 
        # (either local indexes, or remote RPCs), so CPU overhead should be
102
 
        # minimal.
103
 
        for parents_provider in self._parent_providers:
104
 
            get_cached = getattr(parents_provider, 'get_cached_parent_map',
105
 
                                 None)
106
 
            if get_cached is None:
107
 
                continue
108
 
            new_found = get_cached(remaining)
109
 
            found.update(new_found)
110
 
            remaining.difference_update(new_found)
111
 
            if not remaining:
112
 
                break
113
 
        if not remaining:
114
 
            return found
115
98
        for parents_provider in self._parent_providers:
116
99
            new_found = parents_provider.get_parent_map(remaining)
117
100
            found.update(new_found)
171
154
            return None
172
155
        return dict(self._cache)
173
156
 
174
 
    def get_cached_parent_map(self, keys):
175
 
        """Return items from the cache.
176
 
 
177
 
        This returns the same info as get_parent_map, but explicitly does not
178
 
        invoke the supplied ParentsProvider to search for uncached values.
179
 
        """
180
 
        cache = self._cache
181
 
        if cache is None:
182
 
            return {}
183
 
        return dict([(key, cache[key]) for key in keys if key in cache])
184
 
 
185
157
    def get_parent_map(self, keys):
186
158
        """See StackedParentsProvider.get_parent_map."""
187
159
        cache = self._cache
211
183
            self.missing_keys.add(key)
212
184
 
213
185
 
214
 
class CallableToParentsProviderAdapter(object):
215
 
    """A parents provider that adapts any callable to the parents provider API.
216
 
 
217
 
    i.e. it accepts calls to self.get_parent_map and relays them to the
218
 
    callable it was constructed with.
219
 
    """
220
 
 
221
 
    def __init__(self, a_callable):
222
 
        self.callable = a_callable
223
 
 
224
 
    def __repr__(self):
225
 
        return "%s(%r)" % (self.__class__.__name__, self.callable)
226
 
 
227
 
    def get_parent_map(self, keys):
228
 
        return self.callable(keys)
229
 
 
230
 
 
231
186
class Graph(object):
232
187
    """Provide incremental access to revision graphs.
233
188
 
282
237
        common ancestor of all border ancestors, because this shows that it
283
238
        cannot be a descendant of any border ancestor.
284
239
 
285
 
        The scaling of this operation should be proportional to:
286
 
 
 
240
        The scaling of this operation should be proportional to
287
241
        1. The number of uncommon ancestors
288
242
        2. The number of border ancestors
289
243
        3. The length of the shortest path between a border ancestor and an
304
258
        right = searchers[1].seen
305
259
        return (left.difference(right), right.difference(left))
306
260
 
307
 
    def find_descendants(self, old_key, new_key):
308
 
        """Find descendants of old_key that are ancestors of new_key."""
309
 
        child_map = self.get_child_map(self._find_descendant_ancestors(
310
 
            old_key, new_key))
311
 
        graph = Graph(DictParentsProvider(child_map))
312
 
        searcher = graph._make_breadth_first_searcher([old_key])
313
 
        list(searcher)
314
 
        return searcher.seen
315
 
 
316
 
    def _find_descendant_ancestors(self, old_key, new_key):
317
 
        """Find ancestors of new_key that may be descendants of old_key."""
318
 
        stop = self._make_breadth_first_searcher([old_key])
319
 
        descendants = self._make_breadth_first_searcher([new_key])
320
 
        for revisions in descendants:
321
 
            old_stop = stop.seen.intersection(revisions)
322
 
            descendants.stop_searching_any(old_stop)
323
 
            seen_stop = descendants.find_seen_ancestors(stop.step())
324
 
            descendants.stop_searching_any(seen_stop)
325
 
        return descendants.seen.difference(stop.seen)
326
 
 
327
 
    def get_child_map(self, keys):
328
 
        """Get a mapping from parents to children of the specified keys.
329
 
 
330
 
        This is simply the inversion of get_parent_map.  Only supplied keys
331
 
        will be discovered as children.
332
 
        :return: a dict of key:child_list for keys.
333
 
        """
334
 
        parent_map = self._parents_provider.get_parent_map(keys)
335
 
        parent_child = {}
336
 
        for child, parents in sorted(parent_map.items()):
337
 
            for parent in parents:
338
 
                parent_child.setdefault(parent, []).append(child)
339
 
        return parent_child
340
 
 
341
261
    def find_distance_to_null(self, target_revision_id, known_revision_ids):
342
262
        """Find the left-hand distance to the NULL_REVISION.
343
263
 
421
341
 
422
342
        :param unique_revision: The revision_id whose ancestry we are
423
343
            interested in.
424
 
            (XXX: Would this API be better if we allowed multiple revisions on
425
 
            to be searched here?)
 
344
            XXX: Would this API be better if we allowed multiple revisions on
 
345
                 to be searched here?
426
346
        :param common_revisions: Revision_ids of ancestries to exclude.
427
347
        :return: A set of revisions in the ancestry of unique_revision
428
348
        """
942
862
                stop.add(parent_id)
943
863
        return found
944
864
 
945
 
    def find_lefthand_merger(self, merged_key, tip_key):
946
 
        """Find the first lefthand ancestor of tip_key that merged merged_key.
947
 
 
948
 
        We do this by first finding the descendants of merged_key, then
949
 
        walking through the lefthand ancestry of tip_key until we find a key
950
 
        that doesn't descend from merged_key.  Its child is the key that
951
 
        merged merged_key.
952
 
 
953
 
        :return: The first lefthand ancestor of tip_key to merge merged_key.
954
 
            merged_key if it is a lefthand ancestor of tip_key.
955
 
            None if no ancestor of tip_key merged merged_key.
956
 
        """
957
 
        descendants = self.find_descendants(merged_key, tip_key)
958
 
        candidate_iterator = self.iter_lefthand_ancestry(tip_key)
959
 
        last_candidate = None
960
 
        for candidate in candidate_iterator:
961
 
            if candidate not in descendants:
962
 
                return last_candidate
963
 
            last_candidate = candidate
964
 
 
965
865
    def find_unique_lca(self, left_revision, right_revision,
966
866
                        count_steps=False):
967
867
        """Find a unique LCA.
1019
919
                yield (ghost, None)
1020
920
            pending = next_pending
1021
921
 
1022
 
    def iter_lefthand_ancestry(self, start_key, stop_keys=None):
1023
 
        if stop_keys is None:
1024
 
            stop_keys = ()
1025
 
        next_key = start_key
1026
 
        def get_parents(key):
1027
 
            try:
1028
 
                return self._parents_provider.get_parent_map([key])[key]
1029
 
            except KeyError:
1030
 
                raise errors.RevisionNotPresent(next_key, self)
1031
 
        while True:
1032
 
            if next_key in stop_keys:
1033
 
                return
1034
 
            parents = get_parents(next_key)
1035
 
            yield next_key
1036
 
            if len(parents) == 0:
1037
 
                return
1038
 
            else:
1039
 
                next_key = parents[0]
1040
 
 
1041
922
    def iter_topo_order(self, revisions):
1042
923
        """Iterate through the input revisions in topological order.
1043
924
 
1346
1227
        return ('_BreadthFirstSearcher(iterations=%d, %s,'
1347
1228
                ' seen=%r)' % (self._iterations, search, list(self.seen)))
1348
1229
 
1349
 
    def get_state(self):
1350
 
        """Get the current state of this searcher.
 
1230
    def get_result(self):
 
1231
        """Get a SearchResult for the current state of this searcher.
1351
1232
 
1352
 
        :return: Tuple with started keys, excludes and included keys
 
1233
        :return: A SearchResult for this search so far. The SearchResult is
 
1234
            static - the search can be advanced and the search result will not
 
1235
            be invalidated or altered.
1353
1236
        """
1354
1237
        if self._returning == 'next':
1355
1238
            # We have to know the current nodes children to be able to list the
1366
1249
            next_query = self._next_query
1367
1250
        excludes = self._stopped_keys.union(next_query)
1368
1251
        included_keys = self.seen.difference(excludes)
1369
 
        return self._started_keys, excludes, included_keys
1370
 
 
1371
 
    def _get_result(self):
1372
 
        """Get a SearchResult for the current state of this searcher.
1373
 
 
1374
 
        :return: A SearchResult for this search so far. The SearchResult is
1375
 
            static - the search can be advanced and the search result will not
1376
 
            be invalidated or altered.
1377
 
        """
1378
 
        from bzrlib.vf_search import SearchResult
1379
 
        (started_keys, excludes, included_keys) = self.get_state()
1380
 
        return SearchResult(started_keys, excludes, len(included_keys),
 
1252
        return SearchResult(self._started_keys, excludes, len(included_keys),
1381
1253
            included_keys)
1382
1254
 
1383
1255
    def step(self):
1460
1332
        parents_of_found = set()
1461
1333
        # revisions may contain nodes that point to other nodes in revisions:
1462
1334
        # we want to filter them out.
1463
 
        seen = self.seen
1464
 
        seen.update(revisions)
 
1335
        self.seen.update(revisions)
1465
1336
        parent_map = self._parents_provider.get_parent_map(revisions)
1466
1337
        found_revisions.update(parent_map)
1467
1338
        for rev_id, parents in parent_map.iteritems():
1468
1339
            if parents is None:
1469
1340
                continue
1470
 
            new_found_parents = [p for p in parents if p not in seen]
 
1341
            new_found_parents = [p for p in parents if p not in self.seen]
1471
1342
            if new_found_parents:
1472
1343
                # Calling set.update() with an empty generator is actually
1473
1344
                # rather expensive.
1592
1463
            return revs, ghosts
1593
1464
 
1594
1465
 
1595
 
def invert_parent_map(parent_map):
1596
 
    """Given a map from child => parents, create a map of parent=>children"""
1597
 
    child_map = {}
1598
 
    for child, parents in parent_map.iteritems():
1599
 
        for p in parents:
1600
 
            # Any given parent is likely to have only a small handful
1601
 
            # of children, many will have only one. So we avoid mem overhead of
1602
 
            # a list, in exchange for extra copying of tuples
1603
 
            if p not in child_map:
1604
 
                child_map[p] = (child,)
1605
 
            else:
1606
 
                child_map[p] = child_map[p] + (child,)
1607
 
    return child_map
 
1466
class SearchResult(object):
 
1467
    """The result of a breadth first search.
 
1468
 
 
1469
    A SearchResult provides the ability to reconstruct the search or access a
 
1470
    set of the keys the search found.
 
1471
    """
 
1472
 
 
1473
    def __init__(self, start_keys, exclude_keys, key_count, keys):
 
1474
        """Create a SearchResult.
 
1475
 
 
1476
        :param start_keys: The keys the search started at.
 
1477
        :param exclude_keys: The keys the search excludes.
 
1478
        :param key_count: The total number of keys (from start to but not
 
1479
            including exclude).
 
1480
        :param keys: The keys the search found. Note that in future we may get
 
1481
            a SearchResult from a smart server, in which case the keys list is
 
1482
            not necessarily immediately available.
 
1483
        """
 
1484
        self._recipe = ('search', start_keys, exclude_keys, key_count)
 
1485
        self._keys = frozenset(keys)
 
1486
 
 
1487
    def get_recipe(self):
 
1488
        """Return a recipe that can be used to replay this search.
 
1489
 
 
1490
        The recipe allows reconstruction of the same results at a later date
 
1491
        without knowing all the found keys. The essential elements are a list
 
1492
        of keys to start and to stop at. In order to give reproducible
 
1493
        results when ghosts are encountered by a search they are automatically
 
1494
        added to the exclude list (or else ghost filling may alter the
 
1495
        results).
 
1496
 
 
1497
        :return: A tuple ('search', start_keys_set, exclude_keys_set,
 
1498
            revision_count). To recreate the results of this search, create a
 
1499
            breadth first searcher on the same graph starting at start_keys.
 
1500
            Then call next() (or next_with_ghosts()) repeatedly, and on every
 
1501
            result, call stop_searching_any on any keys from the exclude_keys
 
1502
            set. The revision_count value acts as a trivial cross-check - the
 
1503
            found revisions of the new search should have as many elements as
 
1504
            revision_count. If it does not, then additional revisions have been
 
1505
            ghosted since the search was executed the first time and the second
 
1506
            time.
 
1507
        """
 
1508
        return self._recipe
 
1509
 
 
1510
    def get_keys(self):
 
1511
        """Return the keys found in this search.
 
1512
 
 
1513
        :return: A set of keys.
 
1514
        """
 
1515
        return self._keys
 
1516
 
 
1517
    def is_empty(self):
 
1518
        """Return false if the search lists 1 or more revisions."""
 
1519
        return self._recipe[3] == 0
 
1520
 
 
1521
    def refine(self, seen, referenced):
 
1522
        """Create a new search by refining this search.
 
1523
 
 
1524
        :param seen: Revisions that have been satisfied.
 
1525
        :param referenced: Revision references observed while satisfying some
 
1526
            of this search.
 
1527
        """
 
1528
        start = self._recipe[1]
 
1529
        exclude = self._recipe[2]
 
1530
        count = self._recipe[3]
 
1531
        keys = self.get_keys()
 
1532
        # New heads = referenced + old heads - seen things - exclude
 
1533
        pending_refs = set(referenced)
 
1534
        pending_refs.update(start)
 
1535
        pending_refs.difference_update(seen)
 
1536
        pending_refs.difference_update(exclude)
 
1537
        # New exclude = old exclude + satisfied heads
 
1538
        seen_heads = start.intersection(seen)
 
1539
        exclude.update(seen_heads)
 
1540
        # keys gets seen removed
 
1541
        keys = keys - seen
 
1542
        # length is reduced by len(seen)
 
1543
        count -= len(seen)
 
1544
        return SearchResult(pending_refs, exclude, count, keys)
 
1545
 
 
1546
 
 
1547
class PendingAncestryResult(object):
 
1548
    """A search result that will reconstruct the ancestry for some graph heads.
 
1549
 
 
1550
    Unlike SearchResult, this doesn't hold the complete search result in
 
1551
    memory, it just holds a description of how to generate it.
 
1552
    """
 
1553
 
 
1554
    def __init__(self, heads, repo):
 
1555
        """Constructor.
 
1556
 
 
1557
        :param heads: an iterable of graph heads.
 
1558
        :param repo: a repository to use to generate the ancestry for the given
 
1559
            heads.
 
1560
        """
 
1561
        self.heads = frozenset(heads)
 
1562
        self.repo = repo
 
1563
 
 
1564
    def get_recipe(self):
 
1565
        """Return a recipe that can be used to replay this search.
 
1566
 
 
1567
        The recipe allows reconstruction of the same results at a later date.
 
1568
 
 
1569
        :seealso SearchResult.get_recipe:
 
1570
 
 
1571
        :return: A tuple ('proxy-search', start_keys_set, set(), -1)
 
1572
            To recreate this result, create a PendingAncestryResult with the
 
1573
            start_keys_set.
 
1574
        """
 
1575
        return ('proxy-search', self.heads, set(), -1)
 
1576
 
 
1577
    def get_keys(self):
 
1578
        """See SearchResult.get_keys.
 
1579
 
 
1580
        Returns all the keys for the ancestry of the heads, excluding
 
1581
        NULL_REVISION.
 
1582
        """
 
1583
        return self._get_keys(self.repo.get_graph())
 
1584
 
 
1585
    def _get_keys(self, graph):
 
1586
        NULL_REVISION = revision.NULL_REVISION
 
1587
        keys = [key for (key, parents) in graph.iter_ancestry(self.heads)
 
1588
                if key != NULL_REVISION and parents is not None]
 
1589
        return keys
 
1590
 
 
1591
    def is_empty(self):
 
1592
        """Return false if the search lists 1 or more revisions."""
 
1593
        if revision.NULL_REVISION in self.heads:
 
1594
            return len(self.heads) == 1
 
1595
        else:
 
1596
            return len(self.heads) == 0
 
1597
 
 
1598
    def refine(self, seen, referenced):
 
1599
        """Create a new search by refining this search.
 
1600
 
 
1601
        :param seen: Revisions that have been satisfied.
 
1602
        :param referenced: Revision references observed while satisfying some
 
1603
            of this search.
 
1604
        """
 
1605
        referenced = self.heads.union(referenced)
 
1606
        return PendingAncestryResult(referenced - seen, self.repo)
1608
1607
 
1609
1608
 
1610
1609
def collapse_linear_regions(parent_map):
1696
1695
        return set([h[0] for h in head_keys])
1697
1696
 
1698
1697
    def merge_sort(self, tip_revision):
1699
 
        nodes = self._graph.merge_sort((tip_revision,))
1700
 
        for node in nodes:
1701
 
            node.key = node.key[0]
1702
 
        return nodes
1703
 
 
1704
 
    def add_node(self, revision, parents):
1705
 
        self._graph.add_node((revision,), [(p,) for p in parents])
 
1698
        return self._graph.merge_sort((tip_revision,))
1706
1699
 
1707
1700
 
1708
1701
_counters = [0,0,0,0,0,0,0]