~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/bzrdir.py

Basic BzrDir support.

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
    base
 
44
        base directory this is located under.
 
45
    """
 
46
 
 
47
    @staticmethod
 
48
    def create(base):
 
49
        """Create a new BzrDir at the url 'bzr'.
 
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
    def __init__(self, _transport, _format):
 
60
        """Initialize a Bzr control dir object.
 
61
        
 
62
        Only really common logic should reside here, concrete classes should be
 
63
        made with varying behaviours.
 
64
 
 
65
        _format: the format that is creating this BzrDir instance.
 
66
        _transport: the transport this dir is based at.
 
67
        """
 
68
        self._format = _format
 
69
        self._transport = _transport
 
70
 
 
71
    @staticmethod
 
72
    def open_unsupported(base):
 
73
        """Open a branch which is not supported."""
 
74
        return BzrDir.open(base, _unsupported=True)
 
75
        
 
76
    @staticmethod
 
77
    def open(base, _unsupported=False):
 
78
        """Open an existing branch, rooted at 'base' (url)
 
79
        
 
80
        _unsupported is a private parameter to the BzrDir class.
 
81
        """
 
82
        t = get_transport(base)
 
83
        mutter("trying to open %r with transport %r", base, t)
 
84
        format = BzrDirFormat.find_format(t)
 
85
        if not _unsupported and not format.is_supported():
 
86
            # see open_downlevel to open legacy branches.
 
87
            raise errors.UnsupportedFormatError(
 
88
                    'sorry, branch format %s not supported' % format,
 
89
                    ['use a different bzr version',
 
90
                     'or remove the .bzr directory'
 
91
                     ' and "bzr init" again'])
 
92
        return format.open(t)
 
93
 
 
94
    @staticmethod
 
95
    def open_containing(url):
 
96
        """Open an existing branch which contains url.
 
97
        
 
98
        This probes for a branch at url, and searches upwards from there.
 
99
 
 
100
        Basically we keep looking up until we find the control directory or
 
101
        run into the root.  If there isn't one, raises NotBranchError.
 
102
        If there is one and it is either an unrecognised format or an unsupported 
 
103
        format, UnknownFormatError or UnsupportedFormatError are raised.
 
104
        If there is one, it is returned, along with the unused portion of url.
 
105
        """
 
106
        t = get_transport(url)
 
107
        # this gets the normalised url back. I.e. '.' -> the full path.
 
108
        url = t.base
 
109
        while True:
 
110
            try:
 
111
                format = BzrDirFormat.find_format(t)
 
112
                return format.open(t), t.relpath(url)
 
113
            except errors.NotBranchError, e:
 
114
                mutter('not a branch in: %r %s', t.base, e)
 
115
            new_t = t.clone('..')
 
116
            if new_t.base == t.base:
 
117
                # reached the root, whatever that may be
 
118
                raise errors.NotBranchError(path=url)
 
119
            t = new_t
 
120
 
 
121
 
 
122
class BzrDirFormat(object):
 
123
    """An encapsulation of the initialization and open routines for a format.
 
124
 
 
125
    Formats provide three things:
 
126
     * An initialization routine,
 
127
     * a format string,
 
128
     * an open routine.
 
129
 
 
130
    Formats are placed in an dict by their format string for reference 
 
131
    during bzrdir opening. These should be subclasses of BzrDirFormat
 
132
    for consistency.
 
133
 
 
134
    Once a format is deprecated, just deprecate the initialize and open
 
135
    methods on the format class. Do not deprecate the object, as the 
 
136
    object will be created every system load.
 
137
    """
 
138
 
 
139
    _default_format = None
 
140
    """The default format used for new branches."""
 
141
 
 
142
    _formats = {}
 
143
    """The known formats."""
 
144
 
 
145
    @classmethod
 
146
    def find_format(klass, transport):
 
147
        """Return the format registered for URL."""
 
148
        try:
 
149
            format_string = transport.get(".bzr/branch-format").read()
 
150
            return klass._formats[format_string]
 
151
        except errors.NoSuchFile:
 
152
            raise errors.NotBranchError(path=transport.base)
 
153
        except KeyError:
 
154
            raise errors.UnknownFormatError(format_string)
 
155
 
 
156
    @classmethod
 
157
    def get_default_format(klass):
 
158
        """Return the current default format."""
 
159
        return klass._default_format
 
160
 
 
161
    def get_format_string(self):
 
162
        """Return the ASCII format string that identifies this format."""
 
163
        raise NotImplementedError(self.get_format_string)
 
164
 
 
165
    def initialize(self, url):
 
166
        """Create a bzr control dir at this url and return an opened copy."""
 
167
        # Since we don't have a .bzr directory, inherit the
 
168
        # mode from the root directory
 
169
        t = get_transport(url)
 
170
        temp_control = LockableFiles(t, '')
 
171
        temp_control._transport.mkdir('.bzr',
 
172
                                      # FIXME: RBC 20060121 dont peek under
 
173
                                      # the covers
 
174
                                      mode=temp_control._dir_mode)
 
175
        file_mode = temp_control._file_mode
 
176
        del temp_control
 
177
        mutter('created control directory in ' + t.base)
 
178
        control = t.clone('.bzr')
 
179
        lock_file = 'branch-lock'
 
180
        utf8_files = [('README', 
 
181
                       "This is a Bazaar-NG control directory.\n"
 
182
                       "Do not change any files in this directory.\n"),
 
183
                      ('branch-format', self.get_format_string()),
 
184
                      ]
 
