/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

Add RepositoryFormats and allow bzrdir.open or create _repository to be used.

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
from cStringIO import StringIO
 
25
from unittest import TestSuite
 
26
 
 
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.trace import mutter
 
33
from bzrlib.symbol_versioning import *
 
34
from bzrlib.transport import get_transport
 
35
 
 
36
 
 
37
class BzrDir(object):
 
38
    """A .bzr control diretory.
 
39
    
 
40
    BzrDir instances let you create or open any of the things that can be
 
41
    found within .bzr - checkouts, branches and repositories.
 
42
    
 
43
    transport
 
44
        the transport which this bzr dir is rooted at (i.e. file:///.../.bzr/)
 
45
    """
 
46
 
 
47
    @staticmethod
 
48
    def create(base):
 
49
        """Create a new BzrDir at the url 'base'.
 
50
        
 
51
        This will call the current default formats initialize with base
 
52
        as the only parameter.
 
53
 
 
54
        If you need a specific format, consider creating an instance
 
55
        of that and calling initialize().
 
56
        """
 
57
        return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
 
58
 
 
59
    @staticmethod
 
60
    def create_repository(base):
 
61
        """Create a new BzrDir and Repository at the url 'base'.
 
62
 
 
63
        This will use the current default BzrDirFormat, and use whatever 
 
64
        repository format that that uses for bzrdirformat.create_repository.
 
65
 
 
66
        The Repository object is returned.
 
67
 
 
68
        This must be overridden as an instance method in child classes, where
 
69
        it should take no parameters and construct whatever repository format
 
70
        that child class desires.
 
71
        """
 
72
        bzrdir = BzrDir.create(base)
 
73
        return bzrdir.create_repository()
 
74
 
 
75
    def __init__(self, _transport, _format):
 
76
        """Initialize a Bzr control dir object.
 
77
        
 
78
        Only really common logic should reside here, concrete classes should be
 
79
        made with varying behaviours.
 
80
 
 
81
        _format: the format that is creating this BzrDir instance.
 
82
        _transport: the transport this dir is based at.
 
83
        """
 
84
        self._format = _format
 
85
        self.transport = _transport
 
86
 
 
87
    @staticmethod
 
88
    def open_unsupported(base):
 
89
        """Open a branch which is not supported."""
 
90
        return BzrDir.open(base, _unsupported=True)
 
91
        
 
92
    @staticmethod
 
93
    def open(base, _unsupported=False):
 
94
        """Open an existing branch, rooted at 'base' (url)
 
95
        
 
96
        _unsupported is a private parameter to the BzrDir class.
 
97
        """
 
98
        t = get_transport(base)
 
99
        mutter("trying to open %r with transport %r", base, t)
 
100
        format = BzrDirFormat.find_format(t)
 
101
        if not _unsupported and not format.is_supported():
 
102
            # see open_downlevel to open legacy branches.
 
103
            raise errors.UnsupportedFormatError(
 
104
                    'sorry, branch format %s not supported' % format,
 
105
                    ['use a different bzr version',
 
106
                     'or remove the .bzr directory'
 
107
                     ' and "bzr init" again'])
 
108
        return format.open(t)
 
109
 
 
110
    @staticmethod
 
111
    def open_containing(url):
 
112
        """Open an existing branch which contains url.
 
113
        
 
114
        This probes for a branch at url, and searches upwards from there.
 
115
 
 
116
        Basically we keep looking up until we find the control directory or
 
117
        run into the root.  If there isn't one, raises NotBranchError.
 
118
        If there is one and it is either an unrecognised format or an unsupported 
 
119
        format, UnknownFormatError or UnsupportedFormatError are raised.
 
120
        If there is one, it is returned, along with the unused portion of url.
 
121
        """
 
122
        t = get_transport(url)
 
123
        # this gets the normalised url back. I.e. '.' -> the full path.
 
124
        url = t.base
 
125
        while True:
 
126
            try:
 
127
                format = BzrDirFormat.find_format(t)
 
128
                return format.open(t), t.relpath(url)
 
129
            except errors.NotBranchError, e:
 
130
                mutter('not a branch in: %r %s', t.base, e)
 
131
            new_t = t.clone('..')
 
132
            if new_t.base == t.base:
 
133
                # reached the root, whatever that may be
 
134
                raise errors.NotBranchError(path=url)
 
135
            t = new_t
 
136
 
 
137
    def open_repository(self):
 
138
        """Open the repository object at this BzrDir if one is present.
 
139
        
 
140
        TODO: static convenience version of this?
 
141
        TODO: NoRepositoryError that can be raised.
 
142
        """
 
143
        raise NotImplementedError(self.open_repository)
 
144
 
 
145
 
 
146
class BzrDir4(BzrDir):
 
147
    """A .bzr version 4 control object."""
 
148
 
 
149
    def create_repository(self):
 
150
        """See BzrDir.create_repository."""
 
151
        from bzrlib.repository import RepositoryFormat4
 
152
        return RepositoryFormat4().initialize(self)
 
153
 
 
154
    def open_repository(self):
 
