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
"""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
17
# TODO: Don't allow WorkingTrees to be constructed for remote branches.
35
19
# FIXME: I don't know if writing out the cache from the destructor is really a
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
20
# good idea, because destructors are considered poor taste in Python, and
21
# it's not predictable when it will be written out.
50
from bzrlib.branch import (Branch,
55
from bzrlib.errors import (BzrCheckError,
58
WeaveRevisionNotPresent,
61
from bzrlib.inventory import InventoryEntry
62
from bzrlib.osutils import (appendpath,
72
from bzrlib.textui import show_status
27
from bzrlib.branch import Branch
29
from bzrlib.osutils import appendpath, file_kind, isdir, splitpath, relpath
30
from bzrlib.errors import BzrCheckError
74
31
from bzrlib.trace import mutter
78
def gen_file_id(name):
79
"""Return new file id.
81
This should probably generate proper UUIDs, but for the moment we
82
cope with just randomness because running uuidgen every time is
85
from binascii import hexlify
92
idx = name.rfind('\\')
96
# make it not a hidden file
97
name = name.lstrip('.')
99
# remove any wierd characters; we don't escape them but rather
101
name = re.sub(r'[^\w.]', '', name)
103
s = hexlify(rand_bytes(8))
104
return '-'.join((name, compact_date(time()), s))
108
"""Return a new tree-root file id."""
109
return gen_file_id('TREE_ROOT')
112
33
class TreeEntry(object):
113
34
"""An entry that implements the minium interface used by commands.
185
105
from bzrlib.hashcache import HashCache
186
106
from bzrlib.trace import note, mutter
187
assert isinstance(basedir, basestring), \
188
"base directory %r is not a string" % basedir
189
108
if branch is None:
190
109
branch = Branch.open(basedir)
191
assert isinstance(branch, Branch), \
192
"branch %r is not a Branch" % branch
110
self._inventory = branch.inventory
111
self.path2id = self._inventory.path2id
193
112
self.branch = branch
194
self.basedir = realpath(basedir)
196
self._set_inventory(self.read_working_inventory())
113
self.basedir = basedir
198
115
# update the whole cache up front and write to disk if anything changed;
199
116
# in the future we might want to do this more selectively
200
# two possible ways offer themselves : in self._unlock, write the cache
201
# if needed, or, when the cache sees a change, append it to the hash
202
# cache file, and have the parser take the most recent entry for a
204
117
hc = self._hashcache = HashCache(basedir)
208
121
if hc.needs_write:
209
122
mutter("write hc")
212
def _set_inventory(self, inv):
213
self._inventory = inv
214
self.path2id = self._inventory.path2id
217
def open_containing(path=None):
218
"""Open an existing working tree which has its root about path.
220
This probes for a working tree at path and searches upwards from there.
222
Basically we keep looking up until we find the control directory or
223
run into /. If there isn't one, raises NotBranchError.
224
TODO: give this a new exception.
225
If there is one, it is returned, along with the unused portion of path.
231
if path.find('://') != -1:
232
raise NotBranchError(path=path)
233
path = os.path.abspath(path)
237
return WorkingTree(path), tail
238
except NotBranchError:
241
tail = os.path.join(os.path.basename(path), tail)
243
tail = os.path.basename(path)
244
path = os.path.dirname(path)
245
# FIXME: top in windows is indicated how ???
246
if path == os.path.sep:
247
# reached the root, whatever that may be
248
raise NotBranchError(path=path)
127
if self._hashcache.needs_write:
128
self._hashcache.write()
250
131
def __iter__(self):
251
132
"""Iterate through file_ids for this tree.
278
162
def get_file_byname(self, filename):
279
163
return file(self.abspath(filename), 'rb')
281
def get_root_id(self):
282
"""Return the id of this trees root"""
283
inv = self.read_working_inventory()
284
return inv.root.file_id
286
165
def _get_store_filename(self, file_id):
287
## XXX: badly named; this is not in the store at all
166
## XXX: badly named; this isn't in the store at all
288
167
return self.abspath(self.id2path(file_id))
291
def commit(self, *args, **kw):
292
from bzrlib.commit import Commit
293
Commit().commit(self.branch, *args, **kw)
294
self._set_inventory(self.read_working_inventory())
296
170
def id2abspath(self, file_id):
297
171
return self.abspath(self.id2path(file_id))
299
174
def has_id(self, file_id):
300
175
# files that have been deleted are excluded
301
176
inv = self._inventory
326
203
mode = os.lstat(self.abspath(path)).st_mode
327
204
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
330
def add(self, files, ids=None):
331
"""Make files versioned.
333
Note that the command line normally calls smart_add instead,
334
which can automatically recurse.
336
This adds the files to the inventory, so that they will be
337
recorded by the next commit.
340
List of paths to add, relative to the base of the tree.
343
If set, use these instead of automatically generated ids.
344
Must be the same length as the list of files, but may
345
contain None for ids that are to be autogenerated.
347
TODO: Perhaps have an option to add the ids even if the files do
350
TODO: Perhaps callback with the ids and paths as they're added.
352
# TODO: Re-adding a file that is removed in the working copy
353
# should probably put it back with the previous ID.
354
if isinstance(files, basestring):
355
assert(ids is None or isinstance(ids, basestring))
361
ids = [None] * len(files)
363
assert(len(ids) == len(files))
365
inv = self.read_working_inventory()
366
for f,file_id in zip(files, ids):
367
if is_control_file(f):
368
raise BzrError("cannot add control file %s" % quotefn(f))
373
raise BzrError("cannot add top-level %r" % f)
375
fullpath = os.path.normpath(self.abspath(f))
378
kind = file_kind(fullpath)
380
# maybe something better?
381
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
383
if not InventoryEntry.versionable_kind(kind):
384
raise BzrError('cannot add: not a versionable file ('
385
'i.e. regular file, symlink or directory): %s' % quotefn(f))
388
file_id = gen_file_id(f)
389
inv.add_path(f, kind=kind, file_id=file_id)
391
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
392
self._write_inventory(inv)
395
def add_pending_merge(self, *revision_ids):
396
# TODO: Perhaps should check at this point that the
397
# history of the revision is actually present?
398
p = self.pending_merges()
400
for rev_id in revision_ids:
406
self.set_pending_merges(p)
408
def pending_merges(self):
409
"""Return a list of pending merges.
411
These are revisions that have been merged into the working
412
directory but not yet committed.
414
cfn = self.branch._rel_controlfilename('pending-merges')
415
if not self.branch._transport.has(cfn):
418
for l in self.branch.controlfile('pending-merges', 'r').readlines():
419
p.append(l.rstrip('\n'))
423
def set_pending_merges(self, rev_list):
424
self.branch.put_controlfile('pending-merges', '\n'.join(rev_list))
426
206
def get_symlink_target(self, file_id):
427
207
return os.readlink(self.id2abspath(file_id))
504
284
for ff in descend(fp, f_ie.file_id, fap):
507
for f in descend(u'', inv.root.file_id, self.basedir):
287
for f in descend('', inv.root.file_id, self.basedir):
511
def move(self, from_paths, to_name):
514
to_name must exist in the inventory.
516
If to_name exists and is a directory, the files are moved into
517
it, keeping their old names.
519
Note that to_name is only the last component of the new name;
520
this doesn't change the directory.
522
This returns a list of (from_path, to_path) pairs for each
526
## TODO: Option to move IDs only
527
assert not isinstance(from_paths, basestring)
529
to_abs = self.abspath(to_name)
530
if not isdir(to_abs):
531
raise BzrError("destination %r is not a directory" % to_abs)
532
if not self.has_filename(to_name):
533
raise BzrError("destination %r not in working directory" % to_abs)
534
to_dir_id = inv.path2id(to_name)
535
if to_dir_id == None and to_name != '':
536
raise BzrError("destination %r is not a versioned directory" % to_name)
537
to_dir_ie = inv[to_dir_id]
538
if to_dir_ie.kind not in ('directory', 'root_directory'):
539
raise BzrError("destination %r is not a directory" % to_abs)
541
to_idpath = inv.get_idpath(to_dir_id)
544
if not self.has_filename(f):
545
raise BzrError("%r does not exist in working tree" % f)
546
f_id = inv.path2id(f)
548
raise BzrError("%r is not versioned" % f)
549
name_tail = splitpath(f)[-1]
550
dest_path = appendpath(to_name, name_tail)
551
if self.has_filename(dest_path):
552
raise BzrError("destination %r already exists" % dest_path)
553
if f_id in to_idpath:
554
raise BzrError("can't move %r to a subdirectory of itself" % f)
556
# OK, so there's a race here, it's possible that someone will
557
# create a file in this interval and then the rename might be
558
# left half-done. But we should have caught most problems.
559
orig_inv = deepcopy(self.inventory)
562
name_tail = splitpath(f)[-1]
563
dest_path = appendpath(to_name, name_tail)
564
result.append((f, dest_path))
565
inv.rename(inv.path2id(f), to_dir_id, name_tail)
567
rename(self.abspath(f), self.abspath(dest_path))
569
raise BzrError("failed to rename %r to %r: %s" %
570
(f, dest_path, e[1]),
571
["rename rolled back"])
573
# restore the inventory on error
574
self._set_inventory(orig_inv)
576
self._write_inventory(inv)
580
def rename_one(self, from_rel, to_rel):
583
This can change the directory or the filename or both.
586
if not self.has_filename(from_rel):
587
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
588
if self.has_filename(to_rel):
589
raise BzrError("can't rename: new working file %r already exists" % to_rel)
591
file_id = inv.path2id(from_rel)
593
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
596
from_parent = entry.parent_id
597
from_name = entry.name
599
if inv.path2id(to_rel):
600
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
602
to_dir, to_tail = os.path.split(to_rel)
603
to_dir_id = inv.path2id(to_dir)
604
if to_dir_id == None and to_dir != '':
605
raise BzrError("can't determine destination directory id for %r" % to_dir)
607
mutter("rename_one:")
608
mutter(" file_id {%s}" % file_id)
609
mutter(" from_rel %r" % from_rel)
610
mutter(" to_rel %r" % to_rel)
611
mutter(" to_dir %r" % to_dir)
612
mutter(" to_dir_id {%s}" % to_dir_id)
614
inv.rename(file_id, to_dir_id, to_tail)
616
from_abs = self.abspath(from_rel)
617
to_abs = self.abspath(to_rel)
619
rename(from_abs, to_abs)
621
inv.rename(file_id, from_parent, from_name)
622
raise BzrError("failed to rename %r to %r: %s"
623
% (from_abs, to_abs, e[1]),
624
["rename rolled back"])
625
self._write_inventory(inv)
628
292
def unknowns(self):
629
"""Return all unknown files.
631
These are files in the working directory that are not versioned or
632
control files or ignored.
634
>>> from bzrlib.branch import ScratchBranch
635
>>> b = ScratchBranch(files=['foo', 'foo~'])
636
>>> tree = WorkingTree(b.base, b)
637
>>> map(str, tree.unknowns())
640
>>> list(b.unknowns())
642
>>> tree.remove('foo')
643
>>> list(b.unknowns())
646
293
for subp in self.extras():
647
294
if not self.is_ignored(subp):
771
398
def kind(self, file_id):
772
399
return file_kind(self.id2abspath(file_id))
775
"""See Branch.lock_read, and WorkingTree.unlock."""
776
return self.branch.lock_read()
778
def lock_write(self):
779
"""See Branch.lock_write, and WorkingTree.unlock."""
780
return self.branch.lock_write()
782
def _basis_inventory_name(self, revision_id):
783
return 'basis-inventory.%s' % revision_id
785
def set_last_revision(self, new_revision, old_revision=None):
788
path = self._basis_inventory_name(old_revision)
789
path = self.branch._rel_controlfilename(path)
790
self.branch._transport.delete(path)
794
xml = self.branch.get_inventory_xml(new_revision)
795
path = self._basis_inventory_name(new_revision)
796
self.branch.put_controlfile(path, xml)
797
except WeaveRevisionNotPresent:
800
def read_basis_inventory(self, revision_id):
801
"""Read the cached basis inventory."""
802
path = self._basis_inventory_name(revision_id)
803
return self.branch.controlfile(path, 'r').read()
806
def read_working_inventory(self):
807
"""Read the working inventory."""
808
# ElementTree does its own conversion from UTF-8, so open in
810
f = self.branch.controlfile('inventory', 'rb')
811
return bzrlib.xml5.serializer_v5.read_inventory(f)
814
def remove(self, files, verbose=False):
815
"""Remove nominated files from the working inventory..
817
This does not remove their text. This does not run on XXX on what? RBC
819
TODO: Refuse to remove modified files unless --force is given?
821
TODO: Do something useful with directories.
823
TODO: Should this remove the text or not? Tough call; not
824
removing may be useful and the user can just use use rm, and
825
is the opposite of add. Removing it is consistent with most
826
other tools. Maybe an option.
828
## TODO: Normalize names
829
## TODO: Remove nested loops; better scalability
830
if isinstance(files, basestring):
835
# do this before any modifications
839
# TODO: Perhaps make this just a warning, and continue?
840
# This tends to happen when
841
raise NotVersionedError(path=f)
842
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
844
# having remove it, it must be either ignored or unknown
845
if self.is_ignored(f):
849
show_status(new_status, inv[fid].kind, quotefn(f))
852
self._write_inventory(inv)
855
def revert(self, filenames, old_tree=None, backups=True):
856
from bzrlib.merge import merge_inner
858
old_tree = self.branch.basis_tree()
859
merge_inner(self.branch, old_tree,
860
self, ignore_zero=True,
861
backup_files=backups,
862
interesting_files=filenames)
863
if not len(filenames):
864
self.set_pending_merges([])
867
def set_inventory(self, new_inventory_list):
868
from bzrlib.inventory import (Inventory,
873
inv = Inventory(self.get_root_id())
874
for path, file_id, parent, kind in new_inventory_list:
875
name = os.path.basename(path)
878
# fixme, there should be a factory function inv,add_??
879
if kind == 'directory':
880
inv.add(InventoryDirectory(file_id, name, parent))
882
inv.add(InventoryFile(file_id, name, parent))
883
elif kind == 'symlink':
884
inv.add(InventoryLink(file_id, name, parent))
886
raise BzrError("unknown kind %r" % kind)
887
self._write_inventory(inv)
890
def set_root_id(self, file_id):
891
"""Set the root id for this tree."""
892
inv = self.read_working_inventory()
893
orig_root_id = inv.root.file_id
894
del inv._byid[inv.root.file_id]
895
inv.root.file_id = file_id
896
inv._byid[inv.root.file_id] = inv.root
899
if entry.parent_id in (None, orig_root_id):
900
entry.parent_id = inv.root.file_id
901
self._write_inventory(inv)
904
"""See Branch.unlock.
906
WorkingTree locking just uses the Branch locking facilities.
907
This is current because all working trees have an embedded branch
908
within them. IF in the future, we were to make branch data shareable
909
between multiple working trees, i.e. via shared storage, then we
910
would probably want to lock both the local tree, and the branch.
912
return self.branch.unlock()
915
def _write_inventory(self, inv):
916
"""Write inventory as the current inventory."""
917
from cStringIO import StringIO
918
from bzrlib.atomicfile import AtomicFile
920
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
922
f = AtomicFile(self.branch.controlfilename('inventory'))
928
self._set_inventory(inv)
929
mutter('wrote working inventory')
932
401
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
933
402
def get_conflicted_stem(path):
934
403
for suffix in CONFLICT_SUFFIXES: