~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/graph.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2007, 2008, 2009 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
 
20
20
    debug,
21
21
    errors,
22
22
    revision,
 
23
    symbol_versioning,
23
24
    trace,
 
25
    tsort,
24
26
    )
25
 
from bzrlib.symbol_versioning import deprecated_function, deprecated_in
 
27
from bzrlib.deprecated_graph import (node_distances, select_farthest)
26
28
 
27
29
STEP_UNIQUE_SEARCHER_EVERY = 5
28
30
 
59
61
        return 'DictParentsProvider(%r)' % self.ancestry
60
62
 
61
63
    def get_parent_map(self, keys):
62
 
        """See StackedParentsProvider.get_parent_map"""
 
64
        """See _StackedParentsProvider.get_parent_map"""
63
65
        ancestry = self.ancestry
64
66
        return dict((k, ancestry[k]) for k in keys if k in ancestry)
65
67
 
66
 
@deprecated_function(deprecated_in((1, 16, 0)))
67
 
def _StackedParentsProvider(*args, **kwargs):
68
 
    return StackedParentsProvider(*args, **kwargs)
69
 
 
70
 
class StackedParentsProvider(object):
71
 
    """A parents provider which stacks (or unions) multiple providers.
72
 
    
73
 
    The providers are queries in the order of the provided parent_providers.
74
 
    """
75
 
    
 
68
 
 
69
class _StackedParentsProvider(object):
 
70
 
76
71
    def __init__(self, parent_providers):
77
72
        self._parent_providers = parent_providers
78
73
 
79
74
    def __repr__(self):
80
 
        return "%s(%r)" % (self.__class__.__name__, self._parent_providers)
 
75
        return "_StackedParentsProvider(%r)" % self._parent_providers
81
76
 
82
77
    def get_parent_map(self, keys):
83
78
        """Get a mapping of keys => parents
104
99
 
105
100
 
106
101
class CachingParentsProvider(object):
107
 
    """A parents provider which will cache the revision => parents as a dict.
108
 
 
109
 
    This is useful for providers which have an expensive look up.
110
 
 
111
 
    Either a ParentsProvider or a get_parent_map-like callback may be
112
 
    supplied.  If it provides extra un-asked-for parents, they will be cached,
113
 
    but filtered out of get_parent_map.
114
 
 
115
 
    The cache is enabled by default, but may be disabled and re-enabled.
 
102
    """A parents provider which will cache the revision => parents in a dict.
 
103
 
 
104
    This is useful for providers that have an expensive lookup.
116
105
    """
117
 
    def __init__(self, parent_provider=None, get_parent_map=None):
118
 
        """Constructor.
119
106
 
120
 
        :param parent_provider: The ParentProvider to use.  It or
121
 
            get_parent_map must be supplied.
122
 
        :param get_parent_map: The get_parent_map callback to use.  It or
123
 
            parent_provider must be supplied.
124
 
        """
 
107
    def __init__(self, parent_provider):
125
108
        self._real_provider = parent_provider
126
 
        if get_parent_map is None:
127
 
            self._get_parent_map = self._real_provider.get_parent_map
128
 
        else:
129
 
            self._get_parent_map = get_parent_map
130
 
        self._cache = None
131
 
        self.enable_cache(True)
 
109
        # Theoretically we could use an LRUCache here
 
110
        self._cache = {}
132
111
 
133
112
    def __repr__(self):
134
113
        return "%s(%r)" % (self.__class__.__name__, self._real_provider)
135
114
 
136
 
    def enable_cache(self, cache_misses=True):
137
 
        """Enable cache."""
138
 
        if self._cache is not None:
139
 
            raise AssertionError('Cache enabled when already enabled.')
140
 
        self._cache = {}
141
 
        self._cache_misses = cache_misses
142
 
        self.missing_keys = set()
143
 
 
144
 
    def disable_cache(self):
145
 
        """Disable and clear the cache."""
146
 
        self._cache = None
147
 
        self._cache_misses = None
148
 
        self.missing_keys = set()
149
 
 
150
 
    def get_cached_map(self):
151
 
        """Return any cached get_parent_map values."""
152
 
        if self._cache is None:
153
 
            return None
154
 
        return dict(self._cache)
155
 
 
156
115
    def get_parent_map(self, keys):
157
 
        """See StackedParentsProvider.get_parent_map."""
 
116
        """See _StackedParentsProvider.get_parent_map"""
 
117
        needed = set()
 
118
        # If the _real_provider doesn't have a key, we cache a value of None,
 
119
        # which we then later use to realize we cannot provide a value for that
 
120
        # key.
 
121
        parent_map = {}
158
122
        cache = self._cache
159
 
        if cache is None:
160
 
            cache = self._get_parent_map(keys)
161
 
        else:
162
 
            needed_revisions = set(key for key in keys if key not in cache)
163
 
            # Do not ask for negatively cached keys
164
 
            needed_revisions.difference_update(self.missing_keys)
165
 
            if needed_revisions:
