~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/graph.py

  • Committer: Tarmac
  • Author(s): Vincent Ladeuil, Patch Queue Manager, Jelmer Vernooij
  • Date: 2017-01-17 16:20:41 UTC
  • mfrom: (6619.1.2 trunk)
  • Revision ID: tarmac-20170117162041-oo62uk1qsmgc9j31
Merge 2.7 into trunk including fixes for bugs #1622039, #1644003, #1579093 and #1645017. [r=vila]

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2010 Canonical Ltd
 
1
# Copyright (C) 2007-2011 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
 
17
19
import time
18
20
 
19
21
from bzrlib import (
23
25
    revision,
24
26
    trace,
25
27
    )
26
 
from bzrlib.symbol_versioning import deprecated_function, deprecated_in
27
28
 
28
29
STEP_UNIQUE_SEARCHER_EVERY = 5
29
30
 
59
60
    def __repr__(self):
60
61
        return 'DictParentsProvider(%r)' % self.ancestry
61
62
 
 
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
 
62
68
    def get_parent_map(self, keys):
63
69
        """See StackedParentsProvider.get_parent_map"""
64
70
        ancestry = self.ancestry
65
 
        return dict((k, ancestry[k]) for k in keys if k in ancestry)
 
71
        return dict([(k, ancestry[k]) for k in keys if k in ancestry])
66
72
 
67
 
@deprecated_function(deprecated_in((1, 16, 0)))
68
 
def _StackedParentsProvider(*args, **kwargs):
69
 
    return StackedParentsProvider(*args, **kwargs)
70
73
 
71
74
class StackedParentsProvider(object):
72
75
    """A parents provider which stacks (or unions) multiple providers.
73
 
    
 
76
 
74
77
    The providers are queries in the order of the provided parent_providers.
75
78
    """
76
 
    
 
79
 
77
80
    def __init__(self, parent_providers):
78
81
        self._parent_providers = parent_providers
79
82
 
95
98
        """
96
99
        found = {}
97
100
        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
98
117
        for parents_provider in self._parent_providers:
99
118
            new_found = parents_provider.get_parent_map(remaining)
100
119
            found.update(new_found)
154
173
            return None
155
174
        return dict(self._cache)
156
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])
 
186
 
157
187
    def get_parent_map(self, keys):
158
188
        """See StackedParentsProvider.get_parent_map."""
159
189
        cache = self._cache
183
213
            self.missing_keys.add(key)
184
214
 
185
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)
 
231
 
 
232
 
186
233
class Graph(object):
187
234
    """Provide incremental access to revision graphs.
188
235
 
237
284
        common ancestor of all border ancestors, because this shows that it
238
285
        cannot be a descendant of any border ancestor.
239
286
 
240
 
        The scaling of this operation should be proportional to
 
287
        The scaling of this operation should be proportional to:
 
288
 
241
289
        1. The number of uncommon ancestors
242
290
        2. The number of border ancestors
243
291
        3. The length of the shortest path between a border ancestor and an
258
306
        right = searchers[1].seen
259
307
        return (left.difference(right), right.difference(left))
260
308
 
 
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
 
261
343
    def find_distance_to_null(self, target_revision_id, known_revision_ids):
262
344
        """Find the left-hand distance to the NULL_REVISION.
263
345
 
341
423
 
342
424
        :param unique_revision: The revision_id whose ancestry we are
343
425
            interested in.
344
 
            XXX: Would this API be better if we allowed multiple revisions on
345
 
                 to be searched here?
 
426
            (XXX: Would this API be better if we allowed multiple revisions on
 
427
            to be searched here?)
346
428
        :param common_revisions: Revision_ids of ancestries to exclude.
347
429
        :return: A set of revisions in the ancestry of unique_revision
348
430
        """
862
944
                stop.add(parent_id)
863
945
        return found
864
946
 
 
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
 
865
967
    def find_unique_lca(self, left_revision, right_revision,
866
968
                        count_steps=False):
867
969
        """Find a unique LCA.
919
1021
                yield (ghost, None)
920
1022
            pending = next_pending
921
1023
 
 
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
 
922
1043
    def iter_topo_order(self, revisions):
923
1044
        """Iterate through the input revisions in topological order.
924
1045
 
1227
1348
        return ('_BreadthFirstSearcher(iterations=%d, %s,'
1228
1349
                ' seen=%r)' % (self._iterations, search, list(self.seen)))
1229
1350
 
1230
 
    def get_result(self):
1231
 
        """Get a SearchResult for the current state of this searcher.
 
1351
    def get_state(self):
 
1352
        """Get the current state of this searcher.
1232
1353
 
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.
 
1354
        :return: Tuple with started keys, excludes and included keys
1236
1355
        """
1237
1356
        if self._returning == 'next':
1238
1357
            # We have to know the current nodes children to be able to list the
1249
1368
            next_query = self._next_query
1250
1369
        excludes = self._stopped_keys.union(next_query)
1251
1370
        included_keys = self.seen.difference(excludes)
1252
 
        return SearchResult(self._started_keys, excludes, len(included_keys),
 
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),
1253
1383
            included_keys)
1254
1384
 
1255
1385
    def step(self):
1332
1462
        parents_of_found = set()
1333
1463
        # revisions may contain nodes that point to other nodes in revisions:
1334
1464
        # we want to filter them out.
1335
 
        self.seen.update(revisions)
 
1465
        seen = self.seen
 
1466
        seen.update(revisions)
1336
1467
        parent_map = self._parents_provider.get_parent_map(revisions)
1337
1468
        found_revisions.update(parent_map)
1338
1469
        for rev_id, parents in parent_map.iteritems():
1339
1470
            if parents is None:
1340
1471
                continue
1341
 
            new_found_parents = [p for p in parents if p not in self.seen]
 
1472
            new_found_parents = [p for p in parents if p not in seen]
1342
1473
            if new_found_parents:
1343
1474
                # Calling set.update() with an empty generator is actually
1344
1475
                # rather expensive.
1463
1594
            return revs, ghosts
1464
1595
 
1465
1596
 
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)
 
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
1607
1610
 
1608
1611
 
1609
1612
def collapse_linear_regions(parent_map):
1695
1698
        return set([h[0] for h in head_keys])
1696
1699
 
1697
1700
    def merge_sort(self, tip_revision):
1698
 
        return self._graph.merge_sort((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])
1699
1708
 
1700
1709
 
1701
1710
_counters = [0,0,0,0,0,0,0]