~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-09-29 22:03:03 UTC
  • mfrom: (5416.2.6 jam-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20100929220303-cr95h8iwtggco721
(mbp) Add 'break-lock --force'

Show diffs side-by-side

added added

removed removed

Lines of Context:
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,
40
40
    lru_cache,
41
41
    osutils,
42
42
    revision as _mod_revision,
 
43
    static_tuple,
43
44
    symbol_versioning,
44
45
    trace,
45
46
    tsort,
46
 
    ui,
47
47
    versionedfile,
48
48
    )
49
49
from bzrlib.bundle import serializer
52
52
from bzrlib.testament import Testament
53
53
""")
54
54
 
 
55
from bzrlib import (
 
56
    errors,
 
57
    registry,
 
58
    ui,
 
59
    )
55
60
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
56
61
from bzrlib.inter import InterObject
57
62
from bzrlib.inventory import (
60
65
    ROOT_ID,
61
66
    entry_factory,
62
67
    )
63
 
from bzrlib.lock import _RelockDebugMixin
64
 
from bzrlib import registry
 
68
from bzrlib.recordcounter import RecordCounter
 
69
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
65
70
from bzrlib.trace import (
66
71
    log_exception_quietly, note, mutter, mutter_callsite, warning)
67
72
 
70
75
_deprecation_warning_done = False
71
76
 
72
77
 
 
78
class IsInWriteGroupError(errors.InternalBzrError):
 
79
 
 
80
    _fmt = "May not refresh_data of repo %(repo)s while in a write group."
 
81
 
 
82
    def __init__(self, repo):
 
83
        errors.InternalBzrError.__init__(self, repo=repo)
 
84
 
 
85
 
73
86
class CommitBuilder(object):
74
87
    """Provides an interface to build up a commit.
75
88
 
230
243
 
231
244
    def _gen_revision_id(self):
232
245
        """Return new revision-id."""
233
 
        return generate_ids.gen_revision_id(self._config.username(),
234
 
                                            self._timestamp)
 
246
        return generate_ids.gen_revision_id(self._committer, self._timestamp)
235
247
 
236
248
    def _generate_revision_if_needed(self):
237
249
        """Create a revision id if None was supplied.
277
289
 
278
290
        :param tree: The tree which is being committed.
279
291
        """
280
 
        # NB: if there are no parents then this method is not called, so no
281
 
        # need to guard on parents having length.
 
292
        if len(self.parents) == 0:
 
293
            raise errors.RootMissing()
282
294
        entry = entry_factory['directory'](tree.path2id(''), '',
283
295
            None)
284
296
        entry.revision = self._new_revision_id
422
434
            else:
423
435
                # we don't need to commit this, because the caller already
424
436
                # determined that an existing revision of this file is
425
 
                # appropriate. If its not being considered for committing then
 
437
                # appropriate. If it's not being considered for committing then
426
438
                # it and all its parents to the root must be unaltered so
427
439
                # no-change against the basis.
428
440
                if ie.revision == self._new_revision_id:
744
756
                    # after iter_changes examines and decides it has changed,
745
757
                    # we will unconditionally record a new version even if some
746
758
                    # other process reverts it while commit is running (with
747
 
                    # the revert happening after iter_changes did it's
 
759
                    # the revert happening after iter_changes did its
748
760
                    # examination).
749
761
                    if change[7][1]:
750
762
                        entry.executable = True
859
871
        # versioned roots do not change unless the tree found a change.
860
872
 
861
873
 
 
874
class RepositoryWriteLockResult(LogicalLockResult):
 
875
    """The result of write locking a repository.
 
876
 
 
877
    :ivar repository_token: The token obtained from the underlying lock, or
 
878
        None.
 
879
    :ivar unlock: A callable which will unlock the lock.
 
880
    """
 
881
 
 
882
    def __init__(self, unlock, repository_token):
 
883
        LogicalLockResult.__init__(self, unlock)
 
884
        self.repository_token = repository_token
 
885
 
 
886
    def __repr__(self):
 
887
        return "RepositoryWriteLockResult(%s, %s)" % (self.repository_token,
 
888
            self.unlock)
 
889
 
 
890
 
862
891
######################################################################
863
892
# Repositories
864
893
 
865
894
 
866
 
class Repository(_RelockDebugMixin):
 
895
class Repository(_RelockDebugMixin, controldir.ControlComponent):
867
896
    """Repository holding history for one or more branches.
868
897
 
869
898
    The repository holds and retrieves historical information including
916
945
        pointing to .bzr/repository.
917
946
    """
