~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bzrdir.py

  • Committer: Robert Collins
  • Date: 2005-08-25 01:13:32 UTC
  • mto: (974.1.50) (1185.1.10) (1092.3.1)
  • mto: This revision was merged to the branch mainline in revision 1139.
  • Revision ID: robertc@robertcollins.net-20050825011331-6d549d5de7edcec1
two bugfixes to smart_add - do not add paths from nested trees to the parent tree, and do not mutate the user supplied file list

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