/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/bzrdir.py

  • Committer: Robert Collins
  • Date: 2006-02-16 07:20:25 UTC
  • mto: (1534.1.24 integration)
  • mto: This revision was merged to the branch mainline in revision 1554.
  • Revision ID: robertc@robertcollins.net-20060216072025-8a97e0b2c3f3cc92
Start factoring out the upgrade policy logic.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005 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
from copy import deepcopy
 
24
import os
 
25
from cStringIO import StringIO
 
26
from unittest import TestSuite
 
27
 
 
28
import bzrlib
 
29
import bzrlib.errors as errors
 
30
from bzrlib.lockable_files import LockableFiles
 
31
from bzrlib.osutils import safe_unicode
 
32
from bzrlib.osutils import (
 
33
                            abspath,
 
34
                            pathjoin,
 
35
                            safe_unicode,
 
36
                            sha_strings,
 
37
                            sha_string,
 
38
                            )
 
39
from bzrlib.store.text import TextStore
 
40
from bzrlib.store.weave import WeaveStore
 
41
from bzrlib.symbol_versioning import *
 
42
from bzrlib.trace import mutter
 
43
from bzrlib.transactions import PassThroughTransaction
 
44
from bzrlib.transport import get_transport
 
45
from bzrlib.transport.local import LocalTransport
 
46
from bzrlib.weave import Weave
 
47
from bzrlib.weavefile import read_weave, write_weave
 
48
from bzrlib.xml4 import serializer_v4
 
49
from bzrlib.xml5 import serializer_v5
 
50
 
 
51
 
 
52
class BzrDir(object):
 
53
    """A .bzr control diretory.
 
54
    
 
55
    BzrDir instances let you create or open any of the things that can be
 
56
    found within .bzr - checkouts, branches and repositories.
 
57
    
 
58
    transport
 
59
        the transport which this bzr dir is rooted at (i.e. file:///.../.bzr/)
 
60
    root_transport
 
61
        a transport connected to the directory this bzr was opened from.
 
62
    """
 
63
 
 
64
    def can_update_format(self):
 
65
        """Return true if this bzrdir is one whose format we can update."""
 
66
        return True
 
67
 
 
68
    def _check_supported(self, format, allow_unsupported):
 
69
        """Check whether format is a supported format.
 
70
 
 
71
        If allow_unsupported is True, this is a no-op.
 
72
        """
 
73
        if not allow_unsupported and not format.is_supported():
 
74
            raise errors.UnsupportedFormatError(format)
 
75
 
 
76
    def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
 
77
        """Clone this bzrdir and its contents to url verbatim.
 
78
 
 
79
        If urls last component does not exist, it will be created.
 
80
 
 
81
        if revision_id is not None, then the clone operation may tune
 
82
            itself to download less data.
 
83
        :param force_new_repo: Do not use a shared repository for the target 
 
84
                               even if one is available.
 
85
        """
 
86
        self._make_tail(url)
 
87
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
88
        result = self._format.initialize(url)
 
89
        try:
 
90
            local_repo = self.find_repository()
 
91
        except errors.NoRepositoryPresent:
 
92
            local_repo = None
 
93
        if local_repo:
 
94
            # may need to copy content in
 
95
            if force_new_repo:
 
96
                local_repo.clone(result, revision_id=revision_id, basis=basis_repo)
 
97
            else:
 
98
                try:
 
99
                    result_repo = result.find_repository()
 
100
                    # fetch content this dir needs.
 
101
                    if basis_repo:
 
102
                        # XXX FIXME RBC 20060214 need tests for this when the basis
 
103
                        # is incomplete
 
104
                        result_repo.fetch(basis_repo, revision_id=revision_id)
 
105
                    result_repo.fetch(local_repo, revision_id=revision_id)
 
106
                except errors.NoRepositoryPresent:
 
107
                    # needed to make one anyway.
 
108
                    local_repo.clone(result, revision_id=revision_id, basis=basis_repo)
 
109
        # 1 if there is a branch present
 
110
        #   make sure its content is available in the target repository
 
111
        #   clone it.
 
112
        try:
 
113
            self.open_branch().clone(result, revision_id=revision_id)
 
114
        except errors.NotBranchError:
 
115
            pass
 
116
        try:
 
117
            self.open_workingtree().clone(result, basis=basis_tree)
 
118
        except (errors.NoWorkingTree, errors.NotLocalUrl):
 
119
            pass
 
120
        return result
 
121
 
 
122
    def _get_basis_components(self, basis):
 
123
        """Retrieve the basis components that are available at basis."""
 
124
        if basis is None:
 
125
            return None, None, None
 
126
        try:
 
127
            basis_tree = basis.open_workingtree()
 
128
            basis_branch = basis_tree.branch
 
129
            basis_repo = basis_branch.repository
 
130
        except (errors.NoWorkingTree, errors.NotLocalUrl):
 
131
            basis_tree = None
 
132
            try:
 
133
                basis_branch = basis.open_branch()
 
134
                basis_repo = basis_branch.repository
 
135
            except errors.NotBranchError:
 
136
                basis_branch = None
 
137
                try:
 
138
                    basis_repo = basis.open_repository()
 
139
                except errors.NoRepositoryPresent:
 
140
                    basis_repo = None
 
141
        return basis_repo, basis_branch, basis_tree
 
142
 
 
143
    def _make_tail(self, url):
 
144
        segments = url.split('/')
 
145
        if segments and segments[-1] not in ('', '.'):
 
146
            parent = '/'.join(segments[:-1])
 
147
            t = bzrlib.transport.get_transport(parent)
 
148
            try:
 
149
                t.mkdir(segments[-1])
 
150
            except errors.FileExists:
 
151
                pass
 
152
 
 
153
    @staticmethod
 
154
    def create(base):
 
155
        """Create a new BzrDir at the url 'base'.
 
156
        
 
157
        This will call the current default formats initialize with base
 
158
        as the only parameter.
 
159
 
 
160
        If you need a specific format, consider creating an instance
 
161
        of that and calling initialize().
 
162
        """
 
163
        segments = base.split('/')
 
164
        if segments and segments[-1] not in ('', '.'):
 
165
            parent = '/'.join(segments[:-1])
 
166
            t = bzrlib.transport.get_transport(parent)
 
167
            try:
 
168
                t.mkdir(segments[-1])
 
169
            except errors.FileExists:
 
170
                pass
 
171
        return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
 
172
 
 
173
    def create_branch(self):
 
174
        """Create a branch in this BzrDir.
 
175
 
 
176
        The bzrdirs format will control what branch format is created.
 
177
        For more control see BranchFormatXX.create(a_bzrdir).
 
178
        """
 
179
        raise NotImplementedError(self.create_branch)
 
180
 
 
181
    @staticmethod
 
182
    def create_branch_and_repo(base, force_new_repo=False):
 
183
        """Create a new BzrDir, Branch and Repository at the url 'base'.
 
184
 
 
185
        This will use the current default BzrDirFormat, and use whatever 
 
186
        repository format that that uses via bzrdir.create_branch and
 
187
        create_repository. If a shared repository is available that is used
 
188
        preferentially.
 
189
 
 
190
        The created Branch object is returned.
 
191
 
 
192
        :param base: The URL to create the branch at.
 
193
        :param force_new_repo: If True a new repository is always created.
 
194
        """
 
195
        bzrdir = BzrDir.create(base)
 
196
        bzrdir._find_or_create_repository(force_new_repo)
 
197
        return bzrdir.create_branch()
 
198
 
 
199
    def _find_or_create_repository(self, force_new_repo):
 
200
        """Create a new repository if needed, returning the repository."""
 
201
        if force_new_repo:
 
202
            return self.create_repository()
 
203
        try:
 
204
            return self.find_repository()
 
205
        except errors.NoRepositoryPresent:
 
206
            return self.create_repository()
 
