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 WorkingTree(dir[, branch])
19
32
# 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.
33
# good idea, because destructors are considered poor taste in Python, and it's
34
# not predictable when it will be written out.
36
# TODO: Give the workingtree sole responsibility for the working inventory;
37
# remove the variable and references to it from the branch. This may require
38
# updating the commit code so as to update the inventory within the working
39
# copy, and making sure there's only one WorkingTree for any directory on disk.
40
# At the momenthey may alias the inventory and have old copies of it in memory.
42
from copy import deepcopy
43
from cStringIO import StringIO
50
from bzrlib.atomicfile import AtomicFile
51
from bzrlib.branch import (Branch,
57
from bzrlib.decorators import needs_read_lock, needs_write_lock
58
from bzrlib.errors import (BzrCheckError,
61
WeaveRevisionNotPresent,
65
from bzrlib.inventory import InventoryEntry
66
from bzrlib.lockable_files import LockableFiles
67
from bzrlib.osutils import (appendpath,
82
from bzrlib.symbol_versioning import *
83
from bzrlib.textui import show_status
26
from errors import BzrCheckError
27
from trace import mutter
85
from bzrlib.trace import mutter
86
from bzrlib.transport import get_transport
90
def gen_file_id(name):
91
"""Return new file id.
93
This should probably generate proper UUIDs, but for the moment we
94
cope with just randomness because running uuidgen every time is
97
from binascii import hexlify
101
idx = name.rfind('/')
103
name = name[idx+1 : ]
104
idx = name.rfind('\\')
106
name = name[idx+1 : ]
108
# make it not a hidden file
109
name = name.lstrip('.')
111
# remove any wierd characters; we don't escape them but rather
113
name = re.sub(r'[^\w.]', '', name)
115
s = hexlify(rand_bytes(8))
116
return '-'.join((name, compact_date(time()), s))
120
"""Return a new tree-root file id."""
121
return gen_file_id('TREE_ROOT')
124
class TreeEntry(object):
125
"""An entry that implements the minium interface used by commands.
127
This needs further inspection, it may be better to have
128
InventoryEntries without ids - though that seems wrong. For now,
129
this is a parallel hierarchy to InventoryEntry, and needs to become
130
one of several things: decorates to that hierarchy, children of, or
132
Another note is that these objects are currently only used when there is
133
no InventoryEntry available - i.e. for unversioned objects.
134
Perhaps they should be UnversionedEntry et al. ? - RBC 20051003
137
def __eq__(self, other):
138
# yes, this us ugly, TODO: best practice __eq__ style.
139
return (isinstance(other, TreeEntry)
140
and other.__class__ == self.__class__)
142
def kind_character(self):
146
class TreeDirectory(TreeEntry):
147
"""See TreeEntry. This is a directory in a working tree."""
149
def __eq__(self, other):
150
return (isinstance(other, TreeDirectory)
151
and other.__class__ == self.__class__)
153
def kind_character(self):
157
class TreeFile(TreeEntry):
158
"""See TreeEntry. This is a regular file in a working tree."""
160
def __eq__(self, other):
161
return (isinstance(other, TreeFile)
162
and other.__class__ == self.__class__)
164
def kind_character(self):
168
class TreeLink(TreeEntry):
169
"""See TreeEntry. This is a symlink in a working tree."""
171
def __eq__(self, other):
172
return (isinstance(other, TreeLink)
173
and other.__class__ == self.__class__)
175
def kind_character(self):
29
179
class WorkingTree(bzrlib.tree.Tree):
30
180
"""Working copy tree.
35
185
It is possible for a `WorkingTree` to have a filename which is
36
186
not listed in the Inventory and vice versa.
38
def __init__(self, basedir, inv):
189
def __init__(self, basedir='.', branch=None, _inventory=None, _control_files=None):
190
"""Construct a WorkingTree for basedir.
192
If the branch is not supplied, it is opened automatically.
193
If the branch is supplied, it must be the branch for this basedir.
194
(branch.base is not cross checked, because for remote branches that
195
would be meaningless).
39
197
from bzrlib.hashcache import HashCache
40
198
from bzrlib.trace import note, mutter
43
self.basedir = basedir
44
self.path2id = inv.path2id
199
assert isinstance(basedir, basestring), \
200
"base directory %r is not a string" % basedir
201
basedir = safe_unicode(basedir)
202
mutter("openeing working tree %r", basedir)
204
branch = Branch.open(basedir)
205
assert isinstance(branch, Branch), \
206
"branch %r is not a Branch" % branch
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')
46
222
# update the whole cache up front and write to disk if anything changed;
47
223
# in the future we might want to do this more selectively
224
# two possible ways offer themselves : in self._unlock, write the cache
225
# if needed, or, when the cache sees a change, append it to the hash
226
# cache file, and have the parser take the most recent entry for a
48
228
hc = self._hashcache = HashCache(basedir)
52
232
if hc.needs_write:
53
233
mutter("write hc")
58
if self._hashcache.needs_write:
59
self._hashcache.write()
236
if _inventory is None:
237
self._set_inventory(self.read_working_inventory())
239
self._set_inventory(_inventory)
241
def _set_inventory(self, inv):
242
self._inventory = inv
243
self.path2id = self._inventory.path2id
246
def open_containing(path=None):
247
"""Open an existing working tree which has its root about path.
249
This probes for a working tree at path and searches upwards from there.
251
Basically we keep looking up until we find the control directory or
252
run into /. If there isn't one, raises NotBranchError.
253
TODO: give this a new exception.
254
If there is one, it is returned, along with the unused portion of path.
260
if path.find('://') != -1:
261
raise NotBranchError(path=path)
267
return WorkingTree(path), tail
268
except NotBranchError:
271
tail = pathjoin(os.path.basename(path), tail)
273
tail = os.path.basename(path)
275
path = os.path.dirname(path)
277
# reached the root, whatever that may be
278
raise NotBranchError(path=orig_path)
62
280
def __iter__(self):
63
281
"""Iterate through file_ids for this tree.
68
286
inv = self._inventory
69
287
for path, ie in inv.iter_entries():
70
if os.path.exists(self.abspath(path)):
288
if bzrlib.osutils.lexists(self.abspath(path)):
74
291
def __repr__(self):
75
292
return "<%s of %s>" % (self.__class__.__name__,
76
293
getattr(self, 'basedir', None))
80
295
def abspath(self, filename):
81
return os.path.join(self.basedir, filename)
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)
346
def relpath(self, abs):
347
"""Return the local path portion from a given absolute path."""
348
return relpath(self.basedir, abs)
83
350
def has_filename(self, filename):
84
return os.path.exists(self.abspath(filename))
351
return bzrlib.osutils.lexists(self.abspath(filename))
86
353
def get_file(self, file_id):
87
354
return self.get_file_byname(self.id2path(file_id))
89
356
def get_file_byname(self, filename):
90
357
return file(self.abspath(filename), 'rb')
359
def get_root_id(self):
360
"""Return the id of this trees root"""
361
inv = self.read_working_inventory()
362
return inv.root.file_id
92
364
def _get_store_filename(self, file_id):
93
## XXX: badly named; this isn't in the store at all
94
return self.abspath(self.id2path(file_id))
365
## XXX: badly named; this is not in the store at all
366
return self.abspath(self.id2path(file_id))
369
def commit(self, *args, **kwargs):
370
from bzrlib.commit import Commit
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)
376
self._set_inventory(self.read_working_inventory())
378
def id2abspath(self, file_id):
379
return self.abspath(self.id2path(file_id))
97
381
def has_id(self, file_id):
98
382
# files that have been deleted are excluded
99
383
inv = self._inventory
100
384
if not inv.has_id(file_id):
102
386
path = inv.id2path(file_id)
103
return os.path.exists(self.abspath(path))
387
return bzrlib.osutils.lexists(self.abspath(path))
389
def has_or_had_id(self, file_id):
390
if file_id == self.inventory.root.file_id:
392
return self.inventory.has_id(file_id)
106
394
__contains__ = has_id
109
396
def get_file_size(self, file_id):
110
# is this still called?
111
raise NotImplementedError()
397
return os.path.getsize(self.id2abspath(file_id))
114
400
def get_file_sha1(self, file_id):
115
401
path = self._inventory.id2path(file_id)
116
402
return self._hashcache.get_sha1(path)
404
def is_executable(self, file_id):
406
return self._inventory[file_id].executable
408
path = self._inventory.id2path(file_id)
409
mode = os.lstat(self.abspath(path)).st_mode
410
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
413
def add(self, files, ids=None):
414
"""Make files versioned.
416
Note that the command line normally calls smart_add instead,
417
which can automatically recurse.
419
This adds the files to the inventory, so that they will be
420
recorded by the next commit.
423
List of paths to add, relative to the base of the tree.
426
If set, use these instead of automatically generated ids.
427
Must be the same length as the list of files, but may
428
contain None for ids that are to be autogenerated.
430
TODO: Perhaps have an option to add the ids even if the files do
433
TODO: Perhaps callback with the ids and paths as they're added.
435
# TODO: Re-adding a file that is removed in the working copy
436
# should probably put it back with the previous ID.
437
if isinstance(files, basestring):
438
assert(ids is None or isinstance(ids, basestring))
444
ids = [None] * len(files)
446
assert(len(ids) == len(files))
448
inv = self.read_working_inventory()
449
for f,file_id in zip(files, ids):
450
if is_control_file(f):
451
raise BzrError("cannot add control file %s" % quotefn(f))
456
raise BzrError("cannot add top-level %r" % f)
458
fullpath = normpath(self.abspath(f))
461
kind = file_kind(fullpath)
463
# maybe something better?
464
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
466
if not InventoryEntry.versionable_kind(kind):
467
raise BzrError('cannot add: not a versionable file ('
468
'i.e. regular file, symlink or directory): %s' % quotefn(f))
471
file_id = gen_file_id(f)
472
inv.add_path(f, kind=kind, file_id=file_id)
474
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
475
self._write_inventory(inv)
478
def add_pending_merge(self, *revision_ids):
479
# TODO: Perhaps should check at this point that the
480
# history of the revision is actually present?
481
p = self.pending_merges()
483
for rev_id in revision_ids:
489
self.set_pending_merges(p)
492
def pending_merges(self):
493
"""Return a list of pending merges.
495
These are revisions that have been merged into the working
496
directory but not yet committed.
499
merges_file = self._control_files.get_utf8('pending-merges')
501
if e.errno != errno.ENOENT:
505
for l in merges_file.readlines():
506
p.append(l.rstrip('\n'))
510
def set_pending_merges(self, rev_list):
511
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
513
def get_symlink_target(self, file_id):
514
return os.readlink(self.id2abspath(file_id))
119
516
def file_class(self, filename):
120
517
if self.path2id(filename):
184
591
for ff in descend(fp, f_ie.file_id, fap):
187
for f in descend('', inv.root.file_id, self.basedir):
594
for f in descend(u'', inv.root.file_id, self.basedir):
598
def move(self, from_paths, to_name):
601
to_name must exist in the inventory.
603
If to_name exists and is a directory, the files are moved into
604
it, keeping their old names.
606
Note that to_name is only the last component of the new name;
607
this doesn't change the directory.
609
This returns a list of (from_path, to_path) pairs for each
613
## TODO: Option to move IDs only
614
assert not isinstance(from_paths, basestring)
616
to_abs = self.abspath(to_name)
617
if not isdir(to_abs):
618
raise BzrError("destination %r is not a directory" % to_abs)
619
if not self.has_filename(to_name):
620
raise BzrError("destination %r not in working directory" % to_abs)
621
to_dir_id = inv.path2id(to_name)
622
if to_dir_id == None and to_name != '':
623
raise BzrError("destination %r is not a versioned directory" % to_name)
624
to_dir_ie = inv[to_dir_id]
625
if to_dir_ie.kind not in ('directory', 'root_directory'):
626
raise BzrError("destination %r is not a directory" % to_abs)
628
to_idpath = inv.get_idpath(to_dir_id)
631
if not self.has_filename(f):
632
raise BzrError("%r does not exist in working tree" % f)
633
f_id = inv.path2id(f)
635
raise BzrError("%r is not versioned" % f)
636
name_tail = splitpath(f)[-1]
637
dest_path = appendpath(to_name, name_tail)
638
if self.has_filename(dest_path):
639
raise BzrError("destination %r already exists" % dest_path)
640
if f_id in to_idpath:
641
raise BzrError("can't move %r to a subdirectory of itself" % f)
643
# OK, so there's a race here, it's possible that someone will
644
# create a file in this interval and then the rename might be
645
# left half-done. But we should have caught most problems.
646
orig_inv = deepcopy(self.inventory)
649
name_tail = splitpath(f)[-1]
650
dest_path = appendpath(to_name, name_tail)
651
result.append((f, dest_path))
652
inv.rename(inv.path2id(f), to_dir_id, name_tail)
654
rename(self.abspath(f), self.abspath(dest_path))
656
raise BzrError("failed to rename %r to %r: %s" %
657
(f, dest_path, e[1]),
658
["rename rolled back"])
660
# restore the inventory on error
661
self._set_inventory(orig_inv)
663
self._write_inventory(inv)
667
def rename_one(self, from_rel, to_rel):
670
This can change the directory or the filename or both.
673
if not self.has_filename(from_rel):
674
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
675
if self.has_filename(to_rel):
676
raise BzrError("can't rename: new working file %r already exists" % to_rel)
678
file_id = inv.path2id(from_rel)
680
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
683
from_parent = entry.parent_id
684
from_name = entry.name
686
if inv.path2id(to_rel):
687
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
689
to_dir, to_tail = os.path.split(to_rel)
690
to_dir_id = inv.path2id(to_dir)
691
if to_dir_id == None and to_dir != '':
692
raise BzrError("can't determine destination directory id for %r" % to_dir)
694
mutter("rename_one:")
695
mutter(" file_id {%s}" % file_id)
696
mutter(" from_rel %r" % from_rel)
697
mutter(" to_rel %r" % to_rel)
698
mutter(" to_dir %r" % to_dir)
699
mutter(" to_dir_id {%s}" % to_dir_id)
701
inv.rename(file_id, to_dir_id, to_tail)
703
from_abs = self.abspath(from_rel)
704
to_abs = self.abspath(to_rel)
706
rename(from_abs, to_abs)
708
inv.rename(file_id, from_parent, from_name)
709
raise BzrError("failed to rename %r to %r: %s"
710
% (from_abs, to_abs, e[1]),
711
["rename rolled back"])
712
self._write_inventory(inv)
192
715
def unknowns(self):
716
"""Return all unknown files.
718
These are files in the working directory that are not versioned or
719
control files or ignored.
721
>>> from bzrlib.branch import ScratchBranch
722
>>> b = ScratchBranch(files=['foo', 'foo~'])
723
>>> tree = WorkingTree(b.base, b)
724
>>> map(str, tree.unknowns())
727
>>> list(b.unknowns())
729
>>> tree.remove('foo')
730
>>> list(b.unknowns())
193
733
for subp in self.extras():
194
734
if not self.is_ignored(subp):
737
def iter_conflicts(self):
739
for path in (s[0] for s in self.list_files()):
740
stem = get_conflicted_stem(path)
743
if stem not in conflicted:
748
def pull(self, source, overwrite=False):
749
from bzrlib.merge import merge_inner
752
old_revision_history = self.branch.revision_history()
753
count = self.branch.pull(source, overwrite)
754
new_revision_history = self.branch.revision_history()
755
if new_revision_history != old_revision_history:
756
if len(old_revision_history):
757
other_revision = old_revision_history[-1]
759
other_revision = None
760
repository = self.branch.repository
761
merge_inner(self.branch,
762
self.branch.basis_tree(),
763
repository.revision_tree(other_revision),
765
self.set_last_revision(self.branch.last_revision())
198
770
def extras(self):
199
771
"""Yield all unknown files in this WorkingTree.
b'\\ No newline at end of file'
861
def kind(self, file_id):
862
return file_kind(self.id2abspath(file_id))
865
"""See Branch.lock_read, and WorkingTree.unlock."""
866
return self.branch.lock_read()
868
def lock_write(self):
869
"""See Branch.lock_write, and WorkingTree.unlock."""
870
return self.branch.lock_write()
872
def _basis_inventory_name(self, revision_id):
873
return 'basis-inventory.%s' % revision_id
875
def set_last_revision(self, new_revision, old_revision=None):
876
if old_revision is not None:
878
path = self._basis_inventory_name(old_revision)
879
path = self.branch.control_files._escape(path)
880
self.branch.control_files._transport.delete(path)
884
xml = self.branch.repository.get_inventory_xml(new_revision)
885
path = self._basis_inventory_name(new_revision)
886
self.branch.control_files.put_utf8(path, xml)
887
except WeaveRevisionNotPresent:
890
def read_basis_inventory(self, revision_id):
891
"""Read the cached basis inventory."""
892
path = self._basis_inventory_name(revision_id)
893
return self.branch.control_files.get_utf8(path).read()
896
def read_working_inventory(self):
897
"""Read the working inventory."""
898
# ElementTree does its own conversion from UTF-8, so open in
900
return bzrlib.xml5.serializer_v5.read_inventory(
901
self._control_files.get('inventory'))
904
def remove(self, files, verbose=False):
905
"""Remove nominated files from the working inventory..
907
This does not remove their text. This does not run on XXX on what? RBC
909
TODO: Refuse to remove modified files unless --force is given?
911
TODO: Do something useful with directories.
913
TODO: Should this remove the text or not? Tough call; not
914
removing may be useful and the user can just use use rm, and
915
is the opposite of add. Removing it is consistent with most
916
other tools. Maybe an option.
918
## TODO: Normalize names
919
## TODO: Remove nested loops; better scalability
920
if isinstance(files, basestring):
925
# do this before any modifications
929
# TODO: Perhaps make this just a warning, and continue?
930
# This tends to happen when
931
raise NotVersionedError(path=f)
932
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
934
# having remove it, it must be either ignored or unknown
935
if self.is_ignored(f):
939
show_status(new_status, inv[fid].kind, quotefn(f))
942
self._write_inventory(inv)
945
def revert(self, filenames, old_tree=None, backups=True):
946
from bzrlib.merge import merge_inner
948
old_tree = self.branch.basis_tree()
949
merge_inner(self.branch, old_tree,
950
self, ignore_zero=True,
951
backup_files=backups,
952
interesting_files=filenames,
954
if not len(filenames):
955
self.set_pending_merges([])
958
def set_inventory(self, new_inventory_list):
959
from bzrlib.inventory import (Inventory,
964
inv = Inventory(self.get_root_id())
965
for path, file_id, parent, kind in new_inventory_list:
966
name = os.path.basename(path)
969
# fixme, there should be a factory function inv,add_??
970
if kind == 'directory':
971
inv.add(InventoryDirectory(file_id, name, parent))
973
inv.add(InventoryFile(file_id, name, parent))
974
elif kind == 'symlink':
975
inv.add(InventoryLink(file_id, name, parent))
977
raise BzrError("unknown kind %r" % kind)
978
self._write_inventory(inv)
981
def set_root_id(self, file_id):
982
"""Set the root id for this tree."""
983
inv = self.read_working_inventory()
984
orig_root_id = inv.root.file_id
985
del inv._byid[inv.root.file_id]
986
inv.root.file_id = file_id
987
inv._byid[inv.root.file_id] = inv.root
990
if entry.parent_id in (None, orig_root_id):
991
entry.parent_id = inv.root.file_id
992
self._write_inventory(inv)
995
"""See Branch.unlock.
997
WorkingTree locking just uses the Branch locking facilities.
998
This is current because all working trees have an embedded branch
999
within them. IF in the future, we were to make branch data shareable
1000
between multiple working trees, i.e. via shared storage, then we
1001
would probably want to lock both the local tree, and the branch.
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:
1009
self._hashcache.write()
1010
return self.branch.unlock()
1013
def _write_inventory(self, inv):
1014
"""Write inventory as the current inventory."""
1016
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1018
self._control_files.put('inventory', sio)
1019
self._set_inventory(inv)
1020
mutter('wrote working inventory')
1023
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1024
def get_conflicted_stem(path):
1025
for suffix in CONFLICT_SUFFIXES:
1026
if path.endswith(suffix):
1027
return path[:-len(suffix)]