/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: Alexander Belchenko
  • Date: 2007-11-26 08:08:22 UTC
  • mto: This revision was merged to the branch mainline in revision 3032.
  • Revision ID: bialix@ukr.net-20071126080822-iel61wjqbkju9s0v
Mark .bzr directories as "hidden" on Windows (#71147)

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