210
227
(branch.base is not cross checked, because for remote branches that
211
228
would be meaningless).
230
self._format = _format
231
self.bzrdir = _bzrdir
233
# not created via open etc.
234
warn("WorkingTree() is deprecated as of bzr version 0.8. "
235
"Please use bzrdir.open_workingtree or WorkingTree.open().",
238
wt = WorkingTree.open(basedir)
239
self.branch = wt.branch
240
self.basedir = wt.basedir
241
self._control_files = wt._control_files
242
self._hashcache = wt._hashcache
243
self._set_inventory(wt._inventory)
244
self._format = wt._format
245
self.bzrdir = wt.bzrdir
213
246
from bzrlib.hashcache import HashCache
214
247
from bzrlib.trace import note, mutter
215
248
assert isinstance(basedir, basestring), \
216
249
"base directory %r is not a string" % basedir
217
250
basedir = safe_unicode(basedir)
218
mutter("openeing working tree %r", basedir)
220
branch = Branch.open(basedir)
221
assert isinstance(branch, Branch), \
222
"branch %r is not a Branch" % branch
251
mutter("opening working tree %r", basedir)
252
if deprecated_passed(branch):
254
warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
255
" Please use bzrdir.open_workingtree() or WorkingTree.open().",
261
self.branch = self.bzrdir.open_branch()
262
assert isinstance(self.branch, Branch), \
263
"branch %r is not a Branch" % self.branch
224
264
self.basedir = realpath(basedir)
225
265
# if branch is at our basedir and is a format 6 or less
226
if (isinstance(self.branch._branch_format,
227
(BzrBranchFormat4, BzrBranchFormat5, BzrBranchFormat6))
228
# might be able to share control object
229
and self.branch.base.split('/')[-2] == self.basedir.split('/')[-1]):
266
if isinstance(self._format, WorkingTreeFormat2):
267
# share control object
230
268
self._control_files = self.branch.control_files
231
269
elif _control_files is not None:
232
270
assert False, "not done yet"
233
271
# self._control_files = _control_files
273
# only ready for format 3
274
assert isinstance(self._format, WorkingTreeFormat3)
235
275
self._control_files = LockableFiles(
236
get_transport(self.basedir).clone(bzrlib.BZRDIR), 'branch-lock')
276
self.bzrdir.get_workingtree_transport(None),
277
'lock', TransportLock)
238
279
# update the whole cache up front and write to disk if anything changed;
239
280
# in the future we might want to do this more selectively
381
439
## XXX: badly named; this is not in the store at all
382
440
return self.abspath(self.id2path(file_id))
443
def clone(self, to_bzrdir, revision_id=None, basis=None):
444
"""Duplicate this working tree into to_bzr, including all state.
446
Specifically modified files are kept as modified, but
447
ignored and unknown files are discarded.
449
If you want to make a new line of development, see bzrdir.sprout()
452
If not None, the cloned tree will have its last revision set to
453
revision, and and difference between the source trees last revision
454
and this one merged in.
457
If not None, a closer copy of a tree which may have some files in
458
common, and which file content should be preferentially copied from.
460
# assumes the target bzr dir format is compatible.
461
result = self._format.initialize(to_bzrdir)
462
self.copy_content_into(result, revision_id)
466
def copy_content_into(self, tree, revision_id=None):
467
"""Copy the current content and user files of this tree into tree."""
468
if revision_id is None:
469
transform_tree(tree, self)
471
# TODO now merge from tree.last_revision to revision
472
transform_tree(tree, self)
473
tree.set_last_revision(revision_id)
384
475
@needs_write_lock
385
def commit(self, *args, **kwargs):
476
def commit(self, message=None, revprops=None, *args, **kwargs):
477
# avoid circular imports
386
478
from bzrlib.commit import Commit
481
if not 'branch-nick' in revprops:
482
revprops['branch-nick'] = self.branch.nick
387
483
# args for wt.commit start at message from the Commit.commit method,
388
484
# but with branch a kwarg now, passing in args as is results in the
389
485
#message being used for the branch
390
args = (DEPRECATED_PARAMETER, ) + args
391
Commit().commit(working_tree=self, *args, **kwargs)
486
args = (DEPRECATED_PARAMETER, message, ) + args
487
Commit().commit(working_tree=self, revprops=revprops, *args, **kwargs)
392
488
self._set_inventory(self.read_working_inventory())
394
490
def id2abspath(self, file_id):
882
1017
def kind(self, file_id):
883
1018
return file_kind(self.id2abspath(file_id))
1021
def last_revision(self):
1022
"""Return the last revision id of this working tree.
1024
In early branch formats this was == the branch last_revision,
1025
but that cannot be relied upon - for working tree operations,
1026
always use tree.last_revision().
1028
return self.branch.last_revision()
885
1030
def lock_read(self):
886
1031
"""See Branch.lock_read, and WorkingTree.unlock."""
887
return self.branch.lock_read()
1032
self.branch.lock_read()
1034
return self._control_files.lock_read()
1036
self.branch.unlock()
889
1039
def lock_write(self):
890
1040
"""See Branch.lock_write, and WorkingTree.unlock."""
891
return self.branch.lock_write()
1041
self.branch.lock_write()
1043
return self._control_files.lock_write()
1045
self.branch.unlock()
893
1048
def _basis_inventory_name(self, revision_id):
894
1049
return 'basis-inventory.%s' % revision_id
896
1052
def set_last_revision(self, new_revision, old_revision=None):
897
if old_revision is not None:
899
path = self._basis_inventory_name(old_revision)
900
path = self.branch.control_files._escape(path)
901
self.branch.control_files._transport.delete(path)
1053
"""Change the last revision in the working tree."""
1054
self._remove_old_basis(old_revision)
1055
if self._change_last_revision(new_revision):
1056
self._cache_basis_inventory(new_revision)
1058
def _change_last_revision(self, new_revision):
1059
"""Template method part of set_last_revision to perform the change."""
1060
if new_revision is None:
1061
self.branch.set_revision_history([])
1063
# current format is locked in with the branch
1064
revision_history = self.branch.revision_history()
1066
position = revision_history.index(new_revision)
1068
raise errors.NoSuchRevision(self.branch, new_revision)
1069
self.branch.set_revision_history(revision_history[:position + 1])
1072
def _cache_basis_inventory(self, new_revision):
1073
"""Cache new_revision as the basis inventory."""
905
1075
xml = self.branch.repository.get_inventory_xml(new_revision)
906
1076
path = self._basis_inventory_name(new_revision)
907
self.branch.control_files.put_utf8(path, xml)
1077
self._control_files.put_utf8(path, xml)
908
1078
except WeaveRevisionNotPresent:
1081
def _remove_old_basis(self, old_revision):
1082
"""Remove the old basis inventory 'old_revision'."""
1083
if old_revision is not None:
1085
path = self._basis_inventory_name(old_revision)
1086
path = self._control_files._escape(path)
1087
self._control_files._transport.delete(path)
911
1091
def read_basis_inventory(self, revision_id):
912
1092
"""Read the cached basis inventory."""
913
1093
path = self._basis_inventory_name(revision_id)
914
return self.branch.control_files.get_utf8(path).read()
1094
return self._control_files.get_utf8(path).read()
916
1096
@needs_read_lock
917
1097
def read_working_inventory(self):
918
1098
"""Read the working inventory."""
919
1099
# ElementTree does its own conversion from UTF-8, so open in
921
return bzrlib.xml5.serializer_v5.read_inventory(
1101
result = bzrlib.xml5.serializer_v5.read_inventory(
922
1102
self._control_files.get('inventory'))
1103
self._set_inventory(result)
924
1106
@needs_write_lock
925
1107
def remove(self, files, verbose=False):
1026
1205
# of a nasty hack; probably it's better to have a transaction object,
1027
1206
# which can do some finalization when it's either successfully or
1028
1207
# unsuccessfully completed. (Denys's original patch did that.)
1029
if self._hashcache.needs_write and self.branch.control_files._lock_count==1:
1208
# RBC 20060206 hookinhg into transaction will couple lock and transaction
1209
# wrongly. Hookinh into unllock on the control files object is fine though.
1211
# TODO: split this per format so there is no ugly if block
1212
if self._hashcache.needs_write and (
1213
# dedicated lock files
1214
self._control_files._lock_count==1 or
1216
(self._control_files is self.branch.control_files and
1217
self._control_files._lock_count==3)):
1030
1218
self._hashcache.write()
1031
return self.branch.unlock()
1219
# reverse order of locking.
1220
result = self._control_files.unlock()
1222
self.branch.unlock()
1228
"""Update a working tree along its branch.
1230
This will update the branch if its bound too, which means we have multiple trees involved:
1231
The new basis tree of the master.
1232
The old basis tree of the branch.
1233
The old basis tree of the working tree.
1234
The current working tree state.
1235
pathologically all three may be different, and non ancestors of each other.
1236
Conceptually we want to:
1237
Preserve the wt.basis->wt.state changes
1238
Transform the wt.basis to the new master basis.
1239
Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
1240
Restore the wt.basis->wt.state changes.
1242
There isn't a single operation at the moment to do that, so we:
1243
Merge current state -> basis tree of the master w.r.t. the old tree basis.
1244
Do a 'normal' merge of the old branch basis if it is relevant.
1246
old_tip = self.branch.update()
1247
if old_tip is not None:
1248
self.add_pending_merge(old_tip)
1249
self.branch.lock_read()
1252
if self.last_revision() != self.branch.last_revision():
1253
# merge tree state up to new branch tip.
1254
basis = self.basis_tree()
1255
to_tree = self.branch.basis_tree()
1256
result += merge_inner(self.branch,
1260
self.set_last_revision(self.branch.last_revision())
1261
if old_tip and old_tip != self.last_revision():
1262
# our last revision was not the prior branch last reivison
1263
# and we have converted that last revision to a pending merge.
1264
# base is somewhere between the branch tip now
1265
# and the now pending merge
1266
from bzrlib.revision import common_ancestor
1268
base_rev_id = common_ancestor(self.branch.last_revision(),
1270
self.branch.repository)
1271
except errors.NoCommonAncestor:
1273
base_tree = self.branch.repository.revision_tree(base_rev_id)
1274
other_tree = self.branch.repository.revision_tree(old_tip)
1275
result += merge_inner(self.branch,
1281
self.branch.unlock()
1033
1283
@needs_write_lock
1034
1284
def _write_inventory(self, inv):
1039
1289
self._control_files.put('inventory', sio)
1040
1290
self._set_inventory(inv)
1041
1291
mutter('wrote working inventory')
1294
class WorkingTree3(WorkingTree):
1295
"""This is the Format 3 working tree.
1297
This differs from the base WorkingTree by:
1298
- having its own file lock
1299
- having its own last-revision property.
1303
def last_revision(self):
1304
"""See WorkingTree.last_revision."""
1306
return self._control_files.get_utf8('last-revision').read()
1310
def _change_last_revision(self, revision_id):
1311
"""See WorkingTree._change_last_revision."""
1312
if revision_id is None or revision_id == NULL_REVISION:
1314
self._control_files._transport.delete('last-revision')
1315
except errors.NoSuchFile:
1320
self.branch.revision_history().index(revision_id)
1322
raise errors.NoSuchRevision(self.branch, revision_id)
1323
self._control_files.put_utf8('last-revision', revision_id)
1044
1327
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1045
1328
def get_conflicted_stem(path):
1046
1329
for suffix in CONFLICT_SUFFIXES:
1047
1330
if path.endswith(suffix):
1048
1331
return path[:-len(suffix)]
1333
@deprecated_function(zero_eight)
1334
def is_control_file(filename):
1335
"""See WorkingTree.is_control_filename(filename)."""
1336
## FIXME: better check
1337
filename = normpath(filename)
1338
while filename != '':
1339
head, tail = os.path.split(filename)
1340
## mutter('check %r for control file' % ((head, tail),))
1343
if filename == head:
1349
class WorkingTreeFormat(object):
1350
"""An encapsulation of the initialization and open routines for a format.
1352
Formats provide three things:
1353
* An initialization routine,
1357
Formats are placed in an dict by their format string for reference
1358
during workingtree opening. Its not required that these be instances, they
1359
can be classes themselves with class methods - it simply depends on
1360
whether state is needed for a given format or not.
1362
Once a format is deprecated, just deprecate the initialize and open
1363
methods on the format class. Do not deprecate the object, as the
1364
object will be created every time regardless.
1367
_default_format = None
1368
"""The default format used for new trees."""
1371
"""The known formats."""
1374
def find_format(klass, a_bzrdir):
1375
"""Return the format for the working tree object in a_bzrdir."""
1377
transport = a_bzrdir.get_workingtree_transport(None)
1378
format_string = transport.get("format").read()
1379
return klass._formats[format_string]
1381
raise errors.NoWorkingTree(base=transport.base)
1383
raise errors.UnknownFormatError(format_string)
1386
def get_default_format(klass):
1387
"""Return the current default format."""
1388
return klass._default_format
1390
def get_format_string(self):
1391
"""Return the ASCII format string that identifies this format."""
1392
raise NotImplementedError(self.get_format_string)
1394
def is_supported(self):
1395
"""Is this format supported?
1397
Supported formats can be initialized and opened.
1398
Unsupported formats may not support initialization or committing or
1399
some other features depending on the reason for not being supported.
1404
def register_format(klass, format):
1405
klass._formats[format.get_format_string()] = format
1408
def set_default_format(klass, format):
1409
klass._default_format = format
1412
def unregister_format(klass, format):
1413
assert klass._formats[format.get_format_string()] is format
1414
del klass._formats[format.get_format_string()]
1418
class WorkingTreeFormat2(WorkingTreeFormat):
1419
"""The second working tree format.
1421
This format modified the hash cache from the format 1 hash cache.
1424
def initialize(self, a_bzrdir, revision_id=None):
1425
"""See WorkingTreeFormat.initialize()."""
1426
if not isinstance(a_bzrdir.transport, LocalTransport):
1427
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1428
branch = a_bzrdir.open_branch()
1429
if revision_id is not None:
1432
revision_history = branch.revision_history()
1434
position = revision_history.index(revision_id)
1436
raise errors.NoSuchRevision(branch, revision_id)
1437
branch.set_revision_history(revision_history[:position + 1])
1440
revision = branch.last_revision()
1442
wt = WorkingTree(a_bzrdir.root_transport.base,
1448
wt._write_inventory(inv)
1449
wt.set_root_id(inv.root.file_id)
1450
wt.set_last_revision(revision)
1451
wt.set_pending_merges([])
1452
build_tree(wt.basis_tree(), wt)
1456
super(WorkingTreeFormat2, self).__init__()
1457
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1459
def open(self, a_bzrdir, _found=False):
1460
"""Return the WorkingTree object for a_bzrdir
1462
_found is a private parameter, do not use it. It is used to indicate
1463
if format probing has already been done.
1466
# we are being called directly and must probe.
1467
raise NotImplementedError
1468
if not isinstance(a_bzrdir.transport, LocalTransport):
1469
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1470
return WorkingTree(a_bzrdir.root_transport.base,
1476
class WorkingTreeFormat3(WorkingTreeFormat):
1477
"""The second working tree format updated to record a format marker.
1479
This format modified the hash cache from the format 1 hash cache.
1482
def get_format_string(self):
1483
"""See WorkingTreeFormat.get_format_string()."""
1484
return "Bazaar-NG Working Tree format 3"
1486
def initialize(self, a_bzrdir, revision_id=None):
1487
"""See WorkingTreeFormat.initialize().
1489
revision_id allows creating a working tree at a differnet
1490
revision than the branch is at.
1492
if not isinstance(a_bzrdir.transport, LocalTransport):
1493
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1494
transport = a_bzrdir.get_workingtree_transport(self)
1495
control_files = LockableFiles(transport, 'lock', TransportLock)
1496
control_files.put_utf8('format', self.get_format_string())
1497
branch = a_bzrdir.open_branch()
1498
if revision_id is None:
1499
revision_id = branch.last_revision()
1501
wt = WorkingTree3(a_bzrdir.root_transport.base,
1507
wt._write_inventory(inv)
1508
wt.set_root_id(inv.root.file_id)
1509
wt.set_last_revision(revision_id)
1510
wt.set_pending_merges([])
1511
build_tree(wt.basis_tree(), wt)
1515
super(WorkingTreeFormat3, self).__init__()
1516
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1518
def open(self, a_bzrdir, _found=False):
1519
"""Return the WorkingTree object for a_bzrdir
1521
_found is a private parameter, do not use it. It is used to indicate
1522
if format probing has already been done.
1525
# we are being called directly and must probe.
1526
raise NotImplementedError
1527
if not isinstance(a_bzrdir.transport, LocalTransport):
1528
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1529
return WorkingTree3(a_bzrdir.root_transport.base,
1535
return self.get_format_string()
1538
# formats which have no format string are not discoverable
1539
# and not independently creatable, so are not registered.
1540
__default_format = WorkingTreeFormat3()
1541
WorkingTreeFormat.register_format(__default_format)
1542
WorkingTreeFormat.set_default_format(__default_format)
1543
_legacy_formats = [WorkingTreeFormat2(),
1547
class WorkingTreeTestProviderAdapter(object):
1548
"""A tool to generate a suite testing multiple workingtree formats at once.
1550
This is done by copying the test once for each transport and injecting
1551
the transport_server, transport_readonly_server, and workingtree_format
1552
classes into each copy. Each copy is also given a new id() to make it
1556
def __init__(self, transport_server, transport_readonly_server, formats):
1557
self._transport_server = transport_server
1558
self._transport_readonly_server = transport_readonly_server
1559
self._formats = formats
1561
def adapt(self, test):
1562
from bzrlib.tests import TestSuite
1563
result = TestSuite()
1564
for workingtree_format, bzrdir_format in self._formats:
1565
new_test = deepcopy(test)
1566
new_test.transport_server = self._transport_server
1567
new_test.transport_readonly_server = self._transport_readonly_server
1568
new_test.bzrdir_format = bzrdir_format
1569
new_test.workingtree_format = workingtree_format
1570
def make_new_test_id():
1571
new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1572
return lambda: new_id
1573
new_test.id = make_new_test_id()
1574
result.addTest(new_test)