~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

Add bzrlib.pyutils, which has get_named_object, a wrapper around __import__.

This is used to replace various ad hoc implementations of the same logic,
notably the version used in registry's _LazyObjectGetter which had a bug when
getting a module without also getting a member.  And of course, this new
function has unit tests, unlike the replaced code.

This also adds a KnownHooksRegistry subclass to provide a more natural home for
some other logic.

I'm not thrilled about the name of the new module or the new functions, but it's
hard to think of good names for such generic functionality.

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,
39
39
    lockdir,
40
40
    lru_cache,
41
41
    osutils,
 
42
    pyutils,
42
43
    revision as _mod_revision,
43
44
    static_tuple,
44
45
    symbol_versioning,
45
46
    trace,
46
47
    tsort,
47
 
    ui,
48
48
    versionedfile,
49
49
    )
50
50
from bzrlib.bundle import serializer
53
53
from bzrlib.testament import Testament
54
54
""")
55
55
 
 
56
import sys
 
57
from bzrlib import (
 
58
    errors,
 
59
    registry,
 
60
    ui,
 
61
    )
56
62
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
57
63
from bzrlib.inter import InterObject
58
64
from bzrlib.inventory import (
61
67
    ROOT_ID,
62
68
    entry_factory,
63
69
    )
64
 
from bzrlib.lock import _RelockDebugMixin
65
 
from bzrlib import registry
 
70
from bzrlib.recordcounter import RecordCounter
 
71
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
66
72
from bzrlib.trace import (
67
73
    log_exception_quietly, note, mutter, mutter_callsite, warning)
68
74
 
71
77
_deprecation_warning_done = False
72
78
 
73
79
 
 
80
class IsInWriteGroupError(errors.InternalBzrError):
 
81
 
 
82
    _fmt = "May not refresh_data of repo %(repo)s while in a write group."
 
83
 
 
84
    def __init__(self, repo):
 
85
        errors.InternalBzrError.__init__(self, repo=repo)
 
86
 
 
87
 
74
88
class CommitBuilder(object):
75
89
    """Provides an interface to build up a commit.
76
90
 
231
245
 
232
246
    def _gen_revision_id(self):
233
247
        """Return new revision-id."""
234
 
        return generate_ids.gen_revision_id(self._config.username(),
235
 
                                            self._timestamp)
 
248
        return generate_ids.gen_revision_id(self._committer, self._timestamp)
236
249
 
237
250
    def _generate_revision_if_needed(self):
238
251
        """Create a revision id if None was supplied.
278
291
 
279
292
        :param tree: The tree which is being committed.
280
293
        """
281
 
        # NB: if there are no parents then this method is not called, so no
282
 
        # need to guard on parents having length.
 
294
        if len(self.parents) == 0:
 
295
            raise errors.RootMissing()
283
296
        entry = entry_factory['directory'](tree.path2id(''), '',
284
297
            None)
285
298
        entry.revision = self._new_revision_id
860
873
        # versioned roots do not change unless the tree found a change.
861
874
 
862
875
 
 
876
class RepositoryWriteLockResult(LogicalLockResult):
 
877
    """The result of write locking a repository.
 
878
 
 
879
    :ivar repository_token: The token obtained from the underlying lock, or
 
880
        None.
 
881
    :ivar unlock: A callable which will unlock the lock.
 
882
    """
 
883
 
 
884
    def __init__(self, unlock, repository_token):
 
885
        LogicalLockResult.__init__(self, unlock)
 
886
        self.repository_token = repository_token
 
887
 
 
888
    def __repr__(self):
 
889
        return "RepositoryWriteLockResult(%s, %s)" % (self.repository_token,
 
890
            self.unlock)
 
891
 
 
892
 
863
893
######################################################################
864
894
# Repositories
865
895
 
866
896
 
867
 
class Repository(_RelockDebugMixin, bzrdir.ControlComponent):
 