155
        """See BzrDir.open_repository."""
 
156
        from bzrlib.repository import RepositoryFormat4
 
157
        return RepositoryFormat4().open(self, _found=True)
 
158
 
 
159
 
 
160
class BzrDir5(BzrDir):
 
161
    """A .bzr version 5 control object."""
 
162
 
 
163
    def create_repository(self):
 
164
        """See BzrDir.create_repository."""
 
165
        from bzrlib.repository import RepositoryFormat5
 
166
        return RepositoryFormat5().initialize(self)
 
167
 
 
168
    def open_repository(self):
 
169
        """See BzrDir.open_repository."""
 
170
        from bzrlib.repository import RepositoryFormat5
 
171
        return RepositoryFormat5().open(self, _found=True)
 
172
 
 
173
 
 
174
class BzrDir6(BzrDir):
 
175
    """A .bzr version 6 control object."""
 
176
 
 
177
    def create_repository(self):
 
178
        """See BzrDir.create_repository."""
 
179
        from bzrlib.repository import RepositoryFormat6
 
180
        return RepositoryFormat6().initialize(self)
 
181
 
 
182
    def open_repository(self):
 
183
        """See BzrDir.open_repository."""
 
184
        from bzrlib.repository import RepositoryFormat6
 
185
        return RepositoryFormat6().open(self, _found=True)
 
186
 
 
187
 
 
188
class BzrDirFormat(object):
 
189
    """An encapsulation of the initialization and open routines for a format.
 
190
 
 
191
    Formats provide three things:
 
192
     * An initialization routine,
 
193
     * a format string,
 
194
     * an open routine.
 
195
 
 
196
    Formats are placed in an dict by their format string for reference 
 
197
    during bzrdir opening. These should be subclasses of BzrDirFormat
 
198
    for consistency.
 
199
 
 
200
    Once a format is deprecated, just deprecate the initialize and open
 
201
    methods on the format class. Do not deprecate the object, as the 
 
202
    object will be created every system load.
 
203
    """
 
204
 
 
205
    _default_format = None
 
206
    """The default format used for new .bzr dirs."""
 
207
 
 
208
    _formats = {}
 
209
    """The known formats."""
 
210
 
 
211
    @classmethod
 
212
    def find_format(klass, transport):
 
213
        """Return the format registered for URL."""
 
214
        try:
 
215
            format_string = transport.get(".bzr/branch-format").read()
 
216
            return klass._formats[format_string]
 
217
        except errors.NoSuchFile:
 
218
            raise errors.NotBranchError(path=transport.base)
 
219
        except KeyError:
 
220
            raise errors.UnknownFormatError(format_string)
 
221
 
 
222
    @classmethod
 
223
    def get_default_format(klass):
 
224
        """Return the current default format."""
 
225
        return klass._default_format
 
226
 
 
227
    def get_format_string(self):
 
228
        """Return the ASCII format string that identifies this format."""
 
229
        raise NotImplementedError(self.get_format_string)
 
230
 
 
231
    def initialize(self, url):
 
232
        """Create a bzr control dir at this url and return an opened copy."""
 
233
        # Since we don't have a .bzr directory, inherit the
 
234
        # mode from the root directory
 
235
        t = get_transport(url)
 
236
        temp_control = LockableFiles(t, '')
 
237
        temp_control._transport.mkdir('.bzr',
 
238
                                      # FIXME: RBC 20060121 dont peek under
 
239
                                      # the covers
 
240
                                      mode=temp_control._dir_mode)
 
241
        file_mode = temp_control._file_mode
 
242
        del temp_control
 
243
        mutter('created control directory in ' + t.base)
 
244
        control = t.clone('.bzr')
 
245
        lock_file = 'branch-lock'
 
246
        utf8_files = [('README', 
 
247
                       "This is a Bazaar-NG control directory.\n"
 
248
                       "Do not change any files in this directory.\n"),
 
249
                      ('branch-format', self.get_format_string()),
 
250
                      ]
 
251
        # NB: no need to escape relative paths that are url safe.
 
252
        control.put(lock_file, StringIO(), mode=file_mode)
 
253
        control_files = LockableFiles(control, lock_file)
 
254
        control_files.lock_write()
 
255
        try:
 
256
            for file, content in utf8_files:
 
257
                control_files.put_utf8(file, content)
 
258
        finally:
 
259
            control_files.unlock()
 
260
        return self.open(t, _found=True)
 
261
 
 
262
    def is_supported(self):
 
263
        """Is this format supported?
 
264
 
 
265
        Supported formats must be initializable and openable.
 
266
        Unsupported formats may not support initialization or committing or 
 
267
        some other features depending on the reason for not being supported.
 
268
        """
 
269
        return True
 
270
 
 
271
    def open(self, transport, _found=False):
 
272
        """Return an instance of this format for the dir transport points at.
 
273
        
 
274
        _found is a private parameter, do not use it.
 
275
        """
 
276
        if not _found:
 
277
            assert isinstance(BzrDirFormat.find_format(transport),
 
278
                              self.__class__)
 
