~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-11-05 08:35:39 UTC
  • mfrom: (5521.1.2 670851-directory-and-files)
  • Revision ID: pqm@pqm.ubuntu.com-20101105083539-urkk2to9qakthagn
(vila) Respect --directory when path parameters are also used for shelve and
 restore (Vincent Ladeuil)

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,
 
44
    static_tuple,
43
45
    symbol_versioning,
44
46
    trace,
45
47
    tsort,
46
 
    ui,
47
48
    versionedfile,
48
49
    )
49
50
from bzrlib.bundle import serializer
52
53
from bzrlib.testament import Testament
53
54
""")
54
55
 
 
56
import sys
 
57
from bzrlib import (
 
58
    errors,
 
59
    registry,
 
60
    ui,
 
61
    )
55
62
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
56
63
from bzrlib.inter import InterObject
57
64
from bzrlib.inventory import (
60
67
    ROOT_ID,
61
68
    entry_factory,
62
69
    )
63
 
from bzrlib.lock import _RelockDebugMixin
64
 
from bzrlib import registry
 
70
from bzrlib.recordcounter import RecordCounter
 
71
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
65
72
from bzrlib.trace import (
66
73
    log_exception_quietly, note, mutter, mutter_callsite, warning)
67
74
 
70
77
_deprecation_warning_done = False
71
78
 
72
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
 
73
88
class CommitBuilder(object):
74
89
    """Provides an interface to build up a commit.
75
90
 
100
115
 
101
116
        if committer is None:
102
117
            self._committer = self._config.username()
 
118
        elif not isinstance(committer, unicode):
 
119
            self._committer = committer.decode() # throw if non-ascii
103
120
        else:
104
121
            self._committer = committer
105
122
 
230
247
 
231
248
    def _gen_revision_id(self):
232
249
        """Return new revision-id."""
233
 
        return generate_ids.gen_revision_id(self._config.username(),
234
 
                                            self._timestamp)
 
250
        return generate_ids.gen_revision_id(self._committer, self._timestamp)
235
251
 
236
252
    def _generate_revision_if_needed(self):
237
253
        """Create a revision id if None was supplied.
277
293
 
278
294
        :param tree: The tree which is being committed.
279
295
        """
280
 
        # NB: if there are no parents then this method is not called, so no
281
 
        # need to guard on parents having length.
 
296
        if len(self.parents) == 0:
 
297
            raise errors.RootMissing()
282
298
        entry = entry_factory['directory'](tree.path2id(''), '',
283
299
            None)
284
300
        entry.revision = self._new_revision_id
422
438
            else:
423
439
                # we don't need to commit this, because the caller already
424
440
                # determined that an existing revision of this file is
425
 
                # appropriate. If its not being considered for committing then
 
441
                # appropriate. If it's not being considered for committing then
426
442
                # it and all its parents to the root must be unaltered so
427
443
                # no-change against the basis.
428
444
                if ie.revision == self._new_revision_id:
744
760
                    # after iter_changes examines and decides it has changed,
745
761
                    # we will unconditionally record a new version even if some
746
762
                    # other process reverts it while commit is running (with
747
 
                    # the revert happening after iter_changes did it's
 
763
                    # the revert happening after iter_changes did its
748
764
                    # examination).
749
765
                    if change[7][1]:
750
766
                        entry.executable = True
859
875
        # versioned roots do not change unless the tree found a change.
860
876
 
861
877
 
 
878
class RepositoryWriteLockResult(LogicalLockResult):
 
879
    """The result of write locking a repository.
 
880
 
 
881
    :ivar repository_token: The token obtained from the underlying lock, or
 
882
        None.
 
883
    :ivar unlock: A callable which will unlock the lock.
 
884
    """
 
885
 
 
886
    def __init__(self, unlock, repository_token):
 
887
        LogicalLockResult.__init__(self, unlock)
 
888
        self.repository_token = repository_token
 
889
 
 
890
    def __repr__(self):
 
891
        return "RepositoryWriteLockResult(%s, %s)" % (self.repository_token,
 
892
            self.unlock)
 
893
 
 
894
 
862
895
######################################################################
863
896
# Repositories
864
897
 
865
898
 
866
 
class Repository(_RelockDebugMixin):
 
899
class Repository(_RelockDebugMixin, controldir.ControlComponent):
867
900
    """Repository holding history for one or more branches.
868
901
 
869
902
    The repository holds and retrieves historical information including
916
949
        pointing to .bzr/repository.
917
950
    """
918
951
 
919
 
    # What class to use for a CommitBuilder. Often its simpler to change this
 
952
    # What class to use for a CommitBuilder. Often it's simpler to change this
