/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/bzrdir.py

  • Committer: Robert Collins
  • Date: 2006-02-15 08:11:37 UTC
  • mto: (1534.1.24 integration)
  • mto: This revision was merged to the branch mainline in revision 1554.
  • Revision ID: robertc@robertcollins.net-20060215081137-4c27377517e96dd1
Make format 4/5/6 branches share a single LockableFiles instance across wt/branch/repository.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005 Canonical Ltd
 
2
 
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
 
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
 
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""BzrDir logic. The BzrDir is the basic control directory used by bzr.
 
18
 
 
19
At format 7 this was split out into Branch, Repository and Checkout control
 
20
directories.
 
21
"""
 
22
 
 
23
from copy import deepcopy
 
24
from cStringIO import StringIO
 
25
from unittest import TestSuite
 
26
 
 
27
 
 
28
import bzrlib
 
29
import bzrlib.errors as errors
 
30
from bzrlib.lockable_files import LockableFiles
 
31
from bzrlib.osutils import safe_unicode
 
32
from bzrlib.trace import mutter
 
33
from bzrlib.symbol_versioning import *
 
34
from bzrlib.transport import get_transport
 
35
from bzrlib.transport.local import LocalTransport
 
36
 
 
37
 
 
38
class BzrDir(object):
 
39
    """A .bzr control diretory.
 
40
    
 
41
    BzrDir instances let you create or open any of the things that can be
 
42
    found within .bzr - checkouts, branches and repositories.
 
43
    
 
44
    transport
 
