~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bzrdir.py

  • Committer: Martin Pool
  • Date: 2006-06-15 05:36:34 UTC
  • mto: This revision was merged to the branch mainline in revision 1797.
  • Revision ID: mbp@sourcefrog.net-20060615053634-4fd52ba691855659
Clean up many exception classes.

Errors indicating a user error are now shown with is_user_error on the
exception; use this rather than hardcoding a list of exceptions that should be
handled this way.

Exceptions now inherit from BzrNewException where possible to use consistent
formatting method.

Remove rather obsolete docstring test on Branch.missing_revisions.

Remove dead code from find_merge_base.


Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
 
2
 
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
 
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
 
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""BzrDir logic. The BzrDir is the basic control directory used by bzr.
 
18
 
 
19
At format 7 this was split out into Branch, Repository and Checkout control
 
20
directories.
 
21
"""
 
22
 
 
23
from copy import deepcopy
 
24
import os
 
25
from cStringIO import StringIO
 
26
from unittest import TestSuite
 
27
 
 
28
import bzrlib
 
29
import bzrlib.errors as errors
 
30
from bzrlib.lockable_files import LockableFiles, TransportLock
 
31
from bzrlib.lockdir import LockDir
 
32
from bzrlib.osutils import safe_unicode
 
33
from bzrlib.osutils import (
 
34
                            abspath,
 
35
                            pathjoin,
 
36
                            safe_unicode,
 
37
                            sha_strings,
 
38
                            sha_string,
 
39
                            )
 
40
from bzrlib.store.revision.text import TextRevisionStore
 
41
from bzrlib.store.text import TextStore
 
42
from bzrlib.store.versioned import WeaveStore
 
43
from bzrlib.symbol_versioning import *
 
44
from bzrlib.trace import mutter
 
45
from bzrlib.transactions import WriteTransaction
 
46
from bzrlib.transport import get_transport, urlunescape
 
47
from bzrlib.transport.local import LocalTransport
 
48
from bzrlib.weave import Weave
 
49
from bzrlib.xml4 import serializer_v4
 
50
import bzrlib.xml5
 
51
 
 
52
 
 
53
class BzrDir(object):
 
54
    """A .bzr control diretory.
 
55
    
 
56
    BzrDir instances let you create or open any of the things that can be
 
57
    found within .bzr - checkouts, branches and repositories.
 
58
    
 
59
    transport
 
