1
# Copyright (C) 2005 Canonical Ltd
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.
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.
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
17
"""BzrDir logic. The BzrDir is the basic control directory used by bzr.
19
At format 7 this was split out into Branch, Repository and Checkout control
23
from copy import deepcopy
24
from cStringIO import StringIO
25
from unittest import TestSuite
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
38
"""A .bzr control diretory.
40
BzrDir instances let you create or open any of the things that can be
41
found within .bzr - checkouts, branches and repositories.
44
the transport which this bzr dir is rooted at (i.e. file:///.../.bzr/)
49
"""Create a new BzrDir at the url 'base'.
51
This will call the current default formats initialize with base
52
as the only parameter.
54
If you need a specific format, consider creating an instance
55
of that and calling initialize().
57
return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
60
def create_repository(base):
61
"""Create a new BzrDir and Repository at the url 'base'.
63
This will use the current default BzrDirFormat, and use whatever
64
repository format that that uses for bzrdirformat.create_repository.
66
The Repository object is returned.
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.
72
bzrdir = BzrDir.create(base)
73
return bzrdir.create_repository()
75
def __init__(self, _transport, _format):
76
"""Initialize a Bzr control dir object.
78
Only really common logic should reside here, concrete classes should be
79
made with varying behaviours.
81
_format: the format that is creating this BzrDir instance.
82
_transport: the transport this dir is based at.
84
self._format = _format
85
self.transport = _transport
88
def open_unsupported(base):
89
"""Open a branch which is not supported."""
90
return BzrDir.open(base, _unsupported=True)
93
def open(base, _unsupported=False):
94
"""Open an existing branch, rooted at 'base' (url)
96
_unsupported is a private parameter to the BzrDir class.
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)
111
def open_containing(url):
112
"""Open an existing branch which contains url.
114
This probes for a branch at url, and searches upwards from there.
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.
122
t = get_transport(url)
123
# this gets the normalised url back. I.e. '.' -> the full path.
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)
137
def open_repository(self):
138
"""Open the repository object at this BzrDir if one is present.
140
TODO: static convenience version of this?
141
TODO: NoRepositoryError that can be raised.
143
raise NotImplementedError(self.open_repository)
146
class BzrDir4(BzrDir):
147
"""A .bzr version 4 control object."""
149
def create_repository(self):
150
"""See BzrDir.create_repository."""
151
from bzrlib.repository import RepositoryFormat4
152
return RepositoryFormat4().initialize(self)
154
def open_repository(self):
155
"""See BzrDir.open_repository."""
156
from bzrlib.repository import RepositoryFormat4
157
return RepositoryFormat4().open(self, _found=True)
160
class BzrDir5(BzrDir):
161
"""A .bzr version 5 control object."""
163
def create_repository(self):
164
"""See BzrDir.create_repository."""
165
from bzrlib.repository import RepositoryFormat5
166
return RepositoryFormat5().initialize(self)
168
def open_repository(self):
169
"""See BzrDir.open_repository."""
170
from bzrlib.repository import RepositoryFormat5
171
return RepositoryFormat5().open(self, _found=True)
174
class BzrDir6(BzrDir):
175
"""A .bzr version 6 control object."""
177
def create_repository(self):
178
"""See BzrDir.create_repository."""
179
from bzrlib.repository import RepositoryFormat6
180
return RepositoryFormat6().initialize(self)
182
def open_repository(self):
183
"""See BzrDir.open_repository."""
184
from bzrlib.repository import RepositoryFormat6
185
return RepositoryFormat6().open(self, _found=True)
188
class BzrDirFormat(object):
189
"""An encapsulation of the initialization and open routines for a format.
191
Formats provide three things:
192
* An initialization routine,
196
Formats are placed in an dict by their format string for reference
197
during bzrdir opening. These should be subclasses of BzrDirFormat
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.
205
_default_format = None
206
"""The default format used for new .bzr dirs."""
209
"""The known formats."""
212
def find_format(klass, transport):
213
"""Return the format registered for URL."""
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)
220
raise errors.UnknownFormatError(format_string)
223
def get_default_format(klass):
224
"""Return the current default format."""
225
return klass._default_format
227
def get_format_string(self):
228
"""Return the ASCII format string that identifies this format."""
229
raise NotImplementedError(self.get_format_string)
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
240
mode=temp_control._dir_mode)
241
file_mode = temp_control._file_mode
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()),
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()
256
for file, content in utf8_files:
257
control_files.put_utf8(file, content)
259
control_files.unlock()
260
return self.open(t, _found=True)
262
def is_supported(self):
263
"""Is this format supported?
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.
271
def open(self, transport, _found=False):
272
"""Return an instance of this format for the dir transport points at.
274
_found is a private parameter, do not use it.
277
assert isinstance(BzrDirFormat.find_format(transport),
279
return self._open(transport)
281
def _open(self, transport):
282
"""Template method helper for opening BzrDirectories.
284
This performs the actual open and any additional logic or parameter
287
raise NotImplementedError(self._open)
290
def register_format(klass, format):
291
klass._formats[format.get_format_string()] = format
294
def set_default_format(klass, format):
295
klass._default_format = format
298
def unregister_format(klass, format):
299
assert klass._formats[format.get_format_string()] is format
300
del klass._formats[format.get_format_string()]
303
class BzrDirFormat4(BzrDirFormat):
306
This format is a combined format for working tree, branch and repository.
309
- TextStores for texts, inventories,revisions.
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.
315
def get_format_string(self):
316
"""See BzrDirFormat.get_format_string()."""
317
return "Bazaar-NG branch, format 0.0.4\n"
319
def initialize(self, url):
320
"""Format 4 branches cannot be created."""
321
raise errors.UninitializableFormat(self)
323
def is_supported(self):
324
"""Format 4 is not supported.
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
332
def _open(self, transport):
333
"""See BzrDirFormat._open."""
334
return BzrDir4(transport, self)
337
class BzrDirFormat5(BzrDirFormat):
338
"""Bzr control format 5.
340
This format is a combined format for working tree, branch and repository.
342
- weaves for file texts and inventory
344
- TextStores for revisions and signatures.
347
def get_format_string(self):
348
"""See BzrDirFormat.get_format_string()."""
349
return "Bazaar-NG branch, format 5\n"
351
def _open(self, transport):
352
"""See BzrDirFormat._open."""
353
return BzrDir5(transport, self)
356
class BzrDirFormat6(BzrDirFormat):
357
"""Bzr control format 6.
359
This format is a combined format for working tree, branch and repository.
361
- weaves for file texts and inventory
362
- hash subdirectory based stores.
363
- TextStores for revisions and signatures.
366
def get_format_string(self):
367
"""See BzrDirFormat.get_format_string()."""
368
return "Bazaar-NG branch, format 6\n"
370
def _open(self, transport):
371
"""See BzrDirFormat._open."""
372
return BzrDir6(transport, self)
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)
382
class BzrDirTestProviderAdapter(object):
383
"""A tool to generate a suite testing multiple bzrdir formats at once.
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
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
396
def adapt(self, test):
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)