920
953
    # in a Repository class subclass rather than to override
921
954
    # get_commit_builder.
922
955
    _commit_builder_class = CommitBuilder
1017
1050
                " id and insertion revid (%r, %r)"
1018
1051
                % (inv.revision_id, revision_id))
1019
1052
        if inv.root is None:
1020
 
            raise AssertionError()
 
1053
            raise errors.RootMissing()
1021
1054
        return self._add_inventory_checked(revision_id, inv, parents)
1022
1055
 
1023
1056
    def _add_inventory_checked(self, revision_id, inv, parents):
1290
1323
 
1291
1324
        :param _format: The format of the repository on disk.
1292
1325
        :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
1326
        """
 
1327
        # In the future we will have a single api for all stores for
 
1328
        # getting file texts, inventories and revisions, then
 
1329
        # this construct will accept instances of those things.
1298
1330
        super(Repository, self).__init__()
1299
1331
        self._format = _format
1300
1332
        # the following are part of the public API for Repository:
1315
1347
        # rather copying them?
1316
1348
        self._safe_to_return_from_cache = False
1317
1349
 
 
1350
    @property
 
1351
    def user_transport(self):
 
1352
        return self.bzrdir.user_transport
 
1353
 
 
1354
    @property
 
1355
    def control_transport(self):
 
1356
        return self._transport
 
1357
 
1318
1358
    def __repr__(self):
1319
1359
        if self._fallback_repositories:
1320
1360
            return '%s(%r, fallback_repositories=%r)' % (
1368
1408
        data during reads, and allows a 'write_group' to be obtained. Write
1369
1409
        groups must be used for actual data insertion.
1370
1410
 
 
1411
        A token should be passed in if you know that you have locked the object
 
1412
        some other way, and need to synchronise this object's state with that
 
1413
        fact.
 
1414
 
 
1415
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
 
1416
 
1371
1417
        :param token: if this is already locked, then lock_write will fail
1372
1418
            unless the token matches the existing lock.
1373
1419
        :returns: a token if this instance supports tokens, otherwise None.
1376
1422
        :raises MismatchedToken: if the specified token doesn't match the token
1377
1423
            of the existing lock.
1378
1424
        :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
 
1425
        :return: A RepositoryWriteLockResult.
1385
1426
        """
1386
1427
        locked = self.is_locked()
1387
 
        result = self.control_files.lock_write(token=token)
 
1428
        token = self.control_files.lock_write(token=token)
1388
1429
        if not locked:
1389
1430
            self._warn_if_deprecated()
1390
1431
            self._note_lock('w')
1392
1433
                # Writes don't affect fallback repos
1393
1434
                repo.lock_read()
1394
1435
            self._refresh_data()
1395
 
        return result
 
1436
        return RepositoryWriteLockResult(self.unlock, token)
1396
1437
 
1397
1438
    def lock_read(self):
 
1439
        """Lock the repository for read operations.
 
1440
 
 
1441
        :return: An object with an unlock method which will release the lock
 
1442
            obtained.
 
1443
        """
1398
1444
        locked = self.is_locked()
1399
1445
        self.control_files.lock_read()
1400
1446
        if not locked:
1403
1449
            for repo in self._fallback_repositories:
1404
1450
                repo.lock_read()
1405
1451
            self._refresh_data()
 
1452
        return LogicalLockResult(self.unlock)
1406
1453
 
1407
1454
    def get_physical_lock_status(self):
1408
1455
        return self.control_files.get_physical_lock_status()
1468
1515
 
1469
1516
        # now gather global repository information
1470
1517
        # XXX: This is available for many repos regardless of listability.
1471
 
        if self.bzrdir.root_transport.listable():
 
1518
        if self.user_transport.listable():
1472
1519
            # XXX: do we want to __define len__() ?
1473
1520
            # Maybe the versionedfiles object should provide a different
1474
1521
            # method to get the number of keys.
1506
1553
 
1507
1554
        ret = []
1508
1555
        for branches, repository in bzrdir.BzrDir.find_bzrdirs(
1509
 
                self.bzrdir.root_transport, evaluate=Evaluator()):
 
1556
                self.user_transport, evaluate=Evaluator()):
1510
1557
            if branches is not None:
1511
1558
                ret.extend(branches)
1512
1559
            if not using and repository is not None:
1626
1673
        return missing_keys
1627
1674
 
1628
1675
    def refresh_data(self):
1629
 
        """Re-read any data needed to to synchronise with disk.
 
1676
        """Re-read any data needed to synchronise with disk.
1630
1677
 
1631
1678
        This method is intended to be called after another repository instance
