~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Tarmac
  • Author(s): Vincent Ladeuil
  • Date: 2017-01-30 14:42:05 UTC
  • mfrom: (6620.1.1 trunk)
  • Revision ID: tarmac-20170130144205-r8fh2xpmiuxyozpv
Merge  2.7 into trunk including fix for bug #1657238 [r=vila]

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
 
17
from __future__ import absolute_import
 
18
 
17
19
from bzrlib.lazy_import import lazy_import
18
20
lazy_import(globals(), """
19
21
import itertools
20
22
import time
21
23
 
22
24
from bzrlib import (
23
 
    bzrdir,
24
25
    config,
25
26
    controldir,
26
27
    debug,
39
40
""")
40
41
 
41
42
from bzrlib import (
 
43
    bzrdir,
42
44
    errors,
43
45
    registry,
44
46
    symbol_versioning,
74
76
    record_root_entry = True
75
77
    # whether this commit builder supports the record_entry_contents interface
76
78
    supports_record_entry_contents = False
 
79
    # whether this commit builder will automatically update the branch that is
 
80
    # being committed to
 
81
    updates_branch = False
77
82
 
78
 
    def __init__(self, repository, parents, config, timestamp=None,
 
83
    def __init__(self, repository, parents, config_stack, timestamp=None,
79
84
                 timezone=None, committer=None, revprops=None,
80
85
                 revision_id=None, lossy=False):
81
86
        """Initiate a CommitBuilder.
90
95
        :param lossy: Whether to discard data that can not be natively
91
96
            represented, when pushing to a foreign VCS 
92
97
        """
93
 
        self._config = config
 
98
        self._config_stack = config_stack
94
99
        self._lossy = lossy
95
100
 
96
101
        if committer is None:
97
 
            self._committer = self._config.username()
 
102
            self._committer = self._config_stack.get('email')
98
103
        elif not isinstance(committer, unicode):
99
104
            self._committer = committer.decode() # throw if non-ascii
100
105
        else:
342
347
        self.control_files.break_lock()
343
348
 
344
349
    @staticmethod
345
 
    def create(a_bzrdir):
346
 
        """Construct the current default format repository in a_bzrdir."""
347
 
        return RepositoryFormat.get_default_format().initialize(a_bzrdir)
 
350
    def create(controldir):
 
351
        """Construct the current default format repository in controldir."""
 
352
        return RepositoryFormat.get_default_format().initialize(controldir)
348
353
 
349
 
    def __init__(self, _format, a_bzrdir, control_files):
 
354
    def __init__(self, _format, controldir, control_files):
350
355
        """instantiate a Repository.
351
356
 
352
357
        :param _format: The format of the repository on disk.
353
 
        :param a_bzrdir: The BzrDir of the repository.
 
358
        :param controldir: The ControlDir of the repository.
354
359
        :param control_files: Control files to use for locking, etc.
355
360
        """
356
361
        # In the future we will have a single api for all stores for
359
364
        super(Repository, self).__init__()
360
365
        self._format = _format
361
366
        # the following are part of the public API for Repository:
362
 
        self.bzrdir = a_bzrdir
 
367
        self.bzrdir = controldir
363
368
        self.control_files = control_files
364
369
        # for tests
365
370
        self._write_group = None
547
552
            def __init__(self):
548
553
                self.first_call = True
549
554
 
550
 
            def __call__(self, bzrdir):
551
 
                # On the first call, the parameter is always the bzrdir
 
555
            def __call__(self, controldir):
 
556
                # On the first call, the parameter is always the controldir
552
557
                # containing the current repo.
553
558
                if not self.first_call:
554
559
                    try:
555
 
                        repository = bzrdir.open_repository()
 
560
                        repository = controldir.open_repository()
556
561
                    except errors.NoRepositoryPresent:
557
562
                        pass
558
563
                    else:
559
564
                        return False, ([], repository)
560
565
                self.first_call = False
561
 
                value = (bzrdir.list_branches(), None)
 
566
                value = (controldir.list_branches(), None)
562
567
                return True, value
563
568
 
564
569
        ret = []
565
 
        for branches, repository in bzrdir.BzrDir.find_bzrdirs(
 
570
        for branches, repository in controldir.ControlDir.find_bzrdirs(
566
571
                self.user_transport, evaluate=Evaluator()):
567
572
            if branches is not None:
568
573
                ret.extend(branches)
602
607
        For instance, if the repository is at URL/.bzr/repository,
603
608
        Repository.open(URL) -> a Repository instance.
604
609
        """
605
 
        control = bzrdir.BzrDir.open(base)
 
610
        control = controldir.ControlDir.open(base)
606
611
        return control.open_repository()
607
612
 
608
613
    def copy_content_into(self, destination, revision_id=None):
639
644
        """
640
645
 
641
646
    def suspend_write_group(self):
 
647
        """Suspend a write group.
 
648
 
 
649
        :raise UnsuspendableWriteGroup: If the write group can not be
 
650
            suspended.
 
651
        :return: List of tokens
 
652
        """
642
653
        raise errors.UnsuspendableWriteGroup(self)
643
654
 
644
655
    def refresh_data(self):
666
677
    def _resume_write_group(self, tokens):
667
678
        raise errors.UnsuspendableWriteGroup(self)
668
679
 
669
 
    def fetch(self, source, revision_id=None, find_ghosts=False,
670
 
            fetch_spec=None):
 
680
    def fetch(self, source, revision_id=None, find_ghosts=False):
671
681
        """Fetch the content required to construct revision_id from source.
672
682
 
673
 
        If revision_id is None and fetch_spec is None, then all content is
674
 
        copied.
 
683
        If revision_id is None, then all content is copied.
675
684
 
676
685
        fetch() may not be used when the repository is in a write group -
677
686
        either finish the current write group before using fetch, or use
683
692
        :param revision_id: If specified, all the content needed for this
684
693
            revision ID will be copied to the target.  Fetch will determine for
685
694
            itself which content needs to be copied.
686
 
        :param fetch_spec: If specified, a SearchResult or
687
 
            PendingAncestryResult that describes which revisions to copy.  This
688
 
            allows copying multiple heads at once.  Mutually exclusive with
689
 
            revision_id.
690
695
        """
691
 
        if fetch_spec is not None and revision_id is not None:
692
 
            raise AssertionError(
693
 
                "fetch_spec and revision_id are mutually exclusive.")
694
696
        if self.is_in_write_group():
695
697
            raise errors.InternalBzrError(
696
698
                "May not fetch while in a write group.")
698
700
        # TODO: lift out to somewhere common with RemoteRepository
699
701
        # <https://bugs.launchpad.net/bzr/+bug/401646>
700
702
        if (self.has_same_location(source)
701
 
            and fetch_spec is None
702
703
            and self._has_same_fallbacks(source)):
703
704
            # check that last_revision is in 'from' and then return a
704
705
            # no-operation.
707
708
                self.get_revision(revision_id)
708
709
            return 0, []
709
710
        inter = InterRepository.get(source, self)
710
 
        return inter.fetch(revision_id=revision_id,
711
 
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
 
711
        return inter.fetch(revision_id=revision_id, find_ghosts=find_ghosts)
712
712
 
713
713
    def create_bundle(self, target, base, fileobj, format=None):
714
714
        return serializer.write_bundle(self, target, base, fileobj, format)
715
715
 
716
 
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
716
    def get_commit_builder(self, branch, parents, config_stack, timestamp=None,
717
717
                           timezone=None, committer=None, revprops=None,
718
718
                           revision_id=None, lossy=False):
719
719
        """Obtain a CommitBuilder for this repository.
720
720
 
721
721
        :param branch: Branch to commit to.
722
722
        :param parents: Revision ids of the parents of the new revision.
723
 
        :param config: Configuration to use.
 
723
        :param config_stack: Configuration stack to use.
724
724
        :param timestamp: Optional timestamp recorded for commit.
725
725
        :param timezone: Optional timezone for timestamp.
726
726
        :param committer: Optional committer to set for commit.
746
746
                repo.unlock()
747
747
 
748
748
    @needs_read_lock
749
 
    def clone(self, a_bzrdir, revision_id=None):
750
 
        """Clone this repository into a_bzrdir using the current format.
 
749
    def clone(self, controldir, revision_id=None):
 
750
        """Clone this repository into controldir using the current format.
751
751
 
752
752
        Currently no check is made that the format of this repository and
753
753
        the bzrdir format are compatible. FIXME RBC 20060201.
756
756
        """
757
757
        # TODO: deprecate after 0.16; cloning this with all its settings is
758
758
        # probably not very useful -- mbp 20070423
759
 
        dest_repo = self._create_sprouting_repo(a_bzrdir, shared=self.is_shared())
 
759
        dest_repo = self._create_sprouting_repo(
 
760
            controldir, shared=self.is_shared())
760
761
        self.copy_content_into(dest_repo, revision_id)
761
762
        return dest_repo
762
763
 
929
930
        parent_ids.discard(_mod_revision.NULL_REVISION)
930
931
        return parent_ids
931
932
 
932
 
    def fileids_altered_by_revision_ids(self, revision_ids):
933
 
        """Find the file ids and versions affected by revisions.
934
 
 
935
 
        :param revisions: an iterable containing revision ids.
936
 
        :return: a dictionary mapping altered file-ids to an iterable of
937
 
            revision_ids. Each altered file-ids has the exact revision_ids
938
 
            that altered it listed explicitly.
939
 
        """
940
 
        raise NotImplementedError(self.fileids_altered_by_revision_ids)
941
 
 
942
933
    def iter_files_bytes(self, desired_files):
943
934
        """Iterate through file versions.
944
935
 
990
981
            raise AssertionError('_iter_for_revno returned too much history')
991
982
        return (True, partial_history[-1])
992
983
 
993
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
994
 
    def iter_reverse_revision_history(self, revision_id):
995
 
        """Iterate backwards through revision ids in the lefthand history
996
 
 
997
 
        :param revision_id: The revision id to start with.  All its lefthand
998
 
            ancestors will be traversed.
999
 
        """
1000
 
        graph = self.get_graph()
1001
 
        stop_revisions = (None, _mod_revision.NULL_REVISION)
1002
 
        return graph.iter_lefthand_ancestry(revision_id, stop_revisions)
1003
 
 
1004
984
    def is_shared(self):
1005
985
        """Return True if this repository is flagged as a shared repository."""
1006
986
        raise NotImplementedError(self.is_shared)
1043
1023
        """
1044
1024
        raise NotImplementedError(self.revision_trees)
1045
1025
 
1046
 
    @needs_read_lock
1047
 
    @symbol_versioning.deprecated_method(
1048
 
        symbol_versioning.deprecated_in((2, 4, 0)))
1049
 
    def get_ancestry(self, revision_id, topo_sorted=True):
1050
 
        """Return a list of revision-ids integrated by a revision.
1051
 
 
1052
 
        The first element of the list is always None, indicating the origin
1053
 
        revision.  This might change when we have history horizons, or
1054
 
        perhaps we should have a new API.
1055
 
 
1056
 
        This is topologically sorted.
1057
 
        """
1058
 
        if 'evil' in debug.debug_flags:
1059
 
            mutter_callsite(2, "get_ancestry is linear with history.")
1060
 
        if _mod_revision.is_null(revision_id):
1061
 
            return [None]
1062
 
        if not self.has_revision(revision_id):
1063
 
            raise errors.NoSuchRevision(self, revision_id)
1064
 
        graph = self.get_graph()
1065
 
        keys = set()
1066
 
        search = graph._make_breadth_first_searcher([revision_id])
1067
 
        while True:
1068
 
            try:
1069
 
                found, ghosts = search.next_with_ghosts()
1070
 
            except StopIteration:
1071
 
                break
1072
 
            keys.update(found)
1073
 
        if _mod_revision.NULL_REVISION in keys:
1074
 
            keys.remove(_mod_revision.NULL_REVISION)
1075
 
        if topo_sorted:
1076
 
            parent_map = graph.get_parent_map(keys)
1077
 
            keys = tsort.topo_sort(parent_map)
1078
 
        return [None] + list(keys)
1079
 
 
1080
1026
    def pack(self, hint=None, clean_obsolete_packs=False):
1081
1027
        """Compress the data within the repository.
1082
1028
 
1158
1104
                [parents_provider, other_repository._make_parents_provider()])
1159
1105
        return graph.Graph(parents_provider)
1160
1106
 
1161
 
    def revision_ids_to_search_result(self, result_set):
1162
 
        """Convert a set of revision ids to a graph SearchResult."""
1163
 
        result_parents = set()
1164
 
        for parents in self.get_graph().get_parent_map(
1165
 
            result_set).itervalues():
1166
 
            result_parents.update(parents)
1167
 
        included_keys = result_set.intersection(result_parents)
1168
 
        start_keys = result_set.difference(included_keys)
1169
 
        exclude_keys = result_parents.difference(result_set)
1170
 
        result = graph.SearchResult(start_keys, exclude_keys,
1171
 
            len(result_set), result_set)
1172
 
        return result
1173
 
 
1174
1107
    @needs_write_lock
1175
1108
    def set_make_working_trees(self, new_value):
1176
1109
        """Set the policy flag for making working trees when creating branches.
1194
1127
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1195
1128
 
1196
1129
    @needs_read_lock
1197
 
    def verify_revision(self, revision_id, gpg_strategy):
 
1130
    def verify_revision_signature(self, revision_id, gpg_strategy):
1198
1131
        """Verify the signature on a revision.
1199
 
        
 
1132
 
1200
1133
        :param revision_id: the revision to verify
1201
1134
        :gpg_strategy: the GPGStrategy object to used
1202
 
        
 
1135
 
1203
1136
        :return: gpg.SIGNATURE_VALID or a failed SIGNATURE_ value
1204
1137
        """
1205
1138
        if not self.has_signature_for_revision_id(revision_id):
1211
1144
 
1212
1145
        return gpg_strategy.verify(signature, plaintext)
1213
1146
 
 
1147
    @needs_read_lock
 
1148
    def verify_revision_signatures(self, revision_ids, gpg_strategy):
 
1149
        """Verify revision signatures for a number of revisions.
 
1150
 
 
1151
        :param revision_id: the revision to verify
 
1152
        :gpg_strategy: the GPGStrategy object to used
 
1153
        :return: Iterator over tuples with revision id, result and keys
 
1154
        """
 
1155
        for revid in revision_ids:
 
1156
            (result, key) = self.verify_revision_signature(revid, gpg_strategy)
 
1157
            yield revid, result, key
 
1158
 
1214
1159
    def has_signature_for_revision_id(self, revision_id):
1215
1160
        """Query for a revision signature for revision_id in the repository."""
1216
1161
        raise NotImplementedError(self.has_signature_for_revision_id)
1246
1191
            return
1247
1192
        try:
1248
1193
            if branch is None:
1249
 
                conf = config.GlobalConfig()
 
1194
                conf = config.GlobalStack()
1250
1195
            else:
1251
 
                conf = branch.get_config()
1252
 
            if conf.suppress_warning('format_deprecation'):
 
1196
                conf = branch.get_config_stack()
 
1197
            if 'format_deprecation' in conf.get('suppress_warnings'):
1253
1198
                return
1254
1199
            warning("Format %s for %s is deprecated -"
1255
1200
                    " please use 'bzr upgrade' to get better performance"
1315
1260
        """Returns the policy for making working trees on new branches."""
1316
1261
        return not self._transport.has('no-working-trees')
1317
1262
 
 
1263
    @needs_write_lock
 
1264
    def update_feature_flags(self, updated_flags):
 
1265
        """Update the feature flags for this branch.
 
1266
 
 
1267
        :param updated_flags: Dictionary mapping feature names to necessities
 
1268
            A necessity can be None to indicate the feature should be removed
 
1269
        """
 
1270
        self._format._update_feature_flags(updated_flags)
 
1271
        self.control_transport.put_bytes('format', self._format.as_string())
 
1272
 
1318
1273
 
1319
1274
class RepositoryFormatRegistry(controldir.ControlComponentFormatRegistry):
1320
1275
    """Repository format registry."""
1321
1276
 
1322
1277
    def get_default(self):
1323
1278
        """Return the current default format."""
1324
 
        from bzrlib import bzrdir
1325
 
        return bzrdir.format_registry.make_bzrdir('default').repository_format
 
1279
        return controldir.format_registry.make_bzrdir('default').repository_format
1326
1280
 
1327
1281
 
1328
1282
network_format_registry = registry.FormatRegistry()
1371
1325
    created.
1372
1326
 
1373
1327
    Common instance attributes:
1374
 
    _matchingbzrdir - the bzrdir format that the repository format was
 
1328
    _matchingbzrdir - the controldir format that the repository format was
1375
1329
    originally written to work with. This can be used if manually
1376
1330
    constructing a bzrdir and repository, or more commonly for test suite
1377
1331
    parameterization.
1419
1373
    supports_versioned_directories = None
1420
1374
    # Can other repositories be nested into one of this format?
1421
1375
    supports_nesting_repositories = None
 
1376
    # Is it possible for revisions to be present without being referenced
 
1377
    # somewhere ?
 
1378
    supports_unreferenced_revisions = None
1422
1379
 
1423
1380
    def __repr__(self):
1424
1381
        return "%s()" % self.__class__.__name__
1430
1387
    def __ne__(self, other):
1431
1388
        return not self == other
1432
1389
 
1433
 
    @classmethod
1434
 
    def find_format(klass, a_bzrdir):
1435
 
        """Return the format for the repository object in a_bzrdir.
1436
 
 
1437
 
        This is used by bzr native formats that have a "format" file in
1438
 
        the repository.  Other methods may be used by different types of
1439
 
        control directory.
1440
 
        """
1441
 
        try:
1442
 
            transport = a_bzrdir.get_repository_transport(None)
1443
 
            format_string = transport.get_bytes("format")
1444
 
            return format_registry.get(format_string)
1445
 
        except errors.NoSuchFile:
1446
 
            raise errors.NoRepositoryPresent(a_bzrdir)
1447
 
        except KeyError:
1448
 
            raise errors.UnknownFormatError(format=format_string,
1449
 
                                            kind='repository')
1450
 
 
1451
 
    @classmethod
1452
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
1453
 
    def register_format(klass, format):
1454
 
        format_registry.register(format)
1455
 
 
1456
 
    @classmethod
1457
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
1458
 
    def unregister_format(klass, format):
1459
 
        format_registry.remove(format)
1460
 
 
1461
 
    @classmethod
1462
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
1463
 
    def get_default_format(klass):
1464
 
        """Return the current default format."""
1465
 
        return format_registry.get_default()
1466
 
 
1467
 
    def get_format_string(self):
1468
 
        """Return the ASCII format string that identifies this format.
1469
 
 
1470
 
        Note that in pre format ?? repositories the format string is
1471
 
        not permitted nor written to disk.
1472
 
        """
1473
 
        raise NotImplementedError(self.get_format_string)
1474
 
 
1475
1390
    def get_format_description(self):
1476
1391
        """Return the short description for this format."""
1477
1392
        raise NotImplementedError(self.get_format_description)
1478
1393
 
1479
 
    def initialize(self, a_bzrdir, shared=False):
1480
 
        """Initialize a repository of this format in a_bzrdir.
 
1394
    def initialize(self, controldir, shared=False):
 
1395
        """Initialize a repository of this format in controldir.
1481
1396
 
1482
 
        :param a_bzrdir: The bzrdir to put the new repository in it.
 
1397
        :param controldir: The controldir to put the new repository in it.
1483
1398
        :param shared: The repository should be initialized as a sharable one.
1484
1399
        :returns: The new repository object.
1485
1400
 
1486
1401
        This may raise UninitializableFormat if shared repository are not
1487
 
        compatible the a_bzrdir.
 
1402
        compatible the controldir.
1488
1403
        """
1489
1404
        raise NotImplementedError(self.initialize)
1490
1405
 
1526
1441
                'Does not support nested trees', target_format,
1527
1442
                from_format=self)
1528
1443
 
1529
 
    def open(self, a_bzrdir, _found=False):
1530
 
        """Return an instance of this format for the bzrdir a_bzrdir.
 
1444
    def open(self, controldir, _found=False):
 
1445
        """Return an instance of this format for a controldir.
1531
1446
 
1532
1447
        _found is a private parameter, do not use it.
1533
1448
        """
1534
1449
        raise NotImplementedError(self.open)
1535
1450
 
1536
 
    def _run_post_repo_init_hooks(self, repository, a_bzrdir, shared):
1537
 
        from bzrlib.bzrdir import BzrDir, RepoInitHookParams
1538
 
        hooks = BzrDir.hooks['post_repo_init']
 
1451
    def _run_post_repo_init_hooks(self, repository, controldir, shared):
 
1452
        from bzrlib.controldir import ControlDir, RepoInitHookParams
 
1453
        hooks = ControlDir.hooks['post_repo_init']
1539
1454
        if not hooks:
1540
1455
            return
1541
 
        params = RepoInitHookParams(repository, self, a_bzrdir, shared)
 
1456
        params = RepoInitHookParams(repository, self, controldir, shared)
1542
1457
        for hook in hooks:
1543
1458
            hook(params)
1544
1459
 
1545
1460
 
1546
 
class MetaDirRepositoryFormat(RepositoryFormat):
 
1461
class RepositoryFormatMetaDir(bzrdir.BzrFormat, RepositoryFormat):
1547
1462
    """Common base class for the new repositories using the metadir layout."""
1548
1463
 
1549
1464
    rich_root_data = False
1559
1474
        return matching
1560
1475
 
1561
1476
    def __init__(self):
1562
 
        super(MetaDirRepositoryFormat, self).__init__()
 
1477
        RepositoryFormat.__init__(self)
 
1478
        bzrdir.BzrFormat.__init__(self)
1563
1479
 
1564
1480
    def _create_control_files(self, a_bzrdir):
1565
1481
        """Create the required files and the initial control_files object."""
1589
1505
        finally:
1590
1506
            control_files.unlock()
1591
1507
 
1592
 
    def network_name(self):
1593
 
        """Metadir formats have matching disk and network format strings."""
1594
 
        return self.get_format_string()
 
1508
    @classmethod
 
1509
    def find_format(klass, a_bzrdir):
 
1510
        """Return the format for the repository object in a_bzrdir.
 
1511
 
 
1512
        This is used by bzr native formats that have a "format" file in
 
1513
        the repository.  Other methods may be used by different types of
 
1514
        control directory.
 
1515
        """
 
1516
        try:
 
1517
            transport = a_bzrdir.get_repository_transport(None)
 
1518
            format_string = transport.get_bytes("format")
 
1519
        except errors.NoSuchFile:
 
1520
            raise errors.NoRepositoryPresent(a_bzrdir)
 
1521
        return klass._find_format(format_registry, 'repository', format_string)
 
1522
 
 
1523
    def check_support_status(self, allow_unsupported, recommend_upgrade=True,
 
1524
            basedir=None):
 
1525
        RepositoryFormat.check_support_status(self,
 
1526
            allow_unsupported=allow_unsupported, recommend_upgrade=recommend_upgrade,
 
1527
            basedir=basedir)
 
1528
        bzrdir.BzrFormat.check_support_status(self, allow_unsupported=allow_unsupported,
 
1529
            recommend_upgrade=recommend_upgrade, basedir=basedir)
1595
1530
 
1596
1531
 
1597
1532
# formats which have no format string are not discoverable or independently
1598
1533
# creatable on disk, so are not registered in format_registry.  They're
1599
1534
# all in bzrlib.repofmt.knitreponow.  When an instance of one of these is
1600
 
# needed, it's constructed directly by the BzrDir.  Non-native formats where
 
1535
# needed, it's constructed directly by the ControlDir.  Non-native formats where
1601
1536
# the repository is not separately opened are similar.
1602
1537
 
1603
1538
format_registry.register_lazy(
1714
1649
        self.target.fetch(self.source, revision_id=revision_id)
1715
1650
 
1716
1651
    @needs_write_lock
1717
 
    def fetch(self, revision_id=None, find_ghosts=False,
1718
 
            fetch_spec=None):
 
1652
    def fetch(self, revision_id=None, find_ghosts=False):
1719
1653
        """Fetch the content required to construct revision_id.
1720
1654
 
1721
1655
        The content is copied from self.source to self.target.