~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/controldir.py

  • Committer: Jelmer Vernooij
  • Date: 2010-08-02 11:20:39 UTC
  • mto: This revision was merged to the branch mainline in revision 5389.
  • Revision ID: jelmer@samba.org-20100802112039-j96qqgswdytb4are
Rename per_bzrdir => per_controldir.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
"""ControlDir logic. The BzrDir is the basic control directory class.
 
17
"""ControlDir is the basic control directory class.
18
18
 
19
19
The ControlDir class is the base for the control directory used
20
 
by all bzr and foreign formats. For the ".bzr" implementation, 
 
20
by all bzr and foreign formats. For the ".bzr" implementation,
21
21
see bzrlib.bzrdir.BzrDir.
22
22
"""
 
23
 
 
24
from bzrlib.lazy_import import lazy_import
 
25
lazy_import(globals(), """
 
26
from bzrlib import (
 
27
    errors,
 
28
    graph,
 
29
    revision as _mod_revision,
 
30
    urlutils,
 
31
    )
 
32
from bzrlib.push import (
 
33
    PushResult,
 
34
    )
 
35
from bzrlib.trace import (
 
36
    mutter,
 
37
    )
 
38
from bzrlib.transport import (
 
39
    get_transport,
 
40
    local,
 
41
    )
 
42
 
 
43
""")
 
44
 
 
45
class ControlDir(object):
 
46
    """A control directory."""
 
47
 
 
48
    def can_convert_format(self):
 
49
        """Return true if this bzrdir is one whose format we can convert from."""
 
50
        return True
 
51
 
 
52
    def list_branches(self):
 
53
        """Return a sequence of all branches local to this control directory.
 
54
 
 
55
        """
 
56
        try:
 
57
            return [self.open_branch()]
 
58
        except (errors.NotBranchError, errors.NoRepositoryPresent):
 
59
            return []
 
60
 
 
61
    def is_control_filename(self, filename):
 
62
        """True if filename is the name of a path which is reserved for bzrdir's.
 
63
 
 
64
        :param filename: A filename within the root transport of this bzrdir.
 
65
 
 
66
        This is true IF and ONLY IF the filename is part of the namespace reserved
 
67
        for bzr control dirs. Currently this is the '.bzr' directory in the root
 
68
        of the root_transport. it is expected that plugins will need to extend
 
69
        this in the future - for instance to make bzr talk with svn working
 
70
        trees.
 
71
        """
 
72
        raise NotImplementedError(self.is_control_filename)
 
73
 
 
74
    def needs_format_conversion(self, format=None):
 
75
        """Return true if this bzrdir needs convert_format run on it.
 
76
 
 
77
        For instance, if the repository format is out of date but the
 
78
        branch and working tree are not, this should return True.
 
79
 
 
80
        :param format: Optional parameter indicating a specific desired
 
81
                       format we plan to arrive at.
 
82
        """
 
83
        raise NotImplementedError(self.needs_format_conversion)
 
84
 
 
85
    def destroy_repository(self):
 
86
        """Destroy the repository in this BzrDir"""
 
87
        raise NotImplementedError(self.destroy_repository)
 
88
 
 
89
    def create_branch(self, name=None):
 
90
        """Create a branch in this BzrDir.
 
91
 
 
92
        :param name: Name of the colocated branch to create, None for
 
93
            the default branch.
 
94
 
 
95
        The bzrdir's format will control what branch format is created.
 
96
        For more control see BranchFormatXX.create(a_bzrdir).
 
97
        """
 
98
        raise NotImplementedError(self.create_branch)
 
99
 
 
100
    def destroy_branch(self, name=None):
 
101
        """Destroy a branch in this BzrDir.
 
102
 
 
103
        :param name: Name of the branch to destroy, None for the default 
 
104
            branch.
 
105
        """
 
106
        raise NotImplementedError(self.destroy_branch)
 
107
 
 
108
    def create_workingtree(self, revision_id=None, from_branch=None,
 
109
        accelerator_tree=None, hardlink=False):
 
110
        """Create a working tree at this BzrDir.
 