1632
1679
        (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.
 
1680
        repository. On all repositories this will work outside of write groups.
 
1681
        Some repository formats (pack and newer for bzrlib native formats)
 
1682
        support refresh_data inside write groups. If called inside a write
 
1683
        group on a repository that does not support refreshing in a write group
 
1684
        IsInWriteGroupError will be raised.
1635
1685
        """
1636
 
        if self.is_in_write_group():
1637
 
            raise errors.InternalBzrError(
1638
 
                "May not refresh_data while in a write group.")
1639
1686
        self._refresh_data()
1640
1687
 
1641
1688
    def resume_write_group(self, tokens):
1680
1727
                "May not fetch while in a write group.")
1681
1728
        # fast path same-url fetch operations
1682
1729
        # TODO: lift out to somewhere common with RemoteRepository
1683
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/401646>
 
1730
        # <https://bugs.launchpad.net/bzr/+bug/401646>
1684
1731
        if (self.has_same_location(source)
1685
1732
            and fetch_spec is None
1686
1733
            and self._has_same_fallbacks(source)):
2468
2515
            ancestors will be traversed.
2469
2516
        """
2470
2517
        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]
 
2518
        stop_revisions = (None, _mod_revision.NULL_REVISION)
 
2519
        return graph.iter_lefthand_ancestry(revision_id, stop_revisions)
2484
2520
 
2485
2521
    def is_shared(self):
2486
2522
        """Return True if this repository is flagged as a shared repository."""
2580
2616
            keys = tsort.topo_sort(parent_map)
2581
2617
        return [None] + list(keys)
2582
2618
 
2583
 
    def pack(self, hint=None):
 
2619
    def pack(self, hint=None, clean_obsolete_packs=False):
2584
2620
        """Compress the data within the repository.
2585
2621
 
2586
2622
        This operation only makes sense for some repository types. For other
2587
2623
        types it should be a no-op that just returns.
2588
2624
 
2589
2625
        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
 
2626
        @needs_write_lock as this is a long running call it's reasonable to
2591
2627
        implicitly lock for the user.
2592
2628
 
2593
2629
        :param hint: If not supplied, the whole repository is packed.
2596
2632
            obtained from the result of commit_write_group(). Out of
2597
2633
            date hints are simply ignored, because concurrent operations
2598
2634
            can obsolete them rapidly.
 
2635
 
 
2636
        :param clean_obsolete_packs: Clean obsolete packs immediately after
 
2637
            the pack operation.
2599
2638
        """
2600
2639
 
2601
2640
    def get_transaction(self):
2626
2665
    def _make_parents_provider(self):
2627
2666
        return self
2628
2667
 
 
2668
    @needs_read_lock
 
2669
    def get_known_graph_ancestry(self, revision_ids):
 
2670
        """Return the known graph for a set of revision ids and their ancestors.
 
2671
        """
 
2672
        st = static_tuple.StaticTuple
 
2673
        revision_keys = [st(r_id).intern() for r_id in revision_ids]
 
2674
        known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
 
2675
        return graph.GraphThunkIdsToKeys(known_graph)
 
2676
 
2629
2677
    def get_graph(self, other_repository=None):
2630
2678
        """Return the graph walker for this repository format"""
2631
2679
        parents_provider = self._make_parents_provider()
2781
2829
            % (name, from_module),
2782
2830
            DeprecationWarning,
2783
2831
            stacklevel=2)
2784
 
        m = __import__(from_module, globals(), locals(), [name])
2785
2832
        try:
2786
 
            return getattr(m, name)
 
2833
            return pyutils.get_named_object(from_module, name)
2787
2834
        except AttributeError:
2788
2835
            raise AttributeError('module %s has no name %s'
2789
 
                    % (m, name))
 
2836
                    % (sys.modules[from_module], name))
2790
2837
    globals()[name] = _deprecated_repository_forwarder
2791
2838
 
