/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/bzrdir.py

  • Committer: Martin Pool
  • Date: 2006-03-08 08:07:38 UTC
  • mto: This revision was merged to the branch mainline in revision 1602.
  • Revision ID: mbp@sourcefrog.net-20060308080738-bc96b3fb717e72a6
doc

Show diffs side-by-side

added added

removed removed

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