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,
57
from bzrlib.decorators import needs_read_lock, needs_write_lock
58
import bzrlib.errors as errors
59
from bzrlib.errors import (BzrCheckError,
62
WeaveRevisionNotPresent,
66
from bzrlib.inventory import InventoryEntry
67
from bzrlib.lockable_files import LockableFiles
68
from bzrlib.osutils import (appendpath,
83
from bzrlib.symbol_versioning import *
84
from bzrlib.textui import show_status
86
from bzrlib.trace import mutter
87
from bzrlib.transport import get_transport
91
def gen_file_id(name):
92
"""Return new file id.
94
This should probably generate proper UUIDs, but for the moment we
95
cope with just randomness because running uuidgen every time is
98
from binascii import hexlify
102
idx = name.rfind('/')
104
name = name[idx+1 : ]
105
idx = name.rfind('\\')
107
name = name[idx+1 : ]
109
# make it not a hidden file
110
name = name.lstrip('.')
112
# remove any wierd characters; we don't escape them but rather
114
name = re.sub(r'[^\w.]', '', name)
116
s = hexlify(rand_bytes(8))
117
return '-'.join((name, compact_date(time()), s))
121
"""Return a new tree-root file id."""
122
return gen_file_id('TREE_ROOT')
125
class TreeEntry(object):
126
"""An entry that implements the minium interface used by commands.
128
This needs further inspection, it may be better to have
129
InventoryEntries without ids - though that seems wrong. For now,
130
this is a parallel hierarchy to InventoryEntry, and needs to become
131
one of several things: decorates to that hierarchy, children of, or
133
Another note is that these objects are currently only used when there is
134
no InventoryEntry available - i.e. for unversioned objects.
135
Perhaps they should be UnversionedEntry et al. ? - RBC 20051003
138
def __eq__(self, other):
139
# yes, this us ugly, TODO: best practice __eq__ style.
140
return (isinstance(other, TreeEntry)
141
and other.__class__ == self.__class__)
143
def kind_character(self):
147
class TreeDirectory(TreeEntry):
148
"""See TreeEntry. This is a directory in a working tree."""
150
def __eq__(self, other):
151
return (isinstance(other, TreeDirectory)
152
and other.__class__ == self.__class__)
154
def kind_character(self):
158
class TreeFile(TreeEntry):
159
"""See TreeEntry. This is a regular file in a working tree."""
161
def __eq__(self, other):
162
return (isinstance(other, TreeFile)
163
and other.__class__ == self.__class__)
165
def kind_character(self):
169
class TreeLink(TreeEntry):
170
"""See TreeEntry. This is a symlink in a working tree."""
172
def __eq__(self, other):
173
return (isinstance(other, TreeLink)
174
and other.__class__ == self.__class__)
176
def kind_character(self):
180
class WorkingTree(bzrlib.tree.Tree):
181
"""Working copy tree.
183
The inventory is held in the `Branch` working-inventory, and the
184
files are in a directory on disk.
186
It is possible for a `WorkingTree` to have a filename which is
187
not listed in the Inventory and vice versa.
190
def __init__(self, basedir='.', branch=None, _inventory=None, _control_files=None):
191
"""Construct a WorkingTree for basedir.
193
If the branch is not supplied, it is opened automatically.
194
If the branch is supplied, it must be the branch for this basedir.
195
(branch.base is not cross checked, because for remote branches that
196
would be meaningless).
198
from bzrlib.hashcache import HashCache
199
from bzrlib.trace import note, mutter
200
assert isinstance(basedir, basestring), \
201
"base directory %r is not a string" % basedir
202
basedir = safe_unicode(basedir)
203
mutter("openeing working tree %r", basedir)
205
branch = Branch.open(basedir)
206
assert isinstance(branch, Branch), \
207
"branch %r is not a Branch" % branch
209
self.basedir = realpath(basedir)
210
# if branch is at our basedir and is a format 6 or less
211
if (isinstance(self.branch._branch_format,
212
(BzrBranchFormat4, BzrBranchFormat5, BzrBranchFormat6))
213
# might be able to share control object
214
and self.branch.base.split('/')[-2] == self.basedir.split('/')[-1]):
215
self._control_files = self.branch.control_files
216
elif _control_files is not None:
217
assert False, "not done yet"
218
# self._control_files = _control_files
220
self._control_files = LockableFiles(
221
get_transport(self.basedir).clone(bzrlib.BZRDIR), 'branch-lock')
223
# update the whole cache up front and write to disk if anything changed;
224
# in the future we might want to do this more selectively
225
# two possible ways offer themselves : in self._unlock, write the cache
226
# if needed, or, when the cache sees a change, append it to the hash
227
# cache file, and have the parser take the most recent entry for a
229
hc = self._hashcache = HashCache(basedir)
237
if _inventory is None:
238
self._set_inventory(self.read_working_inventory())
240
self._set_inventory(_inventory)
242
def _set_inventory(self, inv):
243
self._inventory = inv
244
self.path2id = self._inventory.path2id
247
def open_containing(path=None):
248
"""Open an existing working tree which has its root about path.
250
This probes for a working tree at path and searches upwards from there.
252
Basically we keep looking up until we find the control directory or
253
run into /. If there isn't one, raises NotBranchError.
254
TODO: give this a new exception.
255
If there is one, it is returned, along with the unused portion of path.
261
if path.find('://') != -1:
262
raise NotBranchError(path=path)
268
return WorkingTree(path), tail
269
except NotBranchError:
272
tail = pathjoin(os.path.basename(path), tail)
274
tail = os.path.basename(path)
276
path = os.path.dirname(path)
278
# reached the root, whatever that may be
279
raise NotBranchError(path=orig_path)
282
"""Iterate through file_ids for this tree.
284
file_ids are in a WorkingTree if they are in the working inventory
285
and the working file exists.
287
inv = self._inventory
288
for path, ie in inv.iter_entries():
289
if bzrlib.osutils.lexists(self.abspath(path)):
293
return "<%s of %s>" % (self.__class__.__name__,
294
getattr(self, 'basedir', None))
296
def abspath(self, filename):
297
return pathjoin(self.basedir, filename)
299
def basis_tree(self):
300
"""Return RevisionTree for the current last revision."""
301
revision_id = self.last_revision()
302
if revision_id is not None:
304
xml = self.read_basis_inventory(revision_id)
305
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
306
return bzrlib.tree.RevisionTree(self.branch.repository, inv,
310
return self.branch.repository.revision_tree(revision_id)
313
def create(branch, directory):
314
"""Create a workingtree for branch at directory.
316
If existing_directory already exists it must have a .bzr directory.
317
If it does not exist, it will be created.
319
This returns a new WorkingTree object for the new checkout.
321
TODO FIXME RBC 20060124 when we have checkout formats in place this
322
should accept an optional revisionid to checkout [and reject this if
323
checking out into the same dir as a pre-checkout-aware branch format.]
328
if e.errno != errno.EEXIST:
331
os.mkdir(pathjoin(directory, '.bzr'))
333
if e.errno != errno.EEXIST:
335
revision_tree = branch.repository.revision_tree(branch.last_revision())
336
inv = revision_tree.inventory
337
wt = WorkingTree(directory, branch, inv)
338
wt._write_inventory(inv)
339
wt.set_root_id(revision_tree.inventory.root.file_id)
340
if branch.last_revision() is not None:
341
wt.set_last_revision(branch.last_revision())
342
wt.set_pending_merges([])
347
def create_standalone(directory):
348
"""Create a checkout and a branch at directory.
350
Directory must exist and be empty.
352
directory = safe_unicode(directory)
353
b = Branch.create(directory)
354
return WorkingTree.create(b, directory)
356
def relpath(self, abs):
357
"""Return the local path portion from a given absolute path."""
358
return relpath(self.basedir, abs)
360
def has_filename(self, filename):
361
return bzrlib.osutils.lexists(self.abspath(filename))
363
def get_file(self, file_id):
364
return self.get_file_byname(self.id2path(file_id))
366
def get_file_byname(self, filename):
367
return file(self.abspath(filename), 'rb')
369
def get_root_id(self):
370
"""Return the id of this trees root"""
371
inv = self.read_working_inventory()
372
return inv.root.file_id
374
def _get_store_filename(self, file_id):
375
## XXX: badly named; this is not in the store at all
376
return self.abspath(self.id2path(file_id))
379
def clone(self, to_directory, revision=None):
380
"""Copy this working tree to a new directory.
382
Currently this will make a new standalone branch at to_directory,
383
but it is planned to change this to use the same branch style that this
384
current tree uses (standalone if standalone, repository if repository)
385
- so that this really is a clone. FIXME RBC 20060127 do this.
386
FIXME MORE RBC 20060127 failed to reach consensus on this in #bzr.
388
If you want a standalone branch, please use branch.clone(to_directory)
389
followed by WorkingTree.create(cloned_branch, to_directory) which is
390
the supported api to produce that.
393
If not None, the cloned tree will have its last revision set to
394
revision, and if a branch is being copied it will be informed
395
of the revision to result in.
397
to_directory -- The destination directory: Must not exist.
399
to_directory = safe_unicode(to_directory)
400
os.mkdir(to_directory)
401
# FIXME here is where the decision to clone the branch should happen.
403
revision = self.last_revision()
404
cloned_branch = self.branch.clone(to_directory, revision)
405
return WorkingTree.create(cloned_branch, to_directory)
408
def commit(self, *args, **kwargs):
409
from bzrlib.commit import Commit
410
# args for wt.commit start at message from the Commit.commit method,
411
# but with branch a kwarg now, passing in args as is results in the
412
#message being used for the branch
413
args = (DEPRECATED_PARAMETER, ) + args
414
Commit().commit(working_tree=self, *args, **kwargs)
415
self._set_inventory(self.read_working_inventory())
417
def id2abspath(self, file_id):
418
return self.abspath(self.id2path(file_id))
420
def has_id(self, file_id):
421
# files that have been deleted are excluded
422
inv = self._inventory
423
if not inv.has_id(file_id):
425
path = inv.id2path(file_id)
426
return bzrlib.osutils.lexists(self.abspath(path))
428
def has_or_had_id(self, file_id):
429
if file_id == self.inventory.root.file_id:
431
return self.inventory.has_id(file_id)
433
__contains__ = has_id
435
def get_file_size(self, file_id):
436
return os.path.getsize(self.id2abspath(file_id))
439
def get_file_sha1(self, file_id):
440
path = self._inventory.id2path(file_id)
441
return self._hashcache.get_sha1(path)
443
def is_executable(self, file_id):
445
return self._inventory[file_id].executable
447
path = self._inventory.id2path(file_id)
448
mode = os.lstat(self.abspath(path)).st_mode
449
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
452
def add(self, files, ids=None):
453
"""Make files versioned.
455
Note that the command line normally calls smart_add instead,
456
which can automatically recurse.
458
This adds the files to the inventory, so that they will be
459
recorded by the next commit.
462
List of paths to add, relative to the base of the tree.
465
If set, use these instead of automatically generated ids.
466
Must be the same length as the list of files, but may
467
contain None for ids that are to be autogenerated.
469
TODO: Perhaps have an option to add the ids even if the files do
472
TODO: Perhaps callback with the ids and paths as they're added.
474
# TODO: Re-adding a file that is removed in the working copy
475
# should probably put it back with the previous ID.
476
if isinstance(files, basestring):
477
assert(ids is None or isinstance(ids, basestring))
483
ids = [None] * len(files)
485
assert(len(ids) == len(files))
487
inv = self.read_working_inventory()
488
for f,file_id in zip(files, ids):
489
if is_control_file(f):
490
raise BzrError("cannot add control file %s" % quotefn(f))
495
raise BzrError("cannot add top-level %r" % f)
497
fullpath = normpath(self.abspath(f))
500
kind = file_kind(fullpath)
502
if e.errno == errno.ENOENT:
503
raise NoSuchFile(fullpath)
504
# maybe something better?
505
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
507
if not InventoryEntry.versionable_kind(kind):
508
raise BzrError('cannot add: not a versionable file ('
509
'i.e. regular file, symlink or directory): %s' % quotefn(f))
512
file_id = gen_file_id(f)
513
inv.add_path(f, kind=kind, file_id=file_id)
515
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
516
self._write_inventory(inv)
519
def add_pending_merge(self, *revision_ids):
520
# TODO: Perhaps should check at this point that the
521
# history of the revision is actually present?
522
p = self.pending_merges()
524
for rev_id in revision_ids:
530
self.set_pending_merges(p)
533
def pending_merges(self):
534
"""Return a list of pending merges.
536
These are revisions that have been merged into the working
537
directory but not yet committed.
540
merges_file = self._control_files.get_utf8('pending-merges')
542
if e.errno != errno.ENOENT:
546
for l in merges_file.readlines():
547
p.append(l.rstrip('\n'))
551
def set_pending_merges(self, rev_list):
552
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
554
def get_symlink_target(self, file_id):
555
return os.readlink(self.id2abspath(file_id))
557
def file_class(self, filename):
558
if self.path2id(filename):
560
elif self.is_ignored(filename):
566
def list_files(self):
567
"""Recursively list all files as (path, class, kind, id).
569
Lists, but does not descend into unversioned directories.
571
This does not include files that have been deleted in this
574
Skips the control directory.
576
inv = self._inventory
578
def descend(from_dir_relpath, from_dir_id, dp):
582
## TODO: If we find a subdirectory with its own .bzr
583
## directory, then that is a separate tree and we
584
## should exclude it.
585
if bzrlib.BZRDIR == f:
589
fp = appendpath(from_dir_relpath, f)
592
fap = appendpath(dp, f)
594
f_ie = inv.get_child(from_dir_id, f)
597
elif self.is_ignored(fp):
606
raise BzrCheckError("file %r entered as kind %r id %r, "
608
% (fap, f_ie.kind, f_ie.file_id, fk))
610
# make a last minute entry
614
if fk == 'directory':
615
entry = TreeDirectory()
618
elif fk == 'symlink':
623
yield fp, c, fk, (f_ie and f_ie.file_id), entry
625
if fk != 'directory':
629
# don't descend unversioned directories
632
for ff in descend(fp, f_ie.file_id, fap):
635
for f in descend(u'', inv.root.file_id, self.basedir):
639
def move(self, from_paths, to_name):
642
to_name must exist in the inventory.
644
If to_name exists and is a directory, the files are moved into
645
it, keeping their old names.
647
Note that to_name is only the last component of the new name;
648
this doesn't change the directory.
650
This returns a list of (from_path, to_path) pairs for each
654
## TODO: Option to move IDs only
655
assert not isinstance(from_paths, basestring)
657
to_abs = self.abspath(to_name)
658
if not isdir(to_abs):
659
raise BzrError("destination %r is not a directory" % to_abs)
660
if not self.has_filename(to_name):
661
raise BzrError("destination %r not in working directory" % to_abs)
662
to_dir_id = inv.path2id(to_name)
663
if to_dir_id == None and to_name != '':
664
raise BzrError("destination %r is not a versioned directory" % to_name)
665
to_dir_ie = inv[to_dir_id]
666
if to_dir_ie.kind not in ('directory', 'root_directory'):
667
raise BzrError("destination %r is not a directory" % to_abs)
669
to_idpath = inv.get_idpath(to_dir_id)
672
if not self.has_filename(f):
673
raise BzrError("%r does not exist in working tree" % f)
674
f_id = inv.path2id(f)
676
raise BzrError("%r is not versioned" % f)
677
name_tail = splitpath(f)[-1]
678
dest_path = appendpath(to_name, name_tail)
679
if self.has_filename(dest_path):
680
raise BzrError("destination %r already exists" % dest_path)
681
if f_id in to_idpath:
682
raise BzrError("can't move %r to a subdirectory of itself" % f)
684
# OK, so there's a race here, it's possible that someone will
685
# create a file in this interval and then the rename might be
686
# left half-done. But we should have caught most problems.
687
orig_inv = deepcopy(self.inventory)
690
name_tail = splitpath(f)[-1]
691
dest_path = appendpath(to_name, name_tail)
692
result.append((f, dest_path))
693
inv.rename(inv.path2id(f), to_dir_id, name_tail)
695
rename(self.abspath(f), self.abspath(dest_path))
697
raise BzrError("failed to rename %r to %r: %s" %
698
(f, dest_path, e[1]),
699
["rename rolled back"])
701
# restore the inventory on error
702
self._set_inventory(orig_inv)
704
self._write_inventory(inv)
708
def rename_one(self, from_rel, to_rel):
711
This can change the directory or the filename or both.
714
if not self.has_filename(from_rel):
715
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
716
if self.has_filename(to_rel):
717
raise BzrError("can't rename: new working file %r already exists" % to_rel)
719
file_id = inv.path2id(from_rel)
721
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
724
from_parent = entry.parent_id
725
from_name = entry.name
727
if inv.path2id(to_rel):
728
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
730
to_dir, to_tail = os.path.split(to_rel)
731
to_dir_id = inv.path2id(to_dir)
732
if to_dir_id == None and to_dir != '':
733
raise BzrError("can't determine destination directory id for %r" % to_dir)
735
mutter("rename_one:")
736
mutter(" file_id {%s}" % file_id)
737
mutter(" from_rel %r" % from_rel)
738
mutter(" to_rel %r" % to_rel)
739
mutter(" to_dir %r" % to_dir)
740
mutter(" to_dir_id {%s}" % to_dir_id)
742
inv.rename(file_id, to_dir_id, to_tail)
744
from_abs = self.abspath(from_rel)
745
to_abs = self.abspath(to_rel)
747
rename(from_abs, to_abs)
749
inv.rename(file_id, from_parent, from_name)
750
raise BzrError("failed to rename %r to %r: %s"
751
% (from_abs, to_abs, e[1]),
752
["rename rolled back"])
753
self._write_inventory(inv)
757
"""Return all unknown files.
759
These are files in the working directory that are not versioned or
760
control files or ignored.
762
>>> from bzrlib.branch import ScratchBranch
763
>>> b = ScratchBranch(files=['foo', 'foo~'])
764
>>> tree = WorkingTree(b.base, b)
765
>>> map(str, tree.unknowns())
768
>>> list(b.unknowns())
770
>>> tree.remove('foo')
771
>>> list(b.unknowns())
774
for subp in self.extras():
775
if not self.is_ignored(subp):
778
def iter_conflicts(self):
780
for path in (s[0] for s in self.list_files()):
781
stem = get_conflicted_stem(path)
784
if stem not in conflicted:
789
def pull(self, source, overwrite=False):
790
from bzrlib.merge import merge_inner
793
old_revision_history = self.branch.revision_history()
794
count = self.branch.pull(source, overwrite)
795
new_revision_history = self.branch.revision_history()
796
if new_revision_history != old_revision_history:
797
if len(old_revision_history):
798
other_revision = old_revision_history[-1]
800
other_revision = None
801
repository = self.branch.repository
802
merge_inner(self.branch,
804
repository.revision_tree(other_revision),
806
self.set_last_revision(self.branch.last_revision())
812
"""Yield all unknown files in this WorkingTree.
814
If there are any unknown directories then only the directory is
815
returned, not all its children. But if there are unknown files
816
under a versioned subdirectory, they are returned.
818
Currently returned depth-first, sorted by name within directories.
820
## TODO: Work from given directory downwards
821
for path, dir_entry in self.inventory.directories():
822
mutter("search for unknowns in %r", path)
823
dirabs = self.abspath(path)
824
if not isdir(dirabs):
825
# e.g. directory deleted
829
for subf in os.listdir(dirabs):
831
and (subf not in dir_entry.children)):
836
subp = appendpath(path, subf)
840
def ignored_files(self):
841
"""Yield list of PATH, IGNORE_PATTERN"""
842
for subp in self.extras():
843
pat = self.is_ignored(subp)
848
def get_ignore_list(self):
849
"""Return list of ignore patterns.
851
Cached in the Tree object after the first call.
853
if hasattr(self, '_ignorelist'):
854
return self._ignorelist
856
l = bzrlib.DEFAULT_IGNORE[:]
857
if self.has_filename(bzrlib.IGNORE_FILENAME):
858
f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
859
l.extend([line.rstrip("\n\r") for line in f.readlines()])
864
def is_ignored(self, filename):
865
r"""Check whether the filename matches an ignore pattern.
867
Patterns containing '/' or '\' need to match the whole path;
868
others match against only the last component.
870
If the file is ignored, returns the pattern which caused it to
871
be ignored, otherwise None. So this can simply be used as a
872
boolean if desired."""
874
# TODO: Use '**' to match directories, and other extended
875
# globbing stuff from cvs/rsync.
877
# XXX: fnmatch is actually not quite what we want: it's only
878
# approximately the same as real Unix fnmatch, and doesn't
879
# treat dotfiles correctly and allows * to match /.
880
# Eventually it should be replaced with something more
883
for pat in self.get_ignore_list():
884
if '/' in pat or '\\' in pat:
886
# as a special case, you can put ./ at the start of a
887
# pattern; this is good to match in the top-level
890
if (pat[:2] == './') or (pat[:2] == '.\\'):
894
if fnmatch.fnmatchcase(filename, newpat):
897
if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
902
def kind(self, file_id):
903
return file_kind(self.id2abspath(file_id))
905
def last_revision(self):
906
"""Return the last revision id of this working tree.
908
In early branch formats this was == the branch last_revision,
909
but that cannot be relied upon - for working tree operations,
910
always use tree.last_revision().
912
return self.branch.last_revision()
915
"""See Branch.lock_read, and WorkingTree.unlock."""
916
return self.branch.lock_read()
918
def lock_write(self):
919
"""See Branch.lock_write, and WorkingTree.unlock."""
920
return self.branch.lock_write()
922
def _basis_inventory_name(self, revision_id):
923
return 'basis-inventory.%s' % revision_id
925
def set_last_revision(self, new_revision, old_revision=None):
926
if old_revision is not None:
928
path = self._basis_inventory_name(old_revision)
929
path = self._control_files._escape(path)
930
self._control_files._transport.delete(path)
933
if new_revision is None:
934
self.branch.set_revision_history([])
936
# current format is locked in with the branch
937
revision_history = self.branch.revision_history()
939
position = revision_history.index(new_revision)
941
raise errors.NoSuchRevision(self.branch, new_revision)
942
self.branch.set_revision_history(revision_history[:position + 1])
944
xml = self.branch.repository.get_inventory_xml(new_revision)
945
path = self._basis_inventory_name(new_revision)
946
self._control_files.put_utf8(path, xml)
947
except WeaveRevisionNotPresent:
950
def read_basis_inventory(self, revision_id):
951
"""Read the cached basis inventory."""
952
path = self._basis_inventory_name(revision_id)
953
return self._control_files.get_utf8(path).read()
956
def read_working_inventory(self):
957
"""Read the working inventory."""
958
# ElementTree does its own conversion from UTF-8, so open in
960
result = bzrlib.xml5.serializer_v5.read_inventory(
961
self._control_files.get('inventory'))
962
self._set_inventory(result)
966
def remove(self, files, verbose=False):
967
"""Remove nominated files from the working inventory..
969
This does not remove their text. This does not run on XXX on what? RBC
971
TODO: Refuse to remove modified files unless --force is given?
973
TODO: Do something useful with directories.
975
TODO: Should this remove the text or not? Tough call; not
976
removing may be useful and the user can just use use rm, and
977
is the opposite of add. Removing it is consistent with most
978
other tools. Maybe an option.
980
## TODO: Normalize names
981
## TODO: Remove nested loops; better scalability
982
if isinstance(files, basestring):
987
# do this before any modifications
991
# TODO: Perhaps make this just a warning, and continue?
992
# This tends to happen when
993
raise NotVersionedError(path=f)
994
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
996
# having remove it, it must be either ignored or unknown
997
if self.is_ignored(f):
1001
show_status(new_status, inv[fid].kind, quotefn(f))
1004
self._write_inventory(inv)
1007
def revert(self, filenames, old_tree=None, backups=True):
1008
from bzrlib.merge import merge_inner
1009
if old_tree is None:
1010
old_tree = self.basis_tree()
1011
merge_inner(self.branch, old_tree,
1012
self, ignore_zero=True,
1013
backup_files=backups,
1014
interesting_files=filenames,
1016
if not len(filenames):
1017
self.set_pending_merges([])
1020
def set_inventory(self, new_inventory_list):
1021
from bzrlib.inventory import (Inventory,
1026
inv = Inventory(self.get_root_id())
1027
for path, file_id, parent, kind in new_inventory_list:
1028
name = os.path.basename(path)
1031
# fixme, there should be a factory function inv,add_??
1032
if kind == 'directory':
1033
inv.add(InventoryDirectory(file_id, name, parent))
1034
elif kind == 'file':
1035
inv.add(InventoryFile(file_id, name, parent))
1036
elif kind == 'symlink':
1037
inv.add(InventoryLink(file_id, name, parent))
1039
raise BzrError("unknown kind %r" % kind)
1040
self._write_inventory(inv)
1043
def set_root_id(self, file_id):
1044
"""Set the root id for this tree."""
1045
inv = self.read_working_inventory()
1046
orig_root_id = inv.root.file_id
1047
del inv._byid[inv.root.file_id]
1048
inv.root.file_id = file_id
1049
inv._byid[inv.root.file_id] = inv.root
1052
if entry.parent_id == orig_root_id:
1053
entry.parent_id = inv.root.file_id
1054
self._write_inventory(inv)
1057
"""See Branch.unlock.
1059
WorkingTree locking just uses the Branch locking facilities.
1060
This is current because all working trees have an embedded branch
1061
within them. IF in the future, we were to make branch data shareable
1062
between multiple working trees, i.e. via shared storage, then we
1063
would probably want to lock both the local tree, and the branch.
1065
# FIXME: We want to write out the hashcache only when the last lock on
1066
# this working copy is released. Peeking at the lock count is a bit
1067
# of a nasty hack; probably it's better to have a transaction object,
1068
# which can do some finalization when it's either successfully or
1069
# unsuccessfully completed. (Denys's original patch did that.)
1070
if self._hashcache.needs_write and self._control_files._lock_count==1:
1071
self._hashcache.write()
1072
return self.branch.unlock()
1075
def _write_inventory(self, inv):
1076
"""Write inventory as the current inventory."""
1078
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1080
self._control_files.put('inventory', sio)
1081
self._set_inventory(inv)
1082
mutter('wrote working inventory')
1085
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1086
def get_conflicted_stem(path):
1087
for suffix in CONFLICT_SUFFIXES:
1088
if path.endswith(suffix):
1089
return path[:-len(suffix)]