45
        the transport which this bzr dir is rooted at (i.e. file:///.../.bzr/)
 
46
    root_transport
 
47
        a transport connected to the directory this bzr was opened from.
 
48
    """
 
49
 
 
50
    def _check_supported(self, format, allow_unsupported):
 
51
        """Check whether format is a supported format.
 
52
 
 
53
        If allow_unsupported is True, this is a no-op.
 
54
        """
 
55
        if not allow_unsupported and not format.is_supported():
 
56
            raise errors.UnsupportedFormatError(format)
 
57
 
 
58
    def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
 
59
        """Clone this bzrdir and its contents to url verbatim.
 
60
 
 
61
        If urls last component does not exist, it will be created.
 
62
 
 
63
        if revision_id is not None, then the clone operation may tune
 
64
            itself to download less data.
 
65
        :param force_new_repo: Do not use a shared repository for the target 
 
66
                               even if one is available.
 
67
        """
 
68
        self._make_tail(url)
 
69
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
70
        result = self._format.initialize(url)
 
71
        try:
 
72
            local_repo = self.find_repository()
 
73
        except errors.NoRepositoryPresent:
 
74
            local_repo = None
 
75
        if local_repo:
 
76
            # may need to copy content in
 
77
            if force_new_repo:
 
78
                local_repo.clone(result, revision_id=revision_id, basis=basis_repo)
 
79
            else:
 
80
                try:
 
81
                    result_repo = result.find_repository()
 
82
                    # fetch content this dir needs.
 
83
                    if basis_repo:
 
84
                        # XXX FIXME RBC 20060214 need tests for this when the basis
 
85
                        # is incomplete
 
86
                        result_repo.fetch(basis_repo, revision_id=revision_id)
 
87
                    result_repo.fetch(local_repo, revision_id=revision_id)
 
88
                except errors.NoRepositoryPresent:
 
89
                    # needed to make one anyway.
 
90
                    local_repo.clone(result, revision_id=revision_id, basis=basis_repo)
 
91
        # 1 if there is a branch present
 
92
        #   make sure its content is available in the target repository
 
93
        #   clone it.
 
94
        try:
 
95
            self.open_branch().clone(result, revision_id=revision_id)
 
96
        except errors.NotBranchError:
 
97
            pass
 
98
        try:
 
99
            self.open_workingtree().clone(result, basis=basis_tree)
 
100
        except (errors.NoWorkingTree, errors.NotLocalUrl):
 
101
            pass
 
102
        return result
 
103
 
 
104
    def _get_basis_components(self, basis):
 
105
        """Retrieve the basis components that are available at basis."""
 
106
        if basis is None:
 
107
            return None, None, None
 
108
        try:
 
109
            basis_tree = basis.open_workingtree()
 
110
            basis_branch = basis_tree.branch
 
111
            basis_repo = basis_branch.repository
 
112
        except (errors.NoWorkingTree, errors.NotLocalUrl):
 
113
            basis_tree = None
 
114
            try:
 
115
                basis_branch = basis.open_branch()
 
116
                basis_repo = basis_branch.repository
 
117
            except errors.NotBranchError:
 
118
                basis_branch = None
 
119
                try:
 
120
                    basis_repo = basis.open_repository()
 
121
                except errors.NoRepositoryPresent:
 
122
                    basis_repo = None
 
123
        return basis_repo, basis_branch, basis_tree
 
124
 
 
125
    def _make_tail(self, url):
 
126
        segments = url.split('/')
 
127
        if segments and segments[-1] not in ('', '.'):
 
128
            parent = '/'.join(segments[:-1])
 
129
            t = bzrlib.transport.get_transport(parent)
 
130
            try:
 
131
                t.mkdir(segments[-1])
 
132
            except errors.FileExists:
 
133
                pass
 
134
 
 
135
    @staticmethod
 
136
    def create(base):
 
137
        """Create a new BzrDir at the url 'base'.
 
138
        
 
139
        This will call the current default formats initialize with base
 
140
        as the only parameter.
 
141
 
 
142
        If you need a specific format, consider creating an instance
 
143
        of that and calling initialize().
 
144
        """
 
145
        segments = base.split('/')
 
146
        if segments and segments[-1] not in ('', '.'):
 
147
            parent = '/'.join(segments[:-1])
 
148
            t = bzrlib.transport.get_transport(parent)
 
149
            try:
 
150
                t.mkdir(segments[-1])
 
151
            except errors.FileExists:
 
152
                pass
 
153
        return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
 
154
 
 
155
    def create_branch(self):
 
156
        """Create a branch in this BzrDir.
 
157
 
 
158
        The bzrdirs format will control what branch format is created.
 
159
        For more control see BranchFormatXX.create(a_bzrdir).
 
160
        """
 
161
        raise NotImplementedError(self.create_branch)
 
162
 
 
163
    @staticmethod
 
164
    def create_branch_and_repo(base, force_new_repo=False):
 
165
        """Create a new BzrDir, Branch and Repository at the url 'base'.
 
166
 
 
167
        This will use the current default BzrDirFormat, and use whatever 
 
168
        repository format that that uses via bzrdir.create_branch and
 
169
        create_repository. If a shared repository is available that is used
 
170
        preferentially.
 
171
 
 
172
        The created Branch object is returned.
 
173
 
 
174
        :param base: The URL to create the branch at.
 
175
        :param force_new_repo: If True a new repository is always created.
 
176
        """
 
177
        bzrdir = BzrDir.create(base)
 
178
        if force_new_repo:
 
179
            bzrdir.create_repository()
 
180
        try:
 
181
            repo = bzrdir.find_repository()
 
182
        except errors.NoRepositoryPresent:
 
183
            bzrdir.create_repository()
 
184
        return bzrdir.create_branch()
 
185
        
 
186
    @staticmethod
 
187
    def create_branch_convenience(base, force_new_repo=False, force_new_tree=None):
 
188
        """Create a new BzrDir, Branch and Repository at the url 'base'.
 
189
 
 
190
        This is a convenience function - it will use an existing repository
 
191
        if possible, can be told explicitly whether to create a working tree or
 
192
        nor.
 
193
 
 
194
        This will use the current default BzrDirFormat, and use whatever 
 
195
        repository format that that uses via bzrdir.create_branch and
 
196
        create_repository. If a shared repository is available that is used
 
197
        preferentially. Whatever repository is used, its tree creation policy
 
198
        is followed.
 
199
 
 
200
        The created Branch object is returned.
 
201
        If a working tree cannot be made due to base not being a file:// url,
 
202
        no error is raised.
 
203
 
 
204
        :param base: The URL to create the branch at.
 
205
        :param force_new_repo: If True a new repository is always created.
 
206
        :param force_new_tree: If True or False force creation of a tree or 
 
207
                               prevent such creation respectively.
 
208
        """
 
209
        bzrdir = BzrDir.create(base)
 
210
        if force_new_repo:
 
211
            bzrdir.create_repository()
 
212
        try:
 
213
            repo = bzrdir.find_repository()
 
214
        except errors.NoRepositoryPresent:
 
215
            repo = bzrdir.create_repository()
 
216
        result = bzrdir.create_branch()
 
217
        if force_new_tree or (repo.make_working_trees() and 
 
218
                              force_new_tree is None):
 
219
            bzrdir.create_workingtree()
 
220
        return result
 
221
        
 
222
    @staticmethod
 
223
    def create_repository(base, shared=False):
 
224
        """Create a new BzrDir and Repository at the url 'base'.
 
225
 
 
226
        This will use the current default BzrDirFormat, and use whatever 
 
227
        repository format that that uses for bzrdirformat.create_repository.
 
228
 
 
229
        ;param shared: Create a shared repository rather than a standalone
 
230
                       repository.
 
231
        The Repository object is returned.
 
232
 
 
233
        This must be overridden as an instance method in child classes, where
 
234
        it should take no parameters and construct whatever repository format
 
235
        that child class desires.
 
236
        """
 
237
        bzrdir = BzrDir.create(base)
 
238
        return bzrdir.create_repository()
 
239
 
 
240
    @staticmethod
 
241
    def create_standalone_workingtree(base):
 
242
        """Create a new BzrDir, WorkingTree, Branch and Repository at 'base'.
 
243
 
 
244
        'base' must be a local path or a file:// url.
 
245
 
 
246
        This will use the current default BzrDirFormat, and use whatever 
 
247
        repository format that that uses for bzrdirformat.create_workingtree,
 
248
        create_branch and create_repository.
 
249
 
 
250
        The WorkingTree object is returned.
 
251
        """
 
252
        t = get_transport(safe_unicode(base))
 
253
        if not isinstance(t, LocalTransport):
 
254
            raise errors.NotLocalUrl(base)
 
255
        bzrdir = BzrDir.create_branch_and_repo(safe_unicode(base),
 
256
                                               force_new_repo=True).bzrdir
 
257
        return bzrdir.create_workingtree()
 
258
 
 
259
    def create_workingtree(self, revision_id=None):
 
260
        """Create a working tree at this BzrDir.
 
261
        
 
262
        revision_id: create it as of this revision id.
 
263
        """
 
264
        raise NotImplementedError(self.create_workingtree)
 
265
 
 
266
    def find_repository(self):
 
267
        """Find the repository that should be used for a_bzrdir.
 
268
 
 
269
        This does not require a branch as we use it to find the repo for
 
270
        new branches as well as to hook existing branches up to their
 
271
        repository.
 
272
        """
 
273
        try:
 
274
            return self.open_repository()
 
275
        except errors.NoRepositoryPresent:
 
276
            pass
 
277
        next_transport = self.root_transport.clone('..')
 
278
        while True:
 
279
            try:
 
280
                found_bzrdir = BzrDir.open_containing_transport(
 
281
                    next_transport)[0]
 
282
            except errors.NotBranchError:
 
283
                raise errors.NoRepositoryPresent(self)
 
284
            try:
 
285
                repository = found_bzrdir.open_repository()
 
286
            except errors.NoRepositoryPresent:
 
287
                next_transport = found_bzrdir.root_transport.clone('..')
 
288
                continue
 
289
            if ((found_bzrdir.root_transport.base == 
 
290
                 self.root_transport.base) or repository.is_shared()):
 
291
                return repository
 
292
            else:
 
293
                raise errors.NoRepositoryPresent(self)
 
294
        raise errors.NoRepositoryPresent(self)
 
295
 
 
296
    def get_branch_transport(self, branch_format):
 
297
        """Get the transport for use by branch format in this BzrDir.
 
298
 
 
299
        Note that bzr dirs that do not support format strings will raise
 
300
        IncompatibleFormat if the branch format they are given has
 
301
        a format string, and vice verca.
 
302
 
 
303
        If branch_format is None, the transport is returned with no 
 
304
        checking. if it is not None, then the returned transport is
 
305
        guaranteed to point to an existing directory ready for use.
 
306
        """
 
307
        raise NotImplementedError(self.get_branch_transport)
 
308
        
 
309
    def get_repository_transport(self, repository_format):
 
310
        """Get the transport for use by repository format in this BzrDir.
 
311
 
 
312
        Note that bzr dirs that do not support format strings will raise
 
313
        IncompatibleFormat if the repository format they are given has
 
314
        a format string, and vice verca.
 
315
 
 
316
        If repository_format is None, the transport is returned with no 
 
317
        checking. if it is not None, then the returned transport is
 
318
        guaranteed to point to an existing directory ready for use.
 
319
        """
 
320
        raise NotImplementedError(self.get_repository_transport)
 
321
        
 
322
    def get_workingtree_transport(self, tree_format):
 
323
        """Get the transport for use by workingtree format in this BzrDir.
 
324
 
 
325
        Note that bzr dirs that do not support format strings will raise
 
326
        IncompatibleFormat if the workingtree format they are given has
 
327
        a format string, and vice verca.
 
328
 
 
329
        If workingtree_format is None, the transport is returned with no 
 
330
        checking. if it is not None, then the returned transport is
 
331
        guaranteed to point to an existing directory ready for use.
 
332
        """
 
333
        raise NotImplementedError(self.get_workingtree_transport)
 
334
        
 
335
    def __init__(self, _transport, _format):
 
336
        """Initialize a Bzr control dir object.
 
337
        
 
338
        Only really common logic should reside here, concrete classes should be
 
339
        made with varying behaviours.
 
340
 
 
341
        :param _format: the format that is creating this BzrDir instance.
 
342
        :param _transport: the transport this dir is based at.
 
343
        """
 
344
        self._format = _format
 
345
        self.transport = _transport.clone('.bzr')
 
346
        self.root_transport = _transport
 
347
 
 
348
    @staticmethod
 
349
    def open_unsupported(base):
 
350
        """Open a branch which is not supported."""
 
351
        return BzrDir.open(base, _unsupported=True)
 
352
        
 
353
    @staticmethod
 
354
    def open(base, _unsupported=False):
 
355
        """Open an existing bzrdir, rooted at 'base' (url)
 
356
        
 
357
        _unsupported is a private parameter to the BzrDir class.
 
358
        """
 
359
        t = get_transport(base)
 
360
        mutter("trying to open %r with transport %r", base, t)
 
361
        format = BzrDirFormat.find_format(t)
 
362
        if not _unsupported and not format.is_supported():
 
363
            # see open_downlevel to open legacy branches.
 
364
            raise errors.UnsupportedFormatError(
 
365
                    'sorry, format %s not supported' % format,
 
366
                    ['use a different bzr version',
 
367
                     'or remove the .bzr directory'
 
368
                     ' and "bzr init" again'])
 
369
        return format.open(t, _found=True)
 
370
 
 
371
    def open_branch(self, unsupported=False):
 
372
        """Open the branch object at this BzrDir if one is present.
 
373
 
 
374
        If unsupported is True, then no longer supported branch formats can
 
375
        still be opened.
 
376
        
 
377
        TODO: static convenience version of this?
 
378
        """
 
379
        raise NotImplementedError(self.open_branch)
 
380
 
 
381
    @staticmethod
 
382
    def open_containing(url):
 
383
        """Open an existing branch which contains url.
 
384
        
 
385
        :param url: url to search from.
 
386
        See open_containing_transport for more detail.
 
387
        """
 
388
        return BzrDir.open_containing_transport(get_transport(url))
 
389
    
 
390
    @staticmethod
 
391
    def open_containing_transport(a_transport):
 
392
        """Open an existing branch which contains a_transport.base
 
393
 
 
394
        This probes for a branch at a_transport, and searches upwards from there.
 
395
 
 
396
        Basically we keep looking up until we find the control directory or
 
397
        run into the root.  If there isn't one, raises NotBranchError.
 
398
        If there is one and it is either an unrecognised format or an unsupported 
 
399
        format, UnknownFormatError or UnsupportedFormatError are raised.
 
400
        If there is one, it is returned, along with the unused portion of url.
 
401
        """
 
402
        # this gets the normalised url back. I.e. '.' -> the full path.
 
403
        url = a_transport.base
 
404
        while True:
 
405
            try:
 
406
                format = BzrDirFormat.find_format(a_transport)
 
407
                return format.open(a_transport), a_transport.relpath(url)
 
408
            except errors.NotBranchError, e:
 
409
                mutter('not a branch in: %r %s', a_transport.base, e)
 
410
            new_t = a_transport.clone('..')
 
411
            if new_t.base == a_transport.base:
 
412
                # reached the root, whatever that may be
 
413
                raise errors.NotBranchError(path=url)
 
414
            a_transport = new_t
 
415
 
 
416
    def open_repository(self, _unsupported=False):
 
417
        """Open the repository object at this BzrDir if one is present.
 
418
 
 
419
        This will not follow the Branch object pointer - its strictly a direct
 
420
        open facility. Most client code should use open_branch().repository to
 
421
        get at a repository.
 
422
 
 
423
        _unsupported is a private parameter, not part of the api.
 
424
        TODO: static convenience version of this?
 
425
        """
 
426
        raise NotImplementedError(self.open_repository)
 
427
 
 
428
    def open_workingtree(self, _unsupported=False):
 
429
        """Open the workingtree object at this BzrDir if one is present.
 
430
        
 
431
        TODO: static convenience version of this?
 
432
        """
 
433
        raise NotImplementedError(self.open_workingtree)
 
434
 
 
435
    def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
 
436
        """Create a copy of this bzrdir prepared for use as a new line of
 
437
        development.
 
438
 
 
439
        If urls last component does not exist, it will be created.
 
440
 
 
441
        Attributes related to the identity of the source branch like
 
442
        branch nickname will be cleaned, a working tree is created
 
443
        whether one existed before or not; and a local branch is always
 
444
        created.
 
445
 
 
446
        if revision_id is not None, then the clone operation may tune
 
447
            itself to download less data.
 
448
        """
 
449
        self._make_tail(url)
 
450
        result = self._format.initialize(url)
 
451
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
452
        try:
 
453
            source_branch = self.open_branch()
 
454
            source_repository = source_branch.repository
 
455
        except errors.NotBranchError:
 
456
            source_branch = None
 
457
            try:
 
458
                source_repository = self.open_repository()
 
459
            except errors.NoRepositoryPresent:
 
460
                # copy the entire basis one if there is one
 
461
                # but there is no repository.
 
462
                source_repository = basis_repo
 
463
        if force_new_repo:
 
464
            result_repo = None
 
465
        else:
 
466
            try:
 
467
                result_repo = result.find_repository()
 
468
            except errors.NoRepositoryPresent:
 
469
                result_repo = None
 
470
        if source_repository is None and result_repo is not None:
 
471
            pass
 
472
        elif source_repository is None and result_repo is None:
 
473
            # no repo available, make a new one
 
474
            result.create_repository()
 
475
        elif source_repository is not None and result_repo is None:
 
476
            # have soure, and want to make a new target repo
 
477
            source_repository.clone(result,
 
478
                                    revision_id=revision_id,
 
479
                                    basis=basis_repo)
 
480
        else:
 
481
            # fetch needed content into target.
 
482
            if basis_repo:
 
483
                # XXX FIXME RBC 20060214 need tests for this when the basis
 
484
                # is incomplete
 
485
                result_repo.fetch(basis_repo, revision_id=revision_id)
 
486
            result_repo.fetch(source_repository, revision_id=revision_id)
 
487
        if source_branch is not None:
 
488
            source_branch.sprout(result, revision_id=revision_id)
 
489
        else:
 
490
            result.create_branch()
 
491
        try:
 
492
            self.open_workingtree().clone(result,
 
493
                                          revision_id=revision_id, 
 
494
                                          basis=basis_tree)
 
495
        except (errors.NoWorkingTree, errors.NotLocalUrl):
 
496
            result.create_workingtree()
 
497
        return result
 
498
 
 
499
 
 
500
class BzrDirPreSplitOut(BzrDir):
 
501
    """A common class for the all-in-one formats."""
 
502
 
 
503
    def __init__(self, _transport, _format):
 
504
        """See BzrDir.__init__."""
 
505
        super(BzrDirPreSplitOut, self).__init__(_transport, _format)
 
506
        self._control_files = LockableFiles(self.get_branch_transport(None),
 
507
                                            'branch-lock')
 
508
 
 
509
    def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
 
510
        """See BzrDir.clone()."""
 
511
        from bzrlib.workingtree import WorkingTreeFormat2
 
512
        self._make_tail(url)
 
513
        result = self._format.initialize(url, _cloning=True)
 
514
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
515
        self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
 
516
        self.open_branch().clone(result, revision_id=revision_id)
 
517
        try:
 
518
            self.open_workingtree().clone(result, basis=basis_tree)
 
519
        except errors.NotLocalUrl:
 
520
            # make a new one, this format always has to have one.
 
521
            WorkingTreeFormat2().initialize(result)
 
522
        return result
 
523
 
 
524
    def create_branch(self):
 
525
        """See BzrDir.create_branch."""
 
526
        return self.open_branch()
 
527
 
 
528
    def create_repository(self, shared=False):
 
529
        """See BzrDir.create_repository."""
 
530
        if shared:
 
531
            raise errors.IncompatibleFormat('shared repository', self._format)
 
532
        return self.open_repository()
 
533
 
 
534
    def create_workingtree(self, revision_id=None):
 
535
        """See BzrDir.create_workingtree."""
 
536
        # this looks buggy but is not -really-
 
537
        # clone and sprout will have set the revision_id
 
538
        # and that will have set it for us, its only
 
539
        # specific uses of create_workingtree in isolation
 
540
        # that can do wonky stuff here, and that only
 
541
        # happens for creating checkouts, which cannot be 
 
542
        # done on this format anyway. So - acceptable wart.
 
543
        result = self.open_workingtree()
 
544
        if revision_id is not None:
 
545
            result.set_last_revision(revision_id)
 
546
        return result
 
547
 
 
548
    def get_branch_transport(self, branch_format):
 
549
        """See BzrDir.get_branch_transport()."""
 
550
        if branch_format is None:
 
551
            return self.transport
 
552
        try:
 
553
            branch_format.get_format_string()
 
554
        except NotImplementedError:
 
555
            return self.transport
 
556
        raise errors.IncompatibleFormat(branch_format, self._format)
 
557
 
 
558
    def get_repository_transport(self, repository_format):
 
559
        """See BzrDir.get_repository_transport()."""
 
560
        if repository_format is None:
 
561
            return self.transport
 
562
        try:
 
563
            repository_format.get_format_string()
 
564
        except NotImplementedError:
 
565
            return self.transport
 
566
        raise errors.IncompatibleFormat(repository_format, self._format)
 
567
 
 
568
    def get_workingtree_transport(self, workingtree_format):
 
569
        """See BzrDir.get_workingtree_transport()."""
 
570
        if workingtree_format is None:
 
571
            return self.transport
 
572
        try:
 
573
            workingtree_format.get_format_string()
 
574
        except NotImplementedError:
 
575
            return self.transport
 
576
        raise errors.IncompatibleFormat(workingtree_format, self._format)
 
577
 
 
578
    def open_branch(self, unsupported=False):
 
579
        """See BzrDir.open_branch."""
 
580
        from bzrlib.branch import BzrBranchFormat4
 
581
        format = BzrBranchFormat4()
 
582
        self._check_supported(format, unsupported)
 
583
        return format.open(self, _found=True)
 
584
 
 
585
    def sprout(self, url, revision_id=None, basis=None):
 
586
        """See BzrDir.sprout()."""
 
587
        from bzrlib.workingtree import WorkingTreeFormat2
 
588
        self._make_tail(url)
 
589
        result = self._format.initialize(url, _cloning=True)
 
590
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
591
        try:
 
592
            self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
 
593
        except errors.NoRepositoryPresent:
 
594
            pass
 
595
        try:
 
596
            self.open_branch().sprout(result, revision_id=revision_id)
 
597
        except errors.NotBranchError:
 
598
            pass
 
599
        try:
 
600
            self.open_workingtree().clone(result, basis=basis_tree)
 
601
        except (errors.NotBranchError, errors.NotLocalUrl):
 
602
            # we always want a working tree
 
603
            WorkingTreeFormat2().initialize(result)
 
604
        return result
 
605
 
 
606
 
 
607
class BzrDir4(BzrDirPreSplitOut):
 
608
    """A .bzr version 4 control object.
 
609
    
 
610
    This is a deprecated format and may be removed after sept 2006.
 
611
    """
 
612
 
 
613
    def create_repository(self, shared=False):
 
614
        """See BzrDir.create_repository."""
 
615
        from bzrlib.repository import RepositoryFormat4
 
616
        return RepositoryFormat4().initialize(self, shared)
 
617
 
 
618
    def open_repository(self):
 
619
        """See BzrDir.open_repository."""
 
620
        from bzrlib.repository import RepositoryFormat4
 
621
        return RepositoryFormat4().open(self, _found=True)
 
622
 
 
623
 
 
624
class BzrDir5(BzrDirPreSplitOut):
 
625
    """A .bzr version 5 control object.
 
626
 
 
627
    This is a deprecated format and may be removed after sept 2006.
 
628
    """
 
629
 
 
630
    def open_repository(self):
 
631
        """See BzrDir.open_repository."""
 
632
        from bzrlib.repository import RepositoryFormat5
 
633
        return RepositoryFormat5().open(self, _found=True)
 
634
 
 
635
    def open_workingtree(self, _unsupported=False):
 
636
        """See BzrDir.create_workingtree."""
 
637
        from bzrlib.workingtree import WorkingTreeFormat2
 
638
        return WorkingTreeFormat2().open(self, _found=True)
 
639
 
 
640
 
 
641
class BzrDir6(BzrDirPreSplitOut):
 
642
    """A .bzr version 6 control object.
 
643
 
 
644
    This is a deprecated format and may be removed after sept 2006.
 
645
    """
 
646
 
 
647
    def open_repository(self):
 
648
        """See BzrDir.open_repository."""
 
649
        from bzrlib.repository import RepositoryFormat6
 
650
        return RepositoryFormat6().open(self, _found=True)
 
651
 
 
652
    def open_workingtree(self, _unsupported=False):
 
653
        """See BzrDir.create_workingtree."""
 
654
        from bzrlib.workingtree import WorkingTreeFormat2
 
655
        return WorkingTreeFormat2().open(self, _found=True)
 
656
 
 
657
 
 
658
class BzrDirMeta1(BzrDir):
 
659
    """A .bzr meta version 1 control object.
 
660
    
 
661
    This is the first control object where the 
 
662
    individual formats are really split out.
 
663
    """
 
664
 
 
665
    def create_branch(self):
 
666
        """See BzrDir.create_branch."""
 
667
        from bzrlib.branch import BranchFormat
 
668
        return BranchFormat.get_default_format().initialize(self)
 
669
 
 
670
    def create_repository(self, shared=False):
 
671
        """See BzrDir.create_repository."""
 
672
        from bzrlib.repository import RepositoryFormat
 
673
        return RepositoryFormat.get_default_format().initialize(self, shared)
 
674
 
 
675
    def create_workingtree(self, revision_id=None):
 
676
        """See BzrDir.create_workingtree."""
 
677
        from bzrlib.workingtree import WorkingTreeFormat
 
678
        return WorkingTreeFormat.get_default_format().initialize(self, revision_id)
 
679
 
 
680
    def get_branch_transport(self, branch_format):
 
681
        """See BzrDir.get_branch_transport()."""
 
682
        if branch_format is None:
 
683
            return self.transport.clone('branch')
 
684
        try:
 
685
            branch_format.get_format_string()
 
686
        except NotImplementedError:
 
687
            raise errors.IncompatibleFormat(branch_format, self._format)
 
688
        try:
 
689
            self.transport.mkdir('branch')
 
690
        except errors.FileExists:
 
691
            pass
 
692
        return self.transport.clone('branch')
 
693
 
 
694
    def get_repository_transport(self, repository_format):
 
695
        """See BzrDir.get_repository_transport()."""
 
696
        if repository_format is None:
 
697
            return self.transport.clone('repository')
 
698
        try:
 
699
            repository_format.get_format_string()
 
700
        except NotImplementedError:
 
701
            raise errors.IncompatibleFormat(repository_format, self._format)
 
702
        try:
 
703
            self.transport.mkdir('repository')
 
704
        except errors.FileExists:
 
705
            pass
 
706
        return self.transport.clone('repository')
 
707
 
 
708
    def get_workingtree_transport(self, workingtree_format):
 
709
        """See BzrDir.get_workingtree_transport()."""
 
710
        if workingtree_format is None:
 
711
            return self.transport.clone('checkout')
 
712
        try:
 
713
            workingtree_format.get_format_string()
 
714
        except NotImplementedError:
 
715
            raise errors.IncompatibleFormat(workingtree_format, self._format)
 
716
        try:
 
717
            self.transport.mkdir('checkout')
 
718
        except errors.FileExists:
 
719
            pass
 
720
        return self.transport.clone('checkout')
 
721
 
 
722
    def open_branch(self, unsupported=False):
 
723
        """See BzrDir.open_branch."""
 
724
        from bzrlib.branch import BranchFormat
 
725
        format = BranchFormat.find_format(self)
 
726
        self._check_supported(format, unsupported)
 
727
        return format.open(self, _found=True)
 
728
 
 
729
    def open_repository(self, unsupported=False):
 
730
        """See BzrDir.open_repository."""
 
731
        from bzrlib.repository import RepositoryFormat
 
732
        format = RepositoryFormat.find_format(self)
 
733
        self._check_supported(format, unsupported)
 
734
        return format.open(self, _found=True)
 
735
 
 
736
    def open_workingtree(self, unsupported=False):
 
737
        """See BzrDir.open_workingtree."""
 
738
        from bzrlib.workingtree import WorkingTreeFormat
 
739
        format = WorkingTreeFormat.find_format(self)
 
740
        self._check_supported(format, unsupported)
 
741
        return format.open(self, _found=True)
 
742
 
 
743
 
 
744
class BzrDirFormat(object):
 
745
    """An encapsulation of the initialization and open routines for a format.
 
746
 
 
747
    Formats provide three things:
 
748
     * An initialization routine,
 
749
     * a format string,
 
750
     * an open routine.
 
751
 
 
752
    Formats are placed in an dict by their format string for reference 
 
753
    during bzrdir opening. These should be subclasses of BzrDirFormat
 
754
    for consistency.
 
755
 
 
756
    Once a format is deprecated, just deprecate the initialize and open
 
757
    methods on the format class. Do not deprecate the object, as the 
 
758
    object will be created every system load.
 
759
    """
 
760
 
 
761
    _default_format = None
 
762
    """The default format used for new .bzr dirs."""
 
763
 
 
764
    _formats = {}
 
765
    """The known formats."""
 
766
 
 
767
    @classmethod
 
768
    def find_format(klass, transport):
 
769
        """Return the format registered for URL."""
 
770
        try:
 
771
            format_string = transport.get(".bzr/branch-format").read()
 
772
            return klass._formats[format_string]
 
773
        except errors.NoSuchFile:
 
774
            raise errors.NotBranchError(path=transport.base)
 
775
        except KeyError:
 
776
            raise errors.UnknownFormatError(format_string)
 
777
 
 
778
    @classmethod
 
779
    def get_default_format(klass):
 
780
        """Return the current default format."""
 
781
        return klass._default_format
 
782
 
 
783
    def get_format_string(self):
 
784
        """Return the ASCII format string that identifies this format."""
 
785
        raise NotImplementedError(self.get_format_string)
 
786
 
 
787
    def initialize(self, url):
 
788
        """Create a bzr control dir at this url and return an opened copy."""
 
789
        # Since we don't have a .bzr directory, inherit the
 
790
        # mode from the root directory
 
791
        t = get_transport(url)
 
792
        temp_control = LockableFiles(t, '')
 
793
        temp_control._transport.mkdir('.bzr',
 
794
                                      # FIXME: RBC 20060121 dont peek under
 
795
                                      # the covers
 
796
                                      mode=temp_control._dir_mode)
 
797
        file_mode = temp_control._file_mode
 
798
        del temp_control
 
799
        mutter('created control directory in ' + t.base)
 
800
        control = t.clone('.bzr')
 
801
        lock_file = 'branch-lock'
 
802
        utf8_files = [('README', 
 
803
                       "This is a Bazaar-NG control directory.\n"
 
804
                       "Do not change any files in this directory.\n"),
 
805
                      ('branch-format', self.get_format_string()),
 
806
                      ]
 
807
        # NB: no need to escape relative paths that are url safe.
 
808
        control.put(lock_file, StringIO(), mode=file_mode)
 
809
        control_files = LockableFiles(control, lock_file)
 
810
        control_files.lock_write()
 
811
        try:
 
812
            for file, content in utf8_files:
 
813
                control_files.put_utf8(file, content)
 
814
        finally:
 
815
            control_files.unlock()
 
816
        return self.open(t, _found=True)
 
817
 
 
818
    def is_supported(self):
 
819
        """Is this format supported?
 
820
 
 
821
        Supported formats must be initializable and openable.
 
822
        Unsupported formats may not support initialization or committing or 
 
823
        some other features depending on the reason for not being supported.
 
824
        """
 
825
        return True
 
826
 
 
827
    def open(self, transport, _found=False):
 
828
        """Return an instance of this format for the dir transport points at.
 
829
        
 
830
        _found is a private parameter, do not use it.
 
831
        """
 
832
        if not _found:
 
833
            assert isinstance(BzrDirFormat.find_format(transport),
 
834
                              self.__class__)
 
835
        return self._open(transport)
 
836
 
 
837
    def _open(self, transport):
 
838
        """Template method helper for opening BzrDirectories.
 
839
 
 
840
        This performs the actual open and any additional logic or parameter
 
841
        passing.
 
842
        """
 
843
        raise NotImplementedError(self._open)
 
844
 
 
845
    @classmethod
 
846
    def register_format(klass, format):
 
847
        klass._formats[format.get_format_string()] = format
 
848
 
 
849
    @classmethod
 
850
    def set_default_format(klass, format):
 
851
        klass._default_format = format
 
852
 
 
853
    def __str__(self):
 
854
        return self.get_format_string()[:-1]
 
855
 
 
856
    @classmethod
 
857
    def unregister_format(klass, format):
 
858
        assert klass._formats[format.get_format_string()] is format
 
859
        del klass._formats[format.get_format_string()]
 
860
 
 
861
 
 
862
class BzrDirFormat4(BzrDirFormat):
 
863
    """Bzr dir format 4.
 
864
 
 
865
    This format is a combined format for working tree, branch and repository.
 
866
    It has:
 
867
     - Format 1 working trees [always]
 
868
     - Format 4 branches [always]
 
869
     - Format 4 repositories [always]
 
870
 
 
871
    This format is deprecated: it indexes texts using a text it which is
 
872
    removed in format 5; write support for this format has been removed.
 
873
    """
 
874
 
 
875
    def get_format_string(self):
 
876
        """See BzrDirFormat.get_format_string()."""
 
877
        return "Bazaar-NG branch, format 0.0.4\n"
 
878
 
 
879
    def initialize(self, url):
 
880
        """Format 4 branches cannot be created."""
 
881
        raise errors.UninitializableFormat(self)
 
882
 
 
883
    def is_supported(self):
 
884
        """Format 4 is not supported.
 
885
 
 
886
        It is not supported because the model changed from 4 to 5 and the
 
887
        conversion logic is expensive - so doing it on the fly was not 
 
888
        feasible.
 
889
        """
 
890
        return False
 
891
 
 
892
    def _open(self, transport):
 
893
        """See BzrDirFormat._open."""
 
894
        return BzrDir4(transport, self)
 
895
 
 
896
 
 
897
class BzrDirFormat5(BzrDirFormat):
 
898
    """Bzr control format 5.
 
899
 
 
900
    This format is a combined format for working tree, branch and repository.
 
901
    It has:
 
902
     - Format 2 working trees [always] 
 
903
     - Format 4 branches [always] 
 
904
     - Format 5 repositories [always]
 
905
       Unhashed stores in the repository.
 
906
    """
 
907
 
 
908
    def get_format_string(self):
 
909
        """See BzrDirFormat.get_format_string()."""
 
910
        return "Bazaar-NG branch, format 5\n"
 
911
 
 
912
    def initialize(self, url, _cloning=False):
 
913
        """Format 5 dirs always have working tree, branch and repository.
 
914
        
 
915
        Except when they are being cloned.
 
916
        """
 
917
        from bzrlib.branch import BzrBranchFormat4
 
918
        from bzrlib.repository import RepositoryFormat5
 
919
        from bzrlib.workingtree import WorkingTreeFormat2
 
920
        result = super(BzrDirFormat5, self).initialize(url)
 
921
        RepositoryFormat5().initialize(result, _internal=True)
 
922
        if not _cloning:
 
923
            BzrBranchFormat4().initialize(result)
 
924
            WorkingTreeFormat2().initialize(result)
 
925
        return result
 
926
 
 
927
    def _open(self, transport):
 
928
        """See BzrDirFormat._open."""
 
929
        return BzrDir5(transport, self)
 
930
 
 
931
 
 
932
class BzrDirFormat6(BzrDirFormat):
 
933
    """Bzr control format 6.
 
934
 
 
935
    This format is a combined format for working tree, branch and repository.
 
936
    It has:
 
937
     - Format 2 working trees [always] 
 
938
     - Format 4 branches [always] 
 
939
     - Format 6 repositories [always]
 
940
    """
 
941
 
 
942
    def get_format_string(self):
 
943
        """See BzrDirFormat.get_format_string()."""
 
944
        return "Bazaar-NG branch, format 6\n"
 
945
 
 
946
    def initialize(self, url, _cloning=False):
 
947
        """Format 6 dirs always have working tree, branch and repository.
 
948
        
 
949
        Except when they are being cloned.
 
950
        """
 
951
        from bzrlib.branch import BzrBranchFormat4
 
952
        from bzrlib.repository import RepositoryFormat6
 
953
        from bzrlib.workingtree import WorkingTreeFormat2
 
954
        result = super(BzrDirFormat6, self).initialize(url)
 
955
        RepositoryFormat6().initialize(result, _internal=True)
 
956
        if not _cloning:
 
957
            BzrBranchFormat4().initialize(result)
 
958
            try:
 
959
                WorkingTreeFormat2().initialize(result)
 
960
            except errors.NotLocalUrl:
 
961
                # emulate pre-check behaviour for working tree and silently 
 
962
                # fail.
 
963
                pass
 
964
        return result
 
965
 
 
966
    def _open(self, transport):
 
967
        """See BzrDirFormat._open."""
 
968
        return BzrDir6(transport, self)
 
969
 
 
970
 
 
971
class BzrDirMetaFormat1(BzrDirFormat):
 
972
    """Bzr meta control format 1
 
973
 
 
974
    This is the first format with split out working tree, branch and repository
 
975
    disk storage.
 
976
    It has:
 
977
     - Format 3 working trees [optional]
 
978
     - Format 5 branches [optional]
 
979
     - Format 7 repositories [optional]
 
980
    """
 
981
 
 
982
    def get_format_string(self):
 
983
        """See BzrDirFormat.get_format_string()."""
 
984
        return "Bazaar-NG meta directory, format 1\n"
 
985
 
 
986
    def _open(self, transport):
 
987
        """See BzrDirFormat._open."""
 
988
        return BzrDirMeta1(transport, self)
 
989
 
 
990
 
 
991
BzrDirFormat.register_format(BzrDirFormat4())
 
992
BzrDirFormat.register_format(BzrDirFormat5())
 
993
BzrDirFormat.register_format(BzrDirMetaFormat1())
 
994
__default_format = BzrDirFormat6()
 
995
BzrDirFormat.register_format(__default_format)
 
996
BzrDirFormat.set_default_format(__default_format)
 
997
 
 
998
 
 
999
class BzrDirTestProviderAdapter(object):
 
1000
    """A tool to generate a suite testing multiple bzrdir formats at once.
 
1001
 
 
1002
    This is done by copying the test once for each transport and injecting
 
1003
    the transport_server, transport_readonly_server, and bzrdir_format
 
1004
    classes into each copy. Each copy is also given a new id() to make it
 
1005
    easy to identify.
 
1006
    """
 
1007
 
 
1008
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1009
        self._transport_server = transport_server
 
1010
        self._transport_readonly_server = transport_readonly_server
 
1011
        self._formats = formats
 
1012
    
 
1013
    def adapt(self, test):
 
1014
        result = TestSuite()
 
1015
        for format in self._formats:
 
1016
            new_test = deepcopy(test)
 
1017
            new_test.transport_server = self._transport_server
 
1018
            new_test.transport_readonly_server = self._transport_readonly_server
 
1019
            new_test.bzrdir_format = format
 
1020
            def make_new_test_id():
 
1021
                new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
 
1022
                return lambda: new_id
 
1023
            new_test.id = make_new_test_id()
 
1024
            result.addTest(new_test)
 
1025
        return result
 
1026
 
 
1027
 
 
1028
class ScratchDir(BzrDir6):
 
1029
    """Special test class: a bzrdir that cleans up itself..
 
1030
 
 
1031
    >>> d = ScratchDir()
 
1032
    >>> base = d.transport.base
 
1033
    >>> isdir(base)
 
1034
    True
 
1035
    >>> b.transport.__del__()
 
1036
    >>> isdir(base)
 
1037
    False
 
1038
    """
 
1039
 
 
1040
    def __init__(self, files=[], dirs=[], transport=None):
 
1041
        """Make a test branch.
 
1042
 
 
1043
        This creates a temporary directory and runs init-tree in it.
 
1044
 
 
1045
        If any files are listed, they are created in the working copy.
 
1046
        """
 
1047
        if transport is None:
 
1048
            transport = bzrlib.transport.local.ScratchTransport()
 
1049
            # local import for scope restriction
 
1050
            BzrDirFormat6().initialize(transport.base)
 
1051
            super(ScratchDir, self).__init__(transport, BzrDirFormat6())
 
1052
            self.create_repository()
 
1053
            self.create_branch()
 
1054
            self.create_workingtree()
 
1055
        else:
 
1056
            super(ScratchDir, self).__init__(transport, BzrDirFormat6())
 
1057
 
 
1058
        # BzrBranch creates a clone to .bzr and then forgets about the
 
1059
        # original transport. A ScratchTransport() deletes itself and
 
1060
        # everything underneath it when it goes away, so we need to
 
1061
        # grab a local copy to prevent that from happening
 
1062
        self._transport = transport
 
1063
 
 
1064
        for d in dirs:
 
1065
            self._transport.mkdir(d)
 
1066
            
 
1067
        for f in files:
 
1068
            self._transport.put(f, 'content of %s' % f)
 
1069
 
 
1070
    def clone(self):
 
1071
        """
 
1072
        >>> orig = ScratchDir(files=["file1", "file2"])
 
1073
        >>> os.listdir(orig.base)
 
1074
        [u'.bzr', u'file1', u'file2']
 
1075
        >>> clone = orig.clone()
 
1076
        >>> if os.name != 'nt':
 
1077
        ...   os.path.samefile(orig.base, clone.base)
 
1078
        ... else:
 
1079
        ...   orig.base == clone.base
 
1080
        ...
 
1081
        False
 
1082
        >>> os.listdir(clone.base)
 
1083
        [u'.bzr', u'file1', u'file2']
 
1084
        """
 
1085
        from shutil import copytree
 
1086
        from bzrlib.osutils import mkdtemp
 
1087
        base = mkdtemp()
 
1088
        os.rmdir(base)
 
1089
        copytree(self.base, base, symlinks=True)
 
1090
        return ScratchDir(
 
1091
            transport=bzrlib.transport.local.ScratchTransport(base))