/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/bzrdir.py

  • Committer: Robert Collins
  • Date: 2006-11-22 02:49:26 UTC
  • mto: (2018.5.34 hpss)
  • mto: This revision was merged to the branch mainline in revision 2435.
  • Revision ID: robertc@robertcollins.net-20061122024926-41ba3a48a35200ee
Implement a BzrDir.open_branch smart server method for opening a branch without VFS.

Show diffs side-by-side

added added

removed removed

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