166
 
                parent_map = self._get_parent_map(needed_revisions)
167
 
                cache.update(parent_map)
168
 
                if self._cache_misses:
169
 
                    for key in needed_revisions:
170
 
                        if key not in parent_map:
171
 
                            self.note_missing_key(key)
172
 
        result = {}
173
123
        for key in keys:
174
 
            value = cache.get(key)
175
 
            if value is not None:
176
 
                result[key] = value
177
 
        return result
 
124
            if key in cache:
 
125
                value = cache[key]
 
126
                if value is not None:
 
127
                    parent_map[key] = value
 
128
            else:
 
129
                needed.add(key)
178
130
 
179
 
    def note_missing_key(self, key):
180
 
        """Note that key is a missing key."""
181
 
        if self._cache_misses:
182
 
            self.missing_keys.add(key)
 
131
        if needed:
 
132
            new_parents = self._real_provider.get_parent_map(needed)
 
133
            cache.update(new_parents)
 
134
            parent_map.update(new_parents)
 
135
            needed.difference_update(new_parents)
 
136
            cache.update(dict.fromkeys(needed, None))
 
137
        return parent_map
183
138
 
184
139
 
185
140
class Graph(object):
310
265
        # get there.
311
266
        return known_revnos[cur_tip] + num_steps
312
267
 
313
 
    def find_lefthand_distances(self, keys):
314
 
        """Find the distance to null for all the keys in keys.
315
 
 
316
 
        :param keys: keys to lookup.
317
 
        :return: A dict key->distance for all of keys.
318
 
        """
319
 
        # Optimisable by concurrent searching, but a random spread should get
320
 
        # some sort of hit rate.
321
 
        result = {}
322
 
        known_revnos = []
323
 
        ghosts = []
324
 
        for key in keys:
325
 
            try:
326
 
                known_revnos.append(
327
 
                    (key, self.find_distance_to_null(key, known_revnos)))
328
 
            except errors.GhostRevisionsHaveNoRevno:
329
 
                ghosts.append(key)
330
 
        for key in ghosts:
331
 
            known_revnos.append((key, -1))
332
 
        return dict(known_revnos)
333
 
 
334
268
    def find_unique_ancestors(self, unique_revision, common_revisions):
335
269
        """Find the unique ancestors for a revision versus others.
336
270
 
587
521
    def _refine_unique_nodes(self, unique_searcher, all_unique_searcher,
588
522
                             unique_tip_searchers, common_searcher):
589
523
        """Steps 5-8 of find_unique_ancestors.
590
 
 
 
524
        
591
525
        This function returns when common_searcher has stopped searching for
592
526
        more nodes.
593
527
        """
636
570
                                 all_unique_searcher._iterations)
637
571
            unique_tip_searchers = next_unique_searchers
638
572
 
 
573
    @symbol_versioning.deprecated_method(symbol_versioning.one_one)
 
574
    def get_parents(self, revisions):
 
575
        """Find revision ids of the parents of a list of revisions
 
576
 
 
577
        A list is returned of the same length as the input.  Each entry
 
578
        is a list of parent ids for the corresponding input revision.
 
579
 
 
580
        [NULL_REVISION] is used as the parent of the first user-committed
 
581
        revision.  Its parent list is empty.
 
582
 
 
583
        If the revision is not present (i.e. a ghost), None is used in place
 
584
        of the list of parents.
 
585
 
 
586
        Deprecated in bzr 1.2 - please see get_parent_map.
 
587
        """
 
588
        parents = self.get_parent_map(revisions)
 
589
        return [parents.get(r, None) for r in revisions]
 
590
 
639
591
    def get_parent_map(self, revisions):
640
592
        """Get a map of key:parent_list for revisions.
641
593
 
925
877
        An ancestor may sort after a descendant if the relationship is not
926
878
        visible in the supplied list of revisions.
927
879
        """
928
 
        from bzrlib import tsort
929
880
        sorter = tsort.TopoSorter(self.get_parent_map(revisions))
930
881
        return sorter.iter_topo_order()
931
882
 
939
890
        return set([candidate_descendant]) == self.heads(
940
891
            [candidate_ancestor, candidate_descendant])
941
892
 
942
 
    def is_between(self, revid, lower_bound_revid, upper_bound_revid):
943
 
        """Determine whether a revision is between two others.
944
 
 
945
 
        returns true if and only if:
946
 
        lower_bound_revid <= revid <= upper_bound_revid
947
 
        """
948
 
        return ((upper_bound_revid is None or
949
 
                    self.is_ancestor(revid, upper_bound_revid)) and
950
 
               (lower_bound_revid is None or
951
 
                    self.is_ancestor(lower_bound_revid, revid)))
952
 
 
953
893
    def _search_for_extra_common(self, common, searchers):
954
894
        """Make sure that unique nodes are genuinely unique.
955
895
 
1228
1168
 
1229
1169
    def get_result(self):