918
947
 
919
 
    # What class to use for a CommitBuilder. Often its simpler to change this
 
948
    # What class to use for a CommitBuilder. Often it's simpler to change this
920
949
    # in a Repository class subclass rather than to override
921
950
    # get_commit_builder.
922
951
    _commit_builder_class = CommitBuilder
1017
1046
                " id and insertion revid (%r, %r)"
1018
1047
                % (inv.revision_id, revision_id))
1019
1048
        if inv.root is None:
1020
 
            raise AssertionError()
 
1049
            raise errors.RootMissing()
1021
1050
        return self._add_inventory_checked(revision_id, inv, parents)
1022
1051
 
1023
1052
    def _add_inventory_checked(self, revision_id, inv, parents):
1290
1319
 
1291
1320
        :param _format: The format of the repository on disk.
1292
1321
        :param a_bzrdir: The BzrDir of the repository.
1293
 
 
1294
 
        In the future we will have a single api for all stores for
1295
 
        getting file texts, inventories and revisions, then
1296
 
        this construct will accept instances of those things.
1297
1322
        """
 
1323
        # In the future we will have a single api for all stores for
 
1324
        # getting file texts, inventories and revisions, then
 
1325
        # this construct will accept instances of those things.
1298
1326
        super(Repository, self).__init__()
1299
1327
        self._format = _format
1300
1328
        # the following are part of the public API for Repository:
1315
1343
        # rather copying them?
1316
1344
        self._safe_to_return_from_cache = False
1317
1345
 
 
1346
    @property
 
1347
    def user_transport(self):
 
1348
        return self.bzrdir.user_transport
 
1349
 
 
1350
    @property
 
1351
    def control_transport(self):
 
1352
        return self._transport
 
1353
 
1318
1354
    def __repr__(self):
1319
1355
        if self._fallback_repositories:
1320
1356
            return '%s(%r, fallback_repositories=%r)' % (
1368
1404
        data during reads, and allows a 'write_group' to be obtained. Write
1369
1405
        groups must be used for actual data insertion.
1370
1406
 
 
1407
        A token should be passed in if you know that you have locked the object
 
1408
        some other way, and need to synchronise this object's state with that
 
1409
        fact.
 
1410
 
 
1411
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
 
1412
 
1371
1413
        :param token: if this is already locked, then lock_write will fail
1372
1414
            unless the token matches the existing lock.
1373
1415
        :returns: a token if this instance supports tokens, otherwise None.
1376
1418
        :raises MismatchedToken: if the specified token doesn't match the token
1377
1419
            of the existing lock.
1378
1420
        :seealso: start_write_group.
1379
 
 
1380
 
        A token should be passed in if you know that you have locked the object
1381
 
        some other way, and need to synchronise this object's state with that
1382
 
        fact.
1383
 
 
1384
 
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
 
1421
        :return: A RepositoryWriteLockResult.
1385
1422
        """
1386
1423
        locked = self.is_locked()
1387
 
        result = self.control_files.lock_write(token=token)
 
1424
        token = self.control_files.lock_write(token=token)
1388
1425
        if not locked:
1389
1426
            self._warn_if_deprecated()
1390
1427
            self._note_lock('w')
1392
1429
                # Writes don't affect fallback repos
1393
1430
                repo.lock_read()
1394
1431
            self._refresh_data()
1395
 
        return result
 
1432
        return RepositoryWriteLockResult(self.unlock, token)
1396
1433
 
1397
1434
    def lock_read(self):
 
1435
        """Lock the repository for read operations.
 
1436
 
 
1437
        :return: An object with an unlock method which will release the lock
 
1438
            obtained.
 
1439
        """
1398
1440
        locked = self.is_locked()
1399
1441
        self.control_files.lock_read()
1400
1442
        if not locked:
1403
1445
            for repo in self._fallback_repositories:
1404
1446
                repo.lock_read()
1405
1447
            self._refresh_data()
 
1448
        return LogicalLockResult(self.unlock)
1406
1449
 
1407
1450
    def get_physical_lock_status(self):
1408
1451
        return self.control_files.get_physical_lock_status()
1468
1511
 
1469
1512
        # now gather global repository information
1470
1513
        # XXX: This is available for many repos regardless of listability.
1471
 
        if self.bzrdir.root_transport.listable():
 
1514
        if self.user_transport.listable():
1472
1515
            # XXX: do we want to __define len__() ?
