/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

Record merge against split out urlutils work.

Show diffs side-by-side

added added

removed removed

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