/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005-2010 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,
29
28
    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,
43
42
    revision as _mod_revision,
44
43
    static_tuple,
 
44
    symbol_versioning,
45
45
    trace,
46
46
    tsort,
 
47
    ui,
47
48
    versionedfile,
48
49
    )
49
50
from bzrlib.bundle import serializer
52
53
from bzrlib.testament import Testament
53
54
""")
54
55
 
55
 
from bzrlib import (
56
 
    errors,
57
 
    registry,
58
 
    symbol_versioning,
59
 
    ui,
60
 
    )
61
56
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
62
57
from bzrlib.inter import InterObject
63
58
from bzrlib.inventory import (
66
61
    ROOT_ID,
67
62
    entry_factory,
68
63
    )
69
 
from bzrlib.recordcounter import RecordCounter
70
 
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
 
64
from bzrlib.lock import _RelockDebugMixin
 
65
from bzrlib import registry
71
66
from bzrlib.trace import (
72
67
    log_exception_quietly, note, mutter, mutter_callsite, warning)
73
68
 
76
71
_deprecation_warning_done = False
77
72
 
78
73
 
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
 
 
87
74
class CommitBuilder(object):
88
75
    """Provides an interface to build up a commit.
89
76
 
114
101
 
115
102
        if committer is None:
116
103
            self._committer = self._config.username()
117
 
        elif not isinstance(committer, unicode):
118
 
            self._committer = committer.decode() # throw if non-ascii
119
104
        else:
120
105
            self._committer = committer
121
106
 
175
160
            self._validate_unicode_text(value,
176
161
                                        'revision property (%s)' % (key,))
177
162
 
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
 
 
213
163
    def commit(self, message):
214
164
        """Make the actual commit.
215
165
 
227
177
        rev.parent_ids = self.parents
228
178
        self.repository.add_revision(self._new_revision_id, rev,
229
179
            self.new_inventory, self._config)
230
 
        self._ensure_fallback_inventories()
231
180
        self.repository.commit_write_group()
232
181
        return self._new_revision_id
233
182
 
282
231
 
283
232
    def _gen_revision_id(self):
284
233
        """Return new revision-id."""
285
 
        return generate_ids.gen_revision_id(self._committer, self._timestamp)
 
234
        return generate_ids.gen_revision_id(self._config.username(),
 
235
                                            self._timestamp)
286
236
 
287
237
    def _generate_revision_if_needed(self):
288
238
        """Create a revision id if None was supplied.
328
278
 
329
279
        :param tree: The tree which is being committed.
330
280
        """
331
 
        if len(self.parents) == 0:
332
 
            raise errors.RootMissing()
 
281
        # NB: if there are no parents then this method is not called, so no
 
282
        # need to guard on parents having length.
333
283
        entry = entry_factory['directory'](tree.path2id(''), '',
334
284
            None)
335
285
        entry.revision = self._new_revision_id
473
423
            else:
474
424
                # we don't need to commit this, because the caller already
475
425
                # determined that an existing revision of this file is
476
 
                # appropriate. If it's not being considered for committing then
 
426
                # appropriate. If its not being considered for committing then
477
427
                # it and all its parents to the root must be unaltered so
478
428
                # no-change against the basis.
479
429
                if ie.revision == self._new_revision_id:
795
745
                    # after iter_changes examines and decides it has changed,
796
746
                    # we will unconditionally record a new version even if some
797
747
                    # other process reverts it while commit is running (with
798
 
                    # the revert happening after iter_changes did its
 
748
                    # the revert happening after iter_changes did it's
799
749
                    # examination).
800
750
                    if change[7][1]:
801
751
                        entry.executable = True
910
860
        # versioned roots do not change unless the tree found a change.
911
861
 
912
862
 
913
 
class RepositoryWriteLockResult(LogicalLockResult):
914
 
    """The result of write locking a repository.
915
 
 
916
 
    :ivar repository_token: The token obtained from the underlying lock, or
917
 
        None.
918
 
    :ivar unlock: A callable which will unlock the lock.
919
 
    """
920
 
 
921
 
    def __init__(self, unlock, repository_token):
922
 
        LogicalLockResult.__init__(self, unlock)
923
 
        self.repository_token = repository_token
924
 
 
925
 
    def __repr__(self):
926
 
        return "RepositoryWriteLockResult(%s, %s)" % (self.repository_token,
927
 
            self.unlock)
928
 
 
929
 
 
930
863
######################################################################
931
864
# Repositories
932
865
 
933
866
 
934
 
class Repository(_RelockDebugMixin, controldir.ControlComponent):
 
867
class Repository(_RelockDebugMixin, bzrdir.ControlComponent):
935
868
    """Repository holding history for one or more branches.
936
869
 
937
870
    The repository holds and retrieves historical information including
984
917
        pointing to .bzr/repository.
985
918
    """
986
919
 
987
 
    # What class to use for a CommitBuilder. Often it's simpler to change this
 
920
    # What class to use for a CommitBuilder. Often its simpler to change this
988
921
    # in a Repository class subclass rather than to override
989
922
    # get_commit_builder.
990
923
    _commit_builder_class = CommitBuilder
1085
1018
                " id and insertion revid (%r, %r)"
1086
1019
                % (inv.revision_id, revision_id))
1087
1020
        if inv.root is None:
1088
 
            raise errors.RootMissing()
 
1021
            raise AssertionError()
1089
1022
        return self._add_inventory_checked(revision_id, inv, parents)
1090
1023
 
1091
1024
    def _add_inventory_checked(self, revision_id, inv, parents):
1443
1376
        data during reads, and allows a 'write_group' to be obtained. Write
1444
1377
        groups must be used for actual data insertion.
1445
1378
 
1446
 
        A token should be passed in if you know that you have locked the object
1447
 
        some other way, and need to synchronise this object's state with that
1448
 
        fact.
1449
 
 
1450
 
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
1451
 
 
1452
1379
        :param token: if this is already locked, then lock_write will fail
1453
1380
            unless the token matches the existing lock.
1454
1381
        :returns: a token if this instance supports tokens, otherwise None.
1457
1384
        :raises MismatchedToken: if the specified token doesn't match the token
1458
1385
            of the existing lock.
1459
1386
        :seealso: start_write_group.
1460
 
        :return: A RepositoryWriteLockResult.
 
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
1393
        """
1462
1394
        locked = self.is_locked()
1463
 
        token = self.control_files.lock_write(token=token)
 
1395
        result = self.control_files.lock_write(token=token)
1464
1396
        if not locked:
1465
1397
            self._warn_if_deprecated()
1466
1398
            self._note_lock('w')
1468
1400
                # Writes don't affect fallback repos
1469
1401
                repo.lock_read()
1470
1402
            self._refresh_data()
1471
 
        return RepositoryWriteLockResult(self.unlock, token)
 
1403
        return result
1472
1404
 
1473
1405
    def lock_read(self):
1474
 
        """Lock the repository for read operations.
1475
 
 
1476
 
        :return: An object with an unlock method which will release the lock
1477
 
            obtained.
1478
 
        """
1479
1406
        locked = self.is_locked()
1480
1407
        self.control_files.lock_read()
1481
1408
        if not locked:
1484
1411
            for repo in self._fallback_repositories:
1485
1412
                repo.lock_read()
1486
1413
            self._refresh_data()
1487
 
        return LogicalLockResult(self.unlock)
1488
1414
 
1489
1415
    def get_physical_lock_status(self):
1490
1416
        return self.control_files.get_physical_lock_status()
1596
1522
        return ret
1597
1523
 
1598
1524
    @needs_read_lock
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):
 