185
        # NB: no need to escape relative paths that are url safe.
 
186
        control.put(lock_file, StringIO(), mode=file_mode)
 
187
        control_files = LockableFiles(control, lock_file)
 
188
        control_files.lock_write()
 
189
        try:
 
190
            for file, content in utf8_files:
 
191
                control_files.put_utf8(file, content)
 
192
        finally:
 
193
            control_files.unlock()
 
194
        return BzrDir(t, self)
 
195
 
 
196
    def is_supported(self):
 
197
        """Is this format supported?
 
198
 
 
199
        Supported formats must be initializable and openable.
 
200
        Unsupported formats may not support initialization or committing or 
 
201
        some other features depending on the reason for not being supported.
 
202
        """
 
203
        return True
 
204
 
 
205
    def open(self, transport, _found=False):
 
206
        """Return an instance of this format for the dir transport points at.
 
207
        
 
208
        _found is a private parameter, do not use it.
 
209
        """
 
210
        if not _found:
 
211
            assert isinstance(BzrDirFormat.find_format(transport),
 
212
                              self.__class__)
 
213
        return BzrDir(transport, self)
 
214
 
 
215
    @classmethod
 
216
    def register_format(klass, format):
 
217
        klass._formats[format.get_format_string()] = format
 
218
 
 
219
    @classmethod
 
220
    def set_default_format(klass, format):
 
221
        klass._default_format = format
 
222
 
 
223
    @classmethod
 
224
    def unregister_format(klass, format):
 
225
        assert klass._formats[format.get_format_string()] is format
 
226
        del klass._formats[format.get_format_string()]
 
227
 
 
228
 
 
229
class BzrDirFormat4(BzrDirFormat):
 
230
    """Bzr dir format 4.
 
231
 
 
232
    This format is a combined format for working tree, branch and repository.
 
233
    It has:
 
234
     - flat stores
 
235
     - TextStores for texts, inventories,revisions.
 
236
 
 
237
    This format is deprecated: it indexes texts using a text it which is
 
238
    removed in format 5; write support for this format has been removed.
 
239
    """
 
240
 
 
241
    def get_format_string(self):
 
242
        """See BzrDirFormat.get_format_string()."""
 
243
        return "Bazaar-NG branch, format 0.0.4\n"
 
244
 
 
245
    def initialize(self, url):
 
246
        """Format 4 branches cannot be created."""
 
247
        raise errors.UninitializableFormat(self)
 
248
 
 
249
    def is_supported(self):
 
250
        """Format 4 is not supported.
 
251
 
 
252
        It is not supported because the model changed from 4 to 5 and the
 
253
        conversion logic is expensive - so doing it on the fly was not 
 
254
        feasible.
 
255
        """
 
256
        return False
 
257
 
 
258
 
 
259
class BzrDirFormat5(BzrDirFormat):
 
260
    """Bzr control format 5.
 
261
 
 
262
    This format is a combined format for working tree, branch and repository.
 
263
    It has:
 
264
     - weaves for file texts and inventory
 
265
     - flat stores
 
266
     - TextStores for revisions and signatures.
 
267
    """
 
268
 
 
269
    def get_format_string(self):
 
270
        """See BzrDirFormat.get_format_string()."""
 
271
        return "Bazaar-NG branch, format 5\n"
 
272
 
 
273
 
 
274
class BzrDirFormat6(BzrDirFormat):
 
275
    """Bzr control format 6.
 
276
 
 
277
    This format is a combined format for working tree, branch and repository.
 
278
    It has:
 
279
     - weaves for file texts and inventory
 
280
     - hash subdirectory based stores.
 
281
     - TextStores for revisions and signatures.
 
282
    """
 
283
 
 
284
    def get_format_string(self):
 
285
        """See BzrDirFormat.get_format_string()."""
 
286
        return "Bazaar-NG branch, format 6\n"
 
287
 
 
288
 
 
289
BzrDirFormat.register_format(BzrDirFormat4())
 
290
BzrDirFormat.register_format(BzrDirFormat5())
 
291
__default_format = BzrDirFormat6()
 
292
BzrDirFormat.register_format(__default_format)
 
293
BzrDirFormat.set_default_format(__default_format)
 
294
 
 
295
 
 
296
class BzrDirTestProviderAdapter(object):
 
297
    """A tool to generate a suite testing multiple bzrdir formats at once.
 
298
 
 
299
    This is done by copying the test once for each transport and injecting
 
300
    the transport_server, transport_readonly_server, and bzrdir_format
 
301
    classes into each copy. Each copy is also given a new id() to make it
 
302
    easy to identify.
 
303
    """
 
304
 
 
305
    def __init__(self, transport_server, transport_readonly_server, formats):
 
306
        self._transport_server = transport_server
 
307
        self._transport_readonly_server = transport_readonly_server
 
308
        self._formats = formats
 
309
    
 
310
    def adapt(self, test):
 
311
        result = TestSuite()
 
312
        for format in self._formats:
 
313
            new_test = deepcopy(test)
 
314
            new_test.transport_server = self._transport_server
 
315
            new_test.transport_readonly_server = self._transport_readonly_server
 
316
            new_test.bzrdir_format = format
 
317
            def make_new_test_id():
 
318
                new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
 
319
                return lambda: new_id
 
320
            new_test.id = make_new_test_id()
 
321
            result.addTest(new_test)
 
322
        return result