~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/graph.py

  • Committer: Matt Nordhoff
  • Date: 2009-04-04 02:50:01 UTC
  • mfrom: (4253 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4256.
  • Revision ID: mnordhoff@mattnordhoff.com-20090404025001-z1403k0tatmc8l91
Merge bzr.dev, fixing conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
import time
18
18
 
121
121
            self._get_parent_map = self._real_provider.get_parent_map
122
122
        else:
123
123
            self._get_parent_map = get_parent_map
124
 
        self._cache = {}
125
 
        self._cache_misses = True
 
124
        self._cache = None
 
125
        self.enable_cache(True)
126
126
 
127
127
    def __repr__(self):
128
128
        return "%s(%r)" % (self.__class__.__name__, self._real_provider)
133
133
            raise AssertionError('Cache enabled when already enabled.')
134
134
        self._cache = {}
135
135
        self._cache_misses = cache_misses
 
136
        self.missing_keys = set()
136
137
 
137
138
    def disable_cache(self):
138
139
        """Disable and clear the cache."""
139
140
        self._cache = None
 
141
        self._cache_misses = None
 
142
        self.missing_keys = set()
140
143
 
141
144
    def get_cached_map(self):
142
145
        """Return any cached get_parent_map values."""
143
146
        if self._cache is None:
144
147
            return None
145
 
        return dict((k, v) for k, v in self._cache.items()
146
 
                    if v is not None)
 
148
        return dict(self._cache)
147
149
 
148
150
    def get_parent_map(self, keys):
149
151
        """See _StackedParentsProvider.get_parent_map."""
150
 
        # Hack to build up the caching logic.
151
 
        ancestry = self._cache
152
 
        if ancestry is None:
153
 
            # Caching is disabled.
154
 
            missing_revisions = set(keys)
155
 
            ancestry = {}
 
152
        cache = self._cache
 
153
        if cache is None:
 
154
            cache = self._get_parent_map(keys)
156
155
        else:
157
 
            missing_revisions = set(key for key in keys if key not in ancestry)
158
 
        if missing_revisions:
159
 
            parent_map = self._get_parent_map(missing_revisions)
160
 
            ancestry.update(parent_map)
161
 
            if self._cache_misses:
162
 
                # None is never a valid parents list, so it can be used to
163
 
                # record misses.
164
 
                ancestry.update(dict((k, None) for k in missing_revisions
165
 
                                     if k not in parent_map))
166
 
        present_keys = [k for k in keys if ancestry.get(k) is not None]
167
 
        return dict((k, ancestry[k]) for k in present_keys)
 
156
            needed_revisions = set(key for key in keys if key not in cache)
 
157
            # Do not ask for negatively cached keys
 
158
            needed_revisions.difference_update(self.missing_keys)
 
159
            if needed_revisions:
 
160
                parent_map = self._get_parent_map(needed_revisions)
 
161
                cache.update(parent_map)
 
162
                if self._cache_misses:
 
163
                    for key in needed_revisions:
 
164
                        if key not in parent_map:
 
165
                            self.note_missing_key(key)
 
166
        result = {}
 
167
        for key in keys:
 
168
            value = cache.get(key)
 
169
            if value is not None:
 
170
                result[key] = value
 
171
        return result
 
172
 
 
173
    def note_missing_key(self, key):
 
174
        """Note that key is a missing key."""
 
175
        if self._cache_misses:
 
176
            self.missing_keys.add(key)
168
177
 
169
178
 
170
179
class Graph(object):
551
560
    def _refine_unique_nodes(self, unique_searcher, all_unique_searcher,
552
561
                             unique_tip_searchers, common_searcher):
553
562
        """Steps 5-8 of find_unique_ancestors.
554
 
        
 
563
 
555
564
        This function returns when common_searcher has stopped searching for
556
565
        more nodes.
557
566
        """
600
609
                                 all_unique_searcher._iterations)
601
610
            unique_tip_searchers = next_unique_searchers
602
611
 
603
 
    @symbol_versioning.deprecated_method(symbol_versioning.one_one)
604
 
    def get_parents(self, revisions):
605
 
        """Find revision ids of the parents of a list of revisions
606
 
 
607
 
        A list is returned of the same length as the input.  Each entry
608
 
        is a list of parent ids for the corresponding input revision.
609
 
 
610
 
        [NULL_REVISION] is used as the parent of the first user-committed
611
 
        revision.  Its parent list is empty.
612
 
 
613
 
        If the revision is not present (i.e. a ghost), None is used in place
614
 
        of the list of parents.
615
 
 
616
 
        Deprecated in bzr 1.2 - please see get_parent_map.
617
 
        """