60
        the transport which this bzr dir is rooted at (i.e. file:///.../.bzr/)
 
61
    root_transport
 
62
        a transport connected to the directory this bzr was opened from.
 
63
    """
 
64
 
 
65
    def break_lock(self):
 
66
        """Invoke break_lock on the first object in the bzrdir.
 
67
 
 
68
        If there is a tree, the tree is opened and break_lock() called.
 
69
        Otherwise, branch is tried, and finally repository.
 
70
        """
 
71
        try:
 
72
            thing_to_unlock = self.open_workingtree()
 
73
        except (errors.NotLocalUrl, errors.NoWorkingTree):
 
74
            try:
 
75
                thing_to_unlock = self.open_branch()
 
76
            except errors.NotBranchError:
 
77
                try:
 
78
                    thing_to_unlock = self.open_repository()
 
79
                except errors.NoRepositoryPresent:
 
80
                    return
 
81
        thing_to_unlock.break_lock()
 
82
 
 
83
    def can_convert_format(self):
 
84
        """Return true if this bzrdir is one whose format we can convert from."""
 
85
        return True
 
86
 
 
87
    @staticmethod
 
88
    def _check_supported(format, allow_unsupported):
 
89
        """Check whether format is a supported format.
 
90
 
 
91
        If allow_unsupported is True, this is a no-op.
 
92
        """
 
93
        if not allow_unsupported and not format.is_supported():
 
94
            # see open_downlevel to open legacy branches.
 
95
            raise errors.UnsupportedFormatError(format=format)
 
96
 
 
97
    def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
 
98
        """Clone this bzrdir and its contents to url verbatim.
 
99
 
 
100
        If urls last component does not exist, it will be created.
 
101
 
 
102
        if revision_id is not None, then the clone operation may tune
 
103
            itself to download less data.
 
104
        :param force_new_repo: Do not use a shared repository for the target 
 
105
                               even if one is available.
 
106
        """
 
107
        self._make_tail(url)
 
108
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
109
        result = self._format.initialize(url)
 
110
        try:
 
111
            local_repo = self.find_repository()
 
112
        except errors.NoRepositoryPresent:
 
113
            local_repo = None
 
114
        if local_repo:
 
115
            # may need to copy content in
 
116
            if force_new_repo:
 
117
                result_repo = local_repo.clone(
 
118
                    result,
 
119
                    revision_id=revision_id,
 
120
                    basis=basis_repo)
 
121
                result_repo.set_make_working_trees(local_repo.make_working_trees())
 
122
            else:
 
123
                try:
 
124
                    result_repo = result.find_repository()
 
125
                    # fetch content this dir needs.
 
126
                    if basis_repo:
 
127
                        # XXX FIXME RBC 20060214 need tests for this when the basis
 
128
                        # is incomplete
 
129
                        result_repo.fetch(basis_repo, revision_id=revision_id)
 
130
                    result_repo.fetch(local_repo, revision_id=revision_id)
 
131
                except errors.NoRepositoryPresent:
 
132
                    # needed to make one anyway.
 
133
                    result_repo = local_repo.clone(
 
134
                        result,
 
135
                        revision_id=revision_id,
 
136
                        basis=basis_repo)
 
137
                    result_repo.set_make_working_trees(local_repo.make_working_trees())
 
138
        # 1 if there is a branch present
 
139
        #   make sure its content is available in the target repository
 
140
        #   clone it.
 
141
        try:
 
142
            self.open_branch().clone(result, revision_id=revision_id)
 
143
        except errors.NotBranchError:
 
144
            pass
 
145
        try:
 
146
            self.open_workingtree().clone(result, basis=basis_tree)
 
147
        except (errors.NoWorkingTree, errors.NotLocalUrl):
 
148
            pass
 
149
        return result
 
150
 
 
151
    def _get_basis_components(self, basis):
 
152
        """Retrieve the basis components that are available at basis."""
 
153
        if basis is None:
 
154
            return None, None, None
 
155
        try:
 
156
            basis_tree = basis.open_workingtree()
 
157
            basis_branch = basis_tree.branch
 
158
            basis_repo = basis_branch.repository
 
159
        except (errors.NoWorkingTree, errors.NotLocalUrl):
 
160
            basis_tree = None
 
161
            try:
 
162
                basis_branch = basis.open_branch()
 
163
                basis_repo = basis_branch.repository
 
164
            except errors.NotBranchError:
 
165
                basis_branch = None
 
166
                try:
 
167
                    basis_repo = basis.open_repository()
 
168
                except errors.NoRepositoryPresent:
 
169
                    basis_repo = None
 
170
        return basis_repo, basis_branch, basis_tree
 
171
 
 
172
    def _make_tail(self, url):
 
173
        segments = url.split('/')
 
174
        if segments and segments[-1] not in ('', '.'):
 
175
            parent = '/'.join(segments[:-1])
 
176
            t = bzrlib.transport.get_transport(parent)
 
177
            try:
 
178
                t.mkdir(segments[-1])
 
179
            except errors.FileExists:
 
180
                pass
 
181
 
 
182
    @classmethod
 
183
    def create(cls, base):
 
184
        """Create a new BzrDir at the url 'base'.
 
185
        
 
186
        This will call the current default formats initialize with base
 
187
        as the only parameter.
 
188
 
 
189
        If you need a specific format, consider creating an instance
 
190
        of that and calling initialize().
 
191
        """
 
192
        if cls is not BzrDir:
 
193
            raise AssertionError("BzrDir.create always creates the default format, "
 
194
                    "not one of %r" % cls)
 
195
        segments = base.split('/')
 
196
        if segments and segments[-1] not in ('', '.'):
 
197
            parent = '/'.join(segments[:-1])
 
198
            t = bzrlib.transport.get_transport(parent)
 
199
            try:
 
200
                t.mkdir(segments[-1])
 
201
            except errors.FileExists:
 
202
                pass
 
203
        return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
 
204
 
 
205
    def create_branch(self):
 
206
        """Create a branch in this BzrDir.
 
207
 
 
208
        The bzrdirs format will control what branch format is created.
 
209
        For more control see BranchFormatXX.create(a_bzrdir).
 
210
        """
 
211
        raise NotImplementedError(self.create_branch)
 
212
 
 
213
    @staticmethod
 
214
    def create_branch_and_repo(base, force_new_repo=False):
 
215
        """Create a new BzrDir, Branch and Repository at the url 'base'.
 
216
 
 
217
        This will use the current default BzrDirFormat, and use whatever 
 
218
        repository format that that uses via bzrdir.create_branch and
 
219
        create_repository. If a shared repository is available that is used
 
220
        preferentially.
 
221
 
 
222
        The created Branch object is returned.
 
223
 
 
224
        :param base: The URL to create the branch at.
 
225
        :param force_new_repo: If True a new repository is always created.
 
226
        """
 
227
        bzrdir = BzrDir.create(base)
 
228
        bzrdir._find_or_create_repository(force_new_repo)
 
229
        return bzrdir.create_branch()
 
230
 
 
231
    def _find_or_create_repository(self, force_new_repo):
 
232
        """Create a new repository if needed, returning the repository."""
 
233
        if force_new_repo:
 
234
            return self.create_repository()
 
235
        try:
 
236
            return self.find_repository()
 
237
        except errors.NoRepositoryPresent:
 
238
            return self.create_repository()
 
239
        
 
240
    @staticmethod
 
241
    def create_branch_convenience(base, force_new_repo=False,
 
242
                                  force_new_tree=None, format=None):
 
243
        """Create a new BzrDir, Branch and Repository at the url 'base'.
 
244
 
 
245
        This is a convenience function - it will use an existing repository
 
246
        if possible, can be told explicitly whether to create a working tree or
 
247
        not.
 
248
 
 
249
        This will use the current default BzrDirFormat, and use whatever 
 
250
        repository format that that uses via bzrdir.create_branch and
 
251
        create_repository. If a shared repository is available that is used
 
252
        preferentially. Whatever repository is used, its tree creation policy
 
253
        is followed.
 
254
 
 
255
        The created Branch object is returned.
 
256
        If a working tree cannot be made due to base not being a file:// url,
 
257
        no error is raised unless force_new_tree is True, in which case no 
 
258
        data is created on disk and NotLocalUrl is raised.
 
259
 
 
260
        :param base: The URL to create the branch at.
 
261
        :param force_new_repo: If True a new repository is always created.
 
262
        :param force_new_tree: If True or False force creation of a tree or 
 
263
                               prevent such creation respectively.
 
264
        :param format: Override for the for the bzrdir format to create
 
265
        """
 
266
        if force_new_tree:
 
267
            # check for non local urls
 
268
            t = get_transport(safe_unicode(base))
 
269
            if not isinstance(t, LocalTransport):
 
270
                raise errors.NotLocalUrl(base)
 
271
        if format is None:
 
272
            bzrdir = BzrDir.create(base)
 
273
        else:
 
274
            bzrdir = format.initialize(base)
 
275
        repo = bzrdir._find_or_create_repository(force_new_repo)
 
276
        result = bzrdir.create_branch()
 
277
        if force_new_tree or (repo.make_working_trees() and 
 
278
                              force_new_tree is None):
 
279
            try:
 
280
                bzrdir.create_workingtree()
 
281
            except errors.NotLocalUrl:
 
282
                pass
 
283
        return result
 
284
        
 
285
    @staticmethod
 
286
    def create_repository(base, shared=False):
 
287
        """Create a new BzrDir and Repository at the url 'base'.
 
288
 
 
289
        This will use the current default BzrDirFormat, and use whatever 
 
290
        repository format that that uses for bzrdirformat.create_repository.
 
291
 
 
292
        ;param shared: Create a shared repository rather than a standalone
 
293
                       repository.
 
294
        The Repository object is returned.
 
295
 
 
296
        This must be overridden as an instance method in child classes, where
 
297
        it should take no parameters and construct whatever repository format
 
298
        that child class desires.
 
299
        """
 
300
        bzrdir = BzrDir.create(base)
 
301
        return bzrdir.create_repository()
 
302
 
 
303
    @staticmethod
 
304
    def create_standalone_workingtree(base):
 
305
        """Create a new BzrDir, WorkingTree, Branch and Repository at 'base'.
 
306
 
 
307
        'base' must be a local path or a file:// url.
 
308
 
 
309
        This will use the current default BzrDirFormat, and use whatever 
 
310
        repository format that that uses for bzrdirformat.create_workingtree,
 
311
        create_branch and create_repository.
 
312
 
 
313
        The WorkingTree object is returned.
 
314
        """
 
315
        t = get_transport(safe_unicode(base))
 
316
        if not isinstance(t, LocalTransport):
 
317
            raise errors.NotLocalUrl(base)
 
318
        bzrdir = BzrDir.create_branch_and_repo(safe_unicode(base),
 
319
                                               force_new_repo=True).bzrdir
 
320
        return bzrdir.create_workingtree()
 
321
 
 
322
    def create_workingtree(self, revision_id=None):
 
323
        """Create a working tree at this BzrDir.
 
324
        
 
325
        revision_id: create it as of this revision id.
 
326
        """
 
327
        raise NotImplementedError(self.create_workingtree)
 
328
 
 
329
    def find_repository(self):
 
330
        """Find the repository that should be used for a_bzrdir.
 
331
 
 
332
        This does not require a branch as we use it to find the repo for
 
333
        new branches as well as to hook existing branches up to their
 
334
        repository.
 
335
        """
 
336
        try:
 
337
            return self.open_repository()
 
338
        except errors.NoRepositoryPresent:
 
339
            pass
 
340
        next_transport = self.root_transport.clone('..')
 
341
        while True:
 
342
            try:
 
343
                found_bzrdir = BzrDir.open_containing_from_transport(
 
344
                    next_transport)[0]
 
345
            except errors.NotBranchError:
 
346
                raise errors.NoRepositoryPresent(self)
 
347
            try:
 
348
                repository = found_bzrdir.open_repository()
 
349
            except errors.NoRepositoryPresent:
 
350
                next_transport = found_bzrdir.root_transport.clone('..')
 
351
                continue
 
352
            if ((found_bzrdir.root_transport.base == 
 
353
                 self.root_transport.base) or repository.is_shared()):
 
354
                return repository
 
355
            else:
 
356
                raise errors.NoRepositoryPresent(self)
 
357
        raise errors.NoRepositoryPresent(self)
 
358
 
 
359
    def get_branch_transport(self, branch_format):
 
360
        """Get the transport for use by branch format in this BzrDir.
 
361
 
 
362
        Note that bzr dirs that do not support format strings will raise
 
363
        IncompatibleFormat if the branch format they are given has
 
364
        a format string, and vice verca.
 
365
 
 
366
        If branch_format is None, the transport is returned with no 
 
367
        checking. if it is not None, then the returned transport is
 
368
        guaranteed to point to an existing directory ready for use.
 
369
        """
 
370
        raise NotImplementedError(self.get_branch_transport)
 
371
        
 
372
    def get_repository_transport(self, repository_format):
 
373
        """Get the transport for use by repository format in this BzrDir.
 
374
 
 
375
        Note that bzr dirs that do not support format strings will raise
 
376
        IncompatibleFormat if the repository format they are given has
 
377
        a format string, and vice verca.
 
378
 
 
379
        If repository_format is None, the transport is returned with no 
 
380
        checking. if it is not None, then the returned transport is
 
381
        guaranteed to point to an existing directory ready for use.
 
382
        """
 
383
        raise NotImplementedError(self.get_repository_transport)
 
384
        
 
385
    def get_workingtree_transport(self, tree_format):
 
386
        """Get the transport for use by workingtree format in this BzrDir.
 
387
 
 
388
        Note that bzr dirs that do not support format strings will raise
 
389
        IncompatibleFormat if the workingtree format they are given has
 
390
        a format string, and vice verca.
 
391
 
 
392
        If workingtree_format is None, the transport is returned with no 
 
393
        checking. if it is not None, then the returned transport is
 
394
        guaranteed to point to an existing directory ready for use.
 
395
        """
 
396
        raise NotImplementedError(self.get_workingtree_transport)
 
397
        
 
398
    def __init__(self, _transport, _format):
 
399
        """Initialize a Bzr control dir object.
 
400
        
 
401
        Only really common logic should reside here, concrete classes should be
 
402
        made with varying behaviours.
 
403
 
 
404
        :param _format: the format that is creating this BzrDir instance.
 
405
        :param _transport: the transport this dir is based at.
 
406
        """
 
407
        self._format = _format
 
408
        self.transport = _transport.clone('.bzr')
 
409
        self.root_transport = _transport
 
410
 
 
411
    def is_control_filename(self, filename):
 
412
        """True if filename is the name of a path which is reserved for bzrdir's.
 
413
        
 
414
        :param filename: A filename within the root transport of this bzrdir.
 
415
 
 
416
        This is true IF and ONLY IF the filename is part of the namespace reserved
 
417
        for bzr control dirs. Currently this is the '.bzr' directory in the root
 
418
        of the root_transport. it is expected that plugins will need to extend
 
419
        this in the future - for instance to make bzr talk with svn working
 
420
        trees.
 
421
        """
 
422
        # this might be better on the BzrDirFormat class because it refers to 
 
423
        # all the possible bzrdir disk formats. 
 
424
        # This method is tested via the workingtree is_control_filename tests- 
 
425
        # it was extractd from WorkingTree.is_control_filename. If the methods
 
426
        # contract is extended beyond the current trivial  implementation please
 
427
        # add new tests for it to the appropriate place.
 
428
        return filename == '.bzr' or filename.startswith('.bzr/')
 
429
 
 
430
    def needs_format_conversion(self, format=None):
 
431
        """Return true if this bzrdir needs convert_format run on it.
 
432
        
 
433
        For instance, if the repository format is out of date but the 
 
434
        branch and working tree are not, this should return True.
 
435
 
 
436
        :param format: Optional parameter indicating a specific desired
 
437
                       format we plan to arrive at.
 
438
        """
 
439
        raise NotImplementedError(self.needs_format_conversion)
 
440
 
 
441
    @staticmethod
 
442
    def open_unsupported(base):
 
443
        """Open a branch which is not supported."""
 
444
        return BzrDir.open(base, _unsupported=True)
 
445
        
 
446
    @staticmethod
 
447
    def open(base, _unsupported=False):
 
448
        """Open an existing bzrdir, rooted at 'base' (url)
 
449
        
 
450
        _unsupported is a private parameter to the BzrDir class.
 
451
        """
 
452
        t = get_transport(base)
 
453
        mutter("trying to open %r with transport %r", base, t)
 
454
        format = BzrDirFormat.find_format(t)
 
455
        BzrDir._check_supported(format, _unsupported)
 
456
        return format.open(t, _found=True)
 
457
 
 
458
    def open_branch(self, unsupported=False):
 
459
        """Open the branch object at this BzrDir if one is present.
 
460
 
 
461
        If unsupported is True, then no longer supported branch formats can
 
462
        still be opened.
 
463
        
 
464
        TODO: static convenience version of this?
 
465
        """
 
466
        raise NotImplementedError(self.open_branch)
 
467
 
 
468
    @staticmethod
 
469
    def open_containing(url):
 
470
        """Open an existing branch which contains url.
 
471
        
 
472
        :param url: url to search from.
 
473
        See open_containing_from_transport for more detail.
 
474
        """
 
475
        return BzrDir.open_containing_from_transport(get_transport(url))
 
476
    
 
477
    @staticmethod
 
478
    def open_containing_from_transport(a_transport):
 
479
        """Open an existing branch which contains a_transport.base
 
480
 
 
481
        This probes for a branch at a_transport, and searches upwards from there.
 
482
 
 
483
        Basically we keep looking up until we find the control directory or
 
484
        run into the root.  If there isn't one, raises NotBranchError.
 
485
        If there is one and it is either an unrecognised format or an unsupported 
 
486
        format, UnknownFormatError or UnsupportedFormatError are raised.
 
487
        If there is one, it is returned, along with the unused portion of url.
 
488
        """
 
489
        # this gets the normalised url back. I.e. '.' -> the full path.
 
490
        url = a_transport.base
 
491
        while True:
 
492
            try:
 
493
                format = BzrDirFormat.find_format(a_transport)
 
494
                BzrDir._check_supported(format, False)
 
495
                return format.open(a_transport), a_transport.relpath(url)
 
496
            except errors.NotBranchError, e:
 
497
                mutter('not a branch in: %r %s', a_transport.base, e)
 
498
            new_t = a_transport.clone('..')
 
499
            if new_t.base == a_transport.base:
 
500
                # reached the root, whatever that may be
 
501
                raise errors.NotBranchError(path=url)
 
502
            a_transport = new_t
 
503
 
 
504
    def open_repository(self, _unsupported=False):
 
505
        """Open the repository object at this BzrDir if one is present.
 
506
 
 
507
        This will not follow the Branch object pointer - its strictly a direct
 
508
        open facility. Most client code should use open_branch().repository to
 
509
        get at a repository.
 
510
 
 
511
        _unsupported is a private parameter, not part of the api.
 
512
        TODO: static convenience version of this?
 
513
        """
 
514
        raise NotImplementedError(self.open_repository)
 
515
 
 
516
    def open_workingtree(self, _unsupported=False):
 
517
        """Open the workingtree object at this BzrDir if one is present.
 
518
        
 
519
        TODO: static convenience version of this?
 
520
        """
 
521
        raise NotImplementedError(self.open_workingtree)
 
522
 
 
523
    def has_branch(self):
 
524
        """Tell if this bzrdir contains a branch.
 
525
        
 
526
        Note: if you're going to open the branch, you should just go ahead
 
527
        and try, and not ask permission first.  (This method just opens the 
 
528
        branch and discards it, and that's somewhat expensive.) 
 
529
        """
 
530
        try:
 
531
            self.open_branch()
 
532
            return True
 
533
        except errors.NotBranchError:
 
534
            return False
 
535
 
 
536
    def has_workingtree(self):
 
537
        """Tell if this bzrdir contains a working tree.
 
538
 
 
539
        This will still raise an exception if the bzrdir has a workingtree that
 
540
        is remote & inaccessible.
 
541
        
 
542
        Note: if you're going to open the working tree, you should just go ahead
 
543
        and try, and not ask permission first.  (This method just opens the 
 
544
        workingtree and discards it, and that's somewhat expensive.) 
 
545
        """
 
546
        try:
 
547
            self.open_workingtree()
 
548
            return True
 
549
        except errors.NoWorkingTree:
 
550
            return False
 
551
 
 
552
    def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
 
553
        """Create a copy of this bzrdir prepared for use as a new line of
 
554
        development.
 
555
 
 
556
        If urls last component does not exist, it will be created.
 
557
 
 
558
        Attributes related to the identity of the source branch like
 
559
        branch nickname will be cleaned, a working tree is created
 
560
        whether one existed before or not; and a local branch is always
 
561
        created.
 
562
 
 
563
        if revision_id is not None, then the clone operation may tune
 
564
            itself to download less data.
 
565
        """
 
566
        self._make_tail(url)
 
567
        result = self._format.initialize(url)
 
568
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
569
        try:
 
570
            source_branch = self.open_branch()
 
571
            source_repository = source_branch.repository
 
572
        except errors.NotBranchError:
 
573
            source_branch = None
 
574
            try:
 
575
                source_repository = self.open_repository()
 
576
            except errors.NoRepositoryPresent:
 
577
                # copy the entire basis one if there is one
 
578
                # but there is no repository.
 
579
                source_repository = basis_repo
 
580
        if force_new_repo:
 
581
            result_repo = None
 
582
        else:
 
583
            try:
 
584
                result_repo = result.find_repository()
 
585
            except errors.NoRepositoryPresent:
 
586
                result_repo = None
 
587
        if source_repository is None and result_repo is not None:
 
588
            pass
 
589
        elif source_repository is None and result_repo is None:
 
590
            # no repo available, make a new one
 
591
            result.create_repository()
 
592
        elif source_repository is not None and result_repo is None:
 
593
            # have source, and want to make a new target repo
 
594
            # we dont clone the repo because that preserves attributes
 
595
            # like is_shared(), and we have not yet implemented a 
 
596
            # repository sprout().
 
597
            result_repo = result.create_repository()
 
598
        if result_repo is not None:
 
599
            # fetch needed content into target.
 
600
            if basis_repo:
 
601
                # XXX FIXME RBC 20060214 need tests for this when the basis
 
602
                # is incomplete
 
603
                result_repo.fetch(basis_repo, revision_id=revision_id)
 
604
            result_repo.fetch(source_repository, revision_id=revision_id)
 
605
        if source_branch is not None:
 
606
            source_branch.sprout(result, revision_id=revision_id)
 
607
        else:
 
608
            result.create_branch()
 
609
        if result_repo is None or result_repo.make_working_trees():
 
610
            result.create_workingtree()
 
611
        return result
 
612
 
 
613
 
 
614
class BzrDirPreSplitOut(BzrDir):
 
615
    """A common class for the all-in-one formats."""
 
616
 
 
617
    def __init__(self, _transport, _format):
 
618
        """See BzrDir.__init__."""
 
619
        super(BzrDirPreSplitOut, self).__init__(_transport, _format)
 
620
        assert self._format._lock_class == TransportLock
 
621
        assert self._format._lock_file_name == 'branch-lock'
 
622
        self._control_files = LockableFiles(self.get_branch_transport(None),
 
623
                                            self._format._lock_file_name,
 
624
                                            self._format._lock_class)
 
625
 
 
626
    def break_lock(self):
 
627
        """Pre-splitout bzrdirs do not suffer from stale locks."""
 
628
        raise NotImplementedError(self.break_lock)
 
629
 
 
630
    def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
 
631
        """See BzrDir.clone()."""
 
632
        from bzrlib.workingtree import WorkingTreeFormat2
 
633
        self._make_tail(url)
 
634
        result = self._format._initialize_for_clone(url)
 
635
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
636
        self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
 
637
        from_branch = self.open_branch()
 
638
        from_branch.clone(result, revision_id=revision_id)
 
639
        try:
 
640
            self.open_workingtree().clone(result, basis=basis_tree)
 
641
        except errors.NotLocalUrl:
 
642
            # make a new one, this format always has to have one.
 
643
            try:
 
644
                WorkingTreeFormat2().initialize(result)
 
645
            except errors.NotLocalUrl:
 
646
                # but we cannot do it for remote trees.
 
647
                to_branch = result.open_branch()
 
648
                WorkingTreeFormat2().stub_initialize_remote(to_branch.control_files)
 
649
        return result
 
650
 
 
651
    def create_branch(self):
 
652
        """See BzrDir.create_branch."""
 
653
        return self.open_branch()
 
654
 
 
655
    def create_repository(self, shared=False):
 
656
        """See BzrDir.create_repository."""
 
657
        if shared:
 
658
            raise errors.IncompatibleFormat('shared repository', self._format)
 
659
        return self.open_repository()
 
660
 
 
661
    def create_workingtree(self, revision_id=None):
 
662
        """See BzrDir.create_workingtree."""
 
663
        # this looks buggy but is not -really-
 
664
        # clone and sprout will have set the revision_id
 
665
        # and that will have set it for us, its only
 
666
        # specific uses of create_workingtree in isolation
 
667
        # that can do wonky stuff here, and that only
 
668
        # happens for creating checkouts, which cannot be 
 
669
        # done on this format anyway. So - acceptable wart.
 
670
        result = self.open_workingtree()
 
671
        if revision_id is not None:
 
672
            result.set_last_revision(revision_id)
 
673
        return result
 
674
 
 
675
    def get_branch_transport(self, branch_format):
 
676
        """See BzrDir.get_branch_transport()."""
 
677
        if branch_format is None:
 
678
            return self.transport
 
679
        try:
 
680
            branch_format.get_format_string()
 
681
        except NotImplementedError:
 
682
            return self.transport
 
683
        raise errors.IncompatibleFormat(branch_format, self._format)
 
684
 
 
685
    def get_repository_transport(self, repository_format):
 
686
        """See BzrDir.get_repository_transport()."""
 
687
        if repository_format is None:
 
688
            return self.transport
 
689
        try:
 
690
            repository_format.get_format_string()
 
691
        except NotImplementedError:
 
692
            return self.transport
 
693
        raise errors.IncompatibleFormat(repository_format, self._format)
 
694
 
 
695
    def get_workingtree_transport(self, workingtree_format):
 
696
        """See BzrDir.get_workingtree_transport()."""
 
697
        if workingtree_format is None:
 
698
            return self.transport
 
699
        try:
 
700
            workingtree_format.get_format_string()
 
701
        except NotImplementedError:
 
702
            return self.transport
 
703
        raise errors.IncompatibleFormat(workingtree_format, self._format)
 
704
 
 
705
    def needs_format_conversion(self, format=None):
 
706
        """See BzrDir.needs_format_conversion()."""
 
707
        # if the format is not the same as the system default,
 
708
        # an upgrade is needed.
 
709
        if format is None:
 
710
            format = BzrDirFormat.get_default_format()
 
711
        return not isinstance(self._format, format.__class__)
 
712
 
 
713
    def open_branch(self, unsupported=False):
 
714
        """See BzrDir.open_branch."""
 
715
        from bzrlib.branch import BzrBranchFormat4
 
716
        format = BzrBranchFormat4()
 
717
        self._check_supported(format, unsupported)
 
718
        return format.open(self, _found=True)
 
719
 
 
720
    def sprout(self, url, revision_id=None, basis=None):
 
721
        """See BzrDir.sprout()."""
 
722
        from bzrlib.workingtree import WorkingTreeFormat2
 
723
        self._make_tail(url)
 
724
        result = self._format._initialize_for_clone(url)
 
725
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
726
        try:
 
727
            self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
 
728
        except errors.NoRepositoryPresent:
 
729
            pass
 
730
        try:
 
731
            self.open_branch().sprout(result, revision_id=revision_id)
 
732
        except errors.NotBranchError:
 
733
            pass
 
734
        # we always want a working tree
 
735
        WorkingTreeFormat2().initialize(result)
 
736
        return result
 
737
 
 
738
 
 
739
class BzrDir4(BzrDirPreSplitOut):
 
740
    """A .bzr version 4 control object.
 
741
    
 
742
    This is a deprecated format and may be removed after sept 2006.
 
743
    """
 
744
 
 
745
    def create_repository(self, shared=False):
 
746
        """See BzrDir.create_repository."""
 
747
        return self._format.repository_format.initialize(self, shared)
 
748
 
 
749
    def needs_format_conversion(self, format=None):
 
750
        """Format 4 dirs are always in need of conversion."""
 
751
        return True
 
752
 
 
753
    def open_repository(self):
 
754
        """See BzrDir.open_repository."""
 
755
        from bzrlib.repository import RepositoryFormat4
 
756
        return RepositoryFormat4().open(self, _found=True)
 
757
 
 
758
 
 
759
class BzrDir5(BzrDirPreSplitOut):
 
760
    """A .bzr version 5 control object.
 
761
 
 
762
    This is a deprecated format and may be removed after sept 2006.
 
763
    """
 
764
 
 
765
    def open_repository(self):
 
766
        """See BzrDir.open_repository."""
 
767
        from bzrlib.repository import RepositoryFormat5
 
768
        return RepositoryFormat5().open(self, _found=True)
 
769
 
 
770
    def open_workingtree(self, _unsupported=False):
 
771
        """See BzrDir.create_workingtree."""
 
772
        from bzrlib.workingtree import WorkingTreeFormat2
 
773
        return WorkingTreeFormat2().open(self, _found=True)
 
774
 
 
775
 
 
776
class BzrDir6(BzrDirPreSplitOut):
 
777
    """A .bzr version 6 control object.
 
778
 
 
779
    This is a deprecated format and may be removed after sept 2006.
 
780
    """
 
781
 
 
782
    def open_repository(self):
 
783
        """See BzrDir.open_repository."""
 
784
        from bzrlib.repository import RepositoryFormat6
 
785
        return RepositoryFormat6().open(self, _found=True)
 
786
 
 
787
    def open_workingtree(self, _unsupported=False):
 
788
        """See BzrDir.create_workingtree."""
 
789
        from bzrlib.workingtree import WorkingTreeFormat2
 
790
        return WorkingTreeFormat2().open(self, _found=True)
 
791
 
 
792
 
 
793
class BzrDirMeta1(BzrDir):
 
794
    """A .bzr meta version 1 control object.
 
795
    
 
796
    This is the first control object where the 
 
797
    individual aspects are really split out: there are separate repository,
 
798
    workingtree and branch subdirectories and any subset of the three can be
 
799
    present within a BzrDir.
 
800
    """
 
801
 
 
802
    def can_convert_format(self):
 
803
        """See BzrDir.can_convert_format()."""
 
804
        return True
 
805
 
 
806
    def create_branch(self):
 
807
        """See BzrDir.create_branch."""
 
808
        from bzrlib.branch import BranchFormat
 
809
        return BranchFormat.get_default_format().initialize(self)
 
810
 
 
811
    def create_repository(self, shared=False):
 
812
        """See BzrDir.create_repository."""
 
813
        return self._format.repository_format.initialize(self, shared)
 
814
 
 
815
    def create_workingtree(self, revision_id=None):
 
816
        """See BzrDir.create_workingtree."""
 
817
        from bzrlib.workingtree import WorkingTreeFormat
 
818
        return WorkingTreeFormat.get_default_format().initialize(self, revision_id)
 
819
 
 
820
    def _get_mkdir_mode(self):
 
821
        """Figure out the mode to use when creating a bzrdir subdir."""
 
822
        temp_control = LockableFiles(self.transport, '', TransportLock)
 
823
        return temp_control._dir_mode
 
824
 
 
825
    def get_branch_transport(self, branch_format):
 
826
        """See BzrDir.get_branch_transport()."""
 
827
        if branch_format is None:
 
828
            return self.transport.clone('branch')
 
829
        try:
 
830
            branch_format.get_format_string()
 
831
        except NotImplementedError:
 
832
            raise errors.IncompatibleFormat(branch_format, self._format)
 
833
        try:
 
834
            self.transport.mkdir('branch', mode=self._get_mkdir_mode())
 
835
        except errors.FileExists:
 
836
            pass
 
837
        return self.transport.clone('branch')
 
838
 
 
839
    def get_repository_transport(self, repository_format):
 
840
        """See BzrDir.get_repository_transport()."""
 
841
        if repository_format is None:
 
842
            return self.transport.clone('repository')
 
843
        try:
 
844
            repository_format.get_format_string()
 
845
        except NotImplementedError:
 
846
            raise errors.IncompatibleFormat(repository_format, self._format)
 
847
        try:
 
848
            self.transport.mkdir('repository', mode=self._get_mkdir_mode())
 
849
        except errors.FileExists:
 
850
            pass
 
851
        return self.transport.clone('repository')
 
852
 
 
853
    def get_workingtree_transport(self, workingtree_format):
 
854
        """See BzrDir.get_workingtree_transport()."""
 
855
        if workingtree_format is None:
 
856
            return self.transport.clone('checkout')
 
857
        try:
 
858
            workingtree_format.get_format_string()
 
859
        except NotImplementedError:
 
860
            raise errors.IncompatibleFormat(workingtree_format, self._format)
 
861
        try:
 
862
            self.transport.mkdir('checkout', mode=self._get_mkdir_mode())
 
863
        except errors.FileExists:
 
864
            pass
 
865
        return self.transport.clone('checkout')
 
866
 
 
867
    def needs_format_conversion(self, format=None):
 
868
        """See BzrDir.needs_format_conversion()."""
 
869
        if format is None:
 
870
            format = BzrDirFormat.get_default_format()
 
871
        if not isinstance(self._format, format.__class__):
 
872
            # it is not a meta dir format, conversion is needed.
 
873
            return True
 
874
        # we might want to push this down to the repository?
 
875
        try:
 
876
            if not isinstance(self.open_repository()._format,
 
877
                              format.repository_format.__class__):
 
878
                # the repository needs an upgrade.
 
879
                return True
 
880
        except errors.NoRepositoryPresent:
 
881
            pass
 
882
        # currently there are no other possible conversions for meta1 formats.
 
883
        return False
 
884
 
 
885
    def open_branch(self, unsupported=False):
 
886
        """See BzrDir.open_branch."""
 
887
        from bzrlib.branch import BranchFormat
 
888
        format = BranchFormat.find_format(self)
 
889
        self._check_supported(format, unsupported)
 
890
        return format.open(self, _found=True)
 
891
 
 
892
    def open_repository(self, unsupported=False):
 
893
        """See BzrDir.open_repository."""
 
894
        from bzrlib.repository import RepositoryFormat
 
895
        format = RepositoryFormat.find_format(self)
 
896
        self._check_supported(format, unsupported)
 
897
        return format.open(self, _found=True)
 
898
 
 
899
    def open_workingtree(self, unsupported=False):
 
900
        """See BzrDir.open_workingtree."""
 
901
        from bzrlib.workingtree import WorkingTreeFormat
 
902
        format = WorkingTreeFormat.find_format(self)
 
903
        self._check_supported(format, unsupported)
 
904
        return format.open(self, _found=True)
 
905
 
 
906
 
 
907
class BzrDirFormat(object):
 
908
    """An encapsulation of the initialization and open routines for a format.
 
909
 
 
910
    Formats provide three things:
 
911
     * An initialization routine,
 
912
     * a format string,
 
913
     * an open routine.
 
914
 
 
915
    Formats are placed in an dict by their format string for reference 
 
916
    during bzrdir opening. These should be subclasses of BzrDirFormat
 
917
    for consistency.
 
918
 
 
919
    Once a format is deprecated, just deprecate the initialize and open
 
920
    methods on the format class. Do not deprecate the object, as the 
 
921
    object will be created every system load.
 
922
    """
 
923
 
 
924
    _default_format = None
 
925
    """The default format used for new .bzr dirs."""
 
926
 
 
927
    _formats = {}
 
928
    """The known formats."""
 
929
 
 
930
    _lock_file_name = 'branch-lock'
 
931
 
 
932
    # _lock_class must be set in subclasses to the lock type, typ.
 
933
    # TransportLock or LockDir
 
934
 
 
935
    @classmethod
 
936
    def find_format(klass, transport):
 
937
        """Return the format registered for URL."""
 
938
        try:
 
939
            format_string = transport.get(".bzr/branch-format").read()
 
940
            return klass._formats[format_string]
 
941
        except errors.NoSuchFile:
 
942
            raise errors.NotBranchError(path=transport.base)
 
943
        except KeyError:
 
944
            raise errors.UnknownFormatError(format=format_string)
 
945
 
 
946
    @classmethod
 
947
    def get_default_format(klass):
 
948
        """Return the current default format."""
 
949
        return klass._default_format
 
950
 
 
951
    def get_format_string(self):
 
952
        """Return the ASCII format string that identifies this format."""
 
953
        raise NotImplementedError(self.get_format_string)
 
954
 
 
955
    def get_format_description(self):
 
956
        """Return the short description for this format."""
 
957
        raise NotImplementedError(self.get_format_description)
 
958
 
 
959
    def get_converter(self, format=None):
 
960
        """Return the converter to use to convert bzrdirs needing converts.
 
961
 
 
962
        This returns a bzrlib.bzrdir.Converter object.
 
963
 
 
964
        This should return the best upgrader to step this format towards the
 
965
        current default format. In the case of plugins we can/shouold provide
 
966
        some means for them to extend the range of returnable converters.
 
967
 
 
968
        :param format: Optional format to override the default foramt of the 
 
969
                       library.
 
970
        """
 
971
        raise NotImplementedError(self.get_converter)
 
972
 
 
973
    def initialize(self, url):
 
974
        """Create a bzr control dir at this url and return an opened copy.
 
975
        
 
976
        Subclasses should typically override initialize_on_transport
 
977
        instead of this method.
 
978
        """
 
979
        return self.initialize_on_transport(get_transport(url))
 
980
 
 
981
    def initialize_on_transport(self, transport):
 
982
        """Initialize a new bzrdir in the base directory of a Transport."""
 
983
        # Since we don'transport have a .bzr directory, inherit the
 
984
        # mode from the root directory
 
985
        temp_control = LockableFiles(transport, '', TransportLock)
 
986
        temp_control._transport.mkdir('.bzr',
 
987
                                      # FIXME: RBC 20060121 dont peek under
 
988
                                      # the covers
 
989
                                      mode=temp_control._dir_mode)
 
990
        file_mode = temp_control._file_mode
 
991
        del temp_control
 
992
        mutter('created control directory in ' + transport.base)
 
993
        control = transport.clone('.bzr')
 
994
        utf8_files = [('README', 
 
995
                       "This is a Bazaar-NG control directory.\n"
 
996
                       "Do not change any files in this directory.\n"),
 
997
                      ('branch-format', self.get_format_string()),
 
998
                      ]
 
999
        # NB: no need to escape relative paths that are url safe.
 
1000
        control_files = LockableFiles(control, self._lock_file_name, 
 
1001
                                      self._lock_class)
 
1002
        control_files.create_lock()
 
1003
        control_files.lock_write()
 
1004
        try:
 
1005
            for file, content in utf8_files:
 
1006
                control_files.put_utf8(file, content)
 
1007
        finally:
 
1008
            control_files.unlock()
 
1009
        return self.open(transport, _found=True)
 
1010
 
 
1011
    def is_supported(self):
 
1012
        """Is this format supported?
 
1013
 
 
1014
        Supported formats must be initializable and openable.
 
1015
        Unsupported formats may not support initialization or committing or 
 
1016
        some other features depending on the reason for not being supported.
 
1017
        """
 
1018
        return True
 
1019
 
 
1020
    def open(self, transport, _found=False):
 
1021
        """Return an instance of this format for the dir transport points at.
 
1022
        
 
1023
        _found is a private parameter, do not use it.
 
1024
        """
 
1025
        if not _found:
 
1026
            assert isinstance(BzrDirFormat.find_format(transport),
 
1027
                              self.__class__)
 
1028
        return self._open(transport)
 
1029
 
 
1030
    def _open(self, transport):
 
1031
        """Template method helper for opening BzrDirectories.
 
1032
 
 
1033
        This performs the actual open and any additional logic or parameter
 
1034
        passing.
 
1035
        """
 
1036
        raise NotImplementedError(self._open)
 
1037
 
 
1038
    @classmethod
 
1039
    def register_format(klass, format):
 
1040
        klass._formats[format.get_format_string()] = format
 
1041
 
 
1042
    @classmethod
 
1043
    def set_default_format(klass, format):
 
1044
        klass._default_format = format
 
1045
 
 
1046
    def __str__(self):
 
1047
        return self.get_format_string()[:-1]
 
1048
 
 
1049
    @classmethod
 
1050
    def unregister_format(klass, format):
 
1051
        assert klass._formats[format.get_format_string()] is format
 
1052
        del klass._formats[format.get_format_string()]
 
1053
 
 
1054
 
 
1055
class BzrDirFormat4(BzrDirFormat):
 
1056
    """Bzr dir format 4.
 
1057
 
 
1058
    This format is a combined format for working tree, branch and repository.
 
1059
    It has:
 
1060
     - Format 1 working trees [always]
 
1061
     - Format 4 branches [always]
 
1062
     - Format 4 repositories [always]
 
1063
 
 
1064
    This format is deprecated: it indexes texts using a text it which is
 
1065
    removed in format 5; write support for this format has been removed.
 
1066
    """
 
1067
 
 
1068
    _lock_class = TransportLock
 
1069
 
 
1070
    def get_format_string(self):
 
1071
        """See BzrDirFormat.get_format_string()."""
 
1072
        return "Bazaar-NG branch, format 0.0.4\n"
 
1073
 
 
1074
    def get_format_description(self):
 
1075
        """See BzrDirFormat.get_format_description()."""
 
1076
        return "All-in-one format 4"
 
1077
 
 
1078
    def get_converter(self, format=None):
 
1079
        """See BzrDirFormat.get_converter()."""
 
1080
        # there is one and only one upgrade path here.
 
1081
        return ConvertBzrDir4To5()
 
1082
        
 
1083
    def initialize_on_transport(self, transport):
 
1084
        """Format 4 branches cannot be created."""
 
1085
        raise errors.UninitializableFormat(self)
 
1086
 
 
1087
    def is_supported(self):
 
1088
        """Format 4 is not supported.
 
1089
 
 
1090
        It is not supported because the model changed from 4 to 5 and the
 
1091
        conversion logic is expensive - so doing it on the fly was not 
 
1092
        feasible.
 
1093
        """
 
1094
        return False
 
1095
 
 
1096
    def _open(self, transport):
 
1097
        """See BzrDirFormat._open."""
 
1098
        return BzrDir4(transport, self)
 
1099
 
 
1100
    def __return_repository_format(self):
 
1101
        """Circular import protection."""
 
1102
        from bzrlib.repository import RepositoryFormat4
 
1103
        return RepositoryFormat4(self)
 
1104
    repository_format = property(__return_repository_format)
 
1105
 
 
1106
 
 
1107
class BzrDirFormat5(BzrDirFormat):
 
1108
    """Bzr control format 5.
 
1109
 
 
1110
    This format is a combined format for working tree, branch and repository.
 
1111
    It has:
 
1112
     - Format 2 working trees [always] 
 
1113
     - Format 4 branches [always] 
 
1114
     - Format 5 repositories [always]
 
1115
       Unhashed stores in the repository.
 
1116
    """
 
1117
 
 
1118
    _lock_class = TransportLock
 
1119
 
 
1120
    def get_format_string(self):
 
1121
        """See BzrDirFormat.get_format_string()."""
 
1122
        return "Bazaar-NG branch, format 5\n"
 
1123
 
 
1124
    def get_format_description(self):
 
1125
        """See BzrDirFormat.get_format_description()."""
 
1126
        return "All-in-one format 5"
 
1127
 
 
1128
    def get_converter(self, format=None):
 
1129
        """See BzrDirFormat.get_converter()."""
 
1130
        # there is one and only one upgrade path here.
 
1131
        return ConvertBzrDir5To6()
 
1132
 
 
1133
    def _initialize_for_clone(self, url):
 
1134
        return self.initialize_on_transport(get_transport(url), _cloning=True)
 
1135
        
 
1136
    def initialize_on_transport(self, transport, _cloning=False):
 
1137
        """Format 5 dirs always have working tree, branch and repository.
 
1138
        
 
1139
        Except when they are being cloned.
 
1140
        """
 
1141
        from bzrlib.branch import BzrBranchFormat4
 
1142
        from bzrlib.repository import RepositoryFormat5
 
1143
        from bzrlib.workingtree import WorkingTreeFormat2
 
1144
        result = (super(BzrDirFormat5, self).initialize_on_transport(transport))
 
1145
        RepositoryFormat5().initialize(result, _internal=True)
 
1146
        if not _cloning:
 
1147
            BzrBranchFormat4().initialize(result)
 
1148
            WorkingTreeFormat2().initialize(result)
 
1149
        return result
 
1150
 
 
1151
    def _open(self, transport):
 
1152
        """See BzrDirFormat._open."""
 
1153
        return BzrDir5(transport, self)
 
1154
 
 
1155
    def __return_repository_format(self):
 
1156
        """Circular import protection."""
 
1157
        from bzrlib.repository import RepositoryFormat5
 
1158
        return RepositoryFormat5(self)
 
1159
    repository_format = property(__return_repository_format)
 
1160
 
 
1161
 
 
1162
class BzrDirFormat6(BzrDirFormat):
 
1163
    """Bzr control format 6.
 
1164
 
 
1165
    This format is a combined format for working tree, branch and repository.
 
1166
    It has:
 
1167
     - Format 2 working trees [always] 
 
1168
     - Format 4 branches [always] 
 
1169
     - Format 6 repositories [always]
 
1170
    """
 
1171
 
 
1172
    _lock_class = TransportLock
 
1173
 
 
1174
    def get_format_string(self):
 
1175
        """See BzrDirFormat.get_format_string()."""
 
1176
        return "Bazaar-NG branch, format 6\n"
 
1177
 
 
1178
    def get_format_description(self):
 
1179
        """See BzrDirFormat.get_format_description()."""
 
1180
        return "All-in-one format 6"
 
1181
 
 
1182
    def get_converter(self, format=None):
 
1183
        """See BzrDirFormat.get_converter()."""
 
1184
        # there is one and only one upgrade path here.
 
1185
        return ConvertBzrDir6ToMeta()
 
1186
        
 
1187
    def _initialize_for_clone(self, url):
 
1188
        return self.initialize_on_transport(get_transport(url), _cloning=True)
 
1189
 
 
1190
    def initialize_on_transport(self, transport, _cloning=False):
 
1191
        """Format 6 dirs always have working tree, branch and repository.
 
1192
        
 
1193
        Except when they are being cloned.
 
1194
        """
 
1195
        from bzrlib.branch import BzrBranchFormat4
 
1196
        from bzrlib.repository import RepositoryFormat6
 
1197
        from bzrlib.workingtree import WorkingTreeFormat2
 
1198
        result = super(BzrDirFormat6, self).initialize_on_transport(transport)
 
1199
        RepositoryFormat6().initialize(result, _internal=True)
 
1200
        if not _cloning:
 
1201
            BzrBranchFormat4().initialize(result)
 
1202
            try:
 
1203
                WorkingTreeFormat2().initialize(result)
 
1204
            except errors.NotLocalUrl:
 
1205
                # emulate pre-check behaviour for working tree and silently 
 
1206
                # fail.
 
1207
                pass
 
1208
        return result
 
1209
 
 
1210
    def _open(self, transport):
 
1211
        """See BzrDirFormat._open."""
 
1212
        return BzrDir6(transport, self)
 
1213
 
 
1214
    def __return_repository_format(self):
 
1215
        """Circular import protection."""
 
1216
        from bzrlib.repository import RepositoryFormat6
 
1217
        return RepositoryFormat6(self)
 
1218
    repository_format = property(__return_repository_format)
 
1219
 
 
1220
 
 
1221
class BzrDirMetaFormat1(BzrDirFormat):
 
1222
    """Bzr meta control format 1
 
1223
 
 
1224
    This is the first format with split out working tree, branch and repository
 
1225
    disk storage.
 
1226
    It has:
 
1227
     - Format 3 working trees [optional]
 
1228
     - Format 5 branches [optional]
 
1229
     - Format 7 repositories [optional]
 
1230
    """
 
1231
 
 
1232
    _lock_class = LockDir
 
1233
 
 
1234
    def get_converter(self, format=None):
 
1235
        """See BzrDirFormat.get_converter()."""
 
1236
        if format is None:
 
1237
            format = BzrDirFormat.get_default_format()
 
1238
        if not isinstance(self, format.__class__):
 
1239
            # converting away from metadir is not implemented
 
1240
            raise NotImplementedError(self.get_converter)
 
1241
        return ConvertMetaToMeta(format)
 
1242
 
 
1243
    def get_format_string(self):
 
1244
        """See BzrDirFormat.get_format_string()."""
 
1245
        return "Bazaar-NG meta directory, format 1\n"
 
1246
 
 
1247
    def get_format_description(self):
 
1248
        """See BzrDirFormat.get_format_description()."""
 
1249
        return "Meta directory format 1"
 
1250
 
 
1251
    def _open(self, transport):
 
1252
        """See BzrDirFormat._open."""
 
1253
        return BzrDirMeta1(transport, self)
 
1254
 
 
1255
    def __return_repository_format(self):
 
1256
        """Circular import protection."""
 
1257
        if getattr(self, '_repository_format', None):
 
1258
            return self._repository_format
 
1259
        from bzrlib.repository import RepositoryFormat
 
1260
        return RepositoryFormat.get_default_format()
 
1261
 
 
1262
    def __set_repository_format(self, value):
 
1263
        """Allow changint the repository format for metadir formats."""
 
1264
        self._repository_format = value
 
1265
 
 
1266
    repository_format = property(__return_repository_format, __set_repository_format)
 
1267
 
 
1268
 
 
1269
BzrDirFormat.register_format(BzrDirFormat4())
 
1270
BzrDirFormat.register_format(BzrDirFormat5())
 
1271
BzrDirFormat.register_format(BzrDirFormat6())
 
1272
__default_format = BzrDirMetaFormat1()
 
1273
BzrDirFormat.register_format(__default_format)
 
1274
BzrDirFormat.set_default_format(__default_format)
 
1275
 
 
1276
 
 
1277
class BzrDirTestProviderAdapter(object):
 
1278
    """A tool to generate a suite testing multiple bzrdir formats at once.
 
1279
 
 
1280
    This is done by copying the test once for each transport and injecting
 
1281
    the transport_server, transport_readonly_server, and bzrdir_format
 
1282
    classes into each copy. Each copy is also given a new id() to make it
 
1283
    easy to identify.
 
1284
    """
 
1285
 
 
1286
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1287
        self._transport_server = transport_server
 
1288
        self._transport_readonly_server = transport_readonly_server
 
1289
        self._formats = formats
 
1290
    
 
1291
    def adapt(self, test):
 
1292
        result = TestSuite()
 
1293
        for format in self._formats:
 
1294
            new_test = deepcopy(test)
 
1295
            new_test.transport_server = self._transport_server
 
1296
            new_test.transport_readonly_server = self._transport_readonly_server
 
1297
            new_test.bzrdir_format = format
 
1298
            def make_new_test_id():
 
1299
                new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
 
1300
                return lambda: new_id
 
1301
            new_test.id = make_new_test_id()
 
1302
            result.addTest(new_test)
 
1303
        return result
 
1304
 
 
1305
 
 
1306
class ScratchDir(BzrDir6):
 
1307
    """Special test class: a bzrdir that cleans up itself..
 
1308
 
 
1309
    >>> d = ScratchDir()
 
1310
    >>> base = d.transport.base
 
1311
    >>> isdir(base)
 
1312
    True
 
1313
    >>> b.transport.__del__()
 
1314
    >>> isdir(base)
 
1315
    False
 
1316
    """
 
1317
 
 
1318
    def __init__(self, files=[], dirs=[], transport=None):
 
1319
        """Make a test branch.
 
1320
 
 
1321
        This creates a temporary directory and runs init-tree in it.
 
1322
 
 
1323
        If any files are listed, they are created in the working copy.
 
1324
        """
 
1325
        if transport is None:
 
1326
            transport = bzrlib.transport.local.ScratchTransport()
 
1327
            # local import for scope restriction
 
1328
            BzrDirFormat6().initialize(transport.base)
 
1329
            super(ScratchDir, self).__init__(transport, BzrDirFormat6())
 
1330
            self.create_repository()
 
1331
            self.create_branch()
 
1332
            self.create_workingtree()
 
1333
        else:
 
1334
            super(ScratchDir, self).__init__(transport, BzrDirFormat6())
 
1335
 
 
1336
        # BzrBranch creates a clone to .bzr and then forgets about the
 
1337
        # original transport. A ScratchTransport() deletes itself and
 
1338
        # everything underneath it when it goes away, so we need to
 
1339
        # grab a local copy to prevent that from happening
 
1340
        self._transport = transport
 
1341
 
 
1342
        for d in dirs:
 
1343
            self._transport.mkdir(d)
 
1344
            
 
1345
        for f in files:
 
1346
            self._transport.put(f, 'content of %s' % f)
 
1347
 
 
1348
    def clone(self):
 
1349
        """
 
1350
        >>> orig = ScratchDir(files=["file1", "file2"])
 
1351
        >>> os.listdir(orig.base)
 
1352
        [u'.bzr', u'file1', u'file2']
 
1353
        >>> clone = orig.clone()
 
1354
        >>> if os.name != 'nt':
 
1355
        ...   os.path.samefile(orig.base, clone.base)
 
1356
        ... else:
 
1357
        ...   orig.base == clone.base
 
1358
        ...
 
1359
        False
 
1360
        >>> os.listdir(clone.base)
 
1361
        [u'.bzr', u'file1', u'file2']
 
1362
        """
 
1363
        from shutil import copytree
 
1364
        from bzrlib.osutils import mkdtemp
 
1365
        base = mkdtemp()
 
1366
        os.rmdir(base)
 
1367
        copytree(self.base, base, symlinks=True)
 
1368
        return ScratchDir(
 
1369
            transport=bzrlib.transport.local.ScratchTransport(base))
 
1370
 
 
1371
 
 
1372
class Converter(object):
 
1373
    """Converts a disk format object from one format to another."""
 
1374
 
 
1375
    def convert(self, to_convert, pb):
 
1376
        """Perform the conversion of to_convert, giving feedback via pb.
 
1377
 
 
1378
        :param to_convert: The disk object to convert.
 
1379
        :param pb: a progress bar to use for progress information.
 
1380
        """
 
1381
 
 
1382
    def step(self, message):
 
1383
        """Update the pb by a step."""
 
1384
        self.count +=1
 
1385
        self.pb.update(message, self.count, self.total)
 
1386
 
 
1387
 
 
1388
class ConvertBzrDir4To5(Converter):
 
1389
    """Converts format 4 bzr dirs to format 5."""
 
1390
 
 
1391
    def __init__(self):
 
1392
        super(ConvertBzrDir4To5, self).__init__()
 
1393
        self.converted_revs = set()
 
1394
        self.absent_revisions = set()
 
1395
        self.text_count = 0
 
1396
        self.revisions = {}
 
1397
        
 
1398
    def convert(self, to_convert, pb):
 
1399
        """See Converter.convert()."""
 
1400
        self.bzrdir = to_convert
 
1401
        self.pb = pb
 
1402
        self.pb.note('starting upgrade from format 4 to 5')
 
1403
        if isinstance(self.bzrdir.transport, LocalTransport):
 
1404
            self.bzrdir.get_workingtree_transport(None).delete('stat-cache')
 
1405
        self._convert_to_weaves()
 
1406
        return BzrDir.open(self.bzrdir.root_transport.base)
 
1407
 
 
1408
    def _convert_to_weaves(self):
 
1409
        self.pb.note('note: upgrade may be faster if all store files are ungzipped first')
 
1410
        try:
 
1411
            # TODO permissions
 
1412
            stat = self.bzrdir.transport.stat('weaves')
 
1413
            if not S_ISDIR(stat.st_mode):
 
1414
                self.bzrdir.transport.delete('weaves')
 
1415
                self.bzrdir.transport.mkdir('weaves')
 
1416
        except errors.NoSuchFile:
 
1417
            self.bzrdir.transport.mkdir('weaves')
 
1418
        # deliberately not a WeaveFile as we want to build it up slowly.
 
1419
        self.inv_weave = Weave('inventory')
 
1420
        # holds in-memory weaves for all files
 
1421
        self.text_weaves = {}
 
1422
        self.bzrdir.transport.delete('branch-format')
 
1423
        self.branch = self.bzrdir.open_branch()
 
1424
        self._convert_working_inv()
 
1425
        rev_history = self.branch.revision_history()
 
1426
        # to_read is a stack holding the revisions we still need to process;
 
1427
        # appending to it adds new highest-priority revisions
 
1428
        self.known_revisions = set(rev_history)
 
1429
        self.to_read = rev_history[-1:]
 
1430
        while self.to_read:
 
1431
            rev_id = self.to_read.pop()
 
1432
            if (rev_id not in self.revisions
 
1433
                and rev_id not in self.absent_revisions):
 
1434
                self._load_one_rev(rev_id)
 
1435
        self.pb.clear()
 
1436
        to_import = self._make_order()
 
1437
        for i, rev_id in enumerate(to_import):
 
1438
            self.pb.update('converting revision', i, len(to_import))
 
1439
            self._convert_one_rev(rev_id)
 
1440
        self.pb.clear()
 
1441
        self._write_all_weaves()
 
1442
        self._write_all_revs()
 
1443
        self.pb.note('upgraded to weaves:')
 
1444
        self.pb.note('  %6d revisions and inventories', len(self.revisions))
 
1445
        self.pb.note('  %6d revisions not present', len(self.absent_revisions))
 
1446
        self.pb.note('  %6d texts', self.text_count)
 
1447
        self._cleanup_spare_files_after_format4()
 
1448
        self.branch.control_files.put_utf8('branch-format', BzrDirFormat5().get_format_string())
 
1449
 
 
1450
    def _cleanup_spare_files_after_format4(self):
 
1451
        # FIXME working tree upgrade foo.
 
1452
        for n in 'merged-patches', 'pending-merged-patches':
 
1453
            try:
 
1454
                ## assert os.path.getsize(p) == 0
 
1455
                self.bzrdir.transport.delete(n)
 
1456
            except errors.NoSuchFile:
 
1457
                pass
 
1458
        self.bzrdir.transport.delete_tree('inventory-store')
 
1459
        self.bzrdir.transport.delete_tree('text-store')
 
1460
 
 
1461
    def _convert_working_inv(self):
 
1462
        inv = serializer_v4.read_inventory(self.branch.control_files.get('inventory'))
 
1463
        new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
 
1464
        # FIXME inventory is a working tree change.
 
1465
        self.branch.control_files.put('inventory', new_inv_xml)
 
1466
 
 
1467
    def _write_all_weaves(self):
 
1468
        controlweaves = WeaveStore(self.bzrdir.transport, prefixed=False)
 
1469
        weave_transport = self.bzrdir.transport.clone('weaves')
 
1470
        weaves = WeaveStore(weave_transport, prefixed=False)
 
1471
        transaction = WriteTransaction()
 
1472
 
 
1473
        try:
 
1474
            i = 0
 
1475
            for file_id, file_weave in self.text_weaves.items():
 
1476
                self.pb.update('writing weave', i, len(self.text_weaves))
 
1477
                weaves._put_weave(file_id, file_weave, transaction)
 
1478
                i += 1
 
1479
            self.pb.update('inventory', 0, 1)
 
1480
            controlweaves._put_weave('inventory', self.inv_weave, transaction)
 
1481
            self.pb.update('inventory', 1, 1)
 
1482
        finally:
 
1483
            self.pb.clear()
 
1484
 
 
1485
    def _write_all_revs(self):
 
1486
        """Write all revisions out in new form."""
 
1487
        self.bzrdir.transport.delete_tree('revision-store')
 
1488
        self.bzrdir.transport.mkdir('revision-store')
 
1489
        revision_transport = self.bzrdir.transport.clone('revision-store')
 
1490
        # TODO permissions
 
1491
        _revision_store = TextRevisionStore(TextStore(revision_transport,
 
1492
                                                      prefixed=False,
 
1493
                                                      compressed=True))
 
1494
        try:
 
1495
            transaction = bzrlib.transactions.WriteTransaction()
 
1496
            for i, rev_id in enumerate(self.converted_revs):
 
1497
                self.pb.update('write revision', i, len(self.converted_revs))
 
1498
                _revision_store.add_revision(self.revisions[rev_id], transaction)
 
1499
        finally:
 
1500
            self.pb.clear()
 
1501
            
 
1502
    def _load_one_rev(self, rev_id):
 
1503
        """Load a revision object into memory.
 
1504
 
 
1505
        Any parents not either loaded or abandoned get queued to be
 
1506
        loaded."""
 
1507
        self.pb.update('loading revision',
 
1508
                       len(self.revisions),
 
1509
                       len(self.known_revisions))
 
1510
        if not self.branch.repository.has_revision(rev_id):
 
1511
            self.pb.clear()
 
1512
            self.pb.note('revision {%s} not present in branch; '
 
1513
                         'will be converted as a ghost',
 
1514
                         rev_id)
 
1515
            self.absent_revisions.add(rev_id)
 
1516
        else:
 
1517
            rev = self.branch.repository._revision_store.get_revision(rev_id,
 
1518
                self.branch.repository.get_transaction())
 
1519
            for parent_id in rev.parent_ids:
 
1520
                self.known_revisions.add(parent_id)
 
1521
                self.to_read.append(parent_id)
 
1522
            self.revisions[rev_id] = rev
 
1523
 
 
1524
    def _load_old_inventory(self, rev_id):
 
1525
        assert rev_id not in self.converted_revs
 
1526
        old_inv_xml = self.branch.repository.inventory_store.get(rev_id).read()
 
1527
        inv = serializer_v4.read_inventory_from_string(old_inv_xml)
 
1528
        rev = self.revisions[rev_id]
 
1529
        if rev.inventory_sha1:
 
1530
            assert rev.inventory_sha1 == sha_string(old_inv_xml), \
 
1531
                'inventory sha mismatch for {%s}' % rev_id
 
1532
        return inv
 
1533
 
 
1534
    def _load_updated_inventory(self, rev_id):
 
1535
        assert rev_id in self.converted_revs
 
1536
        inv_xml = self.inv_weave.get_text(rev_id)
 
1537
        inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(inv_xml)
 
1538
        return inv
 
1539
 
 
1540
    def _convert_one_rev(self, rev_id):
 
1541
        """Convert revision and all referenced objects to new format."""
 
1542
        rev = self.revisions[rev_id]
 
1543
        inv = self._load_old_inventory(rev_id)
 
1544
        present_parents = [p for p in rev.parent_ids
 
1545
                           if p not in self.absent_revisions]
 
1546
        self._convert_revision_contents(rev, inv, present_parents)
 
1547
        self._store_new_weave(rev, inv, present_parents)
 
1548
        self.converted_revs.add(rev_id)
 
1549
 
 
1550
    def _store_new_weave(self, rev, inv, present_parents):
 
1551
        # the XML is now updated with text versions
 
1552
        if __debug__:
 
1553
            for file_id in inv:
 
1554
                ie = inv[file_id]
 
1555
                if ie.kind == 'root_directory':
 
1556
                    continue
 
1557
                assert hasattr(ie, 'revision'), \
 
1558
                    'no revision on {%s} in {%s}' % \
 
1559
                    (file_id, rev.revision_id)
 
1560
        new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
 
1561
        new_inv_sha1 = sha_string(new_inv_xml)
 
1562
        self.inv_weave.add_lines(rev.revision_id, 
 
1563
                                 present_parents,
 
1564
                                 new_inv_xml.splitlines(True))
 
1565
        rev.inventory_sha1 = new_inv_sha1
 
1566
 
 
1567
    def _convert_revision_contents(self, rev, inv, present_parents):
 
1568
        """Convert all the files within a revision.
 
1569
 
 
1570
        Also upgrade the inventory to refer to the text revision ids."""
 
1571
        rev_id = rev.revision_id
 
1572
        mutter('converting texts of revision {%s}',
 
1573
               rev_id)
 
1574
        parent_invs = map(self._load_updated_inventory, present_parents)
 
1575
        for file_id in inv:
 
1576
            ie = inv[file_id]
 
1577
            self._convert_file_version(rev, ie, parent_invs)
 
1578
 
 
1579
    def _convert_file_version(self, rev, ie, parent_invs):
 
1580
        """Convert one version of one file.
 
1581
 
 
1582
        The file needs to be added into the weave if it is a merge
 
1583
        of >=2 parents or if it's changed from its parent.
 
1584
        """
 
1585
        if ie.kind == 'root_directory':
 
1586
            return
 
1587
        file_id = ie.file_id
 
1588
        rev_id = rev.revision_id
 
1589
        w = self.text_weaves.get(file_id)
 
1590
        if w is None:
 
1591
            w = Weave(file_id)
 
1592
            self.text_weaves[file_id] = w
 
1593
        text_changed = False
 
1594
        previous_entries = ie.find_previous_heads(parent_invs,
 
1595
                                                  None,
 
1596
                                                  None,
 
1597
                                                  entry_vf=w)
 
1598
        for old_revision in previous_entries:
 
1599
                # if this fails, its a ghost ?
 
1600
                assert old_revision in self.converted_revs 
 
1601
        self.snapshot_ie(previous_entries, ie, w, rev_id)
 
1602
        del ie.text_id
 
1603
        assert getattr(ie, 'revision', None) is not None
 
1604
 
 
1605
    def snapshot_ie(self, previous_revisions, ie, w, rev_id):
 
1606
        # TODO: convert this logic, which is ~= snapshot to
 
1607
        # a call to:. This needs the path figured out. rather than a work_tree
 
1608
        # a v4 revision_tree can be given, or something that looks enough like
 
1609
        # one to give the file content to the entry if it needs it.
 
1610
        # and we need something that looks like a weave store for snapshot to 
 
1611
        # save against.
 
1612
        #ie.snapshot(rev, PATH, previous_revisions, REVISION_TREE, InMemoryWeaveStore(self.text_weaves))
 
1613
        if len(previous_revisions) == 1:
 
1614
            previous_ie = previous_revisions.values()[0]
 
1615
            if ie._unchanged(previous_ie):
 
1616
                ie.revision = previous_ie.revision
 
1617
                return
 
1618
        if ie.has_text():
 
1619
            text = self.branch.repository.text_store.get(ie.text_id)
 
1620
            file_lines = text.readlines()
 
1621
            assert sha_strings(file_lines) == ie.text_sha1
 
1622
            assert sum(map(len, file_lines)) == ie.text_size
 
1623
            w.add_lines(rev_id, previous_revisions, file_lines)
 
1624
            self.text_count += 1
 
1625
        else:
 
1626
            w.add_lines(rev_id, previous_revisions, [])
 
1627
        ie.revision = rev_id
 
1628
 
 
1629
    def _make_order(self):
 
1630
        """Return a suitable order for importing revisions.
 
1631
 
 
1632
        The order must be such that an revision is imported after all
 
1633
        its (present) parents.
 
1634
        """
 
1635
        todo = set(self.revisions.keys())
 
1636
        done = self.absent_revisions.copy()
 
1637
        order = []
 
1638
        while todo:
 
1639
            # scan through looking for a revision whose parents
 
1640
            # are all done
 
1641
            for rev_id in sorted(list(todo)):
 
1642
                rev = self.revisions[rev_id]
 
1643
                parent_ids = set(rev.parent_ids)
 
1644
                if parent_ids.issubset(done):
 
1645
                    # can take this one now
 
1646
                    order.append(rev_id)
 
1647
                    todo.remove(rev_id)
 
1648
                    done.add(rev_id)
 
1649
        return order
 
1650
 
 
1651
 
 
1652
class ConvertBzrDir5To6(Converter):
 
1653
    """Converts format 5 bzr dirs to format 6."""
 
1654
 
 
1655
    def convert(self, to_convert, pb):
 
1656
        """See Converter.convert()."""
 
1657
        self.bzrdir = to_convert
 
1658
        self.pb = pb
 
1659
        self.pb.note('starting upgrade from format 5 to 6')
 
1660
        self._convert_to_prefixed()
 
1661
        return BzrDir.open(self.bzrdir.root_transport.base)
 
1662
 
 
1663
    def _convert_to_prefixed(self):
 
1664
        from bzrlib.store import TransportStore
 
1665
        self.bzrdir.transport.delete('branch-format')
 
1666
        for store_name in ["weaves", "revision-store"]:
 
1667
            self.pb.note("adding prefixes to %s" % store_name)
 
1668
            store_transport = self.bzrdir.transport.clone(store_name)
 
1669
            store = TransportStore(store_transport, prefixed=True)
 
1670
            for urlfilename in store_transport.list_dir('.'):
 
1671
                filename = urlunescape(urlfilename)
 
1672
                if (filename.endswith(".weave") or
 
1673
                    filename.endswith(".gz") or
 
1674
                    filename.endswith(".sig")):
 
1675
                    file_id = os.path.splitext(filename)[0]
 
1676
                else:
 
1677
                    file_id = filename
 
1678
                prefix_dir = store.hash_prefix(file_id)
 
1679
                # FIXME keep track of the dirs made RBC 20060121
 
1680
                try:
 
1681
                    store_transport.move(filename, prefix_dir + '/' + filename)
 
1682
                except errors.NoSuchFile: # catches missing dirs strangely enough
 
1683
                    store_transport.mkdir(prefix_dir)
 
1684
                    store_transport.move(filename, prefix_dir + '/' + filename)
 
1685
        self.bzrdir._control_files.put_utf8('branch-format', BzrDirFormat6().get_format_string())
 
1686
 
 
1687
 
 
1688
class ConvertBzrDir6ToMeta(Converter):
 
1689
    """Converts format 6 bzr dirs to metadirs."""
 
1690
 
 
1691
    def convert(self, to_convert, pb):
 
1692
        """See Converter.convert()."""
 
1693
        self.bzrdir = to_convert
 
1694
        self.pb = pb
 
1695
        self.count = 0
 
1696
        self.total = 20 # the steps we know about
 
1697
        self.garbage_inventories = []
 
1698
 
 
1699
        self.pb.note('starting upgrade from format 6 to metadir')
 
1700
        self.bzrdir._control_files.put_utf8('branch-format', "Converting to format 6")
 
1701
        # its faster to move specific files around than to open and use the apis...
 
1702
        # first off, nuke ancestry.weave, it was never used.
 
1703
        try:
 
1704
            self.step('Removing ancestry.weave')
 
1705
            self.bzrdir.transport.delete('ancestry.weave')
 
1706
        except errors.NoSuchFile:
 
1707
            pass
 
1708
        # find out whats there
 
1709
        self.step('Finding branch files')
 
1710
        last_revision = self.bzrdir.open_branch().last_revision()
 
1711
        bzrcontents = self.bzrdir.transport.list_dir('.')
 
1712
        for name in bzrcontents:
 
1713
            if name.startswith('basis-inventory.'):
 
1714
                self.garbage_inventories.append(name)
 
1715
        # create new directories for repository, working tree and branch
 
1716
        self.dir_mode = self.bzrdir._control_files._dir_mode
 
1717
        self.file_mode = self.bzrdir._control_files._file_mode
 
1718
        repository_names = [('inventory.weave', True),
 
1719
                            ('revision-store', True),
 
1720
                            ('weaves', True)]
 
1721
        self.step('Upgrading repository  ')
 
1722
        self.bzrdir.transport.mkdir('repository', mode=self.dir_mode)
 
1723
        self.make_lock('repository')
 
1724
        # we hard code the formats here because we are converting into
 
1725
        # the meta format. The meta format upgrader can take this to a 
 
1726
        # future format within each component.
 
1727
        self.put_format('repository', bzrlib.repository.RepositoryFormat7())
 
1728
        for entry in repository_names:
 
1729
            self.move_entry('repository', entry)
 
1730
 
 
1731
        self.step('Upgrading branch      ')
 
1732
        self.bzrdir.transport.mkdir('branch', mode=self.dir_mode)
 
1733
        self.make_lock('branch')
 
1734
        self.put_format('branch', bzrlib.branch.BzrBranchFormat5())
 
1735
        branch_files = [('revision-history', True),
 
1736
                        ('branch-name', True),
 
1737
                        ('parent', False)]
 
1738
        for entry in branch_files:
 
1739
            self.move_entry('branch', entry)
 
1740
 
 
1741
        self.step('Upgrading working tree')
 
1742
        self.bzrdir.transport.mkdir('checkout', mode=self.dir_mode)
 
1743
        self.make_lock('checkout')
 
1744
        self.put_format('checkout', bzrlib.workingtree.WorkingTreeFormat3())
 
1745
        self.bzrdir.transport.delete_multi(self.garbage_inventories, self.pb)
 
1746
        checkout_files = [('pending-merges', True),
 
1747
                          ('inventory', True),
 
1748
                          ('stat-cache', False)]
 
1749
        for entry in checkout_files:
 
1750
            self.move_entry('checkout', entry)
 
1751
        if last_revision is not None:
 
1752
            self.bzrdir._control_files.put_utf8('checkout/last-revision',
 
1753
                                                last_revision)
 
1754
        self.bzrdir._control_files.put_utf8('branch-format', BzrDirMetaFormat1().get_format_string())
 
1755
        return BzrDir.open(self.bzrdir.root_transport.base)
 
1756
 
 
1757
    def make_lock(self, name):
 
1758
        """Make a lock for the new control dir name."""
 
1759
        self.step('Make %s lock' % name)
 
1760
        ld = LockDir(self.bzrdir.transport, 
 
1761
                     '%s/lock' % name,
 
1762
                     file_modebits=self.file_mode,
 
1763
                     dir_modebits=self.dir_mode)
 
1764
        ld.create()
 
1765
 
 
1766
    def move_entry(self, new_dir, entry):
 
1767
        """Move then entry name into new_dir."""
 
1768
        name = entry[0]
 
1769
        mandatory = entry[1]
 
1770
        self.step('Moving %s' % name)
 
1771
        try:
 
1772
            self.bzrdir.transport.move(name, '%s/%s' % (new_dir, name))
 
1773
        except errors.NoSuchFile:
 
1774
            if mandatory:
 
1775
                raise
 
1776
 
 
1777
    def put_format(self, dirname, format):
 
1778
        self.bzrdir._control_files.put_utf8('%s/format' % dirname, format.get_format_string())
 
1779
 
 
1780
 
 
1781
class ConvertMetaToMeta(Converter):
 
1782
    """Converts the components of metadirs."""
 
1783
 
 
1784
    def __init__(self, target_format):
 
1785
        """Create a metadir to metadir converter.
 
1786
 
 
1787
        :param target_format: The final metadir format that is desired.
 
1788
        """
 
1789
        self.target_format = target_format
 
1790
 
 
1791
    def convert(self, to_convert, pb):
 
1792
        """See Converter.convert()."""
 
1793
        self.bzrdir = to_convert
 
1794
        self.pb = pb
 
1795
        self.count = 0
 
1796
        self.total = 1
 
1797
        self.step('checking repository format')
 
1798
        try:
 
1799
            repo = self.bzrdir.open_repository()
 
1800
        except errors.NoRepositoryPresent:
 
1801
            pass
 
1802
        else:
 
1803
            if not isinstance(repo._format, self.target_format.repository_format.__class__):
 
1804
                from bzrlib.repository import CopyConverter
 
1805
                self.pb.note('starting repository conversion')
 
1806
                converter = CopyConverter(self.target_format.repository_format)
 
1807
                converter.convert(repo, pb)
 
1808
        return to_convert