/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: Andrew Bennetts
  • Date: 2007-03-29 09:08:25 UTC
  • mto: (2018.18.6 hpss-faster-copy)
  • mto: This revision was merged to the branch mainline in revision 2435.
  • Revision ID: andrew.bennetts@canonical.com-20070329090825-n2imszarc27gf6ls
All TestLockableFiles_RemoteLockDir tests passing.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007 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
# TODO: remove unittest dependency; put that stuff inside the test suite
 
24
 
 
25
# TODO: Can we move specific formats into separate modules to make this file
 
26
# smaller?
 
27
 
 
28
from cStringIO import StringIO
 
29
import os
 
30
import textwrap
 
31
 
 
32
from bzrlib.lazy_import import lazy_import
 
33
lazy_import(globals(), """
 
34
from copy import deepcopy
 
35
from stat import S_ISDIR
 
36
import unittest
 
37
 
 
38
import bzrlib
 
39
from bzrlib import (
 
40
    errors,
 
41
    lockable_files,
 
42
    lockdir,
 
43
    registry,
 
44
    remote,
 
45
    revision as _mod_revision,
 
46
    symbol_versioning,
 
47
    urlutils,
 
48
    xml4,
 
49
    xml5,
 
50
    workingtree,
 
51
    workingtree_4,
 
52
    )
 
53
from bzrlib.osutils import (
 
54
    safe_unicode,
 
55
    sha_strings,
 
56
    sha_string,
 
57
    )
 
58
from bzrlib.smart.client import SmartClient
 
59
from bzrlib.store.revision.text import TextRevisionStore
 
60
from bzrlib.store.text import TextStore
 
61
from bzrlib.store.versioned import WeaveStore
 
62
from bzrlib.transactions import WriteTransaction
 
63
from bzrlib.transport import get_transport
 
64
from bzrlib.weave import Weave
 
65
""")
 
66
 
 
67
from bzrlib.trace import mutter, note
 
68
from bzrlib.transport.local import LocalTransport
 
69
 
 
70
 
 
71
class BzrDir(object):
 
72
    """A .bzr control diretory.
 
73
    
 
74
    BzrDir instances let you create or open any of the things that can be
 
75
    found within .bzr - checkouts, branches and repositories.
 
76
    
 
77
    transport
 
78
        the transport which this bzr dir is rooted at (i.e. file:///.../.bzr/)
 
79
    root_transport
 
80
        a transport connected to the directory this bzr was opened from.
 
81
    """
 
82
 
 
83
    def break_lock(self):
 
84
        """Invoke break_lock on the first object in the bzrdir.
 
85
 
 
86
        If there is a tree, the tree is opened and break_lock() called.
 
87
        Otherwise, branch is tried, and finally repository.
 
88
        """
 
89
        # XXX: This seems more like a UI function than something that really
 
90
        # belongs in this class.
 
91
        try:
 
92
            thing_to_unlock = self.open_workingtree()
 
93
        except (errors.NotLocalUrl, errors.NoWorkingTree):
 
94
            try:
 
95
                thing_to_unlock = self.open_branch()
 
96
            except errors.NotBranchError:
 
97
                try:
 
98
                    thing_to_unlock = self.open_repository()
 
99
                except errors.NoRepositoryPresent:
 
100
                    return
 
101
        thing_to_unlock.break_lock()
 
102
 
 
103
    def can_convert_format(self):
 
104
        """Return true if this bzrdir is one whose format we can convert from."""
 
105
        return True
 
106
 
 
107
    def check_conversion_target(self, target_format):
 
108
        target_repo_format = target_format.repository_format
 
109
        source_repo_format = self._format.repository_format
 
110
        source_repo_format.check_conversion_target(target_repo_format)
 
111
 
 
112
    @staticmethod
 
113
    def _check_supported(format, allow_unsupported):
 
114
        """Check whether format is a supported format.
 
115
 
 
116
        If allow_unsupported is True, this is a no-op.
 
117
        """
 
118
        if not allow_unsupported and not format.is_supported():
 
119
            # see open_downlevel to open legacy branches.
 
120
            raise errors.UnsupportedFormatError(format=format)
 
121
 
 
122
    def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
 
123
        """Clone this bzrdir and its contents to url verbatim.
 
124
 
 
125
        If urls last component does not exist, it will be created.
 
126
 
 
127
        if revision_id is not None, then the clone operation may tune
 
128
            itself to download less data.
 
129
        :param force_new_repo: Do not use a shared repository for the target 
 
130
                               even if one is available.
 
131
        """
 
132
        self._make_tail(url)
 
133
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
134
        result = self._format.initialize(url)
 
135
        try:
 
136
            local_repo = self.find_repository()
 
137
        except errors.NoRepositoryPresent:
 
138
            local_repo = None
 
139
        if local_repo:
 
140
            # may need to copy content in
 
141
            if force_new_repo:
 
142
                result_repo = local_repo.clone(
 
143
                    result,
 
144
                    revision_id=revision_id,
 
145
                    basis=basis_repo)
 
146
                result_repo.set_make_working_trees(local_repo.make_working_trees())
 
147
            else:
 
148
                try:
 
149
                    result_repo = result.find_repository()
 
150
                    # fetch content this dir needs.
 
151
                    if basis_repo:
 
152
                        # XXX FIXME RBC 20060214 need tests for this when the basis
 
153
                        # is incomplete
 
154
                        result_repo.fetch(basis_repo, revision_id=revision_id)
 
155
                    result_repo.fetch(local_repo, revision_id=revision_id)
 
156
                except errors.NoRepositoryPresent:
 
157
                    # needed to make one anyway.
 
158
                    result_repo = local_repo.clone(
 
159
                        result,
 
160
                        revision_id=revision_id,
 
161
                        basis=basis_repo)
 
162
                    result_repo.set_make_working_trees(local_repo.make_working_trees())
 
163
        # 1 if there is a branch present
 
164
        #   make sure its content is available in the target repository
 
165
        #   clone it.
 
166
        try:
 
167
            self.open_branch().clone(result, revision_id=revision_id)
 
168
        except errors.NotBranchError:
 
169
            pass
 
170
        try:
 
171
            self.open_workingtree().clone(result, basis=basis_tree)
 
172
        except (errors.NoWorkingTree, errors.NotLocalUrl):
 
173
            pass
 
174
        return result
 
175
 
 
176
    def _get_basis_components(self, basis):
 
177
        """Retrieve the basis components that are available at basis."""
 
178
        if basis is None:
 
179
            return None, None, None
 
180
        try:
 
181
            basis_tree = basis.open_workingtree()
 
182
            basis_branch = basis_tree.branch
 
183
            basis_repo = basis_branch.repository
 
184
        except (errors.NoWorkingTree, errors.NotLocalUrl):
 
185
            basis_tree = None
 
186
            try:
 
187
                basis_branch = basis.open_branch()
 
188
                basis_repo = basis_branch.repository
 
189
            except errors.NotBranchError:
 
190
                basis_branch = None
 
191
                try:
 
192
                    basis_repo = basis.open_repository()
 
193
                except errors.NoRepositoryPresent:
 
194
                    basis_repo = None
 
195
        return basis_repo, basis_branch, basis_tree
 
196
 
 
197
    # TODO: This should be given a Transport, and should chdir up; otherwise
 
198
    # this will open a new connection.
 
199
    def _make_tail(self, url):
 
200
        head, tail = urlutils.split(url)
 
201
        if tail and tail != '.':
 
202
            t = get_transport(head)
 
203
            try:
 
204
                t.mkdir(tail)
 
205
            except errors.FileExists:
 
206
                pass
 
207
 
 
208
    # TODO: Should take a Transport
 
209
    @classmethod
 
210
    def create(cls, base, format=None):
 
211
        """Create a new BzrDir at the url 'base'.
 
212
        
 
213
        This will call the current default formats initialize with base
 
214
        as the only parameter.
 
215
 
 
216
        :param format: If supplied, the format of branch to create.  If not
 
217
            supplied, the default is used.
 
218
        """
 
219
        if cls is not BzrDir:
 
220
            raise AssertionError("BzrDir.create always creates the default"
 
221
                " format, not one of %r" % cls)
 
222
        head, tail = urlutils.split(base)
 
223
        if tail and tail != '.':
 
224
            t = get_transport(head)
 
225
            try:
 
226
                t.mkdir(tail)
 
227
            except errors.FileExists:
 
228
                pass
 
229
        if format is None:
 
230
            format = BzrDirFormat.get_default_format()
 
231
        return format.initialize(safe_unicode(base))
 
232
 
 
233
    def create_branch(self):
 
234
        """Create a branch in this BzrDir.
 
235
 
 
236
        The bzrdirs format will control what branch format is created.
 
237
        For more control see BranchFormatXX.create(a_bzrdir).
 
238
        """
 
239
        raise NotImplementedError(self.create_branch)
 
240
 
 
241
    @staticmethod
 
242
    def create_branch_and_repo(base, force_new_repo=False, format=None):
 
243
        """Create a new BzrDir, Branch and Repository at the url 'base'.
 
244
 
 
245
        This will use the current default BzrDirFormat, and use whatever 
 
246
        repository format that that uses via bzrdir.create_branch and
 
247
        create_repository. If a shared repository is available that is used
 
248
        preferentially.
 
249
 
 
250
        The created Branch object is returned.
 
251
 
 
252
        :param base: The URL to create the branch at.
 
253
        :param force_new_repo: If True a new repository is always created.
 
254
        """
 
255
        bzrdir = BzrDir.create(base, format)
 
256
        bzrdir._find_or_create_repository(force_new_repo)
 
257
        return bzrdir.create_branch()
 
258
 
 
259
    def _find_or_create_repository(self, force_new_repo):
 
260
        """Create a new repository if needed, returning the repository."""
 
261
        if force_new_repo:
 
262
            return self.create_repository()
 
263
        try:
 
264
            return self.find_repository()
 
265
        except errors.NoRepositoryPresent:
 
266
            return self.create_repository()
 
267
        
 
268
    @staticmethod
 
269
    def create_branch_convenience(base, force_new_repo=False,
 
270
                                  force_new_tree=None, format=None):
 
271
        """Create a new BzrDir, Branch and Repository at the url 'base'.
 
272
 
 
273
        This is a convenience function - it will use an existing repository
 
274
        if possible, can be told explicitly whether to create a working tree or
 
275
        not.
 
276
 
 
277
        This will use the current default BzrDirFormat, and use whatever 
 
278
        repository format that that uses via bzrdir.create_branch and
 
279
        create_repository. If a shared repository is available that is used
 
280
        preferentially. Whatever repository is used, its tree creation policy
 
281
        is followed.
 
282
 
 
283
        The created Branch object is returned.
 
284
        If a working tree cannot be made due to base not being a file:// url,
 
285
        no error is raised unless force_new_tree is True, in which case no 
 
286
        data is created on disk and NotLocalUrl is raised.
 
287
 
 
288
        :param base: The URL to create the branch at.
 
289
        :param force_new_repo: If True a new repository is always created.
 
290
        :param force_new_tree: If True or False force creation of a tree or 
 
291
                               prevent such creation respectively.
 
292
        :param format: Override for the for the bzrdir format to create
 
293
        """
 
294
        if force_new_tree:
 
295
            # check for non local urls
 
296
            t = get_transport(safe_unicode(base))
 
297
            if not isinstance(t, LocalTransport):
 
298
                raise errors.NotLocalUrl(base)
 
299
        bzrdir = BzrDir.create(base, format)
 
300
        repo = bzrdir._find_or_create_repository(force_new_repo)
 
301
        result = bzrdir.create_branch()
 
302
        if force_new_tree or (repo.make_working_trees() and 
 
303
                              force_new_tree is None):
 
304
            try:
 
305
                bzrdir.create_workingtree()
 
306
            except errors.NotLocalUrl:
 
307
                pass
 
308
        return result
 
309
        
 
310
    @staticmethod
 
311
    def create_repository(base, shared=False, format=None):
 
312
        """Create a new BzrDir and Repository at the url 'base'.
 
313
 
 
314
        If no format is supplied, this will default to the current default
 
315
        BzrDirFormat by default, and use whatever repository format that that
 
316
        uses for bzrdirformat.create_repository.
 
317
 
 
318
        :param shared: Create a shared repository rather than a standalone
 
319
                       repository.
 
320
        The Repository object is returned.
 
321
 
 
322
        This must be overridden as an instance method in child classes, where
 
323
        it should take no parameters and construct whatever repository format
 
324
        that child class desires.
 
325
        """
 
326
        bzrdir = BzrDir.create(base, format)
 
327
        return bzrdir.create_repository(shared)
 
328
 
 
329
    @staticmethod
 
330
    def create_standalone_workingtree(base, format=None):
 
331
        """Create a new BzrDir, WorkingTree, Branch and Repository at 'base'.
 
332
 
 
333
        'base' must be a local path or a file:// url.
 
334
 
 
335
        This will use the current default BzrDirFormat, and use whatever 
 
336
        repository format that that uses for bzrdirformat.create_workingtree,
 
337
        create_branch and create_repository.
 
338
 
 
339
        :return: The WorkingTree object.
 
340
        """
 
341
        t = get_transport(safe_unicode(base))
 