207
        
 
208
    @staticmethod
 
209
    def create_branch_convenience(base, force_new_repo=False, force_new_tree=None):
 
210
        """Create a new BzrDir, Branch and Repository at the url 'base'.
 
211
 
 
212
        This is a convenience function - it will use an existing repository
 
213
        if possible, can be told explicitly whether to create a working tree or
 
214
        not.
 
215
 
 
216
        This will use the current default BzrDirFormat, and use whatever 
 
217
        repository format that that uses via bzrdir.create_branch and
 
218
        create_repository. If a shared repository is available that is used
 
219
        preferentially. Whatever repository is used, its tree creation policy
 
220
        is followed.
 
221
 
 
222
        The created Branch object is returned.
 
223
        If a working tree cannot be made due to base not being a file:// url,
 
224
        no error is raised.
 
225
 
 
226
        :param base: The URL to create the branch at.
 
227
        :param force_new_repo: If True a new repository is always created.
 
228
        :param force_new_tree: If True or False force creation of a tree or 
 
229
                               prevent such creation respectively.
 
230
        """
 
231
        bzrdir = BzrDir.create(base)
 
232
        repo = bzrdir._find_or_create_repository(force_new_repo)
 
233
        result = bzrdir.create_branch()
 
234
        if force_new_tree or (repo.make_working_trees() and 
 
235
                              force_new_tree is None):
 
236
            bzrdir.create_workingtree()
 
237
        return result
 
238
        
 
239
    @staticmethod
 
240
    def create_repository(base, shared=False):
 
241
        """Create a new BzrDir and Repository at the url 'base'.
 
242
 
 
243
        This will use the current default BzrDirFormat, and use whatever 
 
244
        repository format that that uses for bzrdirformat.create_repository.
 
245
 
 
246
        ;param shared: Create a shared repository rather than a standalone
 
247
                       repository.
 
248
        The Repository object is returned.
 
249
 
 
250
        This must be overridden as an instance method in child classes, where
 
251
        it should take no parameters and construct whatever repository format
 
252
        that child class desires.
 
253
        """
 
254
        bzrdir = BzrDir.create(base)
 
255
        return bzrdir.create_repository()
 
256
 
 
257
    @staticmethod
 
258
    def create_standalone_workingtree(base):
 
259
        """Create a new BzrDir, WorkingTree, Branch and Repository at 'base'.
 
260
 
 
261
        'base' must be a local path or a file:// url.
 
262
 
 
263
        This will use the current default BzrDirFormat, and use whatever 
 
264
        repository format that that uses for bzrdirformat.create_workingtree,
 
265
        create_branch and create_repository.
 
266
 
 
267
        The WorkingTree object is returned.
 
268
        """
 
269
        t = get_transport(safe_unicode(base))
 
270
        if not isinstance(t, LocalTransport):
 
271
            raise errors.NotLocalUrl(base)
 
272
        bzrdir = BzrDir.create_branch_and_repo(safe_unicode(base),
 
273
                                               force_new_repo=True).bzrdir
 
274
        return bzrdir.create_workingtree()
 
275
 
 
276
    def create_workingtree(self, revision_id=None):
 
277
        """Create a working tree at this BzrDir.
 
278
        
 
279
        revision_id: create it as of this revision id.
 
280
        """
 
281
        raise NotImplementedError(self.create_workingtree)
 
282
 
 
283
    def find_repository(self):
 
284
        """Find the repository that should be used for a_bzrdir.
 
285
 
 
286
        This does not require a branch as we use it to find the repo for
 
287
        new branches as well as to hook existing branches up to their
 
288
        repository.
 
289
        """
 
290
        try:
 
291
            return self.open_repository()
 
292
        except errors.NoRepositoryPresent:
 
293
            pass
 
294
        next_transport = self.root_transport.clone('..')
 
295
        while True:
 
296
            try:
 
297
                found_bzrdir = BzrDir.open_containing_from_transport(
 
298
                    next_transport)[0]
 
299
            except errors.NotBranchError:
 
300
                raise errors.NoRepositoryPresent(self)
 
301
            try:
 
302
                repository = found_bzrdir.open_repository()
 
303
            except errors.NoRepositoryPresent:
 
304
                next_transport = found_bzrdir.root_transport.clone('..')
 
305
                continue
 
306
            if ((found_bzrdir.root_transport.base == 
 
307
                 self.root_transport.base) or repository.is_shared()):
 
308
                return repository
 
309
            else:
 
310
                raise errors.NoRepositoryPresent(self)
 
311
        raise errors.NoRepositoryPresent(self)
 
312
 
 
313
    def get_branch_transport(self, branch_format):
 
314
        """Get the transport for use by branch format in this BzrDir.
 
315
 
 
316
        Note that bzr dirs that do not support format strings will raise
 
317
        IncompatibleFormat if the branch format they are given has
 
318
        a format string, and vice verca.
 
319
 
 
320
        If branch_format is None, the transport is returned with no 
 
321
        checking. if it is not None, then the returned transport is
 
322
        guaranteed to point to an existing directory ready for use.
 
323
        """
 
324
        raise NotImplementedError(self.get_branch_transport)
 
325
        
 
326
    def get_repository_transport(self, repository_format):
 
327
        """Get the transport for use by repository format in this BzrDir.
 
328
 
 
329
        Note that bzr dirs that do not support format strings will raise
 
330
        IncompatibleFormat if the repository format they are given has
 
331
        a format string, and vice verca.
 
332
 
 
333
        If repository_format is None, the transport is returned with no 
 
334
        checking. if it is not None, then the returned transport is
 
335
        guaranteed to point to an existing directory ready for use.
 
336
        """
 
337
        raise NotImplementedError(self.get_repository_transport)
 
338
        
 
339
    def get_workingtree_transport(self, tree_format):
 
340
        """Get the transport for use by workingtree format in this BzrDir.
 
341
 
 
342
        Note that bzr dirs that do not support format strings will raise
 
343
        IncompatibleFormat if the workingtree format they are given has
 
344
        a format string, and vice verca.
 
345
 
 
346
        If workingtree_format is None, the transport is returned with no 
 
347
        checking. if it is not None, then the returned transport is
 
348
        guaranteed to point to an existing directory ready for use.
 
349
        """
 
350
        raise NotImplementedError(self.get_workingtree_transport)
 
351
        
 
352
    def __init__(self, _transport, _format):
 
353
        """Initialize a Bzr control dir object.
 
354
        
 
355
        Only really common logic should reside here, concrete classes should be
 
356
        made with varying behaviours.
 
357
 
 
358
        :param _format: the format that is creating this BzrDir instance.
 
359
        :param _transport: the transport this dir is based at.
 
360
        """
 
361
        self._format = _format
 
362
        self.transport = _transport.clone('.bzr')
 
363
        self.root_transport = _transport
 
364
 
 
365
    def needs_format_update(self):
 
366
        """Return true if this bzrdir needs update_format run on it.
 
367
        
 
368
        For instance, if the repository format is out of date but the 
 
369
        branch and working tree are not, this should return True.
 
370
        """
 
371
        # for now, if the format is not the same as the system default,
 
372
        # an upgrade is needed.
 
373
        return not isinstance(self._format, BzrDirFormat.get_default_format().__class__)
 
374
 
 
375
    @staticmethod
 
376
    def open_unsupported(base):
 
377
        """Open a branch which is not supported."""
 
378
        return BzrDir.open(base, _unsupported=True)
 
379
        
 
380
    @staticmethod
 
381
    def open(base, _unsupported=False):
 
382
        """Open an existing bzrdir, rooted at 'base' (url)
 
383
        
 
384
        _unsupported is a private parameter to the BzrDir class.
 
385
        """
 
386
        t = get_transport(base)
 
387
        mutter("trying to open %r with transport %r", base, t)
 
388
        format = BzrDirFormat.find_format(t)
 
