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
base directory this is located under.
49
"""Create a new BzrDir at the url 'bzr'.
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))
59
def __init__(self, _transport, _format):
60
"""Initialize a Bzr control dir object.
62
Only really common logic should reside here, concrete classes should be
63
made with varying behaviours.
65
_format: the format that is creating this BzrDir instance.
66
_transport: the transport this dir is based at.
68
self._format = _format
69
self._transport = _transport
72
def open_unsupported(base):
73
"""Open a branch which is not supported."""
74
return BzrDir.open(base, _unsupported=True)
77
def open(base, _unsupported=False):
78
"""Open an existing branch, rooted at 'base' (url)
80
_unsupported is a private parameter to the BzrDir class.
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'])
95
def open_containing(url):
96
"""Open an existing branch which contains url.
98
This probes for a branch at url, and searches upwards from there.
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.
106
t = get_transport(url)
107
# this gets the normalised url back. I.e. '.' -> the full path.
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)
122
class BzrDirFormat(object):
123
"""An encapsulation of the initialization and open routines for a format.
125
Formats provide three things:
126
* An initialization routine,
130
Formats are placed in an dict by their format string for reference
131
during bzrdir opening. These should be subclasses of BzrDirFormat
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.
139
_default_format = None
140
"""The default format used for new branches."""
143
"""The known formats."""
146
def find_format(klass, transport):
147
"""Return the format registered for URL."""
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)
154
raise errors.UnknownFormatError(format_string)
157
def get_default_format(klass):
158
"""Return the current default format."""
159
return klass._default_format
161
def get_format_string(self):
162
"""Return the ASCII format string that identifies this format."""
163
raise NotImplementedError(self.get_format_string)
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
174
mode=temp_control._dir_mode)
175
file_mode = temp_control._file_mode
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()),
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()
190
for file, content in utf8_files:
191
control_files.put_utf8(file, content)
193
control_files.unlock()
194
return BzrDir(t, self)
196
def is_supported(self):
197
"""Is this format supported?
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.
205
def open(self, transport, _found=False):
206
"""Return an instance of this format for the dir transport points at.
208
_found is a private parameter, do not use it.
211
assert isinstance(BzrDirFormat.find_format(transport),
213
return BzrDir(transport, self)
216
def register_format(klass, format):
217
klass._formats[format.get_format_string()] = format
220
def set_default_format(klass, format):
221
klass._default_format = format
224
def unregister_format(klass, format):
225
assert klass._formats[format.get_format_string()] is format
226
del klass._formats[format.get_format_string()]
229
class BzrDirFormat4(BzrDirFormat):
232
This format is a combined format for working tree, branch and repository.
235
- TextStores for texts, inventories,revisions.
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.
241
def get_format_string(self):
242
"""See BzrDirFormat.get_format_string()."""
243
return "Bazaar-NG branch, format 0.0.4\n"
245
def initialize(self, url):
246
"""Format 4 branches cannot be created."""
247
raise errors.UninitializableFormat(self)
249
def is_supported(self):
250
"""Format 4 is not supported.
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
259
class BzrDirFormat5(BzrDirFormat):
260
"""Bzr control format 5.
262
This format is a combined format for working tree, branch and repository.
264
- weaves for file texts and inventory
266
- TextStores for revisions and signatures.
269
def get_format_string(self):
270
"""See BzrDirFormat.get_format_string()."""
271
return "Bazaar-NG branch, format 5\n"
274
class BzrDirFormat6(BzrDirFormat):
275
"""Bzr control format 6.
277
This format is a combined format for working tree, branch and repository.
279
- weaves for file texts and inventory
280
- hash subdirectory based stores.
281
- TextStores for revisions and signatures.
284
def get_format_string(self):
285
"""See BzrDirFormat.get_format_string()."""
286
return "Bazaar-NG branch, format 6\n"
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)
296
class BzrDirTestProviderAdapter(object):
297
"""A tool to generate a suite testing multiple bzrdir formats at once.
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
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
310
def adapt(self, test):
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)