~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/graph.py

  • Committer: Joe Julian
  • Date: 2010-01-10 02:25:31 UTC
  • mto: (4634.119.7 2.0)
  • mto: This revision was merged to the branch mainline in revision 4959.
  • Revision ID: joe@julianfamily.org-20100110022531-wqk61rsagz8xsiga
Added MANIFEST.in to allow bdist_rpm to have all the required include files and tools. bdist_rpm will still fail to build correctly on some distributions due to a disttools bug http://bugs.python.org/issue644744

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2011 Canonical Ltd
 
1
# Copyright (C) 2007, 2008, 2009 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
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
from __future__ import absolute_import
18
 
 
19
17
import time
20
18
 
21
19
from bzrlib import (
22
20
    debug,
23
21
    errors,
24
 
    osutils,
25
22
    revision,
26
23
    trace,
27
24
    )
 
25
from bzrlib.symbol_versioning import deprecated_function, deprecated_in
28
26
 
29
27
STEP_UNIQUE_SEARCHER_EVERY = 5
30
28
 
60
58
    def __repr__(self):
61
59
        return 'DictParentsProvider(%r)' % self.ancestry
62
60
 
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
61
    def get_parent_map(self, keys):
69
62
        """See StackedParentsProvider.get_parent_map"""
70
63
        ancestry = self.ancestry
71
 
        return dict([(k, ancestry[k]) for k in keys if k in ancestry])
 
64
        return dict((k, ancestry[k]) for k in keys if k in ancestry)
72
65
 
 
66
@deprecated_function(deprecated_in((1, 16, 0)))
 
67
def _StackedParentsProvider(*args, **kwargs):
 
68
    return StackedParentsProvider(*args, **kwargs)
73
69
 
74
70
class StackedParentsProvider(object):
75
71
    """A parents provider which stacks (or unions) multiple providers.
76
 
 
 
72
    
77
73
    The providers are queries in the order of the provided parent_providers.
78
74
    """
79
 
 
 
75
    
80
76
    def __init__(self, parent_providers):
81
77
        self._parent_providers = parent_providers
82
78
 
98
94
        """
99
95
        found = {}
100
96
        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
97
        for parents_provider in self._parent_providers:
118
98
            new_found = parents_provider.get_parent_map(remaining)
119
99
            found.update(new_found)
173
153
            return None
174
154
        return dict(self._cache)
175
155
 
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])
186
 
 
187
156
    def get_parent_map(self, keys):
188
157
        """See StackedParentsProvider.get_parent_map."""
189
158
        cache = self._cache
213
182
            self.missing_keys.add(key)
214
183
 
215
184
 
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)
231
 
 
232
 
 
233
185
class Graph(object):
234
186
    """Provide incremental access to revision graphs.
235
187
 
284
236
        common ancestor of all border ancestors, because this shows that it
285
237
        cannot be a descendant of any border ancestor.
286
238
 
287
 
        The scaling of this operation should be proportional to:
288
 
 
 
239
        The scaling of this operation should be proportional to
289
240
        1. The number of uncommon ancestors
290
241
        2. The number of border ancestors
291
242
        3. The length of the shortest path between a border ancestor and an
306
257
        right = searchers[1].seen
307
258
        return (left.difference(right), right.difference(left))
308
259
 
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
260
    def find_distance_to_null(self, target_revision_id, known_revision_ids):
344
261
        """Find the left-hand distance to the NULL_REVISION.
345
262
 
423
340
 
424
341
        :param unique_revision: The revision_id whose ancestry we are
425
342
            interested in.
426
 
            (XXX: Would this API be better if we allowed multiple revisions on
427
 
            to be searched here?)
 
343
            XXX: Would this API be better if we allowed multiple revisions on
 
344
                 to be searched here?
428
345
        :param common_revisions: Revision_ids of ancestries to exclude.
429
346
        :return: A set of revisions in the ancestry of unique_revision
430
347
        """
944
861
                stop.add(parent_id)
945
862
        return found
946
863
 
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
864
    def find_unique_lca(self, left_revision, right_revision,
968
865
                        count_steps=False):
969
866
        """Find a unique LCA.
1021
918
                yield (ghost, None)
1022
919
            pending = next_pending
1023
920
 
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
921
    def iter_topo_order(self, revisions):
1044
922
        """Iterate through the input revisions in topological order.
1045
923
 