389
        if not _unsupported and not format.is_supported():
 
390
            # see open_downlevel to open legacy branches.
 
391
            raise errors.UnsupportedFormatError(
 
392
                    'sorry, format %s not supported' % format,
 
393
                    ['use a different bzr version',
 
394
                     'or remove the .bzr directory'
 
395
                     ' and "bzr init" again'])
 
396
        return format.open(t, _found=True)
 
397
 
 
398
    def open_branch(self, unsupported=False):
 
399
        """Open the branch object at this BzrDir if one is present.
 
400
 
 
401
        If unsupported is True, then no longer supported branch formats can
 
402
        still be opened.
 
403
        
 
404
        TODO: static convenience version of this?
 
405
        """
 
406
        raise NotImplementedError(self.open_branch)
 
407
 
 
408
    @staticmethod
 
409
    def open_containing(url):
 
410
        """Open an existing branch which contains url.
 
411
        
 
412
        :param url: url to search from.
 
413
        See open_containing_from_transport for more detail.
 
414
        """
 
415
        return BzrDir.open_containing_from_transport(get_transport(url))
 
416
    
 
417
    @staticmethod
 
418
    def open_containing_from_transport(a_transport):
 
419
        """Open an existing branch which contains a_transport.base
 
420
 
 
421
        This probes for a branch at a_transport, and searches upwards from there.
 
422
 
 
423
        Basically we keep looking up until we find the control directory or
 
424
        run into the root.  If there isn't one, raises NotBranchError.
 
425
        If there is one and it is either an unrecognised format or an unsupported 
 
426
        format, UnknownFormatError or UnsupportedFormatError are raised.
 
427
        If there is one, it is returned, along with the unused portion of url.
 
428
        """
 
429
        # this gets the normalised url back. I.e. '.' -> the full path.
 
430
        url = a_transport.base
 
431
        while True:
 
432
            try:
 
433
                format = BzrDirFormat.find_format(a_transport)
 
434
                return format.open(a_transport), a_transport.relpath(url)
 
435
            except errors.NotBranchError, e:
 
436
                mutter('not a branch in: %r %s', a_transport.base, e)
 
437
            new_t = a_transport.clone('..')
 
438
            if new_t.base == a_transport.base:
 
439
                # reached the root, whatever that may be
 
440
                raise errors.NotBranchError(path=url)
 
441
            a_transport = new_t
 
442
 
 
443
    def open_repository(self, _unsupported=False):
 
444
        """Open the repository object at this BzrDir if one is present.
 
445
 
 
446
        This will not follow the Branch object pointer - its strictly a direct
 
447
        open facility. Most client code should use open_branch().repository to
 
448
        get at a repository.
 
449
 
 
450
        _unsupported is a private parameter, not part of the api.
 
451
        TODO: static convenience version of this?
 
452
        """
 
453
        raise NotImplementedError(self.open_repository)
 
454
 
 
455
    def open_workingtree(self, _unsupported=False):
 
456
        """Open the workingtree object at this BzrDir if one is present.
 
457
        
 
458
        TODO: static convenience version of this?
 
459
        """
 
460
        raise NotImplementedError(self.open_workingtree)
 
461
 
 
462
    def sprout(self, url, revision_id=None, basis=None, force_new_repo=False):
 
463
        """Create a copy of this bzrdir prepared for use as a new line of
 
464
        development.
 
465
 
 
466
        If urls last component does not exist, it will be created.
 
467
 
 
468
        Attributes related to the identity of the source branch like
 
469
        branch nickname will be cleaned, a working tree is created
 
470
        whether one existed before or not; and a local branch is always
 
471
        created.
 
472
 
 
473
        if revision_id is not None, then the clone operation may tune
 
474
            itself to download less data.
 
475
        """
 
476
        self._make_tail(url)
 
477
        result = self._format.initialize(url)
 
478
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
479
        try:
 
480
            source_branch = self.open_branch()
 
481
            source_repository = source_branch.repository
 
482
        except errors.NotBranchError:
 
483
            source_branch = None
 
484
            try:
 
485
                source_repository = self.open_repository()
 
486
            except errors.NoRepositoryPresent:
 
487
                # copy the entire basis one if there is one
 
488
                # but there is no repository.
 
489
                source_repository = basis_repo
 
490
        if force_new_repo:
 
491
            result_repo = None
 
492
        else:
 
493
            try:
 
494
                result_repo = result.find_repository()
 
495
            except errors.NoRepositoryPresent:
 
496
                result_repo = None
 
497
        if source_repository is None and result_repo is not None:
 
498
            pass
 
499
        elif source_repository is None and result_repo is None:
 
500
            # no repo available, make a new one
 
501
            result.create_repository()
 
502
        elif source_repository is not None and result_repo is None:
 
503
            # have soure, and want to make a new target repo
 
504
            source_repository.clone(result,
 
505
                                    revision_id=revision_id,
 
506
                                    basis=basis_repo)
 
507
        else:
 
508
            # fetch needed content into target.
 
509
            if basis_repo:
 
510
                # XXX FIXME RBC 20060214 need tests for this when the basis
 
511
                # is incomplete
 
512
                result_repo.fetch(basis_repo, revision_id=revision_id)
 
513
            result_repo.fetch(source_repository, revision_id=revision_id)
 
514
        if source_branch is not None:
 
515
            source_branch.sprout(result, revision_id=revision_id)
 
516
        else:
 
517
            result.create_branch()
 
518
        try:
 
519
            self.open_workingtree().clone(result,
 
520
                                          revision_id=revision_id, 
 
521
                                          basis=basis_tree)
 
522
        except (errors.NoWorkingTree, errors.NotLocalUrl):
 
523
            result.create_workingtree()
 
524
        return result
 
525
 
 
526
 
 
527
class BzrDirPreSplitOut(BzrDir):
 
528
    """A common class for the all-in-one formats."""
 
529
 
 
530
    def __init__(self, _transport, _format):
 
531
        """See BzrDir.__init__."""
 
532
        super(BzrDirPreSplitOut, self).__init__(_transport, _format)
 
533
        self._control_files = LockableFiles(self.get_branch_transport(None),
 
534
                                            'branch-lock')
 
535
 
 
536
    def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
 
537
        """See BzrDir.clone()."""
 
538
        from bzrlib.workingtree import WorkingTreeFormat2
 
539
        self._make_tail(url)
 
540
        result = self._format.initialize(url, _cloning=True)
 
541
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
542
        self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
 
543
        self.open_branch().clone(result, revision_id=revision_id)
 
544
        try:
 
545
            self.open_workingtree().clone(result, basis=basis_tree)
 
546
        except errors.NotLocalUrl:
 
547
            # make a new one, this format always has to have one.
 
548
            WorkingTreeFormat2().initialize(result)
 
549
        return result
 
550
 
 
551
    def create_branch(self):
 
552
        """See BzrDir.create_branch."""
 
553
        return self.open_branch()
 
554
 
 
555
    def create_repository(self, shared=False):
 
556
        """See BzrDir.create_repository."""
 
557
        if shared:
 
558
            raise errors.IncompatibleFormat('shared repository', self._format)
 
559
        return self.open_repository()
 
560
 
 
561
    def create_workingtree(self, revision_id=None):
 
562
        """See BzrDir.create_workingtree."""
 
563
        # this looks buggy but is not -really-
 
564
        # clone and sprout will have set the revision_id
 
565
        # and that will have set it for us, its only
 
566
        # specific uses of create_workingtree in isolation
 
567
        # that can do wonky stuff here, and that only
 
568
        # happens for creating checkouts, which cannot be 
 
569
        # done on this format anyway. So - acceptable wart.
 
570
        result = self.open_workingtree()
 
571
        if revision_id is not None:
 
572
            result.set_last_revision(revision_id)
 
573
        return result
 
574
 
 
575
    def get_branch_transport(self, branch_format):
 
