/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: Aaron Bentley
  • Date: 2007-12-19 04:04:56 UTC
  • mto: This revision was merged to the branch mainline in revision 3135.
  • Revision ID: aaron.bentley@utoronto.ca-20071219040456-0bh3fruord0m08gz
Update NEWS

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