/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

Merged unnecessary-test-applicable into weave-fmt-plugin.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-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
25
25
    check,
26
26
    chk_map,
27
27
    config,
 
28
    controldir,
28
29
    debug,
29
 
    errors,
30
30
    fetch as _mod_fetch,
31
31
    fifo_cache,
32
32
    generate_ids,
39
39
    lockdir,
40
40
    lru_cache,
41
41
    osutils,
 
42
    pyutils,
42
43
    revision as _mod_revision,
43
44
    static_tuple,
44
45
    symbol_versioning,
45
46
    trace,
46
47
    tsort,
47
 
    ui,
48
48
    versionedfile,
49
49
    )
50
50
from bzrlib.bundle import serializer
53
53
from bzrlib.testament import Testament
54
54
""")
55
55
 
 
56
import sys
 
57
from bzrlib import (
 
58
    errors,
 
59
    registry,
 
60
    ui,
 
61
    )
56
62
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
57
63
from bzrlib.inter import InterObject
58
64
from bzrlib.inventory import (
61
67
    ROOT_ID,
62
68
    entry_factory,
63
69
    )
64
 
from bzrlib.lock import _RelockDebugMixin
65
 
from bzrlib import registry
 
70
from bzrlib.recordcounter import RecordCounter
 
71
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
66
72
from bzrlib.trace import (
67
73
    log_exception_quietly, note, mutter, mutter_callsite, warning)
68
74
 
71
77
_deprecation_warning_done = False
72
78
 
73
79
 
 
80
class IsInWriteGroupError(errors.InternalBzrError):
 
81
 
 
82
    _fmt = "May not refresh_data of repo %(repo)s while in a write group."
 
83
 
 
84
    def __init__(self, repo):
 
85
        errors.InternalBzrError.__init__(self, repo=repo)
 
86
 
 
87
 
74
88
class CommitBuilder(object):
75
89
    """Provides an interface to build up a commit.
76
90
 
101
115
 
102
116
        if committer is None:
103
117
            self._committer = self._config.username()
 
118
        elif not isinstance(committer, unicode):
 
119
            self._committer = committer.decode() # throw if non-ascii
104
120
        else:
105
121
            self._committer = committer
106
122
 
160
176
            self._validate_unicode_text(value,
161
177
                                        'revision property (%s)' % (key,))
162
178
 
 
179
    def _ensure_fallback_inventories(self):
 
180
        """Ensure that appropriate inventories are available.
 
181
 
 
182
        This only applies to repositories that are stacked, and is about
 
183
        enusring the stacking invariants. Namely, that for any revision that is
 
184
        present, we either have all of the file content, or we have the parent
 
185
        inventory and the delta file content.
 
186
        """
 
187
        if not self.repository._fallback_repositories:
 
188
            return
 
189
        if not self.repository._format.supports_chks:
 
190
            raise errors.BzrError("Cannot commit directly to a stacked branch"
 
191
                " in pre-2a formats. See "
 
192
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
 
193
        # This is a stacked repo, we need to make sure we have the parent
 
194
        # inventories for the parents.
 
195
        parent_keys = [(p,) for p in self.parents]
 
196
        parent_map = self.repository.inventories._index.get_parent_map(parent_keys)
 
197
        missing_parent_keys = set([pk for pk in parent_keys
 
198
                                       if pk not in parent_map])
 
199
        fallback_repos = list(reversed(self.repository._fallback_repositories))
 
200
        missing_keys = [('inventories', pk[0])
 
201
                        for pk in missing_parent_keys]
 
202
        resume_tokens = []
 
203
        while missing_keys and fallback_repos:
 
204
            fallback_repo = fallback_repos.pop()
 
205
            source = fallback_repo._get_source(self.repository._format)
 
206
            sink = self.repository._get_sink()
 
207
            stream = source.get_stream_for_missing_keys(missing_keys)
 
208
            missing_keys = sink.insert_stream_without_locking(stream,
 
209
                self.repository._format)
 
210
        if missing_keys:
 
211
            raise errors.BzrError('Unable to fill in parent inventories for a'
 
212
                                  ' stacked branch')
 
213
 
163
214
    def commit(self, message):
164
215
        """Make the actual commit.
165
216
 
177
228
        rev.parent_ids = self.parents
178
229
        self.repository.add_revision(self._new_revision_id, rev,
179
230
            self.new_inventory, self._config)
 
231
        self._ensure_fallback_inventories()
180
232
        self.repository.commit_write_group()
181
233
        return self._new_revision_id
182
234
 
231
283
 
232
284
    def _gen_revision_id(self):
233
285
        """Return new revision-id."""
234
 
        return generate_ids.gen_revision_id(self._config.username(),
235
 
                                            self._timestamp)
 
286
        return generate_ids.gen_revision_id(self._committer, self._timestamp)
236
287
 
237
288
    def _generate_revision_if_needed(self):
238
289
        """Create a revision id if None was supplied.
278
329
 
279
330
        :param tree: The tree which is being committed.
280
331
        """
281
 
        # NB: if there are no parents then this method is not called, so no
282
 
        # need to guard on parents having length.
 
332
        if len(self.parents) == 0:
 
333
            raise errors.RootMissing()
283
334
        entry = entry_factory['directory'](tree.path2id(''), '',
284
335
            None)
285
336
        entry.revision = self._new_revision_id
423
474
            else:
424
475
                # we don't need to commit this, because the caller already
425
476
                # determined that an existing revision of this file is
426
 
                # appropriate. If its not being considered for committing then
 
477
                # appropriate. If it's not being considered for committing then
427
478
                # it and all its parents to the root must be unaltered so
428
479
                # no-change against the basis.
429
480
                if ie.revision == self._new_revision_id:
745
796
                    # after iter_changes examines and decides it has changed,
746
797
                    # we will unconditionally record a new version even if some
747
798
                    # other process reverts it while commit is running (with
748
 
                    # the revert happening after iter_changes did it's
 
799
                    # the revert happening after iter_changes did its
749
800
                    # examination).
750
801
                    if change[7][1]:
751
802
                        entry.executable = True
860
911
        # versioned roots do not change unless the tree found a change.
861
912
 
862
913
 
 
914
class RepositoryWriteLockResult(LogicalLockResult):
 
915
    """The result of write locking a repository.
 
916
 
 
917
    :ivar repository_token: The token obtained from the underlying lock, or
 
918
        None.
 
919
    :ivar unlock: A callable which will unlock the lock.
 
920
    """
 
921
 
 
922
    def __init__(self, unlock, repository_token):
 
923
        LogicalLockResult.__init__(self, unlock)
 
924
        self.repository_token = repository_token
 
925
 
 
926
    def __repr__(self):
 
927
        return "RepositoryWriteLockResult(%s, %s)" % (self.repository_token,
 
928
            self.unlock)
 
929
 
 
930
 
863
931
######################################################################
864
932
# Repositories
865
933
 
866
934
 
867
 
class Repository(_RelockDebugMixin, bzrdir.ControlComponent):
 
935
class Repository(_RelockDebugMixin, controldir.ControlComponent):
868
936
    """Repository holding history for one or more branches.
869
937
 
870
938
    The repository holds and retrieves historical information including
917
985
        pointing to .bzr/repository.
918
986
    """
919
987
 
920
 
    # What class to use for a CommitBuilder. Often its simpler to change this
 
988
    # What class to use for a CommitBuilder. Often it's simpler to change this
921
989
    # in a Repository class subclass rather than to override
922
990
    # get_commit_builder.
923
991
    _commit_builder_class = CommitBuilder
1018
1086
                " id and insertion revid (%r, %r)"
1019
1087
                % (inv.revision_id, revision_id))
1020
1088
        if inv.root is None:
1021
 
            raise AssertionError()
 
1089
            raise errors.RootMissing()
1022
1090
        return self._add_inventory_checked(revision_id, inv, parents)
1023
1091
 
1024
1092
    def _add_inventory_checked(self, revision_id, inv, parents):
1376
1444
        data during reads, and allows a 'write_group' to be obtained. Write
1377
1445
        groups must be used for actual data insertion.
1378
1446
 
 
1447
        A token should be passed in if you know that you have locked the object
 
1448
        some other way, and need to synchronise this object's state with that
 
1449
        fact.
 
1450
 
 
1451
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
 
1452
 
1379
1453
        :param token: if this is already locked, then lock_write will fail
1380
1454
            unless the token matches the existing lock.
1381
1455
        :returns: a token if this instance supports tokens, otherwise None.
1384
1458
        :raises MismatchedToken: if the specified token doesn't match the token
1385
1459
            of the existing lock.
1386
1460
        :seealso: start_write_group.
1387
 
 
1388
 
        A token should be passed in if you know that you have locked the object
1389
 
        some other way, and need to synchronise this object's state with that
1390
 
        fact.
1391
 
 
1392
 
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
 
1461
        :return: A RepositoryWriteLockResult.
1393
1462
        """
1394
1463
        locked = self.is_locked()
1395
 
        result = self.control_files.lock_write(token=token)
 
1464
        token = self.control_files.lock_write(token=token)
1396
1465
        if not locked:
1397
1466
            self._warn_if_deprecated()
1398
1467
            self._note_lock('w')
1400
1469
                # Writes don't affect fallback repos
1401
1470
                repo.lock_read()
1402
1471
            self._refresh_data()
1403
 
        return result
 
1472
        return RepositoryWriteLockResult(self.unlock, token)
1404
1473
 
1405
1474
    def lock_read(self):
 
1475
        """Lock the repository for read operations.
 
1476
 
 
1477
        :return: An object with an unlock method which will release the lock
 
1478
            obtained.
 
1479
        """
1406
1480
        locked = self.is_locked()
1407
1481
        self.control_files.lock_read()
1408
1482
        if not locked:
1411
1485
            for repo in self._fallback_repositories:
1412
1486
                repo.lock_read()
1413
1487
            self._refresh_data()
 
1488
        return LogicalLockResult(self.unlock)
1414
1489
 
1415
1490
    def get_physical_lock_status(self):
1416
1491
        return self.control_files.get_physical_lock_status()
1634
1709
        return missing_keys
1635
1710
 
1636
1711
    def refresh_data(self):
1637
 
        """Re-read any data needed to to synchronise with disk.
 
1712
        """Re-read any data needed to synchronise with disk.
1638
1713
 
1639
1714
        This method is intended to be called after another repository instance
1640
1715
        (such as one used by a smart server) has inserted data into the
1641
 
        repository. It may not be called during a write group, but may be
1642
 
        called at any other time.
 
1716
        repository. On all repositories this will work outside of write groups.
 
1717
        Some repository formats (pack and newer for bzrlib native formats)
 
1718
        support refresh_data inside write groups. If called inside a write
 
1719
        group on a repository that does not support refreshing in a write group
 
1720
        IsInWriteGroupError will be raised.
1643
1721
        """
1644
 
        if self.is_in_write_group():
1645
 
            raise errors.InternalBzrError(
1646
 
                "May not refresh_data while in a write group.")
1647
1722
        self._refresh_data()
1648
1723
 
1649
1724
    def resume_write_group(self, tokens):
1688
1763
                "May not fetch while in a write group.")
1689
1764
        # fast path same-url fetch operations
1690
1765
        # TODO: lift out to somewhere common with RemoteRepository
1691
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/401646>
 
1766
        # <https://bugs.launchpad.net/bzr/+bug/401646>
1692
1767
        if (self.has_same_location(source)
1693
1768
            and fetch_spec is None
1694
1769
            and self._has_same_fallbacks(source)):
1722
1797
        :param revprops: Optional dictionary of revision properties.
1723
1798
        :param revision_id: Optional revision id.
1724
1799
        """
1725
 
        if self._fallback_repositories:
1726
 
            raise errors.BzrError("Cannot commit from a lightweight checkout "
1727
 
                "to a stacked branch. See "
 
1800
        if self._fallback_repositories and not self._format.supports_chks:
 
1801
            raise errors.BzrError("Cannot commit directly to a stacked branch"
 
1802
                " in pre-2a formats. See "
1728
1803
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1729
1804
        result = self._commit_builder_class(self, parents, config,
1730
1805
            timestamp, timezone, committer, revprops, revision_id)
2476
2551
            ancestors will be traversed.
2477
2552
        """
2478
2553
        graph = self.get_graph()
2479
 
        next_id = revision_id
2480
 
        while True:
2481
 
            if next_id in (None, _mod_revision.NULL_REVISION):
2482
 
                return
2483
 
            try:
2484
 
                parents = graph.get_parent_map([next_id])[next_id]
2485
 
            except KeyError:
2486
 
                raise errors.RevisionNotPresent(next_id, self)
2487
 
            yield next_id
2488
 
            if len(parents) == 0:
2489
 
                return
2490
 
            else:
2491
 
                next_id = parents[0]
 
2554
        stop_revisions = (None, _mod_revision.NULL_REVISION)
 
2555
        return graph.iter_lefthand_ancestry(revision_id, stop_revisions)
2492
2556
 
2493
2557
    def is_shared(self):
2494
2558
        """Return True if this repository is flagged as a shared repository."""
2595
2659
        types it should be a no-op that just returns.
2596
2660
 
2597
2661
        This stub method does not require a lock, but subclasses should use
2598
 
        @needs_write_lock as this is a long running call its reasonable to
 
2662
        @needs_write_lock as this is a long running call it's reasonable to
2599
2663
        implicitly lock for the user.
2600
2664
 
2601
2665
        :param hint: If not supplied, the whole repository is packed.
2794
2858
        raise NotImplementedError(self.revision_graph_can_have_wrong_parents)
2795
2859
 
2796
2860
 
2797
 
# remove these delegates a while after bzr 0.15
2798
 
def __make_delegated(name, from_module):
2799
 
    def _deprecated_repository_forwarder():
2800
 
        symbol_versioning.warn('%s moved to %s in bzr 0.15'
2801
 
            % (name, from_module),
2802
 
            DeprecationWarning,
2803
 
            stacklevel=2)
2804
 
        m = __import__(from_module, globals(), locals(), [name])
2805
 
        try:
2806
 
            return getattr(m, name)
2807
 
        except AttributeError:
2808
 
            raise AttributeError('module %s has no name %s'
2809
 
                    % (m, name))
2810
 
    globals()[name] = _deprecated_repository_forwarder
2811
 
 
2812
 
for _name in [
2813
 
        'AllInOneRepository',
2814
 
        'WeaveMetaDirRepository',
2815
 
        'PreSplitOutRepositoryFormat',
2816
 
        'RepositoryFormat4',
2817
 
        'RepositoryFormat5',
2818
 
        'RepositoryFormat6',
2819
 
        'RepositoryFormat7',
2820
 
        ]:
2821
 
    __make_delegated(_name, 'bzrlib.repofmt.weaverepo')
2822
 
 
2823
 
for _name in [
2824
 
        'KnitRepository',
2825
 
        'RepositoryFormatKnit',
2826
 
        'RepositoryFormatKnit1',
2827
 
        ]:
2828
 
    __make_delegated(_name, 'bzrlib.repofmt.knitrepo')
2829
 
 
2830
 
 
2831
2861
def install_revision(repository, rev, revision_tree):
2832
2862
    """Install all revision data into a repository."""
2833
2863
    install_revisions(repository, [(rev, revision_tree, None)])
3052
3082
    supports_tree_reference = None
3053
3083
    # Is the format experimental ?
3054
3084
    experimental = False
 
3085
    # Does this repository format escape funky characters, or does it create files with
 
3086
    # similar names as the versioned files in its contents on disk ?
 
3087
    supports_funky_characters = True
3055
3088
 
3056
3089
    def __repr__(self):
3057
3090
        return "%s()" % self.__class__.__name__
3237
3270
        return self.get_format_string()
3238
3271
 
3239
3272
 
3240
 
# Pre-0.8 formats that don't have a disk format string (because they are
3241
 
# versioned by the matching control directory). We use the control directories
3242
 
# disk format string as a key for the network_name because they meet the
3243
 
# constraints (simple string, unique, immutable).
3244
 
network_format_registry.register_lazy(
3245
 
    "Bazaar-NG branch, format 5\n",
3246
 
    'bzrlib.repofmt.weaverepo',
3247
 
    'RepositoryFormat5',
3248
 
)
3249
 
network_format_registry.register_lazy(
3250
 
    "Bazaar-NG branch, format 6\n",
3251
 
    'bzrlib.repofmt.weaverepo',
3252
 
    'RepositoryFormat6',
3253
 
)
3254
 
 
3255
3273
# formats which have no format string are not discoverable or independently
3256
3274
# creatable on disk, so are not registered in format_registry.  They're
3257
 
# all in bzrlib.repofmt.weaverepo now.  When an instance of one of these is
 
3275
# all in bzrlib.repofmt.knitreponow.  When an instance of one of these is
3258
3276
# needed, it's constructed directly by the BzrDir.  Non-native formats where
3259
3277
# the repository is not separately opened are similar.
3260
3278
 
3261
3279
format_registry.register_lazy(
3262
 
    'Bazaar-NG Repository format 7',
3263
 
    'bzrlib.repofmt.weaverepo',
3264
 
    'RepositoryFormat7'
3265
 
    )
3266
 
 
3267
 
format_registry.register_lazy(
3268
3280
    'Bazaar-NG Knit Repository Format 1',
3269
3281
    'bzrlib.repofmt.knitrepo',
3270
3282
    'RepositoryFormatKnit1',
3325
3337
    'bzrlib.repofmt.pack_repo',
3326
3338
    'RepositoryFormatKnitPack6RichRoot',
3327
3339
    )
 
3340
format_registry.register_lazy(
 
3341
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
 
3342
    'bzrlib.repofmt.groupcompress_repo',
 
3343
    'RepositoryFormat2a',
 
3344
    )
3328
3345
 
3329
3346
# Development formats.
3330
 
# Obsolete but kept pending a CHK based subtree format.
 
3347
# Check their docstrings to see if/when they are obsolete.
3331
3348
format_registry.register_lazy(
3332
3349
    ("Bazaar development format 2 with subtree support "
3333
3350
        "(needs bzr.dev from before 1.8)\n"),
3334
3351
    'bzrlib.repofmt.pack_repo',
3335
3352
    'RepositoryFormatPackDevelopment2Subtree',
3336
3353
    )
3337
 
 
3338
 
# 1.14->1.16 go below here
3339
 
format_registry.register_lazy(
3340
 
    'Bazaar development format - group compression and chk inventory'
3341
 
        ' (needs bzr.dev from 1.14)\n',
3342
 
    'bzrlib.repofmt.groupcompress_repo',
3343
 
    'RepositoryFormatCHK1',
3344
 
    )
3345
 
 
3346
 
format_registry.register_lazy(
3347
 
    'Bazaar development format - chk repository with bencode revision '
3348
 
        'serialization (needs bzr.dev from 1.16)\n',
3349
 
    'bzrlib.repofmt.groupcompress_repo',
3350
 
    'RepositoryFormatCHK2',
3351
 
    )
3352
 
format_registry.register_lazy(
3353
 
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
3354
 
    'bzrlib.repofmt.groupcompress_repo',
3355
 
    'RepositoryFormat2a',
 
3354
format_registry.register_lazy(
 
3355
    'Bazaar development format 8\n',
 
3356
    'bzrlib.repofmt.groupcompress_repo',
 
3357
    'RepositoryFormat2aSubtree',
3356
3358
    )
3357
3359
 
3358
3360
 
3536
3538
        return InterRepository._same_model(source, target)
3537
3539
 
3538
3540
 
3539
 
class InterWeaveRepo(InterSameDataRepository):
3540
 
    """Optimised code paths between Weave based repositories.
3541
 
 
3542
 
    This should be in bzrlib/repofmt/weaverepo.py but we have not yet
3543
 
    implemented lazy inter-object optimisation.
3544
 
    """
3545
 
 
3546
 
    @classmethod
3547
 
    def _get_repo_format_to_test(self):
3548
 
        from bzrlib.repofmt import weaverepo
3549
 
        return weaverepo.RepositoryFormat7()
3550
 
 
3551
 
    @staticmethod
3552
 
    def is_compatible(source, target):
3553
 
        """Be compatible with known Weave formats.
3554
 
 
3555
 
        We don't test for the stores being of specific types because that
3556
 
        could lead to confusing results, and there is no need to be
3557
 
        overly general.
3558
 
        """
3559
 
        from bzrlib.repofmt.weaverepo import (
3560
 
                RepositoryFormat5,
3561
 
                RepositoryFormat6,
3562
 
                RepositoryFormat7,
3563
 
                )
3564
 
        try:
3565
 
            return (isinstance(source._format, (RepositoryFormat5,
3566
 
                                                RepositoryFormat6,
3567
 
                                                RepositoryFormat7)) and
3568
 
                    isinstance(target._format, (RepositoryFormat5,
3569
 
                                                RepositoryFormat6,
3570
 
                                                RepositoryFormat7)))
3571
 
        except AttributeError:
3572
 
            return False
3573
 
 
3574
 
    @needs_write_lock
3575
 
    def copy_content(self, revision_id=None):
3576
 
        """See InterRepository.copy_content()."""
3577
 
        # weave specific optimised path:
3578
 
        try:
3579
 
            self.target.set_make_working_trees(self.source.make_working_trees())
3580
 
        except (errors.RepositoryUpgradeRequired, NotImplemented):
3581
 
            pass
3582
 
        # FIXME do not peek!
3583
 
        if self.source._transport.listable():
3584
 
            pb = ui.ui_factory.nested_progress_bar()
3585
 
            try:
3586
 
                self.target.texts.insert_record_stream(
3587
 
                    self.source.texts.get_record_stream(
3588
 
                        self.source.texts.keys(), 'topological', False))
3589
 
                pb.update('Copying inventory', 0, 1)
3590
 
                self.target.inventories.insert_record_stream(
3591
 
                    self.source.inventories.get_record_stream(
3592
 
                        self.source.inventories.keys(), 'topological', False))
3593
 
                self.target.signatures.insert_record_stream(
3594
 
                    self.source.signatures.get_record_stream(
3595
 
                        self.source.signatures.keys(),
3596
 
                        'unordered', True))
3597
 
                self.target.revisions.insert_record_stream(
3598
 
                    self.source.revisions.get_record_stream(
3599
 
                        self.source.revisions.keys(),
3600
 
                        'topological', True))
3601
 
            finally:
3602
 
                pb.finished()
3603
 
        else:
3604
 
            self.target.fetch(self.source, revision_id=revision_id)
3605
 
 
3606
 
    @needs_read_lock
3607
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3608
 
        """See InterRepository.missing_revision_ids()."""
3609
 
        # we want all revisions to satisfy revision_id in source.
3610
 
        # but we don't want to stat every file here and there.
3611
 
        # we want then, all revisions other needs to satisfy revision_id
3612
 
        # checked, but not those that we have locally.
3613
 
        # so the first thing is to get a subset of the revisions to
3614
 
        # satisfy revision_id in source, and then eliminate those that
3615
 
        # we do already have.
3616
 
        # this is slow on high latency connection to self, but as this
3617
 
        # disk format scales terribly for push anyway due to rewriting
3618
 
        # inventory.weave, this is considered acceptable.
3619
 
        # - RBC 20060209
3620
 
        if revision_id is not None:
3621
 
            source_ids = self.source.get_ancestry(revision_id)
3622
 
            if source_ids[0] is not None:
3623
 
                raise AssertionError()
3624
 
            source_ids.pop(0)
3625
 
        else:
3626
 
            source_ids = self.source._all_possible_ids()
3627
 
        source_ids_set = set(source_ids)
3628
 
        # source_ids is the worst possible case we may need to pull.
3629
 
        # now we want to filter source_ids against what we actually
3630
 
        # have in target, but don't try to check for existence where we know
3631
 
        # we do not have a revision as that would be pointless.
3632
 
        target_ids = set(self.target._all_possible_ids())
3633
 
        possibly_present_revisions = target_ids.intersection(source_ids_set)
3634
 
        actually_present_revisions = set(
3635
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
3636
 
        required_revisions = source_ids_set.difference(actually_present_revisions)
3637
 
        if revision_id is not None:
3638
 
            # we used get_ancestry to determine source_ids then we are assured all
3639
 
            # revisions referenced are present as they are installed in topological order.
3640
 
            # and the tip revision was validated by get_ancestry.
3641
 
            result_set = required_revisions
3642
 
        else:
3643
 
            # if we just grabbed the possibly available ids, then
3644
 
            # we only have an estimate of whats available and need to validate
3645
 
            # that against the revision records.
3646
 
            result_set = set(
3647
 
                self.source._eliminate_revisions_not_present(required_revisions))
3648
 
        return self.source.revision_ids_to_search_result(result_set)
3649
 
 
3650
 
 
3651
 
class InterKnitRepo(InterSameDataRepository):
3652
 
    """Optimised code paths between Knit based repositories."""
3653
 
 
3654
 
    @classmethod
3655
 
    def _get_repo_format_to_test(self):
3656
 
        from bzrlib.repofmt import knitrepo
3657
 
        return knitrepo.RepositoryFormatKnit1()
3658
 
 
3659
 
    @staticmethod
3660
 
    def is_compatible(source, target):
3661
 
        """Be compatible with known Knit formats.
3662
 
 
3663
 
        We don't test for the stores being of specific types because that
3664
 
        could lead to confusing results, and there is no need to be
3665
 
        overly general.
3666
 
        """
3667
 
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit
3668
 
        try:
3669
 
            are_knits = (isinstance(source._format, RepositoryFormatKnit) and
3670
 
                isinstance(target._format, RepositoryFormatKnit))
3671
 
        except AttributeError:
3672
 
            return False
3673
 
        return are_knits and InterRepository._same_model(source, target)
3674
 
 
3675
 
    @needs_read_lock
3676
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3677
 
        """See InterRepository.missing_revision_ids()."""
3678
 
        if revision_id is not None:
3679
 
            source_ids = self.source.get_ancestry(revision_id)
3680
 
            if source_ids[0] is not None:
3681
 
                raise AssertionError()
3682
 
            source_ids.pop(0)
3683
 
        else:
3684
 
            source_ids = self.source.all_revision_ids()
3685
 
        source_ids_set = set(source_ids)
3686
 
        # source_ids is the worst possible case we may need to pull.
3687
 
        # now we want to filter source_ids against what we actually
3688
 
        # have in target, but don't try to check for existence where we know
3689
 
        # we do not have a revision as that would be pointless.
3690
 
        target_ids = set(self.target.all_revision_ids())
3691
 
        possibly_present_revisions = target_ids.intersection(source_ids_set)
3692
 
        actually_present_revisions = set(
3693
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
3694
 
        required_revisions = source_ids_set.difference(actually_present_revisions)
3695
 
        if revision_id is not None:
3696
 
            # we used get_ancestry to determine source_ids then we are assured all
3697
 
            # revisions referenced are present as they are installed in topological order.
3698
 
            # and the tip revision was validated by get_ancestry.
3699
 
            result_set = required_revisions
3700
 
        else:
3701
 
            # if we just grabbed the possibly available ids, then
3702
 
            # we only have an estimate of whats available and need to validate
3703
 
            # that against the revision records.
3704
 
            result_set = set(
3705
 
                self.source._eliminate_revisions_not_present(required_revisions))
3706
 
        return self.source.revision_ids_to_search_result(result_set)
3707
 
 
3708
 
 
3709
3541
class InterDifferingSerializer(InterRepository):
3710
3542
 
3711
3543
    @classmethod
3813
3645
                basis_id, delta, current_revision_id, parents_parents)
3814
3646
            cache[current_revision_id] = parent_tree
3815
3647
 
3816
 
    def _fetch_batch(self, revision_ids, basis_id, cache, a_graph=None):
 
3648
    def _fetch_batch(self, revision_ids, basis_id, cache):
3817
3649
        """Fetch across a few revisions.
3818
3650
 
3819
3651
        :param revision_ids: The revisions to copy
3820
3652
        :param basis_id: The revision_id of a tree that must be in cache, used
3821
3653
            as a basis for delta when no other base is available
3822
3654
        :param cache: A cache of RevisionTrees that we can use.
3823
 
        :param a_graph: A Graph object to determine the heads() of the
3824
 
            rich-root data stream.
3825
3655
        :return: The revision_id of the last converted tree. The RevisionTree
3826
3656
            for it will be in cache
3827
3657
        """
3895
3725
        if root_keys_to_create:
3896
3726
            root_stream = _mod_fetch._new_root_data_stream(
3897
3727
                root_keys_to_create, self._revision_id_to_root_id, parent_map,
3898
 
                self.source, graph=a_graph)
 
3728
                self.source)
3899
3729
            to_texts.insert_record_stream(root_stream)
3900
3730
        to_texts.insert_record_stream(from_texts.get_record_stream(
3901
3731
            text_keys, self.target._format._fetch_order,
3958
3788
        cache[basis_id] = basis_tree
3959
3789
        del basis_tree # We don't want to hang on to it here
3960
3790
        hints = []
3961
 
        if self._converting_to_rich_root and len(revision_ids) > 100:
3962
 
            a_graph = _mod_fetch._get_rich_root_heads_graph(self.source,
3963
 
                                                            revision_ids)
3964
 
        else:
3965
 
            a_graph = None
 
3791
        a_graph = None
3966
3792
 
3967
3793
        for offset in range(0, len(revision_ids), batch_size):
3968
3794
            self.target.start_write_group()
3970
3796
                pb.update('Transferring revisions', offset,
3971
3797
                          len(revision_ids))
3972
3798
                batch = revision_ids[offset:offset+batch_size]
3973
 
                basis_id = self._fetch_batch(batch, basis_id, cache,
3974
 
                                             a_graph=a_graph)
 
3799
                basis_id = self._fetch_batch(batch, basis_id, cache)
3975
3800
            except:
3976
3801
                self.source._safe_to_return_from_cache = False
3977
3802
                self.target.abort_write_group()
4043
3868
            basis_id = first_rev.parent_ids[0]
4044
3869
            # only valid as a basis if the target has it
4045
3870
            self.target.get_revision(basis_id)
4046
 
            # Try to get a basis tree - if its a ghost it will hit the
 
3871
            # Try to get a basis tree - if it's a ghost it will hit the
4047
3872
            # NoSuchRevision case.
4048
3873
            basis_tree = self.source.revision_tree(basis_id)
4049
3874
        except (IndexError, errors.NoSuchRevision):
4054
3879
 
4055
3880
InterRepository.register_optimiser(InterDifferingSerializer)
4056
3881
InterRepository.register_optimiser(InterSameDataRepository)
4057
 
InterRepository.register_optimiser(InterWeaveRepo)
4058
 
InterRepository.register_optimiser(InterKnitRepo)
4059
3882
 
4060
3883
 
4061
3884
class CopyConverter(object):
4249
4072
                is_resume = False
4250
4073
            try:
4251
4074
                # locked_insert_stream performs a commit|suspend.
4252
 
                return self._locked_insert_stream(stream, src_format, is_resume)
 
4075
                missing_keys = self.insert_stream_without_locking(stream,
 
4076
                                    src_format, is_resume)
 
4077
                if missing_keys:
 
4078
                    # suspend the write group and tell the caller what we is
 
4079
                    # missing. We know we can suspend or else we would not have
 
4080
                    # entered this code path. (All repositories that can handle
 
4081
                    # missing keys can handle suspending a write group).
 
4082
                    write_group_tokens = self.target_repo.suspend_write_group()
 
4083
                    return write_group_tokens, missing_keys
 
4084
                hint = self.target_repo.commit_write_group()
 
4085
                to_serializer = self.target_repo._format._serializer
 
4086
                src_serializer = src_format._serializer
 
4087
                if (to_serializer != src_serializer and
 
4088
                    self.target_repo._format.pack_compresses):
 
4089
                    self.target_repo.pack(hint=hint)
 
4090
                return [], set()
4253
4091
            except:
4254
4092
                self.target_repo.abort_write_group(suppress_errors=True)
4255
4093
                raise
4256
4094
        finally:
4257
4095
            self.target_repo.unlock()
4258
4096
 
4259
 
    def _locked_insert_stream(self, stream, src_format, is_resume):
 
4097
    def insert_stream_without_locking(self, stream, src_format,
 
4098
                                      is_resume=False):
 
4099
        """Insert a stream's content into the target repository.
 
4100
 
 
4101
        This assumes that you already have a locked repository and an active
 
4102
        write group.
 
4103
 
 
4104
        :param src_format: a bzr repository format.
 
4105
        :param is_resume: Passed down to get_missing_parent_inventories to
 
4106
            indicate if we should be checking for missing texts at the same
 
4107
            time.
 
4108
 
 
4109
        :return: A set of keys that are missing.
 
4110
        """
 
4111
        if not self.target_repo.is_write_locked():
 
4112
            raise errors.ObjectNotLocked(self)
 
4113
        if not self.target_repo.is_in_write_group():
 
4114
            raise errors.BzrError('you must already be in a write group')
4260
4115
        to_serializer = self.target_repo._format._serializer
4261
4116
        src_serializer = src_format._serializer
4262
4117
        new_pack = None
4302
4157
                # required if the serializers are different only in terms of
4303
4158
                # the inventory.
4304
4159
                if src_serializer == to_serializer:
4305
 
                    self.target_repo.revisions.insert_record_stream(
4306
 
                        substream)
 
4160
                    self.target_repo.revisions.insert_record_stream(substream)
4307
4161
                else:
4308
4162
                    self._extract_and_insert_revisions(substream,
4309
4163
                        src_serializer)
4342
4196
            # cannot even attempt suspending, and missing would have failed
4343
4197
            # during stream insertion.
4344
4198
            missing_keys = set()
4345
 
        else:
4346
 
            if missing_keys:
4347
 
                # suspend the write group and tell the caller what we is
4348
 
                # missing. We know we can suspend or else we would not have
4349
 
                # entered this code path. (All repositories that can handle
4350
 
                # missing keys can handle suspending a write group).
4351
 
                write_group_tokens = self.target_repo.suspend_write_group()
4352
 
                return write_group_tokens, missing_keys
4353
 
        hint = self.target_repo.commit_write_group()
4354
 
        if (to_serializer != src_serializer and
4355
 
            self.target_repo._format.pack_compresses):
4356
 
            self.target_repo.pack(hint=hint)
4357
 
        return [], set()
 
4199
        return missing_keys
4358
4200
 
4359
4201
    def _extract_and_insert_inventory_deltas(self, substream, serializer):
4360
4202
        target_rich_root = self.target_repo._format.rich_root_data
4417
4259
        """Create a StreamSource streaming from from_repository."""
4418
4260
        self.from_repository = from_repository
4419
4261
        self.to_format = to_format
 
4262
        self._record_counter = RecordCounter()
4420
4263
 
4421
4264
    def delta_on_metadata(self):
4422
4265
        """Return True if delta's are permitted on metadata streams.