/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/bzrdir.py

  • Committer: Robert Collins
  • Date: 2006-02-16 05:54:02 UTC
  • mto: (1534.1.24 integration)
  • mto: This revision was merged to the branch mainline in revision 1554.
  • Revision ID: robertc@robertcollins.net-20060216055402-bb6afc4d15c715cd
split out converter logic into per-format objects.

Show diffs side-by-side

added added

removed removed

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