1
# Copyright (C) 2005, 2006 Canonical Ltd
 
 
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.
 
 
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.
 
 
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
 
 
17
"""BzrDir logic. The BzrDir is the basic control directory used by bzr.
 
 
19
At format 7 this was split out into Branch, Repository and Checkout control
 
 
23
# TODO: remove unittest dependency; put that stuff inside the test suite
 
 
25
from copy import deepcopy
 
 
26
from cStringIO import StringIO
 
 
28
from stat import S_ISDIR
 
 
29
from unittest import TestSuite
 
 
32
import bzrlib.errors as errors
 
 
33
from bzrlib.lockable_files import LockableFiles, TransportLock
 
 
34
from bzrlib.lockdir import LockDir
 
 
35
from bzrlib.osutils import (
 
 
42
from bzrlib.store.revision.text import TextRevisionStore
 
 
43
from bzrlib.store.text import TextStore
 
 
44
from bzrlib.store.versioned import WeaveStore
 
 
45
from bzrlib.trace import mutter
 
 
46
from bzrlib.transactions import WriteTransaction
 
 
47
from bzrlib.transport import get_transport
 
 
48
from bzrlib.transport.local import LocalTransport
 
 
49
import bzrlib.urlutils as urlutils
 
 
50
from bzrlib.weave import Weave
 
 
51
from bzrlib.xml4 import serializer_v4
 
 
56
    """A .bzr control diretory.
 
 
58
    BzrDir instances let you create or open any of the things that can be
 
 
59
    found within .bzr - checkouts, branches and repositories.
 
 