897
class Repository(_RelockDebugMixin, controldir.ControlComponent):
868
898
    """Repository holding history for one or more branches.
869
899
 
870
900
    The repository holds and retrieves historical information including
1018
1048
                " id and insertion revid (%r, %r)"
1019
1049
                % (inv.revision_id, revision_id))
1020
1050
        if inv.root is None:
1021
 
            raise AssertionError()
 
1051
            raise errors.RootMissing()
1022
1052
        return self._add_inventory_checked(revision_id, inv, parents)
1023
1053
 
1024
1054
    def _add_inventory_checked(self, revision_id, inv, parents):
1376
1406
        data during reads, and allows a 'write_group' to be obtained. Write
1377
1407
        groups must be used for actual data insertion.
1378
1408
 
 
1409
        A token should be passed in if you know that you have locked the object
 
1410
        some other way, and need to synchronise this object's state with that
 
1411
        fact.
 
1412
 
 
1413
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
 
1414
 
1379
1415
        :param token: if this is already locked, then lock_write will fail
1380
1416
            unless the token matches the existing lock.
1381
1417
        :returns: a token if this instance supports tokens, otherwise None.
1384
1420
        :raises MismatchedToken: if the specified token doesn't match the token
1385
1421
            of the existing lock.
1386
1422
        :seealso: start_write_group.
1387
 
 
1388
 
        A token should be passed in if you know that you have locked the object
1389
 
        some other way, and need to synchronise this object's state with that
1390
 
        fact.
1391
 
 
1392
 
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
 
1423
        :return: A RepositoryWriteLockResult.
1393
1424
        """
1394
1425
        locked = self.is_locked()
1395
 
        result = self.control_files.lock_write(token=token)
 
1426
        token = self.control_files.lock_write(token=token)
1396
1427
        if not locked:
1397
1428
            self._warn_if_deprecated()
1398
1429
            self._note_lock('w')
1400
1431
                # Writes don't affect fallback repos
1401
1432
                repo.lock_read()
1402
1433
            self._refresh_data()
1403
 
        return result
 
1434
        return RepositoryWriteLockResult(self.unlock, token)
1404
1435
 
1405
1436
    def lock_read(self):
 
1437
        """Lock the repository for read operations.
 
1438
 
 
1439
        :return: An object with an unlock method which will release the lock
 
1440
            obtained.
 
1441
        """
1406
1442
        locked = self.is_locked()
1407
1443
        self.control_files.lock_read()
1408
1444
        if not locked:
1411
1447
            for repo in self._fallback_repositories:
1412
1448
                repo.lock_read()
1413
1449
            self._refresh_data()
 
1450
        return LogicalLockResult(self.unlock)
1414
1451
 
1415
1452
    def get_physical_lock_status(self):
1416
1453
        return self.control_files.get_physical_lock_status()
1634
1671
        return missing_keys
1635
1672
 
1636
1673
    def refresh_data(self):
1637
 
        """Re-read any data needed to to synchronise with disk.
 
1674
        """Re-read any data needed to synchronise with disk.
1638
1675
 
1639
1676
        This method is intended to be called after another repository instance
1640
1677
        (such as one used by a smart server) has inserted data into the
1641
 
        repository. It may not be called during a write group, but may be
1642
 
        called at any other time.
 
1678
        repository. On all repositories this will work outside of write groups.
 
1679
        Some repository formats (pack and newer for bzrlib native formats)
 
1680
        support refresh_data inside write groups. If called inside a write
 
1681
        group on a repository that does not support refreshing in a write group
 
1682
        IsInWriteGroupError will be raised.
1643
1683
        """
1644
 
        if self.is_in_write_group():
1645
 
            raise errors.InternalBzrError(
1646
 
                "May not refresh_data while in a write group.")
1647
1684
        self._refresh_data()
1648
1685
 
1649
1686
    def resume_write_group(self, tokens):
1688
1725
                "May not fetch while in a write group.")
1689
1726
        # fast path same-url fetch operations
1690
1727
        # TODO: lift out to somewhere common with RemoteRepository
1691
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/401646>
 
1728
        # <https://bugs.launchpad.net/bzr/+bug/401646>
1692
1729
        if (self.has_same_location(source)
1693
1730
            and fetch_spec is None
1694
1731
            and self._has_same_fallbacks(source)):
2801
2838
            % (name, from_module),
2802
2839
            DeprecationWarning,
2803
2840
            stacklevel=2)
2804
 
        m = __import__(from_module, globals(), locals(), [name])
2805
2841
        try:
2806
 
            return getattr(m, name)
 
2842
            return pyutils.get_named_object(from_module, name)
2807
2843
        except AttributeError:
2808
2844
            raise AttributeError('module %s has no name %s'
2809
 
                    % (m, name))
 
2845
                    % (sys.modules[from_module], name))
2810
2846
    globals()[name] = _deprecated_repository_forwarder
2811
2847
 
2812
2848
for _name in [
3354
3390
    'bzrlib.repofmt.groupcompress_repo',
3355
3391
    'RepositoryFormat2a',
3356
3392
    )
 
3393
format_registry.register_lazy(
 
3394
    'Bazaar development format 8\n',
 
3395
    'bzrlib.repofmt.groupcompress_repo',
 
3396
    'RepositoryFormat2aSubtree',
 
3397
    )
3357
3398
 
3358
3399
 
3359
3400
class InterRepository(InterObject):
3813
3854
                basis_id, delta, current_revision_id, parents_parents)
3814
3855
            cache[current_revision_id] = parent_tree
3815
3856
 
3816
 
    def _fetch_batch(self, revision_ids, basis_id, cache, a_graph=None):
 
3857
    def _fetch_batch(self, revision_ids, basis_id, cache):
3817
3858
        """Fetch across a few revisions.
