/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: Martin Pool
  • Date: 2006-06-05 18:00:36 UTC
  • mto: This revision was merged to the branch mainline in revision 1752.
  • Revision ID: mbp@sourcefrog.net-20060605180036-04f5d0cea94ed999
clean up test kipple

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