618
 
        parents = self.get_parent_map(revisions)
619
 
        return [parents.get(r, None) for r in revisions]
620
 
 
621
612
    def get_parent_map(self, revisions):
622
613
        """Get a map of key:parent_list for revisions.
623
614
 
1209
1200
 
1210
1201
    def get_result(self):
1211
1202
        """Get a SearchResult for the current state of this searcher.
1212
 
        
 
1203
 
1213
1204
        :return: A SearchResult for this search so far. The SearchResult is
1214
1205
            static - the search can be advanced and the search result will not
1215
1206
            be invalidated or altered.
1219
1210
            # exclude keys for them. However, while we could have a second
1220
1211
            # look-ahead result buffer and shuffle things around, this method
1221
1212
            # is typically only called once per search - when memoising the
1222
 
            # results of the search. 
 
1213
            # results of the search.
1223
1214
            found, ghosts, next, parents = self._do_query(self._next_query)
1224
1215
            # pretend we didn't query: perhaps we should tweak _do_query to be
1225
1216
            # entirely stateless?
1266
1257
 
1267
1258
    def next_with_ghosts(self):
1268
1259
        """Return the next found ancestors, with ghosts split out.
1269
 
        
 
1260
 
1270
1261
        Ancestors are returned in the order they are seen in a breadth-first
1271
1262
        traversal.  No ancestor will be returned more than once. Ancestors are
1272
1263
        returned only after asking for their parents, which allows us to detect
1331
1322
 
1332
1323
    def find_seen_ancestors(self, revisions):
1333
1324
        """Find ancestors of these revisions that have already been seen.
1334
 
        
 
1325
 
1335
1326
        This function generally makes the assumption that querying for the
1336
1327
        parents of a node that has already been queried is reasonably cheap.
1337
1328
        (eg, not a round trip to a remote host).
1393
1384
                self._current_ghosts.intersection(revisions))
1394
1385
            self._current_present.difference_update(stopped)
1395
1386
            self._current_ghosts.difference_update(stopped)
1396
 
            # stopping 'x' should stop returning parents of 'x', but 
 
1387
            # stopping 'x' should stop returning parents of 'x', but
1397
1388
            # not if 'y' always references those same parents
1398
1389
            stop_rev_references = {}
1399
1390
            for rev in stopped_present:
1415
1406
                    stop_parents.add(rev_id)
1416
1407
            self._next_query.difference_update(stop_parents)
1417
1408
        self._stopped_keys.update(stopped)
1418
 
        self._stopped_keys.update(revisions - set([revision.NULL_REVISION]))
 
1409
        self._stopped_keys.update(revisions)
1419
1410
        return stopped
1420
1411
 
1421
1412
    def start_searching(self, revisions):
1461
1452
            a SearchResult from a smart server, in which case the keys list is
1462
1453
            not necessarily immediately available.
1463
1454
        """
1464
 
        self._recipe = (start_keys, exclude_keys, key_count)
 
1455
        self._recipe = ('search', start_keys, exclude_keys, key_count)
1465
1456
        self._keys = frozenset(keys)
1466
1457
 
1467
1458
    def get_recipe(self):
1468
1459
        """Return a recipe that can be used to replay this search.
1469
 
        
 
1460
 
1470
1461
        The recipe allows reconstruction of the same results at a later date
1471
1462
        without knowing all the found keys. The essential elements are a list
1472
1463
        of keys to start and to stop at. In order to give reproducible
1474
1465
        added to the exclude list (or else ghost filling may alter the
1475
1466
        results).
1476
1467
 
1477
 
        :return: A tuple (start_keys_set, exclude_keys_set, revision_count). To
1478
 
            recreate the results of this search, create a breadth first
1479
 
            searcher on the same graph starting at start_keys. Then call next()
1480
 
            (or next_with_ghosts()) repeatedly, and on every result, call
1481
 
            stop_searching_any on any keys from the exclude_keys set. The
1482
 
            revision_count value acts as a trivial cross-check - the found
1483
 
            revisions of the new search should have as many elements as
 
1468
        :return: A tuple ('search', start_keys_set, exclude_keys_set,
 
1469
            revision_count). To recreate the results of this search, create a
 
1470
            breadth first searcher on the same graph starting at start_keys.
 
1471
            Then call next() (or next_with_ghosts()) repeatedly, and on every
 