576
        """See BzrDir.get_branch_transport()."""
 
577
        if branch_format is None:
 
578
            return self.transport
 
579
        try:
 
580
            branch_format.get_format_string()
 
581
        except NotImplementedError:
 
582
            return self.transport
 
583
        raise errors.IncompatibleFormat(branch_format, self._format)
 
584
 
 
585
    def get_repository_transport(self, repository_format):
 
586
        """See BzrDir.get_repository_transport()."""
 
587
        if repository_format is None:
 
588
            return self.transport
 
589
        try:
 
590
            repository_format.get_format_string()
 
591
        except NotImplementedError:
 
592
            return self.transport
 
593
        raise errors.IncompatibleFormat(repository_format, self._format)
 
594
 
 
595
    def get_workingtree_transport(self, workingtree_format):
 
596
        """See BzrDir.get_workingtree_transport()."""
 
597
        if workingtree_format is None:
 
598
            return self.transport
 
599
        try:
 
600
            workingtree_format.get_format_string()
 
601
        except NotImplementedError:
 
602
            return self.transport
 
603
        raise errors.IncompatibleFormat(workingtree_format, self._format)
 
604
 
 
605
    def open_branch(self, unsupported=False):
 
606
        """See BzrDir.open_branch."""
 
607
        from bzrlib.branch import BzrBranchFormat4
 
608
        format = BzrBranchFormat4()
 
609
        self._check_supported(format, unsupported)
 
610
        return format.open(self, _found=True)
 
611
 
 
612
    def sprout(self, url, revision_id=None, basis=None):
 
613
        """See BzrDir.sprout()."""
 
614
        from bzrlib.workingtree import WorkingTreeFormat2
 
615
        self._make_tail(url)
 
616
        result = self._format.initialize(url, _cloning=True)
 
617
        basis_repo, basis_branch, basis_tree = self._get_basis_components(basis)
 
618
        try:
 
619
            self.open_repository().clone(result, revision_id=revision_id, basis=basis_repo)
 
620
        except errors.NoRepositoryPresent:
 
621
            pass
 
622
        try:
 
623
            self.open_branch().sprout(result, revision_id=revision_id)
 
624
        except errors.NotBranchError:
 
625
            pass
 
626
        try:
 
627
            self.open_workingtree().clone(result, basis=basis_tree)
 
628
        except (errors.NotBranchError, errors.NotLocalUrl):
 
629
            # we always want a working tree
 
630
            WorkingTreeFormat2().initialize(result)
 
631
        return result
 
632
 
 
633
 
 
634
class BzrDir4(BzrDirPreSplitOut):
 
635
    """A .bzr version 4 control object.
 
636
    
 
637
    This is a deprecated format and may be removed after sept 2006.
 
638
    """
 
639
 
 
640
    def create_repository(self, shared=False):
 
641
        """See BzrDir.create_repository."""
 
642
        from bzrlib.repository import RepositoryFormat4
 
643
        return RepositoryFormat4().initialize(self, shared)
 
644
 
 
645
    def needs_format_update(self):
 
646
        """Format 4 dirs are always in need of updating."""
 
647
        return True
 
648
 
 
649
    def open_repository(self):
 
650
        """See BzrDir.open_repository."""
 
651
        from bzrlib.repository import RepositoryFormat4
 
652
        return RepositoryFormat4().open(self, _found=True)
 
653
 
 
654
 
 
655
class BzrDir5(BzrDirPreSplitOut):
 
656
    """A .bzr version 5 control object.
 
657
 
 
658
    This is a deprecated format and may be removed after sept 2006.
 
659
    """
 
660
 
 
661
    def open_repository(self):
 
662
        """See BzrDir.open_repository."""
 
663
        from bzrlib.repository import RepositoryFormat5
 
664
        return RepositoryFormat5().open(self, _found=True)
 
665
 
 
666
    def open_workingtree(self, _unsupported=False):
 
667
        """See BzrDir.create_workingtree."""
 
668
        from bzrlib.workingtree import WorkingTreeFormat2
 
669
        return WorkingTreeFormat2().open(self, _found=True)
 
670
 
 
671
 
 
672
class BzrDir6(BzrDirPreSplitOut):
 
673
    """A .bzr version 6 control object.
 
674
 
 
675
    This is a deprecated format and may be removed after sept 2006.
 
676
    """
 
677
 
 
678
    def open_repository(self):
 
679
        """See BzrDir.open_repository."""
 
680
        from bzrlib.repository import RepositoryFormat6
 
681
        return RepositoryFormat6().open(self, _found=True)
 
682
 
 
683
    def open_workingtree(self, _unsupported=False):
 
684
        """See BzrDir.create_workingtree."""
 
685
        from bzrlib.workingtree import WorkingTreeFormat2
 
686
        return WorkingTreeFormat2().open(self, _found=True)
 
687
 
 
688
 
 
689
class BzrDirMeta1(BzrDir):
 
690
    """A .bzr meta version 1 control object.
 
691
    
 
692
    This is the first control object where the 
 
693
    individual formats are really split out.
 
694
    """
 
695
 
 
696
    def create_branch(self):
 
697
        """See BzrDir.create_branch."""
 
698
        from bzrlib.branch import BranchFormat
 
699
        return BranchFormat.get_default_format().initialize(self)
 
700
 
 
701
    def create_repository(self, shared=False):
 
702
        """See BzrDir.create_repository."""
 
703
        from bzrlib.repository import RepositoryFormat
 
704
        return RepositoryFormat.get_default_format().initialize(self, shared)
 
705
 
 
706
    def create_workingtree(self, revision_id=None):
 
707
        """See BzrDir.create_workingtree."""
 
708
        from bzrlib.workingtree import WorkingTreeFormat
 
709
        return WorkingTreeFormat.get_default_format().initialize(self, revision_id)
 
710
 
 
711
    def get_branch_transport(self, branch_format):
 
712
        """See BzrDir.get_branch_transport()."""
 
713
        if branch_format is None:
 
714
            return self.transport.clone('branch')
 
715
        try:
 
716
            branch_format.get_format_string()
 
717
        except NotImplementedError:
 
718
            raise errors.IncompatibleFormat(branch_format, self._format)
 
719
        try:
 
720
            self.transport.mkdir('branch')
 
721
        except errors.FileExists:
 
722
            pass
 
723
        return self.transport.clone('branch')
 
724
 
 
725
    def get_repository_transport(self, repository_format):
 
726
        """See BzrDir.get_repository_transport()."""
 
727
        if repository_format is None:
 
728
            return self.transport.clone('repository')
 
729
        try:
 
730
            repository_format.get_format_string()
 
731
        except NotImplementedError:
 
732
            raise errors.IncompatibleFormat(repository_format, self._format)
 
733
        try:
 
734
            self.transport.mkdir('repository')
 
735
        except errors.FileExists:
 
736
            pass
 
737
        return self.transport.clone('repository')
 
738
 
 
739
    def get_workingtree_transport(self, workingtree_format):
 
740
        """See BzrDir.get_workingtree_transport()."""
 
741
        if workingtree_format is None:
 
742
            return self.transport.clone('checkout')
 
743
        try:
 
744
            workingtree_format.get_format_string()
 
745
        except NotImplementedError:
 
746
            raise errors.IncompatibleFormat(workingtree_format, self._format)
 
747
        try:
 
748
            self.transport.mkdir('checkout')
 
749
        except errors.FileExists:
 
750
            pass
 
751
        return self.transport.clone('checkout')
 
752
 
 
753
    def open_branch(self, unsupported=False):
 
754
        """See BzrDir.open_branch."""
 
755
        from bzrlib.branch import BranchFormat
 
756
        format = BranchFormat.find_format(self)
 
757
        self._check_supported(format, unsupported)
 
758
        return format.open(self, _found=True)
 