111
 
 
112
        :param revision_id: create it as of this revision id.
 
113
        :param from_branch: override bzrdir branch (for lightweight checkouts)
 
114
        :param accelerator_tree: A tree which can be used for retrieving file
 
115
            contents more quickly than the revision tree, i.e. a workingtree.
 
116
            The revision tree will be used for cases where accelerator_tree's
 
117
            content is different.
 
118
        """
 
119
        raise NotImplementedError(self.create_workingtree)
 
120
 
 
121
    def destroy_workingtree(self):
 
122
        """Destroy the working tree at this BzrDir.
 
123
 
 
124
        Formats that do not support this may raise UnsupportedOperation.
 
125
        """
 
126
        raise NotImplementedError(self.destroy_workingtree)
 
127
 
 
128
    def destroy_workingtree_metadata(self):
 
129
        """Destroy the control files for the working tree at this BzrDir.
 
130
 
 
131
        The contents of working tree files are not affected.
 
132
        Formats that do not support this may raise UnsupportedOperation.
 
133
        """
 
134
        raise NotImplementedError(self.destroy_workingtree_metadata)
 
135
 
 
136
    def get_branch_reference(self, name=None):
 
137
        """Return the referenced URL for the branch in this bzrdir.
 
138
 
 
139
        :param name: Optional colocated branch name
 
140
        :raises NotBranchError: If there is no Branch.
 
141
        :raises NoColocatedBranchSupport: If a branch name was specified
 
142
            but colocated branches are not supported.
 
143
        :return: The URL the branch in this bzrdir references if it is a
 
144
            reference branch, or None for regular branches.
 
145
        """
 
146
        if name is not None:
 
147
            raise errors.NoColocatedBranchSupport(self)
 
148
        return None
 
149
 
 
150
    def get_branch_transport(self, branch_format, name=None):
 
151
        """Get the transport for use by branch format in this BzrDir.
 
152
 
 
153
        Note that bzr dirs that do not support format strings will raise
 
154
        IncompatibleFormat if the branch format they are given has
 
155
        a format string, and vice versa.
 
156
 
 
157
        If branch_format is None, the transport is returned with no
 
158
        checking. If it is not None, then the returned transport is
 
159
        guaranteed to point to an existing directory ready for use.
 
160
        """
 
161
        raise NotImplementedError(self.get_branch_transport)
 
162
 
 
163
    def get_repository_transport(self, repository_format):
 
164
        """Get the transport for use by repository format in this BzrDir.
 
165
 
 
166
        Note that bzr dirs that do not support format strings will raise
 
167
        IncompatibleFormat if the repository format they are given has
 
168
        a format string, and vice versa.
 
169
 
 
170
        If repository_format is None, the transport is returned with no
 
171
        checking. If it is not None, then the returned transport is
 
172
        guaranteed to point to an existing directory ready for use.
 
173
        """
 
174
        raise NotImplementedError(self.get_repository_transport)
 
175
 
 
176
    def get_workingtree_transport(self, tree_format):
 
177
        """Get the transport for use by workingtree format in this BzrDir.
 
178
 
 
179
        Note that bzr dirs that do not support format strings will raise
 
180
        IncompatibleFormat if the workingtree format they are given has a
 
181
        format string, and vice versa.
 
182
 
 
183
        If workingtree_format is None, the transport is returned with no
 
184
        checking. If it is not None, then the returned transport is
 
185
        guaranteed to point to an existing directory ready for use.
 
186
        """
 
187
        raise NotImplementedError(self.get_workingtree_transport)
 
188
 
 
189
    def open_branch(self, name=None, unsupported=False,
 
190
                    ignore_fallbacks=False):
 
191
        """Open the branch object at this BzrDir if one is present.
 
192
 
 
193
        If unsupported is True, then no longer supported branch formats can
 
194
        still be opened.
 
195
 
 
196
        TODO: static convenience version of this?
 
