~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bzrdir.py

Merge in bzrdir work to enable checkout improvements.

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):
 
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
        """
 
66
        self._make_tail(url)
 
67
        result = self._format.initialize(url)
 
68
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
69
        try:
 
70
            self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
 
71
        except errors.NoRepositoryPresent:
 
72
            pass
 
73
        try:
 
74
            self.open_branch().clone(result, revision_id=revision_id)
 
75
        except errors.NotBranchError:
 
76
            pass
 
77
        try:
 
78
            self.open_workingtree().clone(result, basis=basis_tree)
 
79
        except (errors.NotBranchError, errors.NotLocalUrl):
 
80
            pass
 
81
        return result
 
82
 
 
83
    def _get_basis_components(self, basis):
 
84
        """Retrieve the basis components that are available at basis."""
 
85
        if basis is None:
 
86
            return None, None, None
 
87
        try:
 
88
            basis_tree = basis.open_workingtree()
 
89
            basis_branch = basis_tree.branch
 
90
            basis_repo = basis_branch.repository
 
91
        except (errors.NoWorkingTree, errors.NotLocalUrl):
 
92
            basis_tree = None
 
93
            try:
 
94
                basis_branch = basis.open_branch()
 
95
                basis_repo = basis_branch.repository
 
96
            except errors.NotBranchError:
 
97
                basis_branch = None
 
98
                try:
 
99
                    basis_repo = basis.open_repository()
 
100
                except errors.NoRepositoryPresent:
 
101
                    basis_repo = None
 
102
        return basis_repo, basis_branch, basis_tree
 
103
 
 
104
    def _make_tail(self, url):
 
105
        segments = url.split('/')
 
106
        if segments and segments[-1] not in ('', '.'):
 
107
            parent = '/'.join(segments[:-1])
 
108
            t = bzrlib.transport.get_transport(parent)
 
109
            try:
 
110
                t.mkdir(segments[-1])
 
111
            except errors.FileExists:
 
112
                pass
 
113
 
 
114
    @staticmethod
 
115
    def create(base):
 
116
        """Create a new BzrDir at the url 'base'.
 
117
        
 
118
        This will call the current default formats initialize with base
 
119
        as the only parameter.
 
120
 
 
121
        If you need a specific format, consider creating an instance
 
122
        of that and calling initialize().
 
123
        """
 
124
        segments = base.split('/')
 
125
        if segments and segments[-1] not in ('', '.'):
 
126
            parent = '/'.join(segments[:-1])
 
127
            t = bzrlib.transport.get_transport(parent)
 
128
            try:
 
129
                t.mkdir(segments[-1])
 
130
            except errors.FileExists:
 
131
                pass
 
132
        return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
 
133
 
 
134
    def create_branch(self):
 
135
        """Create a branch in this BzrDir.
 
136
 
 
137
        The bzrdirs format will control what branch format is created.
 
138
        For more control see BranchFormatXX.create(a_bzrdir).
 
139
        """
 
140
        raise NotImplementedError(self.create_branch)
 
141
 
 
142
    @staticmethod
 
143
    def create_branch_and_repo(base):
 
144
        """Create a new BzrDir, Branch and Repository at the url 'base'.
 
145
 
 
146
        This will use the current default BzrDirFormat, and use whatever 
 
147
        repository format that that uses via bzrdir.create_branch and
 
148
        create_repository.
 
149
 
 
150
        The created Branch object is returned.
 
151
        """
 
152
        bzrdir = BzrDir.create(base)
 
153
        bzrdir.create_repository()
 
154
        return bzrdir.create_branch()
 
155
        
 
156
    @staticmethod
 
157
    def create_repository(base):
 
158
        """Create a new BzrDir and Repository at the url 'base'.
 
159
 
 
160
        This will use the current default BzrDirFormat, and use whatever 
 
161
        repository format that that uses for bzrdirformat.create_repository.
 
162
 
 
163
        The Repository object is returned.
 
164
 
 
165
        This must be overridden as an instance method in child classes, where
 
166
        it should take no parameters and construct whatever repository format
 
167
        that child class desires.
 
168
        """
 
169
        bzrdir = BzrDir.create(base)
 
170
        return bzrdir.create_repository()
 
171
 
 
172
    @staticmethod
 
173
    def create_standalone_workingtree(base):
 
174
        """Create a new BzrDir, WorkingTree, Branch and Repository at 'base'.
 
175
 
 
176
        'base' must be a local path or a file:// url.
 
177
 
 
178
        This will use the current default BzrDirFormat, and use whatever 
 
179
        repository format that that uses for bzrdirformat.create_workingtree,
 
180
        create_branch and create_repository.
 
181
 
 
182
        The WorkingTree object is returned.
 
183
        """
 
184
        t = get_transport(safe_unicode(base))
 
185
        if not isinstance(t, LocalTransport):
 
186
            raise errors.NotLocalUrl(base)
 
187
        bzrdir = BzrDir.create_branch_and_repo(safe_unicode(base)).bzrdir
 
188
        return bzrdir.create_workingtree()
 
189
 
 
190
    def create_workingtree(self):
 
191
        """Create a working tree at this BzrDir"""
 
192
        raise NotImplementedError(self.create_workingtree)
 
193
 
 
194
    def get_branch_transport(self, branch_format):
 
195
        """Get the transport for use by branch format in this BzrDir.
 
196
 
 
197
        Note that bzr dirs that do not support format strings will raise
 
198
        IncompatibleFormat if the branch format they are given has
 
199
        a format string, and vice verca.
 
200
 
 
201
        If branch_format is None, the transport is returned with no 
 
202
        checking. if it is not None, then the returned transport is
 
203
        guaranteed to point to an existing directory ready for use.
 
204
        """
 
205
        raise NotImplementedError(self.get_branch_transport)
 
206
        
 
207
    def get_repository_transport(self, repository_format):
 
208
        """Get the transport for use by repository format in this BzrDir.
 
209
 
 
210
        Note that bzr dirs that do not support format strings will raise
 
211
        IncompatibleFormat if the repository format they are given has
 
212
        a format string, and vice verca.
 
213
 
 
214
        If repository_format is None, the transport is returned with no 
 
215
        checking. if it is not None, then the returned transport is
 
216
        guaranteed to point to an existing directory ready for use.
 
217
        """
 
218
        raise NotImplementedError(self.get_repository_transport)
 
219
        
 
220
    def get_workingtree_transport(self, branch_format):
 
221
        """Get the transport for use by workingtree format in this BzrDir.
 
222
 
 
223
        Note that bzr dirs that do not support format strings will raise
 
224
        IncompatibleFormat if the workingtree format they are given has
 
225
        a format string, and vice verca.
 
226
 
 
227
        If workingtree_format is None, the transport is returned with no 
 
228
        checking. if it is not None, then the returned transport is
 
229
        guaranteed to point to an existing directory ready for use.
 
230
        """
 
231
        raise NotImplementedError(self.get_workingtree_transport)
 
232
        
 
233
    def __init__(self, _transport, _format):
 
234
        """Initialize a Bzr control dir object.
 
235
        
 
236
        Only really common logic should reside here, concrete classes should be
 
237
        made with varying behaviours.
 
238
 
 
239
        _format: the format that is creating this BzrDir instance.
 
240
        _transport: the transport this dir is based at.
 
241
        """
 
242
        self._format = _format
 
243
        self.transport = _transport.clone('.bzr')
 
244
        self.root_transport = _transport
 
245
 
 
246
    @staticmethod
 
247
    def open_unsupported(base):
 
248
        """Open a branch which is not supported."""
 
249
        return BzrDir.open(base, _unsupported=True)
 
250
        
 
251
    @staticmethod
 
252
    def open(base, _unsupported=False):
 
253
        """Open an existing branch, rooted at 'base' (url)
 
254
        
 
255
        _unsupported is a private parameter to the BzrDir class.
 
256
        """
 
257
        t = get_transport(base)
 
258
        mutter("trying to open %r with transport %r", base, t)
 
259
        format = BzrDirFormat.find_format(t)
 
260
        if not _unsupported and not format.is_supported():
 
261
            # see open_downlevel to open legacy branches.
 
262
            raise errors.UnsupportedFormatError(
 
263
                    'sorry, format %s not supported' % format,
 
264
                    ['use a different bzr version',
 
265
                     'or remove the .bzr directory'
 
266
                     ' and "bzr init" again'])
 
267
        return format.open(t, _found=True)
 
268
 
 
269
    def open_branch(self, unsupported=False):
 
270
        """Open the branch object at this BzrDir if one is present.
 
271
 
 
272
        If unsupported is True, then no longer supported branch formats can
 
273
        still be opened.
 
274
        
 
275
        TODO: static convenience version of this?
 
276
        """
 
277
        raise NotImplementedError(self.open_branch)
 
278
 
 
279
    @staticmethod
 
280
    def open_containing(url):
 
281
        """Open an existing branch which contains url.
 
282
        
 
283
        This probes for a branch at url, and searches upwards from there.
 
284
 
 
285
        Basically we keep looking up until we find the control directory or
 
286
        run into the root.  If there isn't one, raises NotBranchError.
 
287
        If there is one and it is either an unrecognised format or an unsupported 
 
288
        format, UnknownFormatError or UnsupportedFormatError are raised.
 
289
        If there is one, it is returned, along with the unused portion of url.
 
290
        """
 
291
        t = get_transport(url)
 
292
        # this gets the normalised url back. I.e. '.' -> the full path.
 
293
        url = t.base
 
294
        while True:
 
295
            try:
 
296
                format = BzrDirFormat.find_format(t)
 
297
                return format.open(t), t.relpath(url)
 
298
            except errors.NotBranchError, e:
 
299
                mutter('not a branch in: %r %s', t.base, e)
 
300
            new_t = t.clone('..')
 
301
            if new_t.base == t.base:
 
302
                # reached the root, whatever that may be
 
303
                raise errors.NotBranchError(path=url)
 
304
            t = new_t
 
305
 
 
306
    def open_repository(self, _unsupported=False):
 
307
        """Open the repository object at this BzrDir if one is present.
 
308
 
 
309
        This will not follow the Branch object pointer - its strictly a direct
 
310
        open facility. Most client code should use open_branch().repository to
 
311
        get at a repository.
 
312
 
 
313
        _unsupported is a private parameter, not part of the api.
 
314
        TODO: static convenience version of this?
 
315
        """
 
316
        raise NotImplementedError(self.open_repository)
 
317
 
 
318
    def open_workingtree(self, _unsupported=False):
 
319
        """Open the workingtree object at this BzrDir if one is present.
 
320
        
 
321
        TODO: static convenience version of this?
 
322
        """
 
323
        raise NotImplementedError(self.open_workingtree)
 
324
 
 
325
    def sprout(self, url, revision_id=None, basis=None):
 
326
        """Create a copy of this bzrdir prepared for use as a new line of
 
327
        development.
 
328
 
 
329
        If urls last component does not exist, it will be created.
 
330
 
 
331
        Attributes related to the identity of the source branch like
 
332
        branch nickname will be cleaned, a working tree is created
 
333
        whether one existed before or not; and a local branch is always
 
334
        created.
 
335
 
 
336
        if revision_id is not None, then the clone operation may tune
 
337
            itself to download less data.
 
338
        """
 
339
        self._make_tail(url)
 
340
        result = self._format.initialize(url)
 
341
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
342
        try:
 
343
            source_branch = self.open_branch()
 
344
            source_repository = source_branch.repository
 
345
        except errors.NotBranchError:
 
346
            source_branch = None
 
347
            try:
 
348
                source_repository = self.open_repository()
 
349
            except errors.NoRepositoryPresent:
 
350
                # copy the basis one if there is one
 
351
                source_repository = basis_repo
 
352
        if source_repository is not None:
 
353
            source_repository.clone(result,
 
354
                                    revision_id=revision_id,
 
355
                                    basis=basis_repo)
 
356
        else:
 
357
            # no repo available, make a new one
 
358
            result.create_repository()
 
359
        if source_branch is not None:
 
360
            source_branch.sprout(result, revision_id=revision_id)
 
361
        else:
 
362
            result.create_branch()
 
363
        try:
 
364
            self.open_workingtree().clone(result,
 
365
                                          revision_id=revision_id, 
 
366
                                          basis=basis_tree)
 
367
        except (errors.NotBranchError, errors.NotLocalUrl):
 
368
            result.create_workingtree()
 
369
        return result
 
370
 
 
371
 
 
372
class BzrDirPreSplitOut(BzrDir):
 
373
    """A common class for the all-in-one formats."""
 
374
 
 
375
    def clone(self, url, revision_id=None, basis=None):
 
376
        """See BzrDir.clone()."""
 
377
        from bzrlib.workingtree import WorkingTreeFormat2
 
378
        self._make_tail(url)
 
379
        result = self._format.initialize(url, _cloning=True)
 
380
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
381
        self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
 
382
        self.open_branch().clone(result, revision_id=revision_id)
 
383
        try:
 
384
            self.open_workingtree().clone(result, basis=basis_tree)
 
385
        except errors.NotLocalUrl:
 
386
            # make a new one, this format always has to have one.
 
387
            WorkingTreeFormat2().initialize(result)
 
388
        return result
 
389
 
 
390
    def create_branch(self):
 
391
        """See BzrDir.create_branch."""
 
392
        return self.open_branch()
 
393
 
 
394
    def create_repository(self):
 
395
        """See BzrDir.create_repository."""
 
396
        return self.open_repository()
 
397
 
 
398
    def create_workingtree(self):
 
399
        """See BzrDir.create_workingtree."""
 
400
        return self.open_workingtree()
 
401
 
 
402
    def get_branch_transport(self, branch_format):
 
403
        """See BzrDir.get_branch_transport()."""
 
404
        if branch_format is None:
 
405
            return self.transport
 
406
        try:
 
407
            branch_format.get_format_string()
 
408
        except NotImplementedError:
 
409
            return self.transport
 
410
        raise errors.IncompatibleFormat(branch_format, self._format)
 
411
 
 
412
    def get_repository_transport(self, repository_format):
 
413
        """See BzrDir.get_repository_transport()."""
 
414
        if repository_format is None:
 
415
            return self.transport
 
416
        try:
 
417
            repository_format.get_format_string()
 
418
        except NotImplementedError:
 
419
            return self.transport
 
420
        raise errors.IncompatibleFormat(repository_format, self._format)
 
421
 
 
422
    def get_workingtree_transport(self, workingtree_format):
 
423
        """See BzrDir.get_workingtree_transport()."""
 
424
        if workingtree_format is None:
 
425
            return self.transport
 
426
        try:
 
427
            workingtree_format.get_format_string()
 
428
        except NotImplementedError:
 
429
            return self.transport
 
430
        raise errors.IncompatibleFormat(workingtree_format, self._format)
 
431
 
 
432
    def open_branch(self, unsupported=False):
 
433
        """See BzrDir.open_branch."""
 
434
        from bzrlib.branch import BzrBranchFormat4
 
435
        format = BzrBranchFormat4()
 
436
        self._check_supported(format, unsupported)
 
437
        return format.open(self, _found=True)
 
438
 
 
439
    def sprout(self, url, revision_id=None, basis=None):
 
440
        """See BzrDir.sprout()."""
 
441
        from bzrlib.workingtree import WorkingTreeFormat2
 
442
        self._make_tail(url)
 
443
        result = self._format.initialize(url, _cloning=True)
 
444
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
445
        try:
 
446
            self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
 
447
        except errors.NoRepositoryPresent:
 
448
            pass
 
449
        try:
 
450
            self.open_branch().sprout(result, revision_id=revision_id)
 
451
        except errors.NotBranchError:
 
452
            pass
 
453
        try:
 
454
            self.open_workingtree().clone(result, basis=basis_tree)
 
455
        except (errors.NotBranchError, errors.NotLocalUrl):
 
456
            # we always want a working tree
 
457
            WorkingTreeFormat2().initialize(result)
 
458
        return result
 
459
 
 
460
 
 
461
class BzrDir4(BzrDirPreSplitOut):
 
462
    """A .bzr version 4 control object."""
 
463
 
 
464
    def create_repository(self):
 
465
        """See BzrDir.create_repository."""
 
466
        from bzrlib.repository import RepositoryFormat4
 
467
        return RepositoryFormat4().initialize(self)
 
468
 
 
469
    def open_repository(self):
 
470
        """See BzrDir.open_repository."""
 
471
        from bzrlib.repository import RepositoryFormat4
 
472
        return RepositoryFormat4().open(self, _found=True)
 
473
 
 
474
 
 
475
class BzrDir5(BzrDirPreSplitOut):
 
476
    """A .bzr version 5 control object."""
 
477
 
 
478
    def open_repository(self):
 
479
        """See BzrDir.open_repository."""
 
480
        from bzrlib.repository import RepositoryFormat5
 
481
        return RepositoryFormat5().open(self, _found=True)
 
482
 
 
483
    def open_workingtree(self, _unsupported=False):
 
484
        """See BzrDir.create_workingtree."""
 
485
        from bzrlib.workingtree import WorkingTreeFormat2
 
486
        return WorkingTreeFormat2().open(self, _found=True)
 
487
 
 
488
 
 
489
class BzrDir6(BzrDirPreSplitOut):
 
490
    """A .bzr version 6 control object."""
 
491
 
 
492
    def open_repository(self):
 
493
        """See BzrDir.open_repository."""
 
494
        from bzrlib.repository import RepositoryFormat6
 
495
        return RepositoryFormat6().open(self, _found=True)
 
496
 
 
497
    def open_workingtree(self, _unsupported=False):
 
498
        """See BzrDir.create_workingtree."""
 
499
        from bzrlib.workingtree import WorkingTreeFormat2
 
500
        return WorkingTreeFormat2().open(self, _found=True)
 
501
 
 
502
 
 
503
class BzrDirMeta1(BzrDir):
 
504
    """A .bzr meta version 1 control object.
 
505
    
 
506
    This is the first control object where the 
 
507
    individual formats are really split out.
 
508
    """
 
509
 
 
510
    def create_branch(self):
 
511
        """See BzrDir.create_branch."""
 
512
        from bzrlib.branch import BranchFormat
 
513
        return BranchFormat.get_default_format().initialize(self)
 
514
 
 
515
    def create_repository(self):
 
516
        """See BzrDir.create_repository."""
 
517
        from bzrlib.repository import RepositoryFormat
 
518
        return RepositoryFormat.get_default_format().initialize(self)
 
519
 
 
520
    def create_workingtree(self):
 
521
        """See BzrDir.create_workingtree."""
 
522
        from bzrlib.workingtree import WorkingTreeFormat
 
523
        return WorkingTreeFormat.get_default_format().initialize(self)
 
524
 
 
525
    def get_branch_transport(self, branch_format):
 
526
        """See BzrDir.get_branch_transport()."""
 
527
        if branch_format is None:
 
528
            return self.transport.clone('branch')
 
529
        try:
 
530
            branch_format.get_format_string()
 
531
        except NotImplementedError:
 
532
            raise errors.IncompatibleFormat(branch_format, self._format)
 
533
        try:
 
534
            self.transport.mkdir('branch')
 
535
        except errors.FileExists:
 
536
            pass
 
537
        return self.transport.clone('branch')
 
538
 
 
539
    def get_repository_transport(self, repository_format):
 
540
        """See BzrDir.get_repository_transport()."""
 
541
        if repository_format is None:
 
542
            return self.transport.clone('repository')
 
543
        try:
 
544
            repository_format.get_format_string()
 
545
        except NotImplementedError:
 
546
            raise errors.IncompatibleFormat(repository_format, self._format)
 
547
        try:
 
548
            self.transport.mkdir('repository')
 
549
        except errors.FileExists:
 
550
            pass
 
551
        return self.transport.clone('repository')
 
552
 
 
553
    def get_workingtree_transport(self, workingtree_format):
 
554
        """See BzrDir.get_workingtree_transport()."""
 
555
        if workingtree_format is None:
 
556
            return self.transport.clone('checkout')
 
557
        try:
 
558
            workingtree_format.get_format_string()
 
559
        except NotImplementedError:
 
560
            raise errors.IncompatibleFormat(workingtree_format, self._format)
 
561
        try:
 
562
            self.transport.mkdir('checkout')
 
563
        except errors.FileExists:
 
564
            pass
 
565
        return self.transport.clone('checkout')
 
566
 
 
567
    def open_branch(self, unsupported=False):
 
568
        """See BzrDir.open_branch."""
 
569
        from bzrlib.branch import BranchFormat
 
570
        format = BranchFormat.find_format(self)
 
571
        self._check_supported(format, unsupported)
 
572
        return format.open(self, _found=True)
 
573
 
 
574
    def open_repository(self, unsupported=False):
 
575
        """See BzrDir.open_repository."""
 
576
        from bzrlib.repository import RepositoryFormat
 
577
        format = RepositoryFormat.find_format(self)
 
578
        self._check_supported(format, unsupported)
 
579
        return format.open(self, _found=True)
 
580
 
 
581
    def open_workingtree(self, unsupported=False):
 
582
        """See BzrDir.create_workingtree."""
 
583
        from bzrlib.workingtree import WorkingTreeFormat
 
584
        format = WorkingTreeFormat.find_format(self)
 
585
        self._check_supported(format, unsupported)
 
586
        return format.open(self, _found=True)
 
587
 
 
588
 
 
589
class BzrDirFormat(object):
 
590
    """An encapsulation of the initialization and open routines for a format.
 
591
 
 
592
    Formats provide three things:
 
593
     * An initialization routine,
 
594
     * a format string,
 
595
     * an open routine.
 
596
 
 
597
    Formats are placed in an dict by their format string for reference 
 
598
    during bzrdir opening. These should be subclasses of BzrDirFormat
 
599
    for consistency.
 
600
 
 
601
    Once a format is deprecated, just deprecate the initialize and open
 
602
    methods on the format class. Do not deprecate the object, as the 
 
603
    object will be created every system load.
 
604
    """
 
605
 
 
606
    _default_format = None
 
607
    """The default format used for new .bzr dirs."""
 
608
 
 
609
    _formats = {}
 
610
    """The known formats."""
 
611
 
 
612
    @classmethod
 
613
    def find_format(klass, transport):
 
614
        """Return the format registered for URL."""
 
615
        try:
 
616
            format_string = transport.get(".bzr/branch-format").read()
 
617
            return klass._formats[format_string]
 
618
        except errors.NoSuchFile:
 
619
            raise errors.NotBranchError(path=transport.base)
 
620
        except KeyError:
 
621
            raise errors.UnknownFormatError(format_string)
 
622
 
 
623
    @classmethod
 
624
    def get_default_format(klass):
 
625
        """Return the current default format."""
 
626
        return klass._default_format
 
627
 
 
628
    def get_format_string(self):
 
629
        """Return the ASCII format string that identifies this format."""
 
630
        raise NotImplementedError(self.get_format_string)
 
631
 
 
632
    def initialize(self, url):
 
633
        """Create a bzr control dir at this url and return an opened copy."""
 
634
        # Since we don't have a .bzr directory, inherit the
 
635
        # mode from the root directory
 
636
        t = get_transport(url)
 
637
        temp_control = LockableFiles(t, '')
 
638
        temp_control._transport.mkdir('.bzr',
 
639
                                      # FIXME: RBC 20060121 dont peek under
 
640
                                      # the covers
 
641
                                      mode=temp_control._dir_mode)
 
642
        file_mode = temp_control._file_mode
 
643
        del temp_control
 
644
        mutter('created control directory in ' + t.base)
 
645
        control = t.clone('.bzr')
 
646
        lock_file = 'branch-lock'
 
647
        utf8_files = [('README', 
 
648
                       "This is a Bazaar-NG control directory.\n"
 
649
                       "Do not change any files in this directory.\n"),
 
650
                      ('branch-format', self.get_format_string()),
 
651
                      ]
 
652
        # NB: no need to escape relative paths that are url safe.
 
653
        control.put(lock_file, StringIO(), mode=file_mode)
 
654
        control_files = LockableFiles(control, lock_file)
 
655
        control_files.lock_write()
 
656
        try:
 
657
            for file, content in utf8_files:
 
658
                control_files.put_utf8(file, content)
 
659
        finally:
 
660
            control_files.unlock()
 
661
        return self.open(t, _found=True)
 
662
 
 
663
    def is_supported(self):
 
664
        """Is this format supported?
 
665
 
 
666
        Supported formats must be initializable and openable.
 
667
        Unsupported formats may not support initialization or committing or 
 
668
        some other features depending on the reason for not being supported.
 
669
        """
 
670
        return True
 
671
 
 
672
    def open(self, transport, _found=False):
 
673
        """Return an instance of this format for the dir transport points at.
 
674
        
 
675
        _found is a private parameter, do not use it.
 
676
        """
 
677
        if not _found:
 
678
            assert isinstance(BzrDirFormat.find_format(transport),
 
679
                              self.__class__)
 
680
        return self._open(transport)
 
681
 
 
682
    def _open(self, transport):
 
683
        """Template method helper for opening BzrDirectories.
 
684
 
 
685
        This performs the actual open and any additional logic or parameter
 
686
        passing.
 
687
        """
 
688
        raise NotImplementedError(self._open)
 
689
 
 
690
    @classmethod
 
691
    def register_format(klass, format):
 
692
        klass._formats[format.get_format_string()] = format
 
693
 
 
694
    @classmethod
 
695
    def set_default_format(klass, format):
 
696
        klass._default_format = format
 
697
 
 
698
    @classmethod
 
699
    def unregister_format(klass, format):
 
700
        assert klass._formats[format.get_format_string()] is format
 
701
        del klass._formats[format.get_format_string()]
 
702
 
 
703
 
 
704
class BzrDirFormat4(BzrDirFormat):
 
705
    """Bzr dir format 4.
 
706
 
 
707
    This format is a combined format for working tree, branch and repository.
 
708
    It has:
 
709
     - Format 1 working trees [always]
 
710
     - Format 4 branches [always]
 
711
     - Format 4 repositories [always]
 
712
 
 
713
    This format is deprecated: it indexes texts using a text it which is
 
714
    removed in format 5; write support for this format has been removed.
 
