1
# Copyright (C) 2005 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
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 WorkingTree(dir[, branch])
32
# FIXME: I don't know if writing out the cache from the destructor is really a
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,
54
import bzrlib.bzrdir as bzrdir
55
from bzrlib.decorators import needs_read_lock, needs_write_lock
56
import bzrlib.errors as errors
57
from bzrlib.errors import (BzrCheckError,
60
WeaveRevisionNotPresent,
64
from bzrlib.inventory import InventoryEntry
65
from bzrlib.lockable_files import LockableFiles
66
from bzrlib.osutils import (appendpath,
81
from bzrlib.symbol_versioning import *
82
from bzrlib.textui import show_status
84
from bzrlib.trace import mutter
85
from bzrlib.transport import get_transport
89
def gen_file_id(name):
90
"""Return new file id.
92
This should probably generate proper UUIDs, but for the moment we
93
cope with just randomness because running uuidgen every time is
96
from binascii import hexlify
100
idx = name.rfind('/')
102
name = name[idx+1 : ]
103
idx = name.rfind('\\')
105
name = name[idx+1 : ]
107
# make it not a hidden file
108
name = name.lstrip('.')
110
# remove any wierd characters; we don't escape them but rather
112
name = re.sub(r'[^\w.]', '', name)
114
s = hexlify(rand_bytes(8))
115
return '-'.join((name, compact_date(time()), s))
119
"""Return a new tree-root file id."""
120
return gen_file_id('TREE_ROOT')
123
class TreeEntry(object):
124
"""An entry that implements the minium interface used by commands.
126
This needs further inspection, it may be better to have
127
InventoryEntries without ids - though that seems wrong. For now,
128
this is a parallel hierarchy to InventoryEntry, and needs to become
129
one of several things: decorates to that hierarchy, children of, or
131
Another note is that these objects are currently only used when there is
132
no InventoryEntry available - i.e. for unversioned objects.
133
Perhaps they should be UnversionedEntry et al. ? - RBC 20051003
136
def __eq__(self, other):
137
# yes, this us ugly, TODO: best practice __eq__ style.
138
return (isinstance(other, TreeEntry)
139
and other.__class__ == self.__class__)
141
def kind_character(self):
145
class TreeDirectory(TreeEntry):
146
"""See TreeEntry. This is a directory in a working tree."""
148
def __eq__(self, other):
149
return (isinstance(other, TreeDirectory)
150
and other.__class__ == self.__class__)
152
def kind_character(self):
156
class TreeFile(TreeEntry):
157
"""See TreeEntry. This is a regular file in a working tree."""
159
def __eq__(self, other):
160
return (isinstance(other, TreeFile)
161
and other.__class__ == self.__class__)
163
def kind_character(self):
167
class TreeLink(TreeEntry):
168
"""See TreeEntry. This is a symlink in a working tree."""
170
def __eq__(self, other):
171
return (isinstance(other, TreeLink)
172
and other.__class__ == self.__class__)
174
def kind_character(self):
178
class WorkingTree(bzrlib.tree.Tree):
179
"""Working copy tree.
181
The inventory is held in the `Branch` working-inventory, and the
182
files are in a directory on disk.
184
It is possible for a `WorkingTree` to have a filename which is
185
not listed in the Inventory and vice versa.
188
def __init__(self, basedir='.', branch=None, _inventory=None, _control_files=None):
189
"""Construct a WorkingTree for basedir.
191
If the branch is not supplied, it is opened automatically.
192
If the branch is supplied, it must be the branch for this basedir.
193
(branch.base is not cross checked, because for remote branches that
194
would be meaningless).
196
from bzrlib.hashcache import HashCache
197
from bzrlib.trace import note, mutter
198
assert isinstance(basedir, basestring), \
199
"base directory %r is not a string" % basedir
200
basedir = safe_unicode(basedir)
201
mutter("openeing working tree %r", basedir)
203
branch = Branch.open(basedir)
204
assert isinstance(branch, Branch), \
205
"branch %r is not a Branch" % branch
207
self.basedir = realpath(basedir)
208
# if branch is at our basedir and is a format 6 or less
209
if (isinstance(self.branch._branch_format,
211
# might be able to share control object
212
and self.branch.base.split('/')[-2] == self.basedir.split('/')[-1]):
213
self._control_files = self.branch.control_files
214
elif _control_files is not None:
215
assert False, "not done yet"
216
# self._control_files = _control_files
218
self._control_files = LockableFiles(
219
get_transport(self.basedir).clone(bzrlib.BZRDIR), 'branch-lock')
221
# update the whole cache up front and write to disk if anything changed;
222
# in the future we might want to do this more selectively
223
# two possible ways offer themselves : in self._unlock, write the cache
224
# if needed, or, when the cache sees a change, append it to the hash
225
# cache file, and have the parser take the most recent entry for a
227
hc = self._hashcache = HashCache(basedir)
235
if _inventory is None:
236
self._set_inventory(self.read_working_inventory())
238
self._set_inventory(_inventory)
240
def _set_inventory(self, inv):
241
self._inventory = inv
242
self.path2id = self._inventory.path2id
245
def open_containing(path=None):
246
"""Open an existing working tree which has its root about path.
248
This probes for a working tree at path and searches upwards from there.
250
Basically we keep looking up until we find the control directory or
251
run into /. If there isn't one, raises NotBranchError.
252
TODO: give this a new exception.
253
If there is one, it is returned, along with the unused portion of path.
259
if path.find('://') != -1:
260
raise NotBranchError(path=path)
266
return WorkingTree(path), tail
267
except NotBranchError:
270
tail = pathjoin(os.path.basename(path), tail)
272
tail = os.path.basename(path)
274
path = os.path.dirname(path)
276
# reached the root, whatever that may be
277
raise NotBranchError(path=orig_path)
280
"""Iterate through file_ids for this tree.
282
file_ids are in a WorkingTree if they are in the working inventory
283
and the working file exists.
285
inv = self._inventory
286
for path, ie in inv.iter_entries():
287
if bzrlib.osutils.lexists(self.abspath(path)):
291
return "<%s of %s>" % (self.__class__.__name__,
292
getattr(self, 'basedir', None))
294
def abspath(self, filename):
295
return pathjoin(self.basedir, filename)
297
def basis_tree(self):
298
"""Return RevisionTree for the current last revision."""
299
revision_id = self.last_revision()
300
if revision_id is not None:
302
xml = self.read_basis_inventory(revision_id)
303
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
304
return bzrlib.tree.RevisionTree(self.branch.repository, inv,
308
return self.branch.repository.revision_tree(revision_id)
311
def create(branch, directory):
312
"""Create a workingtree for branch at directory.
314
If existing_directory already exists it must have a .bzr directory.
315
If it does not exist, it will be created.
317
This returns a new WorkingTree object for the new checkout.
319
TODO FIXME RBC 20060124 when we have checkout formats in place this
320
should accept an optional revisionid to checkout [and reject this if
321
checking out into the same dir as a pre-checkout-aware branch format.]
326
if e.errno != errno.EEXIST:
329
os.mkdir(pathjoin(directory, '.bzr'))
331
if e.errno != errno.EEXIST:
333
revision_tree = branch.repository.revision_tree(branch.last_revision())
334
inv = revision_tree.inventory
335
wt = WorkingTree(directory, branch, inv)
336
wt._write_inventory(inv)
337
wt.set_root_id(revision_tree.inventory.root.file_id)
338
if branch.last_revision() is not None:
339
wt.set_last_revision(branch.last_revision())
340
wt.set_pending_merges([])
345
def create_standalone(directory):
346
"""Create a checkout and a branch and a repo at directory.
348
Directory must exist and be empty.
350
directory = safe_unicode(directory)
351
b = bzrdir.BzrDir.create_branch_and_repo(directory)
352
return WorkingTree.create(b, directory)
354
def relpath(self, abs):
355
"""Return the local path portion from a given absolute path."""
356
return relpath(self.basedir, abs)
358
def has_filename(self, filename):
359
return bzrlib.osutils.lexists(self.abspath(filename))
361
def get_file(self, file_id):
362
return self.get_file_byname(self.id2path(file_id))
364
def get_file_byname(self, filename):
365
return file(self.abspath(filename), 'rb')
367
def get_root_id(self):
368
"""Return the id of this trees root"""
369
inv = self.read_working_inventory()
370
return inv.root.file_id
372
def _get_store_filename(self, file_id):
373
## XXX: badly named; this is not in the store at all
374
return self.abspath(self.id2path(file_id))
377
def clone(self, to_directory, revision=None):
378
"""Copy this working tree to a new directory.
380
Currently this will make a new standalone branch at to_directory,
381
but it is planned to change this to use the same branch style that this
382
current tree uses (standalone if standalone, repository if repository)
383
- so that this really is a clone. FIXME RBC 20060127 do this.
384
FIXME MORE RBC 20060127 failed to reach consensus on this in #bzr.
386
If you want a standalone branch, please use branch.clone(to_directory)
387
followed by WorkingTree.create(cloned_branch, to_directory) which is
388
the supported api to produce that.
391
If not None, the cloned tree will have its last revision set to
392
revision, and if a branch is being copied it will be informed
393
of the revision to result in.
395
to_directory -- The destination directory: Must not exist.
397
to_directory = safe_unicode(to_directory)
398
os.mkdir(to_directory)
399
# FIXME here is where the decision to clone the branch should happen.
401
revision = self.last_revision()
402
cloned_branch = self.branch.clone(to_directory, revision)
403
return WorkingTree.create(cloned_branch, to_directory)
406
def commit(self, *args, **kwargs):
407
from bzrlib.commit import Commit
408
# args for wt.commit start at message from the Commit.commit method,
409
# but with branch a kwarg now, passing in args as is results in the
410
#message being used for the branch
411
args = (DEPRECATED_PARAMETER, ) + args
412
Commit().commit(working_tree=self, *args, **kwargs)
413
self._set_inventory(self.read_working_inventory())
415
def id2abspath(self, file_id):
416
return self.abspath(self.id2path(file_id))
418
def has_id(self, file_id):
419
# files that have been deleted are excluded
420
inv = self._inventory
421
if not inv.has_id(file_id):
423
path = inv.id2path(file_id)
424
return bzrlib.osutils.lexists(self.abspath(path))
426
def has_or_had_id(self, file_id):
427
if file_id == self.inventory.root.file_id:
429
return self.inventory.has_id(file_id)
431
__contains__ = has_id
433
def get_file_size(self, file_id):
434
return os.path.getsize(self.id2abspath(file_id))
437
def get_file_sha1(self, file_id):
438
path = self._inventory.id2path(file_id)
439
return self._hashcache.get_sha1(path)
441
def is_executable(self, file_id):
443
return self._inventory[file_id].executable
445
path = self._inventory.id2path(file_id)
446
mode = os.lstat(self.abspath(path)).st_mode
447
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
450
def add(self, files, ids=None):
451
"""Make files versioned.
453
Note that the command line normally calls smart_add instead,
454
which can automatically recurse.
456
This adds the files to the inventory, so that they will be
457
recorded by the next commit.
460
List of paths to add, relative to the base of the tree.
463
If set, use these instead of automatically generated ids.
464
Must be the same length as the list of files, but may
465
contain None for ids that are to be autogenerated.
467
TODO: Perhaps have an option to add the ids even if the files do
470
TODO: Perhaps callback with the ids and paths as they're added.
472
# TODO: Re-adding a file that is removed in the working copy
473
# should probably put it back with the previous ID.
474
if isinstance(files, basestring):
475
assert(ids is None or isinstance(ids, basestring))
481
ids = [None] * len(files)
483
assert(len(ids) == len(files))
485
inv = self.read_working_inventory()
486
for f,file_id in zip(files, ids):
487
if is_control_file(f):
488
raise BzrError("cannot add control file %s" % quotefn(f))
493
raise BzrError("cannot add top-level %r" % f)
495
fullpath = normpath(self.abspath(f))
498
kind = file_kind(fullpath)
500
if e.errno == errno.ENOENT:
501
raise NoSuchFile(fullpath)
502
# maybe something better?
503
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
505
if not InventoryEntry.versionable_kind(kind):
506
raise BzrError('cannot add: not a versionable file ('
507
'i.e. regular file, symlink or directory): %s' % quotefn(f))
510
file_id = gen_file_id(f)
511
inv.add_path(f, kind=kind, file_id=file_id)
513
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
514
self._write_inventory(inv)
517
def add_pending_merge(self, *revision_ids):
518
# TODO: Perhaps should check at this point that the
519
# history of the revision is actually present?
520
p = self.pending_merges()
522
for rev_id in revision_ids:
528
self.set_pending_merges(p)
531
def pending_merges(self):
532
"""Return a list of pending merges.
534
These are revisions that have been merged into the working
535
directory but not yet committed.
538
merges_file = self._control_files.get_utf8('pending-merges')
540
if e.errno != errno.ENOENT:
544
for l in merges_file.readlines():
545
p.append(l.rstrip('\n'))
549
def set_pending_merges(self, rev_list):
550
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
552
def get_symlink_target(self, file_id):
553
return os.readlink(self.id2abspath(file_id))
555
def file_class(self, filename):
556
if self.path2id(filename):
558
elif self.is_ignored(filename):
564
def list_files(self):
565
"""Recursively list all files as (path, class, kind, id).
567
Lists, but does not descend into unversioned directories.
569
This does not include files that have been deleted in this
572
Skips the control directory.
574
inv = self._inventory
576
def descend(from_dir_relpath, from_dir_id, dp):
580
## TODO: If we find a subdirectory with its own .bzr
581
## directory, then that is a separate tree and we
582
## should exclude it.
583
if bzrlib.BZRDIR == f:
587
fp = appendpath(from_dir_relpath, f)
590
fap = appendpath(dp, f)
592
f_ie = inv.get_child(from_dir_id, f)
595
elif self.is_ignored(fp):
604
raise BzrCheckError("file %r entered as kind %r id %r, "
606
% (fap, f_ie.kind, f_ie.file_id, fk))
608
# make a last minute entry
612
if fk == 'directory':
613
entry = TreeDirectory()
616
elif fk == 'symlink':
621
yield fp, c, fk, (f_ie and f_ie.file_id), entry
623
if fk != 'directory':
627
# don't descend unversioned directories
630
for ff in descend(fp, f_ie.file_id, fap):
633
for f in descend(u'', inv.root.file_id, self.basedir):
637
def move(self, from_paths, to_name):
640
to_name must exist in the inventory.
642
If to_name exists and is a directory, the files are moved into
643
it, keeping their old names.
645
Note that to_name is only the last component of the new name;
646
this doesn't change the directory.
648
This returns a list of (from_path, to_path) pairs for each
652
## TODO: Option to move IDs only
653
assert not isinstance(from_paths, basestring)
655
to_abs = self.abspath(to_name)
656
if not isdir(to_abs):
657
raise BzrError("destination %r is not a directory" % to_abs)
658
if not self.has_filename(to_name):
659
raise BzrError("destination %r not in working directory" % to_abs)
660
to_dir_id = inv.path2id(to_name)
661
if to_dir_id == None and to_name != '':
662
raise BzrError("destination %r is not a versioned directory" % to_name)
663
to_dir_ie = inv[to_dir_id]
664
if to_dir_ie.kind not in ('directory', 'root_directory'):
665
raise BzrError("destination %r is not a directory" % to_abs)
667
to_idpath = inv.get_idpath(to_dir_id)
670
if not self.has_filename(f):
671
raise BzrError("%r does not exist in working tree" % f)
672
f_id = inv.path2id(f)
674
raise BzrError("%r is not versioned" % f)
675
name_tail = splitpath(f)[-1]
676
dest_path = appendpath(to_name, name_tail)
677
if self.has_filename(dest_path):
678
raise BzrError("destination %r already exists" % dest_path)
679
if f_id in to_idpath:
680
raise BzrError("can't move %r to a subdirectory of itself" % f)
682
# OK, so there's a race here, it's possible that someone will
683
# create a file in this interval and then the rename might be
684
# left half-done. But we should have caught most problems.
685
orig_inv = deepcopy(self.inventory)
688
name_tail = splitpath(f)[-1]
689
dest_path = appendpath(to_name, name_tail)
690
result.append((f, dest_path))
691
inv.rename(inv.path2id(f), to_dir_id, name_tail)
693
rename(self.abspath(f), self.abspath(dest_path))
695
raise BzrError("failed to rename %r to %r: %s" %
696
(f, dest_path, e[1]),
697
["rename rolled back"])
699
# restore the inventory on error
700
self._set_inventory(orig_inv)
702
self._write_inventory(inv)
706
def rename_one(self, from_rel, to_rel):
709
This can change the directory or the filename or both.
712
if not self.has_filename(from_rel):
713
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
714
if self.has_filename(to_rel):
715
raise BzrError("can't rename: new working file %r already exists" % to_rel)
717
file_id = inv.path2id(from_rel)
719
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
722
from_parent = entry.parent_id
723
from_name = entry.name
725
if inv.path2id(to_rel):
726
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
728
to_dir, to_tail = os.path.split(to_rel)
729
to_dir_id = inv.path2id(to_dir)
730
if to_dir_id == None and to_dir != '':
731
raise BzrError("can't determine destination directory id for %r" % to_dir)
733
mutter("rename_one:")
734
mutter(" file_id {%s}" % file_id)
735
mutter(" from_rel %r" % from_rel)
736
mutter(" to_rel %r" % to_rel)
737
mutter(" to_dir %r" % to_dir)
738
mutter(" to_dir_id {%s}" % to_dir_id)
740
inv.rename(file_id, to_dir_id, to_tail)
742
from_abs = self.abspath(from_rel)
743
to_abs = self.abspath(to_rel)
745
rename(from_abs, to_abs)
747
inv.rename(file_id, from_parent, from_name)
748
raise BzrError("failed to rename %r to %r: %s"
749
% (from_abs, to_abs, e[1]),
750
["rename rolled back"])
751
self._write_inventory(inv)
755
"""Return all unknown files.
757
These are files in the working directory that are not versioned or
758
control files or ignored.
760
>>> from bzrlib.bzrdir import ScratchDir
761
>>> d = ScratchDir(files=['foo', 'foo~'])
762
>>> b = d.open_branch()
763
>>> tree = WorkingTree(b.base, b)
764
>>> map(str, tree.unknowns())
767
>>> list(b.unknowns())
769
>>> tree.remove('foo')
770
>>> list(b.unknowns())
773
for subp in self.extras():
774
if not self.is_ignored(subp):
777
def iter_conflicts(self):
779
for path in (s[0] for s in self.list_files()):
780
stem = get_conflicted_stem(path)
783
if stem not in conflicted:
788
def pull(self, source, overwrite=False):
789
from bzrlib.merge import merge_inner
792
old_revision_history = self.branch.revision_history()
793
count = self.branch.pull(source, overwrite)
794
new_revision_history = self.branch.revision_history()
795
if new_revision_history != old_revision_history:
796
if len(old_revision_history):
797
other_revision = old_revision_history[-1]
799
other_revision = None
800
repository = self.branch.repository
801
merge_inner(self.branch,
803
repository.revision_tree(other_revision),
805
self.set_last_revision(self.branch.last_revision())
811
"""Yield all unknown files in this WorkingTree.
813
If there are any unknown directories then only the directory is
814
returned, not all its children. But if there are unknown files
815
under a versioned subdirectory, they are returned.
817
Currently returned depth-first, sorted by name within directories.
819
## TODO: Work from given directory downwards
820
for path, dir_entry in self.inventory.directories():
821
mutter("search for unknowns in %r", path)
822
dirabs = self.abspath(path)
823
if not isdir(dirabs):
824
# e.g. directory deleted
828
for subf in os.listdir(dirabs):
830
and (subf not in dir_entry.children)):
835
subp = appendpath(path, subf)
839
def ignored_files(self):
840
"""Yield list of PATH, IGNORE_PATTERN"""
841
for subp in self.extras():
842
pat = self.is_ignored(subp)
847
def get_ignore_list(self):
848
"""Return list of ignore patterns.
850
Cached in the Tree object after the first call.
852
if hasattr(self, '_ignorelist'):
853
return self._ignorelist
855
l = bzrlib.DEFAULT_IGNORE[:]
856
if self.has_filename(bzrlib.IGNORE_FILENAME):
857
f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
858
l.extend([line.rstrip("\n\r") for line in f.readlines()])
863
def is_ignored(self, filename):
864
r"""Check whether the filename matches an ignore pattern.
866
Patterns containing '/' or '\' need to match the whole path;
867
others match against only the last component.
869
If the file is ignored, returns the pattern which caused it to
870
be ignored, otherwise None. So this can simply be used as a
871
boolean if desired."""
873
# TODO: Use '**' to match directories, and other extended
874
# globbing stuff from cvs/rsync.
876
# XXX: fnmatch is actually not quite what we want: it's only
877
# approximately the same as real Unix fnmatch, and doesn't
878
# treat dotfiles correctly and allows * to match /.
879
# Eventually it should be replaced with something more
882
for pat in self.get_ignore_list():
883
if '/' in pat or '\\' in pat:
885
# as a special case, you can put ./ at the start of a
886
# pattern; this is good to match in the top-level
889
if (pat[:2] == './') or (pat[:2] == '.\\'):
893
if fnmatch.fnmatchcase(filename, newpat):
896
if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
901
def kind(self, file_id):
902
return file_kind(self.id2abspath(file_id))
904
def last_revision(self):
905
"""Return the last revision id of this working tree.
907
In early branch formats this was == the branch last_revision,
908
but that cannot be relied upon - for working tree operations,
909
always use tree.last_revision().
911
return self.branch.last_revision()
914
"""See Branch.lock_read, and WorkingTree.unlock."""
915
return self.branch.lock_read()
917
def lock_write(self):
918
"""See Branch.lock_write, and WorkingTree.unlock."""
919
return self.branch.lock_write()
921
def _basis_inventory_name(self, revision_id):
922
return 'basis-inventory.%s' % revision_id
924
def set_last_revision(self, new_revision, old_revision=None):
925
if old_revision is not None:
927
path = self._basis_inventory_name(old_revision)
928
path = self._control_files._escape(path)
929
self._control_files._transport.delete(path)
932
if new_revision is None:
933
self.branch.set_revision_history([])
935
# current format is locked in with the branch
936
revision_history = self.branch.revision_history()
938
position = revision_history.index(new_revision)
940
raise errors.NoSuchRevision(self.branch, new_revision)
941
self.branch.set_revision_history(revision_history[:position + 1])
943
xml = self.branch.repository.get_inventory_xml(new_revision)
944
path = self._basis_inventory_name(new_revision)
945
self._control_files.put_utf8(path, xml)
946
except WeaveRevisionNotPresent:
949
def read_basis_inventory(self, revision_id):
950
"""Read the cached basis inventory."""
951
path = self._basis_inventory_name(revision_id)
952
return self._control_files.get_utf8(path).read()
955
def read_working_inventory(self):
956
"""Read the working inventory."""
957
# ElementTree does its own conversion from UTF-8, so open in
959
result = bzrlib.xml5.serializer_v5.read_inventory(
960
self._control_files.get('inventory'))
961
self._set_inventory(result)
965
def remove(self, files, verbose=False):
966
"""Remove nominated files from the working inventory..
968
This does not remove their text. This does not run on XXX on what? RBC
970
TODO: Refuse to remove modified files unless --force is given?
972
TODO: Do something useful with directories.
974
TODO: Should this remove the text or not? Tough call; not
975
removing may be useful and the user can just use use rm, and
976
is the opposite of add. Removing it is consistent with most
977
other tools. Maybe an option.
979
## TODO: Normalize names
980
## TODO: Remove nested loops; better scalability
981
if isinstance(files, basestring):
986
# do this before any modifications
990
# TODO: Perhaps make this just a warning, and continue?
991
# This tends to happen when
992
raise NotVersionedError(path=f)
993
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
995
# having remove it, it must be either ignored or unknown
996
if self.is_ignored(f):
1000
show_status(new_status, inv[fid].kind, quotefn(f))
1003
self._write_inventory(inv)
1006
def revert(self, filenames, old_tree=None, backups=True):
1007
from bzrlib.merge import merge_inner
1008
if old_tree is None:
1009
old_tree = self.basis_tree()
1010
merge_inner(self.branch, old_tree,
1011
self, ignore_zero=True,
1012
backup_files=backups,
1013
interesting_files=filenames,
1015
if not len(filenames):
1016
self.set_pending_merges([])
1019
def set_inventory(self, new_inventory_list):
1020
from bzrlib.inventory import (Inventory,
1025
inv = Inventory(self.get_root_id())
1026
for path, file_id, parent, kind in new_inventory_list:
1027
name = os.path.basename(path)
1030
# fixme, there should be a factory function inv,add_??
1031
if kind == 'directory':
1032
inv.add(InventoryDirectory(file_id, name, parent))
1033
elif kind == 'file':
1034
inv.add(InventoryFile(file_id, name, parent))
1035
elif kind == 'symlink':
1036
inv.add(InventoryLink(file_id, name, parent))
1038
raise BzrError("unknown kind %r" % kind)
1039
self._write_inventory(inv)
1042
def set_root_id(self, file_id):
1043
"""Set the root id for this tree."""
1044
inv = self.read_working_inventory()
1045
orig_root_id = inv.root.file_id
1046
del inv._byid[inv.root.file_id]
1047
inv.root.file_id = file_id
1048
inv._byid[inv.root.file_id] = inv.root
1051
if entry.parent_id == orig_root_id:
1052
entry.parent_id = inv.root.file_id
1053
self._write_inventory(inv)
1056
"""See Branch.unlock.
1058
WorkingTree locking just uses the Branch locking facilities.
1059
This is current because all working trees have an embedded branch
1060
within them. IF in the future, we were to make branch data shareable
1061
between multiple working trees, i.e. via shared storage, then we
1062
would probably want to lock both the local tree, and the branch.
1064
# FIXME: We want to write out the hashcache only when the last lock on
1065
# this working copy is released. Peeking at the lock count is a bit
1066
# of a nasty hack; probably it's better to have a transaction object,
1067
# which can do some finalization when it's either successfully or
1068
# unsuccessfully completed. (Denys's original patch did that.)
1069
if self._hashcache.needs_write and self._control_files._lock_count==1:
1070
self._hashcache.write()
1071
return self.branch.unlock()
1074
def _write_inventory(self, inv):
1075
"""Write inventory as the current inventory."""
1077
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1079
self._control_files.put('inventory', sio)
1080
self._set_inventory(inv)
1081
mutter('wrote working inventory')
1084
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1085
def get_conflicted_stem(path):
1086
for suffix in CONFLICT_SUFFIXES:
1087
if path.endswith(suffix):
1088
return path[:-len(suffix)]
1090
def is_control_file(filename):
1091
## FIXME: better check
1092
filename = normpath(filename)
1093
while filename != '':
1094
head, tail = os.path.split(filename)
1095
## mutter('check %r for control file' % ((head, tail),))
1096
if tail == bzrlib.BZRDIR:
1098
if filename == head: