~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Jelmer Vernooij
  • Date: 2011-02-20 00:53:24 UTC
  • mto: This revision was merged to the branch mainline in revision 5673.
  • Revision ID: jelmer@samba.org-20110220005324-xnb1ifis5bfkrl51
Lazy load gzip (we don't use it when doing 2a), remove some unused imports.

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
 
    symbol_versioning,
45
45
    trace,
46
46
    tsort,
47
 
    ui,
48
47
    versionedfile,
49
48
    )
50
49
from bzrlib.bundle import serializer
53
52
from bzrlib.testament import Testament
54
53
""")
55
54
 
 
55
from bzrlib import (
 
56
    errors,
 
57
    registry,
 
58
    symbol_versioning,
 
59
    ui,
 
60
    )
56
61
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
57
62
from bzrlib.inter import InterObject
58
63
from bzrlib.inventory import (
61
66
    ROOT_ID,
62
67
    entry_factory,
63
68
    )
64
 
from bzrlib.lock import _RelockDebugMixin
65
 
from bzrlib import registry
 
69
from bzrlib.recordcounter import RecordCounter
 
70
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
66
71
from bzrlib.trace import (
67
72
    log_exception_quietly, note, mutter, mutter_callsite, warning)
68
73
 
71
76
_deprecation_warning_done = False
72
77
 
73
78
 
 
79
class IsInWriteGroupError(errors.InternalBzrError):
 
80
 
 
81
    _fmt = "May not refresh_data of repo %(repo)s while in a write group."
 
82
 
 
83
    def __init__(self, repo):
 
84
        errors.InternalBzrError.__init__(self, repo=repo)
 
85
 
 
86
 
74
87
class CommitBuilder(object):
75
88
    """Provides an interface to build up a commit.
76
89
 
101
114
 
102
115
        if committer is None:
103
116
            self._committer = self._config.username()
 
117
        elif not isinstance(committer, unicode):
 
118
            self._committer = committer.decode() # throw if non-ascii
104
119
        else:
105
120
            self._committer = committer
106
121
 
160
175
            self._validate_unicode_text(value,
161
176
                                        'revision property (%s)' % (key,))
162
177
 
 
178
    def _ensure_fallback_inventories(self):
 
179
        """Ensure that appropriate inventories are available.
 
180
 
 
181
        This only applies to repositories that are stacked, and is about
 
182
        enusring the stacking invariants. Namely, that for any revision that is
 
183
        present, we either have all of the file content, or we have the parent
 
184
        inventory and the delta file content.
 
185
        """
 
186
        if not self.repository._fallback_repositories:
 
187
            return
 
188
        if not self.repository._format.supports_chks:
 
189
            raise errors.BzrError("Cannot commit directly to a stacked branch"
 
190
                " in pre-2a formats. See "
 
191
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
 
192
        # This is a stacked repo, we need to make sure we have the parent
 
193
        # inventories for the parents.
 
194
        parent_keys = [(p,) for p in self.parents]
 
195
        parent_map = self.repository.inventories._index.get_parent_map(parent_keys)
 
196
        missing_parent_keys = set([pk for pk in parent_keys
 
197
                                       if pk not in parent_map])
 
198
        fallback_repos = list(reversed(self.repository._fallback_repositories))
 
199
        missing_keys = [('inventories', pk[0])
 
200
                        for pk in missing_parent_keys]
 
201
        resume_tokens = []
 
202
        while missing_keys and fallback_repos:
 
203
            fallback_repo = fallback_repos.pop()
 
204
            source = fallback_repo._get_source(self.repository._format)
 
205
            sink = self.repository._get_sink()
 
206
            stream = source.get_stream_for_missing_keys(missing_keys)
 
207
            missing_keys = sink.insert_stream_without_locking(stream,
 
208
                self.repository._format)
 
209
        if missing_keys:
 
210
            raise errors.BzrError('Unable to fill in parent inventories for a'
 
211
                                  ' stacked branch')
 
212
 
163
213
    def commit(self, message):
164
214
        """Make the actual commit.
165
215
 
177
227
        rev.parent_ids = self.parents
178
228
        self.repository.add_revision(self._new_revision_id, rev,
179
229
            self.new_inventory, self._config)
 
230
        self._ensure_fallback_inventories()
180
231
        self.repository.commit_write_group()
181
232
        return self._new_revision_id
182
233
 
231
282
 
232
283
    def _gen_revision_id(self):
233
284
        """Return new revision-id."""
234
 
        return generate_ids.gen_revision_id(self._config.username(),
235
 
                                            self._timestamp)
 
285
        return generate_ids.gen_revision_id(self._committer, self._timestamp)
236
286
 
237
287
    def _generate_revision_if_needed(self):
238
288
        """Create a revision id if None was supplied.
278
328
 
279
329
        :param tree: The tree which is being committed.
280
330
        """
281
 
        # NB: if there are no parents then this method is not called, so no
282
 
        # need to guard on parents having length.
 
331
        if len(self.parents) == 0:
 
332
            raise errors.RootMissing()
283
333
        entry = entry_factory['directory'](tree.path2id(''), '',
284
334
            None)
285
335
        entry.revision = self._new_revision_id
423
473
            else:
424
474
                # we don't need to commit this, because the caller already
425
475
                # determined that an existing revision of this file is
426
 
                # appropriate. If its not being considered for committing then
 
476
                # appropriate. If it's not being considered for committing then
427
477
                # it and all its parents to the root must be unaltered so
428
478
                # no-change against the basis.
429
479
                if ie.revision == self._new_revision_id:
745
795
                    # after iter_changes examines and decides it has changed,
746
796
                    # we will unconditionally record a new version even if some
747
797
                    # other process reverts it while commit is running (with
748
 
                    # the revert happening after iter_changes did it's
 
798
                    # the revert happening after iter_changes did its
749
799
                    # examination).
750
800
                    if change[7][1]:
751
801
                        entry.executable = True
860
910
        # versioned roots do not change unless the tree found a change.
861
911
 
862
912
 
863
 
class RepositoryWriteLockResult(object):
 
913
class RepositoryWriteLockResult(LogicalLockResult):
864
914
    """The result of write locking a repository.
865
915
 
866
916
    :ivar repository_token: The token obtained from the underlying lock, or
869
919
    """
870
920
 
871
921
    def __init__(self, unlock, repository_token):
 
922
        LogicalLockResult.__init__(self, unlock)
872
923
        self.repository_token = repository_token
873
 
        self.unlock = unlock
874
924
 
875
 
    def __str__(self):
 
925
    def __repr__(self):
876
926
        return "RepositoryWriteLockResult(%s, %s)" % (self.repository_token,
877
927
            self.unlock)
878
928
 
881
931
# Repositories
882
932
 
883
933
 
884
 
class Repository(_RelockDebugMixin, bzrdir.ControlComponent):
 
934
class Repository(_RelockDebugMixin, controldir.ControlComponent):
885
935
    """Repository holding history for one or more branches.
886
936
 
887
937
    The repository holds and retrieves historical information including
934
984
        pointing to .bzr/repository.
935
985
    """
936
986
 
937
 
    # What class to use for a CommitBuilder. Often its simpler to change this
 
987
    # What class to use for a CommitBuilder. Often it's simpler to change this
938
988
    # in a Repository class subclass rather than to override
939
989
    # get_commit_builder.
940
990
    _commit_builder_class = CommitBuilder
1035
1085
                " id and insertion revid (%r, %r)"
1036
1086
                % (inv.revision_id, revision_id))
1037
1087
        if inv.root is None:
1038
 
            raise AssertionError()
 
1088
            raise errors.RootMissing()
1039
1089
        return self._add_inventory_checked(revision_id, inv, parents)
1040
1090
 
1041
1091
    def _add_inventory_checked(self, revision_id, inv, parents):
1434
1484
            for repo in self._fallback_repositories:
1435
1485
                repo.lock_read()
1436
1486
            self._refresh_data()
1437
 
        return self
 
1487
        return LogicalLockResult(self.unlock)
1438
1488
 
1439
1489
    def get_physical_lock_status(self):
1440
1490
        return self.control_files.get_physical_lock_status()
1546
1596
        return ret
1547
1597
 
1548
1598
    @needs_read_lock
1549
 
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
 
1599
    def search_missing_revision_ids(self, other,
 
1600
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
 
1601
            find_ghosts=True, revision_ids=None, if_present_ids=None):
1550
1602
        """Return the revision ids that other has that this does not.
1551
1603
 
1552
1604
        These are returned in topological order.
1553
1605
 
1554
1606
        revision_id: only return revision ids included by revision_id.
1555
1607
        """
 
1608
        if symbol_versioning.deprecated_passed(revision_id):
 
1609
            symbol_versioning.warn(
 
1610
                'search_missing_revision_ids(revision_id=...) was '
 
1611
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
 
1612
                DeprecationWarning, stacklevel=3)
 
1613
            if revision_ids is not None:
 
1614
                raise AssertionError(
 
1615
                    'revision_ids is mutually exclusive with revision_id')
 
1616
            if revision_id is not None:
 
1617
                revision_ids = [revision_id]
1556
1618
        return InterRepository.get(other, self).search_missing_revision_ids(
1557
 
            revision_id, find_ghosts)
 
1619
            find_ghosts=find_ghosts, revision_ids=revision_ids,
 
1620
            if_present_ids=if_present_ids)
1558
1621
 
1559
1622
    @staticmethod
1560
1623
    def open(base):
1658
1721
        return missing_keys
1659
1722
 
1660
1723
    def refresh_data(self):
1661
 
        """Re-read any data needed to to synchronise with disk.
 
1724
        """Re-read any data needed to synchronise with disk.
1662
1725
 
1663
1726
        This method is intended to be called after another repository instance
1664
1727
        (such as one used by a smart server) has inserted data into the
1665
 
        repository. It may not be called during a write group, but may be
1666
 
        called at any other time.
 
1728
        repository. On all repositories this will work outside of write groups.
 
1729
        Some repository formats (pack and newer for bzrlib native formats)
 
1730
        support refresh_data inside write groups. If called inside a write
 
1731
        group on a repository that does not support refreshing in a write group
 
1732
        IsInWriteGroupError will be raised.
1667
1733
        """
1668
 
        if self.is_in_write_group():
1669
 
            raise errors.InternalBzrError(
1670
 
                "May not refresh_data while in a write group.")
1671
1734
        self._refresh_data()
1672
1735
 
1673
1736
    def resume_write_group(self, tokens):
1712
1775
                "May not fetch while in a write group.")
1713
1776
        # fast path same-url fetch operations
1714
1777
        # TODO: lift out to somewhere common with RemoteRepository
1715
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/401646>
 
1778
        # <https://bugs.launchpad.net/bzr/+bug/401646>
1716
1779
        if (self.has_same_location(source)
1717
1780
            and fetch_spec is None
1718
1781
            and self._has_same_fallbacks(source)):
1722
1785
                not _mod_revision.is_null(revision_id)):
1723
1786
                self.get_revision(revision_id)
1724
1787
            return 0, []
1725
 
        # if there is no specific appropriate InterRepository, this will get
1726
 
        # the InterRepository base class, which raises an
1727
 
        # IncompatibleRepositories when asked to fetch.
1728
1788
        inter = InterRepository.get(source, self)