197
        """
 
198
        raise NotImplementedError(self.open_branch)
 
199
 
 
200
    def open_repository(self, _unsupported=False):
 
201
        """Open the repository object at this BzrDir if one is present.
 
202
 
 
203
        This will not follow the Branch object pointer - it's strictly a direct
 
204
        open facility. Most client code should use open_branch().repository to
 
205
        get at a repository.
 
206
 
 
207
        :param _unsupported: a private parameter, not part of the api.
 
208
        TODO: static convenience version of this?
 
209
        """
 
210
        raise NotImplementedError(self.open_repository)
 
211
 
 
212
    def find_repository(self):
 
213
        """Find the repository that should be used.
 
214
 
 
215
        This does not require a branch as we use it to find the repo for
 
216
        new branches as well as to hook existing branches up to their
 
217
        repository.
 
218
        """
 
219
        raise NotImplementedError(self.find_repository)
 
220
 
 
221
    def open_workingtree(self, _unsupported=False,
 
222
                         recommend_upgrade=True, from_branch=None):
 
223
        """Open the workingtree object at this BzrDir if one is present.
 
224
 
 
225
        :param recommend_upgrade: Optional keyword parameter, when True (the
 
226
            default), emit through the ui module a recommendation that the user
 
227
            upgrade the working tree when the workingtree being opened is old
 
228
            (but still fully supported).
 
229
        :param from_branch: override bzrdir branch (for lightweight checkouts)
 
230
        """
 
231
        raise NotImplementedError(self.open_workingtree)
 
232
 
 
233
    def has_branch(self, name=None):
 
234
        """Tell if this bzrdir contains a branch.
 
235
 
 
236
        Note: if you're going to open the branch, you should just go ahead
 
