~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bzrdir.py

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