1230
1170
        """Get a SearchResult for the current state of this searcher.
1231
 
 
 
1171
        
1232
1172
        :return: A SearchResult for this search so far. The SearchResult is
1233
1173
            static - the search can be advanced and the search result will not
1234
1174
            be invalidated or altered.
1238
1178
            # exclude keys for them. However, while we could have a second
1239
1179
            # look-ahead result buffer and shuffle things around, this method
1240
1180
            # is typically only called once per search - when memoising the
1241
 
            # results of the search.
 
1181
            # results of the search. 
1242
1182
            found, ghosts, next, parents = self._do_query(self._next_query)
1243
1183
            # pretend we didn't query: perhaps we should tweak _do_query to be
1244
1184
            # entirely stateless?
1285
1225
 
1286
1226
    def next_with_ghosts(self):
1287
1227
        """Return the next found ancestors, with ghosts split out.
1288
 
 
 
1228
        
1289
1229
        Ancestors are returned in the order they are seen in a breadth-first
1290
1230
        traversal.  No ancestor will be returned more than once. Ancestors are
1291
1231
        returned only after asking for their parents, which allows us to detect
1350
1290
 
1351
1291
    def find_seen_ancestors(self, revisions):
1352
1292
        """Find ancestors of these revisions that have already been seen.
1353
 
 
 
1293
        
1354
1294
        This function generally makes the assumption that querying for the
1355
1295
        parents of a node that has already been queried is reasonably cheap.
1356
1296
        (eg, not a round trip to a remote host).
1391
1331
        Remove any of the specified revisions from the search list.
1392
1332
 
1393
1333
        None of the specified revisions are required to be present in the
1394
 
        search list.
1395
 
 
1396
 
        It is okay to call stop_searching_any() for revisions which were seen
1397
 
        in previous iterations. It is the callers responsibility to call
1398
 
        find_seen_ancestors() to make sure that current search tips that are
1399
 
        ancestors of those revisions are also stopped.  All explicitly stopped
1400
 
        revisions will be excluded from the search result's get_keys(), though.
 
1334
        search list.  In this case, the call is a no-op.
1401
1335
        """
1402
1336
        # TODO: does this help performance?
1403
1337
        # if not revisions:
1412
1346
                self._current_ghosts.intersection(revisions))
1413
1347
            self._current_present.difference_update(stopped)
1414
1348
            self._current_ghosts.difference_update(stopped)
1415
 
            # stopping 'x' should stop returning parents of 'x', but
 
1349
            # stopping 'x' should stop returning parents of 'x', but 
1416
1350
            # not if 'y' always references those same parents
1417
1351
            stop_rev_references = {}
1418
1352
            for rev in stopped_present:
1434
1368
                    stop_parents.add(rev_id)
1435
1369
            self._next_query.difference_update(stop_parents)
1436
1370
        self._stopped_keys.update(stopped)
1437
 
        self._stopped_keys.update(revisions)
1438
1371
        return stopped
1439
1372
 
1440
1373
    def start_searching(self, revisions):
1480
1413
            a SearchResult from a smart server, in which case the keys list is
1481
1414
            not necessarily immediately available.
1482
1415
        """
1483
 
        self._recipe = ('search', start_keys, exclude_keys, key_count)
 
1416
        self._recipe = (start_keys, exclude_keys, key_count)
1484
1417
        self._keys = frozenset(keys)
1485
1418
 
1486
1419
    def get_recipe(self):
1487
1420
        """Return a recipe that can be used to replay this search.
1488
 
 
 
1421
        
1489
1422
        The recipe allows reconstruction of the same results at a later date
1490
1423
        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
 
1424
        of keys to start and and to stop at. In order to give reproducible
1492
1425
        results when ghosts are encountered by a search they are automatically
1493
1426
        added to the exclude list (or else ghost filling may alter the
1494
1427
        results).
1495
1428
 
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
 
1429
        :return: A tuple (start_keys_set, exclude_keys_set, revision_count). To
 
1430
            recreate the results of this search, create a breadth first
 
1431
            searcher on the same graph starting at start_keys. Then call next()
 
1432
            (or next_with_ghosts()) repeatedly, and on every result, call
 
1433
            stop_searching_any on any keys from the exclude_keys set. The
 
1434
            revision_count value acts as a trivial cross-check - the found
 
1435
            revisions of the new search should have as many elements as
1503
1436
            revision_count. If it does not, then additional revisions have been
1504
1437
            ghosted since the search was executed the first time and the second
1505
1438
            time.
1513
1446
        """
1514
1447
        return self._keys
1515
1448
 
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)
1606
 
 
1607
1449
 
1608
1450
def collapse_linear_regions(parent_map):
1609
1451
    """Collapse regions of the graph that are 'linear'.
1676
1518
            removed.add(node)
1677
1519
 
1678
1520
    return result
1679
 
 
1680
 
 
1681
 
_counters = [0,0,0,0,0,0,0]
1682
 
try:
1683
 
    from bzrlib._known_graph_pyx import KnownGraph
1684
 
except ImportError:
1685
 
    from bzrlib._known_graph_py import KnownGraph