279
        return self._open(transport)
 
280
 
 
281
    def _open(self, transport):
 
282
        """Template method helper for opening BzrDirectories.
 
283
 
 
284
        This performs the actual open and any additional logic or parameter
 
285
        passing.
 
286
        """
 
287
        raise NotImplementedError(self._open)
 
288
 
 
289
    @classmethod
 
290
    def register_format(klass, format):
 
291
        klass._formats[format.get_format_string()] = format
 
292
 
 
293
    @classmethod
 
294
    def set_default_format(klass, format):
 
295
        klass._default_format = format
 
296
 
 
297
    @classmethod
 
298
    def unregister_format(klass, format):
 
299
        assert klass._formats[format.get_format_string()] is format
 
300
        del klass._formats[format.get_format_string()]
 
301
 
 
302
 
 
303
class BzrDirFormat4(BzrDirFormat):
 
304
    """Bzr dir format 4.
 
305
 
 
306
    This format is a combined format for working tree, branch and repository.
 
307
    It has:
 
308
     - flat stores
 
309
     - TextStores for texts, inventories,revisions.
 
310
 
 
311
    This format is deprecated: it indexes texts using a text it which is
 
312
    removed in format 5; write support for this format has been removed.
 
313
    """
 
314
 
 
315
    def get_format_string(self):
 
316
        """See BzrDirFormat.get_format_string()."""
 
317
        return "Bazaar-NG branch, format 0.0.4\n"
 
318
 
 
319
    def initialize(self, url):
 
320
        """Format 4 branches cannot be created."""
 
321
        raise errors.UninitializableFormat(self)
 
322
 
 
323
    def is_supported(self):
 
324
        """Format 4 is not supported.
 
325
 
 
326
        It is not supported because the model changed from 4 to 5 and the
 
327
        conversion logic is expensive - so doing it on the fly was not 
 
328
        feasible.
 
329
        """
 
330
        return False
 
331
 
 
332
    def _open(self, transport):
 
333
        """See BzrDirFormat._open."""
 
334
        return BzrDir4(transport, self)
 
335
 
 
336
 
 
337
class BzrDirFormat5(BzrDirFormat):
 
338
    """Bzr control format 5.
 
339
 
 
340
    This format is a combined format for working tree, branch and repository.
 
341
    It has:
 
342
     - weaves for file texts and inventory
 
343
     - flat stores
 
344
     - TextStores for revisions and signatures.
 
345
    """
 
346
 
 
347
    def get_format_string(self):
 
348
        """See BzrDirFormat.get_format_string()."""
 
349
        return "Bazaar-NG branch, format 5\n"
 
350
 
 
351
    def _open(self, transport):
 
352
        """See BzrDirFormat._open."""
 
353
        return BzrDir5(transport, self)
 
354
 
 
355
 
 
356
class BzrDirFormat6(BzrDirFormat):
 
357
    """Bzr control format 6.
 
358
 
 
359
    This format is a combined format for working tree, branch and repository.
 
360
    It has:
 
361
     - weaves for file texts and inventory
 
362
     - hash subdirectory based stores.
 
363
     - TextStores for revisions and signatures.
 
364
    """
 
365
 
 
366
    def get_format_string(self):
 
367
        """See BzrDirFormat.get_format_string()."""
 
368
        return "Bazaar-NG branch, format 6\n"
 
369
 
 
370
    def _open(self, transport):
 
371
        """See BzrDirFormat._open."""
 
372
        return BzrDir6(transport, self)
 
373
 
 
374
 
 
375
BzrDirFormat.register_format(BzrDirFormat4())
 
376
BzrDirFormat.register_format(BzrDirFormat5())
 
377
__default_format = BzrDirFormat6()
 
378
BzrDirFormat.register_format(__default_format)
 
379
BzrDirFormat.set_default_format(__default_format)
 
380
 
 
381
 
 
382
class BzrDirTestProviderAdapter(object):
 
383
    """A tool to generate a suite testing multiple bzrdir formats at once.
 
384
 
 
385
    This is done by copying the test once for each transport and injecting
 
386
    the transport_server, transport_readonly_server, and bzrdir_format
 
387
    classes into each copy. Each copy is also given a new id() to make it
 
388
    easy to identify.
 
389
    """
 
390
 
 
391
    def __init__(self, transport_server, transport_readonly_server, formats):
 
392
        self._transport_server = transport_server
 
393
        self._transport_readonly_server = transport_readonly_server
 
394
        self._formats = formats
 
395
    
 
396
    def adapt(self, test):
 
397
        result = TestSuite()
 
398
        for format in self._formats:
 
399
            new_test = deepcopy(test)
 
400
            new_test.transport_server = self._transport_server
 
401
            new_test.transport_readonly_server = self._transport_readonly_server
 
402
            new_test.bzrdir_format = format
 
403
            def make_new_test_id():
 
404
                new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
 
405
                return lambda: new_id
 
406
            new_test.id = make_new_test_id()
 
407
            result.addTest(new_test)
 
408
        return result