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
35
from bzrlib.transport.local import LocalTransport
39
"""A .bzr control diretory.
41
BzrDir instances let you create or open any of the things that can be
42
found within .bzr - checkouts, branches and repositories.
45
the transport which this bzr dir is rooted at (i.e. file:///.../.bzr/)
48
def _check_supported(self, format, allow_unsupported):
49
"""Check whether format is a supported format.
51
If allow_unsupported is True, this is a no-op.
53
if not allow_unsupported and not format.is_supported():
54
raise errors.UnsupportedFormatError(format)
58
"""Create a new BzrDir at the url 'base'.
60
This will call the current default formats initialize with base
61
as the only parameter.
63
If you need a specific format, consider creating an instance
64
of that and calling initialize().
66
segments = base.split('/')
67
if segments and segments[-1] not in ('', '.'):
68
parent = '/'.join(segments[:-1])
69
t = bzrlib.transport.get_transport(parent)
72
except errors.FileExists:
74
return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
76
def create_branch(self):
77
"""Create a branch in this BzrDir.
79
The bzrdirs format will control what branch format is created.
80
For more control see BranchFormatXX.create(a_bzrdir).
82
raise NotImplementedError(self.create_branch)
85
def create_branch_and_repo(base):
86
"""Create a new BzrDir, Branch and Repository at the url 'base'.
88
This will use the current default BzrDirFormat, and use whatever
89
repository format that that uses via bzrdir.create_branch and
92
The created Branch object is returned.
94
bzrdir = BzrDir.create(base)
95
bzrdir.create_repository()
96
return bzrdir.create_branch()
99
def create_repository(base):
100
"""Create a new BzrDir and Repository at the url 'base'.
102
This will use the current default BzrDirFormat, and use whatever
103
repository format that that uses for bzrdirformat.create_repository.
105
The Repository object is returned.
107
This must be overridden as an instance method in child classes, where
108
it should take no parameters and construct whatever repository format
109
that child class desires.
111
bzrdir = BzrDir.create(base)
112
return bzrdir.create_repository()
115
def create_standalone_workingtree(base):
116
"""Create a new BzrDir, WorkingTree, Branch and Repository at 'base'.
118
'base' must be a local path or a file:// url.
120
This will use the current default BzrDirFormat, and use whatever
121
repository format that that uses for bzrdirformat.create_workingtree,
122
create_branch and create_repository.
124
The WorkingTree object is returned.
126
t = get_transport(safe_unicode(base))
127
if not isinstance(t, LocalTransport):
128
raise errors.NotLocalUrl(base)
129
bzrdir = BzrDir.create_branch_and_repo(safe_unicode(base)).bzrdir
130
return bzrdir.create_workingtree()
132
def create_workingtree(self):
133
"""Create a working tree at this BzrDir"""
134
raise NotImplementedError(self.create_workingtree)
136
def get_branch_transport(self, branch_format):
137
"""Get the transport for use by branch format in this BzrDir.
139
Note that bzr dirs that do not support format strings will raise
140
IncompatibleFormat if the branch format they are given has
141
a format string, and vice verca.
143
If branch_format is None, the transport is returned with no
144
checking. if it is not None, then the returned transport is
145
guaranteed to point to an existing directory ready for use.
147
raise NotImplementedError(self.get_branch_transport)
149
def get_workingtree_transport(self, branch_format):
150
"""Get the transport for use by workingtree format in this BzrDir.
152
Note that bzr dirs that do not support format strings will raise
153
IncompatibleFormat if the workingtree format they are given has
154
a format string, and vice verca.
156
If workingtree_format is None, the transport is returned with no
157
checking. if it is not None, then the returned transport is
158
guaranteed to point to an existing directory ready for use.
160
raise NotImplementedError(self.get_workingtree_transport)
162
def __init__(self, _transport, _format):
163
"""Initialize a Bzr control dir object.
165
Only really common logic should reside here, concrete classes should be
166
made with varying behaviours.
168
_format: the format that is creating this BzrDir instance.
169
_transport: the transport this dir is based at.
171
self._format = _format
172
self.transport = _transport.clone('.bzr')
175
def open_unsupported(base):
176
"""Open a branch which is not supported."""
177
return BzrDir.open(base, _unsupported=True)
180
def open(base, _unsupported=False):
181
"""Open an existing branch, rooted at 'base' (url)
183
_unsupported is a private parameter to the BzrDir class.
185
t = get_transport(base)
186
mutter("trying to open %r with transport %r", base, t)
187
format = BzrDirFormat.find_format(t)
188
if not _unsupported and not format.is_supported():
189
# see open_downlevel to open legacy branches.
190
raise errors.UnsupportedFormatError(
191
'sorry, format %s not supported' % format,
192
['use a different bzr version',
193
'or remove the .bzr directory'
194
' and "bzr init" again'])
195
return format.open(t, _found=True)
197
def open_branch(self, unsupported=False):
198
"""Open the branch object at this BzrDir if one is present.
200
If unsupported is True, then no longer supported branch formats can
203
TODO: static convenience version of this?
205
raise NotImplementedError(self.open_branch)
208
def open_containing(url):
209
"""Open an existing branch which contains url.
211
This probes for a branch at url, and searches upwards from there.
213
Basically we keep looking up until we find the control directory or
214
run into the root. If there isn't one, raises NotBranchError.
215
If there is one and it is either an unrecognised format or an unsupported
216
format, UnknownFormatError or UnsupportedFormatError are raised.
217
If there is one, it is returned, along with the unused portion of url.
219
t = get_transport(url)
220
# this gets the normalised url back. I.e. '.' -> the full path.
224
format = BzrDirFormat.find_format(t)
225
return format.open(t), t.relpath(url)
226
except errors.NotBranchError, e:
227
mutter('not a branch in: %r %s', t.base, e)
228
new_t = t.clone('..')
229
if new_t.base == t.base:
230
# reached the root, whatever that may be
231
raise errors.NotBranchError(path=url)
234
def open_repository(self):
235
"""Open the repository object at this BzrDir if one is present.
237
TODO: static convenience version of this?
238
TODO: NoRepositoryError that can be raised.
240
raise NotImplementedError(self.open_repository)
242
def open_workingtree(self, _unsupported=False):
243
"""Open the workingtree object at this BzrDir if one is present.
245
TODO: static convenience version of this?
247
raise NotImplementedError(self.open_workingtree)
250
class BzrDirPreSplitOut(BzrDir):
251
"""A common class for the all-in-one formats."""
253
def create_branch(self):
254
"""See BzrDir.create_branch."""
255
from bzrlib.branch import BzrBranchFormat4
256
return BzrBranchFormat4().initialize(self)
258
def get_branch_transport(self, branch_format):
259
"""See BzrDir.get_branch_transport()."""
260
if branch_format is None:
261
return self.transport
263
branch_format.get_format_string()
264
except NotImplementedError:
265
return self.transport
266
raise errors.IncompatibleFormat(branch_format, self._format)
268
def get_workingtree_transport(self, workingtree_format):
269
"""See BzrDir.get_workingtree_transport()."""
270
if workingtree_format is None:
271
return self.transport
273
workingtree_format.get_format_string()
274
except NotImplementedError:
275
return self.transport
276
raise errors.IncompatibleFormat(workingtree_format, self._format)
278
def open_branch(self, unsupported=False):
279
"""See BzrDir.open_branch."""
280
from bzrlib.branch import BzrBranchFormat4
281
format = BzrBranchFormat4()
282
self._check_supported(format, unsupported)
283
return format.open(self, _found=True)
286
class BzrDir4(BzrDirPreSplitOut):
287
"""A .bzr version 4 control object."""
289
def create_repository(self):
290
"""See BzrDir.create_repository."""
291
from bzrlib.repository import RepositoryFormat4
292
return RepositoryFormat4().initialize(self)
294
def open_repository(self):
295
"""See BzrDir.open_repository."""
296
from bzrlib.repository import RepositoryFormat4
297
return RepositoryFormat4().open(self, _found=True)
300
class BzrDir5(BzrDirPreSplitOut):
301
"""A .bzr version 5 control object."""
303
def create_repository(self):
304
"""See BzrDir.create_repository."""
305
from bzrlib.repository import RepositoryFormat5
306
return RepositoryFormat5().initialize(self)
308
def create_workingtree(self):
309
"""See BzrDir.create_workingtree."""
310
from bzrlib.workingtree import WorkingTreeFormat2
311
return WorkingTreeFormat2().initialize(self)
313
def open_repository(self):
314
"""See BzrDir.open_repository."""
315
from bzrlib.repository import RepositoryFormat5
316
return RepositoryFormat5().open(self, _found=True)
318
def open_workingtree(self, _unsupported=False):
319
"""See BzrDir.create_workingtree."""
320
from bzrlib.workingtree import WorkingTreeFormat2
321
return WorkingTreeFormat2().open(self, _found=True)
324
class BzrDir6(BzrDirPreSplitOut):
325
"""A .bzr version 6 control object."""
327
def create_repository(self):
328
"""See BzrDir.create_repository."""
329
from bzrlib.repository import RepositoryFormat6
330
return RepositoryFormat6().initialize(self)
332
def create_workingtree(self):
333
"""See BzrDir.create_workingtree."""
334
from bzrlib.workingtree import WorkingTreeFormat2
335
return WorkingTreeFormat2().initialize(self)
337
def open_repository(self):
338
"""See BzrDir.open_repository."""
339
from bzrlib.repository import RepositoryFormat6
340
return RepositoryFormat6().open(self, _found=True)
342
def open_workingtree(self, _unsupported=False):
343
"""See BzrDir.create_workingtree."""
344
from bzrlib.workingtree import WorkingTreeFormat2
345
return WorkingTreeFormat2().open(self, _found=True)
348
class BzrDirMeta1(BzrDir):
349
"""A .bzr meta version 1 control object.
351
This is the first control object where the
352
individual formats are really split out.
355
def create_branch(self):
356
"""See BzrDir.create_branch."""
357
from bzrlib.branch import BranchFormat
358
return BranchFormat.get_default_format().initialize(self)
360
def create_repository(self):
361
"""See BzrDir.create_repository."""
362
from bzrlib.repository import RepositoryFormat6
363
return RepositoryFormat6().initialize(self)
365
def create_workingtree(self):
366
"""See BzrDir.create_workingtree."""
367
from bzrlib.workingtree import WorkingTreeFormat
368
return WorkingTreeFormat.get_default_format().initialize(self)
370
def get_branch_transport(self, branch_format):
371
"""See BzrDir.get_branch_transport()."""
372
if branch_format is None:
373
return self.transport.clone('branch')
375
branch_format.get_format_string()
376
except NotImplementedError:
377
raise errors.IncompatibleFormat(branch_format, self._format)
379
self.transport.mkdir('branch')
380
except errors.FileExists:
382
return self.transport.clone('branch')
384
def get_workingtree_transport(self, workingtree_format):
385
"""See BzrDir.get_workingtree_transport()."""
386
if workingtree_format is None:
387
return self.transport.clone('checkout')
389
workingtree_format.get_format_string()
390
except NotImplementedError:
391
raise errors.IncompatibleFormat(workingtree_format, self._format)
393
self.transport.mkdir('checkout')
394
except errors.FileExists:
396
return self.transport.clone('checkout')
398
def open_branch(self, unsupported=False):
399
"""See BzrDir.open_branch."""
400
from bzrlib.branch import BranchFormat
401
format = BranchFormat.find_format(self)
402
self._check_supported(format, unsupported)
403
return format.open(self, _found=True)
405
def open_repository(self):
406
"""See BzrDir.open_repository."""
407
from bzrlib.repository import RepositoryFormat6
408
return RepositoryFormat6().open(self, _found=True)
410
def open_workingtree(self, unsupported=False):
411
"""See BzrDir.create_workingtree."""
412
from bzrlib.workingtree import WorkingTreeFormat
413
format = WorkingTreeFormat.find_format(self)
414
self._check_supported(format, unsupported)
415
return format.open(self, _found=True)
418
class BzrDirFormat(object):
419
"""An encapsulation of the initialization and open routines for a format.
421
Formats provide three things:
422
* An initialization routine,
426
Formats are placed in an dict by their format string for reference
427
during bzrdir opening. These should be subclasses of BzrDirFormat
430
Once a format is deprecated, just deprecate the initialize and open
431
methods on the format class. Do not deprecate the object, as the
432
object will be created every system load.
435
_default_format = None
436
"""The default format used for new .bzr dirs."""
439
"""The known formats."""
442
def find_format(klass, transport):
443
"""Return the format registered for URL."""
445
format_string = transport.get(".bzr/branch-format").read()
446
return klass._formats[format_string]
447
except errors.NoSuchFile:
448
raise errors.NotBranchError(path=transport.base)
450
raise errors.UnknownFormatError(format_string)
453
def get_default_format(klass):
454
"""Return the current default format."""
455
return klass._default_format
457
def get_format_string(self):
458
"""Return the ASCII format string that identifies this format."""
459
raise NotImplementedError(self.get_format_string)
461
def initialize(self, url):
462
"""Create a bzr control dir at this url and return an opened copy."""
463
# Since we don't have a .bzr directory, inherit the
464
# mode from the root directory
465
t = get_transport(url)
466
temp_control = LockableFiles(t, '')
467
temp_control._transport.mkdir('.bzr',
468
# FIXME: RBC 20060121 dont peek under
470
mode=temp_control._dir_mode)
471
file_mode = temp_control._file_mode
473
mutter('created control directory in ' + t.base)
474
control = t.clone('.bzr')
475
lock_file = 'branch-lock'
476
utf8_files = [('README',
477
"This is a Bazaar-NG control directory.\n"
478
"Do not change any files in this directory.\n"),
479
('branch-format', self.get_format_string()),
481
# NB: no need to escape relative paths that are url safe.
482
control.put(lock_file, StringIO(), mode=file_mode)
483
control_files = LockableFiles(control, lock_file)
484
control_files.lock_write()
486
for file, content in utf8_files:
487
control_files.put_utf8(file, content)
489
control_files.unlock()
490
return self.open(t, _found=True)
492
def is_supported(self):
493
"""Is this format supported?
495
Supported formats must be initializable and openable.
496
Unsupported formats may not support initialization or committing or
497
some other features depending on the reason for not being supported.
501
def open(self, transport, _found=False):
502
"""Return an instance of this format for the dir transport points at.
504
_found is a private parameter, do not use it.
507
assert isinstance(BzrDirFormat.find_format(transport),
509
return self._open(transport)
511
def _open(self, transport):
512
"""Template method helper for opening BzrDirectories.
514
This performs the actual open and any additional logic or parameter
517
raise NotImplementedError(self._open)
520
def register_format(klass, format):
521
klass._formats[format.get_format_string()] = format
524
def set_default_format(klass, format):
525
klass._default_format = format
528
def unregister_format(klass, format):
529
assert klass._formats[format.get_format_string()] is format
530
del klass._formats[format.get_format_string()]
533
class BzrDirFormat4(BzrDirFormat):
536
This format is a combined format for working tree, branch and repository.
538
- Format 1 working trees
540
- Format 4 repositories
542
This format is deprecated: it indexes texts using a text it which is
543
removed in format 5; write support for this format has been removed.
546
def get_format_string(self):
547
"""See BzrDirFormat.get_format_string()."""
548
return "Bazaar-NG branch, format 0.0.4\n"
550
def initialize(self, url):
551
"""Format 4 branches cannot be created."""
552
raise errors.UninitializableFormat(self)
554
def is_supported(self):
555
"""Format 4 is not supported.
557
It is not supported because the model changed from 4 to 5 and the
558
conversion logic is expensive - so doing it on the fly was not
563
def _open(self, transport):
564
"""See BzrDirFormat._open."""
565
return BzrDir4(transport, self)
568
class BzrDirFormat5(BzrDirFormat):
569
"""Bzr control format 5.
571
This format is a combined format for working tree, branch and repository.
573
- Format 2 working trees
575
- Format 6 repositories
578
def get_format_string(self):
579
"""See BzrDirFormat.get_format_string()."""
580
return "Bazaar-NG branch, format 5\n"
582
def _open(self, transport):
583
"""See BzrDirFormat._open."""
584
return BzrDir5(transport, self)
587
class BzrDirFormat6(BzrDirFormat):
588
"""Bzr control format 6.
590
This format is a combined format for working tree, branch and repository.
592
- Format 2 working trees
594
- Format 6 repositories
597
def get_format_string(self):
598
"""See BzrDirFormat.get_format_string()."""
599
return "Bazaar-NG branch, format 6\n"
601
def _open(self, transport):
602
"""See BzrDirFormat._open."""
603
return BzrDir6(transport, self)
606
class BzrDirMetaFormat1(BzrDirFormat):
607
"""Bzr meta control format 1
609
This is the first format with split out working tree, branch and repository
612
- Format 3 working trees
614
- Format 7 repositories
617
def get_format_string(self):
618
"""See BzrDirFormat.get_format_string()."""
619
return "Bazaar-NG meta directory, format 1\n"
621
def _open(self, transport):
622
"""See BzrDirFormat._open."""
623
return BzrDirMeta1(transport, self)
626
BzrDirFormat.register_format(BzrDirFormat4())
627
BzrDirFormat.register_format(BzrDirFormat5())
628
BzrDirFormat.register_format(BzrDirMetaFormat1())
629
__default_format = BzrDirFormat6()
630
BzrDirFormat.register_format(__default_format)
631
BzrDirFormat.set_default_format(__default_format)
634
class BzrDirTestProviderAdapter(object):
635
"""A tool to generate a suite testing multiple bzrdir formats at once.
637
This is done by copying the test once for each transport and injecting
638
the transport_server, transport_readonly_server, and bzrdir_format
639
classes into each copy. Each copy is also given a new id() to make it
643
def __init__(self, transport_server, transport_readonly_server, formats):
644
self._transport_server = transport_server
645
self._transport_readonly_server = transport_readonly_server
646
self._formats = formats
648
def adapt(self, test):
650
for format in self._formats:
651
new_test = deepcopy(test)
652
new_test.transport_server = self._transport_server
653
new_test.transport_readonly_server = self._transport_readonly_server
654
new_test.bzrdir_format = format
655
def make_new_test_id():
656
new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
657
return lambda: new_id
658
new_test.id = make_new_test_id()
659
result.addTest(new_test)
663
class ScratchDir(BzrDir6):
664
"""Special test class: a bzrdir that cleans up itself..
667
>>> base = d.transport.base
670
>>> b.transport.__del__()
675
def __init__(self, files=[], dirs=[], transport=None):
676
"""Make a test branch.
678
This creates a temporary directory and runs init-tree in it.
680
If any files are listed, they are created in the working copy.
682
if transport is None:
683
transport = bzrlib.transport.local.ScratchTransport()
684
# local import for scope restriction
685
BzrDirFormat6().initialize(transport.base)
686
super(ScratchDir, self).__init__(transport, BzrDirFormat6())
687
self.create_repository()
689
from bzrlib.workingtree import WorkingTree
690
WorkingTree.create(self.open_branch(), transport.base)
692
super(ScratchDir, self).__init__(transport, BzrDirFormat6())
694
# BzrBranch creates a clone to .bzr and then forgets about the
695
# original transport. A ScratchTransport() deletes itself and
696
# everything underneath it when it goes away, so we need to
697
# grab a local copy to prevent that from happening
698
self._transport = transport
701
self._transport.mkdir(d)
704
self._transport.put(f, 'content of %s' % f)
708
>>> orig = ScratchDir(files=["file1", "file2"])
709
>>> os.listdir(orig.base)
710
[u'.bzr', u'file1', u'file2']
711
>>> clone = orig.clone()
712
>>> if os.name != 'nt':
713
... os.path.samefile(orig.base, clone.base)
715
... orig.base == clone.base
718
>>> os.listdir(clone.base)
719
[u'.bzr', u'file1', u'file2']
721
from shutil import copytree
722
from bzrlib.osutils import mkdtemp
725
copytree(self.base, base, symlinks=True)
727
transport=bzrlib.transport.local.ScratchTransport(base))