342
        if not isinstance(t, LocalTransport):
 
343
            raise errors.NotLocalUrl(base)
 
344
        bzrdir = BzrDir.create_branch_and_repo(safe_unicode(base),
 
345
                                               force_new_repo=True,
 
346
                                               format=format).bzrdir
 
347
        return bzrdir.create_workingtree()
 
348
 
 
349
    def create_workingtree(self, revision_id=None):
 
350
        """Create a working tree at this BzrDir.
 
351
        
 
352
        revision_id: create it as of this revision id.
 
353
        """
 
354
        raise NotImplementedError(self.create_workingtree)
 
355
 
 
356
    def retire_bzrdir(self):
 
357
        """Permanently disable the bzrdir.
 
358
 
 
359
        This is done by renaming it to give the user some ability to recover
 
360
        if there was a problem.
 
361
 
 
362
        This will have horrible consequences if anyone has anything locked or
 
363
        in use.
 
364
        """
 
365
        for i in xrange(10000):
 
366
            try:
 
367
                to_path = '.bzr.retired.%d' % i
 
368
                self.root_transport.rename('.bzr', to_path)
 
369
                note("renamed %s to %s"
 
370
                    % (self.root_transport.abspath('.bzr'), to_path))
 
371
                break
 
372
            except (errors.TransportError, IOError, errors.PathError):
 
373
                pass
 
374
 
 
375
    def destroy_workingtree(self):
 
376
        """Destroy the working tree at this BzrDir.
 
377
 
 
378
        Formats that do not support this may raise UnsupportedOperation.
 
379
        """
 
380
        raise NotImplementedError(self.destroy_workingtree)
 
381
 
 
382
    def destroy_workingtree_metadata(self):
 
383
        """Destroy the control files for the working tree at this BzrDir.
 
384
 
 
385
        The contents of working tree files are not affected.
 
386
        Formats that do not support this may raise UnsupportedOperation.
 
387
        """
 
388
        raise NotImplementedError(self.destroy_workingtree_metadata)
 
389
 
 
390
    def find_repository(self):
 
391
        """Find the repository that should be used for a_bzrdir.
 
392
 
 
393
        This does not require a branch as we use it to find the repo for
 
394
        new branches as well as to hook existing branches up to their
 
395
        repository.
 
396
        """
 
397
        try:
 
398
            return self.open_repository()
 
399
        except errors.NoRepositoryPresent:
 
400
            pass
 
401
        next_transport = self.root_transport.clone('..')
 
402
        while True:
 
403
            # find the next containing bzrdir
 
404
            try:
 
405
                found_bzrdir = BzrDir.open_containing_from_transport(
 
406
                    next_transport)[0]
 
407
            except errors.NotBranchError:
 
408
                # none found
 
409
                raise errors.NoRepositoryPresent(self)
 
410
            # does it have a repository ?
 
411
            try:
 
412
                repository = found_bzrdir.open_repository()
 
413
            except errors.NoRepositoryPresent:
 
414
                next_transport = found_bzrdir.root_transport.clone('..')
 
415
                if (found_bzrdir.root_transport.base == next_transport.base):
 
416
                    # top of the file system
 
417
                    break
 
418
                else:
 
419
                    continue
 
420
            if ((found_bzrdir.root_transport.base ==
 
421
                 self.root_transport.base) or repository.is_shared()):
 
422
                return repository
 
423
            else:
 
424
                raise errors.NoRepositoryPresent(self)
 
425
        raise errors.NoRepositoryPresent(self)
 
426
 
 
427
    def get_branch_reference(self):
 
428
        """Return the referenced URL for the branch in this bzrdir.
 
429
 
 
430
        :raises NotBranchError: If there is no Branch.
 
431
        :return: The URL the branch in this bzrdir references if it is a
 
432
            reference branch, or None for regular branches.
 
433
        """
 
434
        return None
 
435
 
 
436
    def get_branch_transport(self, branch_format):
 
437
        """Get the transport for use by branch format in this BzrDir.
 
438
 
 
439
        Note that bzr dirs that do not support format strings will raise
 
440
        IncompatibleFormat if the branch format they are given has
 
441
        a format string, and vice versa.
 
442
 
 
443
        If branch_format is None, the transport is returned with no 
 
444
        checking. if it is not None, then the returned transport is
 
445
        guaranteed to point to an existing directory ready for use.
 
446
        """
 
447
        raise NotImplementedError(self.get_branch_transport)
 
448
        
 
449
    def get_repository_transport(self, repository_format):
 
450
        """Get the transport for use by repository format in this BzrDir.
 
451
 
 
452
        Note that bzr dirs that do not support format strings will raise
 
453
        IncompatibleFormat if the repository format they are given has
 
454
        a format string, and vice versa.
 
455
 
 
456
        If repository_format is None, the transport is returned with no 
 
457
        checking. if it is not None, then the returned transport is
 
458
        guaranteed to point to an existing directory ready for use.
 
459
        """
 
460
        raise NotImplementedError(self.get_repository_transport)
 
461
        
 
462
    def get_workingtree_transport(self, tree_format):
 
463
        """Get the transport for use by workingtree format in this BzrDir.
 
464
 
 
465
        Note that bzr dirs that do not support format strings will raise
 
466
        IncompatibleFormat if the workingtree format they are given has a
 
467
        format string, and vice versa.
 
468
 
 
469
        If workingtree_format is None, the transport is returned with no 
 
470
        checking. if it is not None, then the returned transport is
 
471
        guaranteed to point to an existing directory ready for use.
 
472
        """
 
473
        raise NotImplementedError(self.get_workingtree_transport)
 
474
        
 
475
    def __init__(self, _transport, _format):
 
476
        """Initialize a Bzr control dir object.
 
477
        
 
478
        Only really common logic should reside here, concrete classes should be
 
479
        made with varying behaviours.
 
480
 
 
481
        :param _format: the format that is creating this BzrDir instance.
 
482
        :param _transport: the transport this dir is based at.
 
483
        """
 
484
        self._format = _format
 
485
        self.transport = _transport.clone('.bzr')
 
486
        self.root_transport = _transport
 
487
 
 
488
    def is_control_filename(self, filename):
 
489
        """True if filename is the name of a path which is reserved for bzrdir's.
 
490
        
 
491
        :param filename: A filename within the root transport of this bzrdir.
 
492
 
 
493
        This is true IF and ONLY IF the filename is part of the namespace reserved
 
494
        for bzr control dirs. Currently this is the '.bzr' directory in the root
 
495
        of the root_transport. it is expected that plugins will need to extend
 
496
        this in the future - for instance to make bzr talk with svn working
 
497
        trees.
 
498
        """
 
499
        # this might be better on the BzrDirFormat class because it refers to 
 
500
        # all the possible bzrdir disk formats. 
 
501
        # This method is tested via the workingtree is_control_filename tests- 
 
502
        # it was extracted from WorkingTree.is_control_filename. If the methods
 
503
        # contract is extended beyond the current trivial  implementation please
 
504
        # add new tests for it to the appropriate place.
 
505
        return filename == '.bzr' or filename.startswith('.bzr/')
 
506
 
 
507
    def needs_format_conversion(self, format=None):
 
508
        """Return true if this bzrdir needs convert_format run on it.
 
509
        
 
510
        For instance, if the repository format is out of date but the 
 
511
        branch and working tree are not, this should return True.
 
512
 
 
513
        :param format: Optional parameter indicating a specific desired
 
514
                       format we plan to arrive at.
 
515
        """
 
516
        raise NotImplementedError(self.needs_format_conversion)
 
517
 
 
518
    @staticmethod
 
519
    def open_unsupported(base):
 
520
        """Open a branch which is not supported."""
 
521
        return BzrDir.open(base, _unsupported=True)
 
522
        
 
523
    @staticmethod
 
524
    def open(base, _unsupported=False):
 
525
        """Open an existing bzrdir, rooted at 'base' (url)
 
526
        
 
527
        _unsupported is a private parameter to the BzrDir class.
 
528
        """
 
529
        t = get_transport(base)
 
530
        return BzrDir.open_from_transport(t, _unsupported=_unsupported)
 
531
 
 
532
    @staticmethod
 
533
    def open_from_transport(transport, _unsupported=False):
 
534
        """Open a bzrdir within a particular directory.
 
535
 
 
536
        :param transport: Transport containing the bzrdir.
 
537
        :param _unsupported: private.
 
538
        """
 
539
        format = BzrDirFormat.find_format(transport)
 
540
        BzrDir._check_supported(format, _unsupported)
 
541
        return format.open(transport, _found=True)
 
542
 
 
543
    def open_branch(self, unsupported=False):
 
544
        """Open the branch object at this BzrDir if one is present.
 
545
 
 
546
        If unsupported is True, then no longer supported branch formats can
 
547
        still be opened.
 
548
        
 
549
        TODO: static convenience version of this?
 
550
        """
 
551
        raise NotImplementedError(self.open_branch)
 
552
 
 
553
    @staticmethod
 
554
    def open_containing(url):
 
555
        """Open an existing branch which contains url.
 
556
        
 
557
        :param url: url to search from.
 
558
        See open_containing_from_transport for more detail.
 
559
        """
 
560
        return BzrDir.open_containing_from_transport(get_transport(url))
 
561
    
 
562
    @staticmethod
 
563
    def open_containing_from_transport(a_transport):
 
564
        """Open an existing branch which contains a_transport.base
 
565
 
 
566
        This probes for a branch at a_transport, and searches upwards from there.
 
567
 
 
568
        Basically we keep looking up until we find the control directory or
 
569
        run into the root.  If there isn't one, raises NotBranchError.
 
570
        If there is one and it is either an unrecognised format or an unsupported 
 
571
        format, UnknownFormatError or UnsupportedFormatError are raised.
 
572
        If there is one, it is returned, along with the unused portion of url.
 
573
 
 
574
        :return: The BzrDir that contains the path, and a Unicode path 
 
575
                for the rest of the URL.
 
576
        """
 
577
        # this gets the normalised url back. I.e. '.' -> the full path.
 
578
        url = a_transport.base
 
579
        while True:
 
580
            try:
 
581
                result = BzrDir.open_from_transport(a_transport)
 
582
                return result, urlutils.unescape(a_transport.relpath(url))
 
583
            except errors.NotBranchError, e:
 
584
                pass
 
585
            try:
 
586
                new_t = a_transport.clone('..')
 
587
            except errors.InvalidURLJoin:
 
588
                # reached the root, whatever that may be
 
589
                raise errors.NotBranchError(path=url)
 
590
            if new_t.base == a_transport.base:
 
591
                # reached the root, whatever that may be
 
592
                raise errors.NotBranchError(path=url)
 
593
            a_transport = new_t
 
594
 
 
595
    @classmethod
 
596
    def open_containing_tree_or_branch(klass, location):
 
597
        """Return the branch and working tree contained by a location.
 
598
 
 
599
        Returns (tree, branch, relpath).
 
600
        If there is no tree at containing the location, tree will be None.
 
601
        If there is no branch containing the location, an exception will be
 
602
        raised
 
603
        relpath is the portion of the path that is contained by the branch.
 
604
        """
 
605
        bzrdir, relpath = klass.open_containing(location)
 
606
        try:
 
607
            tree = bzrdir.open_workingtree()
 
608
        except (errors.NoWorkingTree, errors.NotLocalUrl):
 
609
            tree = None
 
610
            branch = bzrdir.open_branch()
 
611
        else:
 
612
            branch = tree.branch
 
613
        return tree, branch, relpath
 
614
 
 
615
    def open_repository(self, _unsupported=False):
 
616
        """Open the repository object at this BzrDir if one is present.
 
617
 
 
618
        This will not follow the Branch object pointer - its strictly a direct
 
619
        open facility. Most client code should use open_branch().repository to
 
620
        get at a repository.
 
621
 
 
622
        _unsupported is a private parameter, not part of the api.
 
623
        TODO: static convenience version of this?
 
624
        """
 
625
        raise NotImplementedError(self.open_repository)
 
626
 
 
627
    def open_workingtree(self, _unsupported=False):
 
628
        """Open the workingtree object at this BzrDir if one is present.
 
629
        
 
630
        TODO: static convenience version of this?
 
631
        """
 
632
        raise NotImplementedError(self.open_workingtree)
 
633
 
 
634
    def has_branch(self):
 
635
        """Tell if this bzrdir contains a branch.
 
636
        
 
637
        Note: if you're going to open the branch, you should just go ahead
 
638
        and try, and not ask permission first.  (This method just opens the 
 
639
        branch and discards it, and that's somewhat expensive.) 
 
640
        """
 
641
        try:
 
642
            self.open_branch()
 
643
            return True
 
644
        except errors.NotBranchError:
 
645
            return False
 
646
 
 
647
    def has_workingtree(self):
 
648
        """Tell if this bzrdir contains a working tree.
 
649
 
 
650
        This will still raise an exception if the bzrdir has a workingtree that
 
651
        is remote & inaccessible.
 
652
        
 
653
        Note: if you're going to open the working tree, you should just go ahead
 
654
        and try, and not ask permission first.  (This method just opens the 
 
655
        workingtree and discards it, and that's somewhat expensive.) 
 
656
        """
 
