~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/graph.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-12-12 01:24:50 UTC
  • mfrom: (3882.4.2 doc-hacking)
  • Revision ID: pqm@pqm.ubuntu.com-20081212012450-3gw576prpztxziib
(mbp) Developer documentation about when to add new exception classes

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007-2010 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
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
import time
18
18
 
19
19
from bzrlib import (
20
20
    debug,
21
21
    errors,
22
 
    osutils,
23
22
    revision,
 
23
    symbol_versioning,
24
24
    trace,
 
25
    tsort,
25
26
    )
26
 
from bzrlib.symbol_versioning import deprecated_function, deprecated_in
 
27
from bzrlib.deprecated_graph import (node_distances, select_farthest)
27
28
 
28
29
STEP_UNIQUE_SEARCHER_EVERY = 5
29
30
 
60
61
        return 'DictParentsProvider(%r)' % self.ancestry
61
62
 
62
63
    def get_parent_map(self, keys):
63
 
        """See StackedParentsProvider.get_parent_map"""
 
64
        """See _StackedParentsProvider.get_parent_map"""
64
65
        ancestry = self.ancestry
65
66
        return dict((k, ancestry[k]) for k in keys if k in ancestry)
66
67
 
67
 
@deprecated_function(deprecated_in((1, 16, 0)))
68
 
def _StackedParentsProvider(*args, **kwargs):
69
 
    return StackedParentsProvider(*args, **kwargs)
70
 
 
71
 
class StackedParentsProvider(object):
72
 
    """A parents provider which stacks (or unions) multiple providers.
73
 
    
74
 
    The providers are queries in the order of the provided parent_providers.
75
 
    """
76
 
    
 
68
 
 
69
class _StackedParentsProvider(object):
 
70
 
77
71
    def __init__(self, parent_providers):
78
72
        self._parent_providers = parent_providers
79
73
 
80
74
    def __repr__(self):
81
 
        return "%s(%r)" % (self.__class__.__name__, self._parent_providers)
 
75
        return "_StackedParentsProvider(%r)" % self._parent_providers
82
76
 
83
77
    def get_parent_map(self, keys):
84
78
        """Get a mapping of keys => parents
115
109
 
116
110
    The cache is enabled by default, but may be disabled and re-enabled.
117
111
    """
118
 
    def __init__(self, parent_provider=None, get_parent_map=None):
 
112
    def __init__(self, parent_provider=None, get_parent_map=None, debug=False):
119
113
        """Constructor.
120
114
 
121
115
        :param parent_provider: The ParentProvider to use.  It or
122
116
            get_parent_map must be supplied.
123
117
        :param get_parent_map: The get_parent_map callback to use.  It or
124
118
            parent_provider must be supplied.
 
119
        :param debug: If true, mutter debugging messages.
125
120
        """
126
121
        self._real_provider = parent_provider
127
122
        if get_parent_map is None:
128
123
            self._get_parent_map = self._real_provider.get_parent_map
129
124
        else:
130
125
            self._get_parent_map = get_parent_map
131
 
        self._cache = None
132
 
        self.enable_cache(True)
 
126
        self._cache = {}
 
127
        self._cache_misses = True
 
128
        self._debug = debug
 
129
        if self._debug:
 
130
            self._requested_parents = None
133
131
 
134
132
    def __repr__(self):
135
133
        return "%s(%r)" % (self.__class__.__name__, self._real_provider)
140
138
            raise AssertionError('Cache enabled when already enabled.')
141
139
        self._cache = {}
142
140
        self._cache_misses = cache_misses
143
 
        self.missing_keys = set()
 
141
        if self._debug:
 
142
            self._requested_parents = set()
144
143
 
145
144
    def disable_cache(self):
146
145
        """Disable and clear the cache."""
147
146
        self._cache = None
148
 
        self._cache_misses = None
149
 
        self.missing_keys = set()
 
147
        if self._debug:
 
148
            self._requested_parents = None
150
149
 
151
150
    def get_cached_map(self):
