/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

Merge from bzr.dev.

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