1729
1789
        return inter.fetch(revision_id=revision_id, pb=pb,
1730
1790
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1746
1806
        :param revprops: Optional dictionary of revision properties.
1747
1807
        :param revision_id: Optional revision id.
1748
1808
        """
1749
 
        if self._fallback_repositories:
1750
 
            raise errors.BzrError("Cannot commit from a lightweight checkout "
1751
 
                "to a stacked branch. See "
 
1809
        if self._fallback_repositories and not self._format.supports_chks:
 
1810
            raise errors.BzrError("Cannot commit directly to a stacked branch"
 
1811
                " in pre-2a formats. See "
1752
1812
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1753
1813
        result = self._commit_builder_class(self, parents, config,
1754
1814
            timestamp, timezone, committer, revprops, revision_id)
2500
2560
            ancestors will be traversed.
2501
2561
        """
2502
2562
        graph = self.get_graph()
2503
 
        next_id = revision_id
2504
 
        while True:
2505
 
            if next_id in (None, _mod_revision.NULL_REVISION):
2506
 
                return
2507
 
            try:
2508
 
                parents = graph.get_parent_map([next_id])[next_id]
2509
 
            except KeyError:
2510
 
                raise errors.RevisionNotPresent(next_id, self)
2511
 
            yield next_id
2512
 
            if len(parents) == 0:
2513
 
                return
2514
 
            else:
2515
 
                next_id = parents[0]
 
2563
        stop_revisions = (None, _mod_revision.NULL_REVISION)
 
2564
        return graph.iter_lefthand_ancestry(revision_id, stop_revisions)
2516
2565
 
2517
2566
    def is_shared(self):
2518
2567
        """Return True if this repository is flagged as a shared repository."""
2619
2668
        types it should be a no-op that just returns.
2620
2669
 
2621
2670
        This stub method does not require a lock, but subclasses should use
2622
 
        @needs_write_lock as this is a long running call its reasonable to
 
2671
        @needs_write_lock as this is a long running call it's reasonable to
2623
2672
        implicitly lock for the user.
2624
2673
 
2625
2674
        :param hint: If not supplied, the whole repository is packed.
2818
2867
        raise NotImplementedError(self.revision_graph_can_have_wrong_parents)
2819
2868
 
2820
2869
 
2821
 
# remove these delegates a while after bzr 0.15
2822
 
def __make_delegated(name, from_module):
2823
 
    def _deprecated_repository_forwarder():
2824
 
        symbol_versioning.warn('%s moved to %s in bzr 0.15'
2825
 
            % (name, from_module),
2826
 
            DeprecationWarning,
2827
 
            stacklevel=2)
2828
 
        m = __import__(from_module, globals(), locals(), [name])
2829
 
        try:
2830
 
            return getattr(m, name)
2831
 
        except AttributeError:
2832
 
            raise AttributeError('module %s has no name %s'
2833
 
                    % (m, name))
2834
 
    globals()[name] = _deprecated_repository_forwarder
2835
 
 
2836
 
for _name in [
2837
 
        'AllInOneRepository',
2838
 
        'WeaveMetaDirRepository',
2839
 
        'PreSplitOutRepositoryFormat',
2840
 
        'RepositoryFormat4',
2841
 
        'RepositoryFormat5',
2842
 
        'RepositoryFormat6',
2843
 
        'RepositoryFormat7',
2844
 
        ]:
2845
 
    __make_delegated(_name, 'bzrlib.repofmt.weaverepo')
2846
 
 
2847
 
for _name in [
2848
 
        'KnitRepository',
2849
 
        'RepositoryFormatKnit',
2850
 
        'RepositoryFormatKnit1',
2851
 
        ]:
2852
 
    __make_delegated(_name, 'bzrlib.repofmt.knitrepo')
2853
 
 
2854
 
 
2855
2870
def install_revision(repository, rev, revision_tree):
2856
2871
    """Install all revision data into a repository."""
2857
2872
    install_revisions(repository, [(rev, revision_tree, None)])
2989
3004
            control_files)
2990
3005
 
2991
3006
 
 
3007
class RepositoryFormatRegistry(registry.FormatRegistry):
 
3008
    """Repository format registry."""
 
3009
 
 
3010
    def __init__(self, other_registry=None):
 
3011
        super(RepositoryFormatRegistry, self).__init__(other_registry)
 
3012
        self._extra_formats = []
 
3013
 
 
3014
    def register(self, format):
 
3015
        """Register a new repository format."""
 
3016
        super(RepositoryFormatRegistry, self).register(
 
3017
            format.get_format_string(), format)
 
3018
 
 
3019
    def remove(self, format):
 
3020
        """Remove a registered repository format."""
 
3021
        super(RepositoryFormatRegistry, self).remove(
 
3022
            format.get_format_string())
 
3023
 
 
3024
    def register_extra(self, format):
 
3025
        """Register a repository format that can not be used in a metadir.
 
3026
 
 
3027
        This is mainly useful to allow custom repository formats, such as older
 
3028
        Bazaar formats and foreign formats, to be tested.
 
3029
        """
 
3030
        self._extra_formats.append(registry._ObjectGetter(format))
 
3031
 
 
3032
    def remove_extra(self, format):
 
3033
        """Remove an extra repository format.
 
3034
        """
 
3035
        self._extra_formats.remove(registry._ObjectGetter(format))
 
3036
 
 
3037
    def register_extra_lazy(self, module_name, member_name):
 
3038
        """Register a repository format lazily.
 
3039
        """
 
3040
        self._extra_formats.append(
 
3041
            registry._LazyObjectGetter(module_name, member_name))
 
3042
 
 
3043
    def get_default(self):
 
3044
        """Return the current default format."""
 
3045
        from bzrlib import bzrdir
 
3046
        return bzrdir.format_registry.make_bzrdir('default').repository_format
 
3047
 
 
3048
    def _get_extra(self):
 
3049
        result = []
 
3050
        for getter in self._extra_formats:
 
3051
            f = getter.get_obj()
 
3052
            if callable(f):
 
3053
                f = f()
 
3054
            result.append(f)
 
3055
        return result
 
3056
 
 
3057
    def _get_all(self):
 
3058
        """Return all repository formats, even those not usable in metadirs.
 
3059
        """
 
3060
        return [self.get(k) for k in self.keys()] + self._get_extra()
 
3061
 
 
3062
 
2992
3063
network_format_registry = registry.FormatRegistry()
2993
3064
"""Registry of formats indexed by their network name.
2994
3065
 
2998
3069
"""
2999
3070
 
3000
3071
 
3001
 
format_registry = registry.FormatRegistry(network_format_registry)
 
3072
format_registry = RepositoryFormatRegistry(network_format_registry)
3002
3073
"""Registry of formats, indexed by their BzrDirMetaFormat format string.
3003
3074
 
3004
3075
This can contain either format instances themselves, or classes/factories that
3076
3147
    supports_tree_reference = None
3077
3148
    # Is the format experimental ?
3078
3149
    experimental = False
 
3150
    # Does this repository format escape funky characters, or does it create files with
 
3151
    # similar names as the versioned files in its contents on disk ?
 
3152
    supports_funky_characters = True
3079
3153
 
3080
3154
    def __repr__(self):
3081
3155
        return "%s()" % self.__class__.__name__
3106
3180
                                            kind='repository')
3107
3181
 
3108
3182
    @classmethod
 
3183
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3109
3184
    def register_format(klass, format):
3110
 
        format_registry.register(format.get_format_string(), format)
 
3185
        format_registry.register(format)
3111
3186
 
3112
3187
    @classmethod
 
3188
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3113
3189
    def unregister_format(klass, format):
3114
 
        format_registry.remove(format.get_format_string())
 
3190
        format_registry.remove(format)
3115
3191
 
3116
3192
    @classmethod
 
3193
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3117
3194
    def get_default_format(klass):
3118
3195
        """Return the current default format."""
3119
 
        from bzrlib import bzrdir
3120
 
        return bzrdir.format_registry.make_bzrdir('default').repository_format
 
3196
        return format_registry.get_default()
3121
3197
 
3122
3198
    def get_format_string(self):
3123
3199
        """Return the ASCII format string that identifies this format.
3276
3352
    'RepositoryFormat6',
3277
3353
)
3278
3354
 
 
3355
format_registry.register_extra_lazy(
 
3356
    'bzrlib.repofmt.weaverepo',
 
3357
    'RepositoryFormat4')
 
3358
format_registry.register_extra_lazy(
 
3359
    'bzrlib.repofmt.weaverepo',
 
3360
    'RepositoryFormat5')
 
3361
format_registry.register_extra_lazy(
 
3362
    'bzrlib.repofmt.weaverepo',
 
3363
    'RepositoryFormat6')
 
3364
 
3279
3365
# formats which have no format string are not discoverable or independently
3280
3366
# creatable on disk, so are not registered in format_registry.  They're
3281
3367
# all in bzrlib.repofmt.weaverepo now.  When an instance of one of these is
3349
3435
    'bzrlib.repofmt.pack_repo',
3350
3436
    'RepositoryFormatKnitPack6RichRoot',
3351
3437
    )
 
3438
format_registry.register_lazy(
 
3439
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
 
3440
    'bzrlib.repofmt.groupcompress_repo',
 
3441
    'RepositoryFormat2a',
 
3442
    )
3352
3443
 
3353
3444
# Development formats.
3354
 
# Obsolete but kept pending a CHK based subtree format.
 
3445
# Check their docstrings to see if/when they are obsolete.
3355
3446
format_registry.register_lazy(
3356
3447
    ("Bazaar development format 2 with subtree support "
3357
3448
        "(needs bzr.dev from before 1.8)\n"),
3358
3449
    'bzrlib.repofmt.pack_repo',
3359
3450
    'RepositoryFormatPackDevelopment2Subtree',
3360
3451
    )
3361
 
 
3362
 
# 1.14->1.16 go below here
3363
 
format_registry.register_lazy(
3364
 
    'Bazaar development format - group compression and chk inventory'
3365
 
        ' (needs bzr.dev from 1.14)\n',
3366
 
    'bzrlib.repofmt.groupcompress_repo',
3367
 
    'RepositoryFormatCHK1',
3368
 
    )
3369
 
 
3370
 
format_registry.register_lazy(
3371
 
    'Bazaar development format - chk repository with bencode revision '
3372
 
        'serialization (needs bzr.dev from 1.16)\n',
3373
 
    'bzrlib.repofmt.groupcompress_repo',
3374
 
    'RepositoryFormatCHK2',
3375
 
    )
3376
 
format_registry.register_lazy(
3377
 
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
3378
 
    'bzrlib.repofmt.groupcompress_repo',
3379
 
    'RepositoryFormat2a',
 
3452
format_registry.register_lazy(
 
3453
    'Bazaar development format 8\n',
 
3454
    'bzrlib.repofmt.groupcompress_repo',
 
3455
    'RepositoryFormat2aSubtree',
3380
3456
    )
3381
3457
 
3382
3458
 
3437
3513
                               fetch_spec=fetch_spec,
3438
3514
                               find_ghosts=find_ghosts)
3439
3515
 
3440
 
    def _walk_to_common_revisions(self, revision_ids):
 
3516
    def _walk_to_common_revisions(self, revision_ids, if_present_ids=None):
3441
3517
        """Walk out from revision_ids in source to revisions target has.
3442
3518
 
3443
3519
        :param revision_ids: The start point for the search.
3445
3521
        """
3446
3522
        target_graph = self.target.get_graph()
3447
3523
        revision_ids = frozenset(revision_ids)
 
3524
        if if_present_ids:
 
3525
            all_wanted_revs = revision_ids.union(if_present_ids)
 
3526
        else:
 
3527
            all_wanted_revs = revision_ids
3448
3528
        missing_revs = set()
3449
3529
        source_graph = self.source.get_graph()
3450
3530
        # ensure we don't pay silly lookup costs.
3451
 
        searcher = source_graph._make_breadth_first_searcher(revision_ids)
 
3531
        searcher = source_graph._make_breadth_first_searcher(all_wanted_revs)
3452
3532
        null_set = frozenset([_mod_revision.NULL_REVISION])
3453
3533
        searcher_exhausted = False
3454
3534
        while True:
3490
3570
        return searcher.get_result()
3491
3571
 
3492
3572
    @needs_read_lock
3493
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
3573
    def search_missing_revision_ids(self,
 
3574
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
 
3575
            find_ghosts=True, revision_ids=None, if_present_ids=None):
3494
3576
        """Return the revision ids that source has that target does not.
3495
3577
 
3496
3578
        :param revision_id: only return revision ids included by this
3497
 
                            revision_id.
 
3579
            revision_id.
 
3580
        :param revision_ids: return revision ids included by these
 
3581
            revision_ids.  NoSuchRevision will be raised if any of these
 
3582
            revisions are not present.
 
3583
        :param if_present_ids: like revision_ids, but will not cause
 
3584
            NoSuchRevision if any of these are absent, instead they will simply
 
3585
            not be in the result.  This is useful for e.g. finding revisions
 
3586
            to fetch for tags, which may reference absent revisions.
3498
3587
        :param find_ghosts: If True find missing revisions in deep history
3499
3588
            rather than just finding the surface difference.
3500
3589
        :return: A bzrlib.graph.SearchResult.
3501
3590
        """
 
3591
        if symbol_versioning.deprecated_passed(revision_id):
 
3592
            symbol_versioning.warn(
 
3593
                'search_missing_revision_ids(revision_id=...) was '
 
3594
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
 
3595
                DeprecationWarning, stacklevel=2)
 
3596
            if revision_ids is not None:
 
3597
                raise AssertionError(
 
3598
                    'revision_ids is mutually exclusive with revision_id')
 
3599
            if revision_id is not None:
 
3600
                revision_ids = [revision_id]
 
3601
        del revision_id
3502
3602
        # stop searching at found target revisions.
3503
 
        if not find_ghosts and revision_id is not None:
3504
 
            return self._walk_to_common_revisions([revision_id])
 
3603
        if not find_ghosts and (revision_ids is not None or if_present_ids is
 
3604
                not None):
 
3605
            return self._walk_to_common_revisions(revision_ids,
 
3606
                    if_present_ids=if_present_ids)
3505
3607
        # generic, possibly worst case, slow code path.
3506
3608
        target_ids = set(self.target.all_revision_ids())
3507
 
        if revision_id is not None:
3508
 
            source_ids = self.source.get_ancestry(revision_id)
3509
 
            if source_ids[0] is not None:
3510
 
                raise AssertionError()
3511
 
            source_ids.pop(0)
3512
 
        else:
3513
 
            source_ids = self.source.all_revision_ids()
 
3609
        source_ids = self._present_source_revisions_for(
 
3610
            revision_ids, if_present_ids)
3514
3611
        result_set = set(source_ids).difference(target_ids)
3515
3612
        return self.source.revision_ids_to_search_result(result_set)
3516
3613
 
 
3614
    def _present_source_revisions_for(self, revision_ids, if_present_ids=None):
 
3615
        """Returns set of all revisions in ancestry of revision_ids present in
 
3616
        the source repo.
 
3617
 
 
3618
        :param revision_ids: if None, all revisions in source are returned.
 
3619
        :param if_present_ids: like revision_ids, but if any/all of these are
 
3620
            absent no error is raised.
 
3621
        """
 
3622
        if revision_ids is not None or if_present_ids is not None:
 
3623
            # First, ensure all specified revisions exist.  Callers expect
 
3624
            # NoSuchRevision when they pass absent revision_ids here.
 
3625
            if revision_ids is None:
 
3626
                revision_ids = set()
 
3627
            if if_present_ids is None:
 
3628
                if_present_ids = set()
 
3629
            revision_ids = set(revision_ids)
 
3630
            if_present_ids = set(if_present_ids)
 
3631
            all_wanted_ids = revision_ids.union(if_present_ids)
 
3632
            graph = self.source.get_graph()
 
3633
            present_revs = set(graph.get_parent_map(all_wanted_ids))
 
3634
            missing = revision_ids.difference(present_revs)
 
3635
            if missing:
 
3636
                raise errors.NoSuchRevision(self.source, missing.pop())
 
3637
            found_ids = all_wanted_ids.intersection(present_revs)
 
3638
            source_ids = [rev_id for (rev_id, parents) in
 
3639
                          graph.iter_ancestry(found_ids)
 
3640
                          if rev_id != _mod_revision.NULL_REVISION
 
3641
                          and parents is not None]
 
3642
        else:
 
3643
            source_ids = self.source.all_revision_ids()
 
3644
        return set(source_ids)
 
3645
 
3517
3646
    @staticmethod
3518
3647
    def _same_model(source, target):
3519
3648
        """True if source and target have the same data representation.
3560
3689
        return InterRepository._same_model(source, target)
3561
3690
 
3562
3691
 
3563
 
class InterWeaveRepo(InterSameDataRepository):
3564
 
    """Optimised code paths between Weave based repositories.
3565
 
 
3566
 
    This should be in bzrlib/repofmt/weaverepo.py but we have not yet
3567
 
    implemented lazy inter-object optimisation.
3568
 
    """
3569
 
 
3570
 
    @classmethod
3571
 
    def _get_repo_format_to_test(self):
3572
 
        from bzrlib.repofmt import weaverepo
3573
 
        return weaverepo.RepositoryFormat7()
3574
 
 
3575
 
    @staticmethod
3576
 
    def is_compatible(source, target):
3577
 
        """Be compatible with known Weave formats.
3578
 
 
3579
 
        We don't test for the stores being of specific types because that
3580
 
        could lead to confusing results, and there is no need to be
3581
 
        overly general.
3582
 
        """
3583
 
        from bzrlib.repofmt.weaverepo import (
3584
 
                RepositoryFormat5,
3585
 
                RepositoryFormat6,
3586
 
                RepositoryFormat7,
3587
 
                )
3588
 
        try:
3589
 
            return (isinstance(source._format, (RepositoryFormat5,
3590
 
                                                RepositoryFormat6,
3591
 
                                                RepositoryFormat7)) and
3592
 
                    isinstance(target._format, (RepositoryFormat5,
3593
 
                                                RepositoryFormat6,
3594
 
                                                RepositoryFormat7)))
3595
 
        except AttributeError:
3596
 
            return False
3597
 
 
3598
 
    @needs_write_lock
3599
 
    def copy_content(self, revision_id=None):
3600
 
        """See InterRepository.copy_content()."""
3601
 
        # weave specific optimised path:
3602
 
        try:
3603
 
            self.target.set_make_working_trees(self.source.make_working_trees())
3604
 
        except (errors.RepositoryUpgradeRequired, NotImplemented):
3605
 
            pass
3606
 
        # FIXME do not peek!
3607
 
        if self.source._transport.listable():
3608
 
            pb = ui.ui_factory.nested_progress_bar()
3609
 
            try:
3610
 
                self.target.texts.insert_record_stream(
3611
 
                    self.source.texts.get_record_stream(
3612
 
                        self.source.texts.keys(), 'topological', False))
3613
 
                pb.update('Copying inventory', 0, 1)
3614
 
                self.target.inventories.insert_record_stream(
3615
 
                    self.source.inventories.get_record_stream(
3616
 
                        self.source.inventories.keys(), 'topological', False))
3617
 
                self.target.signatures.insert_record_stream(
3618
 
                    self.source.signatures.get_record_stream(
3619
 
                        self.source.signatures.keys(),
3620
 
                        'unordered', True))
3621
 
                self.target.revisions.insert_record_stream(
3622
 
                    self.source.revisions.get_record_stream(
3623
 
                        self.source.revisions.keys(),
3624
 
                        'topological', True))
3625
 
            finally:
3626
 
                pb.finished()
3627
 
        else:
3628
 
            self.target.fetch(self.source, revision_id=revision_id)
3629
 
 
3630
 
    @needs_read_lock
3631
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3632
 
        """See InterRepository.missing_revision_ids()."""
3633
 
        # we want all revisions to satisfy revision_id in source.
3634
 
        # but we don't want to stat every file here and there.
3635
 
        # we want then, all revisions other needs to satisfy revision_id
3636
 
        # checked, but not those that we have locally.
3637
 
        # so the first thing is to get a subset of the revisions to
3638
 
        # satisfy revision_id in source, and then eliminate those that
3639
 
        # we do already have.
3640
 
        # this is slow on high latency connection to self, but as this
3641
 
        # disk format scales terribly for push anyway due to rewriting
3642
 
        # inventory.weave, this is considered acceptable.
3643
 
        # - RBC 20060209
3644
 
        if revision_id is not None:
3645
 
            source_ids = self.source.get_ancestry(revision_id)
3646
 
            if source_ids[0] is not None:
3647
 
                raise AssertionError()
3648
 
            source_ids.pop(0)
3649
 
        else:
3650
 
            source_ids = self.source._all_possible_ids()
3651
 
        source_ids_set = set(source_ids)
3652
 
        # source_ids is the worst possible case we may need to pull.
3653
 
        # now we want to filter source_ids against what we actually
3654
 
        # have in target, but don't try to check for existence where we know
3655
 
        # we do not have a revision as that would be pointless.
3656
 
        target_ids = set(self.target._all_possible_ids())
3657
 
        possibly_present_revisions = target_ids.intersection(source_ids_set)
3658
 
        actually_present_revisions = set(
3659
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
3660
 
        required_revisions = source_ids_set.difference(actually_present_revisions)
3661
 
        if revision_id is not None:
3662
 
            # we used get_ancestry to determine source_ids then we are assured all
3663
 
            # revisions referenced are present as they are installed in topological order.
3664
 
            # and the tip revision was validated by get_ancestry.
3665
 
            result_set = required_revisions
3666
 
        else:
3667
 
            # if we just grabbed the possibly available ids, then
3668
 
            # we only have an estimate of whats available and need to validate
3669
 
            # that against the revision records.
3670
 
            result_set = set(
3671
 
                self.source._eliminate_revisions_not_present(required_revisions))
3672
 
        return self.source.revision_ids_to_search_result(result_set)
3673
 
 
3674
 
 
3675
 
class InterKnitRepo(InterSameDataRepository):
3676
 
    """Optimised code paths between Knit based repositories."""
3677
 
 
3678
 
    @classmethod
3679
 
    def _get_repo_format_to_test(self):
3680
 
        from bzrlib.repofmt import knitrepo
3681
 
        return knitrepo.RepositoryFormatKnit1()
3682
 
 
3683
 
    @staticmethod
3684
 
    def is_compatible(source, target):
3685
 
        """Be compatible with known Knit formats.
3686
 
 
3687
 
        We don't test for the stores being of specific types because that
3688
 
        could lead to confusing results, and there is no need to be
3689
 
        overly general.
3690
 
        """
3691
 
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit
3692
 
        try:
3693
 
            are_knits = (isinstance(source._format, RepositoryFormatKnit) and
3694
 
                isinstance(target._format, RepositoryFormatKnit))
3695
 
        except AttributeError:
3696
 
            return False
3697
 
        return are_knits and InterRepository._same_model(source, target)
3698
 
 
3699
 
    @needs_read_lock
3700
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3701
 
        """See InterRepository.missing_revision_ids()."""
3702
 
        if revision_id is not None:
3703
 
            source_ids = self.source.get_ancestry(revision_id)
3704
 
            if source_ids[0] is not None:
3705
 
                raise AssertionError()
3706
 
            source_ids.pop(0)
3707
 
        else:
3708
 
            source_ids = self.source.all_revision_ids()
3709
 
        source_ids_set = set(source_ids)
3710
 
        # source_ids is the worst possible case we may need to pull.
3711
 
        # now we want to filter source_ids against what we actually
3712
 
        # have in target, but don't try to check for existence where we know
3713
 
        # we do not have a revision as that would be pointless.
3714
 
        target_ids = set(self.target.all_revision_ids())
3715
 
        possibly_present_revisions = target_ids.intersection(source_ids_set)
3716
 
        actually_present_revisions = set(
3717
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
3718
 
        required_revisions = source_ids_set.difference(actually_present_revisions)
3719
 
        if revision_id is not None:
3720
 
            # we used get_ancestry to determine source_ids then we are assured all
3721
 
            # revisions referenced are present as they are installed in topological order.
3722
 
            # and the tip revision was validated by get_ancestry.
3723
 
            result_set = required_revisions
3724
 
        else:
3725
 
            # if we just grabbed the possibly available ids, then
3726
 
            # we only have an estimate of whats available and need to validate
3727
 
            # that against the revision records.
3728
 
            result_set = set(
3729
 
                self.source._eliminate_revisions_not_present(required_revisions))
3730
 
        return self.source.revision_ids_to_search_result(result_set)
3731
 
 
3732
 
 
3733
3692
class InterDifferingSerializer(InterRepository):
3734
3693
 
3735
3694
    @classmethod
3837
3796
                basis_id, delta, current_revision_id, parents_parents)
3838
3797
            cache[current_revision_id] = parent_tree
3839
3798
 
3840
 
    def _fetch_batch(self, revision_ids, basis_id, cache, a_graph=None):
 
3799
    def _fetch_batch(self, revision_ids, basis_id, cache):
3841
3800
        """Fetch across a few revisions.
3842
3801
 
3843
3802
        :param revision_ids: The revisions to copy
3844
3803
        :param basis_id: The revision_id of a tree that must be in cache, used
3845
3804
            as a basis for delta when no other base is available
3846
3805
        :param cache: A cache of RevisionTrees that we can use.
3847
 
        :param a_graph: A Graph object to determine the heads() of the
3848
 
            rich-root data stream.
3849
3806
        :return: The revision_id of the last converted tree. The RevisionTree
3850
3807
            for it will be in cache
