/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/bzrdir.py

  • Committer: Aaron Bentley
  • Date: 2006-08-17 19:51:54 UTC
  • mto: (1910.2.43 format-bumps)
  • mto: This revision was merged to the branch mainline in revision 1997.
  • Revision ID: abentley@panoramicfeedback.com-20060817195154-af960bfc59351ebf
Implement knit repo format 2

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