29
29
WorkingTree.open(dir).
33
# FIXME: I don't know if writing out the cache from the destructor is really a
34
# good idea, because destructors are considered poor taste in Python, and it's
35
# not predictable when it will be written out.
32
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
33
CONFLICT_HEADER_1 = "BZR conflict list format 1"
37
35
# TODO: Give the workingtree sole responsibility for the working inventory;
38
36
# remove the variable and references to it from the branch. This may require
39
37
# updating the commit code so as to update the inventory within the working
40
38
# copy, and making sure there's only one WorkingTree for any directory on disk.
41
# At the momenthey may alias the inventory and have old copies of it in memory.
39
# At the moment they may alias the inventory and have old copies of it in
40
# memory. (Now done? -- mbp 20060309)
43
42
from copy import deepcopy
44
43
from cStringIO import StringIO
51
50
from bzrlib.atomicfile import AtomicFile
52
51
from bzrlib.branch import (Branch,
53
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
54
54
import bzrlib.bzrdir as bzrdir
55
55
from bzrlib.decorators import needs_read_lock, needs_write_lock
56
56
import bzrlib.errors as errors
57
57
from bzrlib.errors import (BzrCheckError,
60
61
WeaveRevisionNotPresent,
64
from bzrlib.inventory import InventoryEntry
65
from bzrlib.lockable_files import LockableFiles
65
MergeModifiedFormatError,
68
from bzrlib.inventory import InventoryEntry, Inventory
69
from bzrlib.lockable_files import LockableFiles, TransportLock
70
from bzrlib.lockdir import LockDir
66
71
from bzrlib.merge import merge_inner, transform_tree
67
from bzrlib.osutils import (appendpath,
72
from bzrlib.osutils import (
90
from bzrlib.progress import DummyProgress, ProgressPhase
82
91
from bzrlib.revision import NULL_REVISION
92
from bzrlib.rio import RioReader, rio_file, Stanza
83
93
from bzrlib.symbol_versioning import *
84
94
from bzrlib.textui import show_status
86
from bzrlib.trace import mutter
96
from bzrlib.transform import build_tree
97
from bzrlib.trace import mutter, note
87
98
from bzrlib.transport import get_transport
88
99
from bzrlib.transport.local import LocalTransport
89
101
import bzrlib.xml5
223
235
assert isinstance(basedir, basestring), \
224
236
"base directory %r is not a string" % basedir
225
237
basedir = safe_unicode(basedir)
226
mutter("openeing working tree %r", basedir)
238
mutter("opening working tree %r", basedir)
227
239
if deprecated_passed(branch):
228
240
if not _internal:
229
241
warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
230
" Please use bzrdir.open_workingtree() or WorkingTree.open().",
242
" Please use bzrdir.open_workingtree() or"
243
" WorkingTree.open().",
231
244
DeprecationWarning,
247
self._branch = branch
236
self.branch = self.bzrdir.open_branch()
249
self._branch = self.bzrdir.open_branch()
237
250
assert isinstance(self.branch, Branch), \
238
251
"branch %r is not a Branch" % self.branch
239
252
self.basedir = realpath(basedir)
241
254
if isinstance(self._format, WorkingTreeFormat2):
242
255
# share control object
243
256
self._control_files = self.branch.control_files
244
elif _control_files is not None:
245
assert False, "not done yet"
246
# self._control_files = _control_files
248
258
# only ready for format 3
249
259
assert isinstance(self._format, WorkingTreeFormat3)
250
self._control_files = LockableFiles(
251
self.bzrdir.get_workingtree_transport(None),
260
assert isinstance(_control_files, LockableFiles), \
261
"_control_files must be a LockableFiles, not %r" \
263
self._control_files = _control_files
254
264
# update the whole cache up front and write to disk if anything changed;
255
265
# in the future we might want to do this more selectively
256
266
# two possible ways offer themselves : in self._unlock, write the cache
273
283
self._set_inventory(_inventory)
286
fget=lambda self: self._branch,
287
doc="""The branch this WorkingTree is connected to.
289
This cannot be set - it is reflective of the actual disk structure
290
the working tree has been constructed from.
275
293
def _set_inventory(self, inv):
276
294
self._inventory = inv
277
295
self.path2id = self._inventory.path2id
297
def is_control_filename(self, filename):
298
"""True if filename is the name of a control file in this tree.
300
This is true IF and ONLY IF the filename is part of the meta data
301
that bzr controls in this tree. I.E. a random .bzr directory placed
302
on disk will not be a control file for this tree.
305
self.bzrdir.transport.relpath(self.abspath(filename))
307
except errors.PathNotChild:
280
311
def open(path=None, _unsupported=False):
281
312
"""Open an existing working tree at path.
333
364
revision_id = self.last_revision()
334
365
if revision_id is not None:
336
xml = self.read_basis_inventory(revision_id)
367
xml = self.read_basis_inventory()
337
368
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
371
if inv is not None and inv.revision_id == revision_id:
338
372
return bzrlib.tree.RevisionTree(self.branch.repository, inv,
374
# FIXME? RBC 20060403 should we cache the inventory here ?
342
375
return self.branch.repository.revision_tree(revision_id)
435
468
tree.set_last_revision(revision_id)
437
470
@needs_write_lock
438
def commit(self, *args, **kwargs):
471
def commit(self, message=None, revprops=None, *args, **kwargs):
472
# avoid circular imports
439
473
from bzrlib.commit import Commit
476
if not 'branch-nick' in revprops:
477
revprops['branch-nick'] = self.branch.nick
440
478
# args for wt.commit start at message from the Commit.commit method,
441
479
# but with branch a kwarg now, passing in args as is results in the
442
480
#message being used for the branch
443
args = (DEPRECATED_PARAMETER, ) + args
444
Commit().commit(working_tree=self, *args, **kwargs)
481
args = (DEPRECATED_PARAMETER, message, ) + args
482
Commit().commit(working_tree=self, revprops=revprops, *args, **kwargs)
445
483
self._set_inventory(self.read_working_inventory())
447
485
def id2abspath(self, file_id):
471
509
return self._hashcache.get_sha1(path)
473
511
def is_executable(self, file_id):
512
if not supports_executable():
475
513
return self._inventory[file_id].executable
477
515
path = self._inventory.id2path(file_id)
581
619
def set_pending_merges(self, rev_list):
582
620
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
623
def set_merge_modified(self, modified_hashes):
625
for file_id, hash in modified_hashes.iteritems():
626
yield Stanza(file_id=file_id, hash=hash)
627
self._put_rio('merge-hashes', iter_stanzas(), MERGE_MODIFIED_HEADER_1)
630
def _put_rio(self, filename, stanzas, header):
631
my_file = rio_file(stanzas, header)
632
self._control_files.put(filename, my_file)
635
def merge_modified(self):
637
hashfile = self._control_files.get('merge-hashes')
642
if hashfile.next() != MERGE_MODIFIED_HEADER_1 + '\n':
643
raise MergeModifiedFormatError()
644
except StopIteration:
645
raise MergeModifiedFormatError()
646
for s in RioReader(hashfile):
647
file_id = s.get("file_id")
648
if file_id not in self.inventory:
651
if hash == self.get_file_sha1(file_id):
652
merge_hashes[file_id] = hash
584
655
def get_symlink_target(self, file_id):
585
656
return os.readlink(self.id2abspath(file_id))
805
878
if not self.is_ignored(subp):
881
@deprecated_method(zero_eight)
808
882
def iter_conflicts(self):
883
"""List all files in the tree that have text or content conflicts.
884
DEPRECATED. Use conflicts instead."""
885
return self._iter_conflicts()
887
def _iter_conflicts(self):
809
888
conflicted = set()
810
889
for path in (s[0] for s in self.list_files()):
811
890
stem = get_conflicted_stem(path)
818
897
@needs_write_lock
819
898
def pull(self, source, overwrite=False, stop_revision=None):
899
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
820
900
source.lock_read()
902
pp = ProgressPhase("Pull phase", 2, top_pb)
822
904
old_revision_history = self.branch.revision_history()
905
basis_tree = self.basis_tree()
823
906
count = self.branch.pull(source, overwrite, stop_revision)
824
907
new_revision_history = self.branch.revision_history()
825
908
if new_revision_history != old_revision_history:
826
910
if len(old_revision_history):
827
911
other_revision = old_revision_history[-1]
829
913
other_revision = None
830
914
repository = self.branch.repository
831
merge_inner(self.branch,
833
repository.revision_tree(other_revision),
915
pb = bzrlib.ui.ui_factory.nested_progress_bar()
917
merge_inner(self.branch,
918
self.branch.basis_tree(),
835
924
self.set_last_revision(self.branch.last_revision())
840
930
def extras(self):
841
931
"""Yield all unknown files in this WorkingTree.
959
1049
self.branch.unlock()
962
def _basis_inventory_name(self, revision_id):
963
return 'basis-inventory.%s' % revision_id
1052
def _basis_inventory_name(self):
1053
return 'basis-inventory'
965
1055
@needs_write_lock
966
def set_last_revision(self, new_revision, old_revision=None):
1056
def set_last_revision(self, new_revision):
967
1057
"""Change the last revision in the working tree."""
968
self._remove_old_basis(old_revision)
969
1058
if self._change_last_revision(new_revision):
970
1059
self._cache_basis_inventory(new_revision)
972
1061
def _change_last_revision(self, new_revision):
973
"""Template method part of set_last_revision to perform the change."""
1062
"""Template method part of set_last_revision to perform the change.
1064
This is used to allow WorkingTree3 instances to not affect branch
1065
when their last revision is set.
974
1067
if new_revision is None:
975
1068
self.branch.set_revision_history([])
986
1079
def _cache_basis_inventory(self, new_revision):
987
1080
"""Cache new_revision as the basis inventory."""
989
xml = self.branch.repository.get_inventory_xml(new_revision)
990
path = self._basis_inventory_name(new_revision)
1082
# this double handles the inventory - unpack and repack -
1083
# but is easier to understand. We can/should put a conditional
1084
# in here based on whether the inventory is in the latest format
1085
# - perhaps we should repack all inventories on a repository
1087
inv = self.branch.repository.get_inventory(new_revision)
1088
inv.revision_id = new_revision
1089
xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1091
path = self._basis_inventory_name()
991
1092
self._control_files.put_utf8(path, xml)
992
1093
except WeaveRevisionNotPresent:
995
def _remove_old_basis(self, old_revision):
996
"""Remove the old basis inventory 'old_revision'."""
997
if old_revision is not None:
999
path = self._basis_inventory_name(old_revision)
1000
path = self._control_files._escape(path)
1001
self._control_files._transport.delete(path)
1005
def read_basis_inventory(self, revision_id):
1096
def read_basis_inventory(self):
1006
1097
"""Read the cached basis inventory."""
1007
path = self._basis_inventory_name(revision_id)
1098
path = self._basis_inventory_name()
1008
1099
return self._control_files.get_utf8(path).read()
1010
1101
@needs_read_lock
1059
1150
self._write_inventory(inv)
1061
1152
@needs_write_lock
1062
def revert(self, filenames, old_tree=None, backups=True):
1063
from bzrlib.merge import merge_inner
1153
def revert(self, filenames, old_tree=None, backups=True,
1154
pb=DummyProgress()):
1155
from transform import revert
1156
from conflicts import resolve
1064
1157
if old_tree is None:
1065
1158
old_tree = self.basis_tree()
1066
merge_inner(self.branch, old_tree,
1067
self, ignore_zero=True,
1068
backup_files=backups,
1069
interesting_files=filenames,
1159
conflicts = revert(self, old_tree, filenames, backups, pb)
1071
1160
if not len(filenames):
1072
1161
self.set_pending_merges([])
1164
resolve(self, filenames, ignore_misses=True)
1167
# XXX: This method should be deprecated in favour of taking in a proper
1168
# new Inventory object.
1074
1169
@needs_write_lock
1075
1170
def set_inventory(self, new_inventory_list):
1076
1171
from bzrlib.inventory import (Inventory,
1128
1223
# TODO: split this per format so there is no ugly if block
1129
1224
if self._hashcache.needs_write and (
1225
# dedicated lock files
1130
1226
self._control_files._lock_count==1 or
1131
1228
(self._control_files is self.branch.control_files and
1132
self._control_files._lock_count==2)):
1229
self._control_files._lock_count==3)):
1133
1230
self._hashcache.write()
1134
1231
# reverse order of locking.
1135
1232
result = self._control_files.unlock()
1141
1238
@needs_write_lock
1142
1239
def update(self):
1240
"""Update a working tree along its branch.
1242
This will update the branch if its bound too, which means we have multiple trees involved:
1243
The new basis tree of the master.
1244
The old basis tree of the branch.
1245
The old basis tree of the working tree.
1246
The current working tree state.
1247
pathologically all three may be different, and non ancestors of each other.
1248
Conceptually we want to:
1249
Preserve the wt.basis->wt.state changes
1250
Transform the wt.basis to the new master basis.
1251
Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
1252
Restore the wt.basis->wt.state changes.
1254
There isn't a single operation at the moment to do that, so we:
1255
Merge current state -> basis tree of the master w.r.t. the old tree basis.
1256
Do a 'normal' merge of the old branch basis if it is relevant.
1258
old_tip = self.branch.update()
1259
if old_tip is not None:
1260
self.add_pending_merge(old_tip)
1143
1261
self.branch.lock_read()
1145
if self.last_revision() == self.branch.last_revision():
1147
basis = self.basis_tree()
1148
to_tree = self.branch.basis_tree()
1149
result = merge_inner(self.branch,
1153
self.set_last_revision(self.branch.last_revision())
1264
if self.last_revision() != self.branch.last_revision():
1265
# merge tree state up to new branch tip.
1266
basis = self.basis_tree()
1267
to_tree = self.branch.basis_tree()
1268
result += merge_inner(self.branch,
1272
self.set_last_revision(self.branch.last_revision())
1273
if old_tip and old_tip != self.last_revision():
1274
# our last revision was not the prior branch last reivison
1275
# and we have converted that last revision to a pending merge.
1276
# base is somewhere between the branch tip now
1277
# and the now pending merge
1278
from bzrlib.revision import common_ancestor
1280
base_rev_id = common_ancestor(self.branch.last_revision(),
1282
self.branch.repository)
1283
except errors.NoCommonAncestor:
1285
base_tree = self.branch.repository.revision_tree(base_rev_id)
1286
other_tree = self.branch.repository.revision_tree(old_tip)
1287
result += merge_inner(self.branch,
1156
1293
self.branch.unlock()
1165
1302
self._set_inventory(inv)
1166
1303
mutter('wrote working inventory')
1305
def set_conflicts(self, arg):
1306
raise UnsupportedOperation(self.set_conflicts, self)
1309
def conflicts(self):
1310
conflicts = ConflictList()
1311
for conflicted in self._iter_conflicts():
1314
if file_kind(self.abspath(conflicted)) != "file":
1317
if e.errno == errno.ENOENT:
1322
for suffix in ('.THIS', '.OTHER'):
1324
kind = file_kind(self.abspath(conflicted+suffix))
1326
if e.errno == errno.ENOENT:
1334
ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1335
conflicts.append(Conflict.factory(ctype, path=conflicted,
1336
file_id=self.path2id(conflicted)))
1169
1340
class WorkingTree3(WorkingTree):
1170
1341
"""This is the Format 3 working tree.
1198
1371
self._control_files.put_utf8('last-revision', revision_id)
1202
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1375
def set_conflicts(self, conflicts):
1376
self._put_rio('conflicts', conflicts.to_stanzas(),
1380
def conflicts(self):
1382
confile = self._control_files.get('conflicts')
1384
return ConflictList()
1386
if confile.next() != CONFLICT_HEADER_1 + '\n':
1387
raise ConflictFormatError()
1388
except StopIteration:
1389
raise ConflictFormatError()
1390
return ConflictList.from_stanzas(RioReader(confile))
1203
1393
def get_conflicted_stem(path):
1204
1394
for suffix in CONFLICT_SUFFIXES:
1205
1395
if path.endswith(suffix):
1206
1396
return path[:-len(suffix)]
1398
@deprecated_function(zero_eight)
1208
1399
def is_control_file(filename):
1400
"""See WorkingTree.is_control_filename(filename)."""
1209
1401
## FIXME: better check
1210
1402
filename = normpath(filename)
1211
1403
while filename != '':
1212
1404
head, tail = os.path.split(filename)
1213
1405
## mutter('check %r for control file' % ((head, tail),))
1214
if tail == bzrlib.BZRDIR:
1216
1408
if filename == head:
1264
1456
"""Return the ASCII format string that identifies this format."""
1265
1457
raise NotImplementedError(self.get_format_string)
1459
def get_format_description(self):
1460
"""Return the short description for this format."""
1461
raise NotImplementedError(self.get_format_description)
1267
1463
def is_supported(self):
1268
1464
"""Is this format supported?
1294
1490
This format modified the hash cache from the format 1 hash cache.
1493
def get_format_description(self):
1494
"""See WorkingTreeFormat.get_format_description()."""
1495
return "Working tree format 2"
1297
1497
def initialize(self, a_bzrdir, revision_id=None):
1298
1498
"""See WorkingTreeFormat.initialize()."""
1299
1499
if not isinstance(a_bzrdir.transport, LocalTransport):
1350
1549
class WorkingTreeFormat3(WorkingTreeFormat):
1351
1550
"""The second working tree format updated to record a format marker.
1353
This format modified the hash cache from the format 1 hash cache.
1553
- exists within a metadir controlling .bzr
1554
- includes an explicit version marker for the workingtree control
1555
files, separate from the BzrDir format
1556
- modifies the hash cache format
1558
- uses a LockDir to guard access to the repository
1356
1561
def get_format_string(self):
1357
1562
"""See WorkingTreeFormat.get_format_string()."""
1358
1563
return "Bazaar-NG Working Tree format 3"
1565
def get_format_description(self):
1566
"""See WorkingTreeFormat.get_format_description()."""
1567
return "Working tree format 3"
1569
_lock_file_name = 'lock'
1570
_lock_class = LockDir
1572
def _open_control_files(self, a_bzrdir):
1573
transport = a_bzrdir.get_workingtree_transport(None)
1574
return LockableFiles(transport, self._lock_file_name,
1360
1577
def initialize(self, a_bzrdir, revision_id=None):
1361
1578
"""See WorkingTreeFormat.initialize().
1366
1583
if not isinstance(a_bzrdir.transport, LocalTransport):
1367
1584
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1368
1585
transport = a_bzrdir.get_workingtree_transport(self)
1369
control_files = LockableFiles(transport, 'lock')
1586
control_files = self._open_control_files(a_bzrdir)
1587
control_files.create_lock()
1588
control_files.lock_write()
1370
1589
control_files.put_utf8('format', self.get_format_string())
1371
1590
branch = a_bzrdir.open_branch()
1372
1591
if revision_id is None:
1373
1592
revision_id = branch.last_revision()
1374
new_basis_tree = branch.repository.revision_tree(revision_id)
1375
inv = new_basis_tree.inventory
1376
1594
wt = WorkingTree3(a_bzrdir.root_transport.base,
1379
1597
_internal=True,
1382
wt._write_inventory(inv)
1383
wt.set_root_id(inv.root.file_id)
1384
wt.set_last_revision(revision_id)
1385
wt.set_pending_merges([])
1600
_control_files=control_files)
1603
wt._write_inventory(inv)
1604
wt.set_root_id(inv.root.file_id)
1605
wt.set_last_revision(revision_id)
1606
wt.set_pending_merges([])
1607
build_tree(wt.basis_tree(), wt)
1610
control_files.unlock()
1389
1613
def __init__(self):
1401
1625
raise NotImplementedError
1402
1626
if not isinstance(a_bzrdir.transport, LocalTransport):
1403
1627
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1628
control_files = self._open_control_files(a_bzrdir)
1404
1629
return WorkingTree3(a_bzrdir.root_transport.base,
1405
1630
_internal=True,
1633
_control_files=control_files)
1636
return self.get_format_string()
1410
1639
# formats which have no format string are not discoverable