1473
1516
            # Maybe the versionedfiles object should provide a different
1474
1517
            # method to get the number of keys.
1506
1549
 
1507
1550
        ret = []
1508
1551
        for branches, repository in bzrdir.BzrDir.find_bzrdirs(
1509
 
                self.bzrdir.root_transport, evaluate=Evaluator()):
 
1552
                self.user_transport, evaluate=Evaluator()):
1510
1553
            if branches is not None:
1511
1554
                ret.extend(branches)
1512
1555
            if not using and repository is not None:
1626
1669
        return missing_keys
1627
1670
 
1628
1671
    def refresh_data(self):
1629
 
        """Re-read any data needed to to synchronise with disk.
 
1672
        """Re-read any data needed to synchronise with disk.
1630
1673
 
1631
1674
        This method is intended to be called after another repository instance
1632
1675
        (such as one used by a smart server) has inserted data into the
1633
 
        repository. It may not be called during a write group, but may be
1634
 
        called at any other time.
 
1676
        repository. On all repositories this will work outside of write groups.
 
1677
        Some repository formats (pack and newer for bzrlib native formats)
 
1678
        support refresh_data inside write groups. If called inside a write
 
1679
        group on a repository that does not support refreshing in a write group
 
1680
        IsInWriteGroupError will be raised.
1635
1681
        """
1636
 
        if self.is_in_write_group():
1637
 
            raise errors.InternalBzrError(
1638
 
                "May not refresh_data while in a write group.")
1639
1682
        self._refresh_data()
1640
1683
 
1641
1684
    def resume_write_group(self, tokens):
1680
1723
                "May not fetch while in a write group.")
1681
1724
        # fast path same-url fetch operations
1682
1725
        # TODO: lift out to somewhere common with RemoteRepository
1683
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/401646>
 
1726
        # <https://bugs.launchpad.net/bzr/+bug/401646>
1684
1727
        if (self.has_same_location(source)
1685
1728
            and fetch_spec is None
1686
1729
            and self._has_same_fallbacks(source)):
2468
2511
            ancestors will be traversed.
2469
2512
        """
2470
2513
        graph = self.get_graph()
2471
 
        next_id = revision_id
2472
 
        while True:
2473
 
            if next_id in (None, _mod_revision.NULL_REVISION):
2474
 
                return
2475
 
            try:
2476
 
                parents = graph.get_parent_map([next_id])[next_id]
2477
 
            except KeyError:
2478
 
                raise errors.RevisionNotPresent(next_id, self)
2479
 
            yield next_id
2480
 
            if len(parents) == 0:
2481
 
                return
2482
 
            else:
2483
 
                next_id = parents[0]
 
2514
        stop_revisions = (None, _mod_revision.NULL_REVISION)
 
2515
        return graph.iter_lefthand_ancestry(revision_id, stop_revisions)
2484
2516
 
2485
2517
    def is_shared(self):
2486
2518
        """Return True if this repository is flagged as a shared repository."""
2580
2612
            keys = tsort.topo_sort(parent_map)
2581
2613
        return [None] + list(keys)
2582
2614
 
2583
 
    def pack(self, hint=None):
 
2615
    def pack(self, hint=None, clean_obsolete_packs=False):
2584
2616
        """Compress the data within the repository.
2585
2617
 
2586
2618
        This operation only makes sense for some repository types. For other
2587
2619
        types it should be a no-op that just returns.
2588
2620
 
2589
2621
        This stub method does not require a lock, but subclasses should use
2590
 
        @needs_write_lock as this is a long running call its reasonable to
 
2622
        @needs_write_lock as this is a long running call it's reasonable to
2591
2623
        implicitly lock for the user.
2592
2624
 
2593
2625
        :param hint: If not supplied, the whole repository is packed.
2596
2628
            obtained from the result of commit_write_group(). Out of
2597
2629
            date hints are simply ignored, because concurrent operations
2598
2630
            can obsolete them rapidly.
 
2631
 
 
2632
        :param clean_obsolete_packs: Clean obsolete packs immediately after
 
2633
            the pack operation.
2599
2634
        """
2600
2635
 
2601
2636
    def get_transaction(self):
2626
2661
    def _make_parents_provider(self):
2627
2662
        return self
2628
2663
 
 
2664
    @needs_read_lock
 
2665
    def get_known_graph_ancestry(self, revision_ids):
 
2666
        """Return the known graph for a set of revision ids and their ancestors.
 
2667
        """
 