62
        the transport which this bzr dir is rooted at (i.e. file:///.../.bzr/)
 
 
64
        a transport connected to the directory this bzr was opened from.
 
 
68
        """Invoke break_lock on the first object in the bzrdir.
 
 
70
        If there is a tree, the tree is opened and break_lock() called.
 
 
71
        Otherwise, branch is tried, and finally repository.
 
 
74
            thing_to_unlock = self.open_workingtree()
 
 
75
        except (errors.NotLocalUrl, errors.NoWorkingTree):
 
 
77
                thing_to_unlock = self.open_branch()
 
 
78
            except errors.NotBranchError:
 
 
80
                    thing_to_unlock = self.open_repository()
 
 
81
                except errors.NoRepositoryPresent:
 
 
83
        thing_to_unlock.break_lock()
 
 
85
    def can_convert_format(self):
 
 
86
        """Return true if this bzrdir is one whose format we can convert from."""
 
 
90
    def _check_supported(format, allow_unsupported):
 
 
91
        """Check whether format is a supported format.
 
 
93
        If allow_unsupported is True, this is a no-op.
 
 
95
        if not allow_unsupported and not format.is_supported():
 
 
96
            # see open_downlevel to open legacy branches.
 
 
97
            raise errors.UnsupportedFormatError(format=format)
 
 
99
    def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
 
 
100
        """Clone this bzrdir and its contents to url verbatim.
 
 
102
        If urls last component does not exist, it will be created.
 
 
104
        if revision_id is not None, then the clone operation may tune
 
 
105
            itself to download less data.
 
 
106
        :param force_new_repo: Do not use a shared repository for the target 
 
 
107
                               even if one is available.
 
 
110
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
 
111
        result = self._format.initialize(url)
 
 
113
            local_repo = self.find_repository()
 
 
114
        except errors.NoRepositoryPresent:
 
 
117
            # may need to copy content in
 
 
119
                result_repo = local_repo.clone(
 
 
121
                    revision_id=revision_id,
 
 
123
                result_repo.set_make_working_trees(local_repo.make_working_trees())
 
 
126
                    result_repo = result.find_repository()
 
 
127
                    # fetch content this dir needs.
 
 
129
                        # XXX FIXME RBC 20060214 need tests for this when the basis
 
 
131
                        result_repo.fetch(basis_repo, revision_id=revision_id)
 
 
132
                    result_repo.fetch(local_repo, revision_id=revision_id)
 
 
133
                except errors.NoRepositoryPresent:
 
 
134
                    # needed to make one anyway.
 
 
135
                    result_repo = local_repo.clone(
 
 
137
                        revision_id=revision_id,
 
 
139
                    result_repo.set_make_working_trees(local_repo.make_working_trees())
 
 
140
        # 1 if there is a branch present
 
 
141
        #   make sure its content is available in the target repository
 
 
144
            self.open_branch().clone(result, revision_id=revision_id)
 
 
145
        except errors.NotBranchError:
 
 
148
            self.open_workingtree().clone(result, basis=basis_tree)
 
 
149
        except (errors.NoWorkingTree, errors.NotLocalUrl):
 
 
153
    def _get_basis_components(self, basis):
 
 
154
        """Retrieve the basis components that are available at basis."""
 
 
156
            return None, None, None
 
 
158
            basis_tree = basis.open_workingtree()
 
 
159
            basis_branch = basis_tree.branch
 
 
160
            basis_repo = basis_branch.repository
 
 
161
        except (errors.NoWorkingTree, errors.NotLocalUrl):
 
 
164
                basis_branch = basis.open_branch()
 
 
165
                basis_repo = basis_branch.repository
 
 
166
            except errors.NotBranchError:
 
 
169
                    basis_repo = basis.open_repository()
 
 
170
                except errors.NoRepositoryPresent:
 
 
172
        return basis_repo, basis_branch, basis_tree
 
 
174
    # TODO: This should be given a Transport, and should chdir up; otherwise
 
 
175
    # this will open a new connection.
 
 
176
    def _make_tail(self, url):
 
 
177
        head, tail = urlutils.split(url)
 
 
178
        if tail and tail != '.':
 
 
179
            t = bzrlib.transport.get_transport(head)
 
 
182
            except errors.FileExists:
 
 
185
    # TODO: Should take a Transport
 
 
187
    def create(cls, base):
 
 
188
        """Create a new BzrDir at the url 'base'.
 
 
190
        This will call the current default formats initialize with base
 
 
191
        as the only parameter.
 
 
193
        If you need a specific format, consider creating an instance
 
 
194
        of that and calling initialize().
 
 
196
        if cls is not BzrDir:
 
 
197
            raise AssertionError("BzrDir.create always creates the default format, "
 
 
198
                    "not one of %r" % cls)
 
 
199
        head, tail = urlutils.split(base)
 
 
200
        if tail and tail != '.':
 
 
201
            t = bzrlib.transport.get_transport(head)
 
 
204
            except errors.FileExists:
 
 
206
        return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
 
 
208
    def create_branch(self):
 
 
209
        """Create a branch in this BzrDir.
 
 
211
        The bzrdirs format will control what branch format is created.
 
 
212
        For more control see BranchFormatXX.create(a_bzrdir).
 
 
214
        raise NotImplementedError(self.create_branch)
 
 
217
    def create_branch_and_repo(base, force_new_repo=False):
 
 
218
        """Create a new BzrDir, Branch and Repository at the url 'base'.
 
 
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
 
 
225
        The created Branch object is returned.
 
 
227
        :param base: The URL to create the branch at.
 
 
228
        :param force_new_repo: If True a new repository is always created.
 
 
230
        bzrdir = BzrDir.create(base)
 
 
231
        bzrdir._find_or_create_repository(force_new_repo)
 
 
232
        return bzrdir.create_branch()
 
 
234
    def _find_or_create_repository(self, force_new_repo):
 
 
235
        """Create a new repository if needed, returning the repository."""
 
 
237
            return self.create_repository()
 
 
239
            return self.find_repository()
 
 
240
        except errors.NoRepositoryPresent:
 
 
241
            return self.create_repository()
 
 
244
    def create_branch_convenience(base, force_new_repo=False,
 
 
245
                                  force_new_tree=None, format=None):
 
 
246
        """Create a new BzrDir, Branch and Repository at the url 'base'.
 
 
248
        This is a convenience function - it will use an existing repository
 
 
249
        if possible, can be told explicitly whether to create a working tree or
 
 
252
        This will use the current default BzrDirFormat, and use whatever 
 
 
253
        repository format that that uses via bzrdir.create_branch and
 
 
254
        create_repository. If a shared repository is available that is used
 
 
255
        preferentially. Whatever repository is used, its tree creation policy
 
 
258
        The created Branch object is returned.
 
 
259
        If a working tree cannot be made due to base not being a file:// url,
 
 
260
        no error is raised unless force_new_tree is True, in which case no 
 
 
261
        data is created on disk and NotLocalUrl is raised.
 
 
263
        :param base: The URL to create the branch at.
 
 
264
        :param force_new_repo: If True a new repository is always created.
 
 
265
        :param force_new_tree: If True or False force creation of a tree or 
 
 
266
                               prevent such creation respectively.
 
 
267
        :param format: Override for the for the bzrdir format to create
 
 
270
            # check for non local urls
 
 
271
            t = get_transport(safe_unicode(base))
 
 
272
            if not isinstance(t, LocalTransport):
 
 
273
                raise errors.NotLocalUrl(base)
 
 
275
            bzrdir = BzrDir.create(base)
 
 
277
            bzrdir = format.initialize(base)
 
 
278
        repo = bzrdir._find_or_create_repository(force_new_repo)
 
 
279
        result = bzrdir.create_branch()
 
 
280
        if force_new_tree or (repo.make_working_trees() and 
 
 
281
                              force_new_tree is None):
 
 
283
                bzrdir.create_workingtree()
 
 
284
            except errors.NotLocalUrl:
 
 
289
    def create_repository(base, shared=False):
 
 
290
        """Create a new BzrDir and Repository at the url 'base'.
 
 
292
        This will use the current default BzrDirFormat, and use whatever 
 
 
293
        repository format that that uses for bzrdirformat.create_repository.
 
 
295
        ;param shared: Create a shared repository rather than a standalone
 
 
297
        The Repository object is returned.
 
 
299
        This must be overridden as an instance method in child classes, where
 
 
300
        it should take no parameters and construct whatever repository format
 
 
301
        that child class desires.
 
 
303
        bzrdir = BzrDir.create(base)
 
 
304
        return bzrdir.create_repository(shared)
 
 
307
    def create_standalone_workingtree(base):
 
 
308
        """Create a new BzrDir, WorkingTree, Branch and Repository at 'base'.
 
 
310
        'base' must be a local path or a file:// url.
 
 
312
        This will use the current default BzrDirFormat, and use whatever 
 
 
313
        repository format that that uses for bzrdirformat.create_workingtree,
 
 
314
        create_branch and create_repository.
 
 
316
        The WorkingTree object is returned.
 
 
318
        t = get_transport(safe_unicode(base))
 
 
319
        if not isinstance(t, LocalTransport):
 
 
320
            raise errors.NotLocalUrl(base)
 
 
321
        bzrdir = BzrDir.create_branch_and_repo(safe_unicode(base),
 
 
322
                                               force_new_repo=True).bzrdir
 
 
323
        return bzrdir.create_workingtree()
 
 
325
    def create_workingtree(self, revision_id=None):
 
 
326
        """Create a working tree at this BzrDir.
 
 
328
        revision_id: create it as of this revision id.
 
 
330
        raise NotImplementedError(self.create_workingtree)
 
 
332
    def find_repository(self):
 
 
333
        """Find the repository that should be used for a_bzrdir.
 
 
335
        This does not require a branch as we use it to find the repo for
 
 
336
        new branches as well as to hook existing branches up to their
 
 
340
            return self.open_repository()
 
 
341
        except errors.NoRepositoryPresent:
 
 
343
        next_transport = self.root_transport.clone('..')
 
 
345
            # find the next containing bzrdir
 
 
347
                found_bzrdir = BzrDir.open_containing_from_transport(
 
 
349
            except errors.NotBranchError:
 
 
351
                raise errors.NoRepositoryPresent(self)
 
 
352
            # does it have a repository ?
 
 
354
                repository = found_bzrdir.open_repository()
 
 
355
            except errors.NoRepositoryPresent:
 
 
356
                next_transport = found_bzrdir.root_transport.clone('..')
 
 
357
                if (found_bzrdir.root_transport.base == next_transport.base):
 
 
358
                    # top of the file system
 
 
362
            if ((found_bzrdir.root_transport.base == 
 
 
363
                 self.root_transport.base) or repository.is_shared()):
 
 
366
                raise errors.NoRepositoryPresent(self)
 
 
367
        raise errors.NoRepositoryPresent(self)
 
 
369
    def get_branch_transport(self, branch_format):
 
 
370
        """Get the transport for use by branch format in this BzrDir.
 
 
372
        Note that bzr dirs that do not support format strings will raise
 
 
373
        IncompatibleFormat if the branch format they are given has
 
 
374
        a format string, and vice versa.
 
 
376
        If branch_format is None, the transport is returned with no 
 
 
377
        checking. if it is not None, then the returned transport is
 
 
378
        guaranteed to point to an existing directory ready for use.
 
 
380
        raise NotImplementedError(self.get_branch_transport)
 
 
382
    def get_repository_transport(self, repository_format):
 
 
383
        """Get the transport for use by repository format in this BzrDir.
 
 
385
        Note that bzr dirs that do not support format strings will raise
 
 
386
        IncompatibleFormat if the repository format they are given has
 
 
387
        a format string, and vice versa.
 
 
389
        If repository_format is None, the transport is returned with no 
 
 
390
        checking. if it is not None, then the returned transport is
 
 
391
        guaranteed to point to an existing directory ready for use.
 
 
393
        raise NotImplementedError(self.get_repository_transport)
 
 
395
    def get_workingtree_transport(self, tree_format):
 
 
396
        """Get the transport for use by workingtree format in this BzrDir.
 
 
398
        Note that bzr dirs that do not support format strings will raise
 
 
399
        IncompatibleFormat if the workingtree format they are given has
 
 
400
        a format string, and vice versa.
 
 
402
        If workingtree_format is None, the transport is returned with no 
 
 
403
        checking. if it is not None, then the returned transport is
 
 
404
        guaranteed to point to an existing directory ready for use.
 
 
406
        raise NotImplementedError(self.get_workingtree_transport)
 
 
408
    def __init__(self, _transport, _format):
 
 
409
        """Initialize a Bzr control dir object.
 
 
411
        Only really common logic should reside here, concrete classes should be
 
 
412
        made with varying behaviours.
 
 
414
        :param _format: the format that is creating this BzrDir instance.
 
 
415
        :param _transport: the transport this dir is based at.
 
 
417
        self._format = _format
 
 
418
        self.transport = _transport.clone('.bzr')
 
 
419
        self.root_transport = _transport
 
 
421
    def is_control_filename(self, filename):
 
 
422
        """True if filename is the name of a path which is reserved for bzrdir's.
 
 
424
        :param filename: A filename within the root transport of this bzrdir.
 
 
426
        This is true IF and ONLY IF the filename is part of the namespace reserved
 
 
427
        for bzr control dirs. Currently this is the '.bzr' directory in the root
 
 
428
        of the root_transport. it is expected that plugins will need to extend
 
 
429
        this in the future - for instance to make bzr talk with svn working
 
 
432
        # this might be better on the BzrDirFormat class because it refers to 
 
 
433
        # all the possible bzrdir disk formats. 
 
 
434
        # This method is tested via the workingtree is_control_filename tests- 
 
 
435
        # it was extracted from WorkingTree.is_control_filename. If the methods
 
 
436
        # contract is extended beyond the current trivial  implementation please
 
 
437
        # add new tests for it to the appropriate place.
 
 
438
        return filename == '.bzr' or filename.startswith('.bzr/')
 
 
440
    def needs_format_conversion(self, format=None):
 
 
441
        """Return true if this bzrdir needs convert_format run on it.
 
 
443
        For instance, if the repository format is out of date but the 
 
 
444
        branch and working tree are not, this should return True.
 
 
446
        :param format: Optional parameter indicating a specific desired
 
 
447
                       format we plan to arrive at.
 
 
449
        raise NotImplementedError(self.needs_format_conversion)
 
 
452
    def open_unsupported(base):
 
 
453
        """Open a branch which is not supported."""
 
 
454
        return BzrDir.open(base, _unsupported=True)
 
 
457
    def open(base, _unsupported=False):
 
 
458
        """Open an existing bzrdir, rooted at 'base' (url)
 
 
460
        _unsupported is a private parameter to the BzrDir class.
 
 
462
        t = get_transport(base)
 
 
463
        # mutter("trying to open %r with transport %r", base, t)
 
 
464
        format = BzrDirFormat.find_format(t)
 
 
465
        BzrDir._check_supported(format, _unsupported)
 
 
466
        return format.open(t, _found=True)
 
 
468
    def open_branch(self, unsupported=False):
 
 
469
        """Open the branch object at this BzrDir if one is present.
 
 
471
        If unsupported is True, then no longer supported branch formats can
 
 
474
        TODO: static convenience version of this?
 
 
476
        raise NotImplementedError(self.open_branch)
 
 
479
    def open_containing(url):
 
 
480
        """Open an existing branch which contains url.
 
 
482
        :param url: url to search from.
 
 
483
        See open_containing_from_transport for more detail.
 
 
485
        return BzrDir.open_containing_from_transport(get_transport(url))
 
 
488
    def open_containing_from_transport(a_transport):
 
 
489
        """Open an existing branch which contains a_transport.base
 
 
491
        This probes for a branch at a_transport, and searches upwards from there.
 
 
493
        Basically we keep looking up until we find the control directory or
 
 
494
        run into the root.  If there isn't one, raises NotBranchError.
 
 
495
        If there is one and it is either an unrecognised format or an unsupported 
 
 
496
        format, UnknownFormatError or UnsupportedFormatError are raised.
 
 
497
        If there is one, it is returned, along with the unused portion of url.
 
 
499
        :return: The BzrDir that contains the path, and a Unicode path 
 
 
500
                for the rest of the URL.
 
 
502
        # this gets the normalised url back. I.e. '.' -> the full path.
 
 
503
        url = a_transport.base
 
 
506
                format = BzrDirFormat.find_format(a_transport)
 
 
507
                BzrDir._check_supported(format, False)
 
 
508
                return format.open(a_transport), urlutils.unescape(a_transport.relpath(url))
 
 
509
            except errors.NotBranchError, e:
 
 
510
                ## mutter('not a branch in: %r %s', a_transport.base, e)
 
 
512
            new_t = a_transport.clone('..')
 
 
513
            if new_t.base == a_transport.base:
 
 
514
                # reached the root, whatever that may be
 
 
515
                raise errors.NotBranchError(path=url)
 
 
518
    def open_repository(self, _unsupported=False):
 
 
519
        """Open the repository object at this BzrDir if one is present.
 
 
521
        This will not follow the Branch object pointer - its strictly a direct
 
 
522
        open facility. Most client code should use open_branch().repository to
 
 
525
        _unsupported is a private parameter, not part of the api.
 
 
526
        TODO: static convenience version of this?
 
 
528
        raise NotImplementedError(self.open_repository)
 
 
530
    def open_workingtree(self, _unsupported=False):
 
 
531
        """Open the workingtree object at this BzrDir if one is present.
 
 
533
        TODO: static convenience version of this?
 
 
535
        raise NotImplementedError(self.open_workingtree)
 
 
537
    def has_branch(self):
 
 
538
        """Tell if this bzrdir contains a branch.
 
 
540
        Note: if you're going to open the branch, you should just go ahead
 
 
541
        and try, and not ask permission first.  (This method just opens the 
 
 
542
        branch and discards it, and that's somewhat expensive.) 
 
 
547
        except errors.NotBranchError:
 
 
550
    def has_workingtree(self):
 
 
551
        """Tell if this bzrdir contains a working tree.
 
 
553
        This will still raise an exception if the bzrdir has a workingtree that
 
 
554
        is remote & inaccessible.
 
 
556
        Note: if you're going to open the working tree, you should just go ahead
 
 
557
        and try, and not ask permission first.  (This method just opens the 
 
 
558
        workingtree and discards it, and that's somewhat expensive.) 
 
 
561
            self.open_workingtree()
 
 
563
        except errors.NoWorkingTree:
 
 
566
    def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
 
 
567
        """Create a copy of this bzrdir prepared for use as a new line of
 
 
570
        If urls last component does not exist, it will be created.
 
 
572
        Attributes related to the identity of the source branch like
 
 
573
        branch nickname will be cleaned, a working tree is created
 
 
574
        whether one existed before or not; and a local branch is always
 
 
577
        if revision_id is not None, then the clone operation may tune
 
 
578
            itself to download less data.
 
 
581
        result = self._format.initialize(url)
 
 
582
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
 
584
            source_branch = self.open_branch()
 
 
585
            source_repository = source_branch.repository
 
 
586
        except errors.NotBranchError:
 
 
589
                source_repository = self.open_repository()
 
 
590
            except errors.NoRepositoryPresent:
 
 
591
                # copy the entire basis one if there is one
 
 
592
                # but there is no repository.
 
 
593
                source_repository = basis_repo
 
 
598
                result_repo = result.find_repository()
 
 
599
            except errors.NoRepositoryPresent:
 
 
601
        if source_repository is None and result_repo is not None:
 
 
603
        elif source_repository is None and result_repo is None:
 
 
604
            # no repo available, make a new one
 
 
605
            result.create_repository()
 
 
606
        elif source_repository is not None and result_repo is None:
 
 
607
            # have source, and want to make a new target repo
 
 
608
            # we don't clone the repo because that preserves attributes
 
 
609
            # like is_shared(), and we have not yet implemented a 
 
 
610
            # repository sprout().
 
 
611
            result_repo = result.create_repository()
 
 
612
        if result_repo is not None:
 
 
613
            # fetch needed content into target.
 
 
615
                # XXX FIXME RBC 20060214 need tests for this when the basis
 
 
617
                result_repo.fetch(basis_repo, revision_id=revision_id)
 
 
618
            if source_repository is not None:
 
 
619
                result_repo.fetch(source_repository, revision_id=revision_id)
 
 
620
        if source_branch is not None:
 
 
621
            source_branch.sprout(result, revision_id=revision_id)
 
 
623
            result.create_branch()
 
 
624
        # TODO: jam 20060426 we probably need a test in here in the
 
 
625
        #       case that the newly sprouted branch is a remote one
 
 
626
        if result_repo is None or result_repo.make_working_trees():
 
 
627
            result.create_workingtree()
 
 
631
class BzrDirPreSplitOut(BzrDir):
 
 
632
    """A common class for the all-in-one formats."""
 
 
634
    def __init__(self, _transport, _format):
 
 
635
        """See BzrDir.__init__."""
 
 
636
        super(BzrDirPreSplitOut, self).__init__(_transport, _format)
 
 
637
        assert self._format._lock_class == TransportLock
 
 
638
        assert self._format._lock_file_name == 'branch-lock'
 
 
639
        self._control_files = LockableFiles(self.get_branch_transport(None),
 
 
640
                                            self._format._lock_file_name,
 
 
641
                                            self._format._lock_class)
 
 
643
    def break_lock(self):
 
 
644
        """Pre-splitout bzrdirs do not suffer from stale locks."""
 
 
645
        raise NotImplementedError(self.break_lock)
 
 
647
    def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
 
 
648
        """See BzrDir.clone()."""
 
 
649
        from bzrlib.workingtree import WorkingTreeFormat2
 
 
651
        result = self._format._initialize_for_clone(url)
 
 
652
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
 
653
        self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
 
 
654
        from_branch = self.open_branch()
 
 
655
        from_branch.clone(result, revision_id=revision_id)
 
 
657
            self.open_workingtree().clone(result, basis=basis_tree)
 
 
658
        except errors.NotLocalUrl:
 
 
659
            # make a new one, this format always has to have one.
 
 
661
                WorkingTreeFormat2().initialize(result)
 
 
662
            except errors.NotLocalUrl:
 
 
663
                # but we cannot do it for remote trees.
 
 
664
                to_branch = result.open_branch()
 
 
665
                WorkingTreeFormat2().stub_initialize_remote(to_branch.control_files)
 
 
668
    def create_branch(self):
 
 
669
        """See BzrDir.create_branch."""
 
 
670
        return self.open_branch()
 
 
672
    def create_repository(self, shared=False):
 
 
673
        """See BzrDir.create_repository."""
 
 
675
            raise errors.IncompatibleFormat('shared repository', self._format)
 
 
676
        return self.open_repository()
 
 
678
    def create_workingtree(self, revision_id=None):
 
 
679
        """See BzrDir.create_workingtree."""
 
 
680
        # this looks buggy but is not -really-
 
 
681
        # clone and sprout will have set the revision_id
 
 
682
        # and that will have set it for us, its only
 
 
683
        # specific uses of create_workingtree in isolation
 
 
684
        # that can do wonky stuff here, and that only
 
 
685
        # happens for creating checkouts, which cannot be 
 
 
686
        # done on this format anyway. So - acceptable wart.
 
 
687
        result = self.open_workingtree()
 
 
688
        if revision_id is not None:
 
 
689
            result.set_last_revision(revision_id)
 
 
692
    def get_branch_transport(self, branch_format):
 
 
693
        """See BzrDir.get_branch_transport()."""
 
 
694
        if branch_format is None:
 
 
695
            return self.transport
 
 
697
            branch_format.get_format_string()
 
 
698
        except NotImplementedError:
 
 
699
            return self.transport
 
 
700
        raise errors.IncompatibleFormat(branch_format, self._format)
 
 
702
    def get_repository_transport(self, repository_format):
 
 
703
        """See BzrDir.get_repository_transport()."""
 
 
704
        if repository_format is None:
 
 
705
            return self.transport
 
 
707
            repository_format.get_format_string()
 
 
708
        except NotImplementedError:
 
 
709
            return self.transport
 
 
710
        raise errors.IncompatibleFormat(repository_format, self._format)
 
 
712
    def get_workingtree_transport(self, workingtree_format):
 
 
713
        """See BzrDir.get_workingtree_transport()."""
 
 
714
        if workingtree_format is None:
 
 
715
            return self.transport
 
 
717
            workingtree_format.get_format_string()
 
 
718
        except NotImplementedError:
 
 
719
            return self.transport
 
 
720
        raise errors.IncompatibleFormat(workingtree_format, self._format)
 
 
722
    def needs_format_conversion(self, format=None):
 
 
723
        """See BzrDir.needs_format_conversion()."""
 
 
724
        # if the format is not the same as the system default,
 
 
725
        # an upgrade is needed.
 
 
727
            format = BzrDirFormat.get_default_format()
 
 
728
        return not isinstance(self._format, format.__class__)
 
 
730
    def open_branch(self, unsupported=False):
 
 
731
        """See BzrDir.open_branch."""
 
 
732
        from bzrlib.branch import BzrBranchFormat4
 
 
733
        format = BzrBranchFormat4()
 
 
734
        self._check_supported(format, unsupported)
 
 
735
        return format.open(self, _found=True)
 
 
737
    def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
 
 
738
        """See BzrDir.sprout()."""
 
 
739
        from bzrlib.workingtree import WorkingTreeFormat2
 
 
741
        result = self._format._initialize_for_clone(url)
 
 
742
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
 
744
            self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
 
 
745
        except errors.NoRepositoryPresent:
 
 
748
            self.open_branch().sprout(result, revision_id=revision_id)
 
 
749
        except errors.NotBranchError:
 
 
751
        # we always want a working tree
 
 
752
        WorkingTreeFormat2().initialize(result)
 
 
756
class BzrDir4(BzrDirPreSplitOut):
 
 
757
    """A .bzr version 4 control object.
 
 
759
    This is a deprecated format and may be removed after sept 2006.
 
 
762
    def create_repository(self, shared=False):
 
 
763
        """See BzrDir.create_repository."""
 
 
764
        return self._format.repository_format.initialize(self, shared)
 
 
766
    def needs_format_conversion(self, format=None):
 
 
767
        """Format 4 dirs are always in need of conversion."""
 
 
770
    def open_repository(self):
 
 
771
        """See BzrDir.open_repository."""
 
 
772
        from bzrlib.repository import RepositoryFormat4
 
 
773
        return RepositoryFormat4().open(self, _found=True)
 
 
776
class BzrDir5(BzrDirPreSplitOut):
 
 
777
    """A .bzr version 5 control object.
 
 
779
    This is a deprecated format and may be removed after sept 2006.
 
 
782
    def open_repository(self):
 
 
783
        """See BzrDir.open_repository."""
 
 
784
        from bzrlib.repository import RepositoryFormat5
 
 
785
        return RepositoryFormat5().open(self, _found=True)
 
 
787
    def open_workingtree(self, _unsupported=False):
 
 
788
        """See BzrDir.create_workingtree."""
 
 
789
        from bzrlib.workingtree import WorkingTreeFormat2
 
 
790
        return WorkingTreeFormat2().open(self, _found=True)
 
 
793
class BzrDir6(BzrDirPreSplitOut):
 
 
794
    """A .bzr version 6 control object.
 
 
796
    This is a deprecated format and may be removed after sept 2006.
 
 
799
    def open_repository(self):
 
 
800
        """See BzrDir.open_repository."""
 
 
801
        from bzrlib.repository import RepositoryFormat6
 
 
802
        return RepositoryFormat6().open(self, _found=True)
 
 
804
    def open_workingtree(self, _unsupported=False):
 
 
805
        """See BzrDir.create_workingtree."""
 
 
806
        from bzrlib.workingtree import WorkingTreeFormat2
 
 
807
        return WorkingTreeFormat2().open(self, _found=True)
 
 
810
class BzrDirMeta1(BzrDir):
 
 
811
    """A .bzr meta version 1 control object.
 
 
813
    This is the first control object where the 
 
 
814
    individual aspects are really split out: there are separate repository,
 
 
815
    workingtree and branch subdirectories and any subset of the three can be
 
 
816
    present within a BzrDir.
 
 
819
    def can_convert_format(self):
 
 
820
        """See BzrDir.can_convert_format()."""
 
 
823
    def create_branch(self):
 
 
824
        """See BzrDir.create_branch."""
 
 
825
        from bzrlib.branch import BranchFormat
 
 
826
        return BranchFormat.get_default_format().initialize(self)
 
 
828
    def create_repository(self, shared=False):
 
 
829
        """See BzrDir.create_repository."""
 
 
830
        return self._format.repository_format.initialize(self, shared)
 
 
832
    def create_workingtree(self, revision_id=None):
 
 
833
        """See BzrDir.create_workingtree."""
 
 
834
        from bzrlib.workingtree import WorkingTreeFormat
 
 
835
        return WorkingTreeFormat.get_default_format().initialize(self, revision_id)
 
 
837
    def _get_mkdir_mode(self):
 
 
838
        """Figure out the mode to use when creating a bzrdir subdir."""
 
 
839
        temp_control = LockableFiles(self.transport, '', TransportLock)
 
 
840
        return temp_control._dir_mode
 
 
842
    def get_branch_transport(self, branch_format):
 
 
843
        """See BzrDir.get_branch_transport()."""
 
 
844
        if branch_format is None:
 
 
845
            return self.transport.clone('branch')
 
 
847
            branch_format.get_format_string()
 
 
848
        except NotImplementedError:
 
 
849
            raise errors.IncompatibleFormat(branch_format, self._format)
 
 
851
            self.transport.mkdir('branch', mode=self._get_mkdir_mode())
 
 
852
        except errors.FileExists:
 
 
854
        return self.transport.clone('branch')
 
 
856
    def get_repository_transport(self, repository_format):
 
 
857
        """See BzrDir.get_repository_transport()."""
 
 
858
        if repository_format is None:
 
 
859
            return self.transport.clone('repository')
 
 
861
            repository_format.get_format_string()
 
 
862
        except NotImplementedError:
 
 
863
            raise errors.IncompatibleFormat(repository_format, self._format)
 
 
865
            self.transport.mkdir('repository', mode=self._get_mkdir_mode())
 
 
866
        except errors.FileExists:
 
 
868
        return self.transport.clone('repository')
 
 
870
    def get_workingtree_transport(self, workingtree_format):
 
 
871
        """See BzrDir.get_workingtree_transport()."""
 
 
872
        if workingtree_format is None:
 
 
873
            return self.transport.clone('checkout')
 
 
875
            workingtree_format.get_format_string()
 
 
876
        except NotImplementedError:
 
 
877
            raise errors.IncompatibleFormat(workingtree_format, self._format)
 
 
879
            self.transport.mkdir('checkout', mode=self._get_mkdir_mode())
 
 
880
        except errors.FileExists:
 
 
882
        return self.transport.clone('checkout')
 
 
884
    def needs_format_conversion(self, format=None):
 
 
885
        """See BzrDir.needs_format_conversion()."""
 
 
887
            format = BzrDirFormat.get_default_format()
 
 
888
        if not isinstance(self._format, format.__class__):
 
 
889
            # it is not a meta dir format, conversion is needed.
 
 
891
        # we might want to push this down to the repository?
 
 
893
            if not isinstance(self.open_repository()._format,
 
 
894
                              format.repository_format.__class__):
 
 
895
                # the repository needs an upgrade.
 
 
897
        except errors.NoRepositoryPresent:
 
 
899
        # currently there are no other possible conversions for meta1 formats.
 
 
902
    def open_branch(self, unsupported=False):
 
 
903
        """See BzrDir.open_branch."""
 
 
904
        from bzrlib.branch import BranchFormat
 
 
905
        format = BranchFormat.find_format(self)
 
 
906
        self._check_supported(format, unsupported)
 
 
907
        return format.open(self, _found=True)
 
 
909
    def open_repository(self, unsupported=False):
 
 
910
        """See BzrDir.open_repository."""
 
 
911
        from bzrlib.repository import RepositoryFormat
 
 
912
        format = RepositoryFormat.find_format(self)
 
 
913
        self._check_supported(format, unsupported)
 
 
914
        return format.open(self, _found=True)
 
 
916
    def open_workingtree(self, unsupported=False):
 
 
917
        """See BzrDir.open_workingtree."""
 
 
918
        from bzrlib.workingtree import WorkingTreeFormat
 
 
919
        format = WorkingTreeFormat.find_format(self)
 
 
920
        self._check_supported(format, unsupported)
 
 
921
        return format.open(self, _found=True)
 
 
924
class BzrDirFormat(object):
 
 
925
    """An encapsulation of the initialization and open routines for a format.
 
 
927
    Formats provide three things:
 
 
928
     * An initialization routine,
 
 
932
    Formats are placed in an dict by their format string for reference 
 
 
933
    during bzrdir opening. These should be subclasses of BzrDirFormat
 
 
936
    Once a format is deprecated, just deprecate the initialize and open
 
 
937
    methods on the format class. Do not deprecate the object, as the 
 
 
938
    object will be created every system load.
 
 
941
    _default_format = None
 
 
942
    """The default format used for new .bzr dirs."""
 
 
945
    """The known formats."""
 
 
947
    _control_formats = []
 
 
948
    """The registered control formats - .bzr, ....
 
 
950
    This is a list of BzrDirFormat objects.
 
 
953
    _lock_file_name = 'branch-lock'
 
 
955
    # _lock_class must be set in subclasses to the lock type, typ.
 
 
956
    # TransportLock or LockDir
 
 
959
    def find_format(klass, transport):
 
 
960
        """Return the format present at transport."""
 
 
961
        for format in klass._control_formats:
 
 
963
                return format.probe_transport(transport)
 
 
964
            except errors.NotBranchError:
 
 
965
                # this format does not find a control dir here.
 
 
967
        raise errors.NotBranchError(path=transport.base)
 
 
970
    def probe_transport(klass, transport):
 
 
971
        """Return the .bzrdir style transport present at URL."""
 
 
973
            format_string = transport.get(".bzr/branch-format").read()
 
 
974
        except errors.NoSuchFile:
 
 
975
            raise errors.NotBranchError(path=transport.base)
 
 
978
            return klass._formats[format_string]
 
 
980
            raise errors.UnknownFormatError(format=format_string)
 
 
983
    def get_default_format(klass):
 
 
984
        """Return the current default format."""
 
 
985
        return klass._default_format
 
 
987
    def get_format_string(self):
 
 
988
        """Return the ASCII format string that identifies this format."""
 
 
989
        raise NotImplementedError(self.get_format_string)
 
 
991
    def get_format_description(self):
 
 
992
        """Return the short description for this format."""
 
 
993
        raise NotImplementedError(self.get_format_description)
 
 
995
    def get_converter(self, format=None):
 
 
996
        """Return the converter to use to convert bzrdirs needing converts.
 
 
998
        This returns a bzrlib.bzrdir.Converter object.
 
 
1000
        This should return the best upgrader to step this format towards the
 
 
1001
        current default format. In the case of plugins we can/should provide
 
 
1002
        some means for them to extend the range of returnable converters.
 
 
1004
        :param format: Optional format to override the default format of the 
 
 
1007
        raise NotImplementedError(self.get_converter)
 
 
1009
    def initialize(self, url):
 
 
1010
        """Create a bzr control dir at this url and return an opened copy.
 
 
1012
        Subclasses should typically override initialize_on_transport
 
 
1013
        instead of this method.
 
 
1015
        return self.initialize_on_transport(get_transport(url))
 
 
1017
    def initialize_on_transport(self, transport):
 
 
1018
        """Initialize a new bzrdir in the base directory of a Transport."""
 
 
1019
        # Since we don't have a .bzr directory, inherit the
 
 
1020
        # mode from the root directory
 
 
1021
        temp_control = LockableFiles(transport, '', TransportLock)
 
 
1022
        temp_control._transport.mkdir('.bzr',
 
 
1023
                                      # FIXME: RBC 20060121 don't peek under
 
 
1025
                                      mode=temp_control._dir_mode)
 
 
1026
        file_mode = temp_control._file_mode
 
 
1028
        mutter('created control directory in ' + transport.base)
 
 
1029
        control = transport.clone('.bzr')
 
 
1030
        utf8_files = [('README', 
 
 
1031
                       "This is a Bazaar-NG control directory.\n"
 
 
1032
                       "Do not change any files in this directory.\n"),
 
 
1033
                      ('branch-format', self.get_format_string()),
 
 
1035
        # NB: no need to escape relative paths that are url safe.
 
 
1036
        control_files = LockableFiles(control, self._lock_file_name, 
 
 
1038
        control_files.create_lock()
 
 
1039
        control_files.lock_write()
 
 
1041
            for file, content in utf8_files:
 
 
1042
                control_files.put_utf8(file, content)
 
 
1044
            control_files.unlock()
 
 
1045
        return self.open(transport, _found=True)
 
 
1047
    def is_supported(self):
 
 
1048
        """Is this format supported?
 
 
1050
        Supported formats must be initializable and openable.
 
 
1051
        Unsupported formats may not support initialization or committing or 
 
 
1052
        some other features depending on the reason for not being supported.
 
 
1057
    def known_formats(klass):
 
 
1058
        """Return all the known formats.
 
 
1060
        Concrete formats should override _known_formats.
 
 
1062
        # There is double indirection here to make sure that control 
 
 
1063
        # formats used by more than one dir format will only be probed 
 
 
1064
        # once. This can otherwise be quite expensive for remote connections.
 
 
1066
        for format in klass._control_formats:
 
 
1067
            result.update(format._known_formats())
 
 
1071
    def _known_formats(klass):
 
 
1072
        """Return the known format instances for this control format."""
 
 
1073
        return set(klass._formats.values())
 
 
1075
    def open(self, transport, _found=False):
 
 
1076
        """Return an instance of this format for the dir transport points at.
 
 
1078
        _found is a private parameter, do not use it.
 
 
1081
            assert isinstance(BzrDirFormat.find_format(transport),
 
 
1083
        return self._open(transport)
 
 
1085
    def _open(self, transport):
 
 
1086
        """Template method helper for opening BzrDirectories.
 
 
1088
        This performs the actual open and any additional logic or parameter
 
 
1091
        raise NotImplementedError(self._open)
 
 
1094
    def register_format(klass, format):
 
 
1095
        klass._formats[format.get_format_string()] = format
 
 
1098
    def register_control_format(klass, format):
 
 
1099
        """Register a format that does not use '.bzrdir' for its control dir.
 
 
1101
        TODO: This should be pulled up into a 'ControlDirFormat' base class
 
 
1102
        which BzrDirFormat can inherit from, and renamed to register_format 
 
 
1103
        there. It has been done without that for now for simplicity of
 
 
1106
        klass._control_formats.append(format)
 
 
1109
    def set_default_format(klass, format):
 
 
1110
        klass._default_format = format
 
 
1113
        return self.get_format_string()[:-1]
 
 
1116
    def unregister_format(klass, format):
 
 
1117
        assert klass._formats[format.get_format_string()] is format
 
 
1118
        del klass._formats[format.get_format_string()]
 
 
1121
    def unregister_control_format(klass, format):
 
 
1122
        klass._control_formats.remove(format)
 
 
1125
# register BzrDirFormat as a control format
 
 
1126
BzrDirFormat.register_control_format(BzrDirFormat)
 
 
1129
class BzrDirFormat4(BzrDirFormat):
 
 
1130
    """Bzr dir format 4.
 
 
1132
    This format is a combined format for working tree, branch and repository.
 
 
1134
     - Format 1 working trees [always]
 
 
1135
     - Format 4 branches [always]
 
 
1136
     - Format 4 repositories [always]
 
 
1138
    This format is deprecated: it indexes texts using a text it which is
 
 
1139
    removed in format 5; write support for this format has been removed.
 
 
1142
    _lock_class = TransportLock
 
 
1144
    def get_format_string(self):
 
 
1145
        """See BzrDirFormat.get_format_string()."""
 
 
1146
        return "Bazaar-NG branch, format 0.0.4\n"
 
 
1148
    def get_format_description(self):
 
 
1149
        """See BzrDirFormat.get_format_description()."""
 
 
1150
        return "All-in-one format 4"
 
 
1152
    def get_converter(self, format=None):
 
 
1153
        """See BzrDirFormat.get_converter()."""
 
 
1154
        # there is one and only one upgrade path here.
 
 
1155
        return ConvertBzrDir4To5()
 
 
1157
    def initialize_on_transport(self, transport):
 
 
1158
        """Format 4 branches cannot be created."""
 
 
1159
        raise errors.UninitializableFormat(self)
 
 
1161
    def is_supported(self):
 
 
1162
        """Format 4 is not supported.
 
 
1164
        It is not supported because the model changed from 4 to 5 and the
 
 
1165
        conversion logic is expensive - so doing it on the fly was not 
 
 
1170
    def _open(self, transport):
 
 
1171
        """See BzrDirFormat._open."""
 
 
1172
        return BzrDir4(transport, self)
 
 
1174
    def __return_repository_format(self):
 
 
1175
        """Circular import protection."""
 
 
1176
        from bzrlib.repository import RepositoryFormat4
 
 
1177
        return RepositoryFormat4(self)
 
 
1178
    repository_format = property(__return_repository_format)
 
 
1181
class BzrDirFormat5(BzrDirFormat):
 
 
1182
    """Bzr control format 5.
 
 
1184
    This format is a combined format for working tree, branch and repository.
 
 
1186
     - Format 2 working trees [always] 
 
 
1187
     - Format 4 branches [always] 
 
 
1188
     - Format 5 repositories [always]
 
 
1189
       Unhashed stores in the repository.
 
 
1192
    _lock_class = TransportLock
 
 
1194
    def get_format_string(self):
 
 
1195
        """See BzrDirFormat.get_format_string()."""
 
 
1196
        return "Bazaar-NG branch, format 5\n"
 
 
1198
    def get_format_description(self):
 
 
1199
        """See BzrDirFormat.get_format_description()."""
 
 
1200
        return "All-in-one format 5"
 
 
1202
    def get_converter(self, format=None):
 
 
1203
        """See BzrDirFormat.get_converter()."""
 
 
1204
        # there is one and only one upgrade path here.
 
 
1205
        return ConvertBzrDir5To6()
 
 
1207
    def _initialize_for_clone(self, url):
 
 
1208
        return self.initialize_on_transport(get_transport(url), _cloning=True)
 
 
1210
    def initialize_on_transport(self, transport, _cloning=False):
 
 
1211
        """Format 5 dirs always have working tree, branch and repository.
 
 
1213
        Except when they are being cloned.
 
 
1215
        from bzrlib.branch import BzrBranchFormat4
 
 
1216
        from bzrlib.repository import RepositoryFormat5
 
 
1217
        from bzrlib.workingtree import WorkingTreeFormat2
 
 
1218
        result = (super(BzrDirFormat5, self).initialize_on_transport(transport))
 
 
1219
        RepositoryFormat5().initialize(result, _internal=True)
 
 
1221
            branch = BzrBranchFormat4().initialize(result)
 
 
1223
                WorkingTreeFormat2().initialize(result)
 
 
1224
            except errors.NotLocalUrl:
 
 
1225
                # Even though we can't access the working tree, we need to
 
 
1226
                # create its control files.
 
 
1227
                WorkingTreeFormat2().stub_initialize_remote(branch.control_files)
 
 
1230
    def _open(self, transport):
 
 
1231
        """See BzrDirFormat._open."""
 
 
1232
        return BzrDir5(transport, self)
 
 
1234
    def __return_repository_format(self):
 
 
1235
        """Circular import protection."""
 
 
1236
        from bzrlib.repository import RepositoryFormat5
 
 
1237
        return RepositoryFormat5(self)
 
 
1238
    repository_format = property(__return_repository_format)
 
 
1241
class BzrDirFormat6(BzrDirFormat):
 
 
1242
    """Bzr control format 6.
 
 
1244
    This format is a combined format for working tree, branch and repository.
 
 
1246
     - Format 2 working trees [always] 
 
 
1247
     - Format 4 branches [always] 
 
 
1248
     - Format 6 repositories [always]
 
 
1251
    _lock_class = TransportLock
 
 
1253
    def get_format_string(self):
 
 
1254
        """See BzrDirFormat.get_format_string()."""
 
 
1255
        return "Bazaar-NG branch, format 6\n"
 
 
1257
    def get_format_description(self):
 
 
1258
        """See BzrDirFormat.get_format_description()."""
 
 
1259
        return "All-in-one format 6"
 
 
1261
    def get_converter(self, format=None):
 
 
1262
        """See BzrDirFormat.get_converter()."""
 
 
1263
        # there is one and only one upgrade path here.
 
 
1264
        return ConvertBzrDir6ToMeta()
 
 
1266
    def _initialize_for_clone(self, url):
 
 
1267
        return self.initialize_on_transport(get_transport(url), _cloning=True)
 
 
1269
    def initialize_on_transport(self, transport, _cloning=False):
 
 
1270
        """Format 6 dirs always have working tree, branch and repository.
 
 
1272
        Except when they are being cloned.
 
 
1274
        from bzrlib.branch import BzrBranchFormat4
 
 
1275
        from bzrlib.repository import RepositoryFormat6
 
 
1276
        from bzrlib.workingtree import WorkingTreeFormat2
 
 
1277
        result = super(BzrDirFormat6, self).initialize_on_transport(transport)
 
 
1278
        RepositoryFormat6().initialize(result, _internal=True)
 
 
1280
            branch = BzrBranchFormat4().initialize(result)
 
 
1282
                WorkingTreeFormat2().initialize(result)
 
 
1283
            except errors.NotLocalUrl:
 
 
1284
                # Even though we can't access the working tree, we need to
 
 
1285
                # create its control files.
 
 
1286
                WorkingTreeFormat2().stub_initialize_remote(branch.control_files)
 
 
1289
    def _open(self, transport):
 
 
1290
        """See BzrDirFormat._open."""
 
 
1291
        return BzrDir6(transport, self)
 
 
1293
    def __return_repository_format(self):
 
 
1294
        """Circular import protection."""
 
 
1295
        from bzrlib.repository import RepositoryFormat6
 
 
1296
        return RepositoryFormat6(self)
 
 
1297
    repository_format = property(__return_repository_format)
 
 
1300
class BzrDirMetaFormat1(BzrDirFormat):
 
 
1301
    """Bzr meta control format 1
 
 
1303
    This is the first format with split out working tree, branch and repository
 
 
1306
     - Format 3 working trees [optional]
 
 
1307
     - Format 5 branches [optional]
 
 
1308
     - Format 7 repositories [optional]
 
 
1311
    _lock_class = LockDir
 
 
1313
    def get_converter(self, format=None):
 
 
1314
        """See BzrDirFormat.get_converter()."""
 
 
1316
            format = BzrDirFormat.get_default_format()
 
 
1317
        if not isinstance(self, format.__class__):
 
 
1318
            # converting away from metadir is not implemented
 
 
1319
            raise NotImplementedError(self.get_converter)
 
 
1320
        return ConvertMetaToMeta(format)
 
 
1322
    def get_format_string(self):
 
 
1323
        """See BzrDirFormat.get_format_string()."""
 
 
1324
        return "Bazaar-NG meta directory, format 1\n"
 
 
1326
    def get_format_description(self):
 
 
1327
        """See BzrDirFormat.get_format_description()."""
 
 
1328
        return "Meta directory format 1"
 
 
1330
    def _open(self, transport):
 
 
1331
        """See BzrDirFormat._open."""
 
 
1332
        return BzrDirMeta1(transport, self)
 
 
1334
    def __return_repository_format(self):
 
 
1335
        """Circular import protection."""
 
 
1336
        if getattr(self, '_repository_format', None):
 
 
1337
            return self._repository_format
 
 
1338
        from bzrlib.repository import RepositoryFormat
 
 
1339
        return RepositoryFormat.get_default_format()
 
 
1341
    def __set_repository_format(self, value):
 
 
1342
        """Allow changint the repository format for metadir formats."""
 
 
1343
        self._repository_format = value
 
 
1345
    repository_format = property(__return_repository_format, __set_repository_format)
 
 
1348
BzrDirFormat.register_format(BzrDirFormat4())
 
 
1349
BzrDirFormat.register_format(BzrDirFormat5())
 
 
1350
BzrDirFormat.register_format(BzrDirFormat6())
 
 
1351
__default_format = BzrDirMetaFormat1()
 
 
1352
BzrDirFormat.register_format(__default_format)
 
 
1353
BzrDirFormat.set_default_format(__default_format)
 
 
1356
class BzrDirTestProviderAdapter(object):
 
 
1357
    """A tool to generate a suite testing multiple bzrdir formats at once.
 
 
1359
    This is done by copying the test once for each transport and injecting
 
 
1360
    the transport_server, transport_readonly_server, and bzrdir_format
 
 
1361
    classes into each copy. Each copy is also given a new id() to make it
 
 
1365
    def __init__(self, transport_server, transport_readonly_server, formats):
 
 
1366
        self._transport_server = transport_server
 
 
1367
        self._transport_readonly_server = transport_readonly_server
 
 
1368
        self._formats = formats
 
 
1370
    def adapt(self, test):
 
 
1371
        result = TestSuite()
 
 
1372
        for format in self._formats:
 
 
1373
            new_test = deepcopy(test)
 
 
1374
            new_test.transport_server = self._transport_server
 
 
1375
            new_test.transport_readonly_server = self._transport_readonly_server
 
 
1376
            new_test.bzrdir_format = format
 
 
1377
            def make_new_test_id():
 
 
1378
                new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
 
 
1379
                return lambda: new_id
 
 
1380
            new_test.id = make_new_test_id()
 
 
1381
            result.addTest(new_test)
 
 
1385
class Converter(object):
 
 
1386
    """Converts a disk format object from one format to another."""
 
 
1388
    def convert(self, to_convert, pb):
 
 
1389
        """Perform the conversion of to_convert, giving feedback via pb.
 
 
1391
        :param to_convert: The disk object to convert.
 
 
1392
        :param pb: a progress bar to use for progress information.
 
 
1395
    def step(self, message):
 
 
1396
        """Update the pb by a step."""
 
 
1398
        self.pb.update(message, self.count, self.total)
 
 
1401
class ConvertBzrDir4To5(Converter):
 
 
1402
    """Converts format 4 bzr dirs to format 5."""
 
 
1405
        super(ConvertBzrDir4To5, self).__init__()
 
 
1406
        self.converted_revs = set()
 
 
1407
        self.absent_revisions = set()
 
 
1411
    def convert(self, to_convert, pb):
 
 
1412
        """See Converter.convert()."""
 
 
1413
        self.bzrdir = to_convert
 
 
1415
        self.pb.note('starting upgrade from format 4 to 5')
 
 
1416
        if isinstance(self.bzrdir.transport, LocalTransport):
 
 
1417
            self.bzrdir.get_workingtree_transport(None).delete('stat-cache')
 
 
1418
        self._convert_to_weaves()
 
 
1419
        return BzrDir.open(self.bzrdir.root_transport.base)
 
 
1421
    def _convert_to_weaves(self):
 
 
1422
        self.pb.note('note: upgrade may be faster if all store files are ungzipped first')
 
 
1425
            stat = self.bzrdir.transport.stat('weaves')
 
 
1426
            if not S_ISDIR(stat.st_mode):
 
 
1427
                self.bzrdir.transport.delete('weaves')
 
 
1428
                self.bzrdir.transport.mkdir('weaves')
 
 
1429
        except errors.NoSuchFile:
 
 
1430
            self.bzrdir.transport.mkdir('weaves')
 
 
1431
        # deliberately not a WeaveFile as we want to build it up slowly.
 
 
1432
        self.inv_weave = Weave('inventory')
 
 
1433
        # holds in-memory weaves for all files
 
 
1434
        self.text_weaves = {}
 
 
1435
        self.bzrdir.transport.delete('branch-format')
 
 
1436
        self.branch = self.bzrdir.open_branch()
 
 
1437
        self._convert_working_inv()
 
 
1438
        rev_history = self.branch.revision_history()
 
 
1439
        # to_read is a stack holding the revisions we still need to process;
 
 
1440
        # appending to it adds new highest-priority revisions
 
 
1441
        self.known_revisions = set(rev_history)
 
 
1442
        self.to_read = rev_history[-1:]
 
 
1444
            rev_id = self.to_read.pop()
 
 
1445
            if (rev_id not in self.revisions
 
 
1446
                and rev_id not in self.absent_revisions):
 
 
1447
                self._load_one_rev(rev_id)
 
 
1449
        to_import = self._make_order()
 
 
1450
        for i, rev_id in enumerate(to_import):
 
 
1451
            self.pb.update('converting revision', i, len(to_import))
 
 
1452
            self._convert_one_rev(rev_id)
 
 
1454
        self._write_all_weaves()
 
 
1455
        self._write_all_revs()
 
 
1456
        self.pb.note('upgraded to weaves:')
 
 
1457
        self.pb.note('  %6d revisions and inventories', len(self.revisions))
 
 
1458
        self.pb.note('  %6d revisions not present', len(self.absent_revisions))
 
 
1459
        self.pb.note('  %6d texts', self.text_count)
 
 
1460
        self._cleanup_spare_files_after_format4()
 
 
1461
        self.branch.control_files.put_utf8('branch-format', BzrDirFormat5().get_format_string())
 
 
1463
    def _cleanup_spare_files_after_format4(self):
 
 
1464
        # FIXME working tree upgrade foo.
 
 
1465
        for n in 'merged-patches', 'pending-merged-patches':
 
 
1467
                ## assert os.path.getsize(p) == 0
 
 
1468
                self.bzrdir.transport.delete(n)
 
 
1469
            except errors.NoSuchFile:
 
 
1471
        self.bzrdir.transport.delete_tree('inventory-store')
 
 
1472
        self.bzrdir.transport.delete_tree('text-store')
 
 
1474
    def _convert_working_inv(self):
 
 
1475
        inv = serializer_v4.read_inventory(self.branch.control_files.get('inventory'))
 
 
1476
        new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
 
 
1477
        # FIXME inventory is a working tree change.
 
 
1478
        self.branch.control_files.put('inventory', new_inv_xml)
 
 
1480
    def _write_all_weaves(self):
 
 
1481
        controlweaves = WeaveStore(self.bzrdir.transport, prefixed=False)
 
 
1482
        weave_transport = self.bzrdir.transport.clone('weaves')
 
 
1483
        weaves = WeaveStore(weave_transport, prefixed=False)
 
 
1484
        transaction = WriteTransaction()
 
 
1488
            for file_id, file_weave in self.text_weaves.items():
 
 
1489
                self.pb.update('writing weave', i, len(self.text_weaves))
 
 
1490
                weaves._put_weave(file_id, file_weave, transaction)
 
 
1492
            self.pb.update('inventory', 0, 1)
 
 
1493
            controlweaves._put_weave('inventory', self.inv_weave, transaction)
 
 
1494
            self.pb.update('inventory', 1, 1)
 
 
1498
    def _write_all_revs(self):
 
 
1499
        """Write all revisions out in new form."""
 
 
1500
        self.bzrdir.transport.delete_tree('revision-store')
 
 
1501
        self.bzrdir.transport.mkdir('revision-store')
 
 
1502
        revision_transport = self.bzrdir.transport.clone('revision-store')
 
 
1504
        _revision_store = TextRevisionStore(TextStore(revision_transport,
 
 
1508
            transaction = bzrlib.transactions.WriteTransaction()
 
 
1509
            for i, rev_id in enumerate(self.converted_revs):
 
 
1510
                self.pb.update('write revision', i, len(self.converted_revs))
 
 
1511
                _revision_store.add_revision(self.revisions[rev_id], transaction)
 
 
1515
    def _load_one_rev(self, rev_id):
 
 
1516
        """Load a revision object into memory.
 
 
1518
        Any parents not either loaded or abandoned get queued to be
 
 
1520
        self.pb.update('loading revision',
 
 
1521
                       len(self.revisions),
 
 
1522
                       len(self.known_revisions))
 
 
1523
        if not self.branch.repository.has_revision(rev_id):
 
 
1525
            self.pb.note('revision {%s} not present in branch; '
 
 
1526
                         'will be converted as a ghost',
 
 
1528
            self.absent_revisions.add(rev_id)
 
 
1530
            rev = self.branch.repository._revision_store.get_revision(rev_id,
 
 
1531
                self.branch.repository.get_transaction())
 
 
1532
            for parent_id in rev.parent_ids:
 
 
1533
                self.known_revisions.add(parent_id)
 
 
1534
                self.to_read.append(parent_id)
 
 
1535
            self.revisions[rev_id] = rev
 
 
1537
    def _load_old_inventory(self, rev_id):
 
 
1538
        assert rev_id not in self.converted_revs
 
 
1539
        old_inv_xml = self.branch.repository.inventory_store.get(rev_id).read()
 
 
1540
        inv = serializer_v4.read_inventory_from_string(old_inv_xml)
 
 
1541
        rev = self.revisions[rev_id]
 
 
1542
        if rev.inventory_sha1:
 
 
1543
            assert rev.inventory_sha1 == sha_string(old_inv_xml), \
 
 
1544
                'inventory sha mismatch for {%s}' % rev_id
 
 
1547
    def _load_updated_inventory(self, rev_id):
 
 
1548
        assert rev_id in self.converted_revs
 
 
1549
        inv_xml = self.inv_weave.get_text(rev_id)
 
 
1550
        inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(inv_xml)
 
 
1553
    def _convert_one_rev(self, rev_id):
 
 
1554
        """Convert revision and all referenced objects to new format."""
 
 
1555
        rev = self.revisions[rev_id]
 
 
1556
        inv = self._load_old_inventory(rev_id)
 
 
1557
        present_parents = [p for p in rev.parent_ids
 
 
1558
                           if p not in self.absent_revisions]
 
 
1559
        self._convert_revision_contents(rev, inv, present_parents)
 
 
1560
        self._store_new_weave(rev, inv, present_parents)
 
 
1561
        self.converted_revs.add(rev_id)
 
 
1563
    def _store_new_weave(self, rev, inv, present_parents):
 
 
1564
        # the XML is now updated with text versions
 
 
1566
            entries = inv.iter_entries()
 
 
1568
            for path, ie in entries:
 
 
1569
                assert hasattr(ie, 'revision'), \
 
 
1570
                    'no revision on {%s} in {%s}' % \
 
 
1571
                    (file_id, rev.revision_id)
 
 
1572
        new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
 
 
1573
        new_inv_sha1 = sha_string(new_inv_xml)
 
 
1574
        self.inv_weave.add_lines(rev.revision_id, 
 
 
1576
                                 new_inv_xml.splitlines(True))
 
 
1577
        rev.inventory_sha1 = new_inv_sha1
 
 
1579
    def _convert_revision_contents(self, rev, inv, present_parents):
 
 
1580
        """Convert all the files within a revision.
 
 
1582
        Also upgrade the inventory to refer to the text revision ids."""
 
 
1583
        rev_id = rev.revision_id
 
 
1584
        mutter('converting texts of revision {%s}',
 
 
1586
        parent_invs = map(self._load_updated_inventory, present_parents)
 
 
1587
        entries = inv.iter_entries()
 
 
1589
        for path, ie in entries:
 
 
1590
            self._convert_file_version(rev, ie, parent_invs)
 
 
1592
    def _convert_file_version(self, rev, ie, parent_invs):
 
 
1593
        """Convert one version of one file.
 
 
1595
        The file needs to be added into the weave if it is a merge
 
 
1596
        of >=2 parents or if it's changed from its parent.
 
 
1598
        file_id = ie.file_id
 
 
1599
        rev_id = rev.revision_id
 
 
1600
        w = self.text_weaves.get(file_id)
 
 
1603
            self.text_weaves[file_id] = w
 
 
1604
        text_changed = False
 
 
1605
        previous_entries = ie.find_previous_heads(parent_invs,
 
 
1609
        for old_revision in previous_entries:
 
 
1610
                # if this fails, its a ghost ?
 
 
1611
                assert old_revision in self.converted_revs 
 
 
1612
        self.snapshot_ie(previous_entries, ie, w, rev_id)
 
 
1614
        assert getattr(ie, 'revision', None) is not None
 
 
1616
    def snapshot_ie(self, previous_revisions, ie, w, rev_id):
 
 
1617
        # TODO: convert this logic, which is ~= snapshot to
 
 
1618
        # a call to:. This needs the path figured out. rather than a work_tree
 
 
1619
        # a v4 revision_tree can be given, or something that looks enough like
 
 
1620
        # one to give the file content to the entry if it needs it.
 
 
1621
        # and we need something that looks like a weave store for snapshot to 
 
 
1623
        #ie.snapshot(rev, PATH, previous_revisions, REVISION_TREE, InMemoryWeaveStore(self.text_weaves))
 
 
1624
        if len(previous_revisions) == 1:
 
 
1625
            previous_ie = previous_revisions.values()[0]
 
 
1626
            if ie._unchanged(previous_ie):
 
 
1627
                ie.revision = previous_ie.revision
 
 
1630
            text = self.branch.repository.text_store.get(ie.text_id)
 
 
1631
            file_lines = text.readlines()
 
 
1632
            assert sha_strings(file_lines) == ie.text_sha1
 
 
1633
            assert sum(map(len, file_lines)) == ie.text_size
 
 
1634
            w.add_lines(rev_id, previous_revisions, file_lines)
 
 
1635
            self.text_count += 1
 
 
1637
            w.add_lines(rev_id, previous_revisions, [])
 
 
1638
        ie.revision = rev_id
 
 
1640
    def _make_order(self):
 
 
1641
        """Return a suitable order for importing revisions.
 
 
1643
        The order must be such that an revision is imported after all
 
 
1644
        its (present) parents.
 
 
1646
        todo = set(self.revisions.keys())
 
 
1647
        done = self.absent_revisions.copy()
 
 
1650
            # scan through looking for a revision whose parents
 
 
1652
            for rev_id in sorted(list(todo)):
 
 
1653
                rev = self.revisions[rev_id]
 
 
1654
                parent_ids = set(rev.parent_ids)
 
 
1655
                if parent_ids.issubset(done):
 
 
1656
                    # can take this one now
 
 
1657
                    order.append(rev_id)
 
 
1663
class ConvertBzrDir5To6(Converter):
 
 
1664
    """Converts format 5 bzr dirs to format 6."""
 
 
1666
    def convert(self, to_convert, pb):
 
 
1667
        """See Converter.convert()."""
 
 
1668
        self.bzrdir = to_convert
 
 
1670
        self.pb.note('starting upgrade from format 5 to 6')
 
 
1671
        self._convert_to_prefixed()
 
 
1672
        return BzrDir.open(self.bzrdir.root_transport.base)
 
 
1674
    def _convert_to_prefixed(self):
 
 
1675
        from bzrlib.store import TransportStore
 
 
1676
        self.bzrdir.transport.delete('branch-format')
 
 
1677
        for store_name in ["weaves", "revision-store"]:
 
 
1678
            self.pb.note("adding prefixes to %s" % store_name)
 
 
1679
            store_transport = self.bzrdir.transport.clone(store_name)
 
 
1680
            store = TransportStore(store_transport, prefixed=True)
 
 
1681
            for urlfilename in store_transport.list_dir('.'):
 
 
1682
                filename = urlutils.unescape(urlfilename)
 
 
1683
                if (filename.endswith(".weave") or
 
 
1684
                    filename.endswith(".gz") or
 
 
1685
                    filename.endswith(".sig")):
 
 
1686
                    file_id = os.path.splitext(filename)[0]
 
 
1689
                prefix_dir = store.hash_prefix(file_id)
 
 
1690
                # FIXME keep track of the dirs made RBC 20060121
 
 
1692
                    store_transport.move(filename, prefix_dir + '/' + filename)
 
 
1693
                except errors.NoSuchFile: # catches missing dirs strangely enough
 
 
1694
                    store_transport.mkdir(prefix_dir)
 
 
1695
                    store_transport.move(filename, prefix_dir + '/' + filename)
 
 
1696
        self.bzrdir._control_files.put_utf8('branch-format', BzrDirFormat6().get_format_string())
 
 
1699
class ConvertBzrDir6ToMeta(Converter):
 
 
1700
    """Converts format 6 bzr dirs to metadirs."""
 
 
1702
    def convert(self, to_convert, pb):
 
 
1703
        """See Converter.convert()."""
 
 
1704
        self.bzrdir = to_convert
 
 
1707
        self.total = 20 # the steps we know about
 
 
1708
        self.garbage_inventories = []
 
 
1710
        self.pb.note('starting upgrade from format 6 to metadir')
 
 
1711
        self.bzrdir._control_files.put_utf8('branch-format', "Converting to format 6")
 
 
1712
        # its faster to move specific files around than to open and use the apis...
 
 
1713
        # first off, nuke ancestry.weave, it was never used.
 
 
1715
            self.step('Removing ancestry.weave')
 
 
1716
            self.bzrdir.transport.delete('ancestry.weave')
 
 
1717
        except errors.NoSuchFile:
 
 
1719
        # find out whats there
 
 
1720
        self.step('Finding branch files')
 
 
1721
        last_revision = self.bzrdir.open_branch().last_revision()
 
 
1722
        bzrcontents = self.bzrdir.transport.list_dir('.')
 
 
1723
        for name in bzrcontents:
 
 
1724
            if name.startswith('basis-inventory.'):
 
 
1725
                self.garbage_inventories.append(name)
 
 
1726
        # create new directories for repository, working tree and branch
 
 
1727
        self.dir_mode = self.bzrdir._control_files._dir_mode
 
 
1728
        self.file_mode = self.bzrdir._control_files._file_mode
 
 
1729
        repository_names = [('inventory.weave', True),
 
 
1730
                            ('revision-store', True),
 
 
1732
        self.step('Upgrading repository  ')
 
 
1733
        self.bzrdir.transport.mkdir('repository', mode=self.dir_mode)
 
 
1734
        self.make_lock('repository')
 
 
1735
        # we hard code the formats here because we are converting into
 
 
1736
        # the meta format. The meta format upgrader can take this to a 
 
 
1737
        # future format within each component.
 
 
1738
        self.put_format('repository', bzrlib.repository.RepositoryFormat7())
 
 
1739
        for entry in repository_names:
 
 
1740
            self.move_entry('repository', entry)
 
 
1742
        self.step('Upgrading branch      ')
 
 
1743
        self.bzrdir.transport.mkdir('branch', mode=self.dir_mode)
 
 
1744
        self.make_lock('branch')
 
 
1745
        self.put_format('branch', bzrlib.branch.BzrBranchFormat5())
 
 
1746
        branch_files = [('revision-history', True),
 
 
1747
                        ('branch-name', True),
 
 
1749
        for entry in branch_files:
 
 
1750
            self.move_entry('branch', entry)
 
 
1752
        self.step('Upgrading working tree')
 
 
1753
        self.bzrdir.transport.mkdir('checkout', mode=self.dir_mode)
 
 
1754
        self.make_lock('checkout')
 
 
1755
        self.put_format('checkout', bzrlib.workingtree.WorkingTreeFormat3())
 
 
1756
        self.bzrdir.transport.delete_multi(self.garbage_inventories, self.pb)
 
 
1757
        checkout_files = [('pending-merges', True),
 
 
1758
                          ('inventory', True),
 
 
1759
                          ('stat-cache', False)]
 
 
1760
        for entry in checkout_files:
 
 
1761
            self.move_entry('checkout', entry)
 
 
1762
        if last_revision is not None:
 
 
1763
            self.bzrdir._control_files.put_utf8('checkout/last-revision',
 
 
1765
        self.bzrdir._control_files.put_utf8('branch-format', BzrDirMetaFormat1().get_format_string())
 
 
1766
        return BzrDir.open(self.bzrdir.root_transport.base)
 
 
1768
    def make_lock(self, name):
 
 
1769
        """Make a lock for the new control dir name."""
 
 
1770
        self.step('Make %s lock' % name)
 
 
1771
        ld = LockDir(self.bzrdir.transport, 
 
 
1773
                     file_modebits=self.file_mode,
 
 
1774
                     dir_modebits=self.dir_mode)
 
 
1777
    def move_entry(self, new_dir, entry):
 
 
1778
        """Move then entry name into new_dir."""
 
 
1780
        mandatory = entry[1]
 
 
1781
        self.step('Moving %s' % name)
 
 
1783
            self.bzrdir.transport.move(name, '%s/%s' % (new_dir, name))
 
 
1784
        except errors.NoSuchFile:
 
 
1788
    def put_format(self, dirname, format):
 
 
1789
        self.bzrdir._control_files.put_utf8('%s/format' % dirname, format.get_format_string())
 
 
1792
class ConvertMetaToMeta(Converter):
 
 
1793
    """Converts the components of metadirs."""
 
 
1795
    def __init__(self, target_format):
 
 
1796
        """Create a metadir to metadir converter.
 
 
1798
        :param target_format: The final metadir format that is desired.
 
 
1800
        self.target_format = target_format
 
 
1802
    def convert(self, to_convert, pb):
 
 
1803
        """See Converter.convert()."""
 
 
1804
        self.bzrdir = to_convert
 
 
1808
        self.step('checking repository format')
 
 
1810
            repo = self.bzrdir.open_repository()
 
 
1811
        except errors.NoRepositoryPresent:
 
 
1814
            if not isinstance(repo._format, self.target_format.repository_format.__class__):
 
 
1815
                from bzrlib.repository import CopyConverter
 
 
1816
                self.pb.note('starting repository conversion')
 
 
1817
                converter = CopyConverter(self.target_format.repository_format)
 
 
1818
                converter.convert(repo, pb)