3818
3859
 
3819
3860
        :param revision_ids: The revisions to copy
3820
3861
        :param basis_id: The revision_id of a tree that must be in cache, used
3821
3862
            as a basis for delta when no other base is available
3822
3863
        :param cache: A cache of RevisionTrees that we can use.
3823
 
        :param a_graph: A Graph object to determine the heads() of the
3824
 
            rich-root data stream.
3825
3864
        :return: The revision_id of the last converted tree. The RevisionTree
3826
3865
            for it will be in cache
3827
3866
        """
3895
3934
        if root_keys_to_create:
3896
3935
            root_stream = _mod_fetch._new_root_data_stream(
3897
3936
                root_keys_to_create, self._revision_id_to_root_id, parent_map,
3898
 
                self.source, graph=a_graph)
 
3937
                self.source)
3899
3938
            to_texts.insert_record_stream(root_stream)
3900
3939
        to_texts.insert_record_stream(from_texts.get_record_stream(
3901
3940
            text_keys, self.target._format._fetch_order,
3958
3997
        cache[basis_id] = basis_tree
3959
3998
        del basis_tree # We don't want to hang on to it here
3960
3999
        hints = []
3961
 
        if self._converting_to_rich_root and len(revision_ids) > 100:
3962
 
            a_graph = _mod_fetch._get_rich_root_heads_graph(self.source,
3963
 
                                                            revision_ids)
3964
 
        else:
3965
 
            a_graph = None
 
4000
        a_graph = None
3966
4001
 
3967
4002
        for offset in range(0, len(revision_ids), batch_size):
3968
4003
            self.target.start_write_group()
3970
4005
                pb.update('Transferring revisions', offset,
3971
4006
                          len(revision_ids))
3972
4007
                batch = revision_ids[offset:offset+batch_size]
3973
 
                basis_id = self._fetch_batch(batch, basis_id, cache,
3974
 
                                             a_graph=a_graph)
 
4008
                basis_id = self._fetch_batch(batch, basis_id, cache)
3975
4009
            except:
3976
4010
                self.source._safe_to_return_from_cache = False
3977
4011
                self.target.abort_write_group()
4249
4283
                is_resume = False
4250
4284
            try:
4251
4285
                # locked_insert_stream performs a commit|suspend.
4252
 
                return self._locked_insert_stream(stream, src_format, is_resume)
 
4286
                return self._locked_insert_stream(stream, src_format,
 
4287
                    is_resume)
4253
4288
            except:
4254
4289
                self.target_repo.abort_write_group(suppress_errors=True)
4255
4290
                raise
4302
4337
                # required if the serializers are different only in terms of
4303
4338
                # the inventory.
4304
4339
                if src_serializer == to_serializer:
4305
 
                    self.target_repo.revisions.insert_record_stream(
4306
 
                        substream)
 
4340
                    self.target_repo.revisions.insert_record_stream(substream)
4307
4341
                else:
4308
4342
                    self._extract_and_insert_revisions(substream,
4309
4343
                        src_serializer)
4417
4451
        """Create a StreamSource streaming from from_repository."""
4418
4452
        self.from_repository = from_repository
4419
4453
        self.to_format = to_format
 
4454
        self._record_counter = RecordCounter()
4420
4455
 
4421
4456
    def delta_on_metadata(self):
4422
4457
        """Return True if delta's are permitted on metadata streams.