1472
            result, call stop_searching_any on any keys from the exclude_keys
 
1473
            set. The revision_count value acts as a trivial cross-check - the
 
1474
            found revisions of the new search should have as many elements as
1484
1475
            revision_count. If it does not, then additional revisions have been
1485
1476
            ghosted since the search was executed the first time and the second
1486
1477
            time.
1494
1485
        """
1495
1486
        return self._keys
1496
1487
 
 
1488
    def is_empty(self):
 
1489
        """Return false if the search lists 1 or more revisions."""
 
1490
        return self._recipe[3] == 0
 
1491
 
 
1492
    def refine(self, seen, referenced):
 
1493
        """Create a new search by refining this search.
 
1494
 
 
1495
        :param seen: Revisions that have been satisfied.
 
1496
        :param referenced: Revision references observed while satisfying some
 
1497
            of this search.
 
1498
        """
 
1499
        start = self._recipe[1]
 
1500
        exclude = self._recipe[2]
 
1501
        count = self._recipe[3]
 
1502
        keys = self.get_keys()
 
1503
        # New heads = referenced + old heads - seen things - exclude
 
1504
        pending_refs = set(referenced)
 
1505
        pending_refs.update(start)
 
1506
        pending_refs.difference_update(seen)
 
1507
        pending_refs.difference_update(exclude)
 
1508
        # New exclude = old exclude + satisfied heads
 
1509
        seen_heads = start.intersection(seen)
 
1510
        exclude.update(seen_heads)
 
1511
        # keys gets seen removed
 
1512
        keys = keys - seen
 
1513
        # length is reduced by len(seen)
 
1514
        count -= len(seen)
 
1515
        return SearchResult(pending_refs, exclude, count, keys)
 
1516
 
 
1517
 
 
1518
class PendingAncestryResult(object):
 
1519
    """A search result that will reconstruct the ancestry for some graph heads.
 
1520
 
 
1521
    Unlike SearchResult, this doesn't hold the complete search result in
 
1522
    memory, it just holds a description of how to generate it.
 
1523
    """
 
1524
 
 
1525
    def __init__(self, heads, repo):
 
1526
        """Constructor.
 
1527
 
 
1528
        :param heads: an iterable of graph heads.
 
1529
        :param repo: a repository to use to generate the ancestry for the given
 
1530
            heads.
 
1531
        """
 
1532
        self.heads = frozenset(heads)
 
1533
        self.repo = repo
 
1534
 
 
1535
    def get_recipe(self):
 
1536
        """Return a recipe that can be used to replay this search.
 
1537
 
 
1538
        The recipe allows reconstruction of the same results at a later date.
 
1539
 
 
1540
        :seealso SearchResult.get_recipe:
 
1541
 
 
1542
        :return: A tuple ('proxy-search', start_keys_set, set(), -1)
 
1543
            To recreate this result, create a PendingAncestryResult with the
 
1544
            start_keys_set.
 
1545
        """
 
1546
        return ('proxy-search', self.heads, set(), -1)
 
1547
 
 
1548
    def get_keys(self):
 
1549
        """See SearchResult.get_keys.
 
1550
 
 
1551
        Returns all the keys for the ancestry of the heads, excluding
 
1552
        NULL_REVISION.
 
1553
        """
 
1554
        return self._get_keys(self.repo.get_graph())
 
1555
 
 
1556
    def _get_keys(self, graph):
 
1557
        NULL_REVISION = revision.NULL_REVISION
 
1558
        keys = [key for (key, parents) in graph.iter_ancestry(self.heads)
 
1559
                if key != NULL_REVISION]
 
1560
        return keys
 
1561
 
 
1562
    def is_empty(self):
 
1563
        """Return false if the search lists 1 or more revisions."""
 
1564
        if revision.NULL_REVISION in self.heads:
 
1565
            return len(self.heads) == 1
 
1566
        else:
 
1567
            return len(self.heads) == 0
 
1568
 
 
1569
    def refine(self, seen, referenced):
 
1570
        """Create a new search by refining this search.
 
1571
 
 
1572
        :param seen: Revisions that have been satisfied.
 
1573
        :param referenced: Revision references observed while satisfying some
 
1574
            of this search.
 
1575
        """
 
1576
        referenced = self.heads.union(referenced)
 
1577
        return PendingAncestryResult(referenced - seen, self.repo)
 
1578
 
1497
1579
 
1498
1580
def collapse_linear_regions(parent_map):
1499
1581
    """Collapse regions of the graph that are 'linear'.