759
 
 
760
    def open_repository(self, unsupported=False):
 
761
        """See BzrDir.open_repository."""
 
762
        from bzrlib.repository import RepositoryFormat
 
763
        format = RepositoryFormat.find_format(self)
 
764
        self._check_supported(format, unsupported)
 
765
        return format.open(self, _found=True)
 
766
 
 
767
    def open_workingtree(self, unsupported=False):
 
768
        """See BzrDir.open_workingtree."""
 
769
        from bzrlib.workingtree import WorkingTreeFormat
 
770
        format = WorkingTreeFormat.find_format(self)
 
771
        self._check_supported(format, unsupported)
 
772
        return format.open(self, _found=True)
 
773
 
 
774
 
 
775
class BzrDirFormat(object):
 
776
    """An encapsulation of the initialization and open routines for a format.
 
777
 
 
778
    Formats provide three things:
 
779
     * An initialization routine,
 
780
     * a format string,
 
781
     * an open routine.
 
782
 
 
783
    Formats are placed in an dict by their format string for reference 
 
784
    during bzrdir opening. These should be subclasses of BzrDirFormat
 
785
    for consistency.
 
786
 
 
787
    Once a format is deprecated, just deprecate the initialize and open
 
788
    methods on the format class. Do not deprecate the object, as the 
 
789
    object will be created every system load.
 
790
    """
 
791
 
 
792
    _default_format = None
 
793
    """The default format used for new .bzr dirs."""
 
794
 
 
795
    _formats = {}
 
796
    """The known formats."""
 
797
 
 
798
    @classmethod
 
799
    def find_format(klass, transport):
 
800
        """Return the format registered for URL."""
 
801
        try:
 
802
            format_string = transport.get(".bzr/branch-format").read()
 
803
            return klass._formats[format_string]
 
804
        except errors.NoSuchFile:
 
805
            raise errors.NotBranchError(path=transport.base)
 
806
        except KeyError:
 
807
            raise errors.UnknownFormatError(format_string)
 
808
 
 
809
    @classmethod
 
810
    def get_default_format(klass):
 
811
        """Return the current default format."""
 
812
        return klass._default_format
 
813
 
 
814
    def get_format_string(self):
 
815
        """Return the ASCII format string that identifies this format."""
 
816
        raise NotImplementedError(self.get_format_string)
 
817
 
 
818
    def initialize(self, url):
 
819
        """Create a bzr control dir at this url and return an opened copy."""
 
820
        # Since we don't have a .bzr directory, inherit the
 
821
        # mode from the root directory
 
822
        t = get_transport(url)
 
823
        temp_control = LockableFiles(t, '')
 
824
        temp_control._transport.mkdir('.bzr',
 
825
                                      # FIXME: RBC 20060121 dont peek under
 
826
                                      # the covers
 
827
                                      mode=temp_control._dir_mode)
 
828
        file_mode = temp_control._file_mode
 
829
        del temp_control
 
830
        mutter('created control directory in ' + t.base)
 
831
        control = t.clone('.bzr')
 
832
        lock_file = 'branch-lock'
 
833
        utf8_files = [('README', 
 
834
                       "This is a Bazaar-NG control directory.\n"
 
835
                       "Do not change any files in this directory.\n"),
 
836
                      ('branch-format', self.get_format_string()),
 
837
                      ]
 
838
        # NB: no need to escape relative paths that are url safe.
 
839
        control.put(lock_file, StringIO(), mode=file_mode)
 
840
        control_files = LockableFiles(control, lock_file)
 
841
        control_files.lock_write()
 
842
        try:
 
843
            for file, content in utf8_files:
 
844
                control_files.put_utf8(file, content)
 
845
        finally:
 
846
            control_files.unlock()
 
847
        return self.open(t, _found=True)
 
848
 
 
849
    def is_supported(self):
 
850
        """Is this format supported?
 
851
 
 
852
        Supported formats must be initializable and openable.
 
853
        Unsupported formats may not support initialization or committing or 
 
854
        some other features depending on the reason for not being supported.
 
855
        """
 
856
        return True
 
857
 
 
858
    def open(self, transport, _found=False):
 
859
        """Return an instance of this format for the dir transport points at.
 
860
        
 
861
        _found is a private parameter, do not use it.
 
862
        """
 
863
        if not _found:
 
864
            assert isinstance(BzrDirFormat.find_format(transport),
 
865
                              self.__class__)
 
866
        return self._open(transport)
 
867
 
 
868
    def _open(self, transport):
 
869
        """Template method helper for opening BzrDirectories.
 
870
 
 
871
        This performs the actual open and any additional logic or parameter
 
872
        passing.
 
873
        """
 
874
        raise NotImplementedError(self._open)
 
875
 
 
876
    @classmethod
 
877
    def register_format(klass, format):
 
878
        klass._formats[format.get_format_string()] = format
 
879
 
 
880
    @classmethod
 
881
    def set_default_format(klass, format):
 
882
        klass._default_format = format
 
883
 
 
884
    def __str__(self):
 
885
        return self.get_format_string()[:-1]
 
886
 
 
887
    @classmethod
 
888
    def unregister_format(klass, format):
 
889
        assert klass._formats[format.get_format_string()] is format
 
890
        del klass._formats[format.get_format_string()]
 
891
 
 
892
 
 
893
class BzrDirFormat4(BzrDirFormat):
 
894
    """Bzr dir format 4.
 
895
 
 
896
    This format is a combined format for working tree, branch and repository.
 
897
    It has:
 
898
     - Format 1 working trees [always]
 
899
     - Format 4 branches [always]
 
900
     - Format 4 repositories [always]
 
901
 
 
902
    This format is deprecated: it indexes texts using a text it which is
 
903
    removed in format 5; write support for this format has been removed.
 
904
    """
 
905
 
 
906
    def get_format_string(self):
 
907
        """See BzrDirFormat.get_format_string()."""
 
908
        return "Bazaar-NG branch, format 0.0.4\n"
 
909
 
 
910
    def initialize(self, url):
 
911
        """Format 4 branches cannot be created."""
 
912
        raise errors.UninitializableFormat(self)
 
913
 
 
914
    def is_supported(self):
 
915
        """Format 4 is not supported.
 
916
 
 
917
        It is not supported because the model changed from 4 to 5 and the
 
918
        conversion logic is expensive - so doing it on the fly was not 
 
919
        feasible.
 
920
        """
 
921
        return False
 
922
 
 
923
    def _open(self, transport):
 
924
        """See BzrDirFormat._open."""
 
925
        return BzrDir4(transport, self)
 
926
 
 
927
 
 
928
class BzrDirFormat5(BzrDirFormat):
 
929
    """Bzr control format 5.
 
930
 
 
931
    This format is a combined format for working tree, branch and repository.
 
932
    It has:
 
933
     - Format 2 working trees [always] 
 
934
     - Format 4 branches [always] 
 
935
     - Format 5 repositories [always]
 
936
       Unhashed stores in the repository.
 
937
    """
 
938
 
 
939
    def get_format_string(self):
 
940
        """See BzrDirFormat.get_format_string()."""
 
941
        return "Bazaar-NG branch, format 5\n"
 
942
 
 
943
    def initialize(self, url, _cloning=False):
 
944
        """Format 5 dirs always have working tree, branch and repository.
 
945
        
 
946
        Except when they are being cloned.
 
947
        """
 
948
        from bzrlib.branch import BzrBranchFormat4
 
949
        from bzrlib.repository import RepositoryFormat5
 
950
        from bzrlib.workingtree import WorkingTreeFormat2
 
951
        result = super(BzrDirFormat5, self).initialize(url)
 
952
        RepositoryFormat5().initialize(result, _internal=True)
 
953
        if not _cloning:
 
954
            BzrBranchFormat4().initialize(result)
 
955
            WorkingTreeFormat2().initialize(result)
 
956
        return result
 
957
 
 
958
    def _open(self, transport):
 
959
        """See BzrDirFormat._open."""
 
960
        return BzrDir5(transport, self)
 
961
 
 
962
 
 
963
class BzrDirFormat6(BzrDirFormat):
 
964
    """Bzr control format 6.
 
965
 
 
966
    This format is a combined format for working tree, branch and repository.
 
967
    It has:
 
968
     - Format 2 working trees [always] 
 
969
     - Format 4 branches [always] 
 
970
     - Format 6 repositories [always]
 
971
    """
 
972
 
 
973
    def get_format_string(self):
 
974
        """See BzrDirFormat.get_format_string()."""
 
975
        return "Bazaar-NG branch, format 6\n"
 
976
 
 
977
    def initialize(self, url, _cloning=False):
 
978
        """Format 6 dirs always have working tree, branch and repository.
 
979
        
 
980
        Except when they are being cloned.
 
981
        """
 
982
        from bzrlib.branch import BzrBranchFormat4
 
983
        from bzrlib.repository import RepositoryFormat6
 
984
        from bzrlib.workingtree import WorkingTreeFormat2
 
985
        result = super(BzrDirFormat6, self).initialize(url)
 
986
        RepositoryFormat6().initialize(result, _internal=True)
 
987
        if not _cloning:
 
988
            BzrBranchFormat4().initialize(result)
 
989
            try:
 
990
                WorkingTreeFormat2().initialize(result)
 
991
            except errors.NotLocalUrl:
 
992
                # emulate pre-check behaviour for working tree and silently 
 
993
                # fail.
 
994
                pass
 
995
        return result
 
996
 
 
997
    def _open(self, transport):
 
998
        """See BzrDirFormat._open."""
 
999
        return BzrDir6(transport, self)
 
1000
 
 
1001
 
 
1002
class BzrDirMetaFormat1(BzrDirFormat):
 
1003
    """Bzr meta control format 1
 
1004
 
 
1005
    This is the first format with split out working tree, branch and repository
 
1006
    disk storage.
 
1007
    It has:
 
1008
     - Format 3 working trees [optional]
 
1009
     - Format 5 branches [optional]
 
1010
     - Format 7 repositories [optional]
 
1011
    """
 
1012
 
 
1013
    def get_format_string(self):
 
1014
        """See BzrDirFormat.get_format_string()."""
 
1015
        return "Bazaar-NG meta directory, format 1\n"
 
1016
 
 
1017
    def _open(self, transport):
 
1018
        """See BzrDirFormat._open."""
 
1019
        return BzrDirMeta1(transport, self)
 
1020
 
 
1021
 
 
1022
BzrDirFormat.register_format(BzrDirFormat4())
 
1023
BzrDirFormat.register_format(BzrDirFormat5())
 
1024
BzrDirFormat.register_format(BzrDirMetaFormat1())
 
1025
__default_format = BzrDirFormat6()
 
1026
BzrDirFormat.register_format(__default_format)
 
1027
BzrDirFormat.set_default_format(__default_format)
 
1028
 
 
1029
 
 
1030
class BzrDirTestProviderAdapter(object):
 
1031
    """A tool to generate a suite testing multiple bzrdir formats at once.
 
1032
 
 
1033
    This is done by copying the test once for each transport and injecting
 
1034
    the transport_server, transport_readonly_server, and bzrdir_format
 
1035
    classes into each copy. Each copy is also given a new id() to make it
 
1036
    easy to identify.
 
1037
    """
 
1038
 
 
1039
    def __init__(self, transport_server, transport_readonly_server, formats):
 
1040
        self._transport_server = transport_server
 
1041
        self._transport_readonly_server = transport_readonly_server
 
1042
        self._formats = formats
 
1043
    
 
1044
    def adapt(self, test):
 
1045
        result = TestSuite()
 
1046
        for format in self._formats:
 
1047
            new_test = deepcopy(test)
 
1048
            new_test.transport_server = self._transport_server
 
1049
            new_test.transport_readonly_server = self._transport_readonly_server
 
1050
            new_test.bzrdir_format = format
 
1051
            def make_new_test_id():
 
1052
                new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
 
1053
                return lambda: new_id
 
1054
            new_test.id = make_new_test_id()
 
1055
            result.addTest(new_test)
 
1056
        return result
 
1057
 
 
1058
 
 
1059
class ScratchDir(BzrDir6):
 
1060
    """Special test class: a bzrdir that cleans up itself..
 
1061
 
 
1062
    >>> d = ScratchDir()
 
1063
    >>> base = d.transport.base
 
1064
    >>> isdir(base)
 
1065
    True
 
1066
    >>> b.transport.__del__()
 
1067
    >>> isdir(base)
 
1068
    False
 
1069
    """
 
1070
 
 
1071
    def __init__(self, files=[], dirs=[], transport=None):
 
1072
        """Make a test branch.
 
1073
 
 
1074
        This creates a temporary directory and runs init-tree in it.
 
1075
 
 
1076
        If any files are listed, they are created in the working copy.
 
1077
        """
 
1078
        if transport is None:
 
1079
            transport = bzrlib.transport.local.ScratchTransport()
 
1080
            # local import for scope restriction
 
1081
            BzrDirFormat6().initialize(transport.base)
 
1082
            super(ScratchDir, self).__init__(transport, BzrDirFormat6())
 
1083
            self.create_repository()
 
1084
            self.create_branch()
 
1085
            self.create_workingtree()
 
1086
        else:
 
1087
            super(ScratchDir, self).__init__(transport, BzrDirFormat6())
 
1088
 
 
1089
        # BzrBranch creates a clone to .bzr and then forgets about the
 
1090
        # original transport. A ScratchTransport() deletes itself and
 
1091
        # everything underneath it when it goes away, so we need to
 
1092
        # grab a local copy to prevent that from happening
 
1093
        self._transport = transport
 
1094
 
 
1095
        for d in dirs:
 
1096
            self._transport.mkdir(d)
 
1097
            
 
1098
        for f in files:
 
1099
            self._transport.put(f, 'content of %s' % f)
 
1100
 
 
1101
    def clone(self):
 
1102
        """
 
1103
        >>> orig = ScratchDir(files=["file1", "file2"])
 
1104
        >>> os.listdir(orig.base)
 
1105
        [u'.bzr', u'file1', u'file2']
 
1106
        >>> clone = orig.clone()
 
1107
        >>> if os.name != 'nt':
 
1108
        ...   os.path.samefile(orig.base, clone.base)
 
1109
        ... else:
 
1110
        ...   orig.base == clone.base
 
1111
        ...
 
1112
        False
 
1113
        >>> os.listdir(clone.base)
 
1114
        [u'.bzr', u'file1', u'file2']
 
1115
        """
 
1116
        from shutil import copytree
 
1117
        from bzrlib.osutils import mkdtemp
 
1118
        base = mkdtemp()
 
1119
        os.rmdir(base)
 
1120
        copytree(self.base, base, symlinks=True)
 
1121
        return ScratchDir(
 
1122
            transport=bzrlib.transport.local.ScratchTransport(base))
 
1123
 
 
1124
 
 
1125
class Converter(object):
 
1126
    """Converts a disk format object from one format to another."""
 
1127
 
 
1128
    def __init__(self, pb):
 
1129
        """Create a converter.
 
1130
 
 
1131
        :param pb: a progress bar to use for progress information.
 
1132
        """
 
1133
        self.pb = pb
 
1134
 
 
1135
 
 
1136
class ConvertBzrDir4To5(Converter):
 
1137
    """Converts format 4 bzr dirs to format 5."""
 
1138
 
 
1139
    def __init__(self, to_convert, pb):
 
1140
        """Create a converter.
 
1141
 
 
1142
        :param to_convert: The disk object to convert.
 
1143
        :param pb: a progress bar to use for progress information.
 
1144
        """
 
