/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/bzrdir.py

  • Committer: Andrew Bennetts
  • Date: 2007-02-28 07:08:25 UTC
  • mfrom: (2018.13.1 hpss)
  • mto: (2018.5.80 hpss)
  • mto: This revision was merged to the branch mainline in revision 2435.
  • Revision ID: andrew.bennetts@canonical.com-20070228070825-q2dvkjb0a11ouhtx
Update to current hpss branch?  Fix lots of test failures.

Show diffs side-by-side

added added

removed removed

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