25
25
At the moment every WorkingTree has its own branch. Remote
26
26
WorkingTrees aren't supported.
28
To get a WorkingTree, call Branch.working_tree():
28
To get a WorkingTree, call WorkingTree(dir[, branch])
32
# TODO: Don't allow WorkingTrees to be constructed for remote branches if
35
32
# FIXME: I don't know if writing out the cache from the destructor is really a
36
33
# good idea, because destructors are considered poor taste in Python, and it's
37
34
# not predictable when it will be written out.
43
40
# At the momenthey may alias the inventory and have old copies of it in memory.
45
42
from copy import deepcopy
43
from cStringIO import StringIO
50
from bzrlib.atomicfile import AtomicFile
50
51
from bzrlib.branch import (Branch,
57
from bzrlib.decorators import needs_read_lock, needs_write_lock
55
58
from bzrlib.errors import (BzrCheckError,
58
61
WeaveRevisionNotPresent,
61
65
from bzrlib.inventory import InventoryEntry
66
from bzrlib.lockable_files import LockableFiles
62
67
from bzrlib.osutils import (appendpath,
178
186
not listed in the Inventory and vice versa.
181
def __init__(self, basedir=u'.', branch=None):
189
def __init__(self, basedir='.', branch=None, _inventory=None, _control_files=None):
182
190
"""Construct a WorkingTree for basedir.
184
192
If the branch is not supplied, it is opened automatically.
190
198
from bzrlib.trace import note, mutter
191
199
assert isinstance(basedir, basestring), \
192
200
"base directory %r is not a string" % basedir
201
basedir = safe_unicode(basedir)
202
mutter("openeing working tree %r", basedir)
193
203
if branch is None:
194
204
branch = Branch.open(basedir)
195
205
assert isinstance(branch, Branch), \
196
206
"branch %r is not a Branch" % branch
197
207
self.branch = branch
198
208
self.basedir = realpath(basedir)
209
# if branch is at our basedir and is a format 6 or less
210
if (isinstance(self.branch._branch_format,
211
(BzrBranchFormat4, BzrBranchFormat5, BzrBranchFormat6))
212
# might be able to share control object
213
and self.branch.base.split('/')[-2] == self.basedir.split('/')[-1]):
214
self._control_files = self.branch.control_files
215
elif _control_files is not None:
216
assert False, "not done yet"
217
# self._control_files = _control_files
219
self._control_files = LockableFiles(
220
get_transport(self.basedir).clone(bzrlib.BZRDIR), 'branch-lock')
200
222
# update the whole cache up front and write to disk if anything changed;
201
223
# in the future we might want to do this more selectively
211
233
mutter("write hc")
214
self._set_inventory(self.read_working_inventory())
236
if _inventory is None:
237
self._set_inventory(self.read_working_inventory())
239
self._set_inventory(_inventory)
216
241
def _set_inventory(self, inv):
217
242
self._inventory = inv
249
275
path = os.path.dirname(path)
250
276
if lastpath == path:
251
277
# reached the root, whatever that may be
252
raise NotBranchError(path=path)
278
raise NotBranchError(path=orig_path)
254
280
def __iter__(self):
255
281
"""Iterate through file_ids for this tree.
269
295
def abspath(self, filename):
270
296
return pathjoin(self.basedir, filename)
299
def create(branch, directory):
300
"""Create a workingtree for branch at directory.
302
If existing_directory already exists it must have a .bzr directory.
303
If it does not exist, it will be created.
305
This returns a new WorkingTree object for the new checkout.
307
TODO FIXME RBC 20060124 when we have checkout formats in place this
308
should accept an optional revisionid to checkout [and reject this if
309
checking out into the same dir as a pre-checkout-aware branch format.]
311
XXX: When BzrDir is present, these should be created through that
317
if e.errno != errno.EEXIST:
320
os.mkdir(pathjoin(directory, '.bzr'))
322
if e.errno != errno.EEXIST:
324
inv = branch.repository.revision_tree(branch.last_revision()).inventory
325
wt = WorkingTree(directory, branch, inv)
326
wt._write_inventory(inv)
327
if branch.last_revision() is not None:
328
wt.set_last_revision(branch.last_revision())
329
wt.set_pending_merges([])
334
def create_standalone(directory):
335
"""Create a checkout and a branch at directory.
337
Directory must exist and be empty.
339
XXX: When BzrDir is present, these should be created through that
342
directory = safe_unicode(directory)
343
b = Branch.create(directory)
344
return WorkingTree.create(b, directory)
272
346
def relpath(self, abs):
273
347
"""Return the local path portion from a given absolute path."""
274
348
return relpath(self.basedir, abs)
292
366
return self.abspath(self.id2path(file_id))
294
368
@needs_write_lock
295
def commit(self, *args, **kw):
369
def commit(self, *args, **kwargs):
296
370
from bzrlib.commit import Commit
297
Commit().commit(self.branch, *args, **kw)
371
# args for wt.commit start at message from the Commit.commit method,
372
# but with branch a kwarg now, passing in args as is results in the
373
#message being used for the branch
374
args = (DEPRECATED_PARAMETER, ) + args
375
Commit().commit(working_tree=self, *args, **kwargs)
298
376
self._set_inventory(self.read_working_inventory())
300
378
def id2abspath(self, file_id):
411
489
self.set_pending_merges(p)
413
492
def pending_merges(self):
414
493
"""Return a list of pending merges.
416
495
These are revisions that have been merged into the working
417
496
directory but not yet committed.
419
cfn = self.branch._rel_controlfilename('pending-merges')
420
if not self.branch._transport.has(cfn):
499
merges_file = self._control_files.get_utf8('pending-merges')
501
if e.errno != errno.ENOENT:
423
for l in self.branch.controlfile('pending-merges', 'r').readlines():
505
for l in merges_file.readlines():
424
506
p.append(l.rstrip('\n'))
427
509
@needs_write_lock
428
510
def set_pending_merges(self, rev_list):
429
self.branch.put_controlfile('pending-merges', '\n'.join(rev_list))
511
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
431
513
def get_symlink_target(self, file_id):
432
514
return os.readlink(self.id2abspath(file_id))
675
757
other_revision = old_revision_history[-1]
677
759
other_revision = None
760
repository = self.branch.repository
678
761
merge_inner(self.branch,
679
762
self.branch.basis_tree(),
680
self.branch.revision_tree(other_revision))
763
repository.revision_tree(other_revision),
765
self.set_last_revision(self.branch.last_revision())
788
873
return 'basis-inventory.%s' % revision_id
790
875
def set_last_revision(self, new_revision, old_revision=None):
876
if old_revision is not None:
793
878
path = self._basis_inventory_name(old_revision)
794
path = self.branch._rel_controlfilename(path)
795
self.branch._transport.delete(path)
879
path = self.branch.control_files._escape(path)
880
self.branch.control_files._transport.delete(path)
799
xml = self.branch.get_inventory_xml(new_revision)
884
xml = self.branch.repository.get_inventory_xml(new_revision)
800
885
path = self._basis_inventory_name(new_revision)
801
self.branch.put_controlfile(path, xml)
886
self.branch.control_files.put_utf8(path, xml)
802
887
except WeaveRevisionNotPresent:
805
890
def read_basis_inventory(self, revision_id):
806
891
"""Read the cached basis inventory."""
807
892
path = self._basis_inventory_name(revision_id)
808
return self.branch.controlfile(path, 'r').read()
893
return self.branch.control_files.get_utf8(path).read()
811
896
def read_working_inventory(self):
812
897
"""Read the working inventory."""
813
898
# ElementTree does its own conversion from UTF-8, so open in
815
f = self.branch.controlfile('inventory', 'rb')
816
return bzrlib.xml5.serializer_v5.read_inventory(f)
900
return bzrlib.xml5.serializer_v5.read_inventory(
901
self._control_files.get('inventory'))
818
903
@needs_write_lock
819
904
def remove(self, files, verbose=False):
864
949
merge_inner(self.branch, old_tree,
865
950
self, ignore_zero=True,
866
951
backup_files=backups,
867
interesting_files=filenames)
952
interesting_files=filenames,
868
954
if not len(filenames):
869
955
self.set_pending_merges([])
914
1000
between multiple working trees, i.e. via shared storage, then we
915
1001
would probably want to lock both the local tree, and the branch.
917
if self._hashcache.needs_write:
1003
# FIXME: We want to write out the hashcache only when the last lock on
1004
# this working copy is released. Peeking at the lock count is a bit
1005
# of a nasty hack; probably it's better to have a transaction object,
1006
# which can do some finalization when it's either successfully or
1007
# unsuccessfully completed. (Denys's original patch did that.)
1008
if self._hashcache.needs_write and self.branch.control_files._lock_count==1:
918
1009
self._hashcache.write()
919
1010
return self.branch.unlock()
921
1012
@needs_write_lock
922
1013
def _write_inventory(self, inv):
923
1014
"""Write inventory as the current inventory."""
924
from cStringIO import StringIO
925
from bzrlib.atomicfile import AtomicFile
926
1015
sio = StringIO()
927
1016
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
929
f = AtomicFile(self.branch.controlfilename('inventory'))
1018
self._control_files.put('inventory', sio)
935
1019
self._set_inventory(inv)
936
1020
mutter('wrote working inventory')