3851
3808
        """
3919
3876
        if root_keys_to_create:
3920
3877
            root_stream = _mod_fetch._new_root_data_stream(
3921
3878
                root_keys_to_create, self._revision_id_to_root_id, parent_map,
3922
 
                self.source, graph=a_graph)
 
3879
                self.source)
3923
3880
            to_texts.insert_record_stream(root_stream)
3924
3881
        to_texts.insert_record_stream(from_texts.get_record_stream(
3925
3882
            text_keys, self.target._format._fetch_order,
3982
3939
        cache[basis_id] = basis_tree
3983
3940
        del basis_tree # We don't want to hang on to it here
3984
3941
        hints = []
3985
 
        if self._converting_to_rich_root and len(revision_ids) > 100:
3986
 
            a_graph = _mod_fetch._get_rich_root_heads_graph(self.source,
3987
 
                                                            revision_ids)
3988
 
        else:
3989
 
            a_graph = None
 
3942
        a_graph = None
3990
3943
 
3991
3944
        for offset in range(0, len(revision_ids), batch_size):
3992
3945
            self.target.start_write_group()
3994
3947
                pb.update('Transferring revisions', offset,
3995
3948
                          len(revision_ids))
3996
3949
                batch = revision_ids[offset:offset+batch_size]
3997
 
                basis_id = self._fetch_batch(batch, basis_id, cache,
3998
 
                                             a_graph=a_graph)
 
3950
                basis_id = self._fetch_batch(batch, basis_id, cache)
3999
3951
            except:
4000
3952
                self.source._safe_to_return_from_cache = False
4001
3953
                self.target.abort_write_group()
4014
3966
            fetch_spec=None):
4015
3967
        """See InterRepository.fetch()."""
4016
3968
        if fetch_spec is not None:
4017
 
            raise AssertionError("Not implemented yet...")
 
3969
            revision_ids = fetch_spec.get_keys()
 
3970
        else:
 
3971
            revision_ids = None
4018
3972
        ui.ui_factory.warn_experimental_format_fetch(self)
4019
3973
        if (not self.source.supports_rich_root()
4020
3974
            and self.target.supports_rich_root()):
4027
3981
            ui.ui_factory.show_user_warning('cross_format_fetch',
4028
3982
                from_format=self.source._format,
4029
3983
                to_format=self.target._format)
4030
 
        revision_ids = self.target.search_missing_revision_ids(self.source,
4031
 
            revision_id, find_ghosts=find_ghosts).get_keys()
 
3984
        if revision_ids is None:
 
3985
            if revision_id:
 
3986
                search_revision_ids = [revision_id]
 
3987
            else:
 
3988
                search_revision_ids = None
 
3989
            revision_ids = self.target.search_missing_revision_ids(self.source,
 
3990
                revision_ids=search_revision_ids,
 
3991
                find_ghosts=find_ghosts).get_keys()
4032
3992
        if not revision_ids:
4033
3993
            return 0, 0
4034
3994
        revision_ids = tsort.topo_sort(
4067
4027
            basis_id = first_rev.parent_ids[0]
4068
4028
            # only valid as a basis if the target has it
4069
4029
            self.target.get_revision(basis_id)
4070
 
            # Try to get a basis tree - if its a ghost it will hit the
 
4030
            # Try to get a basis tree - if it's a ghost it will hit the
4071
4031
            # NoSuchRevision case.
4072
4032
            basis_tree = self.source.revision_tree(basis_id)
4073
4033
        except (IndexError, errors.NoSuchRevision):
4078
4038
 
4079
4039
InterRepository.register_optimiser(InterDifferingSerializer)
4080
4040
InterRepository.register_optimiser(InterSameDataRepository)
4081
 
InterRepository.register_optimiser(InterWeaveRepo)
4082
 
InterRepository.register_optimiser(InterKnitRepo)
4083
4041
 
4084
4042
 
4085
4043
class CopyConverter(object):
4273
4231
                is_resume = False
4274
4232
            try:
4275
4233
                # locked_insert_stream performs a commit|suspend.
4276
 
                return self._locked_insert_stream(stream, src_format, is_resume)
 
4234
                missing_keys = self.insert_stream_without_locking(stream,
 
4235
                                    src_format, is_resume)
 
4236
                if missing_keys:
 
4237
                    # suspend the write group and tell the caller what we is
 
4238
                    # missing. We know we can suspend or else we would not have
 
4239
                    # entered this code path. (All repositories that can handle
 
4240
                    # missing keys can handle suspending a write group).
 
4241
                    write_group_tokens = self.target_repo.suspend_write_group()
 
4242
                    return write_group_tokens, missing_keys
 
4243
                hint = self.target_repo.commit_write_group()
 
4244
                to_serializer = self.target_repo._format._serializer
 
4245
                src_serializer = src_format._serializer
 
4246
                if (to_serializer != src_serializer and
 
4247
                    self.target_repo._format.pack_compresses):
 
4248
                    self.target_repo.pack(hint=hint)
 
4249
                return [], set()
4277
4250
            except:
4278
4251
                self.target_repo.abort_write_group(suppress_errors=True)
4279
4252
                raise
4280
4253
        finally:
4281
4254
            self.target_repo.unlock()
4282
4255
 
4283
 
    def _locked_insert_stream(self, stream, src_format, is_resume):
 
4256
    def insert_stream_without_locking(self, stream, src_format,
 
4257
                                      is_resume=False):
 
4258
        """Insert a stream's content into the target repository.
 
4259
 
 
4260
        This assumes that you already have a locked repository and an active
 
4261
        write group.
 
4262
 
 
4263
        :param src_format: a bzr repository format.
 
4264
        :param is_resume: Passed down to get_missing_parent_inventories to
 
4265
            indicate if we should be checking for missing texts at the same
 
4266
            time.
 
4267
 
 
4268
        :return: A set of keys that are missing.
 
4269
        """
 
4270
        if not self.target_repo.is_write_locked():
 
4271
            raise errors.ObjectNotLocked(self)
 
4272
        if not self.target_repo.is_in_write_group():
 
4273
            raise errors.BzrError('you must already be in a write group')
4284
4274
        to_serializer = self.target_repo._format._serializer
4285
4275
        src_serializer = src_format._serializer
4286
4276
        new_pack = None
4326
4316
                # required if the serializers are different only in terms of
4327
4317
                # the inventory.
4328
4318
                if src_serializer == to_serializer:
4329
 
                    self.target_repo.revisions.insert_record_stream(
4330
 
                        substream)
 
4319
                    self.target_repo.revisions.insert_record_stream(substream)
4331
4320
                else:
4332
4321
                    self._extract_and_insert_revisions(substream,
4333
4322
                        src_serializer)
4366
4355
            # cannot even attempt suspending, and missing would have failed
4367
4356
            # during stream insertion.
4368
4357
            missing_keys = set()
4369
 
        else:
4370
 
            if missing_keys:
4371
 
                # suspend the write group and tell the caller what we is
4372
 
                # missing. We know we can suspend or else we would not have
4373
 
                # entered this code path. (All repositories that can handle
4374
 
                # missing keys can handle suspending a write group).
4375
 
                write_group_tokens = self.target_repo.suspend_write_group()
4376
 
                return write_group_tokens, missing_keys
4377
 
        hint = self.target_repo.commit_write_group()
4378
 
        if (to_serializer != src_serializer and
4379
 
            self.target_repo._format.pack_compresses):
4380
 
            self.target_repo.pack(hint=hint)
4381
 
        return [], set()
 
4358
        return missing_keys
4382
4359
 
4383
4360
    def _extract_and_insert_inventory_deltas(self, substream, serializer):
4384
4361
        target_rich_root = self.target_repo._format.rich_root_data
4441
4418
        """Create a StreamSource streaming from from_repository."""
4442
4419
        self.from_repository = from_repository
4443
4420
        self.to_format = to_format
 
4421
        self._record_counter = RecordCounter()
4444
4422
 
4445
4423
    def delta_on_metadata(self):
4446
4424
        """Return True if delta's are permitted on metadata streams.