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,
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.osutils import appendpath, file_kind, isdir, splitpath
28
from bzrlib.errors import BzrCheckError
29
74
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
class TreeEntry(object):
113
"""An entry that implements the minium interface used by commands.
115
This needs further inspection, it may be better to have
116
InventoryEntries without ids - though that seems wrong. For now,
117
this is a parallel hierarchy to InventoryEntry, and needs to become
118
one of several things: decorates to that hierarchy, children of, or
120
Another note is that these objects are currently only used when there is
121
no InventoryEntry available - i.e. for unversioned objects.
122
Perhaps they should be UnversionedEntry et al. ? - RBC 20051003
125
def __eq__(self, other):
126
# yes, this us ugly, TODO: best practice __eq__ style.
127
return (isinstance(other, TreeEntry)
128
and other.__class__ == self.__class__)
130
def kind_character(self):
134
class TreeDirectory(TreeEntry):
135
"""See TreeEntry. This is a directory in a working tree."""
137
def __eq__(self, other):
138
return (isinstance(other, TreeDirectory)
139
and other.__class__ == self.__class__)
141
def kind_character(self):
145
class TreeFile(TreeEntry):
146
"""See TreeEntry. This is a regular file in a working tree."""
148
def __eq__(self, other):
149
return (isinstance(other, TreeFile)
150
and other.__class__ == self.__class__)
152
def kind_character(self):
156
class TreeLink(TreeEntry):
157
"""See TreeEntry. This is a symlink in a working tree."""
159
def __eq__(self, other):
160
return (isinstance(other, TreeLink)
161
and other.__class__ == self.__class__)
163
def kind_character(self):
31
167
class WorkingTree(bzrlib.tree.Tree):
32
168
"""Working copy tree.
37
173
It is possible for a `WorkingTree` to have a filename which is
38
174
not listed in the Inventory and vice versa.
40
def __init__(self, basedir, inv):
177
def __init__(self, basedir=u'.', branch=None):
178
"""Construct a WorkingTree for basedir.
180
If the branch is not supplied, it is opened automatically.
181
If the branch is supplied, it must be the branch for this basedir.
182
(branch.base is not cross checked, because for remote branches that
183
would be meaningless).
41
185
from bzrlib.hashcache import HashCache
42
186
from bzrlib.trace import note, mutter
187
assert isinstance(basedir, basestring), \
188
"base directory %r is not a string" % basedir
190
branch = Branch.open(basedir)
191
assert isinstance(branch, Branch), \
192
"branch %r is not a Branch" % branch
194
self.basedir = realpath(basedir)
45
self.basedir = basedir
46
self.path2id = inv.path2id
196
self._set_inventory(self.read_working_inventory())
48
198
# update the whole cache up front and write to disk if anything changed;
49
199
# 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
50
204
hc = self._hashcache = HashCache(basedir)
54
208
if hc.needs_write:
55
209
mutter("write hc")
60
if self._hashcache.needs_write:
61
self._hashcache.write()
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)
64
250
def __iter__(self):
65
251
"""Iterate through file_ids for this tree.
70
256
inv = self._inventory
71
257
for path, ie in inv.iter_entries():
72
if os.path.exists(self.abspath(path)):
258
if bzrlib.osutils.lexists(self.abspath(path)):
76
261
def __repr__(self):
77
262
return "<%s of %s>" % (self.__class__.__name__,
78
263
getattr(self, 'basedir', None))
82
265
def abspath(self, filename):
83
266
return os.path.join(self.basedir, filename)
268
def relpath(self, abspath):
269
"""Return the local path portion from a given absolute path."""
270
return relpath(self.basedir, abspath)
85
272
def has_filename(self, filename):
86
return os.path.exists(self.abspath(filename))
273
return bzrlib.osutils.lexists(self.abspath(filename))
88
275
def get_file(self, file_id):
89
276
return self.get_file_byname(self.id2path(file_id))
91
278
def get_file_byname(self, filename):
92
279
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
94
286
def _get_store_filename(self, file_id):
95
## XXX: badly named; this isn't in the store at all
96
return self.abspath(self.id2path(file_id))
287
## XXX: badly named; this is not in the store at all
288
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
def id2abspath(self, file_id):
297
return self.abspath(self.id2path(file_id))
99
299
def has_id(self, file_id):
100
300
# files that have been deleted are excluded
101
301
inv = self._inventory
102
302
if not inv.has_id(file_id):
104
304
path = inv.id2path(file_id)
105
return os.path.exists(self.abspath(path))
305
return bzrlib.osutils.lexists(self.abspath(path))
307
def has_or_had_id(self, file_id):
308
if file_id == self.inventory.root.file_id:
310
return self.inventory.has_id(file_id)
108
312
__contains__ = has_id
111
314
def get_file_size(self, file_id):
112
# is this still called?
113
raise NotImplementedError()
315
return os.path.getsize(self.id2abspath(file_id))
116
317
def get_file_sha1(self, file_id):
117
318
path = self._inventory.id2path(file_id)
118
319
return self._hashcache.get_sha1(path)
321
def is_executable(self, file_id):
323
return self._inventory[file_id].executable
325
path = self._inventory.id2path(file_id)
326
mode = os.lstat(self.abspath(path)).st_mode
327
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
def get_symlink_target(self, file_id):
427
return os.readlink(self.id2abspath(file_id))
121
429
def file_class(self, filename):
122
430
if self.path2id(filename):
183
504
for ff in descend(fp, f_ie.file_id, fap):
186
for f in descend('', inv.root.file_id, self.basedir):
507
for f in descend(u'', 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)
191
628
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())
192
646
for subp in self.extras():
193
647
if not self.is_ignored(subp):
650
def iter_conflicts(self):
652
for path in (s[0] for s in self.list_files()):
653
stem = get_conflicted_stem(path)
656
if stem not in conflicted:
661
def pull(self, source, overwrite=False):
662
from bzrlib.merge import merge_inner
665
old_revision_history = self.branch.revision_history()
666
count = self.branch.pull(source, overwrite)
667
new_revision_history = self.branch.revision_history()
668
if new_revision_history != old_revision_history:
669
if len(old_revision_history):
670
other_revision = old_revision_history[-1]
672
other_revision = None
673
merge_inner(self.branch,
674
self.branch.basis_tree(),
675
self.branch.revision_tree(other_revision))
197
680
def extras(self):
198
681
"""Yield all unknown files in this WorkingTree.
771
def kind(self, file_id):
772
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
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
933
def get_conflicted_stem(path):
934
for suffix in CONFLICT_SUFFIXES:
935
if path.endswith(suffix):
936
return path[:-len(suffix)]