1525
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1602
1526
        """Return the revision ids that other has that this does not.
1603
1527
 
1604
1528
        These are returned in topological order.
1605
1529
 
1606
1530
        revision_id: only return revision ids included by revision_id.
1607
1531
        """
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]
1618
1532
        return InterRepository.get(other, self).search_missing_revision_ids(
1619
 
            find_ghosts=find_ghosts, revision_ids=revision_ids,
1620
 
            if_present_ids=if_present_ids)
 
1533
            revision_id, find_ghosts)
1621
1534
 
1622
1535
    @staticmethod
1623
1536
    def open(base):
1721
1634
        return missing_keys
1722
1635
 
1723
1636
    def refresh_data(self):
1724
 
        """Re-read any data needed to synchronise with disk.
 
1637
        """Re-read any data needed to to synchronise with disk.
1725
1638
 
1726
1639
        This method is intended to be called after another repository instance
1727
1640
        (such as one used by a smart server) has inserted data into the
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.
 
1641
        repository. It may not be called during a write group, but may be
 
1642
        called at any other time.
1733
1643
        """
 
1644
        if self.is_in_write_group():
 
1645
            raise errors.InternalBzrError(
 
1646
                "May not refresh_data while in a write group.")
1734
1647
        self._refresh_data()
1735
1648
 
1736
1649
    def resume_write_group(self, tokens):
1775
1688
                "May not fetch while in a write group.")
1776
1689
        # fast path same-url fetch operations
1777
1690
        # TODO: lift out to somewhere common with RemoteRepository
1778
 
        # <https://bugs.launchpad.net/bzr/+bug/401646>
 
1691
        # <https://bugs.edge.launchpad.net/bzr/+bug/401646>
1779
1692
        if (self.has_same_location(source)
1780
1693
            and fetch_spec is None
1781
1694
            and self._has_same_fallbacks(source)):
1785
1698
                not _mod_revision.is_null(revision_id)):
1786
1699
                self.get_revision(revision_id)
1787
1700
            return 0, []
 
1701
        # if there is no specific appropriate InterRepository, this will get
 
1702
        # the InterRepository base class, which raises an
 
1703
        # IncompatibleRepositories when asked to fetch.
1788
1704
        inter = InterRepository.get(source, self)
1789
1705
        return inter.fetch(revision_id=revision_id, pb=pb,
1790
1706
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1806
1722
        :param revprops: Optional dictionary of revision properties.
1807
1723
        :param revision_id: Optional revision id.
1808
1724
        """
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 "
 
1725
        if self._fallback_repositories:
 
