/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/bzrdir.py

Merge in supports-rich-root, another test passing.

Show diffs side-by-side

added added

removed removed

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