152
151
        """Return any cached get_parent_map values."""
153
152
        if self._cache is None:
154
153
            return None
155
 
        return dict(self._cache)
 
154
        return dict((k, v) for k, v in self._cache.items()
 
155
                    if v is not None)
156
156
 
157
157
    def get_parent_map(self, keys):
158
 
        """See StackedParentsProvider.get_parent_map."""
159
 
        cache = self._cache
160
 
        if cache is None:
161
 
            cache = self._get_parent_map(keys)
 
158
        """See _StackedParentsProvider.get_parent_map."""
 
159
        # Hack to build up the caching logic.
 
160
        ancestry = self._cache
 
161
        if ancestry is None:
 
162
            # Caching is disabled.
 
163
            missing_revisions = set(keys)
 
164
            ancestry = {}
162
165
        else:
163
 
            needed_revisions = set(key for key in keys if key not in cache)
164
 
            # Do not ask for negatively cached keys
165
 
            needed_revisions.difference_update(self.missing_keys)
166
 
            if needed_revisions:
167
 
                parent_map = self._get_parent_map(needed_revisions)
168
 
                cache.update(parent_map)
169
 
                if self._cache_misses:
170
 
                    for key in needed_revisions:
171
 
                        if key not in parent_map:
172
 
                            self.note_missing_key(key)
173
 
        result = {}
174
 
        for key in keys:
175
 
            value = cache.get(key)
176
 
            if value is not None:
177
 
                result[key] = value
178
 
        return result
179
 
 
180
 
    def note_missing_key(self, key):
181
 
        """Note that key is a missing key."""
182
 
        if self._cache_misses:
183
 
            self.missing_keys.add(key)
 
166
            missing_revisions = set(key for key in keys if key not in ancestry)
 
167
        if missing_revisions:
 
168
            parent_map = self._get_parent_map(missing_revisions)
 
169
            if self._debug:
 
170
                mutter('re-retrieved revisions: %d of %d',
 
171
                        len(set(ancestry).intersection(parent_map)),
 
172
                        len(parent_map))
 
173
            ancestry.update(parent_map)
 
174
            if self._cache_misses:
 
175
                # None is never a valid parents list, so it can be used to
 
176
                # record misses.
 
177
                ancestry.update(dict((k, None) for k in missing_revisions
 
178
                                     if k not in parent_map))
 
179
        present_keys = [k for k in keys if ancestry.get(k) is not None]
 
180
        if self._debug:
 
181
            if self._requested_parents is not None and len(ancestry) != 0:
 
182
                self._requested_parents.update(present_keys)
 
183
                mutter('Current hit rate: %d%%',
 
184
                    100.0 * len(self._requested_parents) / len(ancestry))
 
185
        return dict((k, ancestry[k]) for k in present_keys)
184
186
 
185
187
 
186
188
class Graph(object):
311
313
        # get there.
312
314
        return known_revnos[cur_tip] + num_steps
313
315
 
314
 
    def find_lefthand_distances(self, keys):
315
 
        """Find the distance to null for all the keys in keys.
316
 
 
317
 
        :param keys: keys to lookup.
318
 
        :return: A dict key->distance for all of keys.
319
 
        """
320
 
        # Optimisable by concurrent searching, but a random spread should get
321
 
        # some sort of hit rate.
322
 
        result = {}
323
 
        known_revnos = []
324
 
        ghosts = []
325
 
        for key in keys:
326
 
            try:
327
 
                known_revnos.append(
328
 
                    (key, self.find_distance_to_null(key, known_revnos)))
329
 
            except errors.GhostRevisionsHaveNoRevno:
330
 
                ghosts.append(key)
331
 
        for key in ghosts:
332
 
            known_revnos.append((key, -1))
333
 
        return dict(known_revnos)
334
 
 
335
316
    def find_unique_ancestors(self, unique_revision, common_revisions):