2792
2839
for _name in [
3158
3205
        """
3159
3206
        raise NotImplementedError(self.open)
3160
3207
 
 
3208
    def _run_post_repo_init_hooks(self, repository, a_bzrdir, shared):
 
3209
        from bzrlib.bzrdir import BzrDir, RepoInitHookParams
 
3210
        hooks = BzrDir.hooks['post_repo_init']
 
3211
        if not hooks:
 
3212
            return
 
3213
        params = RepoInitHookParams(repository, self, a_bzrdir, shared)
 
3214
        for hook in hooks:
 
3215
            hook(params)
 
3216
 
3161
3217
 
3162
3218
class MetaDirRepositoryFormat(RepositoryFormat):
3163
3219
    """Common base class for the new repositories using the metadir layout."""
3325
3381
    'bzrlib.repofmt.groupcompress_repo',
3326
3382
    'RepositoryFormat2a',
3327
3383
    )
 
3384
format_registry.register_lazy(
 
3385
    'Bazaar development format 8\n',
 
3386
    'bzrlib.repofmt.groupcompress_repo',
 
3387
    'RepositoryFormat2aSubtree',
 
3388
    )
3328
3389
 
3329
3390
 
3330
3391
class InterRepository(InterObject):
3784
3845
                basis_id, delta, current_revision_id, parents_parents)
3785
3846
            cache[current_revision_id] = parent_tree
3786
3847
 
3787
 
    def _fetch_batch(self, revision_ids, basis_id, cache, a_graph=None):
 
3848
    def _fetch_batch(self, revision_ids, basis_id, cache):
3788
3849
        """Fetch across a few revisions.
3789
3850
 
3790
3851
        :param revision_ids: The revisions to copy
3791
3852
        :param basis_id: The revision_id of a tree that must be in cache, used
3792
3853
            as a basis for delta when no other base is available
3793
3854
        :param cache: A cache of RevisionTrees that we can use.
3794
 
        :param a_graph: A Graph object to determine the heads() of the
3795
 
            rich-root data stream.
3796
3855
        :return: The revision_id of the last converted tree. The RevisionTree
3797
3856
            for it will be in cache
3798
3857
        """
3866
3925
        if root_keys_to_create:
3867
3926
            root_stream = _mod_fetch._new_root_data_stream(
3868
3927
                root_keys_to_create, self._revision_id_to_root_id, parent_map,
3869
 
                self.source, graph=a_graph)
 
3928
                self.source)
3870
3929
            to_texts.insert_record_stream(root_stream)
3871
3930
        to_texts.insert_record_stream(from_texts.get_record_stream(
3872
3931
            text_keys, self.target._format._fetch_order,
3929
3988
        cache[basis_id] = basis_tree
3930
3989
        del basis_tree # We don't want to hang on to it here
3931
3990
        hints = []
3932
 
        if self._converting_to_rich_root and len(revision_ids) > 100:
3933
 
            a_graph = _mod_fetch._get_rich_root_heads_graph(self.source,
3934
 
                                                            revision_ids)
3935
 
        else:
3936
 
            a_graph = None
 
3991
        a_graph = None
3937
3992
 
3938
3993
        for offset in range(0, len(revision_ids), batch_size):
3939
3994
            self.target.start_write_group()
3941
3996
                pb.update('Transferring revisions', offset,
3942
3997
                          len(revision_ids))
3943
3998
                batch = revision_ids[offset:offset+batch_size]
3944
 
                basis_id = self._fetch_batch(batch, basis_id, cache,
3945
 
                                             a_graph=a_graph)
 
3999
                basis_id = self._fetch_batch(batch, basis_id, cache)
3946
4000
            except:
3947
4001
                self.source._safe_to_return_from_cache = False
3948
4002
                self.target.abort_write_group()
4014
4068
            basis_id = first_rev.parent_ids[0]
4015
4069
            # only valid as a basis if the target has it
4016
4070
            self.target.get_revision(basis_id)
4017
 
            # Try to get a basis tree - if its a ghost it will hit the
 
4071
            # Try to get a basis tree - if it's a ghost it will hit the
4018
4072
            # NoSuchRevision case.
4019
4073
            basis_tree = self.source.revision_tree(basis_id)
4020
4074
        except (IndexError, errors.NoSuchRevision):
4220
4274
                is_resume = False
4221
4275
            try:
4222
4276
                # locked_insert_stream performs a commit|suspend.
4223
 
                return self._locked_insert_stream(stream, src_format, is_resume)
 
4277
                return self._locked_insert_stream(stream, src_format,
 
4278
                    is_resume)
4224
4279
            except:
4225
4280
                self.target_repo.abort_write_group(suppress_errors=True)
4226
4281
                raise
4273
4328
                # required if the serializers are different only in terms of
4274
4329
                # the inventory.
4275
4330
                if src_serializer == to_serializer:
4276
 
                    self.target_repo.revisions.insert_record_stream(
4277
 
                        substream)
 
4331
                    self.target_repo.revisions.insert_record_stream(substream)
4278
4332
                else:
4279
4333
                    self._extract_and_insert_revisions(substream,
4280
4334
                        src_serializer)
4388
4442
        """Create a StreamSource streaming from from_repository."""
4389
4443
        self.from_repository = from_repository
4390
4444
        self.to_format = to_format
 
4445
        self._record_counter = RecordCounter()
4391
4446
 
4392
4447
    def delta_on_metadata(self):
4393
4448
        """Return True if delta's are permitted on metadata streams.