657
        try:
 
658
            self.open_workingtree()
 
659
            return True
 
660
        except errors.NoWorkingTree:
 
661
            return False
 
662
 
 
663
    def _cloning_metadir(self):
 
664
        """Produce a metadir suitable for cloning with"""
 
665
        def related_repository(bzrdir):
 
666
            try:
 
667
                branch = bzrdir.open_branch()
 
668
                return branch.repository
 
669
            except errors.NotBranchError:
 
670
                source_branch = None
 
671
                return bzrdir.open_repository()
 
672
        result_format = self._format.__class__()
 
673
        try:
 
674
            source_repository = related_repository(self)
 
675
        except errors.NoRepositoryPresent:
 
676
            source_repository = None
 
677
        else:
 
678
            repo_format = source_repository._format
 
679
            if not isinstance(repo_format, remote.RemoteRepositoryFormat):
 
680
                result_format.repository_format = repo_format
 
681
        try:
 
682
            tree = self.open_workingtree()
 
683
        except (errors.NoWorkingTree, errors.NotLocalUrl):
 
684
            result_format.workingtree_format = None
 
685
        else:
 
686
            result_format.workingtree_format = tree._format.__class__()
 
687
        return result_format, source_repository
 
688
 
 
689
    def cloning_metadir(self):
 
690
        """Produce a metadir suitable for cloning or sprouting with.
 
691
 
 
692
        These operations may produce workingtrees (yes, even though they're
 
693
        "cloning" something that doesn't have a tree, so a viable workingtree
 
694
        format must be selected.
 
695
        """
 
696
        format, repository = self._cloning_metadir()
 
697
        if format._workingtree_format is None:
 
698
            if repository is None:
 
699
                return format
 
700
            tree_format = repository._format._matchingbzrdir.workingtree_format
 
701
            format.workingtree_format = tree_format.__class__()
 
702
        return format
 
703
 
 
704
    def checkout_metadir(self):
 
705
        return self.cloning_metadir()
 
706
 
 
707
    def sprout(self, url, revision_id=None, basis=None, force_new_repo=False,
 
708
               recurse='down'):
 
709
        """Create a copy of this bzrdir prepared for use as a new line of
 
710
        development.
 
711
 
 
712
        If urls last component does not exist, it will be created.
 
713
 
 
714
        Attributes related to the identity of the source branch like
 
715
        branch nickname will be cleaned, a working tree is created
 
716
        whether one existed before or not; and a local branch is always
 
717
        created.
 
718
 
 
719
        if revision_id is not None, then the clone operation may tune
 
720
            itself to download less data.
 
721
        """
 
722
        self._make_tail(url)
 
723
        cloning_format = self.cloning_metadir()
 
724
        result = cloning_format.initialize(url)
 
725
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
726
        try:
 
727
            source_branch = self.open_branch()
 
728
            source_repository = source_branch.repository
 
729
        except errors.NotBranchError:
 
730
            source_branch = None
 
731
            try:
 
732
                source_repository = self.open_repository()
 
733
            except errors.NoRepositoryPresent:
 
734
                # copy the entire basis one if there is one
 
735
                # but there is no repository.
 
736
                source_repository = basis_repo
 
737
        if force_new_repo:
 
738
            result_repo = None
 
739
        else:
 
740
            try:
 
741
                result_repo = result.find_repository()
 
742
            except errors.NoRepositoryPresent:
 
743
                result_repo = None
 
744
        if source_repository is None and result_repo is not None:
 
745
            pass
 
746
        elif source_repository is None and result_repo is None:
 
747
            # no repo available, make a new one
 
748
            result.create_repository()
 
749
        elif source_repository is not None and result_repo is None:
 
750
            # have source, and want to make a new target repo
 
751
            # we don't clone the repo because that preserves attributes
 
752
            # like is_shared(), and we have not yet implemented a 
 
753
            # repository sprout().
 
754
            result_repo = result.create_repository()
 
755
        if result_repo is not None:
 
756
            # fetch needed content into target.
 
757
            if basis_repo:
 
758
                # XXX FIXME RBC 20060214 need tests for this when the basis
 
759
                # is incomplete
 
760
                result_repo.fetch(basis_repo, revision_id=revision_id)
 
761
            if source_repository is not None:
 
762
                result_repo.fetch(source_repository, revision_id=revision_id)
 
763
        if source_branch is not None:
 
764
            source_branch.sprout(result, revision_id=revision_id)
 
765
        else:
 
766
            result.create_branch()
 
767
        # TODO: jam 20060426 we probably need a test in here in the
 
768
        #       case that the newly sprouted branch is a remote one
 
769
        if result_repo is None or result_repo.make_working_trees():
 
770
            wt = result.create_workingtree()
 
771
            wt.lock_write()
 
772
            try:
 
773
                if wt.path2id('') is None:
 
774
                    try:
 
775
                        wt.set_root_id(self.open_workingtree.get_root_id())
 
776
                    except errors.NoWorkingTree:
 
777
                        pass
 
778
            finally:
 
779
                wt.unlock()
 
780
        else:
 
781
            wt = None
 
782
        if recurse == 'down':
 
783
            if wt is not None:
 
784
                basis = wt.basis_tree()
 
785
                basis.lock_read()
 
786
                subtrees = basis.iter_references()
 
787
                recurse_branch = wt.branch
 
788
            elif source_branch is not None:
 
789
                basis = source_branch.basis_tree()
 
790
                basis.lock_read()
 
791
                subtrees = basis.iter_references()
 
792
                recurse_branch = source_branch
 
793
            else:
 
794
                subtrees = []
 
795
                basis = None
 
796
            try:
 
797
                for path, file_id in subtrees:
 
798
                    target = urlutils.join(url, urlutils.escape(path))
 
799
                    sublocation = source_branch.reference_parent(file_id, path)
 
800
                    sublocation.bzrdir.sprout(target,
 
801
                        basis.get_reference_revision(file_id, path),
 
802
                        force_new_repo=force_new_repo, recurse=recurse)
 
803
            finally:
 
804
                if basis is not None:
 
805
                    basis.unlock()
 
806
        return result
 
807
 
 
808
 
 
809
class BzrDirPreSplitOut(BzrDir):
 
810
    """A common class for the all-in-one formats."""
 
811
 
 
812
    def __init__(self, _transport, _format):
 
813
        """See BzrDir.__init__."""
 
814
        super(BzrDirPreSplitOut, self).__init__(_transport, _format)
 
815
        assert self._format._lock_class == lockable_files.TransportLock
 
816
        assert self._format._lock_file_name == 'branch-lock'
 
817
        self._control_files = lockable_files.LockableFiles(
 
818
                                            self.get_branch_transport(None),
 
819
                                            self._format._lock_file_name,
 
820
                                            self._format._lock_class)
 
821
 
 
822
    def break_lock(self):
 
823
        """Pre-splitout bzrdirs do not suffer from stale locks."""
 
824
        raise NotImplementedError(self.break_lock)
 
825
 
 
826
    def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
 
827
        """See BzrDir.clone()."""
 
828
        from bzrlib.workingtree import WorkingTreeFormat2
 
829
        self._make_tail(url)
 
830
        result = self._format._initialize_for_clone(url)
 
831
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
832
        self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
 
833
        from_branch = self.open_branch()
 
834
        from_branch.clone(result, revision_id=revision_id)
 
835
        try:
 
836
            self.open_workingtree().clone(result, basis=basis_tree)
 
837
        except errors.NotLocalUrl:
 
838
            # make a new one, this format always has to have one.
 
839
            try:
 
840
                WorkingTreeFormat2().initialize(result)
 
841
            except errors.NotLocalUrl:
 
842
                # but we cannot do it for remote trees.
 
843
                to_branch = result.open_branch()
 
844
                WorkingTreeFormat2().stub_initialize_remote(to_branch.control_files)
 
845
        return result
 
846
 
 
847
    def create_branch(self):
 
848
        """See BzrDir.create_branch."""
 
849
        return self.open_branch()
 
850
 
 
851
    def create_repository(self, shared=False):
 
852
        """See BzrDir.create_repository."""
 
853
        if shared:
 
854
            raise errors.IncompatibleFormat('shared repository', self._format)
 
855
        return self.open_repository()
 
856
 
 
857
    def create_workingtree(self, revision_id=None):
 
858
        """See BzrDir.create_workingtree."""
 
859
        # this looks buggy but is not -really-
 
860
        # clone and sprout will have set the revision_id
 
861
        # and that will have set it for us, its only
 
862
        # specific uses of create_workingtree in isolation
 
863
        # that can do wonky stuff here, and that only
 
864
        # happens for creating checkouts, which cannot be 
 
865
        # done on this format anyway. So - acceptable wart.
 
866
        result = self.open_workingtree()
 
867
        if revision_id is not None:
 
868
            if revision_id == _mod_revision.NULL_REVISION:
 
869
                result.set_parent_ids([])
 
870
            else:
 
871
                result.set_parent_ids([revision_id])
 
872
        return result
 
873
 
 
874
    def destroy_workingtree(self):
 
875
        """See BzrDir.destroy_workingtree."""
 
876
        raise errors.UnsupportedOperation(self.destroy_workingtree, self)
 
877
 
 
878
    def destroy_workingtree_metadata(self):
 
879
        """See BzrDir.destroy_workingtree_metadata."""
 
880
        raise errors.UnsupportedOperation(self.destroy_workingtree_metadata, 
 
881
                                          self)
 
882
 
 
883
    def get_branch_transport(self, branch_format):
 
884
        """See BzrDir.get_branch_transport()."""
 
885
        if branch_format is None:
 
886
            return self.transport
 
887
        try:
 
888
            branch_format.get_format_string()
 
889
        except NotImplementedError:
 
890
            return self.transport
 
891
        raise errors.IncompatibleFormat(branch_format, self._format)
 
892
 
 
893
    def get_repository_transport(self, repository_format):
 
894
        """See BzrDir.get_repository_transport()."""
 
895
        if repository_format is None:
 
896
            return self.transport
 
897
        try:
 
898
            repository_format.get_format_string()
 
899
        except NotImplementedError:
 
900
            return self.transport
 
901
        raise errors.IncompatibleFormat(repository_format, self._format)
 
902
 
 
903
    def get_workingtree_transport(self, workingtree_format):
 
904
        """See BzrDir.get_workingtree_transport()."""
 
905
        if workingtree_format is None:
 
906
            return self.transport
 
907
        try:
 
908
            workingtree_format.get_format_string()
 
909
        except NotImplementedError:
 
910
            return self.transport
 
911
        raise errors.IncompatibleFormat(workingtree_format, self._format)
 
912
 
 
913
    def needs_format_conversion(self, format=None):
 
914
        """See BzrDir.needs_format_conversion()."""
 
915
        # if the format is not the same as the system default,
 
916
        # an upgrade is needed.
 
917
        if format is None:
 
918
            format = BzrDirFormat.get_default_format()
 
919
        return not isinstance(self._format, format.__class__)
 
920
 
 
921
    def open_branch(self, unsupported=False):
 
922
        """See BzrDir.open_branch."""
 
923
        from bzrlib.branch import BzrBranchFormat4
 
924
        format = BzrBranchFormat4()
 
925
        self._check_supported(format, unsupported)
 
926
        return format.open(self, _found=True)
 
927
 
 
928
    def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
 
929
        """See BzrDir.sprout()."""
 
930
        from bzrlib.workingtree import WorkingTreeFormat2
 
931
        self._make_tail(url)
 
932
        result = self._format._initialize_for_clone(url)
 
933
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
934
        try:
 
935
            self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
 
936
        except errors.NoRepositoryPresent:
 
937
            pass
 
938
        try:
 
939
            self.open_branch().sprout(result, revision_id=revision_id)
 
940
        except errors.NotBranchError:
 
941
            pass
 
942
        # we always want a working tree
 
943
        WorkingTreeFormat2().initialize(result)
 
944
        return result
 
945
 
 
946
 
 
947
class BzrDir4(BzrDirPreSplitOut):
 
948
    """A .bzr version 4 control object.
 
949
    
 
950
    This is a deprecated format and may be removed after sept 2006.
 
951
    """
 
952
 
 
953
    def create_repository(self, shared=False):
 
954
        """See BzrDir.create_repository."""
 
955
        return self._format.repository_format.initialize(self, shared)
 
956
 
 
957
    def needs_format_conversion(self, format=None):
 
958
        """Format 4 dirs are always in need of conversion."""
 
959
        return True
 
960
 
 
961
    def open_repository(self):
 
962
        """See BzrDir.open_repository."""
 
963
        from bzrlib.repofmt.weaverepo import RepositoryFormat4
 
964
        return RepositoryFormat4().open(self, _found=True)
 
965
 
 
966
 
 
967
class BzrDir5(BzrDirPreSplitOut):
 
968
    """A .bzr version 5 control object.
 
969
 
 
970
    This is a deprecated format and may be removed after sept 2006.
 
971
    """
 
972
 
 
973
    def open_repository(self):
 
974
        """See BzrDir.open_repository."""
 
975
        from bzrlib.repofmt.weaverepo import RepositoryFormat5
 
976
        return RepositoryFormat5().open(self, _found=True)
 