1726
            raise errors.BzrError("Cannot commit from a lightweight checkout "
 
1727
                "to a stacked branch. See "
1812
1728
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1813
1729
        result = self._commit_builder_class(self, parents, config,
1814
1730
            timestamp, timezone, committer, revprops, revision_id)
2560
2476
            ancestors will be traversed.
2561
2477
        """
2562
2478
        graph = self.get_graph()
2563
 
        stop_revisions = (None, _mod_revision.NULL_REVISION)
2564
 
        return graph.iter_lefthand_ancestry(revision_id, stop_revisions)
 
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]
2565
2492
 
2566
2493
    def is_shared(self):
2567
2494
        """Return True if this repository is flagged as a shared repository."""
2668
2595
        types it should be a no-op that just returns.
2669
2596
 
2670
2597
        This stub method does not require a lock, but subclasses should use
2671
 
        @needs_write_lock as this is a long running call it's reasonable to
 
2598
        @needs_write_lock as this is a long running call its reasonable to
2672
2599
        implicitly lock for the user.
2673
2600
 
2674
2601
        :param hint: If not supplied, the whole repository is packed.
2867
2794
        raise NotImplementedError(self.revision_graph_can_have_wrong_parents)
2868
2795
 
2869
2796
 
 
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
 
2870
2831
def install_revision(repository, rev, revision_tree):
2871
2832
    """Install all revision data into a repository."""
2872
2833
    install_revisions(repository, [(rev, revision_tree, None)])
3091
3052
    supports_tree_reference = None
3092
3053
    # Is the format experimental ?
3093
3054
    experimental = False
3094
 
    # Does this repository format escape funky characters, or does it create files with
3095
 
    # similar names as the versioned files in its contents on disk ?
3096
 
    supports_funky_characters = True
3097
3055
 
3098
3056
    def __repr__(self):
3099
3057
        return "%s()" % self.__class__.__name__
3367
3325
    'bzrlib.repofmt.pack_repo',
3368
3326
    'RepositoryFormatKnitPack6RichRoot',
3369
3327
    )
3370
 
format_registry.register_lazy(
3371
 
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
3372
 
    'bzrlib.repofmt.groupcompress_repo',
3373
 
    'RepositoryFormat2a',
3374
 
    )
3375
3328
 
3376
3329
# Development formats.
3377
 
# Check their docstrings to see if/when they are obsolete.
 
3330
# Obsolete but kept pending a CHK based subtree format.
3378
3331
format_registry.register_lazy(
3379
3332
    ("Bazaar development format 2 with subtree support "
3380
3333
        "(needs bzr.dev from before 1.8)\n"),
3381
3334
    'bzrlib.repofmt.pack_repo',
3382
3335
    'RepositoryFormatPackDevelopment2Subtree',
3383
3336
    )
3384
 
format_registry.register_lazy(
3385
 
    'Bazaar development format 8\n',
3386
 
    'bzrlib.repofmt.groupcompress_repo',
3387
 
    'RepositoryFormat2aSubtree',
 
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',
3388
3356
    )
3389
3357
 
3390
3358
 
3445
3413
                               fetch_spec=fetch_spec,
3446
3414
                               find_ghosts=find_ghosts)
3447
3415
 
3448
 
    def _walk_to_common_revisions(self, revision_ids, if_present_ids=None):
 
3416
    def _walk_to_common_revisions(self, revision_ids):
3449
3417
        """Walk out from revision_ids in source to revisions target has.
3450
3418
 
3451
3419
        :param revision_ids: The start point for the search.
3453
3421
        """
3454
3422
        target_graph = self.target.get_graph()
3455
3423
        revision_ids = frozenset(revision_ids)
3456
 
        if if_present_ids:
3457
 
            all_wanted_revs = revision_ids.union(if_present_ids)
3458
 
        else:
3459
 
            all_wanted_revs = revision_ids
3460
3424
        missing_revs = set()
3461
3425
        source_graph = self.source.get_graph()
3462
3426
        # ensure we don't pay silly lookup costs.
3463
 
        searcher = source_graph._make_breadth_first_searcher(all_wanted_revs)
 
3427
        searcher = source_graph._make_breadth_first_searcher(revision_ids)
3464
3428
        null_set = frozenset([_mod_revision.NULL_REVISION])
3465
3429
        searcher_exhausted = False
3466
3430
        while True:
3502
3466
        return searcher.get_result()
3503
3467
 
3504
3468
    @needs_read_lock
3505
 
    def search_missing_revision_ids(self,
3506
 
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
3507
 
            find_ghosts=True, revision_ids=None, if_present_ids=None):
 
3469
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3508
3470
        """Return the revision ids that source has that target does not.
3509
3471
 
3510
3472
        :param revision_id: only return revision ids included by this
3511
 
            revision_id.
3512
 
        :param revision_ids: return revision ids included by these
3513
 
            revision_ids.  NoSuchRevision will be raised if any of these
3514
 
            revisions are not present.
3515
 
        :param if_present_ids: like revision_ids, but will not cause
3516
 
            NoSuchRevision if any of these are absent, instead they will simply
3517
 
            not be in the result.  This is useful for e.g. finding revisions
3518
 
            to fetch for tags, which may reference absent revisions.
 
3473
                            revision_id.
3519
3474
        :param find_ghosts: If True find missing revisions in deep history
3520
3475
            rather than just finding the surface difference.
3521
3476
        :return: A bzrlib.graph.SearchResult.
3522
3477
        """
3523
 
        if symbol_versioning.deprecated_passed(revision_id):
3524
 
            symbol_versioning.warn(
3525
 
                'search_missing_revision_ids(revision_id=...) was '
3526
 
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
3527
 
                DeprecationWarning, stacklevel=2)
3528
 
            if revision_ids is not None:
3529
 
                raise AssertionError(
3530
 
                    'revision_ids is mutually exclusive with revision_id')
3531
 
            if revision_id is not None:
3532
 
                revision_ids = [revision_id]
3533
 
        del revision_id
3534
3478
        # stop searching at found target revisions.
3535
 
        if not find_ghosts and (revision_ids is not None or if_present_ids is
3536
 
                not None):
3537
 
            return self._walk_to_common_revisions(revision_ids,
3538
 
                    if_present_ids=if_present_ids)
 
3479
        if not find_ghosts and revision_id is not None:
 
3480
            return self._walk_to_common_revisions([revision_id])
3539
3481
        # generic, possibly worst case, slow code path.
3540
3482
        target_ids = set(self.target.all_revision_ids())
3541
 
        source_ids = self._present_source_revisions_for(
3542
 
            revision_ids, if_present_ids)
 
3483
        if revision_id is not None:
 
3484
            source_ids = self.source.get_ancestry(revision_id)
 
3485
            if source_ids[0] is not None:
 
3486
                raise AssertionError()
 
3487
            source_ids.pop(0)
 
3488
        else:
 
3489
            source_ids = self.source.all_revision_ids()
3543
3490
        result_set = set(source_ids).difference(target_ids)
3544
3491
        return self.source.revision_ids_to_search_result(result_set)
3545
3492
 
3546
 
    def _present_source_revisions_for(self, revision_ids, if_present_ids=None):
3547
 
        """Returns set of all revisions in ancestry of revision_ids present in
3548
 
        the source repo.
3549
 
 
3550
 
        :param revision_ids: if None, all revisions in source are returned.
3551
 
        :param if_present_ids: like revision_ids, but if any/all of these are
3552
 
            absent no error is raised.
3553
 
        """
3554
 
        if revision_ids is not None or if_present_ids is not None:
3555
 
            # First, ensure all specified revisions exist.  Callers expect
3556
 
            # NoSuchRevision when they pass absent revision_ids here.
3557
 
            if revision_ids is None:
3558
 
                revision_ids = set()
3559
 
            if if_present_ids is None:
3560
 
                if_present_ids = set()
3561
 
            revision_ids = set(revision_ids)
3562
 
            if_present_ids = set(if_present_ids)
3563
 
            all_wanted_ids = revision_ids.union(if_present_ids)
3564
 
            graph = self.source.get_graph()
3565
 
            present_revs = set(graph.get_parent_map(all_wanted_ids))
3566
 
            missing = revision_ids.difference(present_revs)
3567
 
            if missing:
3568
 
                raise errors.NoSuchRevision(self.source, missing.pop())
3569
 
            found_ids = all_wanted_ids.intersection(present_revs)
3570
 
            source_ids = [rev_id for (rev_id, parents) in
3571
 
                          graph.iter_ancestry(found_ids)
3572
 
                          if rev_id != _mod_revision.NULL_REVISION
3573
 
                          and parents is not None]
3574
 
        else:
3575
 
            source_ids = self.source.all_revision_ids()
3576
 
        return set(source_ids)
3577
 
 
3578
3493
    @staticmethod
3579
3494
    def _same_model(source, target):
3580
3495
        """True if source and target have the same data representation.
3621
3536
        return InterRepository._same_model(source, target)
3622
3537
 
3623
3538
 
 
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
 
3624
3709
class InterDifferingSerializer(InterRepository):
3625
3710
 
3626
3711
    @classmethod
3728
3813
                basis_id, delta, current_revision_id, parents_parents)
