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
# TODO: The Format probe_transport seems a bit redundant with just trying to
 
 
26
# open the bzrdir. -- mbp
 
 
28
# TODO: Can we move specific formats into separate modules to make this file
 
 
31
from copy import deepcopy
 
 
32
from cStringIO import StringIO
 
 
34
from stat import S_ISDIR
 
 
35
from unittest import TestSuite
 
 
38
import bzrlib.errors as errors
 
 
39
from bzrlib.lockable_files import LockableFiles, TransportLock
 
 
40
from bzrlib.lockdir import LockDir
 
 
41
from bzrlib.osutils import (
 
 
48
import bzrlib.revision
 
 
49
from bzrlib.store.revision.text import TextRevisionStore
 
 
50
from bzrlib.store.text import TextStore
 
 
51
from bzrlib.store.versioned import WeaveStore
 
 
52
from bzrlib.trace import mutter
 
 
53
from bzrlib.transactions import WriteTransaction
 
 
54
from bzrlib.transport import get_transport
 
 
55
from bzrlib.transport.local import LocalTransport
 
 
56
import bzrlib.urlutils as urlutils
 
 
57
from bzrlib.weave import Weave
 
 
58
from bzrlib.xml4 import serializer_v4
 
 
63
    """A .bzr control diretory.
 
 
65
    BzrDir instances let you create or open any of the things that can be
 
 
66
    found within .bzr - checkouts, branches and repositories.
 
 
69
        the transport which this bzr dir is rooted at (i.e. file:///.../.bzr/)
 
 
71
        a transport connected to the directory this bzr was opened from.
 
 
75
        """Invoke break_lock on the first object in the bzrdir.
 
 
77
        If there is a tree, the tree is opened and break_lock() called.
 
 
78
        Otherwise, branch is tried, and finally repository.
 
 
81
            thing_to_unlock = self.open_workingtree()
 
 
82
        except (errors.NotLocalUrl, errors.NoWorkingTree):
 
 
84
                thing_to_unlock = self.open_branch()
 
 
85
            except errors.NotBranchError:
 
 
87
                    thing_to_unlock = self.open_repository()
 
 
88
                except errors.NoRepositoryPresent:
 
 
90
        thing_to_unlock.break_lock()
 
 
92
    def can_convert_format(self):
 
 
93
        """Return true if this bzrdir is one whose format we can convert from."""
 
 
97
    def _check_supported(format, allow_unsupported):
 
 
98
        """Check whether format is a supported format.
 
 
100
        If allow_unsupported is True, this is a no-op.
 
 
102
        if not allow_unsupported and not format.is_supported():
 
 
103
            # see open_downlevel to open legacy branches.
 
 
104
            raise errors.UnsupportedFormatError(format=format)
 
 
106
    def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
 
 
107
        """Clone this bzrdir and its contents to url verbatim.
 
 
109
        If urls last component does not exist, it will be created.
 
 
111
        if revision_id is not None, then the clone operation may tune
 
 
112
            itself to download less data.
 
 
113
        :param force_new_repo: Do not use a shared repository for the target 
 
 
114
                               even if one is available.
 
 
117
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
 
118
        result = self._format.initialize(url)
 
 
120
            local_repo = self.find_repository()
 
 
121
        except errors.NoRepositoryPresent:
 
 
124
            # may need to copy content in
 
 
126
                result_repo = local_repo.clone(
 
 
128
                    revision_id=revision_id,
 
 
130
                result_repo.set_make_working_trees(local_repo.make_working_trees())
 
 
133
                    result_repo = result.find_repository()
 
 
134
                    # fetch content this dir needs.
 
 
136
                        # XXX FIXME RBC 20060214 need tests for this when the basis
 
 
138
                        result_repo.fetch(basis_repo, revision_id=revision_id)
 
 
139
                    result_repo.fetch(local_repo, revision_id=revision_id)
 
 
140
                except errors.NoRepositoryPresent:
 
 
141
                    # needed to make one anyway.
 
 
142
                    result_repo = local_repo.clone(
 
 
144
                        revision_id=revision_id,
 
 
146
                    result_repo.set_make_working_trees(local_repo.make_working_trees())
 
 
147
        # 1 if there is a branch present
 
 
148
        #   make sure its content is available in the target repository
 
 
151
            self.open_branch().clone(result, revision_id=revision_id)
 
 
152
        except errors.NotBranchError:
 
 
155
            self.open_workingtree().clone(result, basis=basis_tree)
 
 
156
        except (errors.NoWorkingTree, errors.NotLocalUrl):
 
 
160
    def _get_basis_components(self, basis):
 
 
161
        """Retrieve the basis components that are available at basis."""
 
 
163
            return None, None, None
 
 
165
            basis_tree = basis.open_workingtree()
 
 
166
            basis_branch = basis_tree.branch
 
 
167
            basis_repo = basis_branch.repository
 
 
168
        except (errors.NoWorkingTree, errors.NotLocalUrl):
 
 
171
                basis_branch = basis.open_branch()
 
 
172
                basis_repo = basis_branch.repository
 
 
173
            except errors.NotBranchError:
 
 
176
                    basis_repo = basis.open_repository()
 
 
177
                except errors.NoRepositoryPresent:
 
 
179
        return basis_repo, basis_branch, basis_tree
 
 
181
    # TODO: This should be given a Transport, and should chdir up; otherwise
 
 
182
    # this will open a new connection.
 
 
183
    def _make_tail(self, url):
 
 
184
        head, tail = urlutils.split(url)
 
 
185
        if tail and tail != '.':
 
 
186
            t = bzrlib.transport.get_transport(head)
 
 
189
            except errors.FileExists:
 
 
192
    # TODO: Should take a Transport
 
 
194
    def create(cls, base):
 
 
195
        """Create a new BzrDir at the url 'base'.
 
 
197
        This will call the current default formats initialize with base
 
 
198
        as the only parameter.
 
 
200
        If you need a specific format, consider creating an instance
 
 
201
        of that and calling initialize().
 
 
203
        if cls is not BzrDir:
 
 
204
            raise AssertionError("BzrDir.create always creates the default format, "
 
 
205
                    "not one of %r" % cls)
 
 
206
        head, tail = urlutils.split(base)
 
 
207
        if tail and tail != '.':
 
 
208
            t = bzrlib.transport.get_transport(head)
 
 
211
            except errors.FileExists:
 
 
213
        return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
 
 
215
    def create_branch(self):
 
 
216
        """Create a branch in this BzrDir.
 
 
218
        The bzrdirs format will control what branch format is created.
 
 
219
        For more control see BranchFormatXX.create(a_bzrdir).
 
 
221
        raise NotImplementedError(self.create_branch)
 
 
224
    def create_branch_and_repo(base, force_new_repo=False):
 
 
225
        """Create a new BzrDir, Branch and Repository at the url 'base'.
 
 
227
        This will use the current default BzrDirFormat, and use whatever 
 
 
228
        repository format that that uses via bzrdir.create_branch and
 
 
229
        create_repository. If a shared repository is available that is used
 
 
232
        The created Branch object is returned.
 
 
234
        :param base: The URL to create the branch at.
 
 
235
        :param force_new_repo: If True a new repository is always created.
 
 
237
        bzrdir = BzrDir.create(base)
 
 
238
        bzrdir._find_or_create_repository(force_new_repo)
 
 
239
        return bzrdir.create_branch()
 
 
241
    def _find_or_create_repository(self, force_new_repo):
 
 
242
        """Create a new repository if needed, returning the repository."""
 
 
244
            return self.create_repository()
 
 
246
            return self.find_repository()
 
 
247
        except errors.NoRepositoryPresent:
 
 
248
            return self.create_repository()
 
 
251
    def create_branch_convenience(base, force_new_repo=False,
 
 
252
                                  force_new_tree=None, format=None):
 
 
253
        """Create a new BzrDir, Branch and Repository at the url 'base'.
 
 
255
        This is a convenience function - it will use an existing repository
 
 
256
        if possible, can be told explicitly whether to create a working tree or
 
 
259
        This will use the current default BzrDirFormat, and use whatever 
 
 
260
        repository format that that uses via bzrdir.create_branch and
 
 
261
        create_repository. If a shared repository is available that is used
 
 
262
        preferentially. Whatever repository is used, its tree creation policy
 
 
265
        The created Branch object is returned.
 
 
266
        If a working tree cannot be made due to base not being a file:// url,
 
 
267
        no error is raised unless force_new_tree is True, in which case no 
 
 
268
        data is created on disk and NotLocalUrl is raised.
 
 
270
        :param base: The URL to create the branch at.
 
 
271
        :param force_new_repo: If True a new repository is always created.
 
 
272
        :param force_new_tree: If True or False force creation of a tree or 
 
 
273
                               prevent such creation respectively.
 
 
274
        :param format: Override for the for the bzrdir format to create
 
 
277
            # check for non local urls
 
 
278
            t = get_transport(safe_unicode(base))
 
 
279
            if not isinstance(t, LocalTransport):
 
 
280
                raise errors.NotLocalUrl(base)
 
 
282
            bzrdir = BzrDir.create(base)
 
 
284
            bzrdir = format.initialize(base)
 
 
285
        repo = bzrdir._find_or_create_repository(force_new_repo)
 
 
286
        result = bzrdir.create_branch()
 
 
287
        if force_new_tree or (repo.make_working_trees() and 
 
 
288
                              force_new_tree is None):
 
 
290
                bzrdir.create_workingtree()
 
 
291
            except errors.NotLocalUrl:
 
 
296
    def create_repository(base, shared=False):
 
 
297
        """Create a new BzrDir and Repository at the url 'base'.
 
 
299
        This will use the current default BzrDirFormat, and use whatever 
 
 
300
        repository format that that uses for bzrdirformat.create_repository.
 
 
302
        :param shared: Create a shared repository rather than a standalone
 
 
304
        The Repository object is returned.
 
 
306
        This must be overridden as an instance method in child classes, where
 
 
307
        it should take no parameters and construct whatever repository format
 
 
308
        that child class desires.
 
 
310
        bzrdir = BzrDir.create(base)
 
 
311
        return bzrdir.create_repository(shared)
 
 
314
    def create_standalone_workingtree(base):
 
 
315
        """Create a new BzrDir, WorkingTree, Branch and Repository at 'base'.
 
 
317
        'base' must be a local path or a file:// url.
 
 
319
        This will use the current default BzrDirFormat, and use whatever 
 
 
320
        repository format that that uses for bzrdirformat.create_workingtree,
 
 
321
        create_branch and create_repository.
 
 
323
        :return: The WorkingTree object.
 
 
325
        t = get_transport(safe_unicode(base))
 
 
326
        if not isinstance(t, LocalTransport):
 
 
327
            raise errors.NotLocalUrl(base)
 
 
328
        bzrdir = BzrDir.create_branch_and_repo(safe_unicode(base),
 
 
329
                                               force_new_repo=True).bzrdir
 
 
330
        return bzrdir.create_workingtree()
 
 
332
    def create_workingtree(self, revision_id=None):
 
 
333
        """Create a working tree at this BzrDir.
 
 
335
        revision_id: create it as of this revision id.
 
 
337
        raise NotImplementedError(self.create_workingtree)
 
 
339
    def find_repository(self):
 
 
340
        """Find the repository that should be used for a_bzrdir.
 
 
342
        This does not require a branch as we use it to find the repo for
 
 
343
        new branches as well as to hook existing branches up to their
 
 
347
            return self.open_repository()
 
 
348
        except errors.NoRepositoryPresent:
 
 
350
        next_transport = self.root_transport.clone('..')
 
 
352
            # find the next containing bzrdir
 
 
354
                found_bzrdir = BzrDir.open_containing_from_transport(
 
 
356
            except errors.NotBranchError:
 
 
358
                raise errors.NoRepositoryPresent(self)
 
 
359
            # does it have a repository ?
 
 
361
                repository = found_bzrdir.open_repository()
 
 
362
            except errors.NoRepositoryPresent:
 
 
363
                next_transport = found_bzrdir.root_transport.clone('..')
 
 
364
                if (found_bzrdir.root_transport.base == next_transport.base):
 
 
365
                    # top of the file system
 
 
369
            if ((found_bzrdir.root_transport.base == 
 
 
370
                 self.root_transport.base) or repository.is_shared()):
 
 
373
                raise errors.NoRepositoryPresent(self)
 
 
374
        raise errors.NoRepositoryPresent(self)
 
 
376
    def get_branch_transport(self, branch_format):
 
 
377
        """Get the transport for use by branch format in this BzrDir.
 
 
379
        Note that bzr dirs that do not support format strings will raise
 
 
380
        IncompatibleFormat if the branch format they are given has
 
 
381
        a format string, and vice versa.
 
 
383
        If branch_format is None, the transport is returned with no 
 
 
384
        checking. if it is not None, then the returned transport is
 
 
385
        guaranteed to point to an existing directory ready for use.
 
 
387
        raise NotImplementedError(self.get_branch_transport)
 
 
389
    def get_repository_transport(self, repository_format):
 
 
390
        """Get the transport for use by repository format in this BzrDir.
 
 
392
        Note that bzr dirs that do not support format strings will raise
 
 
393
        IncompatibleFormat if the repository format they are given has
 
 
394
        a format string, and vice versa.
 
 
396
        If repository_format is None, the transport is returned with no 
 
 
397
        checking. if it is not None, then the returned transport is
 
 
398
        guaranteed to point to an existing directory ready for use.
 
 
400
        raise NotImplementedError(self.get_repository_transport)
 
 
402
    def get_workingtree_transport(self, tree_format):
 
 
403
        """Get the transport for use by workingtree format in this BzrDir.
 
 
405
        Note that bzr dirs that do not support format strings will raise
 
 
406
        IncompatibleFormat if the workingtree format they are given has
 
 
407
        a format string, and vice versa.
 
 
409
        If workingtree_format is None, the transport is returned with no 
 
 
410
        checking. if it is not None, then the returned transport is
 
 
411
        guaranteed to point to an existing directory ready for use.
 
 
413
        raise NotImplementedError(self.get_workingtree_transport)
 
 
415
    def __init__(self, _transport, _format):
 
 
416
        """Initialize a Bzr control dir object.
 
 
418
        Only really common logic should reside here, concrete classes should be
 
 
419
        made with varying behaviours.
 
 
421
        :param _format: the format that is creating this BzrDir instance.
 
 
422
        :param _transport: the transport this dir is based at.
 
 
424
        self._format = _format
 
 
425
        self.transport = _transport.clone('.bzr')
 
 
426
        self.root_transport = _transport
 
 
428
    def is_control_filename(self, filename):
 
 
429
        """True if filename is the name of a path which is reserved for bzrdir's.
 
 
431
        :param filename: A filename within the root transport of this bzrdir.
 
 
433
        This is true IF and ONLY IF the filename is part of the namespace reserved
 
 
434
        for bzr control dirs. Currently this is the '.bzr' directory in the root
 
 
435
        of the root_transport. it is expected that plugins will need to extend
 
 
436
        this in the future - for instance to make bzr talk with svn working
 
 
439
        # this might be better on the BzrDirFormat class because it refers to 
 
 
440
        # all the possible bzrdir disk formats. 
 
 
441
        # This method is tested via the workingtree is_control_filename tests- 
 
 
442
        # it was extracted from WorkingTree.is_control_filename. If the methods
 
 
443
        # contract is extended beyond the current trivial  implementation please
 
 
444
        # add new tests for it to the appropriate place.
 
 
445
        return filename == '.bzr' or filename.startswith('.bzr/')
 
 
447
    def needs_format_conversion(self, format=None):
 
 
448
        """Return true if this bzrdir needs convert_format run on it.
 
 
450
        For instance, if the repository format is out of date but the 
 
 
451
        branch and working tree are not, this should return True.
 
 
453
        :param format: Optional parameter indicating a specific desired
 
 
454
                       format we plan to arrive at.
 
 
456
        raise NotImplementedError(self.needs_format_conversion)
 
 
459
    def open_unsupported(base):
 
 
460
        """Open a branch which is not supported."""
 
 
461
        return BzrDir.open(base, _unsupported=True)
 
 
464
    def open(base, _unsupported=False):
 
 
465
        """Open an existing bzrdir, rooted at 'base' (url)
 
 
467
        _unsupported is a private parameter to the BzrDir class.
 
 
469
        t = get_transport(base)
 
 
470
        return BzrDir.open_from_transport(t, _unsupported=_unsupported)
 
 
473
    def open_from_transport(transport, _unsupported=False):
 
 
474
        """Open a bzrdir within a particular directory.
 
 
476
        :param transport: Transport containing the bzrdir.
 
 
477
        :param _unsupported: private.
 
 
479
        format = BzrDirFormat.find_format(transport)
 
 
480
        BzrDir._check_supported(format, _unsupported)
 
 
481
        return format.open(transport, _found=True)
 
 
483
    def open_branch(self, unsupported=False):
 
 
484
        """Open the branch object at this BzrDir if one is present.
 
 
486
        If unsupported is True, then no longer supported branch formats can
 
 
489
        TODO: static convenience version of this?
 
 
491
        raise NotImplementedError(self.open_branch)
 
 
494
    def open_containing(url):
 
 
495
        """Open an existing branch which contains url.
 
 
497
        :param url: url to search from.
 
 
498
        See open_containing_from_transport for more detail.
 
 
500
        return BzrDir.open_containing_from_transport(get_transport(url))
 
 
503
    def open_containing_from_transport(a_transport):
 
 
504
        """Open an existing branch which contains a_transport.base
 
 
506
        This probes for a branch at a_transport, and searches upwards from there.
 
 
508
        Basically we keep looking up until we find the control directory or
 
 
509
        run into the root.  If there isn't one, raises NotBranchError.
 
 
510
        If there is one and it is either an unrecognised format or an unsupported 
 
 
511
        format, UnknownFormatError or UnsupportedFormatError are raised.
 
 
512
        If there is one, it is returned, along with the unused portion of url.
 
 
514
        :return: The BzrDir that contains the path, and a Unicode path 
 
 
515
                for the rest of the URL.
 
 
517
        # this gets the normalised url back. I.e. '.' -> the full path.
 
 
518
        url = a_transport.base
 
 
521
                result = BzrDir.open_from_transport(a_transport)
 
 
522
                return result, urlutils.unescape(a_transport.relpath(url))
 
 
523
            except errors.NotBranchError, e:
 
 
525
            new_t = a_transport.clone('..')
 
 
526
            if new_t.base == a_transport.base:
 
 
527
                # reached the root, whatever that may be
 
 
528
                raise errors.NotBranchError(path=url)
 
 
531
    def open_repository(self, _unsupported=False):
 
 
532
        """Open the repository object at this BzrDir if one is present.
 
 
534
        This will not follow the Branch object pointer - its strictly a direct
 
 
535
        open facility. Most client code should use open_branch().repository to
 
 
538
        _unsupported is a private parameter, not part of the api.
 
 
539
        TODO: static convenience version of this?
 
 
541
        raise NotImplementedError(self.open_repository)
 
 
543
    def open_workingtree(self, _unsupported=False):
 
 
544
        """Open the workingtree object at this BzrDir if one is present.
 
 
546
        TODO: static convenience version of this?
 
 
548
        raise NotImplementedError(self.open_workingtree)
 
 
550
    def has_branch(self):
 
 
551
        """Tell if this bzrdir contains a branch.
 
 
553
        Note: if you're going to open the branch, you should just go ahead
 
 
554
        and try, and not ask permission first.  (This method just opens the 
 
 
555
        branch and discards it, and that's somewhat expensive.) 
 
 
560
        except errors.NotBranchError:
 
 
563
    def has_workingtree(self):
 
 
564
        """Tell if this bzrdir contains a working tree.
 
 
566
        This will still raise an exception if the bzrdir has a workingtree that
 
 
567
        is remote & inaccessible.
 
 
569
        Note: if you're going to open the working tree, you should just go ahead
 
 
570
        and try, and not ask permission first.  (This method just opens the 
 
 
571
        workingtree and discards it, and that's somewhat expensive.) 
 
 
574
            self.open_workingtree()
 
 
576
        except errors.NoWorkingTree:
 
 
579
    def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
 
 
580
        """Create a copy of this bzrdir prepared for use as a new line of
 
 
583
        If urls last component does not exist, it will be created.
 
 
585
        Attributes related to the identity of the source branch like
 
 
586
        branch nickname will be cleaned, a working tree is created
 
 
587
        whether one existed before or not; and a local branch is always
 
 
590
        if revision_id is not None, then the clone operation may tune
 
 
591
            itself to download less data.
 
 
594
        result = self._format.initialize(url)
 
 
595
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
 
597
            source_branch = self.open_branch()
 
 
598
            source_repository = source_branch.repository
 
 
599
        except errors.NotBranchError:
 
 
602
                source_repository = self.open_repository()
 
 
603
            except errors.NoRepositoryPresent:
 
 
604
                # copy the entire basis one if there is one
 
 
605
                # but there is no repository.
 
 
606
                source_repository = basis_repo
 
 
611
                result_repo = result.find_repository()
 
 
612
            except errors.NoRepositoryPresent:
 
 
614
        if source_repository is None and result_repo is not None:
 
 
616
        elif source_repository is None and result_repo is None:
 
 
617
            # no repo available, make a new one
 
 
618
            result.create_repository()
 
 
619
        elif source_repository is not None and result_repo is None:
 
 
620
            # have source, and want to make a new target repo
 
 
621
            # we don't clone the repo because that preserves attributes
 
 
622
            # like is_shared(), and we have not yet implemented a 
 
 
623
            # repository sprout().
 
 
624
            result_repo = result.create_repository()
 
 
625
        if result_repo is not None:
 
 
626
            # fetch needed content into target.
 
 
628
                # XXX FIXME RBC 20060214 need tests for this when the basis
 
 
630
                result_repo.fetch(basis_repo, revision_id=revision_id)
 
 
631
            if source_repository is not None:
 
 
632
                result_repo.fetch(source_repository, revision_id=revision_id)
 
 
633
        if source_branch is not None:
 
 
634
            source_branch.sprout(result, revision_id=revision_id)
 
 
636
            result.create_branch()
 
 
637
        # TODO: jam 20060426 we probably need a test in here in the
 
 
638
        #       case that the newly sprouted branch is a remote one
 
 
639
        if result_repo is None or result_repo.make_working_trees():
 
 
640
            result.create_workingtree()
 
 
644
class BzrDirPreSplitOut(BzrDir):
 
 
645
    """A common class for the all-in-one formats."""
 
 
647
    def __init__(self, _transport, _format):
 
 
648
        """See BzrDir.__init__."""
 
 
649
        super(BzrDirPreSplitOut, self).__init__(_transport, _format)
 
 
650
        assert self._format._lock_class == TransportLock
 
 
651
        assert self._format._lock_file_name == 'branch-lock'
 
 
652
        self._control_files = LockableFiles(self.get_branch_transport(None),
 
 
653
                                            self._format._lock_file_name,
 
 
654
                                            self._format._lock_class)
 
 
656
    def break_lock(self):
 
 
657
        """Pre-splitout bzrdirs do not suffer from stale locks."""
 
 
658
        raise NotImplementedError(self.break_lock)
 
 
660
    def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
 
 
661
        """See BzrDir.clone()."""
 
 
662
        from bzrlib.workingtree import WorkingTreeFormat2
 
 
664
        result = self._format._initialize_for_clone(url)
 
 
665
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
 
666
        self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
 
 
667
        from_branch = self.open_branch()
 
 
668
        from_branch.clone(result, revision_id=revision_id)
 
 
670
            self.open_workingtree().clone(result, basis=basis_tree)
 
 
671
        except errors.NotLocalUrl:
 
 
672
            # make a new one, this format always has to have one.
 
 
674
                WorkingTreeFormat2().initialize(result)
 
 
675
            except errors.NotLocalUrl:
 
 
676
                # but we cannot do it for remote trees.
 
 
677
                to_branch = result.open_branch()
 
 
678
                WorkingTreeFormat2().stub_initialize_remote(to_branch.control_files)
 
 
681
    def create_branch(self):
 
 
682
        """See BzrDir.create_branch."""
 
 
683
        return self.open_branch()
 
 
685
    def create_repository(self, shared=False):
 
 
686
        """See BzrDir.create_repository."""
 
 
688
            raise errors.IncompatibleFormat('shared repository', self._format)
 
 
689
        return self.open_repository()
 
 
691
    def create_workingtree(self, revision_id=None):
 
 
692
        """See BzrDir.create_workingtree."""
 
 
693
        # this looks buggy but is not -really-
 
 
694
        # clone and sprout will have set the revision_id
 
 
695
        # and that will have set it for us, its only
 
 
696
        # specific uses of create_workingtree in isolation
 
 
697
        # that can do wonky stuff here, and that only
 
 
698
        # happens for creating checkouts, which cannot be 
 
 
699
        # done on this format anyway. So - acceptable wart.
 
 
700
        result = self.open_workingtree()
 
 
701
        if revision_id is not None:
 
 
702
            if revision_id == bzrlib.revision.NULL_REVISION:
 
 
703
                result.set_parent_ids([])
 
 
705
                result.set_parent_ids([revision_id])
 
 
708
    def get_branch_transport(self, branch_format):
 
 
709
        """See BzrDir.get_branch_transport()."""
 
 
710
        if branch_format is None:
 
 
711
            return self.transport
 
 
713
            branch_format.get_format_string()
 
 
714
        except NotImplementedError:
 
 
715
            return self.transport
 
 
716
        raise errors.IncompatibleFormat(branch_format, self._format)
 
 
718
    def get_repository_transport(self, repository_format):
 
 
719
        """See BzrDir.get_repository_transport()."""
 
 
720
        if repository_format is None:
 
 
721
            return self.transport
 
 
723
            repository_format.get_format_string()
 
 
724
        except NotImplementedError:
 
 
725
            return self.transport
 
 
726
        raise errors.IncompatibleFormat(repository_format, self._format)
 
 
728
    def get_workingtree_transport(self, workingtree_format):
 
 
729
        """See BzrDir.get_workingtree_transport()."""
 
 
730
        if workingtree_format is None:
 
 
731
            return self.transport
 
 
733
            workingtree_format.get_format_string()
 
 
734
        except NotImplementedError:
 
 
735
            return self.transport
 
 
736
        raise errors.IncompatibleFormat(workingtree_format, self._format)
 
 
738
    def needs_format_conversion(self, format=None):
 
 
739
        """See BzrDir.needs_format_conversion()."""
 
 
740
        # if the format is not the same as the system default,
 
 
741
        # an upgrade is needed.
 
 
743
            format = BzrDirFormat.get_default_format()
 
 
744
        return not isinstance(self._format, format.__class__)
 
 
746
    def open_branch(self, unsupported=False):
 
 
747
        """See BzrDir.open_branch."""
 
 
748
        from bzrlib.branch import BzrBranchFormat4
 
 
749
        format = BzrBranchFormat4()
 
 
750
        self._check_supported(format, unsupported)
 
 
751
        return format.open(self, _found=True)
 
 
753
    def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
 
 
754
        """See BzrDir.sprout()."""
 
 
755
        from bzrlib.workingtree import WorkingTreeFormat2
 
 
757
        result = self._format._initialize_for_clone(url)
 
 
758
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
 
760
            self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
 
 
761
        except errors.NoRepositoryPresent:
 
 
764
            self.open_branch().sprout(result, revision_id=revision_id)
 
 
765
        except errors.NotBranchError:
 
 
767
        # we always want a working tree
 
 
768
        WorkingTreeFormat2().initialize(result)
 
 
772
class BzrDir4(BzrDirPreSplitOut):
 
 
773
    """A .bzr version 4 control object.
 
 
775
    This is a deprecated format and may be removed after sept 2006.
 
 
778
    def create_repository(self, shared=False):
 
 
779
        """See BzrDir.create_repository."""
 
 
780
        return self._format.repository_format.initialize(self, shared)
 
 
782
    def needs_format_conversion(self, format=None):
 
 
783
        """Format 4 dirs are always in need of conversion."""
 
 
786
    def open_repository(self):
 
 
787
        """See BzrDir.open_repository."""
 
 
788
        from bzrlib.repository import RepositoryFormat4
 
 
789
        return RepositoryFormat4().open(self, _found=True)
 
 
792
class BzrDir5(BzrDirPreSplitOut):
 
 
793
    """A .bzr version 5 control object.
 
 
795
    This is a deprecated format and may be removed after sept 2006.
 
 
798
    def open_repository(self):
 
 
799
        """See BzrDir.open_repository."""
 
 
800
        from bzrlib.repository import RepositoryFormat5
 
 
801
        return RepositoryFormat5().open(self, _found=True)
 
 
803
    def open_workingtree(self, _unsupported=False):
 
 
804
        """See BzrDir.create_workingtree."""
 
 
805
        from bzrlib.workingtree import WorkingTreeFormat2
 
 
806
        return WorkingTreeFormat2().open(self, _found=True)
 
 
809
class BzrDir6(BzrDirPreSplitOut):
 
 
810
    """A .bzr version 6 control object.
 
 
812
    This is a deprecated format and may be removed after sept 2006.
 
 
815
    def open_repository(self):
 
 
816
        """See BzrDir.open_repository."""
 
 
817
        from bzrlib.repository import RepositoryFormat6
 
 
818
        return RepositoryFormat6().open(self, _found=True)
 
 
820
    def open_workingtree(self, _unsupported=False):
 
 
821
        """See BzrDir.create_workingtree."""
 
 
822
        from bzrlib.workingtree import WorkingTreeFormat2
 
 
823
        return WorkingTreeFormat2().open(self, _found=True)
 
 
826
class BzrDirMeta1(BzrDir):
 
 
827
    """A .bzr meta version 1 control object.
 
 
829
    This is the first control object where the 
 
 
830
    individual aspects are really split out: there are separate repository,
 
 
831
    workingtree and branch subdirectories and any subset of the three can be
 
 
832
    present within a BzrDir.
 
 
835
    def can_convert_format(self):
 
 
836
        """See BzrDir.can_convert_format()."""
 
 
839
    def create_branch(self):
 
 
840
        """See BzrDir.create_branch."""
 
 
841
        from bzrlib.branch import BranchFormat
 
 
842
        return BranchFormat.get_default_format().initialize(self)
 
 
844
    def create_repository(self, shared=False):
 
 
845
        """See BzrDir.create_repository."""
 
 
846
        return self._format.repository_format.initialize(self, shared)
 
 
848
    def create_workingtree(self, revision_id=None):
 
 
849
        """See BzrDir.create_workingtree."""
 
 
850
        from bzrlib.workingtree import WorkingTreeFormat
 
 
851
        return WorkingTreeFormat.get_default_format().initialize(self, revision_id)
 
 
853
    def _get_mkdir_mode(self):
 
 
854
        """Figure out the mode to use when creating a bzrdir subdir."""
 
 
855
        temp_control = LockableFiles(self.transport, '', TransportLock)
 
 
856
        return temp_control._dir_mode
 
 
858
    def get_branch_transport(self, branch_format):
 
 
859
        """See BzrDir.get_branch_transport()."""
 
 
860
        if branch_format is None:
 
 
861
            return self.transport.clone('branch')
 
 
863
            branch_format.get_format_string()
 
 
864
        except NotImplementedError:
 
 
865
            raise errors.IncompatibleFormat(branch_format, self._format)
 
 
867
            self.transport.mkdir('branch', mode=self._get_mkdir_mode())
 
 
868
        except errors.FileExists:
 
 
870
        return self.transport.clone('branch')
 
 
872
    def get_repository_transport(self, repository_format):
 
 
873
        """See BzrDir.get_repository_transport()."""
 
 
874
        if repository_format is None:
 
 
875
            return self.transport.clone('repository')
 
 
877
            repository_format.get_format_string()
 
 
878
        except NotImplementedError:
 
 
879
            raise errors.IncompatibleFormat(repository_format, self._format)
 
 
881
            self.transport.mkdir('repository', mode=self._get_mkdir_mode())
 
 
882
        except errors.FileExists:
 
 
884
        return self.transport.clone('repository')
 
 
886
    def get_workingtree_transport(self, workingtree_format):
 
 
887
        """See BzrDir.get_workingtree_transport()."""
 
 
888
        if workingtree_format is None:
 
 
889
            return self.transport.clone('checkout')
 
 
891
            workingtree_format.get_format_string()
 
 
892
        except NotImplementedError:
 
 
893
            raise errors.IncompatibleFormat(workingtree_format, self._format)
 
 
895
            self.transport.mkdir('checkout', mode=self._get_mkdir_mode())
 
 
896
        except errors.FileExists:
 
 
898
        return self.transport.clone('checkout')
 
 
900
    def needs_format_conversion(self, format=None):
 
 
901
        """See BzrDir.needs_format_conversion()."""
 
 
903
            format = BzrDirFormat.get_default_format()
 
 
904
        if not isinstance(self._format, format.__class__):
 
 
905
            # it is not a meta dir format, conversion is needed.
 
 
907
        # we might want to push this down to the repository?
 
 
909
            if not isinstance(self.open_repository()._format,
 
 
910
                              format.repository_format.__class__):
 
 
911
                # the repository needs an upgrade.
 
 
913
        except errors.NoRepositoryPresent:
 
 
915
        # currently there are no other possible conversions for meta1 formats.
 
 
918
    def open_branch(self, unsupported=False):
 
 
919
        """See BzrDir.open_branch."""
 
 
920
        from bzrlib.branch import BranchFormat
 
 
921
        format = BranchFormat.find_format(self)
 
 
922
        self._check_supported(format, unsupported)
 
 
923
        return format.open(self, _found=True)
 
 
925
    def open_repository(self, unsupported=False):
 
 
926
        """See BzrDir.open_repository."""
 
 
927
        from bzrlib.repository import RepositoryFormat
 
 
928
        format = RepositoryFormat.find_format(self)
 
 
929
        self._check_supported(format, unsupported)
 
 
930
        return format.open(self, _found=True)
 
 
932
    def open_workingtree(self, unsupported=False):
 
 
933
        """See BzrDir.open_workingtree."""
 
 
934
        from bzrlib.workingtree import WorkingTreeFormat
 
 
935
        format = WorkingTreeFormat.find_format(self)
 
 
936
        self._check_supported(format, unsupported)
 
 
937
        return format.open(self, _found=True)
 
 
940
class BzrDirFormat(object):
 
 
941
    """An encapsulation of the initialization and open routines for a format.
 
 
943
    Formats provide three things:
 
 
944
     * An initialization routine,
 
 
948
    Formats are placed in an dict by their format string for reference 
 
 
949
    during bzrdir opening. These should be subclasses of BzrDirFormat
 
 
952
    Once a format is deprecated, just deprecate the initialize and open
 
 
953
    methods on the format class. Do not deprecate the object, as the 
 
 
954
    object will be created every system load.
 
 
957
    _default_format = None
 
 
958
    """The default format used for new .bzr dirs."""
 
 
961
    """The known formats."""
 
 
963
    _control_formats = []
 
 
964
    """The registered control formats - .bzr, ....
 
 
966
    This is a list of BzrDirFormat objects.
 
 
969
    _lock_file_name = 'branch-lock'
 
 
971
    # _lock_class must be set in subclasses to the lock type, typ.
 
 
972
    # TransportLock or LockDir
 
 
975
    def find_format(klass, transport):
 
 
976
        """Return the format present at transport."""
 
 
977
        for format in klass._control_formats:
 
 
979
                return format.probe_transport(transport)
 
 
980
            except errors.NotBranchError:
 
 
981
                # this format does not find a control dir here.
 
 
983
        raise errors.NotBranchError(path=transport.base)
 
 
986
    def probe_transport(klass, transport):
 
 
987
        """Return the .bzrdir style transport present at URL."""
 
 
989
            format_string = transport.get(".bzr/branch-format").read()
 
 
990
        except errors.NoSuchFile:
 
 
991
            raise errors.NotBranchError(path=transport.base)
 
 
994
            return klass._formats[format_string]
 
 
996
            raise errors.UnknownFormatError(format=format_string)
 
 
999
    def get_default_format(klass):
 
 
1000
        """Return the current default format."""
 
 
1001
        return klass._default_format
 
 
1003
    def get_format_string(self):
 
 
1004
        """Return the ASCII format string that identifies this format."""
 
 
1005
        raise NotImplementedError(self.get_format_string)
 
 
1007
    def get_format_description(self):
 
 
1008
        """Return the short description for this format."""
 
 
1009
        raise NotImplementedError(self.get_format_description)
 
 
1011
    def get_converter(self, format=None):
 
 
1012
        """Return the converter to use to convert bzrdirs needing converts.
 
 
1014
        This returns a bzrlib.bzrdir.Converter object.
 
 
1016
        This should return the best upgrader to step this format towards the
 
 
1017
        current default format. In the case of plugins we can/should provide
 
 
1018
        some means for them to extend the range of returnable converters.
 
 
1020
        :param format: Optional format to override the default format of the 
 
 
1023
        raise NotImplementedError(self.get_converter)
 
 
1025
    def initialize(self, url):
 
 
1026
        """Create a bzr control dir at this url and return an opened copy.
 
 
1028
        Subclasses should typically override initialize_on_transport
 
 
1029
        instead of this method.
 
 
1031
        return self.initialize_on_transport(get_transport(url))
 
 
1033
    def initialize_on_transport(self, transport):
 
 
1034
        """Initialize a new bzrdir in the base directory of a Transport."""
 
 
1035
        # Since we don't have a .bzr directory, inherit the
 
 
1036
        # mode from the root directory
 
 
1037
        temp_control = LockableFiles(transport, '', TransportLock)
 
 
1038
        temp_control._transport.mkdir('.bzr',
 
 
1039
                                      # FIXME: RBC 20060121 don't peek under
 
 
1041
                                      mode=temp_control._dir_mode)
 
 
1042
        file_mode = temp_control._file_mode
 
 
1044
        mutter('created control directory in ' + transport.base)
 
 
1045
        control = transport.clone('.bzr')
 
 
1046
        utf8_files = [('README', 
 
 
1047
                       "This is a Bazaar-NG control directory.\n"
 
 
1048
                       "Do not change any files in this directory.\n"),
 
 
1049
                      ('branch-format', self.get_format_string()),
 
 
1051
        # NB: no need to escape relative paths that are url safe.
 
 
1052
        control_files = LockableFiles(control, self._lock_file_name, 
 
 
1054
        control_files.create_lock()
 
 
1055
        control_files.lock_write()
 
 
1057
            for file, content in utf8_files:
 
 
1058
                control_files.put_utf8(file, content)
 
 
1060
            control_files.unlock()
 
 
1061
        return self.open(transport, _found=True)
 
 
1063
    def is_supported(self):
 
 
1064
        """Is this format supported?
 
 
1066
        Supported formats must be initializable and openable.
 
 
1067
        Unsupported formats may not support initialization or committing or 
 
 
1068
        some other features depending on the reason for not being supported.
 
 
1073
    def known_formats(klass):
 
 
1074
        """Return all the known formats.
 
 
1076
        Concrete formats should override _known_formats.
 
 
1078
        # There is double indirection here to make sure that control 
 
 
1079
        # formats used by more than one dir format will only be probed 
 
 
1080
        # once. This can otherwise be quite expensive for remote connections.
 
 
1082
        for format in klass._control_formats:
 
 
1083
            result.update(format._known_formats())
 
 
1087
    def _known_formats(klass):
 
 
1088
        """Return the known format instances for this control format."""
 
 
1089
        return set(klass._formats.values())
 
 
1091
    def open(self, transport, _found=False):
 
 
1092
        """Return an instance of this format for the dir transport points at.
 
 
1094
        _found is a private parameter, do not use it.
 
 
1097
            assert isinstance(BzrDirFormat.find_format(transport),
 
 
1099
        return self._open(transport)
 
 
1101
    def _open(self, transport):
 
 
1102
        """Template method helper for opening BzrDirectories.
 
 
1104
        This performs the actual open and any additional logic or parameter
 
 
1107
        raise NotImplementedError(self._open)
 
 
1110
    def register_format(klass, format):
 
 
1111
        klass._formats[format.get_format_string()] = format
 
 
1114
    def register_control_format(klass, format):
 
 
1115
        """Register a format that does not use '.bzrdir' for its control dir.
 
 
1117
        TODO: This should be pulled up into a 'ControlDirFormat' base class
 
 
1118
        which BzrDirFormat can inherit from, and renamed to register_format 
 
 
1119
        there. It has been done without that for now for simplicity of
 
 
1122
        klass._control_formats.append(format)
 
 
1125
    def set_default_format(klass, format):
 
 
1126
        klass._default_format = format
 
 
1129
        return self.get_format_string()[:-1]
 
 
1132
    def unregister_format(klass, format):
 
 
1133
        assert klass._formats[format.get_format_string()] is format
 
 
1134
        del klass._formats[format.get_format_string()]
 
 
1137
    def unregister_control_format(klass, format):
 
 
1138
        klass._control_formats.remove(format)
 
 
1141
# register BzrDirFormat as a control format
 
 
1142
BzrDirFormat.register_control_format(BzrDirFormat)
 
 
1145
class BzrDirFormat4(BzrDirFormat):
 
 
1146
    """Bzr dir format 4.
 
 
1148
    This format is a combined format for working tree, branch and repository.
 
 
1150
     - Format 1 working trees [always]
 
 
1151
     - Format 4 branches [always]
 
 
1152
     - Format 4 repositories [always]
 
 
1154
    This format is deprecated: it indexes texts using a text it which is
 
 
1155
    removed in format 5; write support for this format has been removed.
 
 
1158
    _lock_class = TransportLock
 
 
1160
    def get_format_string(self):
 
 
1161
        """See BzrDirFormat.get_format_string()."""
 
 
1162
        return "Bazaar-NG branch, format 0.0.4\n"
 
 
1164
    def get_format_description(self):
 
 
1165
        """See BzrDirFormat.get_format_description()."""
 
 
1166
        return "All-in-one format 4"
 
 
1168
    def get_converter(self, format=None):
 
 
1169
        """See BzrDirFormat.get_converter()."""
 
 
1170
        # there is one and only one upgrade path here.
 
 
1171
        return ConvertBzrDir4To5()
 
 
1173
    def initialize_on_transport(self, transport):
 
 
1174
        """Format 4 branches cannot be created."""
 
 
1175
        raise errors.UninitializableFormat(self)
 
 
1177
    def is_supported(self):
 
 
1178
        """Format 4 is not supported.
 
 
1180
        It is not supported because the model changed from 4 to 5 and the
 
 
1181
        conversion logic is expensive - so doing it on the fly was not 
 
 
1186
    def _open(self, transport):
 
 
1187
        """See BzrDirFormat._open."""
 
 
1188
        return BzrDir4(transport, self)
 
 
1190
    def __return_repository_format(self):
 
 
1191
        """Circular import protection."""
 
 
1192
        from bzrlib.repository import RepositoryFormat4
 
 
1193
        return RepositoryFormat4(self)
 
 
1194
    repository_format = property(__return_repository_format)
 
 
1197
class BzrDirFormat5(BzrDirFormat):
 
 
1198
    """Bzr control format 5.
 
 
1200
    This format is a combined format for working tree, branch and repository.
 
 
1202
     - Format 2 working trees [always] 
 
 
1203
     - Format 4 branches [always] 
 
 
1204
     - Format 5 repositories [always]
 
 
1205
       Unhashed stores in the repository.
 
 
1208
    _lock_class = TransportLock
 
 
1210
    def get_format_string(self):
 
 
1211
        """See BzrDirFormat.get_format_string()."""
 
 
1212
        return "Bazaar-NG branch, format 5\n"
 
 
1214
    def get_format_description(self):
 
 
1215
        """See BzrDirFormat.get_format_description()."""
 
 
1216
        return "All-in-one format 5"
 
 
1218
    def get_converter(self, format=None):
 
 
1219
        """See BzrDirFormat.get_converter()."""
 
 
1220
        # there is one and only one upgrade path here.
 
 
1221
        return ConvertBzrDir5To6()
 
 
1223
    def _initialize_for_clone(self, url):
 
 
1224
        return self.initialize_on_transport(get_transport(url), _cloning=True)
 
 
1226
    def initialize_on_transport(self, transport, _cloning=False):
 
 
1227
        """Format 5 dirs always have working tree, branch and repository.
 
 
1229
        Except when they are being cloned.
 
 
1231
        from bzrlib.branch import BzrBranchFormat4
 
 
1232
        from bzrlib.repository import RepositoryFormat5
 
 
1233
        from bzrlib.workingtree import WorkingTreeFormat2
 
 
1234
        result = (super(BzrDirFormat5, self).initialize_on_transport(transport))
 
 
1235
        RepositoryFormat5().initialize(result, _internal=True)
 
 
1237
            branch = BzrBranchFormat4().initialize(result)
 
 
1239
                WorkingTreeFormat2().initialize(result)
 
 
1240
            except errors.NotLocalUrl:
 
 
1241
                # Even though we can't access the working tree, we need to
 
 
1242
                # create its control files.
 
 
1243
                WorkingTreeFormat2().stub_initialize_remote(branch.control_files)
 
 
1246
    def _open(self, transport):
 
 
1247
        """See BzrDirFormat._open."""
 
 
1248
        return BzrDir5(transport, self)
 
 
1250
    def __return_repository_format(self):
 
 
1251
        """Circular import protection."""
 
 
1252
        from bzrlib.repository import RepositoryFormat5
 
 
1253
        return RepositoryFormat5(self)
 
 
1254
    repository_format = property(__return_repository_format)
 
 
1257
class BzrDirFormat6(BzrDirFormat):
 
 
1258
    """Bzr control format 6.
 
 
1260
    This format is a combined format for working tree, branch and repository.
 
 
1262
     - Format 2 working trees [always] 
 
 
1263
     - Format 4 branches [always] 
 
 
1264
     - Format 6 repositories [always]
 
 
1267
    _lock_class = TransportLock
 
 
1269
    def get_format_string(self):
 
 
1270
        """See BzrDirFormat.get_format_string()."""
 
 
1271
        return "Bazaar-NG branch, format 6\n"
 
 
1273
    def get_format_description(self):
 
 
1274
        """See BzrDirFormat.get_format_description()."""
 
 
1275
        return "All-in-one format 6"
 
 
1277
    def get_converter(self, format=None):
 
 
1278
        """See BzrDirFormat.get_converter()."""
 
 
1279
        # there is one and only one upgrade path here.
 
 
1280
        return ConvertBzrDir6ToMeta()
 
 
1282
    def _initialize_for_clone(self, url):
 
 
1283
        return self.initialize_on_transport(get_transport(url), _cloning=True)
 
 
1285
    def initialize_on_transport(self, transport, _cloning=False):
 
 
1286
        """Format 6 dirs always have working tree, branch and repository.
 
 
1288
        Except when they are being cloned.
 
 
1290
        from bzrlib.branch import BzrBranchFormat4
 
 
1291
        from bzrlib.repository import RepositoryFormat6
 
 
1292
        from bzrlib.workingtree import WorkingTreeFormat2
 
 
1293
        result = super(BzrDirFormat6, self).initialize_on_transport(transport)
 
 
1294
        RepositoryFormat6().initialize(result, _internal=True)
 
 
1296
            branch = BzrBranchFormat4().initialize(result)
 
 
1298
                WorkingTreeFormat2().initialize(result)
 
 
1299
            except errors.NotLocalUrl:
 
 
1300
                # Even though we can't access the working tree, we need to
 
 
1301
                # create its control files.
 
 
1302
                WorkingTreeFormat2().stub_initialize_remote(branch.control_files)
 
 
1305
    def _open(self, transport):
 
 
1306
        """See BzrDirFormat._open."""
 
 
1307
        return BzrDir6(transport, self)
 
 
1309
    def __return_repository_format(self):
 
 
1310
        """Circular import protection."""
 
 
1311
        from bzrlib.repository import RepositoryFormat6
 
 
1312
        return RepositoryFormat6(self)
 
 
1313
    repository_format = property(__return_repository_format)
 
 
1316
class BzrDirMetaFormat1(BzrDirFormat):
 
 
1317
    """Bzr meta control format 1
 
 
1319
    This is the first format with split out working tree, branch and repository
 
 
1322
     - Format 3 working trees [optional]
 
 
1323
     - Format 5 branches [optional]
 
 
1324
     - Format 7 repositories [optional]
 
 
1327
    _lock_class = LockDir
 
 
1329
    def get_converter(self, format=None):
 
 
1330
        """See BzrDirFormat.get_converter()."""
 
 
1332
            format = BzrDirFormat.get_default_format()
 
 
1333
        if not isinstance(self, format.__class__):
 
 
1334
            # converting away from metadir is not implemented
 
 
1335
            raise NotImplementedError(self.get_converter)
 
 
1336
        return ConvertMetaToMeta(format)
 
 
1338
    def get_format_string(self):
 
 
1339
        """See BzrDirFormat.get_format_string()."""
 
 
1340
        return "Bazaar-NG meta directory, format 1\n"
 
 
1342
    def get_format_description(self):
 
 
1343
        """See BzrDirFormat.get_format_description()."""
 
 
1344
        return "Meta directory format 1"
 
 
1346
    def _open(self, transport):
 
 
1347
        """See BzrDirFormat._open."""
 
 
1348
        return BzrDirMeta1(transport, self)
 
 
1350
    def __return_repository_format(self):
 
 
1351
        """Circular import protection."""
 
 
1352
        if getattr(self, '_repository_format', None):
 
 
1353
            return self._repository_format
 
 
1354
        from bzrlib.repository import RepositoryFormat
 
 
1355
        return RepositoryFormat.get_default_format()
 
 
1357
    def __set_repository_format(self, value):
 
 
1358
        """Allow changint the repository format for metadir formats."""
 
 
1359
        self._repository_format = value
 
 
1361
    repository_format = property(__return_repository_format, __set_repository_format)
 
 
1364
BzrDirFormat.register_format(BzrDirFormat4())
 
 
1365
BzrDirFormat.register_format(BzrDirFormat5())
 
 
1366
BzrDirFormat.register_format(BzrDirFormat6())
 
 
1367
__default_format = BzrDirMetaFormat1()
 
 
1368
BzrDirFormat.register_format(__default_format)
 
 
1369
BzrDirFormat.set_default_format(__default_format)
 
 
1372
class BzrDirTestProviderAdapter(object):
 
 
1373
    """A tool to generate a suite testing multiple bzrdir formats at once.
 
 
1375
    This is done by copying the test once for each transport and injecting
 
 
1376
    the transport_server, transport_readonly_server, and bzrdir_format
 
 
1377
    classes into each copy. Each copy is also given a new id() to make it
 
 
1381
    def __init__(self, transport_server, transport_readonly_server, formats):
 
 
1382
        self._transport_server = transport_server
 
 
1383
        self._transport_readonly_server = transport_readonly_server
 
 
1384
        self._formats = formats
 
 
1386
    def adapt(self, test):
 
 
1387
        result = TestSuite()
 
 
1388
        for format in self._formats:
 
 
1389
            new_test = deepcopy(test)
 
 
1390
            new_test.transport_server = self._transport_server
 
 
1391
            new_test.transport_readonly_server = self._transport_readonly_server
 
 
1392
            new_test.bzrdir_format = format
 
 
1393
            def make_new_test_id():
 
 
1394
                new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
 
 
1395
                return lambda: new_id
 
 
1396
            new_test.id = make_new_test_id()
 
 
1397
            result.addTest(new_test)
 
 
1401
class Converter(object):
 
 
1402
    """Converts a disk format object from one format to another."""
 
 
1404
    def convert(self, to_convert, pb):
 
 
1405
        """Perform the conversion of to_convert, giving feedback via pb.
 
 
1407
        :param to_convert: The disk object to convert.
 
 
1408
        :param pb: a progress bar to use for progress information.
 
 
1411
    def step(self, message):
 
 
1412
        """Update the pb by a step."""
 
 
1414
        self.pb.update(message, self.count, self.total)
 
 
1417
class ConvertBzrDir4To5(Converter):
 
 
1418
    """Converts format 4 bzr dirs to format 5."""
 
 
1421
        super(ConvertBzrDir4To5, self).__init__()
 
 
1422
        self.converted_revs = set()
 
 
1423
        self.absent_revisions = set()
 
 
1427
    def convert(self, to_convert, pb):
 
 
1428
        """See Converter.convert()."""
 
 
1429
        self.bzrdir = to_convert
 
 
1431
        self.pb.note('starting upgrade from format 4 to 5')
 
 
1432
        if isinstance(self.bzrdir.transport, LocalTransport):
 
 
1433
            self.bzrdir.get_workingtree_transport(None).delete('stat-cache')
 
 
1434
        self._convert_to_weaves()
 
 
1435
        return BzrDir.open(self.bzrdir.root_transport.base)
 
 
1437
    def _convert_to_weaves(self):
 
 
1438
        self.pb.note('note: upgrade may be faster if all store files are ungzipped first')
 
 
1441
            stat = self.bzrdir.transport.stat('weaves')
 
 
1442
            if not S_ISDIR(stat.st_mode):
 
 
1443
                self.bzrdir.transport.delete('weaves')
 
 
1444
                self.bzrdir.transport.mkdir('weaves')
 
 
1445
        except errors.NoSuchFile:
 
 
1446
            self.bzrdir.transport.mkdir('weaves')
 
 
1447
        # deliberately not a WeaveFile as we want to build it up slowly.
 
 
1448
        self.inv_weave = Weave('inventory')
 
 
1449
        # holds in-memory weaves for all files
 
 
1450
        self.text_weaves = {}
 
 
1451
        self.bzrdir.transport.delete('branch-format')
 
 
1452
        self.branch = self.bzrdir.open_branch()
 
 
1453
        self._convert_working_inv()
 
 
1454
        rev_history = self.branch.revision_history()
 
 
1455
        # to_read is a stack holding the revisions we still need to process;
 
 
1456
        # appending to it adds new highest-priority revisions
 
 
1457
        self.known_revisions = set(rev_history)
 
 
1458
        self.to_read = rev_history[-1:]
 
 
1460
            rev_id = self.to_read.pop()
 
 
1461
            if (rev_id not in self.revisions
 
 
1462
                and rev_id not in self.absent_revisions):
 
 
1463
                self._load_one_rev(rev_id)
 
 
1465
        to_import = self._make_order()
 
 
1466
        for i, rev_id in enumerate(to_import):
 
 
1467
            self.pb.update('converting revision', i, len(to_import))
 
 
1468
            self._convert_one_rev(rev_id)
 
 
1470
        self._write_all_weaves()
 
 
1471
        self._write_all_revs()
 
 
1472
        self.pb.note('upgraded to weaves:')
 
 
1473
        self.pb.note('  %6d revisions and inventories', len(self.revisions))
 
 
1474
        self.pb.note('  %6d revisions not present', len(self.absent_revisions))
 
 
1475
        self.pb.note('  %6d texts', self.text_count)
 
 
1476
        self._cleanup_spare_files_after_format4()
 
 
1477
        self.branch.control_files.put_utf8('branch-format', BzrDirFormat5().get_format_string())
 
 
1479
    def _cleanup_spare_files_after_format4(self):
 
 
1480
        # FIXME working tree upgrade foo.
 
 
1481
        for n in 'merged-patches', 'pending-merged-patches':
 
 
1483
                ## assert os.path.getsize(p) == 0
 
 
1484
                self.bzrdir.transport.delete(n)
 
 
1485
            except errors.NoSuchFile:
 
 
1487
        self.bzrdir.transport.delete_tree('inventory-store')
 
 
1488
        self.bzrdir.transport.delete_tree('text-store')
 
 
1490
    def _convert_working_inv(self):
 
 
1491
        inv = serializer_v4.read_inventory(self.branch.control_files.get('inventory'))
 
 
1492
        new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
 
 
1493
        # FIXME inventory is a working tree change.
 
 
1494
        self.branch.control_files.put('inventory', StringIO(new_inv_xml))
 
 
1496
    def _write_all_weaves(self):
 
 
1497
        controlweaves = WeaveStore(self.bzrdir.transport, prefixed=False)
 
 
1498
        weave_transport = self.bzrdir.transport.clone('weaves')
 
 
1499
        weaves = WeaveStore(weave_transport, prefixed=False)
 
 
1500
        transaction = WriteTransaction()
 
 
1504
            for file_id, file_weave in self.text_weaves.items():
 
 
1505
                self.pb.update('writing weave', i, len(self.text_weaves))
 
 
1506
                weaves._put_weave(file_id, file_weave, transaction)
 
 
1508
            self.pb.update('inventory', 0, 1)
 
 
1509
            controlweaves._put_weave('inventory', self.inv_weave, transaction)
 
 
1510
            self.pb.update('inventory', 1, 1)
 
 
1514
    def _write_all_revs(self):
 
 
1515
        """Write all revisions out in new form."""
 
 
1516
        self.bzrdir.transport.delete_tree('revision-store')
 
 
1517
        self.bzrdir.transport.mkdir('revision-store')
 
 
1518
        revision_transport = self.bzrdir.transport.clone('revision-store')
 
 
1520
        _revision_store = TextRevisionStore(TextStore(revision_transport,
 
 
1524
            transaction = bzrlib.transactions.WriteTransaction()
 
 
1525
            for i, rev_id in enumerate(self.converted_revs):
 
 
1526
                self.pb.update('write revision', i, len(self.converted_revs))
 
 
1527
                _revision_store.add_revision(self.revisions[rev_id], transaction)
 
 
1531
    def _load_one_rev(self, rev_id):
 
 
1532
        """Load a revision object into memory.
 
 
1534
        Any parents not either loaded or abandoned get queued to be
 
 
1536
        self.pb.update('loading revision',
 
 
1537
                       len(self.revisions),
 
 
1538
                       len(self.known_revisions))
 
 
1539
        if not self.branch.repository.has_revision(rev_id):
 
 
1541
            self.pb.note('revision {%s} not present in branch; '
 
 
1542
                         'will be converted as a ghost',
 
 
1544
            self.absent_revisions.add(rev_id)
 
 
1546
            rev = self.branch.repository._revision_store.get_revision(rev_id,
 
 
1547
                self.branch.repository.get_transaction())
 
 
1548
            for parent_id in rev.parent_ids:
 
 
1549
                self.known_revisions.add(parent_id)
 
 
1550
                self.to_read.append(parent_id)
 
 
1551
            self.revisions[rev_id] = rev
 
 
1553
    def _load_old_inventory(self, rev_id):
 
 
1554
        assert rev_id not in self.converted_revs
 
 
1555
        old_inv_xml = self.branch.repository.inventory_store.get(rev_id).read()
 
 
1556
        inv = serializer_v4.read_inventory_from_string(old_inv_xml)
 
 
1557
        rev = self.revisions[rev_id]
 
 
1558
        if rev.inventory_sha1:
 
 
1559
            assert rev.inventory_sha1 == sha_string(old_inv_xml), \
 
 
1560
                'inventory sha mismatch for {%s}' % rev_id
 
 
1563
    def _load_updated_inventory(self, rev_id):
 
 
1564
        assert rev_id in self.converted_revs
 
 
1565
        inv_xml = self.inv_weave.get_text(rev_id)
 
 
1566
        inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(inv_xml)
 
 
1569
    def _convert_one_rev(self, rev_id):
 
 
1570
        """Convert revision and all referenced objects to new format."""
 
 
1571
        rev = self.revisions[rev_id]
 
 
1572
        inv = self._load_old_inventory(rev_id)
 
 
1573
        present_parents = [p for p in rev.parent_ids
 
 
1574
                           if p not in self.absent_revisions]
 
 
1575
        self._convert_revision_contents(rev, inv, present_parents)
 
 
1576
        self._store_new_weave(rev, inv, present_parents)
 
 
1577
        self.converted_revs.add(rev_id)
 
 
1579
    def _store_new_weave(self, rev, inv, present_parents):
 
 
1580
        # the XML is now updated with text versions
 
 
1582
            entries = inv.iter_entries()
 
 
1584
            for path, ie in entries:
 
 
1585
                assert getattr(ie, 'revision', None) is not None, \
 
 
1586
                    'no revision on {%s} in {%s}' % \
 
 
1587
                    (file_id, rev.revision_id)
 
 
1588
        new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
 
 
1589
        new_inv_sha1 = sha_string(new_inv_xml)
 
 
1590
        self.inv_weave.add_lines(rev.revision_id, 
 
 
1592
                                 new_inv_xml.splitlines(True))
 
 
1593
        rev.inventory_sha1 = new_inv_sha1
 
 
1595
    def _convert_revision_contents(self, rev, inv, present_parents):
 
 
1596
        """Convert all the files within a revision.
 
 
1598
        Also upgrade the inventory to refer to the text revision ids."""
 
 
1599
        rev_id = rev.revision_id
 
 
1600
        mutter('converting texts of revision {%s}',
 
 
1602
        parent_invs = map(self._load_updated_inventory, present_parents)
 
 
1603
        entries = inv.iter_entries()
 
 
1605
        for path, ie in entries:
 
 
1606
            self._convert_file_version(rev, ie, parent_invs)
 
 
1608
    def _convert_file_version(self, rev, ie, parent_invs):
 
 
1609
        """Convert one version of one file.
 
 
1611
        The file needs to be added into the weave if it is a merge
 
 
1612
        of >=2 parents or if it's changed from its parent.
 
 
1614
        file_id = ie.file_id
 
 
1615
        rev_id = rev.revision_id
 
 
1616
        w = self.text_weaves.get(file_id)
 
 
1619
            self.text_weaves[file_id] = w
 
 
1620
        text_changed = False
 
 
1621
        previous_entries = ie.find_previous_heads(parent_invs,
 
 
1625
        for old_revision in previous_entries:
 
 
1626
                # if this fails, its a ghost ?
 
 
1627
                assert old_revision in self.converted_revs 
 
 
1628
        self.snapshot_ie(previous_entries, ie, w, rev_id)
 
 
1630
        assert getattr(ie, 'revision', None) is not None
 
 
1632
    def snapshot_ie(self, previous_revisions, ie, w, rev_id):
 
 
1633
        # TODO: convert this logic, which is ~= snapshot to
 
 
1634
        # a call to:. This needs the path figured out. rather than a work_tree
 
 
1635
        # a v4 revision_tree can be given, or something that looks enough like
 
 
1636
        # one to give the file content to the entry if it needs it.
 
 
1637
        # and we need something that looks like a weave store for snapshot to 
 
 
1639
        #ie.snapshot(rev, PATH, previous_revisions, REVISION_TREE, InMemoryWeaveStore(self.text_weaves))
 
 
1640
        if len(previous_revisions) == 1:
 
 
1641
            previous_ie = previous_revisions.values()[0]
 
 
1642
            if ie._unchanged(previous_ie):
 
 
1643
                ie.revision = previous_ie.revision
 
 
1646
            text = self.branch.repository.text_store.get(ie.text_id)
 
 
1647
            file_lines = text.readlines()
 
 
1648
            assert sha_strings(file_lines) == ie.text_sha1
 
 
1649
            assert sum(map(len, file_lines)) == ie.text_size
 
 
1650
            w.add_lines(rev_id, previous_revisions, file_lines)
 
 
1651
            self.text_count += 1
 
 
1653
            w.add_lines(rev_id, previous_revisions, [])
 
 
1654
        ie.revision = rev_id
 
 
1656
    def _make_order(self):
 
 
1657
        """Return a suitable order for importing revisions.
 
 
1659
        The order must be such that an revision is imported after all
 
 
1660
        its (present) parents.
 
 
1662
        todo = set(self.revisions.keys())
 
 
1663
        done = self.absent_revisions.copy()
 
 
1666
            # scan through looking for a revision whose parents
 
 
1668
            for rev_id in sorted(list(todo)):
 
 
1669
                rev = self.revisions[rev_id]
 
 
1670
                parent_ids = set(rev.parent_ids)
 
 
1671
                if parent_ids.issubset(done):
 
 
1672
                    # can take this one now
 
 
1673
                    order.append(rev_id)
 
 
1679
class ConvertBzrDir5To6(Converter):
 
 
1680
    """Converts format 5 bzr dirs to format 6."""
 
 
1682
    def convert(self, to_convert, pb):
 
 
1683
        """See Converter.convert()."""
 
 
1684
        self.bzrdir = to_convert
 
 
1686
        self.pb.note('starting upgrade from format 5 to 6')
 
 
1687
        self._convert_to_prefixed()
 
 
1688
        return BzrDir.open(self.bzrdir.root_transport.base)
 
 
1690
    def _convert_to_prefixed(self):
 
 
1691
        from bzrlib.store import TransportStore
 
 
1692
        self.bzrdir.transport.delete('branch-format')
 
 
1693
        for store_name in ["weaves", "revision-store"]:
 
 
1694
            self.pb.note("adding prefixes to %s" % store_name)
 
 
1695
            store_transport = self.bzrdir.transport.clone(store_name)
 
 
1696
            store = TransportStore(store_transport, prefixed=True)
 
 
1697
            for urlfilename in store_transport.list_dir('.'):
 
 
1698
                filename = urlutils.unescape(urlfilename)
 
 
1699
                if (filename.endswith(".weave") or
 
 
1700
                    filename.endswith(".gz") or
 
 
1701
                    filename.endswith(".sig")):
 
 
1702
                    file_id = os.path.splitext(filename)[0]
 
 
1705
                prefix_dir = store.hash_prefix(file_id)
 
 
1706
                # FIXME keep track of the dirs made RBC 20060121
 
 
1708
                    store_transport.move(filename, prefix_dir + '/' + filename)
 
 
1709
                except errors.NoSuchFile: # catches missing dirs strangely enough
 
 
1710
                    store_transport.mkdir(prefix_dir)
 
 
1711
                    store_transport.move(filename, prefix_dir + '/' + filename)
 
 
1712
        self.bzrdir._control_files.put_utf8('branch-format', BzrDirFormat6().get_format_string())
 
 
1715
class ConvertBzrDir6ToMeta(Converter):
 
 
1716
    """Converts format 6 bzr dirs to metadirs."""
 
 
1718
    def convert(self, to_convert, pb):
 
 
1719
        """See Converter.convert()."""
 
 
1720
        self.bzrdir = to_convert
 
 
1723
        self.total = 20 # the steps we know about
 
 
1724
        self.garbage_inventories = []
 
 
1726
        self.pb.note('starting upgrade from format 6 to metadir')
 
 
1727
        self.bzrdir._control_files.put_utf8('branch-format', "Converting to format 6")
 
 
1728
        # its faster to move specific files around than to open and use the apis...
 
 
1729
        # first off, nuke ancestry.weave, it was never used.
 
 
1731
            self.step('Removing ancestry.weave')
 
 
1732
            self.bzrdir.transport.delete('ancestry.weave')
 
 
1733
        except errors.NoSuchFile:
 
 
1735
        # find out whats there
 
 
1736
        self.step('Finding branch files')
 
 
1737
        last_revision = self.bzrdir.open_branch().last_revision()
 
 
1738
        bzrcontents = self.bzrdir.transport.list_dir('.')
 
 
1739
        for name in bzrcontents:
 
 
1740
            if name.startswith('basis-inventory.'):
 
 
1741
                self.garbage_inventories.append(name)
 
 
1742
        # create new directories for repository, working tree and branch
 
 
1743
        self.dir_mode = self.bzrdir._control_files._dir_mode
 
 
1744
        self.file_mode = self.bzrdir._control_files._file_mode
 
 
1745
        repository_names = [('inventory.weave', True),
 
 
1746
                            ('revision-store', True),
 
 
1748
        self.step('Upgrading repository  ')
 
 
1749
        self.bzrdir.transport.mkdir('repository', mode=self.dir_mode)
 
 
1750
        self.make_lock('repository')
 
 
1751
        # we hard code the formats here because we are converting into
 
 
1752
        # the meta format. The meta format upgrader can take this to a 
 
 
1753
        # future format within each component.
 
 
1754
        self.put_format('repository', bzrlib.repository.RepositoryFormat7())
 
 
1755
        for entry in repository_names:
 
 
1756
            self.move_entry('repository', entry)
 
 
1758
        self.step('Upgrading branch      ')
 
 
1759
        self.bzrdir.transport.mkdir('branch', mode=self.dir_mode)
 
 
1760
        self.make_lock('branch')
 
 
1761
        self.put_format('branch', bzrlib.branch.BzrBranchFormat5())
 
 
1762
        branch_files = [('revision-history', True),
 
 
1763
                        ('branch-name', True),
 
 
1765
        for entry in branch_files:
 
 
1766
            self.move_entry('branch', entry)
 
 
1768
        checkout_files = [('pending-merges', True),
 
 
1769
                          ('inventory', True),
 
 
1770
                          ('stat-cache', False)]
 
 
1771
        # If a mandatory checkout file is not present, the branch does not have
 
 
1772
        # a functional checkout. Do not create a checkout in the converted
 
 
1774
        for name, mandatory in checkout_files:
 
 
1775
            if mandatory and name not in bzrcontents:
 
 
1776
                has_checkout = False
 
 
1780
        if not has_checkout:
 
 
1781
            self.pb.note('No working tree.')
 
 
1782
            # If some checkout files are there, we may as well get rid of them.
 
 
1783
            for name, mandatory in checkout_files:
 
 
1784
                if name in bzrcontents:
 
 
1785
                    self.bzrdir.transport.delete(name)
 
 
1787
            self.step('Upgrading working tree')
 
 
1788
            self.bzrdir.transport.mkdir('checkout', mode=self.dir_mode)
 
 
1789
            self.make_lock('checkout')
 
 
1791
                'checkout', bzrlib.workingtree.WorkingTreeFormat3())
 
 
1792
            self.bzrdir.transport.delete_multi(
 
 
1793
                self.garbage_inventories, self.pb)
 
 
1794
            for entry in checkout_files:
 
 
1795
                self.move_entry('checkout', entry)
 
 
1796
            if last_revision is not None:
 
 
1797
                self.bzrdir._control_files.put_utf8(
 
 
1798
                    'checkout/last-revision', last_revision)
 
 
1799
        self.bzrdir._control_files.put_utf8(
 
 
1800
            'branch-format', BzrDirMetaFormat1().get_format_string())
 
 
1801
        return BzrDir.open(self.bzrdir.root_transport.base)
 
 
1803
    def make_lock(self, name):
 
 
1804
        """Make a lock for the new control dir name."""
 
 
1805
        self.step('Make %s lock' % name)
 
 
1806
        ld = LockDir(self.bzrdir.transport, 
 
 
1808
                     file_modebits=self.file_mode,
 
 
1809
                     dir_modebits=self.dir_mode)
 
 
1812
    def move_entry(self, new_dir, entry):
 
 
1813
        """Move then entry name into new_dir."""
 
 
1815
        mandatory = entry[1]
 
 
1816
        self.step('Moving %s' % name)
 
 
1818
            self.bzrdir.transport.move(name, '%s/%s' % (new_dir, name))
 
 
1819
        except errors.NoSuchFile:
 
 
1823
    def put_format(self, dirname, format):
 
 
1824
        self.bzrdir._control_files.put_utf8('%s/format' % dirname, format.get_format_string())
 
 
1827
class ConvertMetaToMeta(Converter):
 
 
1828
    """Converts the components of metadirs."""
 
 
1830
    def __init__(self, target_format):
 
 
1831
        """Create a metadir to metadir converter.
 
 
1833
        :param target_format: The final metadir format that is desired.
 
 
1835
        self.target_format = target_format
 
 
1837
    def convert(self, to_convert, pb):
 
 
1838
        """See Converter.convert()."""
 
 
1839
        self.bzrdir = to_convert
 
 
1843
        self.step('checking repository format')
 
 
1845
            repo = self.bzrdir.open_repository()
 
 
1846
        except errors.NoRepositoryPresent:
 
 
1849
            if not isinstance(repo._format, self.target_format.repository_format.__class__):
 
 
1850
                from bzrlib.repository import CopyConverter
 
 
1851
                self.pb.note('starting repository conversion')
 
 
1852
                converter = CopyConverter(self.target_format.repository_format)
 
 
1853
                converter.convert(repo, pb)