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
segments = base.split('/')
58
if segments and segments[-1] not in ('', '.'):
59
parent = '/'.join(segments[:-1])
60
t = bzrlib.transport.get_transport(parent)
63
except errors.FileExists:
65
return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
67
def create_branch(self):
68
"""Create a branch in this BzrDir.
70
The bzrdirs format will control what branch format is created.
71
For more control see BranchFormatXX.create(a_bzrdir).
73
raise NotImplementedError(self.create_branch)
76
def create_branch_and_repo(base):
77
"""Create a new BzrDir, Branch and Repository at the url 'base'.
79
This will use the current default BzrDirFormat, and use whatever
80
repository format that that uses via bzrdir.create_branch and
83
The created Branch object is returned.
85
bzrdir = BzrDir.create(base)
86
bzrdir.create_repository()
87
return bzrdir.create_branch()
90
def create_repository(base):
91
"""Create a new BzrDir and Repository at the url 'base'.
93
This will use the current default BzrDirFormat, and use whatever
94
repository format that that uses for bzrdirformat.create_repository.
96
The Repository object is returned.
98
This must be overridden as an instance method in child classes, where
99
it should take no parameters and construct whatever repository format
100
that child class desires.
102
bzrdir = BzrDir.create(base)
103
return bzrdir.create_repository()
105
def __init__(self, _transport, _format):
106
"""Initialize a Bzr control dir object.
108
Only really common logic should reside here, concrete classes should be
109
made with varying behaviours.
111
_format: the format that is creating this BzrDir instance.
112
_transport: the transport this dir is based at.
114
self._format = _format
115
self.transport = _transport.clone('.bzr')
118
def open_unsupported(base):
119
"""Open a branch which is not supported."""
120
return BzrDir.open(base, _unsupported=True)
123
def open(base, _unsupported=False):
124
"""Open an existing branch, rooted at 'base' (url)
126
_unsupported is a private parameter to the BzrDir class.
128
t = get_transport(base)
129
mutter("trying to open %r with transport %r", base, t)
130
format = BzrDirFormat.find_format(t)
131
if not _unsupported and not format.is_supported():
132
# see open_downlevel to open legacy branches.
133
raise errors.UnsupportedFormatError(
134
'sorry, format %s not supported' % format,
135
['use a different bzr version',
136
'or remove the .bzr directory'
137
' and "bzr init" again'])
138
return format.open(t, _found=True)
140
def open_branch(self):
141
"""Open the branch object at this BzrDir if one is present.
143
TODO: static convenience version of this?
145
raise NotImplementedError(self.open_branch)
148
def open_containing(url):
149
"""Open an existing branch which contains url.
151
This probes for a branch at url, and searches upwards from there.
153
Basically we keep looking up until we find the control directory or
154
run into the root. If there isn't one, raises NotBranchError.
155
If there is one and it is either an unrecognised format or an unsupported
156
format, UnknownFormatError or UnsupportedFormatError are raised.
157
If there is one, it is returned, along with the unused portion of url.
159
t = get_transport(url)
160
# this gets the normalised url back. I.e. '.' -> the full path.
164
format = BzrDirFormat.find_format(t)
165
return format.open(t), t.relpath(url)
166
except errors.NotBranchError, e:
167
mutter('not a branch in: %r %s', t.base, e)
168
new_t = t.clone('..')
169
if new_t.base == t.base:
170
# reached the root, whatever that may be
171
raise errors.NotBranchError(path=url)
174
def open_repository(self):
175
"""Open the repository object at this BzrDir if one is present.
177
TODO: static convenience version of this?
178
TODO: NoRepositoryError that can be raised.
180
raise NotImplementedError(self.open_repository)
183
class BzrDir4(BzrDir):
184
"""A .bzr version 4 control object."""
186
def create_branch(self):
187
"""See BzrDir.create_branch."""
188
from bzrlib.branch import BzrBranchFormat4
189
return BzrBranchFormat4().initialize(self)
191
def create_repository(self):
192
"""See BzrDir.create_repository."""
193
from bzrlib.repository import RepositoryFormat4
194
return RepositoryFormat4().initialize(self)
196
def open_branch(self):
197
"""See BzrDir.open_branch."""
198
from bzrlib.branch import BzrBranchFormat4
199
return BzrBranchFormat4().open(self, _found=True)
201
def open_repository(self):
202
"""See BzrDir.open_repository."""
203
from bzrlib.repository import RepositoryFormat4
204
return RepositoryFormat4().open(self, _found=True)
207
class BzrDir5(BzrDir):
208
"""A .bzr version 5 control object."""
210
def create_branch(self):
211
"""See BzrDir.create_branch."""
212
from bzrlib.branch import BzrBranchFormat4
213
return BzrBranchFormat4().initialize(self)
215
def create_repository(self):
216
"""See BzrDir.create_repository."""
217
from bzrlib.repository import RepositoryFormat5
218
return RepositoryFormat5().initialize(self)
220
def open_branch(self):
221
"""See BzrDir.open_branch."""
222
from bzrlib.branch import BzrBranchFormat4
223
return BzrBranchFormat4().open(self, _found=True)
225
def open_repository(self):
226
"""See BzrDir.open_repository."""
227
from bzrlib.repository import RepositoryFormat5
228
return RepositoryFormat5().open(self, _found=True)
231
class BzrDir6(BzrDir):
232
"""A .bzr version 6 control object."""
234
def create_branch(self):
235
"""See BzrDir.create_branch."""
236
from bzrlib.branch import BzrBranchFormat4
237
return BzrBranchFormat4().initialize(self)
239
def create_repository(self):
240
"""See BzrDir.create_repository."""
241
from bzrlib.repository import RepositoryFormat6
242
return RepositoryFormat6().initialize(self)
244
def open_branch(self):
245
"""See BzrDir.open_branch."""
246
from bzrlib.branch import BzrBranchFormat4
247
return BzrBranchFormat4().open(self, _found=True)
249
def open_repository(self):
250
"""See BzrDir.open_repository."""
251
from bzrlib.repository import RepositoryFormat6
252
return RepositoryFormat6().open(self, _found=True)
255
class BzrDirFormat(object):
256
"""An encapsulation of the initialization and open routines for a format.
258
Formats provide three things:
259
* An initialization routine,
263
Formats are placed in an dict by their format string for reference
264
during bzrdir opening. These should be subclasses of BzrDirFormat
267
Once a format is deprecated, just deprecate the initialize and open
268
methods on the format class. Do not deprecate the object, as the
269
object will be created every system load.
272
_default_format = None
273
"""The default format used for new .bzr dirs."""
276
"""The known formats."""
279
def find_format(klass, transport):
280
"""Return the format registered for URL."""
282
format_string = transport.get(".bzr/branch-format").read()
283
return klass._formats[format_string]
284
except errors.NoSuchFile:
285
raise errors.NotBranchError(path=transport.base)
287
raise errors.UnknownFormatError(format_string)
290
def get_default_format(klass):
291
"""Return the current default format."""
292
return klass._default_format
294
def get_format_string(self):
295
"""Return the ASCII format string that identifies this format."""
296
raise NotImplementedError(self.get_format_string)
298
def initialize(self, url):
299
"""Create a bzr control dir at this url and return an opened copy."""
300
# Since we don't have a .bzr directory, inherit the
301
# mode from the root directory
302
t = get_transport(url)
303
temp_control = LockableFiles(t, '')
304
temp_control._transport.mkdir('.bzr',
305
# FIXME: RBC 20060121 dont peek under
307
mode=temp_control._dir_mode)
308
file_mode = temp_control._file_mode
310
mutter('created control directory in ' + t.base)
311
control = t.clone('.bzr')
312
lock_file = 'branch-lock'
313
utf8_files = [('README',
314
"This is a Bazaar-NG control directory.\n"
315
"Do not change any files in this directory.\n"),
316
('branch-format', self.get_format_string()),
318
# NB: no need to escape relative paths that are url safe.
319
control.put(lock_file, StringIO(), mode=file_mode)
320
control_files = LockableFiles(control, lock_file)
321
control_files.lock_write()
323
for file, content in utf8_files:
324
control_files.put_utf8(file, content)
326
control_files.unlock()
327
return self.open(t, _found=True)
329
def is_supported(self):
330
"""Is this format supported?
332
Supported formats must be initializable and openable.
333
Unsupported formats may not support initialization or committing or
334
some other features depending on the reason for not being supported.
338
def open(self, transport, _found=False):
339
"""Return an instance of this format for the dir transport points at.
341
_found is a private parameter, do not use it.
344
assert isinstance(BzrDirFormat.find_format(transport),
346
return self._open(transport)
348
def _open(self, transport):
349
"""Template method helper for opening BzrDirectories.
351
This performs the actual open and any additional logic or parameter
354
raise NotImplementedError(self._open)
357
def register_format(klass, format):
358
klass._formats[format.get_format_string()] = format
361
def set_default_format(klass, format):
362
klass._default_format = format
365
def unregister_format(klass, format):
366
assert klass._formats[format.get_format_string()] is format
367
del klass._formats[format.get_format_string()]
370
class BzrDirFormat4(BzrDirFormat):
373
This format is a combined format for working tree, branch and repository.
375
- Format 1 working trees
377
- Format 4 repositories
379
This format is deprecated: it indexes texts using a text it which is
380
removed in format 5; write support for this format has been removed.
383
def get_format_string(self):
384
"""See BzrDirFormat.get_format_string()."""
385
return "Bazaar-NG branch, format 0.0.4\n"
387
def initialize(self, url):
388
"""Format 4 branches cannot be created."""
389
raise errors.UninitializableFormat(self)
391
def is_supported(self):
392
"""Format 4 is not supported.
394
It is not supported because the model changed from 4 to 5 and the
395
conversion logic is expensive - so doing it on the fly was not
400
def _open(self, transport):
401
"""See BzrDirFormat._open."""
402
return BzrDir4(transport, self)
405
class BzrDirFormat5(BzrDirFormat):
406
"""Bzr control format 5.
408
This format is a combined format for working tree, branch and repository.
410
- Format 2 working trees
412
- Format 6 repositories
415
def get_format_string(self):
416
"""See BzrDirFormat.get_format_string()."""
417
return "Bazaar-NG branch, format 5\n"
419
def _open(self, transport):
420
"""See BzrDirFormat._open."""
421
return BzrDir5(transport, self)
424
class BzrDirFormat6(BzrDirFormat):
425
"""Bzr control format 6.
427
This format is a combined format for working tree, branch and repository.
429
- Format 2 working trees
431
- Format 6 repositories
434
def get_format_string(self):
435
"""See BzrDirFormat.get_format_string()."""
436
return "Bazaar-NG branch, format 6\n"
438
def _open(self, transport):
439
"""See BzrDirFormat._open."""
440
return BzrDir6(transport, self)
443
BzrDirFormat.register_format(BzrDirFormat4())
444
BzrDirFormat.register_format(BzrDirFormat5())
445
__default_format = BzrDirFormat6()
446
BzrDirFormat.register_format(__default_format)
447
BzrDirFormat.set_default_format(__default_format)
450
class BzrDirTestProviderAdapter(object):
451
"""A tool to generate a suite testing multiple bzrdir formats at once.
453
This is done by copying the test once for each transport and injecting
454
the transport_server, transport_readonly_server, and bzrdir_format
455
classes into each copy. Each copy is also given a new id() to make it
459
def __init__(self, transport_server, transport_readonly_server, formats):
460
self._transport_server = transport_server
461
self._transport_readonly_server = transport_readonly_server
462
self._formats = formats
464
def adapt(self, test):
466
for format in self._formats:
467
new_test = deepcopy(test)
468
new_test.transport_server = self._transport_server
469
new_test.transport_readonly_server = self._transport_readonly_server
470
new_test.bzrdir_format = format
471
def make_new_test_id():
472
new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
473
return lambda: new_id
474
new_test.id = make_new_test_id()
475
result.addTest(new_test)
479
class ScratchDir(BzrDir6):
480
"""Special test class: a bzrdir that cleans up itself..
483
>>> base = d.transport.base
486
>>> b.transport.__del__()
491
def __init__(self, files=[], dirs=[], transport=None):
492
"""Make a test branch.
494
This creates a temporary directory and runs init-tree in it.
496
If any files are listed, they are created in the working copy.
498
if transport is None:
499
transport = bzrlib.transport.local.ScratchTransport()
500
# local import for scope restriction
501
BzrDirFormat6().initialize(transport.base)
502
super(ScratchDir, self).__init__(transport, BzrDirFormat6())
503
self.create_repository()
505
from bzrlib.workingtree import WorkingTree
506
WorkingTree.create(self.open_branch(), transport.base)
508
super(ScratchDir, self).__init__(transport, BzrDirFormat6())
510
# BzrBranch creates a clone to .bzr and then forgets about the
511
# original transport. A ScratchTransport() deletes itself and
512
# everything underneath it when it goes away, so we need to
513
# grab a local copy to prevent that from happening
514
self._transport = transport
517
self._transport.mkdir(d)
520
self._transport.put(f, 'content of %s' % f)
524
>>> orig = ScratchDir(files=["file1", "file2"])
525
>>> os.listdir(orig.base)
526
[u'.bzr', u'file1', u'file2']
527
>>> clone = orig.clone()
528
>>> if os.name != 'nt':
529
... os.path.samefile(orig.base, clone.base)
531
... orig.base == clone.base
534
>>> os.listdir(clone.base)
535
[u'.bzr', u'file1', u'file2']
537
from shutil import copytree
538
from bzrlib.osutils import mkdtemp
541
copytree(self.base, base, symlinks=True)
543
transport=bzrlib.transport.local.ScratchTransport(base))