1145
        super(ConvertBzrDir4To5, self).__init__(pb)
 
1146
        self.bzrdir = to_convert
 
1147
        self.converted_revs = set()
 
1148
        self.absent_revisions = set()
 
1149
        self.text_count = 0
 
1150
        self.revisions = {}
 
1151
        
 
1152
    def convert(self):
 
1153
        """See Converter.convert()."""
 
1154
        self.pb.note('starting upgrade from format 4 to 5')
 
1155
        if isinstance(self.bzrdir.transport, LocalTransport):
 
1156
            self.bzrdir.get_workingtree_transport(None).delete('stat-cache')
 
1157
        self._convert_to_weaves()
 
1158
        return BzrDir.open(self.bzrdir.root_transport.base)
 
1159
 
 
1160
    def _convert_to_weaves(self):
 
1161
        self.pb.note('note: upgrade may be faster if all store files are ungzipped first')
 
1162
        try:
 
1163
            # TODO permissions
 
1164
            stat = self.bzrdir.transport.stat('weaves')
 
1165
            if not S_ISDIR(stat.st_mode):
 
1166
                self.bzrdir.transport.delete('weaves')
 
1167
                self.bzrdir.transport.mkdir('weaves')
 
1168
        except errors.NoSuchFile:
 
1169
            self.bzrdir.transport.mkdir('weaves')
 
1170
        self.inv_weave = Weave('inventory')
 
1171
        # holds in-memory weaves for all files
 
1172
        self.text_weaves = {}
 
1173
        self.bzrdir.transport.delete('branch-format')
 
1174
        self.branch = self.bzrdir.open_branch()
 
1175
        self._convert_working_inv()
 
1176
        rev_history = self.branch.revision_history()
 
1177
        # to_read is a stack holding the revisions we still need to process;
 
1178
        # appending to it adds new highest-priority revisions
 
1179
        self.known_revisions = set(rev_history)
 
1180
        self.to_read = rev_history[-1:]
 
1181
        while self.to_read:
 
1182
            rev_id = self.to_read.pop()
 
1183
            if (rev_id not in self.revisions
 
1184
                and rev_id not in self.absent_revisions):
 
1185
                self._load_one_rev(rev_id)
 
1186
        self.pb.clear()
 
1187
        to_import = self._make_order()
 
1188
        for i, rev_id in enumerate(to_import):
 
1189
            self.pb.update('converting revision', i, len(to_import))
 
1190
            self._convert_one_rev(rev_id)
 
1191
        self.pb.clear()
 
1192
        self._write_all_weaves()
 
1193
        self._write_all_revs()
 
1194
        self.pb.note('upgraded to weaves:')
 
1195
        self.pb.note('  %6d revisions and inventories', len(self.revisions))
 
1196
        self.pb.note('  %6d revisions not present', len(self.absent_revisions))
 
1197
        self.pb.note('  %6d texts', self.text_count)
 
1198
        self._cleanup_spare_files_after_format4()
 
1199
        self.branch.control_files.put_utf8('branch-format', BzrDirFormat5().get_format_string())
 
1200
 
 
1201
    def _cleanup_spare_files_after_format4(self):
 
1202
        # FIXME working tree upgrade foo.
 
1203
        for n in 'merged-patches', 'pending-merged-patches':
 
1204
            try:
 
1205
                ## assert os.path.getsize(p) == 0
 
1206
                self.bzrdir.transport.delete(n)
 
1207
            except errors.NoSuchFile:
 
1208
                pass
 
1209
        self.bzrdir.transport.delete_tree('inventory-store')
 
1210
        self.bzrdir.transport.delete_tree('text-store')
 
1211
 
 
1212
    def _convert_working_inv(self):
 
1213
        inv = serializer_v4.read_inventory(self.branch.control_files.get('inventory'))
 
1214
        new_inv_xml = serializer_v5.write_inventory_to_string(inv)
 
1215
        # FIXME inventory is a working tree change.
 
1216
        self.branch.control_files.put('inventory', new_inv_xml)
 
1217
 
 
1218
    def _write_all_weaves(self):
 
1219
        controlweaves = WeaveStore(self.bzrdir.transport, prefixed=False)
 
1220
        weave_transport = self.bzrdir.transport.clone('weaves')
 
1221
        weaves = WeaveStore(weave_transport, prefixed=False)
 
1222
        transaction = PassThroughTransaction()
 
1223
 
 
1224
        controlweaves.put_weave('inventory', self.inv_weave, transaction)
 
1225
        i = 0
 
1226
        try:
 
1227
            for file_id, file_weave in self.text_weaves.items():
 
1228
                self.pb.update('writing weave', i, len(self.text_weaves))
 
1229
                weaves.put_weave(file_id, file_weave, transaction)
 
1230
                i += 1
 
1231
        finally:
 
1232
            self.pb.clear()
 
1233
 
 
1234
    def _write_all_revs(self):
 
1235
        """Write all revisions out in new form."""
 
1236
        self.bzrdir.transport.delete_tree('revision-store')
 
1237
        self.bzrdir.transport.mkdir('revision-store')
 
1238
        revision_transport = self.bzrdir.transport.clone('revision-store')
 
1239
        # TODO permissions
 
1240
        revision_store = TextStore(revision_transport,
 
1241
                                   prefixed=False,
 
1242
                                   compressed=True)
 
1243
        try:
 
1244
            for i, rev_id in enumerate(self.converted_revs):
 
1245
                self.pb.update('write revision', i, len(self.converted_revs))
 
1246
                rev_tmp = StringIO()
 
1247
                serializer_v5.write_revision(self.revisions[rev_id], rev_tmp)
 
1248
                rev_tmp.seek(0)
 
1249
                revision_store.add(rev_tmp, rev_id)
 
1250
        finally:
 
1251
            self.pb.clear()
 
1252
 
 
1253
            
 
1254
    def _load_one_rev(self, rev_id):
 
1255
        """Load a revision object into memory.
 
1256
 
 
1257
        Any parents not either loaded or abandoned get queued to be
 
1258
        loaded."""
 
1259
        self.pb.update('loading revision',
 
1260
                       len(self.revisions),
 
1261
                       len(self.known_revisions))
 
1262
        if not self.branch.repository.revision_store.has_id(rev_id):
 
1263
            self.pb.clear()
 
1264
            self.pb.note('revision {%s} not present in branch; '
 
1265
                         'will be converted as a ghost',
 
1266
                         rev_id)
 
1267
            self.absent_revisions.add(rev_id)
 
1268
        else:
 
1269
            rev_xml = self.branch.repository.revision_store.get(rev_id).read()
 
1270
            rev = serializer_v4.read_revision_from_string(rev_xml)
 
1271
            for parent_id in rev.parent_ids:
 
1272
                self.known_revisions.add(parent_id)
 
1273
                self.to_read.append(parent_id)
 
1274
            self.revisions[rev_id] = rev
 
1275
 
 
1276
 
 
1277
    def _load_old_inventory(self, rev_id):
 
1278
        assert rev_id not in self.converted_revs
 
1279
        old_inv_xml = self.branch.repository.inventory_store.get(rev_id).read()
 
1280
        inv = serializer_v4.read_inventory_from_string(old_inv_xml)
 
1281
        rev = self.revisions[rev_id]
 
1282
        if rev.inventory_sha1:
 
1283
            assert rev.inventory_sha1 == sha_string(old_inv_xml), \
 
1284
                'inventory sha mismatch for {%s}' % rev_id
 
1285
        return inv
 
1286
        
 
1287
 
 
1288
    def _load_updated_inventory(self, rev_id):
 
1289
        assert rev_id in self.converted_revs
 
1290
        inv_xml = self.inv_weave.get_text(rev_id)
 
1291
        inv = serializer_v5.read_inventory_from_string(inv_xml)
 
1292
        return inv
 
