/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/bzrdir.py

  • Committer: Vincent Ladeuil
  • Date: 2007-05-04 10:26:43 UTC
  • mto: (2485.8.14 reuse.transports)
  • mto: This revision was merged to the branch mainline in revision 2646.
  • Revision ID: v.ladeuil+lp@free.fr-20070504102643-3qbwrvmvzjildjez
Add a test for create_branch_convenience. Mark some places to test for multiple connections.

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