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
17
# TODO: Don't allow WorkingTrees to be constructed for remote branches.
17
"""WorkingTree object and friends.
19
A WorkingTree represents the editable working copy of a branch.
20
Operations which represent the WorkingTree are also done here,
21
such as renaming or adding files. The WorkingTree has an inventory
22
which is updated by these operations. A commit produces a
23
new revision based on the workingtree and its inventory.
25
At the moment every WorkingTree has its own branch. Remote
26
WorkingTrees aren't supported.
28
To get a WorkingTree, call Branch.working_tree():
32
# TODO: Don't allow WorkingTrees to be constructed for remote branches if
19
35
# FIXME: I don't know if writing out the cache from the destructor is really a
20
# good idea, because destructors are considered poor taste in Python, and
21
# it's not predictable when it will be written out.
36
# good idea, because destructors are considered poor taste in Python, and it's
37
# not predictable when it will be written out.
39
# TODO: Give the workingtree sole responsibility for the working inventory;
40
# remove the variable and references to it from the branch. This may require
41
# updating the commit code so as to update the inventory within the working
42
# copy, and making sure there's only one WorkingTree for any directory on disk.
43
# At the momenthey may alias the inventory and have old copies of it in memory.
45
from copy import deepcopy
50
from bzrlib.branch import (Branch,
53
from bzrlib.errors import (BzrCheckError,
56
WeaveRevisionNotPresent,
60
from bzrlib.inventory import InventoryEntry
61
from bzrlib.osutils import (appendpath,
75
from bzrlib.textui import show_status
28
from bzrlib.osutils import appendpath, file_kind, isdir, splitpath
29
from bzrlib.errors import BzrCheckError
30
77
from bzrlib.trace import mutter
79
from bzrlib.decorators import needs_read_lock, needs_write_lock
82
def gen_file_id(name):
83
"""Return new file id.
85
This should probably generate proper UUIDs, but for the moment we
86
cope with just randomness because running uuidgen every time is
89
from binascii import hexlify
96
idx = name.rfind('\\')
100
# make it not a hidden file
101
name = name.lstrip('.')
103
# remove any wierd characters; we don't escape them but rather
105
name = re.sub(r'[^\w.]', '', name)
107
s = hexlify(rand_bytes(8))
108
return '-'.join((name, compact_date(time()), s))
112
"""Return a new tree-root file id."""
113
return gen_file_id('TREE_ROOT')
32
116
class TreeEntry(object):
33
117
"""An entry that implements the minium interface used by commands.
93
177
It is possible for a `WorkingTree` to have a filename which is
94
178
not listed in the Inventory and vice versa.
96
def __init__(self, basedir, inv):
181
def __init__(self, basedir=u'.', branch=None):
182
"""Construct a WorkingTree for basedir.
184
If the branch is not supplied, it is opened automatically.
185
If the branch is supplied, it must be the branch for this basedir.
186
(branch.base is not cross checked, because for remote branches that
187
would be meaningless).
97
189
from bzrlib.hashcache import HashCache
98
190
from bzrlib.trace import note, mutter
100
self._inventory = inv
101
self.basedir = basedir
102
self.path2id = inv.path2id
191
assert isinstance(basedir, basestring), \
192
"base directory %r is not a string" % basedir
194
branch = Branch.open(basedir)
195
assert isinstance(branch, Branch), \
196
"branch %r is not a Branch" % branch
198
self.basedir = realpath(basedir)
104
200
# update the whole cache up front and write to disk if anything changed;
105
201
# in the future we might want to do this more selectively
202
# two possible ways offer themselves : in self._unlock, write the cache
203
# if needed, or, when the cache sees a change, append it to the hash
204
# cache file, and have the parser take the most recent entry for a
106
206
hc = self._hashcache = HashCache(basedir)
110
210
if hc.needs_write:
111
211
mutter("write hc")
116
if self._hashcache.needs_write:
117
self._hashcache.write()
214
self._set_inventory(self.read_working_inventory())
216
def _set_inventory(self, inv):
217
self._inventory = inv
218
self.path2id = self._inventory.path2id
221
def open_containing(path=None):
222
"""Open an existing working tree which has its root about path.
224
This probes for a working tree at path and searches upwards from there.
226
Basically we keep looking up until we find the control directory or
227
run into /. If there isn't one, raises NotBranchError.
228
TODO: give this a new exception.
229
If there is one, it is returned, along with the unused portion of path.
235
if path.find('://') != -1:
236
raise NotBranchError(path=path)
242
return WorkingTree(path), tail
243
except NotBranchError:
246
tail = pathjoin(os.path.basename(path), tail)
248
tail = os.path.basename(path)
250
path = os.path.dirname(path)
252
# reached the root, whatever that may be
253
raise NotBranchError(path=orig_path)
120
255
def __iter__(self):
121
256
"""Iterate through file_ids for this tree.
147
283
def get_file_byname(self, filename):
148
284
return file(self.abspath(filename), 'rb')
286
def get_root_id(self):
287
"""Return the id of this trees root"""
288
inv = self.read_working_inventory()
289
return inv.root.file_id
150
291
def _get_store_filename(self, file_id):
151
## XXX: badly named; this isn't in the store at all
292
## XXX: badly named; this is not in the store at all
152
293
return self.abspath(self.id2path(file_id))
296
def commit(self, *args, **kw):
297
from bzrlib.commit import Commit
298
Commit().commit(self.branch, *args, **kw)
299
self._set_inventory(self.read_working_inventory())
155
301
def id2abspath(self, file_id):
156
302
return self.abspath(self.id2path(file_id))
159
304
def has_id(self, file_id):
160
305
# files that have been deleted are excluded
161
306
inv = self._inventory
184
332
mode = os.lstat(self.abspath(path)).st_mode
185
333
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
336
def add(self, files, ids=None):
337
"""Make files versioned.
339
Note that the command line normally calls smart_add instead,
340
which can automatically recurse.
342
This adds the files to the inventory, so that they will be
343
recorded by the next commit.
346
List of paths to add, relative to the base of the tree.
349
If set, use these instead of automatically generated ids.
350
Must be the same length as the list of files, but may
351
contain None for ids that are to be autogenerated.
353
TODO: Perhaps have an option to add the ids even if the files do
356
TODO: Perhaps callback with the ids and paths as they're added.
358
# TODO: Re-adding a file that is removed in the working copy
359
# should probably put it back with the previous ID.
360
if isinstance(files, basestring):
361
assert(ids is None or isinstance(ids, basestring))
367
ids = [None] * len(files)
369
assert(len(ids) == len(files))
371
inv = self.read_working_inventory()
372
for f,file_id in zip(files, ids):
373
if is_control_file(f):
374
raise BzrError("cannot add control file %s" % quotefn(f))
379
raise BzrError("cannot add top-level %r" % f)
381
fullpath = normpath(self.abspath(f))
384
kind = file_kind(fullpath)
386
# maybe something better?
387
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
389
if not InventoryEntry.versionable_kind(kind):
390
raise BzrError('cannot add: not a versionable file ('
391
'i.e. regular file, symlink or directory): %s' % quotefn(f))
394
file_id = gen_file_id(f)
395
inv.add_path(f, kind=kind, file_id=file_id)
397
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
398
self._write_inventory(inv)
401
def add_pending_merge(self, *revision_ids):
402
# TODO: Perhaps should check at this point that the
403
# history of the revision is actually present?
404
p = self.pending_merges()
406
for rev_id in revision_ids:
412
self.set_pending_merges(p)
415
def pending_merges(self):
416
"""Return a list of pending merges.
418
These are revisions that have been merged into the working
419
directory but not yet committed.
422
f = self.branch.control_files.get_utf8('pending-merges')
426
for l in f.readlines():
427
p.append(l.rstrip('\n'))
431
def set_pending_merges(self, rev_list):
432
self.branch.control_files.put_utf8('pending-merges', '\n'.join(rev_list))
187
434
def get_symlink_target(self, file_id):
188
return os.readlink(self.id2path(file_id))
435
return os.readlink(self.id2abspath(file_id))
190
437
def file_class(self, filename):
191
438
if self.path2id(filename):
265
512
for ff in descend(fp, f_ie.file_id, fap):
268
for f in descend('', inv.root.file_id, self.basedir):
515
for f in descend(u'', inv.root.file_id, self.basedir):
519
def move(self, from_paths, to_name):
522
to_name must exist in the inventory.
524
If to_name exists and is a directory, the files are moved into
525
it, keeping their old names.
527
Note that to_name is only the last component of the new name;
528
this doesn't change the directory.
530
This returns a list of (from_path, to_path) pairs for each
534
## TODO: Option to move IDs only
535
assert not isinstance(from_paths, basestring)
537
to_abs = self.abspath(to_name)
538
if not isdir(to_abs):
539
raise BzrError("destination %r is not a directory" % to_abs)
540
if not self.has_filename(to_name):
541
raise BzrError("destination %r not in working directory" % to_abs)
542
to_dir_id = inv.path2id(to_name)
543
if to_dir_id == None and to_name != '':
544
raise BzrError("destination %r is not a versioned directory" % to_name)
545
to_dir_ie = inv[to_dir_id]
546
if to_dir_ie.kind not in ('directory', 'root_directory'):
547
raise BzrError("destination %r is not a directory" % to_abs)
549
to_idpath = inv.get_idpath(to_dir_id)
552
if not self.has_filename(f):
553
raise BzrError("%r does not exist in working tree" % f)
554
f_id = inv.path2id(f)
556
raise BzrError("%r is not versioned" % f)
557
name_tail = splitpath(f)[-1]
558
dest_path = appendpath(to_name, name_tail)
559
if self.has_filename(dest_path):
560
raise BzrError("destination %r already exists" % dest_path)
561
if f_id in to_idpath:
562
raise BzrError("can't move %r to a subdirectory of itself" % f)
564
# OK, so there's a race here, it's possible that someone will
565
# create a file in this interval and then the rename might be
566
# left half-done. But we should have caught most problems.
567
orig_inv = deepcopy(self.inventory)
570
name_tail = splitpath(f)[-1]
571
dest_path = appendpath(to_name, name_tail)
572
result.append((f, dest_path))
573
inv.rename(inv.path2id(f), to_dir_id, name_tail)
575
rename(self.abspath(f), self.abspath(dest_path))
577
raise BzrError("failed to rename %r to %r: %s" %
578
(f, dest_path, e[1]),
579
["rename rolled back"])
581
# restore the inventory on error
582
self._set_inventory(orig_inv)
584
self._write_inventory(inv)
588
def rename_one(self, from_rel, to_rel):
591
This can change the directory or the filename or both.
594
if not self.has_filename(from_rel):
595
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
596
if self.has_filename(to_rel):
597
raise BzrError("can't rename: new working file %r already exists" % to_rel)
599
file_id = inv.path2id(from_rel)
601
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
604
from_parent = entry.parent_id
605
from_name = entry.name
607
if inv.path2id(to_rel):
608
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
610
to_dir, to_tail = os.path.split(to_rel)
611
to_dir_id = inv.path2id(to_dir)
612
if to_dir_id == None and to_dir != '':
613
raise BzrError("can't determine destination directory id for %r" % to_dir)
615
mutter("rename_one:")
616
mutter(" file_id {%s}" % file_id)
617
mutter(" from_rel %r" % from_rel)
618
mutter(" to_rel %r" % to_rel)
619
mutter(" to_dir %r" % to_dir)
620
mutter(" to_dir_id {%s}" % to_dir_id)
622
inv.rename(file_id, to_dir_id, to_tail)
624
from_abs = self.abspath(from_rel)
625
to_abs = self.abspath(to_rel)
627
rename(from_abs, to_abs)
629
inv.rename(file_id, from_parent, from_name)
630
raise BzrError("failed to rename %r to %r: %s"
631
% (from_abs, to_abs, e[1]),
632
["rename rolled back"])
633
self._write_inventory(inv)
273
636
def unknowns(self):
637
"""Return all unknown files.
639
These are files in the working directory that are not versioned or
640
control files or ignored.
642
>>> from bzrlib.branch import ScratchBranch
643
>>> b = ScratchBranch(files=['foo', 'foo~'])
644
>>> tree = WorkingTree(b.base, b)
645
>>> map(str, tree.unknowns())
648
>>> list(b.unknowns())
650
>>> tree.remove('foo')
651
>>> list(b.unknowns())
274
654
for subp in self.extras():
275
655
if not self.is_ignored(subp):
780
def kind(self, file_id):
781
return file_kind(self.id2abspath(file_id))
784
"""See Branch.lock_read, and WorkingTree.unlock."""
785
return self.branch.lock_read()
787
def lock_write(self):
788
"""See Branch.lock_write, and WorkingTree.unlock."""
789
return self.branch.lock_write()
791
def _basis_inventory_name(self, revision_id):
792
return 'basis-inventory.%s' % revision_id
794
def set_last_revision(self, new_revision, old_revision=None):
795
if old_revision is not None:
797
path = self._basis_inventory_name(old_revision)
798
path = self.branch.control_files._escape(path)
799
self.branch.control_files._transport.delete(path)
803
xml = self.branch.repository.get_inventory_xml(new_revision)
804
path = self._basis_inventory_name(new_revision)
805
self.branch.control_files.put_utf8(path, xml)
806
except WeaveRevisionNotPresent:
809
def read_basis_inventory(self, revision_id):
810
"""Read the cached basis inventory."""
811
path = self._basis_inventory_name(revision_id)
812
return self.branch.control_files.get_utf8(path).read()
815
def read_working_inventory(self):
816
"""Read the working inventory."""
817
# ElementTree does its own conversion from UTF-8, so open in
819
f = self.branch.control_files.get('inventory')
820
return bzrlib.xml5.serializer_v5.read_inventory(f)
823
def remove(self, files, verbose=False):
824
"""Remove nominated files from the working inventory..
826
This does not remove their text. This does not run on XXX on what? RBC
828
TODO: Refuse to remove modified files unless --force is given?
830
TODO: Do something useful with directories.
832
TODO: Should this remove the text or not? Tough call; not
833
removing may be useful and the user can just use use rm, and
834
is the opposite of add. Removing it is consistent with most
835
other tools. Maybe an option.
837
## TODO: Normalize names
838
## TODO: Remove nested loops; better scalability
839
if isinstance(files, basestring):
844
# do this before any modifications
848
# TODO: Perhaps make this just a warning, and continue?
849
# This tends to happen when
850
raise NotVersionedError(path=f)
851
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
853
# having remove it, it must be either ignored or unknown
854
if self.is_ignored(f):
858
show_status(new_status, inv[fid].kind, quotefn(f))
861
self._write_inventory(inv)
864
def revert(self, filenames, old_tree=None, backups=True):
865
from bzrlib.merge import merge_inner
867
old_tree = self.branch.basis_tree()
868
merge_inner(self.branch, old_tree,
869
self, ignore_zero=True,
870
backup_files=backups,
871
interesting_files=filenames)
872
if not len(filenames):
873
self.set_pending_merges([])
876
def set_inventory(self, new_inventory_list):
877
from bzrlib.inventory import (Inventory,
882
inv = Inventory(self.get_root_id())
883
for path, file_id, parent, kind in new_inventory_list:
884
name = os.path.basename(path)
887
# fixme, there should be a factory function inv,add_??
888
if kind == 'directory':
889
inv.add(InventoryDirectory(file_id, name, parent))
891
inv.add(InventoryFile(file_id, name, parent))
892
elif kind == 'symlink':
893
inv.add(InventoryLink(file_id, name, parent))
895
raise BzrError("unknown kind %r" % kind)
896
self._write_inventory(inv)
899
def set_root_id(self, file_id):
900
"""Set the root id for this tree."""
901
inv = self.read_working_inventory()
902
orig_root_id = inv.root.file_id
903
del inv._byid[inv.root.file_id]
904
inv.root.file_id = file_id
905
inv._byid[inv.root.file_id] = inv.root
908
if entry.parent_id in (None, orig_root_id):
909
entry.parent_id = inv.root.file_id
910
self._write_inventory(inv)
913
"""See Branch.unlock.
915
WorkingTree locking just uses the Branch locking facilities.
916
This is current because all working trees have an embedded branch
917
within them. IF in the future, we were to make branch data shareable
918
between multiple working trees, i.e. via shared storage, then we
919
would probably want to lock both the local tree, and the branch.
921
# FIXME: We want to write out the hashcache only when the last lock on
922
# this working copy is released. Peeking at the lock count is a bit
923
# of a nasty hack; probably it's better to have a transaction object,
924
# which can do some finalization when it's either successfully or
925
# unsuccessfully completed. (Denys's original patch did that.)
926
if self._hashcache.needs_write and self.branch.control_files._lock_count==1:
927
self._hashcache.write()
928
return self.branch.unlock()
931
def _write_inventory(self, inv):
932
"""Write inventory as the current inventory."""
933
from cStringIO import StringIO
934
from bzrlib.atomicfile import AtomicFile
936
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
938
f = AtomicFile(self.branch.control_files.controlfilename('inventory'))
944
self._set_inventory(inv)
945
mutter('wrote working inventory')
379
948
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
380
949
def get_conflicted_stem(path):
381
950
for suffix in CONFLICT_SUFFIXES: