/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/bzrdir.py

  • Committer: Robert Collins
  • Date: 2007-04-20 07:38:20 UTC
  • mto: This revision was merged to the branch mainline in revision 2434.
  • Revision ID: robertc@robertcollins.net-20070420073820-iulf74ans1v2fn8e
(robertc) Typo in the help for ``register-branch`` fixed. (Robert Collins, #96770)

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