1293
 
 
1294
 
 
1295
    def _convert_one_rev(self, rev_id):
 
1296
        """Convert revision and all referenced objects to new format."""
 
1297
        rev = self.revisions[rev_id]
 
1298
        inv = self._load_old_inventory(rev_id)
 
1299
        present_parents = [p for p in rev.parent_ids
 
1300
                           if p not in self.absent_revisions]
 
1301
        self._convert_revision_contents(rev, inv, present_parents)
 
1302
        self._store_new_weave(rev, inv, present_parents)
 
1303
        self.converted_revs.add(rev_id)
 
1304
 
 
1305
 
 
1306
    def _store_new_weave(self, rev, inv, present_parents):
 
1307
        # the XML is now updated with text versions
 
1308
        if __debug__:
 
1309
            for file_id in inv:
 
1310
                ie = inv[file_id]
 
1311
                if ie.kind == 'root_directory':
 
1312
                    continue
 
1313
                assert hasattr(ie, 'revision'), \
 
1314
                    'no revision on {%s} in {%s}' % \
 
1315
                    (file_id, rev.revision_id)
 
1316
        new_inv_xml = serializer_v5.write_inventory_to_string(inv)
 
1317
        new_inv_sha1 = sha_string(new_inv_xml)
 
1318
        self.inv_weave.add(rev.revision_id, 
 
1319
                           present_parents,
 
1320
                           new_inv_xml.splitlines(True),
 
1321
                           new_inv_sha1)
 
1322
        rev.inventory_sha1 = new_inv_sha1
 
1323
 
 
1324
    def _convert_revision_contents(self, rev, inv, present_parents):
 
1325
        """Convert all the files within a revision.
 
1326
 
 
1327
        Also upgrade the inventory to refer to the text revision ids."""
 
1328
        rev_id = rev.revision_id
 
1329
        mutter('converting texts of revision {%s}',
 
1330
               rev_id)
 
1331
        parent_invs = map(self._load_updated_inventory, present_parents)
 
1332
        for file_id in inv:
 
1333
            ie = inv[file_id]
 
1334
            self._convert_file_version(rev, ie, parent_invs)
 
1335
 
 
1336
    def _convert_file_version(self, rev, ie, parent_invs):
 
1337
        """Convert one version of one file.
 
1338
 
 
1339
        The file needs to be added into the weave if it is a merge
 
1340
        of >=2 parents or if it's changed from its parent.
 
1341
        """
 
1342
        if ie.kind == 'root_directory':
 
1343
            return
 
1344
        file_id = ie.file_id
 
1345
        rev_id = rev.revision_id
 
1346
        w = self.text_weaves.get(file_id)
 
1347
        if w is None:
 
1348
            w = Weave(file_id)
 
1349
            self.text_weaves[file_id] = w
 
1350
        text_changed = False
 
1351
        previous_entries = ie.find_previous_heads(parent_invs, w)
 
1352
        for old_revision in previous_entries:
 
1353
                # if this fails, its a ghost ?
 
1354
                assert old_revision in self.converted_revs 
 
1355
        self.snapshot_ie(previous_entries, ie, w, rev_id)
 
1356
        del ie.text_id
 
1357
        assert getattr(ie, 'revision', None) is not None
 
1358
 
 
1359
    def snapshot_ie(self, previous_revisions, ie, w, rev_id):
 
1360
        # TODO: convert this logic, which is ~= snapshot to
 
1361
        # a call to:. This needs the path figured out. rather than a work_tree
 
1362
        # a v4 revision_tree can be given, or something that looks enough like
 
1363
        # one to give the file content to the entry if it needs it.
 
1364
        # and we need something that looks like a weave store for snapshot to 
 
1365
        # save against.
 
1366
        #ie.snapshot(rev, PATH, previous_revisions, REVISION_TREE, InMemoryWeaveStore(self.text_weaves))
 
1367
        if len(previous_revisions) == 1:
 
1368
            previous_ie = previous_revisions.values()[0]
 
1369
            if ie._unchanged(previous_ie):
 
1370
                ie.revision = previous_ie.revision
 
1371
                return
 
1372
        parent_indexes = map(w.lookup, previous_revisions)
 
1373
        if ie.has_text():
 
1374
            text = self.branch.repository.text_store.get(ie.text_id)
 
1375
            file_lines = text.readlines()
 
1376
            assert sha_strings(file_lines) == ie.text_sha1
 
1377
            assert sum(map(len, file_lines)) == ie.text_size
 
1378
            w.add(rev_id, parent_indexes, file_lines, ie.text_sha1)
 
1379
            self.text_count += 1
 
1380
        else:
 
1381
            w.add(rev_id, parent_indexes, [], None)
 
1382
        ie.revision = rev_id
 
1383
        ##mutter('import text {%s} of {%s}',
 
1384
        ##       ie.text_id, file_id)
 
1385
 
 
1386
    def _make_order(self):
 
1387
        """Return a suitable order for importing revisions.
 
1388
 
 
1389
        The order must be such that an revision is imported after all
 
1390
        its (present) parents.
 
1391
        """
 
1392
        todo = set(self.revisions.keys())
 
1393
        done = self.absent_revisions.copy()
 
1394
        o = []
 
1395
        while todo:
 
1396
            # scan through looking for a revision whose parents
 
1397
            # are all done
 
1398
            for rev_id in sorted(list(todo)):
 
1399
                rev = self.revisions[rev_id]
 
1400
                parent_ids = set(rev.parent_ids)
 
1401
                if parent_ids.issubset(done):
 
1402
                    # can take this one now
 
1403
                    o.append(rev_id)
 
1404
                    todo.remove(rev_id)
 
1405
                    done.add(rev_id)
 
1406
        return o
 
1407
 
 
1408
 
 
1409
class ConvertBzrDir5To6(Converter):
 
1410
    """Converts format 5 bzr dirs to format 6."""
 
1411
 
 
1412
    def __init__(self, to_convert, pb):
 
1413
        """Create a converter.
 
1414
 
 
1415
        :param to_convert: The disk object to convert.
 
1416
        :param pb: a progress bar to use for progress information.
 
1417
        """
 
1418
        super(ConvertBzrDir5To6, self).__init__(pb)
 
1419
        self.bzrdir = to_convert
 
1420
        
 
1421
    def convert(self):
 
1422
        """See Converter.convert()."""
 
1423
        self.pb.note('starting upgrade from format 5 to 6')
 
1424
        self._convert_to_prefixed()
 
1425
        return BzrDir.open(self.bzrdir.root_transport.base)
 
1426
 
 
1427
    def _convert_to_prefixed(self):
 
1428
        from bzrlib.store import hash_prefix
 
1429
        self.bzrdir.transport.delete('branch-format')
 
1430
        for store_name in ["weaves", "revision-store"]:
 
1431
            self.pb.note("adding prefixes to %s" % store_name) 
 
1432
            store_transport = self.bzrdir.transport.clone(store_name)
 
1433
            for filename in store_transport.list_dir('.'):
 
1434
                if (filename.endswith(".weave") or
 
1435
                    filename.endswith(".gz") or
 
1436
                    filename.endswith(".sig")):
 
1437
                    file_id = os.path.splitext(filename)[0]
 
1438
                else:
 
1439
                    file_id = filename
 
1440
                prefix_dir = hash_prefix(file_id)
 
1441
                # FIXME keep track of the dirs made RBC 20060121
 
1442
                try:
 
1443
                    store_transport.move(filename, prefix_dir + '/' + filename)
 
1444
                except errors.NoSuchFile: # catches missing dirs strangely enough
 
1445
                    store_transport.mkdir(prefix_dir)
 
1446
                    store_transport.move(filename, prefix_dir + '/' + filename)
 
1447
        self.bzrdir._control_files.put_utf8('branch-format', BzrDirFormat6().get_format_string())
 
1448
 
 
1449