237
        and try, and not ask permission first.  (This method just opens the
 
238
        branch and discards it, and that's somewhat expensive.)
 
239
        """
 
240
        try:
 
241
            self.open_branch(name)
 
242
            return True
 
243
        except errors.NotBranchError:
 
244
            return False
 
245
 
 
246
    def has_workingtree(self):
 
247
        """Tell if this bzrdir contains a working tree.
 
248
 
 
249
        This will still raise an exception if the bzrdir has a workingtree that
 
250
        is remote & inaccessible.
 
251
 
 
252
        Note: if you're going to open the working tree, you should just go ahead
 
253
        and try, and not ask permission first.  (This method just opens the
 
254
        workingtree and discards it, and that's somewhat expensive.)
 
255
        """
 
256
        try:
 
257
            self.open_workingtree(recommend_upgrade=False)
 
258
            return True
 
259
        except errors.NoWorkingTree:
 
260
            return False
 
261
 
 
262
    def cloning_metadir(self, require_stacking=False):
 
263
        """Produce a metadir suitable for cloning or sprouting with.
 
264
 
 
265
        These operations may produce workingtrees (yes, even though they're
 
266
        "cloning" something that doesn't have a tree), so a viable workingtree
 
267
        format must be selected.
 
268
 
 
269
        :require_stacking: If True, non-stackable formats will be upgraded
 
270
            to similar stackable formats.
 
271
        :returns: a BzrDirFormat with all component formats either set
 
272
            appropriately or set to None if that component should not be
 
273
            created.
 
274
        """
 
275
        raise NotImplementedError(self.cloning_metadir)
 
276
 
 
277
    def checkout_metadir(self):
 
278
        """Produce a metadir suitable for checkouts of this controldir."""
 
279
        return self.cloning_metadir()
 
280
 
 
281
    def sprout(self, url, revision_id=None, force_new_repo=False,
 
282
               recurse='down', possible_transports=None,
 
283
               accelerator_tree=None, hardlink=False, stacked=False,
 
284
               source_branch=None, create_tree_if_local=True):
 
285
        """Create a copy of this bzrdir prepared for use as a new line of
 
286
        development.
 
287
 
 
288
        If url's last component does not exist, it will be created.
 
289
 
 
290
        Attributes related to the identity of the source branch like
 
291
        branch nickname will be cleaned, a working tree is created
 
292
        whether one existed before or not; and a local branch is always
 
293
        created.
 
294
 
 
295
        if revision_id is not None, then the clone operation may tune
 
296
            itself to download less data.
 
297
        :param accelerator_tree: A tree which can be used for retrieving file
 
298
            contents more quickly than the revision tree, i.e. a workingtree.
 
299
            The revision tree will be used for cases where accelerator_tree's
 
300
            content is different.
 
301
        :param hardlink: If true, hard-link files from accelerator_tree,
 
302
            where possible.
 
303
        :param stacked: If true, create a stacked branch referring to the
 
304
            location of this control directory.
 
305
        :param create_tree_if_local: If true, a working-tree will be created
 
306
            when working locally.
 
307
        """
 
308
        target_transport = get_transport(url, possible_transports)
 
309
        target_transport.ensure_base()
 
310
        cloning_format = self.cloning_metadir(stacked)
 
311
        # Create/update the result branch
 
312
        result = cloning_format.initialize_on_transport(target_transport)
 
313
        # if a stacked branch wasn't requested, we don't create one
 
314
        # even if the origin was stacked
 
315
        stacked_branch_url = None
 
316
        if source_branch is not None:
 
317
            if stacked:
 
318
                stacked_branch_url = self.root_transport.base
 
319
            source_repository = source_branch.repository
 
320
        else:
 
321
            try:
 
322
                source_branch = self.open_branch()
 
323
                source_repository = source_branch.repository
 
324
                if stacked:
 
325
                    stacked_branch_url = self.root_transport.base
 
326
            except errors.NotBranchError:
 
327
                source_branch = None
 
328
                try:
 
329
                    source_repository = self.open_repository()
 
330
                except errors.NoRepositoryPresent:
 
331
                    source_repository = None
 
332
        repository_policy = result.determine_repository_policy(
 
333
            force_new_repo, stacked_branch_url, require_stacking=stacked)
 
334
        result_repo, is_new_repo = repository_policy.acquire_repository()
 
335
        is_stacked = stacked or (len(result_repo._fallback_repositories) != 0)
 
336
        if is_new_repo and revision_id is not None and not is_stacked:
 
337
            fetch_spec = graph.PendingAncestryResult(
 
338
                [revision_id], source_repository)
 
339
        else:
 
340
            fetch_spec = None
 
341
        if source_repository is not None:
 
342
            # Fetch while stacked to prevent unstacked fetch from
 
343
            # Branch.sprout.
 
344
            if fetch_spec is None:
 
345
                result_repo.fetch(source_repository, revision_id=revision_id)
 
346
            else:
 
347
                result_repo.fetch(source_repository, fetch_spec=fetch_spec)
 
348
 
 
349
        if source_branch is None:
 
350
            # this is for sprouting a bzrdir without a branch; is that
 
351
            # actually useful?
 
352
            # Not especially, but it's part of the contract.
 
353
            result_branch = result.create_branch()
 
354
        else:
 
355
            result_branch = source_branch.sprout(result,
 
356
                revision_id=revision_id, repository_policy=repository_policy)
 
357
        mutter("created new branch %r" % (result_branch,))
 
358
 
 
359
        # Create/update the result working tree
 
360
        if (create_tree_if_local and
 
361
            isinstance(target_transport, local.LocalTransport) and
 
362
            (result_repo is None or result_repo.make_working_trees())):
 
363
            wt = result.create_workingtree(accelerator_tree=accelerator_tree,
 
364
                hardlink=hardlink)
 
365
            wt.lock_write()
 
366
            try:
 
367
                if wt.path2id('') is None:
 
368
                    try:
 
369
                        wt.set_root_id(self.open_workingtree.get_root_id())
 
370
                    except errors.NoWorkingTree:
 
371
                        pass
 
372
            finally:
 
373
                wt.unlock()
 
374
        else:
 
375
            wt = None
 
376
        if recurse == 'down':
 
377
            if wt is not None:
 
378
                basis = wt.basis_tree()
 
379
                basis.lock_read()
 
380
                subtrees = basis.iter_references()
 
381
            elif result_branch is not None:
 
382
                basis = result_branch.basis_tree()
 
383
                basis.lock_read()
 
384
                subtrees = basis.iter_references()
 
385
            elif source_branch is not None:
 
386
                basis = source_branch.basis_tree()
 
387
                basis.lock_read()
 
388
                subtrees = basis.iter_references()
 
389
            else:
 
390
                subtrees = []
 
391
                basis = None
 
392
            try:
 
393
                for path, file_id in subtrees:
 
394
                    target = urlutils.join(url, urlutils.escape(path))
 
395
                    sublocation = source_branch.reference_parent(file_id, path)
 
396
                    sublocation.bzrdir.sprout(target,
 
397
                        basis.get_reference_revision(file_id, path),
 
398
                        force_new_repo=force_new_repo, recurse=recurse,
 
399
                        stacked=stacked)
 
400
            finally:
 
401
                if basis is not None:
 
402
                    basis.unlock()
 
403
        return result
 
404
 
 
405
    def push_branch(self, source, revision_id=None, overwrite=False, 
 
406
        remember=False, create_prefix=False):
 
407
        """Push the source branch into this BzrDir."""
 
408
        br_to = None
 
409
        # If we can open a branch, use its direct repository, otherwise see
 
410
        # if there is a repository without a branch.
 
411
        try:
 
412
            br_to = self.open_branch()
 
413
        except errors.NotBranchError:
 
414
            # Didn't find a branch, can we find a repository?
 
415
            repository_to = self.find_repository()
 
416
        else:
 
417
            # Found a branch, so we must have found a repository
 
418
            repository_to = br_to.repository
 
419
 
 
420
        push_result = PushResult()
 
421
        push_result.source_branch = source
 
422
        if br_to is None:
 
423
            # We have a repository but no branch, copy the revisions, and then
 
424
            # create a branch.
 
425
            repository_to.fetch(source.repository, revision_id=revision_id)
 
426
            br_to = source.clone(self, revision_id=revision_id)
 
427
            if source.get_push_location() is None or remember:
 
428
                source.set_push_location(br_to.base)
 
429
            push_result.stacked_on = None
 
430
            push_result.branch_push_result = None
 
431
            push_result.old_revno = None
 
432
            push_result.old_revid = _mod_revision.NULL_REVISION
 
433
            push_result.target_branch = br_to
 
434
            push_result.master_branch = None
 
435
            push_result.workingtree_updated = False
 
436
        else:
 
437
            # We have successfully opened the branch, remember if necessary:
 
438
            if source.get_push_location() is None or remember:
 
439
                source.set_push_location(br_to.base)
 
440
            try:
 
441
                tree_to = self.open_workingtree()
 
442
            except errors.NotLocalUrl:
 
443
                push_result.branch_push_result = source.push(br_to, 
 
444
                    overwrite, stop_revision=revision_id)
 
445
                push_result.workingtree_updated = False
 
446
            except errors.NoWorkingTree:
 
447
                push_result.branch_push_result = source.push(br_to,
 
448
                    overwrite, stop_revision=revision_id)
 
449
                push_result.workingtree_updated = None # Not applicable
 
450
            else:
 
451
                tree_to.lock_write()
 
452
                try:
 
453
                    push_result.branch_push_result = source.push(
 
454
                        tree_to.branch, overwrite, stop_revision=revision_id)
 
455
                    tree_to.update()
 
456
                finally:
 
457
                    tree_to.unlock()
 
458
                push_result.workingtree_updated = True
 
459
            push_result.old_revno = push_result.branch_push_result.old_revno
 
460
            push_result.old_revid = push_result.branch_push_result.old_revid
 
461
            push_result.target_branch = \
 
462
                push_result.branch_push_result.target_branch
 
463
        return push_result
 
464
 
 
465