2668
        st = static_tuple.StaticTuple
 
2669
        revision_keys = [st(r_id).intern() for r_id in revision_ids]
 
2670
        known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
 
2671
        return graph.GraphThunkIdsToKeys(known_graph)
 
2672
 
2629
2673
    def get_graph(self, other_repository=None):
2630
2674
        """Return the graph walker for this repository format"""
2631
2675
        parents_provider = self._make_parents_provider()
3033
3077
    # Is the format experimental ?
3034
3078
    experimental = False
3035
3079
 
3036
 
    def __str__(self):
3037
 
        return "<%s>" % self.__class__.__name__
 
3080
    def __repr__(self):
 
3081
        return "%s()" % self.__class__.__name__
3038
3082
 
3039
3083
    def __eq__(self, other):
3040
3084
        # format objects are generally stateless
3158
3202
        """
3159
3203
        raise NotImplementedError(self.open)
3160
3204
 
 
3205
    def _run_post_repo_init_hooks(self, repository, a_bzrdir, shared):
 
3206
        from bzrlib.bzrdir import BzrDir, RepoInitHookParams
 
3207
        hooks = BzrDir.hooks['post_repo_init']
 
3208
        if not hooks:
 
3209
            return
 
3210
        params = RepoInitHookParams(repository, self, a_bzrdir, shared)
 
3211
        for hook in hooks:
 
3212
            hook(params)
 
3213
 
3161
3214
 
3162
3215
class MetaDirRepositoryFormat(RepositoryFormat):
3163
3216
    """Common base class for the new repositories using the metadir layout."""
3325
3378
    'bzrlib.repofmt.groupcompress_repo',
3326
3379
    'RepositoryFormat2a',
3327
3380
    )
 
3381
format_registry.register_lazy(
 
3382
    'Bazaar development format 8\n',
 
3383
    'bzrlib.repofmt.groupcompress_repo',
 
3384
    'RepositoryFormat2aSubtree',
 
3385
    )
3328
3386
 
3329
3387
 
3330
3388
class InterRepository(InterObject):
3372
3430
        :return: None.
3373
3431
        """
3374
3432
        ui.ui_factory.warn_experimental_format_fetch(self)
3375
 
        f = _mod_fetch.RepoFetcher(to_repository=self.target,
 
3433
        from bzrlib.fetch import RepoFetcher
 
3434
        # See <https://launchpad.net/bugs/456077> asking for a warning here
 
3435
        if self.source._format.network_name() != self.target._format.network_name():
 
3436
            ui.ui_factory.show_user_warning('cross_format_fetch',
 
3437
                from_format=self.source._format,
 
3438
                to_format=self.target._format)
 
3439
        f = RepoFetcher(to_repository=self.target,
3376
3440
                               from_repository=self.source,
3377
3441
                               last_revision=revision_id,
3378
3442
                               fetch_spec=fetch_spec,
3778
3842
                basis_id, delta, current_revision_id, parents_parents)
3779
3843
            cache[current_revision_id] = parent_tree
3780
3844
 
3781
 
    def _fetch_batch(self, revision_ids, basis_id, cache, a_graph=None):
 
3845
    def _fetch_batch(self, revision_ids, basis_id, cache):
3782
3846
        """Fetch across a few revisions.
3783
3847
 
3784
3848
        :param revision_ids: The revisions to copy
3785
3849
        :param basis_id: The revision_id of a tree that must be in cache, used
3786
3850
            as a basis for delta when no other base is available
3787
3851
        :param cache: A cache of RevisionTrees that we can use.
3788
 
        :param a_graph: A Graph object to determine the heads() of the
3789
 
            rich-root data stream.
3790
3852
        :return: The revision_id of the last converted tree. The RevisionTree
3791
3853
            for it will be in cache
3792
3854
        """
3860
3922
        if root_keys_to_create:
3861
3923
            root_stream = _mod_fetch._new_root_data_stream(
3862
3924
                root_keys_to_create, self._revision_id_to_root_id, parent_map,
3863
 
                self.source, graph=a_graph)
 
3925
                self.source)
3864
3926
            to_texts.insert_record_stream(root_stream)