715
    """
 
716
 
 
717
    def get_format_string(self):
 
718
        """See BzrDirFormat.get_format_string()."""
 
719
        return "Bazaar-NG branch, format 0.0.4\n"
 
720
 
 
721
    def initialize(self, url):
 
722
        """Format 4 branches cannot be created."""
 
723
        raise errors.UninitializableFormat(self)
 
724
 
 
725
    def is_supported(self):
 
726
        """Format 4 is not supported.
 
727
 
 
728
        It is not supported because the model changed from 4 to 5 and the
 
729
        conversion logic is expensive - so doing it on the fly was not 
 
730
        feasible.
 
731
        """
 
732
        return False
 
733
 
 
734
    def _open(self, transport):
 
735
        """See BzrDirFormat._open."""
 
736
        return BzrDir4(transport, self)
 
737
 
 
738
 
 
739
class BzrDirFormat5(BzrDirFormat):
 
740
    """Bzr control format 5.
 
741
 
 
742
    This format is a combined format for working tree, branch and repository.
 
743
    It has:
 
744
     - Format 2 working trees [always] 
 
745
     - Format 4 branches [always] 
 
746
     - Format 6 repositories [always]
 
747
       Unhashed stores in the repository.
 
748
    """
 
749
 
 
750
    def get_format_string(self):
 
751
        """See BzrDirFormat.get_format_string()."""
 
752
        return "Bazaar-NG branch, format 5\n"
 
753
 
 
754
    def initialize(self, url, _cloning=False):
 
755
        """Format 5 dirs always have working tree, branch and repository.
 
756
        
 
757
        Except when they are being cloned.
 
758
        """
 
759
        from bzrlib.branch import BzrBranchFormat4
 
760
        from bzrlib.repository import RepositoryFormat5
 
761
        from bzrlib.workingtree import WorkingTreeFormat2
 
762
        result = super(BzrDirFormat5, self).initialize(url)
 
763
        RepositoryFormat5().initialize(result, _internal=True)
 
764
        if not _cloning:
 
765
            BzrBranchFormat4().initialize(result)
 
766
            WorkingTreeFormat2().initialize(result)
 
767
        return result
 
768
 
 
769
    def _open(self, transport):
 
770
        """See BzrDirFormat._open."""
 
771
        return BzrDir5(transport, self)
 
772
 
 
773
 
 
774
class BzrDirFormat6(BzrDirFormat):
 
775
    """Bzr control format 6.
 
776
 
 
777
    This format is a combined format for working tree, branch and repository.
 
778
    It has:
 
779
     - Format 2 working trees [always] 
 
780
     - Format 4 branches [always] 
 
781
     - Format 6 repositories [always]
 
782
    """
 
783
 
 
784
    def get_format_string(self):
 
785
        """See BzrDirFormat.get_format_string()."""
 
786
        return "Bazaar-NG branch, format 6\n"
 
787
 
 
788
    def initialize(self, url, _cloning=False):
 
789
        """Format 6 dirs always have working tree, branch and repository.
 
790
        
 
791
        Except when they are being cloned.
 
792
        """
 
793
        from bzrlib.branch import BzrBranchFormat4
 
794
        from bzrlib.repository import RepositoryFormat6
 
795
        from bzrlib.workingtree import WorkingTreeFormat2
 
796
        result = super(BzrDirFormat6, self).initialize(url)
 
797
        RepositoryFormat6().initialize(result, _internal=True)
 
798
        if not _cloning:
 
799
            BzrBranchFormat4().initialize(result)
 
800
            try:
 
801
                WorkingTreeFormat2().initialize(result)
 
802
            except errors.NotLocalUrl:
 
803
                # emulate pre-check behaviour for working tree and silently 
 
804
                # fail.
 
805
                pass
 
806
        return result
 
807
 
 
808
    def _open(self, transport):
 
809
        """See BzrDirFormat._open."""
 
810
        return BzrDir6(transport, self)
 
811
 
 
812
 
 
813
class BzrDirMetaFormat1(BzrDirFormat):
 
814
    """Bzr meta control format 1
 
815
 
 
816
    This is the first format with split out working tree, branch and repository
 
817
    disk storage.
 
818
    It has:
 
819
     - Format 3 working trees [optional]
 
820
     - Format 5 branches [optional]
 
821
     - Format 7 repositories [optional]
 
