23
# TODO: remove unittest dependency; put that stuff inside the test suite
23
25
from copy import deepcopy
26
from cStringIO import StringIO
25
from cStringIO import StringIO
28
from stat import S_ISDIR
26
29
from unittest import TestSuite
29
32
import bzrlib.errors as errors
30
33
from bzrlib.lockable_files import LockableFiles, TransportLock
31
34
from bzrlib.lockdir import LockDir
32
from bzrlib.osutils import safe_unicode
33
35
from bzrlib.osutils import (
40
42
from bzrlib.store.revision.text import TextRevisionStore
41
43
from bzrlib.store.text import TextStore
42
44
from bzrlib.store.versioned import WeaveStore
43
from bzrlib.symbol_versioning import *
44
45
from bzrlib.trace import mutter
45
46
from bzrlib.transactions import WriteTransaction
46
from bzrlib.transport import get_transport, urlunescape
47
from bzrlib.transport import get_transport
47
48
from bzrlib.transport.local import LocalTransport
49
import bzrlib.urlutils as urlutils
48
50
from bzrlib.weave import Weave
49
51
from bzrlib.xml4 import serializer_v4
50
from bzrlib.xml5 import serializer_v5
53
55
class BzrDir(object):
93
95
if not allow_unsupported and not format.is_supported():
94
96
# see open_downlevel to open legacy branches.
95
raise errors.UnsupportedFormatError(
96
'sorry, format %s not supported' % format,
97
['use a different bzr version',
98
'or remove the .bzr directory'
99
' and "bzr init" again'])
97
raise errors.UnsupportedFormatError(format=format)
101
99
def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
102
100
"""Clone this bzrdir and its contents to url verbatim.
173
171
basis_repo = None
174
172
return basis_repo, basis_branch, basis_tree
174
# TODO: This should be given a Transport, and should chdir up; otherwise
175
# this will open a new connection.
176
176
def _make_tail(self, url):
177
segments = url.split('/')
178
if segments and segments[-1] not in ('', '.'):
179
parent = '/'.join(segments[:-1])
180
t = bzrlib.transport.get_transport(parent)
177
head, tail = urlutils.split(url)
178
if tail and tail != '.':
179
t = bzrlib.transport.get_transport(head)
182
t.mkdir(segments[-1])
183
182
except errors.FileExists:
185
# TODO: Should take a Transport
187
187
def create(cls, base):
188
188
"""Create a new BzrDir at the url 'base'.
196
196
if cls is not BzrDir:
197
197
raise AssertionError("BzrDir.create always creates the default format, "
198
198
"not one of %r" % cls)
199
segments = base.split('/')
200
if segments and segments[-1] not in ('', '.'):
201
parent = '/'.join(segments[:-1])
202
t = bzrlib.transport.get_transport(parent)
199
head, tail = urlutils.split(base)
200
if tail and tail != '.':
201
t = bzrlib.transport.get_transport(head)
204
t.mkdir(segments[-1])
205
204
except errors.FileExists:
207
206
return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
344
343
next_transport = self.root_transport.clone('..')
345
# find the next containing bzrdir
347
347
found_bzrdir = BzrDir.open_containing_from_transport(
348
348
next_transport)[0]
349
349
except errors.NotBranchError:
350
351
raise errors.NoRepositoryPresent(self)
352
# does it have a repository ?
352
354
repository = found_bzrdir.open_repository()
353
355
except errors.NoRepositoryPresent:
354
356
next_transport = found_bzrdir.root_transport.clone('..')
357
if (found_bzrdir.root_transport.base == next_transport.base):
358
# top of the file system
356
362
if ((found_bzrdir.root_transport.base ==
357
363
self.root_transport.base) or repository.is_shared()):
358
364
return repository
366
372
Note that bzr dirs that do not support format strings will raise
367
373
IncompatibleFormat if the branch format they are given has
368
a format string, and vice verca.
374
a format string, and vice versa.
370
376
If branch_format is None, the transport is returned with no
371
377
checking. if it is not None, then the returned transport is
379
385
Note that bzr dirs that do not support format strings will raise
380
386
IncompatibleFormat if the repository format they are given has
381
a format string, and vice verca.
387
a format string, and vice versa.
383
389
If repository_format is None, the transport is returned with no
384
390
checking. if it is not None, then the returned transport is
392
398
Note that bzr dirs that do not support format strings will raise
393
399
IncompatibleFormat if the workingtree format they are given has
394
a format string, and vice verca.
400
a format string, and vice versa.
396
402
If workingtree_format is None, the transport is returned with no
397
403
checking. if it is not None, then the returned transport is
426
432
# this might be better on the BzrDirFormat class because it refers to
427
433
# all the possible bzrdir disk formats.
428
434
# This method is tested via the workingtree is_control_filename tests-
429
# it was extractd from WorkingTree.is_control_filename. If the methods
435
# it was extracted from WorkingTree.is_control_filename. If the methods
430
436
# contract is extended beyond the current trivial implementation please
431
437
# add new tests for it to the appropriate place.
432
438
return filename == '.bzr' or filename.startswith('.bzr/')
489
495
If there is one and it is either an unrecognised format or an unsupported
490
496
format, UnknownFormatError or UnsupportedFormatError are raised.
491
497
If there is one, it is returned, along with the unused portion of url.
499
:return: The BzrDir that contains the path, and a Unicode path
500
for the rest of the URL.
493
502
# this gets the normalised url back. I.e. '.' -> the full path.
494
503
url = a_transport.base
497
506
format = BzrDirFormat.find_format(a_transport)
498
507
BzrDir._check_supported(format, False)
499
return format.open(a_transport), a_transport.relpath(url)
508
return format.open(a_transport), urlutils.unescape(a_transport.relpath(url))
500
509
except errors.NotBranchError, e:
501
mutter('not a branch in: %r %s', a_transport.base, e)
510
## mutter('not a branch in: %r %s', a_transport.base, e)
502
512
new_t = a_transport.clone('..')
503
513
if new_t.base == a_transport.base:
504
514
# reached the root, whatever that may be
595
605
result.create_repository()
596
606
elif source_repository is not None and result_repo is None:
597
607
# have source, and want to make a new target repo
598
# we dont clone the repo because that preserves attributes
608
# we don't clone the repo because that preserves attributes
599
609
# like is_shared(), and we have not yet implemented a
600
610
# repository sprout().
601
611
result_repo = result.create_repository()
610
620
source_branch.sprout(result, revision_id=revision_id)
612
622
result.create_branch()
623
# TODO: jam 20060426 we probably need a test in here in the
624
# case that the newly sprouted branch is a remote one
613
625
if result_repo is None or result_repo.make_working_trees():
614
626
result.create_workingtree()
932
944
"""The known formats."""
946
_control_formats = []
947
"""The registered control formats - .bzr, ....
949
This is a list of BzrDirFormat objects.
934
952
_lock_file_name = 'branch-lock'
936
954
# _lock_class must be set in subclasses to the lock type, typ.
940
958
def find_format(klass, transport):
941
"""Return the format registered for URL."""
959
"""Return the format present at transport."""
960
for format in klass._control_formats:
962
return format.probe_transport(transport)
963
except errors.NotBranchError:
964
# this format does not find a control dir here.
966
raise errors.NotBranchError(path=transport.base)
969
def probe_transport(klass, transport):
970
"""Return the .bzrdir style transport present at URL."""
943
972
format_string = transport.get(".bzr/branch-format").read()
973
except errors.NoSuchFile:
974
raise errors.NotBranchError(path=transport.base)
944
977
return klass._formats[format_string]
945
except errors.NoSuchFile:
946
raise errors.NotBranchError(path=transport.base)
948
raise errors.UnknownFormatError(format_string)
979
raise errors.UnknownFormatError(format=format_string)
951
982
def get_default_format(klass):
966
997
This returns a bzrlib.bzrdir.Converter object.
968
999
This should return the best upgrader to step this format towards the
969
current default format. In the case of plugins we can/shouold provide
1000
current default format. In the case of plugins we can/should provide
970
1001
some means for them to extend the range of returnable converters.
972
:param format: Optional format to override the default foramt of the
1003
:param format: Optional format to override the default format of the
975
1006
raise NotImplementedError(self.get_converter)
985
1016
def initialize_on_transport(self, transport):
986
1017
"""Initialize a new bzrdir in the base directory of a Transport."""
987
# Since we don'transport have a .bzr directory, inherit the
1018
# Since we don't have a .bzr directory, inherit the
988
1019
# mode from the root directory
989
1020
temp_control = LockableFiles(transport, '', TransportLock)
990
1021
temp_control._transport.mkdir('.bzr',
991
# FIXME: RBC 20060121 dont peek under
1022
# FIXME: RBC 20060121 don't peek under
993
1024
mode=temp_control._dir_mode)
994
1025
file_mode = temp_control._file_mode
1056
def known_formats(klass):
1057
"""Return all the known formats.
1059
Concrete formats should override _known_formats.
1061
# There is double indirection here to make sure that control
1062
# formats used by more than one dir format will only be probed
1063
# once. This can otherwise be quite expensive for remote connections.
1065
for format in klass._control_formats:
1066
result.update(format._known_formats())
1070
def _known_formats(klass):
1071
"""Return the known format instances for this control format."""
1072
return set(klass._formats.values())
1024
1074
def open(self, transport, _found=False):
1025
1075
"""Return an instance of this format for the dir transport points at.
1044
1094
klass._formats[format.get_format_string()] = format
1097
def register_control_format(klass, format):
1098
"""Register a format that does not use '.bzrdir' for its control dir.
1100
TODO: This should be pulled up into a 'ControlDirFormat' base class
1101
which BzrDirFormat can inherit from, and renamed to register_format
1102
there. It has been done without that for now for simplicity of
1105
klass._control_formats.append(format)
1047
1108
def set_default_format(klass, format):
1048
1109
klass._default_format = format
1055
1116
assert klass._formats[format.get_format_string()] is format
1056
1117
del klass._formats[format.get_format_string()]
1120
def unregister_control_format(klass, format):
1121
klass._control_formats.remove(format)
1124
# register BzrDirFormat as a control format
1125
BzrDirFormat.register_control_format(BzrDirFormat)
1059
1128
class BzrDirFormat4(BzrDirFormat):
1060
1129
"""Bzr dir format 4.
1310
class ScratchDir(BzrDir6):
1311
"""Special test class: a bzrdir that cleans up itself..
1313
>>> d = ScratchDir()
1314
>>> base = d.transport.base
1317
>>> b.transport.__del__()
1322
def __init__(self, files=[], dirs=[], transport=None):
1323
"""Make a test branch.
1325
This creates a temporary directory and runs init-tree in it.
1327
If any files are listed, they are created in the working copy.
1329
if transport is None:
1330
transport = bzrlib.transport.local.ScratchTransport()
1331
# local import for scope restriction
1332
BzrDirFormat6().initialize(transport.base)
1333
super(ScratchDir, self).__init__(transport, BzrDirFormat6())
1334
self.create_repository()
1335
self.create_branch()
1336
self.create_workingtree()
1338
super(ScratchDir, self).__init__(transport, BzrDirFormat6())
1340
# BzrBranch creates a clone to .bzr and then forgets about the
1341
# original transport. A ScratchTransport() deletes itself and
1342
# everything underneath it when it goes away, so we need to
1343
# grab a local copy to prevent that from happening
1344
self._transport = transport
1347
self._transport.mkdir(d)
1350
self._transport.put(f, 'content of %s' % f)
1354
>>> orig = ScratchDir(files=["file1", "file2"])
1355
>>> os.listdir(orig.base)
1356
[u'.bzr', u'file1', u'file2']
1357
>>> clone = orig.clone()
1358
>>> if os.name != 'nt':
1359
... os.path.samefile(orig.base, clone.base)
1361
... orig.base == clone.base
1364
>>> os.listdir(clone.base)
1365
[u'.bzr', u'file1', u'file2']
1367
from shutil import copytree
1368
from bzrlib.osutils import mkdtemp
1371
copytree(self.base, base, symlinks=True)
1373
transport=bzrlib.transport.local.ScratchTransport(base))
1376
1379
class Converter(object):
1377
1380
"""Converts a disk format object from one format to another."""
1465
1468
def _convert_working_inv(self):
1466
1469
inv = serializer_v4.read_inventory(self.branch.control_files.get('inventory'))
1467
new_inv_xml = serializer_v5.write_inventory_to_string(inv)
1470
new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1468
1471
# FIXME inventory is a working tree change.
1469
1472
self.branch.control_files.put('inventory', new_inv_xml)
1538
1541
def _load_updated_inventory(self, rev_id):
1539
1542
assert rev_id in self.converted_revs
1540
1543
inv_xml = self.inv_weave.get_text(rev_id)
1541
inv = serializer_v5.read_inventory_from_string(inv_xml)
1544
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(inv_xml)
1544
1547
def _convert_one_rev(self, rev_id):
1561
1564
assert hasattr(ie, 'revision'), \
1562
1565
'no revision on {%s} in {%s}' % \
1563
1566
(file_id, rev.revision_id)
1564
new_inv_xml = serializer_v5.write_inventory_to_string(inv)
1567
new_inv_xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1565
1568
new_inv_sha1 = sha_string(new_inv_xml)
1566
1569
self.inv_weave.add_lines(rev.revision_id,
1567
1570
present_parents,
1672
1675
store_transport = self.bzrdir.transport.clone(store_name)
1673
1676
store = TransportStore(store_transport, prefixed=True)
1674
1677
for urlfilename in store_transport.list_dir('.'):
1675
filename = urlunescape(urlfilename)
1678
filename = urlutils.unescape(urlfilename)
1676
1679
if (filename.endswith(".weave") or
1677
1680
filename.endswith(".gz") or
1678
1681
filename.endswith(".sig")):