977
 
 
978
    def open_workingtree(self, _unsupported=False):
 
979
        """See BzrDir.create_workingtree."""
 
980
        from bzrlib.workingtree import WorkingTreeFormat2
 
981
        return WorkingTreeFormat2().open(self, _found=True)
 
982
 
 
983
 
 
984
class BzrDir6(BzrDirPreSplitOut):
 
985
    """A .bzr version 6 control object.
 
986
 
 
987
    This is a deprecated format and may be removed after sept 2006.
 
988
    """
 
989
 
 
990
    def open_repository(self):
 
991
        """See BzrDir.open_repository."""
 
992
        from bzrlib.repofmt.weaverepo import RepositoryFormat6
 
993
        return RepositoryFormat6().open(self, _found=True)
 
994
 
 
995
    def open_workingtree(self, _unsupported=False):
 
996
        """See BzrDir.create_workingtree."""
 
997
        from bzrlib.workingtree import WorkingTreeFormat2
 
998
        return WorkingTreeFormat2().open(self, _found=True)
 
999
 
 
1000
 
 
1001
class BzrDirMeta1(BzrDir):
 
1002
    """A .bzr meta version 1 control object.
 
1003
    
 
1004
    This is the first control object where the 
 
1005
    individual aspects are really split out: there are separate repository,
 
1006
    workingtree and branch subdirectories and any subset of the three can be
 
1007
    present within a BzrDir.
 
1008
    """
 
1009
 
 
1010
    def can_convert_format(self):
 
1011
        """See BzrDir.can_convert_format()."""
 
1012
        return True
 
1013
 
 
1014
    def create_branch(self):
 
1015
        """See BzrDir.create_branch."""
 
1016
        return self._format.get_branch_format().initialize(self)
 
1017
 
 
1018
    def create_repository(self, shared=False):
 
1019
        """See BzrDir.create_repository."""
 
1020
        return self._format.repository_format.initialize(self, shared)
 
1021
 
 
1022
    def create_workingtree(self, revision_id=None):
 
1023
        """See BzrDir.create_workingtree."""
 
1024
        from bzrlib.workingtree import WorkingTreeFormat
 
1025
        return self._format.workingtree_format.initialize(self, revision_id)
 
1026
 
 
1027
    def destroy_workingtree(self):
 
1028
        """See BzrDir.destroy_workingtree."""
 
1029
        wt = self.open_workingtree()
 
1030
        repository = wt.branch.repository
 
1031
        empty = repository.revision_tree(_mod_revision.NULL_REVISION)
 
1032
        wt.revert([], old_tree=empty)
 
1033
        self.destroy_workingtree_metadata()
 
1034
 
 
1035
    def destroy_workingtree_metadata(self):
 
1036
        self.transport.delete_tree('checkout')
 
1037
 
 
1038
    def find_branch_format(self):
 
1039
        """Find the branch 'format' for this bzrdir.
 
1040
 
 
1041
        This might be a synthetic object for e.g. RemoteBranch and SVN.
 
1042
        """
 
1043
        from bzrlib.branch import BranchFormat
 
1044
        return BranchFormat.find_format(self)
 
1045
 
 
1046
    def _get_mkdir_mode(self):
 
1047
        """Figure out the mode to use when creating a bzrdir subdir."""
 
1048
        temp_control = lockable_files.LockableFiles(self.transport, '',
 
1049
                                     lockable_files.TransportLock)
 
1050
        return temp_control._dir_mode
 
1051
 
 
1052
    def get_branch_reference(self):
 
1053
        """See BzrDir.get_branch_reference()."""
 
1054
        from bzrlib.branch import BranchFormat
 
1055
        format = BranchFormat.find_format(self)
 
1056
        return format.get_reference(self)
 
1057
 
 
1058
    def get_branch_transport(self, branch_format):
 
1059
        """See BzrDir.get_branch_transport()."""
 
1060
        if branch_format is None:
 
1061
            return self.transport.clone('branch')
 
1062
        try:
 
1063
            branch_format.get_format_string()
 
1064
        except NotImplementedError:
 
1065
            raise errors.IncompatibleFormat(branch_format, self._format)
 
1066
        try:
 
1067
            self.transport.mkdir('branch', mode=self._get_mkdir_mode())
 
1068
        except errors.FileExists:
 
1069
            pass
 
1070
        return self.transport.clone('branch')
 
1071
 
 
1072
    def get_repository_transport(self, repository_format):
 
1073
        """See BzrDir.get_repository_transport()."""
 
1074
        if repository_format is None:
 
1075
            return self.transport.clone('repository')
 
1076
        try:
 
1077
            repository_format.get_format_string()
 
1078
        except NotImplementedError:
 
1079
            raise errors.IncompatibleFormat(repository_format, self._format)
 
1080
        try:
 
1081
            self.transport.mkdir('repository', mode=self._get_mkdir_mode())
 
1082
        except errors.FileExists:
 
1083
            pass
 
1084
        return self.transport.clone('repository')
 
1085
 
 
1086
    def get_workingtree_transport(self, workingtree_format):
 
1087
        """See BzrDir.get_workingtree_transport()."""
 
1088
        if workingtree_format is None:
 
1089
            return self.transport.clone('checkout')
 
1090
        try:
 
1091
            workingtree_format.get_format_string()
 
1092
        except NotImplementedError:
 
1093
            raise errors.IncompatibleFormat(workingtree_format, self._format)
 
1094
        try:
 
1095
            self.transport.mkdir('checkout', mode=self._get_mkdir_mode())
 
1096
        except errors.FileExists:
 
1097
            pass
 
1098
        return self.transport.clone('checkout')
 
1099
 
 
1100
    def needs_format_conversion(self, format=None):
 
1101
        """See BzrDir.needs_format_conversion()."""
 
1102
        if format is None:
 
1103
            format = BzrDirFormat.get_default_format()
 
1104
        if not isinstance(self._format, format.__class__):
 
1105
            # it is not a meta dir format, conversion is needed.
 
1106
            return True
 
1107
        # we might want to push this down to the repository?
 
1108
        try:
 
1109
            if not isinstance(self.open_repository()._format,
 
1110
                              format.repository_format.__class__):
 
1111
                # the repository needs an upgrade.
 
1112
                return True
 
1113
        except errors.NoRepositoryPresent:
 
1114
            pass
 
1115
        try:
 
1116
            if not isinstance(self.open_branch()._format,
 
1117
                              format.get_branch_format().__class__):
 
1118
                # the branch needs an upgrade.
 
1119
                return True
 
1120
        except errors.NotBranchError:
 
1121
            pass
 
1122
        try:
 
1123
            if not isinstance(self.open_workingtree()._format,
 
1124
                              format.workingtree_format.__class__):
 
1125
                # the workingtree needs an upgrade.
 
1126
                return True
 
1127
        except (errors.NoWorkingTree, errors.NotLocalUrl):
 
1128
            pass
 
1129
        return False
 
1130
 
 
1131
    def open_branch(self, unsupported=False):
 
1132
        """See BzrDir.open_branch."""
 
1133
        format = self.find_branch_format()
 
1134
        self._check_supported(format, unsupported)
 
1135
        return format.open(self, _found=True)
 
1136
 
 
1137
    def open_repository(self, unsupported=False):
 
1138
        """See BzrDir.open_repository."""
 
1139
        from bzrlib.repository import RepositoryFormat
 
1140
        format = RepositoryFormat.find_format(self)
 
1141
        self._check_supported(format, unsupported)
 
1142
        return format.open(self, _found=True)
 
1143
 
 
1144
    def open_workingtree(self, unsupported=False):
 
1145
        """See BzrDir.open_workingtree."""
 
1146
        from bzrlib.workingtree import WorkingTreeFormat
 
1147
        format = WorkingTreeFormat.find_format(self)
 
1148
        self._check_supported(format, unsupported)
 
1149
        return format.open(self, _found=True)
 
1150
 
 
1151
 
 
1152
class BzrDirFormat(object):
 
1153
    """An encapsulation of the initialization and open routines for a format.
 
1154
 
 
1155
    Formats provide three things:
 
1156
     * An initialization routine,
 
1157
     * a format string,
 
1158
     * an open routine.
 
1159
 
 
1160
    Formats are placed in an dict by their format string for reference 
 
1161
    during bzrdir opening. These should be subclasses of BzrDirFormat
 
1162
    for consistency.
 
1163
 
 
1164
    Once a format is deprecated, just deprecate the initialize and open
 
1165
    methods on the format class. Do not deprecate the object, as the 
 
1166
    object will be created every system load.
 
1167
    """
 
1168
 
 
1169
    _default_format = None
 
1170
    """The default format used for new .bzr dirs."""
 
1171
 
 
1172
    _formats = {}
 
1173
    """The known formats."""
 
1174
 
 
1175
    _control_formats = []
 
1176
    """The registered control formats - .bzr, ....
 
1177
    
 
1178
    This is a list of BzrDirFormat objects.
 
1179
    """
 
1180
 
 
1181
    _lock_file_name = 'branch-lock'
 
1182
 
 
1183
    # _lock_class must be set in subclasses to the lock type, typ.
 
1184
    # TransportLock or LockDir
 
1185
 
 
1186
    @classmethod
 
1187
    def find_format(klass, transport):
 
1188
        """Return the format present at transport."""
 
1189
        for format in klass._control_formats:
 
1190
            try:
 
1191
                return format.probe_transport(transport)
 
1192
            except errors.NotBranchError:
 
1193
                # this format does not find a control dir here.
 
1194
                pass
 
1195
        raise errors.NotBranchError(path=transport.base)
 
1196
 
 
1197
    @classmethod
 
1198
    def probe_transport(klass, transport):
 
1199
        """Return the .bzrdir style format present in a directory."""
 
1200
        try:
 
1201
            format_string = transport.get(".bzr/branch-format").read()
 
1202
        except errors.NoSuchFile:
 
1203
            raise errors.NotBranchError(path=transport.base)
 
1204
 
 
1205
        try:
 
1206
            return klass._formats[format_string]
 
1207
        except KeyError:
 
1208
            raise errors.UnknownFormatError(format=format_string)
 
1209
 
 
1210
    @classmethod
 
1211
    def get_default_format(klass):
 
1212
        """Return the current default format."""
 
1213
        return klass._default_format
 
1214
 
 
1215
    def get_format_string(self):
 
1216
        """Return the ASCII format string that identifies this format."""
 
1217
        raise NotImplementedError(self.get_format_string)
 
1218
 
 
1219
    def get_format_description(self):
 
1220
        """Return the short description for this format."""
 
1221
        raise NotImplementedError(self.get_format_description)
 
1222
 
 
1223
    def get_converter(self, format=None):
 
1224
        """Return the converter to use to convert bzrdirs needing converts.
 
1225
 
 
1226
        This returns a bzrlib.bzrdir.Converter object.
 
1227
 
 
1228
        This should return the best upgrader to step this format towards the
 
1229
        current default format. In the case of plugins we can/should provide
 
1230
        some means for them to extend the range of returnable converters.
 
1231
 
 
1232
        :param format: Optional format to override the default format of the 
 
1233
                       library.
 
1234
        """
 
1235
        raise NotImplementedError(self.get_converter)
 
1236
 
 
1237
    def initialize(self, url):
 
1238
        """Create a bzr control dir at this url and return an opened copy.
 
1239
        
 
1240
        Subclasses should typically override initialize_on_transport
 
1241
        instead of this method.
 
1242
        """
 
1243
        return self.initialize_on_transport(get_transport(url))
 
1244
 
 
1245
    def initialize_on_transport(self, transport):
 
1246
        """Initialize a new bzrdir in the base directory of a Transport."""
 
1247
        # Since we don't have a .bzr directory, inherit the
 
1248
        # mode from the root directory
 
1249
        temp_control = lockable_files.LockableFiles(transport,
 
1250
                            '', lockable_files.TransportLock)
 
1251
        temp_control._transport.mkdir('.bzr',
 
1252
                                      # FIXME: RBC 20060121 don't peek under
 
1253
                                      # the covers
 
1254
                                      mode=temp_control._dir_mode)
 
1255
        file_mode = temp_control._file_mode
 
1256
        del temp_control
 
1257
        mutter('created control directory in ' + transport.base)
 
1258
        control = transport.clone('.bzr')
 
1259
        utf8_files = [('README', 
 
1260
                       "This is a Bazaar-NG control directory.\n"
 
1261
                       "Do not change any files in this directory.\n"),
 
1262
                      ('branch-format', self.get_format_string()),
 
1263
                      ]
 
1264
        # NB: no need to escape relative paths that are url safe.
 
1265
        control_files = lockable_files.LockableFiles(control,
 
1266
                            self._lock_file_name, self._lock_class)
 
1267
        control_files.create_lock()
 
1268
        control_files.lock_write()
 
1269
        try:
 
1270
            for file, content in utf8_files:
 
1271
                control_files.put_utf8(file, content)
 
1272
        finally:
 
1273
            control_files.unlock()
 
1274
        return self.open(transport, _found=True)
 
1275
 
 
1276
    def is_supported(self):
 
1277
        """Is this format supported?
 
1278
 
 
1279
        Supported formats must be initializable and openable.
 
1280
        Unsupported formats may not support initialization or committing or 
 
1281
        some other features depending on the reason for not being supported.
 
1282
        """
 
1283
        return True
 
1284
 
 
1285
    def same_model(self, target_format):
 
1286
        return (self.repository_format.rich_root_data == 
 
1287
            target_format.rich_root_data)
 
1288
 
 
1289
    @classmethod
 
1290
    def known_formats(klass):
 
1291
        """Return all the known formats.
 
1292
        
 
1293
        Concrete formats should override _known_formats.
 
1294
        """
 
1295
        # There is double indirection here to make sure that control 
 
1296
        # formats used by more than one dir format will only be probed 
 
1297
        # once. This can otherwise be quite expensive for remote connections.
 
1298
        result = set()
 
1299
        for format in klass._control_formats:
 
1300
            result.update(format._known_formats())
 
1301
        return result
 
1302
    
 
1303
    @classmethod
 
1304
    def _known_formats(klass):
 
1305
        """Return the known format instances for this control format."""
 
1306
        return set(klass._formats.values())
 
1307
 
 
1308
    def open(self, transport, _found=False):
 
1309
        """Return an instance of this format for the dir transport points at.
 
1310
        
 
1311
        _found is a private parameter, do not use it.
 
1312
        """
 
1313
        if not _found:
 
1314
            found_format = BzrDirFormat.find_format(transport)
 
1315
            if not isinstance(found_format, self.__class__):
 
1316
                raise AssertionError("%s was asked to open %s, but it seems to need "
 
1317
                        "format %s" 
 
1318
                        % (self, transport, found_format))
 
1319
        return self._open(transport)
 
1320
 
 
1321
    def _open(self, transport):
 
1322
        """Template method helper for opening BzrDirectories.
 
1323
 
 
1324
        This performs the actual open and any additional logic or parameter
 
1325
        passing.
 
1326
        """
 
1327
        raise NotImplementedError(self._open)
 
1328
 
 
1329
    @classmethod
 
1330
    def register_format(klass, format):
 
1331
        klass._formats[format.get_format_string()] = format
 
1332
 
 
1333
    @classmethod
 
1334
    def register_control_format(klass, format):
 
1335
        """Register a format that does not use '.bzrdir' for its control dir.
 
1336
 
 
1337
        TODO: This should be pulled up into a 'ControlDirFormat' base class
 
1338
        which BzrDirFormat can inherit from, and renamed to register_format 
 
1339
        there. It has been done without that for now for simplicity of
 
1340
        implementation.
 
1341
        """
 
1342
        klass._control_formats.append(format)
 
1343
 
 
1344
    @classmethod
 
1345
    @symbol_versioning.deprecated_method(symbol_versioning.zero_fourteen)
 
1346
    def set_default_format(klass, format):
 
1347
        klass._set_default_format(format)
 
1348
 
 
1349
    @classmethod
 
1350
    def _set_default_format(klass, format):
 
1351
        """Set default format (for testing behavior of defaults only)"""
 
1352
        klass._default_format = format
 
1353
 
 
1354
    def __str__(self):
 
1355
        return self.get_format_string()[:-1]
 
1356
 
 
1357
    @classmethod
 
1358
    def unregister_format(klass, format):
 
1359
        assert klass._formats[format.get_format_string()] is format
 
1360
        del klass._formats[format.get_format_string()]
 
1361
 
 
1362
    @classmethod
 
1363
    def unregister_control_format(klass, format):
 
1364
        klass._control_formats.remove(format)
 
1365
 
 
1366
 
 
1367
# register BzrDirFormat as a control format
 
1368
BzrDirFormat.register_control_format(BzrDirFormat)
 
1369
 
 
1370
 
 
1371
class BzrDirFormat4(BzrDirFormat):
 
1372
    """Bzr dir format 4.
 
1373
 
 
1374
    This format is a combined format for working tree, branch and repository.
 
1375
    It has:
 
1376
     - Format 1 working trees [always]
 
1377
     - Format 4 branches [always]
 
1378
     - Format 4 repositories [always]
 
1379
 
 
1380
    This format is deprecated: it indexes texts using a text it which is
 
1381
    removed in format 5; write support for this format has been removed.
 
1382
    """
 
1383
 
 
1384
    _lock_class = lockable_files.TransportLock
 
1385
 
 
1386
    def get_format_string(self):
 
1387
        """See BzrDirFormat.get_format_string()."""
 
1388
        return "Bazaar-NG branch, format 0.0.4\n"
 
1389
 
 
1390
    def get_format_description(self):
 
1391
        """See BzrDirFormat.get_format_description()."""
 
1392
        return "All-in-one format 4"
 
1393
 
 
1394
    def get_converter(self, format=None):
 
1395
        """See BzrDirFormat.get_converter()."""
 
1396
        # there is one and only one upgrade path here.
 
1397
        return ConvertBzrDir4To5()
 
1398
        
 
1399
    def initialize_on_transport(self, transport):
 
1400
        """Format 4 branches cannot be created."""
 
1401
        raise errors.UninitializableFormat(self)
 
1402
 
 
1403
    def is_supported(self):
 
1404
        """Format 4 is not supported.
 
1405
 
 
1406
        It is not supported because the model changed from 4 to 5 and the
 
1407
        conversion logic is expensive - so doing it on the fly was not 
 
1408
        feasible.
 
1409
        """
 
1410
        return False
 
1411
 
 
1412
    def _open(self, transport):
 
1413
        """See BzrDirFormat._open."""
 
1414
        return BzrDir4(transport, self)
 
1415
 
 
1416
    def __return_repository_format(self):
 
1417
        """Circular import protection."""
 
1418
        from bzrlib.repofmt.weaverepo import RepositoryFormat4
 
1419
        return RepositoryFormat4()
 
1420
    repository_format = property(__return_repository_format)
 
1421
 
 
1422
 
 
1423
class BzrDirFormat5(BzrDirFormat):
 
1424
    """Bzr control format 5.
 
1425
 
 
1426
    This format is a combined format for working tree, branch and repository.
 
1427
    It has:
 
1428
     - Format 2 working trees [always] 
 
1429
     - Format 4 branches [always] 
 
1430
     - Format 5 repositories [always]
 
1431
       Unhashed stores in the repository.
 
1432
    """
 
1433
 
 
1434
    _lock_class = lockable_files.TransportLock
 
1435
 
 
1436
    def get_format_string(self):
 
1437
        """See BzrDirFormat.get_format_string()."""
 
1438
        return "Bazaar-NG branch, format 5\n"
 
1439
 
 
1440
    def get_format_description(self):
 
1441
        """See BzrDirFormat.get_format_description()."""
 
1442
        return "All-in-one format 5"
 
1443
 
 
1444
    def get_converter(self, format=None):
 
1445
        """See BzrDirFormat.get_converter()."""
 
1446
        # there is one and only one upgrade path here.
 
1447
        return ConvertBzrDir5To6()
 
1448
 
 
1449
    def _initialize_for_clone(self, url):
 
1450
        return self.initialize_on_transport(get_transport(url), _cloning=True)
 
1451
        
 
1452
    def initialize_on_transport(self, transport, _cloning=False):
 
1453
        """Format 5 dirs always have working tree, branch and repository.
 
1454
        
 
1455
        Except when they are being cloned.
 
1456
        """
 
1457
        from bzrlib.branch import BzrBranchFormat4
 
1458
        from bzrlib.repofmt.weaverepo import RepositoryFormat5
 
1459
        from bzrlib.workingtree import WorkingTreeFormat2
 
1460
        result = (super(BzrDirFormat5, self).initialize_on_transport(transport))
 
1461
        RepositoryFormat5().initialize(result, _internal=True)
 
1462
        if not _cloning:
 
1463
            branch = BzrBranchFormat4().initialize(result)
 
1464
            try:
 
1465
                WorkingTreeFormat2().initialize(result)
 
1466
            except errors.NotLocalUrl:
 
1467
                # Even though we can't access the working tree, we need to
 
1468
                # create its control files.
 
1469
                WorkingTreeFormat2().stub_initialize_remote(branch.control_files)
 
1470
        return result
 
1471
 
 
1472
    def _open(self, transport):
 
1473
        """See BzrDirFormat._open."""
 
1474
        return BzrDir5(transport, self)
 
1475
 
 
1476
    def __return_repository_format(self):
 
1477
        """Circular import protection."""
 
1478
        from bzrlib.repofmt.weaverepo import RepositoryFormat5
 
1479
        return RepositoryFormat5()
 
1480
    repository_format = property(__return_repository_format)
 
1481
 
 
1482
 
 
1483
class BzrDirFormat6(BzrDirFormat):
 
1484
    """Bzr control format 6.
 
1485
 
 
1486
    This format is a combined format for working tree, branch and repository.
 
1487
    It has:
 
1488
     - Format 2 working trees [always] 
 
1489
     - Format 4 branches [always] 
 
1490
     - Format 6 repositories [always]
 
1491
    """
 
1492
 
 
1493
    _lock_class = lockable_files.TransportLock
 
1494
 
 
1495
    def get_format_string(self):
 
1496
        """See BzrDirFormat.get_format_string()."""
 
1497
        return "Bazaar-NG branch, format 6\n"
 
1498
 
 
1499
    def get_format_description(self):
 
1500
        """See BzrDirFormat.get_format_description()."""
 
1501
        return "All-in-one format 6"
 
1502
 
 
1503
    def get_converter(self, format=None):
 
1504
        """See BzrDirFormat.get_converter()."""
 
1505
        # there is one and only one upgrade path here.
 
1506
        return ConvertBzrDir6ToMeta()
 
1507
        
 
1508
    def _initialize_for_clone(self, url):
 
1509
        return self.initialize_on_transport(get_transport(url), _cloning=True)
 
1510
 
 
1511
    def initialize_on_transport(self, transport, _cloning=False):
 
1512
        """Format 6 dirs always have working tree, branch and repository.
 
1513
        
 
1514
        Except when they are being cloned.
 
1515
        """
 
1516
        from bzrlib.branch import BzrBranchFormat4
 
1517
        from bzrlib.repofmt.weaverepo import RepositoryFormat6
 
1518
        from bzrlib.workingtree import WorkingTreeFormat2
 
1519
        result = super(BzrDirFormat6, self).initialize_on_transport(transport)
 
1520
        RepositoryFormat6().initialize(result, _internal=True)
 
1521
        if not _cloning:
 
1522
            branch = BzrBranchFormat4().initialize(result)
 
1523
            try:
 
1524
                WorkingTreeFormat2().initialize(result)
 
1525
            except errors.NotLocalUrl:
 
1526
                # Even though we can't access the working tree, we need to
 
1527
                # create its control files.
 
1528
                WorkingTreeFormat2().stub_initialize_remote(branch.control_files)
 
1529
        return result
 
1530
 
 
1531
    def _open(self, transport):
 
1532
        """See BzrDirFormat._open."""
 
1533
        return BzrDir6(transport, self)
 
1534
 
 
1535
    def __return_repository_format(self):
 
1536
        """Circular import protection."""
 
1537
        from bzrlib.repofmt.weaverepo import RepositoryFormat6
 
1538
        return RepositoryFormat6()
 
1539
    repository_format = property(__return_repository_format)
 
1540
 
 
1541
 
 
1542
class BzrDirMetaFormat1(BzrDirFormat):
 
1543
    """Bzr meta control format 1
 
1544
 
 
1545
    This is the first format with split out working tree, branch and repository
 
1546
    disk storage.
 
1547
    It has:
 
1548
     - Format 3 working trees [optional]
 
1549
     - Format 5 branches [optional]
 
1550
     - Format 7 repositories [optional]
 
1551
    """
 
1552
 
 
1553
    _lock_class = lockdir.LockDir
 
1554
 
 
1555
    def __init__(self):
 
1556
        self._workingtree_format = None
 
1557
        self._branch_format = None
 
1558
 
 
1559
    def __eq__(self, other):
 
1560
        if other.__class__ is not self.__class__:
 
1561
            return False
 
1562
        if other.repository_format != self.repository_format:
 
1563
            return False
 
1564
        if other.workingtree_format != self.workingtree_format:
 
1565
            return False
 
1566
        return True
 
1567
 
 
1568
    def __ne__(self, other):
 
1569
        return not self == other
 
1570
 
 
1571
    def get_branch_format(self):
 
1572
        if self._branch_format is None:
 
1573
            from bzrlib.branch import BranchFormat
 
1574
            self._branch_format = BranchFormat.get_default_format()
 
1575
        return self._branch_format
 
1576
 
 
1577
    def set_branch_format(self, format):
 
1578
        self._branch_format = format
 
1579
 
 
1580
    def get_converter(self, format=None):
 
1581
        """See BzrDirFormat.get_converter()."""
 
1582
        if format is None:
 
1583
            format = BzrDirFormat.get_default_format()
 
1584
        if not isinstance(self, format.__class__):
 
1585
            # converting away from metadir is not implemented
 
1586
            raise NotImplementedError(self.get_converter)
 
1587
        return ConvertMetaToMeta(format)
 
1588
 
 
1589
    def get_format_string(self):
 
1590
        """See BzrDirFormat.get_format_string()."""
 
1591
        return "Bazaar-NG meta directory, format 1\n"
 
1592
 
 
1593
    def get_format_description(self):
 
1594
        """See BzrDirFormat.get_format_description()."""
 
1595
        return "Meta directory format 1"
 
1596
 
 
1597
    def _open(self, transport):
 
1598
        """See BzrDirFormat._open."""
 
1599
        return BzrDirMeta1(transport, self)
 
1600
 
 
1601
    def __return_repository_format(self):
 
1602
        """Circular import protection."""
 
1603
        if getattr(self, '_repository_format', None):
 
1604
            return self._repository_format
 
1605
        from bzrlib.repository import RepositoryFormat
 
1606
        return RepositoryFormat.get_default_format()
 
1607
 
 
1608
    def __set_repository_format(self, value):
 
1609
        """Allow changint the repository format for metadir formats."""
 
1610
        self._repository_format = value
 
1611
 
 
1612
    repository_format = property(__return_repository_format, __set_repository_format)
 
1613
 
 
1614
    def __get_workingtree_format(self):
 
1615
        if self._workingtree_format is None:
 
1616
            from bzrlib.workingtree import WorkingTreeFormat
 
1617
            self._workingtree_format = WorkingTreeFormat.get_default_format()
 
1618
        return self._workingtree_format
 
1619
 
 
1620
    def __set_workingtree_format(self, wt_format):
 
1621
        self._workingtree_format = wt_format
 
1622
 
 
1623
    workingtree_format = property(__get_workingtree_format,
 
1624
                                  __set_workingtree_format)
 
1625
 
 
1626
 
 
1627
BzrDirFormat.register_format(BzrDirFormat4())
 
1628
BzrDirFormat.register_format(BzrDirFormat5())
 
1629
BzrDirFormat.register_format(BzrDirFormat6())
 
1630
__default_format = BzrDirMetaFormat1()
 
1631
BzrDirFormat.register_format(__default_format)
 
1632
BzrDirFormat._default_format = __default_format
 
1633
 
 
1634
 
 
1635
class BzrDirTestProviderAdapter(object):
 
1636
    """A tool to generate a suite testing multiple bzrdir formats at once.
 
1637
 
 
1638
    This is done by copying the test once for each transport and injecting
 
1639
    the transport_server, transport_readonly_server, and bzrdir_format
 
1640
    classes into each copy. Each copy is also given a new id() to make it
 
1641
    easy to identify.
 
1642
    """
 
1643
 
 
1644
    def __init__(self, vfs_factory, transport_server, transport_readonly_server,
 
1645
        formats):
 
1646
        """Create an object to adapt tests.
 
1647
 
 
1648
        :param vfs_server: A factory to create a Transport Server which has
 
1649
            all the VFS methods working, and is writable.
 
1650
        """
 
1651
        self._vfs_factory = vfs_factory
 
1652
        self._transport_server = transport_server
 
1653
        self._transport_readonly_server = transport_readonly_server
 
1654
        self._formats = formats
 
1655
    
 
1656
    def adapt(self, test):
 
1657
        result = unittest.TestSuite()
 
1658
        for format in self._formats:
 
1659
            new_test = deepcopy(test)
 
1660
            new_test.vfs_transport_factory = self._vfs_factory
 
1661
            new_test.transport_server = self._transport_server
 
1662
            new_test.transport_readonly_server = self._transport_readonly_server
 
1663
            new_test.bzrdir_format = format
 
1664
            def make_new_test_id():
 
1665
                new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
 
1666
                return lambda: new_id
 
1667
            new_test.id = make_new_test_id()
 
1668
            result.addTest(new_test)
 
1669
        return result
 
1670
 
 
1671
 
 
1672
class Converter(object):
 
1673
    """Converts a disk format object from one format to another."""
 
1674
 
 
1675
    def convert(self, to_convert, pb):
 
1676
        """Perform the conversion of to_convert, giving feedback via pb.
 
1677
 
 
1678
        :param to_convert: The disk object to convert.
 
1679
        :param pb: a progress bar to use for progress information.
 
1680
        """
 
1681
 
 
1682
    def step(self, message):
 
1683
        """Update the pb by a step."""
 
1684
        self.count +=1
 
1685
        self.pb.update(message, self.count, self.total)
 
1686
 
 
1687
 
 
1688
class ConvertBzrDir4To5(Converter):
 
1689
    """Converts format 4 bzr dirs to format 5."""
 
1690
 
 
1691
    def __init__(self):
 
1692
        super(ConvertBzrDir4To5, self).__init__()
 
1693
        self.converted_revs = set()
 
1694
        self.absent_revisions = set()
 
1695
        self.text_count = 0
 
1696
        self.revisions = {}
 
1697
        
 
1698
    def convert(self, to_convert, pb):
 
1699
        """See Converter.convert()."""
 
1700
        self.bzrdir = to_convert
 
1701
        self.pb = pb
 
1702
        self.pb.note('starting upgrade from format 4 to 5')
 
1703
        if isinstance(self.bzrdir.transport, LocalTransport):
 
1704
            self.bzrdir.get_workingtree_transport(None).delete('stat-cache')
 
1705
        self._convert_to_weaves()
 
1706
        return BzrDir.open(self.bzrdir.root_transport.base)
 
1707
 
 
1708
    def _convert_to_weaves(self):
 
1709
        self.pb.note('note: upgrade may be faster if all store files are ungzipped first')
 
1710
        try:
 
1711
            # TODO permissions
 
1712
            stat = self.bzrdir.transport.stat('weaves')
 
1713
            if not S_ISDIR(stat.st_mode):
 
1714
                self.bzrdir.transport.delete('weaves')
 
1715
                self.bzrdir.transport.mkdir('weaves')
 
1716
        except errors.NoSuchFile:
 
1717
            self.bzrdir.transport.mkdir('weaves')
 
1718
        # deliberately not a WeaveFile as we want to build it up slowly.
 
1719
        self.inv_weave = Weave('inventory')
 
1720
        # holds in-memory weaves for all files
 
1721
        self.text_weaves = {}
 
1722
        self.bzrdir.transport.delete('branch-format')
 
1723
        self.branch = self.bzrdir.open_branch()
 
1724
        self._convert_working_inv()
 
1725
        rev_history = self.branch.revision_history()
 
1726
        # to_read is a stack holding the revisions we still need to process;
 
1727
        # appending to it adds new highest-priority revisions
 
1728
        self.known_revisions = set(rev_history)
 
1729
        self.to_read = rev_history[-1:]
 
1730
        while self.to_read:
 
1731
            rev_id = self.to_read.pop()
 
1732
            if (rev_id not in self.revisions
 
1733
                and rev_id not in self.absent_revisions):
 
1734
                self._load_one_rev(rev_id)
 
1735
        self.pb.clear()
 
1736
        to_import = self._make_order()
 
1737
        for i, rev_id in enumerate(to_import):
 
1738
            self.pb.update('converting revision', i, len(to_import))
 
1739
            self._convert_one_rev(rev_id)
 
1740
        self.pb.clear()
 
1741
        self._write_all_weaves()
 
1742
        self._write_all_revs()
 
1743
        self.pb.note('upgraded to weaves:')
 
1744
        self.pb.note('  %6d revisions and inventories', len(self.revisions))
 
1745
        self.pb.note('  %6d revisions not present', len(self.absent_revisions))
 
1746
        self.pb.note('  %6d texts', self.text_count)
 
1747
        self._cleanup_spare_files_after_format4()
 
1748
        self.branch.control_files.put_utf8('branch-format', BzrDirFormat5().get_format_string())
 
1749
 
 
1750
    def _cleanup_spare_files_after_format4(self):
 
1751
        # FIXME working tree upgrade foo.
 
1752
        for n in 'merged-patches', 'pending-merged-patches':
 
1753
            try:
 
1754
                ## assert os.path.getsize(p) == 0
 
1755
                self.bzrdir.transport.delete(n)
 
1756
            except errors.NoSuchFile:
 
1757
                pass
 
1758
        self.bzrdir.transport.delete_tree('inventory-store')
 
1759
        self.bzrdir.transport.delete_tree('text-store')
 
1760
 
 
1761
    def _convert_working_inv(self):
 
1762
        inv = xml4.serializer_v4.read_inventory(
 
1763
                    self.branch.control_files.get('inventory'))
 
1764
        new_inv_xml = xml5.serializer_v5.write_inventory_to_string(inv)
 
1765
        # FIXME inventory is a working tree change.
 
1766
        self.branch.control_files.put('inventory', StringIO(new_inv_xml))
 
1767
 
 
1768
    def _write_all_weaves(self):
 
1769
        controlweaves = WeaveStore(self.bzrdir.transport, prefixed=False)
 
1770
        weave_transport = self.bzrdir.transport.clone('weaves')
 
1771
        weaves = WeaveStore(weave_transport, prefixed=False)
 
1772
        transaction = WriteTransaction()
 
1773
 
 
1774
        try:
 
1775
            i = 0
 
1776
            for file_id, file_weave in self.text_weaves.items():
 
1777
                self.pb.update('writing weave', i, len(self.text_weaves))
 
1778
                weaves._put_weave(file_id, file_weave, transaction)
 
1779
                i += 1
 
1780
            self.pb.update('inventory', 0, 1)
 
1781
            controlweaves._put_weave('inventory', self.inv_weave, transaction)
 
1782
            self.pb.update('inventory', 1, 1)
 
1783
        finally:
 
1784
            self.pb.clear()
 
1785
 
 
1786
    def _write_all_revs(self):
 
1787
        """Write all revisions out in new form."""
 
1788
        self.bzrdir.transport.delete_tree('revision-store')
 
1789
        self.bzrdir.transport.mkdir('revision-store')
 
1790
        revision_transport = self.bzrdir.transport.clone('revision-store')
 
1791
        # TODO permissions
 
1792
        _revision_store = TextRevisionStore(TextStore(revision_transport,
 
1793
                                                      prefixed=False,
 
1794
                                                      compressed=True))
 
1795
        try:
 
1796
            transaction = WriteTransaction()
 
1797
            for i, rev_id in enumerate(self.converted_revs):
 
1798
                self.pb.update('write revision', i, len(self.converted_revs))
 
1799
                _revision_store.add_revision(self.revisions[rev_id], transaction)
 
1800
        finally:
 
1801
            self.pb.clear()
 
1802
            
 
1803
    def _load_one_rev(self, rev_id):
 
1804
        """Load a revision object into memory.
 
1805
 
 
1806
        Any parents not either loaded or abandoned get queued to be
 
1807
        loaded."""
 
1808
        self.pb.update('loading revision',
 
1809
                       len(self.revisions),
 
1810
                       len(self.known_revisions))
 
1811
        if not self.branch.repository.has_revision(rev_id):
 
1812
            self.pb.clear()
 
1813
            self.pb.note('revision {%s} not present in branch; '
 
1814
                         'will be converted as a ghost',
 
1815
                         rev_id)
 
1816
            self.absent_revisions.add(rev_id)
 
1817
        else:
 
1818
            rev = self.branch.repository._revision_store.get_revision(rev_id,
 
1819
                self.branch.repository.get_transaction())
 
1820
            for parent_id in rev.parent_ids:
 
1821
                self.known_revisions.add(parent_id)
 
1822
                self.to_read.append(parent_id)
 
1823
            self.revisions[rev_id] = rev
 
1824
 
 
1825
    def _load_old_inventory(self, rev_id):
 
1826
        assert rev_id not in self.converted_revs
 
1827
        old_inv_xml = self.branch.repository.inventory_store.get(rev_id).read()
 
1828
        inv = xml4.serializer_v4.read_inventory_from_string(old_inv_xml)
 
1829
        inv.revision_id = rev_id
 
1830
        rev = self.revisions[rev_id]
 
1831
        if rev.inventory_sha1:
 
1832
            assert rev.inventory_sha1 == sha_string(old_inv_xml), \
 
1833
                'inventory sha mismatch for {%s}' % rev_id
 
1834
        return inv
 
1835
 
 
1836
    def _load_updated_inventory(self, rev_id):
 
1837
        assert rev_id in self.converted_revs
 
1838
        inv_xml = self.inv_weave.get_text(rev_id)
 
1839
        inv = xml5.serializer_v5.read_inventory_from_string(inv_xml)
 
1840
        return inv
 
1841
 
 
1842
    def _convert_one_rev(self, rev_id):
 
1843
        """Convert revision and all referenced objects to new format."""
 
1844
        rev = self.revisions[rev_id]
 
1845
        inv = self._load_old_inventory(rev_id)
 
1846
        present_parents = [p for p in rev.parent_ids
 
1847
                           if p not in self.absent_revisions]
 
1848
        self._convert_revision_contents(rev, inv, present_parents)
 
1849
        self._store_new_weave(rev, inv, present_parents)
 
1850
        self.converted_revs.add(rev_id)
 
1851
 
 
1852
    def _store_new_weave(self, rev, inv, present_parents):
 
1853
        # the XML is now updated with text versions
 
1854
        if __debug__:
 
1855
            entries = inv.iter_entries()
 
1856
            entries.next()
 
1857
            for path, ie in entries:
 
1858
                assert getattr(ie, 'revision', None) is not None, \
 