336
317
        """Find the unique ancestors for a revision versus others.
337
318
 
588
569
    def _refine_unique_nodes(self, unique_searcher, all_unique_searcher,
589
570
                             unique_tip_searchers, common_searcher):
590
571
        """Steps 5-8 of find_unique_ancestors.
591
 
 
 
572
        
592
573
        This function returns when common_searcher has stopped searching for
593
574
        more nodes.
594
575
        """
637
618
                                 all_unique_searcher._iterations)
638
619
            unique_tip_searchers = next_unique_searchers
639
620
 
 
621
    @symbol_versioning.deprecated_method(symbol_versioning.one_one)
 
622
    def get_parents(self, revisions):
 
623
        """Find revision ids of the parents of a list of revisions
 
624
 
 
625
        A list is returned of the same length as the input.  Each entry
 
626
        is a list of parent ids for the corresponding input revision.
 
627
 
 
628
        [NULL_REVISION] is used as the parent of the first user-committed
 
629
        revision.  Its parent list is empty.
 
630
 
 
631
        If the revision is not present (i.e. a ghost), None is used in place
 
632
        of the list of parents.
 
633
 
 
634
        Deprecated in bzr 1.2 - please see get_parent_map.
 
635
        """
 
636
        parents = self.get_parent_map(revisions)
 
637
        return [parents.get(r, None) for r in revisions]
 
638
 
640
639
    def get_parent_map(self, revisions):
641
640
        """Get a map of key:parent_list for revisions.
642
641
 
926
925
        An ancestor may sort after a descendant if the relationship is not
927
926
        visible in the supplied list of revisions.
928
927
        """
929
 
        from bzrlib import tsort
930
928
        sorter = tsort.TopoSorter(self.get_parent_map(revisions))
931
929
        return sorter.iter_topo_order()
932
930
 
940
938
        return set([candidate_descendant]) == self.heads(
941
939
            [candidate_ancestor, candidate_descendant])
942
940
 
943
 
    def is_between(self, revid, lower_bound_revid, upper_bound_revid):
944
 
        """Determine whether a revision is between two others.
945
 
 
946
 
        returns true if and only if:
947
 
        lower_bound_revid <= revid <= upper_bound_revid
948
 
        """
949
 
        return ((upper_bound_revid is None or
950
 
                    self.is_ancestor(revid, upper_bound_revid)) and
951
 
               (lower_bound_revid is None or
952
 
                    self.is_ancestor(lower_bound_revid, revid)))
953
 
 
954
941
    def _search_for_extra_common(self, common, searchers):
955
942
        """Make sure that unique nodes are genuinely unique.
956
943
 
1229
1216
 
1230
1217
    def get_result(self):
1231
1218
        """Get a SearchResult for the current state of this searcher.
1232
 
 
 
1219
        
1233
1220
        :return: A SearchResult for this search so far. The SearchResult is
1234
1221
            static - the search can be advanced and the search result will not
1235
1222
            be invalidated or altered.
1239
1226
            # exclude keys for them. However, while we could have a second
1240
1227
            # look-ahead result buffer and shuffle things around, this method
1241
1228
            # is typically only called once per search - when memoising the
1242
 
            # results of the search.
 
1229
            # results of the search. 
1243
1230
            found, ghosts, next, parents = self._do_query(self._next_query)
1244
1231
            # pretend we didn't query: perhaps we should tweak _do_query to be
1245
1232
            # entirely stateless?
1286
1273
 
1287
1274
    def next_with_ghosts(self):
1288
1275
        """Return the next found ancestors, with ghosts split out.
1289
 
 
 
1276
        
1290
1277
        Ancestors are returned in the order they are seen in a breadth-first
1291
1278
        traversal.  No ancestor will be returned more than once. Ancestors are
1292
1279
        returned only after asking for their parents, which allows us to detect
1351
1338
 
1352
1339
    def find_seen_ancestors(self, revisions):