1348
1226
        return ('_BreadthFirstSearcher(iterations=%d, %s,'
1349
1227
                ' seen=%r)' % (self._iterations, search, list(self.seen)))
1350
1228
 
1351
 
    def get_state(self):
1352
 
        """Get the current state of this searcher.
 
1229
    def get_result(self):
 
1230
        """Get a SearchResult for the current state of this searcher.
1353
1231
 
1354
 
        :return: Tuple with started keys, excludes and included keys
 
1232
        :return: A SearchResult for this search so far. The SearchResult is
 
1233
            static - the search can be advanced and the search result will not
 
1234
            be invalidated or altered.
1355
1235
        """
1356
1236
        if self._returning == 'next':
1357
1237
            # We have to know the current nodes children to be able to list the
1368
1248
            next_query = self._next_query
1369
1249
        excludes = self._stopped_keys.union(next_query)
1370
1250
        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),
 
1251
        return SearchResult(self._started_keys, excludes, len(included_keys),
1383
1252
            included_keys)
1384
1253
 
1385
1254
    def step(self):
1462
1331
        parents_of_found = set()
1463
1332
        # revisions may contain nodes that point to other nodes in revisions:
1464
1333
        # we want to filter them out.
1465
 
        seen = self.seen
1466
 
        seen.update(revisions)
 
1334
        self.seen.update(revisions)
1467
1335
        parent_map = self._parents_provider.get_parent_map(revisions)
1468
1336
        found_revisions.update(parent_map)
1469
1337
        for rev_id, parents in parent_map.iteritems():
1470
1338
            if parents is None:
1471
1339
                continue
1472
 
            new_found_parents = [p for p in parents if p not in seen]
 
1340
            new_found_parents = [p for p in parents if p not in self.seen]
1473
1341
            if new_found_parents:
1474
1342
                # Calling set.update() with an empty generator is actually
1475
1343
                # rather expensive.
1594
1462
            return revs, ghosts
1595
1463
 
1596
1464
 
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
 
1465
class SearchResult(object):
 
1466
    """The result of a breadth first search.
 
1467
 
 
1468
    A SearchResult provides the ability to reconstruct the search or access a
 
1469
    set of the keys the search found.
 
1470
    """
 
1471
 
 
1472
    def __init__(self, start_keys, exclude_keys, key_count, keys):
 
1473
        """Create a SearchResult.
 
1474
 
 
1475
        :param start_keys: The keys the search started at.
 
1476
        :param exclude_keys: The keys the search excludes.
 
1477
        :param key_count: The total number of keys (from start to but not
 
1478
            including exclude).
 
1479
        :param keys: The keys the search found. Note that in future we may get
 
1480
            a SearchResult from a smart server, in which case the keys list is
 
1481
            not necessarily immediately available.
 
1482
        """
 
1483
        self._recipe = ('search', start_keys, exclude_keys, key_count)
 
1484
        self._keys = frozenset(keys)
 
1485
 
 
1486
    def get_recipe(self):
 
1487
        """Return a recipe that can be used to replay this search.
 
1488
 
 
1489
        The recipe allows reconstruction of the same results at a later date
 
1490
        without knowing all the found keys. The essential elements are a list
 
1491
        of keys to start and to stop at. In order to give reproducible
 
1492
        results when ghosts are encountered by a search they are automatically
 
1493
        added to the exclude list (or else ghost filling may alter the
 
1494
        results).
 
1495
 
 
1496
        :return: A tuple ('search', start_keys_set, exclude_keys_set,
 
1497
            revision_count). To recreate the results of this search, create a
 
1498
            breadth first searcher on the same graph starting at start_keys.
 
1499
            Then call next() (or next_with_ghosts()) repeatedly, and on every
 
1500
            result, call stop_searching_any on any keys from the exclude_keys
 
1501
            set. The revision_count value acts as a trivial cross-check - the
 
1502
            found revisions of the new search should have as many elements as
 
1503
            revision_count. If it does not, then additional revisions have been
 
1504
            ghosted since the search was executed the first time and the second
 
1505
            time.
 
1506
        """
 
1507
        return self._recipe
 
1508
 
 
1509
    def get_keys(self):
 
1510
        """Return the keys found in this search.
 
1511
 
 
1512
        :return: A set of keys.
 