3729
3814
            cache[current_revision_id] = parent_tree
3730
3815
 
3731
 
    def _fetch_batch(self, revision_ids, basis_id, cache):
 
3816
    def _fetch_batch(self, revision_ids, basis_id, cache, a_graph=None):
3732
3817
        """Fetch across a few revisions.
3733
3818
 
3734
3819
        :param revision_ids: The revisions to copy
3735
3820
        :param basis_id: The revision_id of a tree that must be in cache, used
3736
3821
            as a basis for delta when no other base is available
3737
3822
        :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.
3738
3825
        :return: The revision_id of the last converted tree. The RevisionTree
3739
3826
            for it will be in cache
3740
3827
        """
3808
3895
        if root_keys_to_create:
3809
3896
            root_stream = _mod_fetch._new_root_data_stream(
3810
3897
                root_keys_to_create, self._revision_id_to_root_id, parent_map,
3811
 
                self.source)
 
3898
                self.source, graph=a_graph)
3812
3899
            to_texts.insert_record_stream(root_stream)
3813
3900
        to_texts.insert_record_stream(from_texts.get_record_stream(
3814
3901
            text_keys, self.target._format._fetch_order,
3871
3958
        cache[basis_id] = basis_tree
3872
3959
        del basis_tree # We don't want to hang on to it here
3873
3960
        hints = []
3874
 
        a_graph = None
 
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
3875
3966
 
3876
3967
        for offset in range(0, len(revision_ids), batch_size):
3877
3968
            self.target.start_write_group()
3879
3970
                pb.update('Transferring revisions', offset,
3880
3971
                          len(revision_ids))
3881
3972
                batch = revision_ids[offset:offset+batch_size]
3882
 
                basis_id = self._fetch_batch(batch, basis_id, cache)
 
3973
                basis_id = self._fetch_batch(batch, basis_id, cache,
 
3974
                                             a_graph=a_graph)
3883
3975
            except:
3884
3976
                self.source._safe_to_return_from_cache = False
3885
3977
                self.target.abort_write_group()
3898
3990
            fetch_spec=None):
3899
3991
        """See InterRepository.fetch()."""
3900
3992
        if fetch_spec is not None:
3901
 
            revision_ids = fetch_spec.get_keys()
3902
 
        else:
3903
 
            revision_ids = None
 
3993
            raise AssertionError("Not implemented yet...")
3904
3994
        ui.ui_factory.warn_experimental_format_fetch(self)
3905
3995
        if (not self.source.supports_rich_root()
3906
3996
            and self.target.supports_rich_root()):
3913
4003
            ui.ui_factory.show_user_warning('cross_format_fetch',
3914
4004
                from_format=self.source._format,
3915
4005
                to_format=self.target._format)
3916
 
        if revision_ids is None:
3917
 
            if revision_id:
3918
 
                search_revision_ids = [revision_id]
3919
 
            else:
3920
 
                search_revision_ids = None
3921
 
            revision_ids = self.target.search_missing_revision_ids(self.source,
3922
 
                revision_ids=search_revision_ids,
3923
 
                find_ghosts=find_ghosts).get_keys()
 
4006
        revision_ids = self.target.search_missing_revision_ids(self.source,
 
4007
            revision_id, find_ghosts=find_ghosts).get_keys()
3924
4008
        if not revision_ids:
3925
4009
            return 0, 0
3926
4010
        revision_ids = tsort.topo_sort(
3959
4043
            basis_id = first_rev.parent_ids[0]
3960
4044
            # only valid as a basis if the target has it
3961
4045
            self.target.get_revision(basis_id)
3962
 
            # Try to get a basis tree - if it's a ghost it will hit the
 
4046
            # Try to get a basis tree - if its a ghost it will hit the
3963
4047
            # NoSuchRevision case.
3964
4048
            basis_tree = self.source.revision_tree(basis_id)
3965
4049
        except (IndexError, errors.NoSuchRevision):
3970
4054
 
3971
4055
InterRepository.register_optimiser(InterDifferingSerializer)
3972
4056
InterRepository.register_optimiser(InterSameDataRepository)
 
4057
InterRepository.register_optimiser(InterWeaveRepo)
 
4058
InterRepository.register_optimiser(InterKnitRepo)
3973
4059
 
3974
4060
 
3975
4061
class CopyConverter(object):
4163
4249
                is_resume = False
4164
4250
            try:
4165
4251
                # locked_insert_stream performs a commit|suspend.
4166
 
                missing_keys = self.insert_stream_without_locking(stream,
4167
 
                                    src_format, is_resume)
4168
 
                if missing_keys:
4169
 
                    # suspend the write group and tell the caller what we is
4170
 
                    # missing. We know we can suspend or else we would not have
4171
 
                    # entered this code path. (All repositories that can handle
4172
 
                    # missing keys can handle suspending a write group).
4173
 
                    write_group_tokens = self.target_repo.suspend_write_group()
4174
 
                    return write_group_tokens, missing_keys
4175
 
                hint = self.target_repo.commit_write_group()
4176
 
                to_serializer = self.target_repo._format._serializer
4177
 
                src_serializer = src_format._serializer
4178
 
                if (to_serializer != src_serializer and
4179
 
                    self.target_repo._format.pack_compresses):
4180
 
                    self.target_repo.pack(hint=hint)
4181
 
                return [], set()
 
4252
                return self._locked_insert_stream(stream, src_format, is_resume)
4182
4253
            except:
4183
4254
                self.target_repo.abort_write_group(suppress_errors=True)
4184
4255
                raise
4185
4256
        finally:
4186
4257
            self.target_repo.unlock()
4187
4258
 
4188
 
    def insert_stream_without_locking(self, stream, src_format,
4189
 
                                      is_resume=False):
4190
 
        """Insert a stream's content into the target repository.
4191
 
 
4192
 
        This assumes that you already have a locked repository and an active
4193
 
        write group.
4194
 
 
4195
 
        :param src_format: a bzr repository format.
4196
 
        :param is_resume: Passed down to get_missing_parent_inventories to
4197
 
            indicate if we should be checking for missing texts at the same
4198
 
            time.
4199
 
 
4200
 
        :return: A set of keys that are missing.
4201
 
        """
4202
 
        if not self.target_repo.is_write_locked():
4203
 
            raise errors.ObjectNotLocked(self)
4204
 
        if not self.target_repo.is_in_write_group():
4205
 
            raise errors.BzrError('you must already be in a write group')
 
4259
    def _locked_insert_stream(self, stream, src_format, is_resume):
4206
4260
        to_serializer = self.target_repo._format._serializer
4207
4261
        src_serializer = src_format._serializer
4208
4262
        new_pack = None
4248
4302
                # required if the serializers are different only in terms of
4249
4303
                # the inventory.
4250
4304
                if src_serializer == to_serializer:
4251
 
                    self.target_repo.revisions.insert_record_stream(substream)
 
4305
                    self.target_repo.revisions.insert_record_stream(
 
4306
                        substream)
4252
4307
                else:
4253
4308
                    self._extract_and_insert_revisions(substream,
4254
4309
                        src_serializer)
4287
4342
            # cannot even attempt suspending, and missing would have failed
4288
4343
            # during stream insertion.
4289
4344
            missing_keys = set()
4290
 
        return missing_keys
 
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()
4291
4358
 
4292
4359
    def _extract_and_insert_inventory_deltas(self, substream, serializer):
4293
4360
        target_rich_root = self.target_repo._format.rich_root_data
4350
4417
        """Create a StreamSource streaming from from_repository."""
4351
4418
        self.from_repository = from_repository
4352
4419
        self.to_format = to_format
4353
 
        self._record_counter = RecordCounter()
4354
4420
 
4355
4421
    def delta_on_metadata(self):
4356
4422
        """Return True if delta's are permitted on metadata streams.