/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

Make sure all the request handlers in bzrlib/smart/vfs.py have consistent names.

Show diffs side-by-side

added added

removed removed

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