1513
        """
 
1514
        return self._keys
 
1515
 
 
1516
    def is_empty(self):
 
1517
        """Return false if the search lists 1 or more revisions."""
 
1518
        return self._recipe[3] == 0
 
1519
 
 
1520
    def refine(self, seen, referenced):
 
1521
        """Create a new search by refining this search.
 
1522
 
 
1523
        :param seen: Revisions that have been satisfied.
 
1524
        :param referenced: Revision references observed while satisfying some
 
1525
            of this search.
 
1526
        """
 
1527
        start = self._recipe[1]
 
1528
        exclude = self._recipe[2]
 
1529
        count = self._recipe[3]
 
1530
        keys = self.get_keys()
 
1531
        # New heads = referenced + old heads - seen things - exclude
 
1532
        pending_refs = set(referenced)
 
1533
        pending_refs.update(start)
 
1534
        pending_refs.difference_update(seen)
 
1535
        pending_refs.difference_update(exclude)
 
1536
        # New exclude = old exclude + satisfied heads
 
1537
        seen_heads = start.intersection(seen)
 
1538
        exclude.update(seen_heads)
 
1539
        # keys gets seen removed
 
1540
        keys = keys - seen
 
1541
        # length is reduced by len(seen)
 
1542
        count -= len(seen)
 
1543
        return SearchResult(pending_refs, exclude, count, keys)
 
1544
 
 
1545
 
 
1546
class PendingAncestryResult(object):
 
1547
    """A search result that will reconstruct the ancestry for some graph heads.
 
1548
 
 
1549
    Unlike SearchResult, this doesn't hold the complete search result in
 
1550
    memory, it just holds a description of how to generate it.
 
1551
    """
 
1552
 
 
1553
    def __init__(self, heads, repo):
 
1554
        """Constructor.
 
1555
 
 
1556
        :param heads: an iterable of graph heads.
 
1557
        :param repo: a repository to use to generate the ancestry for the given
 
1558
            heads.
 
1559
        """
 
1560
        self.heads = frozenset(heads)
 
1561
        self.repo = repo
 
1562
 
 
1563
    def get_recipe(self):
 
1564
        """Return a recipe that can be used to replay this search.
 
1565
 
 
1566
        The recipe allows reconstruction of the same results at a later date.
 
1567
 
 
1568
        :seealso SearchResult.get_recipe:
 
1569
 
 
1570
        :return: A tuple ('proxy-search', start_keys_set, set(), -1)
 
1571
            To recreate this result, create a PendingAncestryResult with the
 
1572
            start_keys_set.
 
1573
        """
 
1574
        return ('proxy-search', self.heads, set(), -1)
 
1575
 
 
1576
    def get_keys(self):
 
1577
        """See SearchResult.get_keys.
 
1578
 
 
1579
        Returns all the keys for the ancestry of the heads, excluding
 
1580
        NULL_REVISION.
 
1581
        """
 
1582
        return self._get_keys(self.repo.get_graph())
 
1583
 
 
1584
    def _get_keys(self, graph):
 
1585
        NULL_REVISION = revision.NULL_REVISION
 
1586
        keys = [key for (key, parents) in graph.iter_ancestry(self.heads)
 
1587
                if key != NULL_REVISION and parents is not None]
 
1588
        return keys
 
1589
 
 
1590
    def is_empty(self):
 
1591
        """Return false if the search lists 1 or more revisions."""
 
1592
        if revision.NULL_REVISION in self.heads:
 
1593
            return len(self.heads) == 1
 
1594
        else:
 
1595
            return len(self.heads) == 0
 
1596
 
 
1597
    def refine(self, seen, referenced):
 
1598
        """Create a new search by refining this search.
 
1599
 
 
1600
        :param seen: Revisions that have been satisfied.
 
1601
        :param referenced: Revision references observed while satisfying some
 
1602
            of this search.
 
1603
        """
 
1604
        referenced = self.heads.union(referenced)
 
1605
        return PendingAncestryResult(referenced - seen, self.repo)
1610
1606
 
1611
1607
 
1612
1608
def collapse_linear_regions(parent_map):
1682
1678
    return result
1683
1679
 
1684
1680
 
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
1681
_counters = [0,0,0,0,0,0,0]
1711
1682
try:
1712
1683
    from bzrlib._known_graph_pyx import KnownGraph
1713
 
except ImportError, e:
1714
 
    osutils.failed_to_load_extension(e)
 
1684
except ImportError:
1715
1685
    from bzrlib._known_graph_py import KnownGraph