~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bzrdir.py

  • Committer: Martin Pool
  • Date: 2005-09-16 09:56:24 UTC
  • Revision ID: mbp@sourcefrog.net-20050916095623-ca0dff452934f21f
- make progress bar more tolerant of out-of-range values

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.NoWorkingTree, 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.NoWorkingTree, 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))