/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: Aaron Bentley
  • Date: 2007-02-21 06:06:06 UTC
  • mto: (2255.6.1 dirstate)
  • mto: This revision was merged to the branch mainline in revision 2322.
  • Revision ID: aaron.bentley@utoronto.ca-20070221060606-unjaailciijp12ab
rename working tree format 4 to AB1 everywhere

Show diffs side-by-side

added added

removed removed

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