3865
3927
        to_texts.insert_record_stream(from_texts.get_record_stream(
3866
3928
            text_keys, self.target._format._fetch_order,
3923
3985
        cache[basis_id] = basis_tree
3924
3986
        del basis_tree # We don't want to hang on to it here
3925
3987
        hints = []
3926
 
        if self._converting_to_rich_root and len(revision_ids) > 100:
3927
 
            a_graph = _mod_fetch._get_rich_root_heads_graph(self.source,
3928
 
                                                            revision_ids)
3929
 
        else:
3930
 
            a_graph = None
 
3988
        a_graph = None
3931
3989
 
3932
3990
        for offset in range(0, len(revision_ids), batch_size):
3933
3991
            self.target.start_write_group()
3935
3993
                pb.update('Transferring revisions', offset,
3936
3994
                          len(revision_ids))
3937
3995
                batch = revision_ids[offset:offset+batch_size]
3938
 
                basis_id = self._fetch_batch(batch, basis_id, cache,
3939
 
                                             a_graph=a_graph)
 
3996
                basis_id = self._fetch_batch(batch, basis_id, cache)
3940
3997
            except:
3941
3998
                self.source._safe_to_return_from_cache = False
3942
3999
                self.target.abort_write_group()
3956
4013
        """See InterRepository.fetch()."""
3957
4014
        if fetch_spec is not None:
3958
4015
            raise AssertionError("Not implemented yet...")
3959
 
        # See <https://launchpad.net/bugs/456077> asking for a warning here
3960
 
        #
3961
 
        # nb this is only active for local-local fetches; other things using
3962
 
        # streaming.
3963
 
        ui.ui_factory.warn_cross_format_fetch(self.source._format,
3964
 
            self.target._format)
3965
4016
        ui.ui_factory.warn_experimental_format_fetch(self)
3966
4017
        if (not self.source.supports_rich_root()
3967
4018
            and self.target.supports_rich_root()):
3969
4020
            self._revision_id_to_root_id = {}
3970
4021
        else:
3971
4022
            self._converting_to_rich_root = False
 
4023
        # See <https://launchpad.net/bugs/456077> asking for a warning here
 
4024
        if self.source._format.network_name() != self.target._format.network_name():
 
4025
            ui.ui_factory.show_user_warning('cross_format_fetch',
 
4026
                from_format=self.source._format,
 
4027
                to_format=self.target._format)
3972
4028
        revision_ids = self.target.search_missing_revision_ids(self.source,
3973
4029
            revision_id, find_ghosts=find_ghosts).get_keys()
3974
4030
        if not revision_ids:
4009
4065
            basis_id = first_rev.parent_ids[0]
4010
4066
            # only valid as a basis if the target has it
4011
4067
            self.target.get_revision(basis_id)
4012
 
            # Try to get a basis tree - if its a ghost it will hit the
 
4068
            # Try to get a basis tree - if it's a ghost it will hit the
4013
4069
            # NoSuchRevision case.
4014
4070
            basis_tree = self.source.revision_tree(basis_id)
4015
4071
        except (IndexError, errors.NoSuchRevision):
4215
4271
                is_resume = False
4216
4272
            try:
4217
4273
                # locked_insert_stream performs a commit|suspend.
4218
 
                return self._locked_insert_stream(stream, src_format, is_resume)
 
4274
                return self._locked_insert_stream(stream, src_format,
 
4275
                    is_resume)
4219
4276
            except:
4220
4277
                self.target_repo.abort_write_group(suppress_errors=True)
4221
4278
                raise
4257
4314
                    self._extract_and_insert_inventories(
4258
4315
                        substream, src_serializer)
4259
4316
            elif substream_type == 'inventory-deltas':
4260
 
                ui.ui_factory.warn_cross_format_fetch(src_format,
4261
 
                    self.target_repo._format)
4262
4317
                self._extract_and_insert_inventory_deltas(
4263
4318
                    substream, src_serializer)
4264
4319
            elif substream_type == 'chk_bytes':
4270
4325
                # required if the serializers are different only in terms of
4271
4326
                # the inventory.
4272
4327
                if src_serializer == to_serializer:
4273
 
                    self.target_repo.revisions.insert_record_stream(
4274
 
                        substream)
 
4328
                    self.target_repo.revisions.insert_record_stream(substream)
4275
4329
                else:
4276
4330
                    self._extract_and_insert_revisions(substream,
4277
4331
                        src_serializer)
4385
4439
        """Create a StreamSource streaming from from_repository."""
4386
4440
        self.from_repository = from_repository
4387
4441
        self.to_format = to_format
 
4442
        self._record_counter = RecordCounter()
4388
4443
 
4389
4444
    def delta_on_metadata(self):
4390
4445
        """Return True if delta's are permitted on metadata streams.