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_repository_transport(self, repository_format):
150
"""Get the transport for use by repository format in this BzrDir.
152
Note that bzr dirs that do not support format strings will raise
153
IncompatibleFormat if the repository format they are given has
154
a format string, and vice verca.
156
If repository_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_repository_transport)
162
def get_workingtree_transport(self, branch_format):
163
"""Get the transport for use by workingtree format in this BzrDir.
165
Note that bzr dirs that do not support format strings will raise
166
IncompatibleFormat if the workingtree format they are given has
167
a format string, and vice verca.
169
If workingtree_format is None, the transport is returned with no
170
checking. if it is not None, then the returned transport is
171
guaranteed to point to an existing directory ready for use.
173
raise NotImplementedError(self.get_workingtree_transport)
175
def __init__(self, _transport, _format):
176
"""Initialize a Bzr control dir object.
178
Only really common logic should reside here, concrete classes should be
179
made with varying behaviours.
181
_format: the format that is creating this BzrDir instance.
182
_transport: the transport this dir is based at.
184
self._format = _format
185
self.transport = _transport.clone('.bzr')
188
def open_unsupported(base):
189
"""Open a branch which is not supported."""
190
return BzrDir.open(base, _unsupported=True)
193
def open(base, _unsupported=False):
194
"""Open an existing branch, rooted at 'base' (url)
196
_unsupported is a private parameter to the BzrDir class.
198
t = get_transport(base)
199
mutter("trying to open %r with transport %r", base, t)
200
format = BzrDirFormat.find_format(t)
201
if not _unsupported and not format.is_supported():
202
# see open_downlevel to open legacy branches.
203
raise errors.UnsupportedFormatError(
204
'sorry, format %s not supported' % format,
205
['use a different bzr version',
206
'or remove the .bzr directory'
207
' and "bzr init" again'])
208
return format.open(t, _found=True)
210
def open_branch(self, unsupported=False):
211
"""Open the branch object at this BzrDir if one is present.
213
If unsupported is True, then no longer supported branch formats can
216
TODO: static convenience version of this?
218
raise NotImplementedError(self.open_branch)
221
def open_containing(url):
222
"""Open an existing branch which contains url.
224
This probes for a branch at url, and searches upwards from there.
226
Basically we keep looking up until we find the control directory or
227
run into the root. If there isn't one, raises NotBranchError.
228
If there is one and it is either an unrecognised format or an unsupported
229
format, UnknownFormatError or UnsupportedFormatError are raised.
230
If there is one, it is returned, along with the unused portion of url.
232
t = get_transport(url)
233
# this gets the normalised url back. I.e. '.' -> the full path.
237
format = BzrDirFormat.find_format(t)
238
return format.open(t), t.relpath(url)
239
except errors.NotBranchError, e:
240
mutter('not a branch in: %r %s', t.base, e)
241
new_t = t.clone('..')
242
if new_t.base == t.base:
243
# reached the root, whatever that may be
244
raise errors.NotBranchError(path=url)
247
def open_repository(self, _unsupported=False):
248
"""Open the repository object at this BzrDir if one is present.
250
This will not follow the Branch object pointer - its strictly a direct
251
open facility. Most client code should use open_branch().repository to
254
_unsupported is a private parameter, not part of the api.
255
TODO: static convenience version of this?
257
raise NotImplementedError(self.open_repository)
259
def open_workingtree(self, _unsupported=False):
260
"""Open the workingtree object at this BzrDir if one is present.
262
TODO: static convenience version of this?
264
raise NotImplementedError(self.open_workingtree)
267
class BzrDirPreSplitOut(BzrDir):
268
"""A common class for the all-in-one formats."""
270
def create_branch(self):
271
"""See BzrDir.create_branch."""
272
from bzrlib.branch import BzrBranchFormat4
273
return BzrBranchFormat4().initialize(self)
275
def get_branch_transport(self, branch_format):
276
"""See BzrDir.get_branch_transport()."""
277
if branch_format is None:
278
return self.transport
280
branch_format.get_format_string()
281
except NotImplementedError:
282
return self.transport
283
raise errors.IncompatibleFormat(branch_format, self._format)
285
def get_repository_transport(self, repository_format):
286
"""See BzrDir.get_repository_transport()."""
287
if repository_format is None:
288
return self.transport
290
repository_format.get_format_string()
291
except NotImplementedError:
292
return self.transport
293
raise errors.IncompatibleFormat(repository_format, self._format)
295
def get_workingtree_transport(self, workingtree_format):
296
"""See BzrDir.get_workingtree_transport()."""
297
if workingtree_format is None:
298
return self.transport
300
workingtree_format.get_format_string()
301
except NotImplementedError:
302
return self.transport
303
raise errors.IncompatibleFormat(workingtree_format, self._format)
305
def open_branch(self, unsupported=False):
306
"""See BzrDir.open_branch."""
307
from bzrlib.branch import BzrBranchFormat4
308
format = BzrBranchFormat4()
309
self._check_supported(format, unsupported)
310
return format.open(self, _found=True)
313
class BzrDir4(BzrDirPreSplitOut):
314
"""A .bzr version 4 control object."""
316
def create_repository(self):
317
"""See BzrDir.create_repository."""
318
from bzrlib.repository import RepositoryFormat4
319
return RepositoryFormat4().initialize(self)
321
def open_repository(self):
322
"""See BzrDir.open_repository."""
323
from bzrlib.repository import RepositoryFormat4
324
return RepositoryFormat4().open(self, _found=True)
327
class BzrDir5(BzrDirPreSplitOut):
328
"""A .bzr version 5 control object."""
330
def create_repository(self):
331
"""See BzrDir.create_repository."""
332
from bzrlib.repository import RepositoryFormat5
333
return RepositoryFormat5().initialize(self)
335
def create_workingtree(self):
336
"""See BzrDir.create_workingtree."""
337
from bzrlib.workingtree import WorkingTreeFormat2
338
return WorkingTreeFormat2().initialize(self)
340
def open_repository(self):
341
"""See BzrDir.open_repository."""
342
from bzrlib.repository import RepositoryFormat5
343
return RepositoryFormat5().open(self, _found=True)
345
def open_workingtree(self, _unsupported=False):
346
"""See BzrDir.create_workingtree."""
347
from bzrlib.workingtree import WorkingTreeFormat2
348
return WorkingTreeFormat2().open(self, _found=True)
351
class BzrDir6(BzrDirPreSplitOut):
352
"""A .bzr version 6 control object."""
354
def create_repository(self):
355
"""See BzrDir.create_repository."""
356
from bzrlib.repository import RepositoryFormat6
357
return RepositoryFormat6().initialize(self)
359
def create_workingtree(self):
360
"""See BzrDir.create_workingtree."""
361
from bzrlib.workingtree import WorkingTreeFormat2
362
return WorkingTreeFormat2().initialize(self)
364
def open_repository(self):
365
"""See BzrDir.open_repository."""
366
from bzrlib.repository import RepositoryFormat6
367
return RepositoryFormat6().open(self, _found=True)
369
def open_workingtree(self, _unsupported=False):
370
"""See BzrDir.create_workingtree."""
371
from bzrlib.workingtree import WorkingTreeFormat2
372
return WorkingTreeFormat2().open(self, _found=True)
375
class BzrDirMeta1(BzrDir):
376
"""A .bzr meta version 1 control object.
378
This is the first control object where the
379
individual formats are really split out.
382
def create_branch(self):
383
"""See BzrDir.create_branch."""
384
from bzrlib.branch import BranchFormat
385
return BranchFormat.get_default_format().initialize(self)
387
def create_repository(self):
388
"""See BzrDir.create_repository."""
389
from bzrlib.repository import RepositoryFormat
390
return RepositoryFormat.get_default_format().initialize(self)
392
def create_workingtree(self):
393
"""See BzrDir.create_workingtree."""
394
from bzrlib.workingtree import WorkingTreeFormat
395
return WorkingTreeFormat.get_default_format().initialize(self)
397
def get_branch_transport(self, branch_format):
398
"""See BzrDir.get_branch_transport()."""
399
if branch_format is None:
400
return self.transport.clone('branch')
402
branch_format.get_format_string()
403
except NotImplementedError:
404
raise errors.IncompatibleFormat(branch_format, self._format)
406
self.transport.mkdir('branch')
407
except errors.FileExists:
409
return self.transport.clone('branch')
411
def get_repository_transport(self, repository_format):
412
"""See BzrDir.get_repository_transport()."""
413
if repository_format is None:
414
return self.transport.clone('repository')
416
repository_format.get_format_string()
417
except NotImplementedError:
418
raise errors.IncompatibleFormat(repository_format, self._format)
420
self.transport.mkdir('repository')
421
except errors.FileExists:
423
return self.transport.clone('repository')
425
def get_workingtree_transport(self, workingtree_format):
426
"""See BzrDir.get_workingtree_transport()."""
427
if workingtree_format is None:
428
return self.transport.clone('checkout')
430
workingtree_format.get_format_string()
431
except NotImplementedError:
432
raise errors.IncompatibleFormat(workingtree_format, self._format)
434
self.transport.mkdir('checkout')
435
except errors.FileExists:
437
return self.transport.clone('checkout')
439
def open_branch(self, unsupported=False):
440
"""See BzrDir.open_branch."""
441
from bzrlib.branch import BranchFormat
442
format = BranchFormat.find_format(self)
443
self._check_supported(format, unsupported)
444
return format.open(self, _found=True)
446
def open_repository(self, unsupported=False):
447
"""See BzrDir.open_repository."""
448
from bzrlib.repository import RepositoryFormat
449
format = RepositoryFormat.find_format(self)
450
self._check_supported(format, unsupported)
451
return format.open(self, _found=True)
453
def open_workingtree(self, unsupported=False):
454
"""See BzrDir.create_workingtree."""
455
from bzrlib.workingtree import WorkingTreeFormat
456
format = WorkingTreeFormat.find_format(self)
457
self._check_supported(format, unsupported)
458
return format.open(self, _found=True)
461
class BzrDirFormat(object):
462
"""An encapsulation of the initialization and open routines for a format.
464
Formats provide three things:
465
* An initialization routine,
469
Formats are placed in an dict by their format string for reference
470
during bzrdir opening. These should be subclasses of BzrDirFormat
473
Once a format is deprecated, just deprecate the initialize and open
474
methods on the format class. Do not deprecate the object, as the
475
object will be created every system load.
478
_default_format = None
479
"""The default format used for new .bzr dirs."""
482
"""The known formats."""
485
def find_format(klass, transport):
486
"""Return the format registered for URL."""
488
format_string = transport.get(".bzr/branch-format").read()
489
return klass._formats[format_string]
490
except errors.NoSuchFile:
491
raise errors.NotBranchError(path=transport.base)
493
raise errors.UnknownFormatError(format_string)
496
def get_default_format(klass):
497
"""Return the current default format."""
498
return klass._default_format
500
def get_format_string(self):
501
"""Return the ASCII format string that identifies this format."""
502
raise NotImplementedError(self.get_format_string)
504
def initialize(self, url):
505
"""Create a bzr control dir at this url and return an opened copy."""
506
# Since we don't have a .bzr directory, inherit the
507
# mode from the root directory
508
t = get_transport(url)
509
temp_control = LockableFiles(t, '')
510
temp_control._transport.mkdir('.bzr',
511
# FIXME: RBC 20060121 dont peek under
513
mode=temp_control._dir_mode)
514
file_mode = temp_control._file_mode
516
mutter('created control directory in ' + t.base)
517
control = t.clone('.bzr')
518
lock_file = 'branch-lock'
519
utf8_files = [('README',
520
"This is a Bazaar-NG control directory.\n"
521
"Do not change any files in this directory.\n"),
522
('branch-format', self.get_format_string()),
524
# NB: no need to escape relative paths that are url safe.
525
control.put(lock_file, StringIO(), mode=file_mode)
526
control_files = LockableFiles(control, lock_file)
527
control_files.lock_write()
529
for file, content in utf8_files:
530
control_files.put_utf8(file, content)
532
control_files.unlock()
533
return self.open(t, _found=True)
535
def is_supported(self):
536
"""Is this format supported?
538
Supported formats must be initializable and openable.
539
Unsupported formats may not support initialization or committing or
540
some other features depending on the reason for not being supported.
544
def open(self, transport, _found=False):
545
"""Return an instance of this format for the dir transport points at.
547
_found is a private parameter, do not use it.
550
assert isinstance(BzrDirFormat.find_format(transport),
552
return self._open(transport)
554
def _open(self, transport):
555
"""Template method helper for opening BzrDirectories.
557
This performs the actual open and any additional logic or parameter
560
raise NotImplementedError(self._open)
563
def register_format(klass, format):
564
klass._formats[format.get_format_string()] = format
567
def set_default_format(klass, format):
568
klass._default_format = format
571
def unregister_format(klass, format):
572
assert klass._formats[format.get_format_string()] is format
573
del klass._formats[format.get_format_string()]
576
class BzrDirFormat4(BzrDirFormat):
579
This format is a combined format for working tree, branch and repository.
581
- Format 1 working trees
583
- Format 4 repositories
585
This format is deprecated: it indexes texts using a text it which is
586
removed in format 5; write support for this format has been removed.
589
def get_format_string(self):
590
"""See BzrDirFormat.get_format_string()."""
591
return "Bazaar-NG branch, format 0.0.4\n"
593
def initialize(self, url):
594
"""Format 4 branches cannot be created."""
595
raise errors.UninitializableFormat(self)
597
def is_supported(self):
598
"""Format 4 is not supported.
600
It is not supported because the model changed from 4 to 5 and the
601
conversion logic is expensive - so doing it on the fly was not
606
def _open(self, transport):
607
"""See BzrDirFormat._open."""
608
return BzrDir4(transport, self)
611
class BzrDirFormat5(BzrDirFormat):
612
"""Bzr control format 5.
614
This format is a combined format for working tree, branch and repository.
616
- Format 2 working trees
618
- Format 6 repositories
621
def get_format_string(self):
622
"""See BzrDirFormat.get_format_string()."""
623
return "Bazaar-NG branch, format 5\n"
625
def _open(self, transport):
626
"""See BzrDirFormat._open."""
627
return BzrDir5(transport, self)
630
class BzrDirFormat6(BzrDirFormat):
631
"""Bzr control format 6.
633
This format is a combined format for working tree, branch and repository.
635
- Format 2 working trees
637
- Format 6 repositories
640
def get_format_string(self):
641
"""See BzrDirFormat.get_format_string()."""
642
return "Bazaar-NG branch, format 6\n"
644
def _open(self, transport):
645
"""See BzrDirFormat._open."""
646
return BzrDir6(transport, self)
649
class BzrDirMetaFormat1(BzrDirFormat):
650
"""Bzr meta control format 1
652
This is the first format with split out working tree, branch and repository
655
- Format 3 working trees
657
- Format 7 repositories
660
def get_format_string(self):
661
"""See BzrDirFormat.get_format_string()."""
662
return "Bazaar-NG meta directory, format 1\n"
664
def _open(self, transport):
665
"""See BzrDirFormat._open."""
666
return BzrDirMeta1(transport, self)
669
BzrDirFormat.register_format(BzrDirFormat4())
670
BzrDirFormat.register_format(BzrDirFormat5())
671
BzrDirFormat.register_format(BzrDirMetaFormat1())
672
__default_format = BzrDirFormat6()
673
BzrDirFormat.register_format(__default_format)
674
BzrDirFormat.set_default_format(__default_format)
677
class BzrDirTestProviderAdapter(object):
678
"""A tool to generate a suite testing multiple bzrdir formats at once.
680
This is done by copying the test once for each transport and injecting
681
the transport_server, transport_readonly_server, and bzrdir_format
682
classes into each copy. Each copy is also given a new id() to make it
686
def __init__(self, transport_server, transport_readonly_server, formats):
687
self._transport_server = transport_server
688
self._transport_readonly_server = transport_readonly_server
689
self._formats = formats
691
def adapt(self, test):
693
for format in self._formats:
694
new_test = deepcopy(test)
695
new_test.transport_server = self._transport_server
696
new_test.transport_readonly_server = self._transport_readonly_server
697
new_test.bzrdir_format = format
698
def make_new_test_id():
699
new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
700
return lambda: new_id
701
new_test.id = make_new_test_id()
702
result.addTest(new_test)
706
class ScratchDir(BzrDir6):
707
"""Special test class: a bzrdir that cleans up itself..
710
>>> base = d.transport.base
713
>>> b.transport.__del__()
718
def __init__(self, files=[], dirs=[], transport=None):
719
"""Make a test branch.
721
This creates a temporary directory and runs init-tree in it.
723
If any files are listed, they are created in the working copy.
725
if transport is None:
726
transport = bzrlib.transport.local.ScratchTransport()
727
# local import for scope restriction
728
BzrDirFormat6().initialize(transport.base)
729
super(ScratchDir, self).__init__(transport, BzrDirFormat6())
730
self.create_repository()
732
from bzrlib.workingtree import WorkingTree
733
WorkingTree.create(self.open_branch(), transport.base)
735
super(ScratchDir, self).__init__(transport, BzrDirFormat6())
737
# BzrBranch creates a clone to .bzr and then forgets about the
738
# original transport. A ScratchTransport() deletes itself and
739
# everything underneath it when it goes away, so we need to
740
# grab a local copy to prevent that from happening
741
self._transport = transport
744
self._transport.mkdir(d)
747
self._transport.put(f, 'content of %s' % f)
751
>>> orig = ScratchDir(files=["file1", "file2"])
752
>>> os.listdir(orig.base)
753
[u'.bzr', u'file1', u'file2']
754
>>> clone = orig.clone()
755
>>> if os.name != 'nt':
756
... os.path.samefile(orig.base, clone.base)
758
... orig.base == clone.base
761
>>> os.listdir(clone.base)
762
[u'.bzr', u'file1', u'file2']
764
from shutil import copytree
765
from bzrlib.osutils import mkdtemp
768
copytree(self.base, base, symlinks=True)
770
transport=bzrlib.transport.local.ScratchTransport(base))