1859
                    'no revision on {%s} in {%s}' % \
 
1860
                    (file_id, rev.revision_id)
 
1861
        new_inv_xml = xml5.serializer_v5.write_inventory_to_string(inv)
 
1862
        new_inv_sha1 = sha_string(new_inv_xml)
 
1863
        self.inv_weave.add_lines(rev.revision_id, 
 
1864
                                 present_parents,
 
1865
                                 new_inv_xml.splitlines(True))
 
1866
        rev.inventory_sha1 = new_inv_sha1
 
1867
 
 
1868
    def _convert_revision_contents(self, rev, inv, present_parents):
 
1869
        """Convert all the files within a revision.
 
1870
 
 
1871
        Also upgrade the inventory to refer to the text revision ids."""
 
1872
        rev_id = rev.revision_id
 
1873
        mutter('converting texts of revision {%s}',
 
1874
               rev_id)
 
1875
        parent_invs = map(self._load_updated_inventory, present_parents)
 
1876
        entries = inv.iter_entries()
 
1877
        entries.next()
 
1878
        for path, ie in entries:
 
1879
            self._convert_file_version(rev, ie, parent_invs)
 
1880
 
 
1881
    def _convert_file_version(self, rev, ie, parent_invs):
 
1882
        """Convert one version of one file.
 
1883
 
 
1884
        The file needs to be added into the weave if it is a merge
 
1885
        of >=2 parents or if it's changed from its parent.
 
1886
        """
 
1887
        file_id = ie.file_id
 
1888
        rev_id = rev.revision_id
 
1889
        w = self.text_weaves.get(file_id)
 
1890
        if w is None:
 
1891
            w = Weave(file_id)
 
1892
            self.text_weaves[file_id] = w
 
1893
        text_changed = False
 
1894
        previous_entries = ie.find_previous_heads(parent_invs,
 
1895
                                                  None,
 
1896
                                                  None,
 
1897
                                                  entry_vf=w)
 
1898
        for old_revision in previous_entries:
 
1899
                # if this fails, its a ghost ?
 
1900
                assert old_revision in self.converted_revs, \
 
1901
                    "Revision {%s} not in converted_revs" % old_revision
 
1902
        self.snapshot_ie(previous_entries, ie, w, rev_id)
 
1903
        del ie.text_id
 
1904
        assert getattr(ie, 'revision', None) is not None
 
1905
 
 
1906
    def snapshot_ie(self, previous_revisions, ie, w, rev_id):
 
1907
        # TODO: convert this logic, which is ~= snapshot to
 
1908
        # a call to:. This needs the path figured out. rather than a work_tree
 
1909
        # a v4 revision_tree can be given, or something that looks enough like
 
1910
        # one to give the file content to the entry if it needs it.
 
1911
        # and we need something that looks like a weave store for snapshot to 
 
1912
        # save against.
 
1913
        #ie.snapshot(rev, PATH, previous_revisions, REVISION_TREE, InMemoryWeaveStore(self.text_weaves))
 
1914
        if len(previous_revisions) == 1:
 
1915
            previous_ie = previous_revisions.values()[0]
 
1916
            if ie._unchanged(previous_ie):
 
1917
                ie.revision = previous_ie.revision
 
1918
                return
 
1919
        if ie.has_text():
 
1920
            text = self.branch.repository.text_store.get(ie.text_id)
 
1921
            file_lines = text.readlines()
 
1922
            assert sha_strings(file_lines) == ie.text_sha1
 
1923
            assert sum(map(len, file_lines)) == ie.text_size
 
1924
            w.add_lines(rev_id, previous_revisions, file_lines)
 
1925
            self.text_count += 1
 
1926
        else:
 
1927
            w.add_lines(rev_id, previous_revisions, [])
 
1928
        ie.revision = rev_id
 
1929
 
 
1930
    def _make_order(self):
 
1931
        """Return a suitable order for importing revisions.
 
1932
 
 
1933
        The order must be such that an revision is imported after all
 
1934
        its (present) parents.
 
1935
        """
 
1936
        todo = set(self.revisions.keys())
 
1937
        done = self.absent_revisions.copy()
 
1938
        order = []
 
1939
        while todo:
 
1940
            # scan through looking for a revision whose parents
 
1941
            # are all done
 
1942
            for rev_id in sorted(list(todo)):
 
1943
                rev = self.revisions[rev_id]
 
1944
                parent_ids = set(rev.parent_ids)
 
1945
                if parent_ids.issubset(done):
 
1946
                    # can take this one now
 
1947
                    order.append(rev_id)
 
1948
                    todo.remove(rev_id)
 
1949
                    done.add(rev_id)
 
1950
        return order
 
1951
 
 
1952
 
 
1953
class ConvertBzrDir5To6(Converter):
 
1954
    """Converts format 5 bzr dirs to format 6."""
 
1955
 
 
1956
    def convert(self, to_convert, pb):
 
1957
        """See Converter.convert()."""
 
1958
        self.bzrdir = to_convert
 
1959
        self.pb = pb
 
1960
        self.pb.note('starting upgrade from format 5 to 6')
 
1961
        self._convert_to_prefixed()
 
1962
        return BzrDir.open(self.bzrdir.root_transport.base)
 
1963
 
 
1964
    def _convert_to_prefixed(self):
 
1965
        from bzrlib.store import TransportStore
 
1966
        self.bzrdir.transport.delete('branch-format')
 
1967
        for store_name in ["weaves", "revision-store"]:
 
1968
            self.pb.note("adding prefixes to %s" % store_name)
 
1969
            store_transport = self.bzrdir.transport.clone(store_name)
 
1970
            store = TransportStore(store_transport, prefixed=True)
 
1971
            for urlfilename in store_transport.list_dir('.'):
 
1972
                filename = urlutils.unescape(urlfilename)
 
1973
                if (filename.endswith(".weave") or
 
1974
                    filename.endswith(".gz") or
 
1975
                    filename.endswith(".sig")):
 
1976
                    file_id = os.path.splitext(filename)[0]
 
1977
                else:
 
1978
                    file_id = filename
 
1979
                prefix_dir = store.hash_prefix(file_id)
 
1980
                # FIXME keep track of the dirs made RBC 20060121
 
1981
                try:
 
1982
                    store_transport.move(filename, prefix_dir + '/' + filename)
 
1983
                except errors.NoSuchFile: # catches missing dirs strangely enough
 
1984
                    store_transport.mkdir(prefix_dir)
 
1985
                    store_transport.move(filename, prefix_dir + '/' + filename)
 
1986
        self.bzrdir._control_files.put_utf8('branch-format', BzrDirFormat6().get_format_string())
 
1987
 
 
1988
 
 
1989
class ConvertBzrDir6ToMeta(Converter):
 
1990
    """Converts format 6 bzr dirs to metadirs."""
 
1991
 
 
1992
    def convert(self, to_convert, pb):
 
1993
        """See Converter.convert()."""
 
1994
        from bzrlib.repofmt.weaverepo import RepositoryFormat7
 
1995
        from bzrlib.branch import BzrBranchFormat5
 
1996
        self.bzrdir = to_convert
 
1997
        self.pb = pb
 
1998
        self.count = 0
 
1999
        self.total = 20 # the steps we know about
 
2000
        self.garbage_inventories = []
 
2001
 
 
2002
        self.pb.note('starting upgrade from format 6 to metadir')
 
2003
        self.bzrdir._control_files.put_utf8('branch-format', "Converting to format 6")
 
2004
        # its faster to move specific files around than to open and use the apis...
 
2005
        # first off, nuke ancestry.weave, it was never used.
 
2006
        try:
 
2007
            self.step('Removing ancestry.weave')
 
2008
            self.bzrdir.transport.delete('ancestry.weave')
 
2009
        except errors.NoSuchFile:
 
2010
            pass
 
2011
        # find out whats there
 
2012
        self.step('Finding branch files')
 
2013
        last_revision = self.bzrdir.open_branch().last_revision()
 
2014
        bzrcontents = self.bzrdir.transport.list_dir('.')
 
2015
        for name in bzrcontents:
 
2016
            if name.startswith('basis-inventory.'):
 
2017
                self.garbage_inventories.append(name)
 
2018
        # create new directories for repository, working tree and branch
 
2019
        self.dir_mode = self.bzrdir._control_files._dir_mode
 
2020
        self.file_mode = self.bzrdir._control_files._file_mode
 
2021
        repository_names = [('inventory.weave', True),
 
2022
                            ('revision-store', True),
 
2023
                            ('weaves', True)]
 
2024
        self.step('Upgrading repository  ')
 
2025
        self.bzrdir.transport.mkdir('repository', mode=self.dir_mode)
 
2026
        self.make_lock('repository')
 
2027
        # we hard code the formats here because we are converting into
 
2028
        # the meta format. The meta format upgrader can take this to a 
 
2029
        # future format within each component.
 
2030
        self.put_format('repository', RepositoryFormat7())
 
2031
        for entry in repository_names:
 
2032
            self.move_entry('repository', entry)
 
2033
 
 
2034
        self.step('Upgrading branch      ')
 
2035
        self.bzrdir.transport.mkdir('branch', mode=self.dir_mode)
 
2036
        self.make_lock('branch')
 
2037
        self.put_format('branch', BzrBranchFormat5())
 
2038
        branch_files = [('revision-history', True),
 
2039
                        ('branch-name', True),
 
2040
                        ('parent', False)]
 
2041
        for entry in branch_files:
 
2042
            self.move_entry('branch', entry)
 
2043
 
 
2044
        checkout_files = [('pending-merges', True),
 
2045
                          ('inventory', True),
 
2046
                          ('stat-cache', False)]
 
2047
        # If a mandatory checkout file is not present, the branch does not have
 
2048
        # a functional checkout. Do not create a checkout in the converted
 
2049
        # branch.
 
2050
        for name, mandatory in checkout_files:
 
2051
            if mandatory and name not in bzrcontents:
 
2052
                has_checkout = False
 
2053
                break
 
2054
        else:
 
2055
            has_checkout = True
 
2056
        if not has_checkout:
 
2057
            self.pb.note('No working tree.')
 
2058
            # If some checkout files are there, we may as well get rid of them.
 
2059
            for name, mandatory in checkout_files:
 
2060
                if name in bzrcontents:
 
2061
                    self.bzrdir.transport.delete(name)
 
2062
        else:
 
2063
            from bzrlib.workingtree import WorkingTreeFormat3
 
2064
            self.step('Upgrading working tree')
 
2065
            self.bzrdir.transport.mkdir('checkout', mode=self.dir_mode)
 
2066
            self.make_lock('checkout')
 
2067
            self.put_format(
 
2068
                'checkout', WorkingTreeFormat3())
 
2069
            self.bzrdir.transport.delete_multi(
 
2070
                self.garbage_inventories, self.pb)
 
2071
            for entry in checkout_files:
 
2072
                self.move_entry('checkout', entry)
 
2073
            if last_revision is not None:
 
2074
                self.bzrdir._control_files.put_utf8(
 
2075
                    'checkout/last-revision', last_revision)
 
2076
        self.bzrdir._control_files.put_utf8(
 
2077
            'branch-format', BzrDirMetaFormat1().get_format_string())
 
2078
        return BzrDir.open(self.bzrdir.root_transport.base)
 
2079
 
 
2080
    def make_lock(self, name):
 
2081
        """Make a lock for the new control dir name."""
 
2082
        self.step('Make %s lock' % name)
 
2083
        ld = lockdir.LockDir(self.bzrdir.transport,
 
2084
                             '%s/lock' % name,
 
2085
                             file_modebits=self.file_mode,
 
2086
                             dir_modebits=self.dir_mode)
 
2087
        ld.create()
 
2088
 
 
2089
    def move_entry(self, new_dir, entry):
 
2090
        """Move then entry name into new_dir."""
 
2091
        name = entry[0]
 
2092
        mandatory = entry[1]
 
2093
        self.step('Moving %s' % name)
 
2094
        try:
 
2095
            self.bzrdir.transport.move(name, '%s/%s' % (new_dir, name))
 
2096
        except errors.NoSuchFile:
 
2097
            if mandatory:
 
2098
                raise
 
2099
 
 
2100
    def put_format(self, dirname, format):
 
2101
        self.bzrdir._control_files.put_utf8('%s/format' % dirname, format.get_format_string())
 
2102
 
 
2103
 
 
2104
class ConvertMetaToMeta(Converter):
 
2105
    """Converts the components of metadirs."""
 
2106
 
 
2107
    def __init__(self, target_format):
 
2108
        """Create a metadir to metadir converter.
 
2109
 
 
2110
        :param target_format: The final metadir format that is desired.
 
2111
        """
 
2112
        self.target_format = target_format
 
2113
 
 
2114
    def convert(self, to_convert, pb):
 
2115
        """See Converter.convert()."""
 
2116
        self.bzrdir = to_convert
 
2117
        self.pb = pb
 
2118
        self.count = 0
 
2119
        self.total = 1
 
2120
        self.step('checking repository format')
 
2121
        try:
 
2122
            repo = self.bzrdir.open_repository()
 
2123
        except errors.NoRepositoryPresent:
 
2124
            pass
 
2125
        else:
 
