1
1
# Copyright (C) 2005, 2006 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23
# TODO: remove unittest dependency; put that stuff inside the test suite
25
23
from copy import deepcopy
26
25
from cStringIO import StringIO
28
from stat import S_ISDIR
29
26
from unittest import TestSuite
32
29
import bzrlib.errors as errors
33
30
from bzrlib.lockable_files import LockableFiles, TransportLock
34
31
from bzrlib.lockdir import LockDir
32
from bzrlib.osutils import safe_unicode
35
33
from bzrlib.osutils import (
42
40
from bzrlib.store.revision.text import TextRevisionStore
43
41
from bzrlib.store.text import TextStore
44
42
from bzrlib.store.versioned import WeaveStore
43
from bzrlib.symbol_versioning import *
45
44
from bzrlib.trace import mutter
46
45
from bzrlib.transactions import WriteTransaction
47
from bzrlib.transport import get_transport
46
from bzrlib.transport import get_transport, urlunescape
48
47
from bzrlib.transport.local import LocalTransport
49
import bzrlib.urlutils as urlutils
50
48
from bzrlib.weave import Weave
51
49
from bzrlib.xml4 import serializer_v4
95
93
if not allow_unsupported and not format.is_supported():
96
94
# see open_downlevel to open legacy branches.
97
raise errors.UnsupportedFormatError(format=format)
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'])
99
101
def clone(self, url, revision_id=None, basis=None, force_new_repo=False):
100
102
"""Clone this bzrdir and its contents to url verbatim.
171
173
basis_repo = None
172
174
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
head, tail = urlutils.split(url)
178
if tail and tail != '.':
179
t = bzrlib.transport.get_transport(head)
177
segments = url.split('/')
178
if segments and segments[-1] not in ('', '.'):
179
parent = '/'.join(segments[:-1])
180
t = bzrlib.transport.get_transport(parent)
182
t.mkdir(segments[-1])
182
183
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
head, tail = urlutils.split(base)
200
if tail and tail != '.':
201
t = bzrlib.transport.get_transport(head)
199
segments = base.split('/')
200
if segments and segments[-1] not in ('', '.'):
201
parent = '/'.join(segments[:-1])
202
t = bzrlib.transport.get_transport(parent)
204
t.mkdir(segments[-1])
204
205
except errors.FileExists:
206
207
return BzrDirFormat.get_default_format().initialize(safe_unicode(base))
301
302
that child class desires.
303
304
bzrdir = BzrDir.create(base)
304
return bzrdir.create_repository(shared)
305
return bzrdir.create_repository()
307
308
def create_standalone_workingtree(base):
343
344
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:
351
350
raise errors.NoRepositoryPresent(self)
352
# does it have a repository ?
354
352
repository = found_bzrdir.open_repository()
355
353
except errors.NoRepositoryPresent:
356
354
next_transport = found_bzrdir.root_transport.clone('..')
357
if (found_bzrdir.root_transport.base == next_transport.base):
358
# top of the file system
362
356
if ((found_bzrdir.root_transport.base ==
363
357
self.root_transport.base) or repository.is_shared()):
364
358
return repository
372
366
Note that bzr dirs that do not support format strings will raise
373
367
IncompatibleFormat if the branch format they are given has
374
a format string, and vice versa.
368
a format string, and vice verca.
376
370
If branch_format is None, the transport is returned with no
377
371
checking. if it is not None, then the returned transport is
385
379
Note that bzr dirs that do not support format strings will raise
386
380
IncompatibleFormat if the repository format they are given has
387
a format string, and vice versa.
381
a format string, and vice verca.
389
383
If repository_format is None, the transport is returned with no
390
384
checking. if it is not None, then the returned transport is
398
392
Note that bzr dirs that do not support format strings will raise
399
393
IncompatibleFormat if the workingtree format they are given has
400
a format string, and vice versa.
394
a format string, and vice verca.
402
396
If workingtree_format is None, the transport is returned with no
403
397
checking. if it is not None, then the returned transport is
432
426
# this might be better on the BzrDirFormat class because it refers to
433
427
# all the possible bzrdir disk formats.
434
428
# This method is tested via the workingtree is_control_filename tests-
435
# it was extracted from WorkingTree.is_control_filename. If the methods
429
# it was extractd from WorkingTree.is_control_filename. If the methods
436
430
# contract is extended beyond the current trivial implementation please
437
431
# add new tests for it to the appropriate place.
438
432
return filename == '.bzr' or filename.startswith('.bzr/')
495
489
If there is one and it is either an unrecognised format or an unsupported
496
490
format, UnknownFormatError or UnsupportedFormatError are raised.
497
491
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.
502
493
# this gets the normalised url back. I.e. '.' -> the full path.
503
494
url = a_transport.base
506
497
format = BzrDirFormat.find_format(a_transport)
507
498
BzrDir._check_supported(format, False)
508
return format.open(a_transport), urlutils.unescape(a_transport.relpath(url))
499
return format.open(a_transport), a_transport.relpath(url)
509
500
except errors.NotBranchError, e:
510
## mutter('not a branch in: %r %s', a_transport.base, e)
501
mutter('not a branch in: %r %s', a_transport.base, e)
512
502
new_t = a_transport.clone('..')
513
503
if new_t.base == a_transport.base:
514
504
# reached the root, whatever that may be
605
595
result.create_repository()
606
596
elif source_repository is not None and result_repo is None:
607
597
# have source, and want to make a new target repo
608
# we don't clone the repo because that preserves attributes
598
# we dont clone the repo because that preserves attributes
609
599
# like is_shared(), and we have not yet implemented a
610
600
# repository sprout().
611
601
result_repo = result.create_repository()
620
610
source_branch.sprout(result, revision_id=revision_id)
622
612
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
625
613
if result_repo is None or result_repo.make_working_trees():
626
614
result.create_workingtree()
944
932
"""The known formats."""
946
_control_formats = []
947
"""The registered control formats - .bzr, ....
949
This is a list of BzrDirFormat objects.
952
934
_lock_file_name = 'branch-lock'
954
936
# _lock_class must be set in subclasses to the lock type, typ.
958
940
def find_format(klass, transport):
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."""
941
"""Return the format registered for URL."""
972
943
format_string = transport.get(".bzr/branch-format").read()
944
return klass._formats[format_string]
973
945
except errors.NoSuchFile:
974
946
raise errors.NotBranchError(path=transport.base)
977
return klass._formats[format_string]
979
raise errors.UnknownFormatError(format=format_string)
948
raise errors.UnknownFormatError(format_string)
982
951
def get_default_format(klass):
997
966
This returns a bzrlib.bzrdir.Converter object.
999
968
This should return the best upgrader to step this format towards the
1000
current default format. In the case of plugins we can/should provide
969
current default format. In the case of plugins we can/shouold provide
1001
970
some means for them to extend the range of returnable converters.
1003
:param format: Optional format to override the default format of the
972
:param format: Optional format to override the default foramt of the
1006
975
raise NotImplementedError(self.get_converter)
1016
985
def initialize_on_transport(self, transport):
1017
986
"""Initialize a new bzrdir in the base directory of a Transport."""
1018
# Since we don't have a .bzr directory, inherit the
987
# Since we don'transport have a .bzr directory, inherit the
1019
988
# mode from the root directory
1020
989
temp_control = LockableFiles(transport, '', TransportLock)
1021
990
temp_control._transport.mkdir('.bzr',
1022
# FIXME: RBC 20060121 don't peek under
991
# FIXME: RBC 20060121 dont peek under
1024
993
mode=temp_control._dir_mode)
1025
994
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())
1074
1024
def open(self, transport, _found=False):
1075
1025
"""Return an instance of this format for the dir transport points at.
1094
1044
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)
1108
1047
def set_default_format(klass, format):
1109
1048
klass._default_format = format
1116
1055
assert klass._formats[format.get_format_string()] is format
1117
1056
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)
1128
1059
class BzrDirFormat4(BzrDirFormat):
1129
1060
"""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))
1379
1376
class Converter(object):
1380
1377
"""Converts a disk format object from one format to another."""
1557
1554
def _store_new_weave(self, rev, inv, present_parents):
1558
1555
# the XML is now updated with text versions
1560
entries = inv.iter_entries()
1562
for path, ie in entries:
1559
if ie.kind == 'root_directory':
1563
1561
assert hasattr(ie, 'revision'), \
1564
1562
'no revision on {%s} in {%s}' % \
1565
1563
(file_id, rev.revision_id)
1578
1576
mutter('converting texts of revision {%s}',
1580
1578
parent_invs = map(self._load_updated_inventory, present_parents)
1581
entries = inv.iter_entries()
1583
for path, ie in entries:
1584
1581
self._convert_file_version(rev, ie, parent_invs)
1586
1583
def _convert_file_version(self, rev, ie, parent_invs):
1673
1672
store_transport = self.bzrdir.transport.clone(store_name)
1674
1673
store = TransportStore(store_transport, prefixed=True)
1675
1674
for urlfilename in store_transport.list_dir('.'):
1676
filename = urlutils.unescape(urlfilename)
1675
filename = urlunescape(urlfilename)
1677
1676
if (filename.endswith(".weave") or
1678
1677
filename.endswith(".gz") or
1679
1678
filename.endswith(".sig")):