/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

Merge from bzr.dev

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