822
    """
 
823
 
 
824
    def get_format_string(self):
 
825
        """See BzrDirFormat.get_format_string()."""
 
826
        return "Bazaar-NG meta directory, format 1\n"
 
827
 
 
828
    def _open(self, transport):
 
829
        """See BzrDirFormat._open."""
 
830
        return BzrDirMeta1(transport, self)
 
831
 
 
832
 
 
833
BzrDirFormat.register_format(BzrDirFormat4())
 
834
BzrDirFormat.register_format(BzrDirFormat5())
 
835
BzrDirFormat.register_format(BzrDirMetaFormat1())
 
836
__default_format = BzrDirFormat6()
 
837
BzrDirFormat.register_format(__default_format)
 
838
BzrDirFormat.set_default_format(__default_format)
 
839
 
 
840
 
 
841
class BzrDirTestProviderAdapter(object):
 
842
    """A tool to generate a suite testing multiple bzrdir formats at once.
 
843
 
 
844
    This is done by copying the test once for each transport and injecting
 
845
    the transport_server, transport_readonly_server, and bzrdir_format
 
846
    classes into each copy. Each copy is also given a new id() to make it
 
847
    easy to identify.
 
848
    """
 
849
 
 
850
    def __init__(self, transport_server, transport_readonly_server, formats):
 
851
        self._transport_server = transport_server
 
852
        self._transport_readonly_server = transport_readonly_server
 
853
        self._formats = formats
 
854
    
 
855
    def adapt(self, test):
 
856
        result = TestSuite()
 
857
        for format in self._formats:
 
858
            new_test = deepcopy(test)
 
859
            new_test.transport_server = self._transport_server
 
860
            new_test.transport_readonly_server = self._transport_readonly_server
 
861
            new_test.bzrdir_format = format
 
862
            def make_new_test_id():
 
863
                new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
 
864
                return lambda: new_id
 
865
            new_test.id = make_new_test_id()
 
866
            result.addTest(new_test)
 
867
        return result
 
868
 
 
869
 
 
870
class ScratchDir(BzrDir6):
 
871
    """Special test class: a bzrdir that cleans up itself..
 
872
 
 
873
    >>> d = ScratchDir()
 
874
    >>> base = d.transport.base
 
875
    >>> isdir(base)
 
876
    True
 
877
    >>> b.transport.__del__()
 
878
    >>> isdir(base)
 
879
    False
 
880
    """
 
881
 
 
882
    def __init__(self, files=[], dirs=[], transport=None):
 
883
        """Make a test branch.
 
884
 
 
885
        This creates a temporary directory and runs init-tree in it.
 
886
 
 
887
        If any files are listed, they are created in the working copy.
 
888
        """
 
889
        if transport is None:
 
890
            transport = bzrlib.transport.local.ScratchTransport()
 
891
            # local import for scope restriction
 
892
            BzrDirFormat6().initialize(transport.base)
 
893
            super(ScratchDir, self).__init__(transport, BzrDirFormat6())
 
894
            self.create_repository()
 
895
            self.create_branch()
 
896
            self.create_workingtree()
 
897
        else:
 
898
            super(ScratchDir, self).__init__(transport, BzrDirFormat6())
 
899
 
 
900
        # BzrBranch creates a clone to .bzr and then forgets about the
 
901
        # original transport. A ScratchTransport() deletes itself and
 
902
        # everything underneath it when it goes away, so we need to
 
903
        # grab a local copy to prevent that from happening
 
904
        self._transport = transport
 
905
 
 
906
        for d in dirs:
 
907
            self._transport.mkdir(d)
 
908
            
 
909
        for f in files:
 
910
            self._transport.put(f, 'content of %s' % f)
 
911
 
 
912
    def clone(self):
 
913
        """
 
914
        >>> orig = ScratchDir(files=["file1", "file2"])
 
915
        >>> os.listdir(orig.base)
 
916
        [u'.bzr', u'file1', u'file2']
 
917
        >>> clone = orig.clone()
 
918
        >>> if os.name != 'nt':
 
919
        ...   os.path.samefile(orig.base, clone.base)
 
920
        ... else:
 
921
        ...   orig.base == clone.base
 
922
        ...
 
923
        False
 
924
        >>> os.listdir(clone.base)
 
925
        [u'.bzr', u'file1', u'file2']
 
926
        """
 
927
        from shutil import copytree
 
928
        from bzrlib.osutils import mkdtemp
 
929
        base = mkdtemp()
 
930
        os.rmdir(base)
 
931
        copytree(self.base, base, symlinks=True)
 
932
        return ScratchDir(
 
933
            transport=bzrlib.transport.local.ScratchTransport(base))