2126
            if not isinstance(repo._format, self.target_format.repository_format.__class__):
 
2127
                from bzrlib.repository import CopyConverter
 
2128
                self.pb.note('starting repository conversion')
 
2129
                converter = CopyConverter(self.target_format.repository_format)
 
2130
                converter.convert(repo, pb)
 
2131
        try:
 
2132
            branch = self.bzrdir.open_branch()
 
2133
        except errors.NotBranchError:
 
2134
            pass
 
2135
        else:
 
2136
            # TODO: conversions of Branch and Tree should be done by
 
2137
            # InterXFormat lookups
 
2138
            # Avoid circular imports
 
2139
            from bzrlib import branch as _mod_branch
 
2140
            if (branch._format.__class__ is _mod_branch.BzrBranchFormat5 and
 
2141
                self.target_format.get_branch_format().__class__ is
 
2142
                _mod_branch.BzrBranchFormat6):
 
2143
                branch_converter = _mod_branch.Converter5to6()
 
2144
                branch_converter.convert(branch)
 
2145
        try:
 
2146
            tree = self.bzrdir.open_workingtree()
 
2147
        except (errors.NoWorkingTree, errors.NotLocalUrl):
 
2148
            pass
 
2149
        else:
 
2150
            # TODO: conversions of Branch and Tree should be done by
 
2151
            # InterXFormat lookups
 
2152
            if (isinstance(tree, workingtree.WorkingTree3) and
 
2153
                not isinstance(tree, workingtree_4.WorkingTree4) and
 
2154
                isinstance(self.target_format.workingtree_format,
 
2155
                    workingtree_4.WorkingTreeFormat4)):
 
2156
                workingtree_4.Converter3to4().convert(tree)
 
2157
        return to_convert
 
2158
 
 
2159
 
 
2160
# This is not in remote.py because it's small, and needs to be registered.
 
2161
# Putting it in remote.py creates a circular import problem.
 
2162
# we can make it a lazy object if the control formats is turned into something
 
2163
# like a registry.
 
2164
class RemoteBzrDirFormat(BzrDirMetaFormat1):
 
2165
    """Format representing bzrdirs accessed via a smart server"""
 
2166
 
 
2167
    def get_format_description(self):
 
2168
        return 'bzr remote bzrdir'
 
2169
    
 
2170
    @classmethod
 
2171
    def probe_transport(klass, transport):
 
2172
        """Return a RemoteBzrDirFormat object if it looks possible."""
 
2173
        try:
 
2174
            transport.get_smart_client()
 
2175
        except (NotImplementedError, AttributeError,
 
2176
                errors.TransportNotPossible):
 
2177
            # no smart server, so not a branch for this format type.
 
2178
            raise errors.NotBranchError(path=transport.base)
 
2179
        else:
 
2180
            return klass()
 
2181
 
 
2182
    def initialize_on_transport(self, transport):
 
2183
        try:
 
2184
            # hand off the request to the smart server
 
2185
            medium = transport.get_smart_medium()
 
2186
        except errors.NoSmartMedium:
 
2187
            # TODO: lookup the local format from a server hint.
 
2188
            local_dir_format = BzrDirMetaFormat1()
 
2189
            return local_dir_format.initialize_on_transport(transport)
 
2190
        client = SmartClient(medium)
 
2191
        path = client.remote_path_from_transport(transport)
 
2192
        response = SmartClient(medium).call('BzrDirFormat.initialize', path)
 
2193
        assert response[0] in ('ok', ), 'unexpected response code %s' % (response,)
 
2194
        return remote.RemoteBzrDir(transport)
 
2195
 
 
2196
    def _open(self, transport):
 
2197
        return remote.RemoteBzrDir(transport)
 
2198
 
 
2199
    def __eq__(self, other):
 
2200
        if not isinstance(other, RemoteBzrDirFormat):
 
2201
            return False
 
2202
        return self.get_format_description() == other.get_format_description()
 
2203
 
 
2204
 
 
2205
# We can't use register_control_format because it adds it at a lower priority
 
2206
# than the existing branches, whereas this should take priority.
 
2207
BzrDirFormat._control_formats.insert(0, RemoteBzrDirFormat)
 
2208
 
 
2209
 
 
2210
class BzrDirFormatInfo(object):
 
2211
 
 
2212
    def __init__(self, native, deprecated, hidden):
 
2213
        self.deprecated = deprecated
 
2214
        self.native = native
 
2215
        self.hidden = hidden
 
2216
 
 
2217
 
 
2218
class BzrDirFormatRegistry(registry.Registry):
 
2219
    """Registry of user-selectable BzrDir subformats.
 
2220
    
 
2221
    Differs from BzrDirFormat._control_formats in that it provides sub-formats,
 
2222
    e.g. BzrDirMeta1 with weave repository.  Also, it's more user-oriented.
 
2223
    """
 
2224
 
 
2225
    def register_metadir(self, key,
 
2226
             repository_format, help, native=True, deprecated=False,
 
2227
             branch_format=None,
 
2228
             tree_format=None,
 
2229
             hidden=False):
 
2230
        """Register a metadir subformat.
 
2231
 
 
2232
        These all use a BzrDirMetaFormat1 bzrdir, but can be parameterized
 
2233
        by the Repository format.
 
2234
 
 
2235
        :param repository_format: The fully-qualified repository format class
 
2236
            name as a string.
 
2237
        :param branch_format: Fully-qualified branch format class name as
 
2238
            a string.
 
2239
        :param tree_format: Fully-qualified tree format class name as
 
2240
            a string.
 
2241
        """
 
2242
        # This should be expanded to support setting WorkingTree and Branch
 
2243
        # formats, once BzrDirMetaFormat1 supports that.
 
2244
        def _load(full_name):
 
2245
            mod_name, factory_name = full_name.rsplit('.', 1)
 
2246
            try:
 
2247
                mod = __import__(mod_name, globals(), locals(),
 
2248
                        [factory_name])
 
2249
            except ImportError, e:
 
2250
                raise ImportError('failed to load %s: %s' % (full_name, e))
 
2251
            try:
 
2252
                factory = getattr(mod, factory_name)
 
2253
            except AttributeError:
 
2254
                raise AttributeError('no factory %s in module %r'
 
2255
                    % (full_name, mod))
 
2256
            return factory()
 
2257
 
 
2258
        def helper():
 
2259
            bd = BzrDirMetaFormat1()
 
2260
            if branch_format is not None:
 
2261
                bd.set_branch_format(_load(branch_format))
 
2262
            if tree_format is not None:
 
2263
                bd.workingtree_format = _load(tree_format)
 
2264
            if repository_format is not None:
 
2265
                bd.repository_format = _load(repository_format)
 
2266
            return bd
 
2267
        self.register(key, helper, help, native, deprecated, hidden)
 
2268
 
 
2269
    def register(self, key, factory, help, native=True, deprecated=False,
 
2270
                 hidden=False):
 
2271
        """Register a BzrDirFormat factory.
 
2272
        
 
2273
        The factory must be a callable that takes one parameter: the key.
 
2274
        It must produce an instance of the BzrDirFormat when called.
 
2275
 
 
2276
        This function mainly exists to prevent the info object from being
 
2277
        supplied directly.
 
2278
        """
 
2279
        registry.Registry.register(self, key, factory, help, 
 
2280
            BzrDirFormatInfo(native, deprecated, hidden))
 
2281
 
 
2282
    def register_lazy(self, key, module_name, member_name, help, native=True,
 
2283
                      deprecated=False, hidden=False):
 
2284
        registry.Registry.register_lazy(self, key, module_name, member_name, 
 
2285
            help, BzrDirFormatInfo(native, deprecated, hidden))
 
2286
 
 
2287
    def set_default(self, key):
 
2288
        """Set the 'default' key to be a clone of the supplied key.
 
2289
        
 
2290
        This method must be called once and only once.
 
2291
        """
 
2292
        registry.Registry.register(self, 'default', self.get(key), 
 
2293
            self.get_help(key), info=self.get_info(key))
 
2294
 
 
2295
    def set_default_repository(self, key):
 
2296
        """Set the FormatRegistry default and Repository default.
 
2297
        
 
2298
        This is a transitional method while Repository.set_default_format
 
2299
        is deprecated.
 
2300
        """
 
2301
        if 'default' in self:
 
2302
            self.remove('default')
 
2303
        self.set_default(key)
 
2304
        format = self.get('default')()
 
2305
        assert isinstance(format, BzrDirMetaFormat1)
 
2306
 
 
2307
    def make_bzrdir(self, key):
 
2308
        return self.get(key)()
 
2309
 
 
2310
    def help_topic(self, topic):
 
2311
        output = textwrap.dedent("""\
 
2312
            Bazaar directory formats
 
2313
            ------------------------
 
2314
 
 
2315
            These formats can be used for creating branches, working trees, and
 
2316
            repositories.
 
2317
 
 
2318
            """)
 
2319
        default_help = self.get_help('default')
 
2320
        help_pairs = []
 
2321
        for key in self.keys():
 
2322
            if key == 'default':
 
2323
                continue
 
2324
            help = self.get_help(key)
 
2325
            if help == default_help:
 
2326
                default_realkey = key
 
2327
            else:
 
2328
                help_pairs.append((key, help))
 
2329
 
 
2330
        def wrapped(key, help, info):
 
2331
            if info.native:
 
2332
                help = '(native) ' + help
 
2333
            return '  %s:\n%s\n\n' % (key, 
 
2334
                    textwrap.fill(help, initial_indent='    ', 
 
2335
                    subsequent_indent='    '))
 
2336
        output += wrapped('%s/default' % default_realkey, default_help,
 
2337
                          self.get_info('default'))
 
2338
        deprecated_pairs = []
 
2339
        for key, help in help_pairs:
 
2340
            info = self.get_info(key)
 
2341
            if info.hidden:
 
2342
                continue
 
2343
            elif info.deprecated:
 
2344
                deprecated_pairs.append((key, help))
 
2345
            else:
 
2346
                output += wrapped(key, help, info)
 
2347
        if len(deprecated_pairs) > 0:
 
2348
            output += "Deprecated formats\n------------------\n\n"
 
2349
            for key, help in deprecated_pairs:
 
2350
                info = self.get_info(key)
 
2351
                output += wrapped(key, help, info)
 
2352
 
 
2353
        return output
 
2354
 
 
2355
 
 
2356
format_registry = BzrDirFormatRegistry()
 
2357
format_registry.register('weave', BzrDirFormat6,
 
2358
    'Pre-0.8 format.  Slower than knit and does not'
 
2359
    ' support checkouts or shared repositories.',
 
2360
    deprecated=True)
 
2361
format_registry.register_metadir('knit',
 
2362
    'bzrlib.repofmt.knitrepo.RepositoryFormatKnit1',
 
2363
    'Format using knits.  Recommended for interoperation with bzr <= 0.14.',
 
2364
    branch_format='bzrlib.branch.BzrBranchFormat5',
 
2365
    tree_format='bzrlib.workingtree.WorkingTreeFormat3')
 
2366
format_registry.register_metadir('metaweave',
 
2367
    'bzrlib.repofmt.weaverepo.RepositoryFormat7',
 
2368
    'Transitional format in 0.8.  Slower than knit.',
 
2369
    branch_format='bzrlib.branch.BzrBranchFormat5',
 
2370
    tree_format='bzrlib.workingtree.WorkingTreeFormat3',
 
2371
    deprecated=True)
 
2372
format_registry.register_metadir('dirstate',
 
2373
    'bzrlib.repofmt.knitrepo.RepositoryFormatKnit1',
 
2374
    help='New in 0.15: Fast local operations. Compatible with bzr 0.8 and '
 
2375
        'above when accessed over the network.',
 
2376
    branch_format='bzrlib.branch.BzrBranchFormat5',
 
2377
    # this uses bzrlib.workingtree.WorkingTreeFormat4 because importing
 
2378
    # directly from workingtree_4 triggers a circular import.
 
2379
    tree_format='bzrlib.workingtree.WorkingTreeFormat4',
 
2380
    )
 
2381
format_registry.register_metadir('dirstate-tags',
 
2382
    'bzrlib.repofmt.knitrepo.RepositoryFormatKnit1',
 
2383
    help='New in 0.15: Fast local operations and improved scaling for '
 
2384
        'network operations. Additionally adds support for tags.'
 
2385
        ' Incompatible with bzr < 0.15.',
 
2386
    branch_format='bzrlib.branch.BzrBranchFormat6',
 
2387
    tree_format='bzrlib.workingtree.WorkingTreeFormat4',
 
2388
    )
 
2389
format_registry.register_metadir('dirstate-with-subtree',
 
2390
    'bzrlib.repofmt.knitrepo.RepositoryFormatKnit3',
 
2391
    help='New in 0.15: Fast local operations and improved scaling for '
 
2392
        'network operations. Additionally adds support for versioning nested '
 
2393
        'bzr branches. Incompatible with bzr < 0.15.',
 
2394
    branch_format='bzrlib.branch.BzrBranchFormat6',
 
2395
    tree_format='bzrlib.workingtree.WorkingTreeFormat4',
 
2396
    hidden=True,
 
2397
    )
 
2398
format_registry.set_default('dirstate')