1353
1340
        """Find ancestors of these revisions that have already been seen.
1354
 
 
 
1341
        
1355
1342
        This function generally makes the assumption that querying for the
1356
1343
        parents of a node that has already been queried is reasonably cheap.
1357
1344
        (eg, not a round trip to a remote host).
1413
1400
                self._current_ghosts.intersection(revisions))
1414
1401
            self._current_present.difference_update(stopped)
1415
1402
            self._current_ghosts.difference_update(stopped)
1416
 
            # stopping 'x' should stop returning parents of 'x', but
 
1403
            # stopping 'x' should stop returning parents of 'x', but 
1417
1404
            # not if 'y' always references those same parents
1418
1405
            stop_rev_references = {}
1419
1406
            for rev in stopped_present:
1435
1422
                    stop_parents.add(rev_id)
1436
1423
            self._next_query.difference_update(stop_parents)
1437
1424
        self._stopped_keys.update(stopped)
1438
 
        self._stopped_keys.update(revisions)
 
1425
        self._stopped_keys.update(revisions - set([revision.NULL_REVISION]))
1439
1426
        return stopped
1440
1427
 
1441
1428
    def start_searching(self, revisions):
1481
1468
            a SearchResult from a smart server, in which case the keys list is
1482
1469
            not necessarily immediately available.
1483
1470
        """
1484
 
        self._recipe = ('search', start_keys, exclude_keys, key_count)
 
1471
        self._recipe = (start_keys, exclude_keys, key_count)
1485
1472
        self._keys = frozenset(keys)
1486
1473
 
1487
1474
    def get_recipe(self):
1488
1475
        """Return a recipe that can be used to replay this search.
1489
 
 
 
1476
        
1490
1477
        The recipe allows reconstruction of the same results at a later date
1491
1478
        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
 
1479
        of keys to start and and to stop at. In order to give reproducible
1493
1480
        results when ghosts are encountered by a search they are automatically
1494
1481
        added to the exclude list (or else ghost filling may alter the
1495
1482
        results).
1496
1483
 
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
 
1484
        :return: A tuple (start_keys_set, exclude_keys_set, revision_count). To
 
1485
            recreate the results of this search, create a breadth first
 
1486
            searcher on the same graph starting at start_keys. Then call next()
 
1487
            (or next_with_ghosts()) repeatedly, and on every result, call
 
1488
            stop_searching_any on any keys from the exclude_keys set. The
 
1489
            revision_count value acts as a trivial cross-check - the found
 
1490
            revisions of the new search should have as many elements as
1504
1491
            revision_count. If it does not, then additional revisions have been
1505
1492
            ghosted since the search was executed the first time and the second
1506
1493
            time.
1514
1501
        """
1515
1502
        return self._keys
1516
1503
 
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)
1607
 
 
1608
1504
 
1609
1505
def collapse_linear_regions(parent_map):
1610
1506
    """Collapse regions of the graph that are 'linear'.
1677
1573
            removed.add(node)
1678
1574
 
1679
1575
    return result
1680
 
 
1681
 
 
1682
 
class GraphThunkIdsToKeys(object):
1683
 
    """Forwards calls about 'ids' to be about keys internally."""
1684
 
 
1685
 
    def __init__(self, graph):
1686
 
        self._graph = graph
1687
 
 
1688
 
    def topo_sort(self):
1689
 
        return [r for (r,) in self._graph.topo_sort()]
1690
 
 
1691
 
    def heads(self, ids):
1692
 
        """See Graph.heads()"""
1693
 
        as_keys = [(i,) for i in ids]
1694
 
        head_keys = self._graph.heads(as_keys)
1695
 
        return set([h[0] for h in head_keys])
1696
 
 
1697
 
    def merge_sort(self, tip_revision):
1698
 
        return self._graph.merge_sort((tip_revision,))
1699
 
 
1700
 
 
1701
 
_counters = [0,0,0,0,0,0,0]
1702
 
try:
1703
 
    from bzrlib._known_graph_pyx import KnownGraph
1704
 
except ImportError, e:
1705
 
    osutils.failed_to_load_extension(e)
1706
 
    from bzrlib._known_graph_py import KnownGraph