/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: John Arbash Meinel
  • Date: 2008-06-05 16:27:16 UTC
  • mfrom: (3475 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3476.
  • Revision ID: john@arbash-meinel.com-20080605162716-a3hn238tnctbfd8j
merge bzr.dev, resolve NEWS

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
    bzrdir,
26
26
    check,
27
27
    debug,
28
 
    deprecated_graph,
29
28
    errors,
30
29
    generate_ids,
31
30
    gpg,
95
94
        if committer is None:
96
95
            self._committer = self._config.username()
97
96
        else:
98
 
            assert isinstance(committer, basestring), type(committer)
99
97
            self._committer = committer
100
98
 
101
99
        self.new_inventory = Inventory(None)
328
326
            if kind != parent_entry.kind:
329
327
                store = True
330
328
        if kind == 'file':
331
 
            assert content_summary[2] is not None, \
332
 
                "Files must not have executable = None"
 
329
            if content_summary[2] is None:
 
330
                raise ValueError("Files must not have executable = None")
333
331
            if not store:
334
332
                if (# if the file length changed we have to store:
335
333
                    parent_entry.text_size != content_summary[1] or
420
418
        # implementation could give us bad output from readlines() so this is
421
419
        # not a guarantee of safety. What would be better is always checking
422
420
        # the content during test suite execution. RBC 20070912
423
 
        try:
424
 
            return versionedfile.add_lines_with_ghosts(
425
 
                self._new_revision_id, parents, new_lines,
426
 
                nostore_sha=nostore_sha, random_id=self.random_revid,
427
 
                check_content=False)[0:2]
428
 
        finally:
429
 
            versionedfile.clear_cache()
 
421
        return versionedfile.add_lines_with_ghosts(
 
422
            self._new_revision_id, parents, new_lines,
 
423
            nostore_sha=nostore_sha, random_id=self.random_revid,
 
424
            check_content=False)[0:2]
430
425
 
431
426
 
432
427
class RootCommitBuilder(CommitBuilder):
458
453
    The Repository builds on top of Stores and a Transport, which respectively 
459
454
    describe the disk data format and the way of accessing the (possibly 
460
455
    remote) disk.
 
456
 
 
457
    :ivar _transport: Transport for file access to repository, typically
 
458
        pointing to .bzr/repository.
461
459
    """
462
460
 
463
461
    # What class to use for a CommitBuilder. Often its simpler to change this
504
502
        :returns: The validator(which is a sha1 digest, though what is sha'd is
505
503
            repository format specific) of the serialized inventory.
506
504
        """
507
 
        assert self.is_in_write_group()
 
505
        if not self.is_in_write_group():
 
506
            raise AssertionError("%r not in write group" % (self,))
508
507
        _mod_revision.check_not_reserved_id(revision_id)
509
 
        assert inv.revision_id is None or inv.revision_id == revision_id, \
510
 
            "Mismatch between inventory revision" \
511
 
            " id and insertion revid (%r, %r)" % (inv.revision_id, revision_id)
512
 
        assert inv.root is not None
 
508
        if not (inv.revision_id is None or inv.revision_id == revision_id):
 
509
            raise AssertionError(
 
510
                "Mismatch between inventory revision"
 
511
                " id and insertion revid (%r, %r)"
 
512
                % (inv.revision_id, revision_id))
 
513
        if inv.root is None:
 
514
            raise AssertionError()
513
515
        inv_lines = self._serialise_inventory_to_lines(inv)
514
516
        inv_vf = self.get_inventory_weave()
515
517
        return self._inventory_add_lines(inv_vf, revision_id, parents,
545
547
            plaintext = Testament(rev, inv).as_short_text()
546
548
            self.store_revision_signature(
547
549
                gpg.GPGStrategy(config), plaintext, revision_id)
548
 
        if not revision_id in self.get_inventory_weave():
 
550
        inventory_vf = self.get_inventory_weave()
 
551
        if not revision_id in inventory_vf:
549
552
            if inv is None:
550
553
                raise errors.WeaveRevisionNotPresent(revision_id,
551
 
                                                     self.get_inventory_weave())
 
554
                                                     inventory_vf)
552
555
            else:
553
556
                # yes, this is not suitable for adding with ghosts.
554
 
                self.add_inventory(revision_id, inv, rev.parent_ids)
 
557
                rev.inventory_sha1 = self.add_inventory(revision_id, inv,
 
558
                                                        rev.parent_ids)
 
559
        else:
 
560
            rev.inventory_sha1 = inventory_vf.get_sha1s([revision_id])[0]
555
561
        self._revision_store.add_revision(rev, self.get_transaction())
556
562
 
557
563
    def _add_revision_text(self, revision_id, text):
594
600
        Returns a set of the present revisions.
595
601
        """
596
602
        result = []
597
 
        for id in revision_ids:
598
 
            if self.has_revision(id):
599
 
               result.append(id)
600
 
        return result
 
603
        graph = self.get_graph()
 
604
        parent_map = graph.get_parent_map(revision_ids)
 
605
        # The old API returned a list, should this actually be a set?
 
606
        return parent_map.keys()
601
607
 
602
608
    @staticmethod
603
609
    def create(a_bzrdir):
604
610
        """Construct the current default format repository in a_bzrdir."""
605
611
        return RepositoryFormat.get_default_format().initialize(a_bzrdir)
606
612
 
607
 
    def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
 
613
    def __init__(self, _format, a_bzrdir, control_files,
 
614
                 _revision_store, control_store, text_store):
608
615
        """instantiate a Repository.
609
616
 
610
617
        :param _format: The format of the repository on disk.
619
626
        # the following are part of the public API for Repository:
620
627
        self.bzrdir = a_bzrdir
621
628
        self.control_files = control_files
 
629
        self._transport = control_files._transport
 
630
        self.base = self._transport.base
622
631
        self._revision_store = _revision_store
623
632
        # backwards compatibility
624
633
        self.weave_store = text_store
634
643
        # on whether escaping is required.
635
644
        self._warn_if_deprecated()
636
645
        self._write_group = None
637
 
        self.base = control_files._transport.base
638
646
 
639
647
    def __repr__(self):
640
648
        return '%s(%r)' % (self.__class__.__name__,
649
657
        """
650
658
        if self.__class__ is not other.__class__:
651
659
            return False
652
 
        return (self.control_files._transport.base ==
653
 
                other.control_files._transport.base)
 
660
        return (self._transport.base == other._transport.base)
654
661
 
655
662
    def is_in_write_group(self):
656
663
        """Return True if there is an open write group.
1094
1101
                raise errors.InvalidRevisionId(revision_id=rev_id, branch=self)
1095
1102
        revs = self._revision_store.get_revisions(revision_ids,
1096
1103
                                                  self.get_transaction())
1097
 
        for rev in revs:
1098
 
            assert not isinstance(rev.revision_id, unicode)
1099
 
            for parent_id in rev.parent_ids:
1100
 
                assert not isinstance(parent_id, unicode)
1101
1104
        return revs
1102
1105
 
1103
1106
    @needs_read_lock
1112
1115
        rev_tmp.seek(0)
1113
1116
        return rev_tmp.getvalue()
1114
1117
 
1115
 
    @needs_read_lock
1116
1118
    def get_deltas_for_revisions(self, revisions):
1117
1119
        """Produce a generator of revision deltas.
1118
1120
        
1280
1282
                setdefault(file_id, set()).add(revision_id)
1281
1283
        return result
1282
1284
 
1283
 
    def fileids_altered_by_revision_ids(self, revision_ids):
 
1285
    def fileids_altered_by_revision_ids(self, revision_ids, _inv_weave=None):
1284
1286
        """Find the file ids and versions affected by revisions.
1285
1287
 
1286
1288
        :param revisions: an iterable containing revision ids.
 
1289
        :param _inv_weave: The inventory weave from this repository or None.
 
1290
            If None, the inventory weave will be opened automatically.
1287
1291
        :return: a dictionary mapping altered file-ids to an iterable of
1288
1292
        revision_ids. Each altered file-ids has the exact revision_ids that
1289
1293
        altered it listed explicitly.
1290
1294
        """
1291
1295
        selected_revision_ids = set(revision_ids)
1292
 
        w = self.get_inventory_weave()
 
1296
        w = _inv_weave or self.get_inventory_weave()
1293
1297
        pb = ui.ui_factory.nested_progress_bar()
1294
1298
        try:
1295
1299
            return self._find_file_ids_from_xml_inventory_lines(
1338
1342
        """
1339
1343
        # All revisions, to find inventory parents.
1340
1344
        if ancestors is None:
1341
 
            revision_graph = self.get_revision_graph_with_ghosts()
1342
 
            ancestors = revision_graph.get_ancestors()
 
1345
            graph = self.get_graph()
 
1346
            ancestors = graph.get_parent_map(self.all_revision_ids())
1343
1347
        if text_key_references is None:
1344
1348
            text_key_references = self.find_text_key_references()
1345
1349
        pb = ui.ui_factory.nested_progress_bar()
1447
1451
        # maybe this generator should explicitly have the contract that it
1448
1452
        # should not be iterated until the previously yielded item has been
1449
1453
        # processed?
1450
 
        self.lock_read()
1451
1454
        inv_w = self.get_inventory_weave()
1452
 
        inv_w.enable_cache()
1453
1455
 
1454
1456
        # file ids that changed
1455
 
        file_ids = self.fileids_altered_by_revision_ids(revision_ids)
 
1457
        file_ids = self.fileids_altered_by_revision_ids(revision_ids, inv_w)
1456
1458
        count = 0
1457
1459
        num_file_ids = len(file_ids)
1458
1460
        for file_id, altered_versions in file_ids.iteritems():
1466
1468
 
1467
1469
        # inventory
1468
1470
        yield ("inventory", None, revision_ids)
1469
 
        inv_w.clear_cache()
1470
1471
 
1471
1472
        # signatures
1472
1473
        revisions_with_signatures = set()
1478
1479
                pass
1479
1480
            else:
1480
1481
                revisions_with_signatures.add(rev_id)
1481
 
        self.unlock()
1482
1482
        yield ("signatures", None, revisions_with_signatures)
1483
1483
 
1484
1484
        # revisions
1503
1503
 
1504
1504
        :return: An iterator of inventories.
1505
1505
        """
1506
 
        assert None not in revision_ids
1507
 
        assert _mod_revision.NULL_REVISION not in revision_ids
 
1506
        if ((None in revision_ids)
 
1507
            or (_mod_revision.NULL_REVISION in revision_ids)):
 
1508
            raise ValueError('cannot get null revision inventory')
1508
1509
        return self._iter_inventories(revision_ids)
1509
1510
 
1510
1511
    def _iter_inventories(self, revision_ids):
1538
1539
    def get_inventory_xml(self, revision_id):
1539
1540
        """Get inventory XML as a file object."""
1540
1541
        try:
1541
 
            assert isinstance(revision_id, str), type(revision_id)
1542
1542
            iw = self.get_inventory_weave()
1543
1543
            return iw.get_text(revision_id)
1544
1544
        except IndexError:
1550
1550
        """
1551
1551
        return self.get_revision(revision_id).inventory_sha1
1552
1552
 
1553
 
    @needs_read_lock
1554
 
    def get_revision_graph(self, revision_id=None):
1555
 
        """Return a dictionary containing the revision graph.
1556
 
 
1557
 
        NB: This method should not be used as it accesses the entire graph all
1558
 
        at once, which is much more data than most operations should require.
1559
 
 
1560
 
        :param revision_id: The revision_id to get a graph from. If None, then
1561
 
        the entire revision graph is returned. This is a deprecated mode of
1562
 
        operation and will be removed in the future.
1563
 
        :return: a dictionary of revision_id->revision_parents_list.
1564
 
        """
1565
 
        raise NotImplementedError(self.get_revision_graph)
1566
 
 
1567
 
    @needs_read_lock
1568
 
    def get_revision_graph_with_ghosts(self, revision_ids=None):
1569
 
        """Return a graph of the revisions with ghosts marked as applicable.
1570
 
 
1571
 
        :param revision_ids: an iterable of revisions to graph or None for all.
1572
 
        :return: a Graph object with the graph reachable from revision_ids.
1573
 
        """
1574
 
        if 'evil' in debug.debug_flags:
1575
 
            mutter_callsite(3,
1576
 
                "get_revision_graph_with_ghosts scales with size of history.")
1577
 
        result = deprecated_graph.Graph()
1578
 
        if not revision_ids:
1579
 
            pending = set(self.all_revision_ids())
1580
 
            required = set([])
1581
 
        else:
1582
 
            pending = set(revision_ids)
1583
 
            # special case NULL_REVISION
1584
 
            if _mod_revision.NULL_REVISION in pending:
1585
 
                pending.remove(_mod_revision.NULL_REVISION)
1586
 
            required = set(pending)
1587
 
        done = set([])
1588
 
        while len(pending):
1589
 
            revision_id = pending.pop()
1590
 
            try:
1591
 
                rev = self.get_revision(revision_id)
1592
 
            except errors.NoSuchRevision:
1593
 
                if revision_id in required:
1594
 
                    raise
1595
 
                # a ghost
1596
 
                result.add_ghost(revision_id)
1597
 
                continue
1598
 
            for parent_id in rev.parent_ids:
1599
 
                # is this queued or done ?
1600
 
                if (parent_id not in pending and
1601
 
                    parent_id not in done):
1602
 
                    # no, queue it.
1603
 
                    pending.add(parent_id)
1604
 
            result.add_node(revision_id, rev.parent_ids)
1605
 
            done.add(revision_id)
1606
 
        return result
1607
 
 
1608
 
    def _get_history_vf(self):
1609
 
        """Get a versionedfile whose history graph reflects all revisions.
1610
 
 
1611
 
        For weave repositories, this is the inventory weave.
1612
 
        """
1613
 
        return self.get_inventory_weave()
1614
 
 
1615
1553
    def iter_reverse_revision_history(self, revision_id):
1616
1554
        """Iterate backwards through revision ids in the lefthand history
1617
1555
 
1618
1556
        :param revision_id: The revision id to start with.  All its lefthand
1619
1557
            ancestors will be traversed.
1620
1558
        """
1621
 
        if revision_id in (None, _mod_revision.NULL_REVISION):
1622
 
            return
 
1559
        graph = self.get_graph()
1623
1560
        next_id = revision_id
1624
 
        versionedfile = self._get_history_vf()
1625
1561
        while True:
 
1562
            if next_id in (None, _mod_revision.NULL_REVISION):
 
1563
                return
1626
1564
            yield next_id
1627
 
            parents = versionedfile.get_parents(next_id)
 
1565
            # Note: The following line may raise KeyError in the event of
 
1566
            # truncated history. We decided not to have a try:except:raise
 
1567
            # RevisionNotPresent here until we see a use for it, because of the
 
1568
            # cost in an inner loop that is by its very nature O(history).
 
1569
            # Robert Collins 20080326
 
1570
            parents = graph.get_parent_map([next_id])[next_id]
1628
1571
            if len(parents) == 0:
1629
1572
                return
1630
1573
            else:
1684
1627
            inv = self.get_revision_inventory(revision_id)
1685
1628
            return RevisionTree(self, inv, revision_id)
1686
1629
 
1687
 
    @needs_read_lock
1688
1630
    def revision_trees(self, revision_ids):
1689
1631
        """Return Tree for a revision on this branch.
1690
1632
 
1743
1685
    def get_transaction(self):
1744
1686
        return self.control_files.get_transaction()
1745
1687
 
1746
 
    def revision_parents(self, revision_id):
1747
 
        return self.get_inventory_weave().parent_names(revision_id)
1748
 
 
1749
1688
    @deprecated_method(symbol_versioning.one_one)
1750
1689
    def get_parents(self, revision_ids):
1751
1690
        """See StackedParentsProvider.get_parents"""
1756
1695
        """See graph._StackedParentsProvider.get_parent_map"""
1757
1696
        parent_map = {}
1758
1697
        for revision_id in keys:
 
1698
            if revision_id is None:
 
1699
                raise ValueError('get_parent_map(None) is not valid')
1759
1700
            if revision_id == _mod_revision.NULL_REVISION:
1760
1701
                parent_map[revision_id] = ()
1761
1702
            else:
1997
1938
 
1998
1939
 
1999
1940
class MetaDirRepository(Repository):
2000
 
    """Repositories in the new meta-dir layout."""
 
1941
    """Repositories in the new meta-dir layout.
 
1942
    
 
1943
    :ivar _transport: Transport for access to repository control files,
 
1944
        typically pointing to .bzr/repository.
 
1945
    """
2001
1946
 
2002
1947
    def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
2003
1948
        super(MetaDirRepository, self).__init__(_format,
2006
1951
                                                _revision_store,
2007
1952
                                                control_store,
2008
1953
                                                text_store)
2009
 
        dir_mode = self.control_files._dir_mode
2010
 
        file_mode = self.control_files._file_mode
 
1954
        self._transport = control_files._transport
2011
1955
 
2012
1956
    @needs_read_lock
2013
1957
    def is_shared(self):
2014
1958
        """Return True if this repository is flagged as a shared repository."""
2015
 
        return self.control_files._transport.has('shared-storage')
 
1959
        return self._transport.has('shared-storage')
2016
1960
 
2017
1961
    @needs_write_lock
2018
1962
    def set_make_working_trees(self, new_value):
2026
1970
        """
2027
1971
        if new_value:
2028
1972
            try:
2029
 
                self.control_files._transport.delete('no-working-trees')
 
1973
                self._transport.delete('no-working-trees')
2030
1974
            except errors.NoSuchFile:
2031
1975
                pass
2032
1976
        else:
2033
 
            self.control_files.put_utf8('no-working-trees', '')
 
1977
            self._transport.put_bytes('no-working-trees', '',
 
1978
                mode=self.bzrdir._get_file_mode())
2034
1979
    
2035
1980
    def make_working_trees(self):
2036
1981
        """Returns the policy for making working trees on new branches."""
2037
 
        return not self.control_files._transport.has('no-working-trees')
 
1982
        return not self._transport.has('no-working-trees')
 
1983
 
 
1984
 
 
1985
class MetaDirVersionedFileRepository(MetaDirRepository):
 
1986
    """Repositories in a meta-dir, that work via versioned file objects."""
 
1987
 
 
1988
    def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
 
1989
        super(MetaDirVersionedFileRepository, self).__init__(_format, a_bzrdir,
 
1990
            control_files, _revision_store, control_store, text_store)
 
1991
        _revision_store.get_scope = self.get_transaction
 
1992
        control_store.get_scope = self.get_transaction
 
1993
        text_store.get_scope = self.get_transaction
2038
1994
 
2039
1995
 
2040
1996
class RepositoryFormatRegistry(registry.Registry):
2119
2075
        except errors.NoSuchFile:
2120
2076
            raise errors.NoRepositoryPresent(a_bzrdir)
2121
2077
        except KeyError:
2122
 
            raise errors.UnknownFormatError(format=format_string)
 
2078
            raise errors.UnknownFormatError(format=format_string,
 
2079
                                            kind='repository')
2123
2080
 
2124
2081
    @classmethod
2125
2082
    def register_format(klass, format):
2257
2214
        """Upload the initial blank content."""
2258
2215
        control_files = self._create_control_files(a_bzrdir)
2259
2216
        control_files.lock_write()
 
2217
        transport = control_files._transport
 
2218
        if shared == True:
 
2219
            utf8_files += [('shared-storage', '')]
2260
2220
        try:
2261
 
            control_files._transport.mkdir_multi(dirs,
2262
 
                    mode=control_files._dir_mode)
2263
 
            for file, content in files:
2264
 
                control_files.put(file, content)
2265
 
            for file, content in utf8_files:
2266
 
                control_files.put_utf8(file, content)
2267
 
            if shared == True:
2268
 
                control_files.put_utf8('shared-storage', '')
 
2221
            transport.mkdir_multi(dirs, mode=a_bzrdir._get_dir_mode())
 
2222
            for (filename, content_stream) in files:
 
2223
                transport.put_file(filename, content_stream,
 
2224
                    mode=a_bzrdir._get_file_mode())
 
2225
            for (filename, content_bytes) in utf8_files:
 
2226
                transport.put_bytes_non_atomic(filename, content_bytes,
 
2227
                    mode=a_bzrdir._get_file_mode())
2269
2228
        finally:
2270
2229
            control_files.unlock()
2271
2230
 
2374
2333
        :param revision_ids: The start point for the search.
2375
2334
        :return: A set of revision ids.
2376
2335
        """
2377
 
        graph = self.source.get_graph()
 
2336
        target_graph = self.target.get_graph()
 
2337
        revision_ids = frozenset(revision_ids)
 
2338
        if set(target_graph.get_parent_map(revision_ids)) == revision_ids:
 
2339
            return graph.SearchResult(revision_ids, set(), 0, set())
2378
2340
        missing_revs = set()
 
2341
        source_graph = self.source.get_graph()
2379
2342
        # ensure we don't pay silly lookup costs.
2380
 
        revision_ids = frozenset(revision_ids)
2381
 
        searcher = graph._make_breadth_first_searcher(revision_ids)
 
2343
        searcher = source_graph._make_breadth_first_searcher(revision_ids)
2382
2344
        null_set = frozenset([_mod_revision.NULL_REVISION])
2383
2345
        while True:
2384
2346
            try:
2389
2351
                absent_ids = set(revision_ids.intersection(ghosts))
2390
2352
                # If all absent_ids are present in target, no error is needed.
2391
2353
                absent_ids.difference_update(
2392
 
                    self.target.has_revisions(absent_ids))
 
2354
                    set(target_graph.get_parent_map(absent_ids)))
2393
2355
                if absent_ids:
2394
2356
                    raise errors.NoSuchRevision(self.source, absent_ids.pop())
2395
2357
            # we don't care about other ghosts as we can't fetch them and
2396
2358
            # haven't been asked to.
2397
2359
            next_revs = set(next_revs)
2398
2360
            # we always have NULL_REVISION present.
2399
 
            have_revs = self.target.has_revisions(next_revs).union(null_set)
 
2361
            have_revs = set(target_graph.get_parent_map(next_revs)).union(null_set)
2400
2362
            missing_revs.update(next_revs - have_revs)
2401
2363
            searcher.stop_searching_any(have_revs)
2402
2364
        return searcher.get_result()
2433
2395
        target_ids = set(self.target.all_revision_ids())
2434
2396
        if revision_id is not None:
2435
2397
            source_ids = self.source.get_ancestry(revision_id)
2436
 
            assert source_ids[0] is None
 
2398
            if source_ids[0] is not None:
 
2399
                raise AssertionError()
2437
2400
            source_ids.pop(0)
2438
2401
        else:
2439
2402
            source_ids = self.source.all_revision_ids()
2548
2511
        # weave specific optimised path:
2549
2512
        try:
2550
2513
            self.target.set_make_working_trees(self.source.make_working_trees())
2551
 
        except NotImplementedError:
 
2514
        except (errors.RepositoryUpgradeRequired, NotImplemented):
2552
2515
            pass
2553
2516
        # FIXME do not peek!
2554
 
        if self.source.control_files._transport.listable():
 
2517
        if self.source._transport.listable():
2555
2518
            pb = ui.ui_factory.nested_progress_bar()
2556
2519
            try:
2557
2520
                self.target.weave_store.copy_all_ids(
2600
2563
        # - RBC 20060209
2601
2564
        if revision_id is not None:
2602
2565
            source_ids = self.source.get_ancestry(revision_id)
2603
 
            assert source_ids[0] is None
 
2566
            if source_ids[0] is not None:
 
2567
                raise AssertionError()
2604
2568
            source_ids.pop(0)
2605
2569
        else:
2606
2570
            source_ids = self.source._all_possible_ids()
2669
2633
        """See InterRepository.missing_revision_ids()."""
2670
2634
        if revision_id is not None:
2671
2635
            source_ids = self.source.get_ancestry(revision_id)
2672
 
            assert source_ids[0] is None
 
2636
            if source_ids[0] is not None:
 
2637
                raise AssertionError()
2673
2638
            source_ids.pop(0)
2674
2639
        else:
2675
2640
            source_ids = self.source.all_revision_ids()
2735
2700
            # inventory parsing etc, IFF nothing to be copied is in the target.
2736
2701
            # till then:
2737
2702
            revision_ids = self.source.all_revision_ids()
 
2703
            revision_keys = [(revid,) for revid in revision_ids]
 
2704
            index = self.target._pack_collection.revision_index.combined_index
 
2705
            present_revision_ids = set(item[1][0] for item in
 
2706
                index.iter_entries(revision_keys))
 
2707
            revision_ids = set(revision_ids) - present_revision_ids
2738
2708
            # implementing the TODO will involve:
2739
2709
            # - detecting when all of a pack is selected
2740
2710
            # - avoiding as much as possible pre-selection, so the
2751
2721
                    find_ghosts=find_ghosts).get_keys()
2752
2722
            except errors.NoSuchRevision:
2753
2723
                raise errors.InstallFailed([revision_id])
 
2724
            if len(revision_ids) == 0:
 
2725
                return (0, [])
2754
2726
        packs = self.source._pack_collection.all_packs()
2755
2727
        pack = Packer(self.target._pack_collection, packs, '.fetch',
2756
2728
            revision_ids).pack()
2775
2747
            return self._walk_to_common_revisions([revision_id])
2776
2748
        elif revision_id is not None:
2777
2749
            source_ids = self.source.get_ancestry(revision_id)
2778
 
            assert source_ids[0] is None
 
2750
            if source_ids[0] is not None:
 
2751
                raise AssertionError()
2779
2752
            source_ids.pop(0)
2780
2753
        else:
2781
2754
            source_ids = self.source.all_revision_ids()
2942
2915
        # Is source's model compatible with target's model?
2943
2916
        source._ensure_real()
2944
2917
        real_source = source._real_repository
2945
 
        assert not isinstance(real_source, remote.RemoteRepository), (
2946
 
            "We don't support remote repos backed by remote repos yet.")
 
2918
        if isinstance(real_source, remote.RemoteRepository):
 
2919
            raise NotImplementedError(
 
2920
                "We don't support remote repos backed by remote repos yet.")
2947
2921
        return InterRepository._same_model(real_source, target)
2948
2922
 
2949
2923
    @needs_write_lock
3121
3095
        """
3122
3096
        wrong_parents = {}
3123
3097
        unused_versions = set()
3124
 
        for num, revision_id in enumerate(weave.versions()):
 
3098
        versions = weave.versions()
 
3099
        parent_map = weave.get_parent_map(versions)
 
3100
        for num, revision_id in enumerate(versions):
3125
3101
            try:
3126
3102
                correct_parents = self.calculate_file_version_parents(
3127
3103
                    revision_id, file_id)
3130
3106
                unused_versions.add(revision_id)
3131
3107
            else:
3132
3108
                try:
3133
 
                    knit_parents = tuple(weave.get_parents(revision_id))
 
3109
                    knit_parents = tuple(parent_map[revision_id])
3134
3110
                except errors.RevisionNotPresent:
3135
3111
                    knit_parents = None
3136
3112
                if correct_parents != knit_parents:
3137
3113
                    wrong_parents[revision_id] = (knit_parents, correct_parents)
3138
3114
        return wrong_parents, unused_versions
 
3115
 
 
3116
 
 
3117
def _old_get_graph(repository, revision_id):
 
3118
    """DO NOT USE. That is all. I'm serious."""
 
3119
    graph = repository.get_graph()
 
3120
    revision_graph = dict(((key, value) for key, value in
 
3121
        graph.iter_ancestry([revision_id]) if value is not None))
 
3122
    return _strip_NULL_ghosts(revision_graph)
 
3123
 
 
3124
 
 
3125
def _strip_NULL_ghosts(revision_graph):
 
3126
    """Also don't use this. more compatibility code for unmigrated clients."""
 
3127
    # Filter ghosts, and null:
 
3128
    if _mod_revision.NULL_REVISION in revision_graph:
 
3129
        del revision_graph[_mod_revision.NULL_REVISION]
 
3130
    for key, parents in revision_graph.items():
 
3131
        revision_graph[key] = tuple(parent for parent in parents if parent
 
3132
            in revision_graph)
 
3133
    return revision_graph