14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""WorkingTree object and friends.
19
A WorkingTree represents the editable working copy of a branch.
20
Operations which represent the WorkingTree are also done here,
21
such as renaming or adding files. The WorkingTree has an inventory
22
which is updated by these operations. A commit produces a
23
new revision based on the workingtree and its inventory.
25
At the moment every WorkingTree has its own branch. Remote
26
WorkingTrees aren't supported.
28
To get a WorkingTree, call bzrdir.open_workingtree() or
29
WorkingTree.open(dir).
32
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
33
CONFLICT_HEADER_1 = "BZR conflict list format 1"
35
# TODO: Give the workingtree sole responsibility for the working inventory;
36
# remove the variable and references to it from the branch. This may require
37
# updating the commit code so as to update the inventory within the working
38
# copy, and making sure there's only one WorkingTree for any directory on disk.
39
# At the moment they may alias the inventory and have old copies of it in
40
# memory. (Now done? -- mbp 20060309)
42
from copy import deepcopy
43
from cStringIO import StringIO
17
# TODO: Don't allow WorkingTrees to be constructed for remote branches.
19
# 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.
50
from bzrlib.atomicfile import AtomicFile
51
from bzrlib.branch import (Branch,
53
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
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,
61
WeaveRevisionNotPresent,
65
MergeModifiedFormatError,
68
from bzrlib.inventory import InventoryEntry, Inventory
69
from bzrlib.lockable_files import LockableFiles, TransportLock
70
from bzrlib.lockdir import LockDir
71
from bzrlib.merge import merge_inner, transform_tree
72
from bzrlib.osutils import (
90
from bzrlib.progress import DummyProgress, ProgressPhase
91
from bzrlib.revision import NULL_REVISION
92
from bzrlib.rio import RioReader, rio_file, Stanza
93
from bzrlib.symbol_versioning import *
94
from bzrlib.textui import show_status
96
from bzrlib.transform import build_tree
97
from bzrlib.trace import mutter, note
98
from bzrlib.transport import get_transport
99
from bzrlib.transport.local import LocalTransport
104
def gen_file_id(name):
105
"""Return new file id.
107
This should probably generate proper UUIDs, but for the moment we
108
cope with just randomness because running uuidgen every time is
111
from binascii import hexlify
112
from time import time
115
idx = name.rfind('/')
117
name = name[idx+1 : ]
118
idx = name.rfind('\\')
120
name = name[idx+1 : ]
122
# make it not a hidden file
123
name = name.lstrip('.')
125
# remove any wierd characters; we don't escape them but rather
127
name = re.sub(r'[^\w.]', '', name)
129
s = hexlify(rand_bytes(8))
130
return '-'.join((name, compact_date(time()), s))
134
"""Return a new tree-root file id."""
135
return gen_file_id('TREE_ROOT')
138
class TreeEntry(object):
139
"""An entry that implements the minium interface used by commands.
141
This needs further inspection, it may be better to have
142
InventoryEntries without ids - though that seems wrong. For now,
143
this is a parallel hierarchy to InventoryEntry, and needs to become
144
one of several things: decorates to that hierarchy, children of, or
146
Another note is that these objects are currently only used when there is
147
no InventoryEntry available - i.e. for unversioned objects.
148
Perhaps they should be UnversionedEntry et al. ? - RBC 20051003
151
def __eq__(self, other):
152
# yes, this us ugly, TODO: best practice __eq__ style.
153
return (isinstance(other, TreeEntry)
154
and other.__class__ == self.__class__)
156
def kind_character(self):
160
class TreeDirectory(TreeEntry):
161
"""See TreeEntry. This is a directory in a working tree."""
163
def __eq__(self, other):
164
return (isinstance(other, TreeDirectory)
165
and other.__class__ == self.__class__)
167
def kind_character(self):
171
class TreeFile(TreeEntry):
172
"""See TreeEntry. This is a regular file in a working tree."""
174
def __eq__(self, other):
175
return (isinstance(other, TreeFile)
176
and other.__class__ == self.__class__)
178
def kind_character(self):
182
class TreeLink(TreeEntry):
183
"""See TreeEntry. This is a symlink in a working tree."""
185
def __eq__(self, other):
186
return (isinstance(other, TreeLink)
187
and other.__class__ == self.__class__)
189
def kind_character(self):
26
from errors import BzrCheckError
27
from trace import mutter
193
29
class WorkingTree(bzrlib.tree.Tree):
194
30
"""Working copy tree.
199
35
It is possible for a `WorkingTree` to have a filename which is
200
36
not listed in the Inventory and vice versa.
203
def __init__(self, basedir='.',
204
branch=DEPRECATED_PARAMETER,
210
"""Construct a WorkingTree for basedir.
212
If the branch is not supplied, it is opened automatically.
213
If the branch is supplied, it must be the branch for this basedir.
214
(branch.base is not cross checked, because for remote branches that
215
would be meaningless).
217
self._format = _format
218
self.bzrdir = _bzrdir
220
# not created via open etc.
221
warn("WorkingTree() is deprecated as of bzr version 0.8. "
222
"Please use bzrdir.open_workingtree or WorkingTree.open().",
225
wt = WorkingTree.open(basedir)
226
self._branch = wt.branch
227
self.basedir = wt.basedir
228
self._control_files = wt._control_files
229
self._hashcache = wt._hashcache
230
self._set_inventory(wt._inventory)
231
self._format = wt._format
232
self.bzrdir = wt.bzrdir
38
def __init__(self, basedir, inv):
233
39
from bzrlib.hashcache import HashCache
234
40
from bzrlib.trace import note, mutter
235
assert isinstance(basedir, basestring), \
236
"base directory %r is not a string" % basedir
237
basedir = safe_unicode(basedir)
238
mutter("opening working tree %r", basedir)
239
if deprecated_passed(branch):
241
warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
242
" Please use bzrdir.open_workingtree() or"
243
" WorkingTree.open().",
247
self._branch = branch
249
self._branch = self.bzrdir.open_branch()
250
assert isinstance(self.branch, Branch), \
251
"branch %r is not a Branch" % self.branch
252
self.basedir = realpath(basedir)
253
# if branch is at our basedir and is a format 6 or less
254
if isinstance(self._format, WorkingTreeFormat2):
255
# share control object
256
self._control_files = self.branch.control_files
258
# only ready for format 3
259
assert isinstance(self._format, WorkingTreeFormat3)
260
assert isinstance(_control_files, LockableFiles), \
261
"_control_files must be a LockableFiles, not %r" \
263
self._control_files = _control_files
43
self.basedir = basedir
44
self.path2id = inv.path2id
264
46
# update the whole cache up front and write to disk if anything changed;
265
47
# in the future we might want to do this more selectively
266
# two possible ways offer themselves : in self._unlock, write the cache
267
# if needed, or, when the cache sees a change, append it to the hash
268
# cache file, and have the parser take the most recent entry for a
270
cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
271
hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
48
hc = self._hashcache = HashCache(basedir)
273
# is this scan needed ? it makes things kinda slow.
276
52
if hc.needs_write:
277
53
mutter("write hc")
280
if _inventory is None:
281
self._set_inventory(self.read_working_inventory())
283
self._set_inventory(_inventory)
286
fget=lambda self: self._branch,
287
doc="""The branch this WorkingTree is connected to.
289
This cannot be set - it is reflective of the actual disk structure
290
the working tree has been constructed from.
293
def _set_inventory(self, inv):
294
self._inventory = inv
295
self.path2id = self._inventory.path2id
297
def is_control_filename(self, filename):
298
"""True if filename is the name of a control file in this tree.
300
This is true IF and ONLY IF the filename is part of the meta data
301
that bzr controls in this tree. I.E. a random .bzr directory placed
302
on disk will not be a control file for this tree.
305
self.bzrdir.transport.relpath(self.abspath(filename))
307
except errors.PathNotChild:
311
def open(path=None, _unsupported=False):
312
"""Open an existing working tree at path.
316
path = os.path.getcwdu()
317
control = bzrdir.BzrDir.open(path, _unsupported)
318
return control.open_workingtree(_unsupported)
321
def open_containing(path=None):
322
"""Open an existing working tree which has its root about path.
324
This probes for a working tree at path and searches upwards from there.
326
Basically we keep looking up until we find the control directory or
327
run into /. If there isn't one, raises NotBranchError.
328
TODO: give this a new exception.
329
If there is one, it is returned, along with the unused portion of path.
333
control, relpath = bzrdir.BzrDir.open_containing(path)
334
return control.open_workingtree(), relpath
337
def open_downlevel(path=None):
338
"""Open an unsupported working tree.
340
Only intended for advanced situations like upgrading part of a bzrdir.
342
return WorkingTree.open(path, _unsupported=True)
58
if self._hashcache.needs_write:
59
self._hashcache.write()
344
62
def __iter__(self):
345
63
"""Iterate through file_ids for this tree.
425
89
def get_file_byname(self, filename):
426
90
return file(self.abspath(filename), 'rb')
428
def get_root_id(self):
429
"""Return the id of this trees root"""
430
inv = self.read_working_inventory()
431
return inv.root.file_id
433
92
def _get_store_filename(self, file_id):
434
## XXX: badly named; this is not in the store at all
435
return self.abspath(self.id2path(file_id))
438
def clone(self, to_bzrdir, revision_id=None, basis=None):
439
"""Duplicate this working tree into to_bzr, including all state.
441
Specifically modified files are kept as modified, but
442
ignored and unknown files are discarded.
444
If you want to make a new line of development, see bzrdir.sprout()
447
If not None, the cloned tree will have its last revision set to
448
revision, and and difference between the source trees last revision
449
and this one merged in.
452
If not None, a closer copy of a tree which may have some files in
453
common, and which file content should be preferentially copied from.
455
# assumes the target bzr dir format is compatible.
456
result = self._format.initialize(to_bzrdir)
457
self.copy_content_into(result, revision_id)
461
def copy_content_into(self, tree, revision_id=None):
462
"""Copy the current content and user files of this tree into tree."""
463
if revision_id is None:
464
transform_tree(tree, self)
466
# TODO now merge from tree.last_revision to revision
467
transform_tree(tree, self)
468
tree.set_last_revision(revision_id)
471
def commit(self, message=None, revprops=None, *args, **kwargs):
472
# avoid circular imports
473
from bzrlib.commit import Commit
476
if not 'branch-nick' in revprops:
477
revprops['branch-nick'] = self.branch.nick
478
# args for wt.commit start at message from the Commit.commit method,
479
# but with branch a kwarg now, passing in args as is results in the
480
#message being used for the branch
481
args = (DEPRECATED_PARAMETER, message, ) + args
482
Commit().commit(working_tree=self, revprops=revprops, *args, **kwargs)
483
self._set_inventory(self.read_working_inventory())
485
def id2abspath(self, file_id):
486
return self.abspath(self.id2path(file_id))
93
## XXX: badly named; this isn't in the store at all
94
return self.abspath(self.id2path(file_id))
488
97
def has_id(self, file_id):
489
98
# files that have been deleted are excluded
490
99
inv = self._inventory
491
100
if not inv.has_id(file_id):
493
102
path = inv.id2path(file_id)
494
return bzrlib.osutils.lexists(self.abspath(path))
103
return os.path.exists(self.abspath(path))
496
def has_or_had_id(self, file_id):
497
if file_id == self.inventory.root.file_id:
499
return self.inventory.has_id(file_id)
501
106
__contains__ = has_id
503
109
def get_file_size(self, file_id):
504
return os.path.getsize(self.id2abspath(file_id))
110
# is this still called?
111
raise NotImplementedError()
507
114
def get_file_sha1(self, file_id):
508
115
path = self._inventory.id2path(file_id)
509
116
return self._hashcache.get_sha1(path)
511
def is_executable(self, file_id):
512
if not supports_executable():
513
return self._inventory[file_id].executable
515
path = self._inventory.id2path(file_id)
516
mode = os.lstat(self.abspath(path)).st_mode
517
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
520
def add(self, files, ids=None):
521
"""Make files versioned.
523
Note that the command line normally calls smart_add instead,
524
which can automatically recurse.
526
This adds the files to the inventory, so that they will be
527
recorded by the next commit.
530
List of paths to add, relative to the base of the tree.
533
If set, use these instead of automatically generated ids.
534
Must be the same length as the list of files, but may
535
contain None for ids that are to be autogenerated.
537
TODO: Perhaps have an option to add the ids even if the files do
540
TODO: Perhaps callback with the ids and paths as they're added.
542
# TODO: Re-adding a file that is removed in the working copy
543
# should probably put it back with the previous ID.
544
if isinstance(files, basestring):
545
assert(ids is None or isinstance(ids, basestring))
551
ids = [None] * len(files)
553
assert(len(ids) == len(files))
555
inv = self.read_working_inventory()
556
for f,file_id in zip(files, ids):
557
if self.is_control_filename(f):
558
raise BzrError("cannot add control file %s" % quotefn(f))
563
raise BzrError("cannot add top-level %r" % f)
565
fullpath = normpath(self.abspath(f))
568
kind = file_kind(fullpath)
570
if e.errno == errno.ENOENT:
571
raise NoSuchFile(fullpath)
572
# maybe something better?
573
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
575
if not InventoryEntry.versionable_kind(kind):
576
raise BzrError('cannot add: not a versionable file ('
577
'i.e. regular file, symlink or directory): %s' % quotefn(f))
580
file_id = gen_file_id(f)
581
inv.add_path(f, kind=kind, file_id=file_id)
583
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
584
self._write_inventory(inv)
587
def add_pending_merge(self, *revision_ids):
588
# TODO: Perhaps should check at this point that the
589
# history of the revision is actually present?
590
p = self.pending_merges()
592
for rev_id in revision_ids:
598
self.set_pending_merges(p)
601
def pending_merges(self):
602
"""Return a list of pending merges.
604
These are revisions that have been merged into the working
605
directory but not yet committed.
608
merges_file = self._control_files.get_utf8('pending-merges')
610
if e.errno != errno.ENOENT:
614
for l in merges_file.readlines():
615
p.append(l.rstrip('\n'))
619
def set_pending_merges(self, rev_list):
620
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
623
def set_merge_modified(self, modified_hashes):
625
for file_id, hash in modified_hashes.iteritems():
626
yield Stanza(file_id=file_id, hash=hash)
627
self._put_rio('merge-hashes', iter_stanzas(), MERGE_MODIFIED_HEADER_1)
630
def _put_rio(self, filename, stanzas, header):
631
my_file = rio_file(stanzas, header)
632
self._control_files.put(filename, my_file)
635
def merge_modified(self):
637
hashfile = self._control_files.get('merge-hashes')
642
if hashfile.next() != MERGE_MODIFIED_HEADER_1 + '\n':
643
raise MergeModifiedFormatError()
644
except StopIteration:
645
raise MergeModifiedFormatError()
646
for s in RioReader(hashfile):
647
file_id = s.get("file_id")
648
if file_id not in self.inventory:
651
if hash == self.get_file_sha1(file_id):
652
merge_hashes[file_id] = hash
655
def get_symlink_target(self, file_id):
656
return os.readlink(self.id2abspath(file_id))
658
119
def file_class(self, filename):
659
120
if self.path2id(filename):
734
184
for ff in descend(fp, f_ie.file_id, fap):
737
for f in descend(u'', inv.root.file_id, self.basedir):
187
for f in descend('', inv.root.file_id, self.basedir):
741
def move(self, from_paths, to_name):
744
to_name must exist in the inventory.
746
If to_name exists and is a directory, the files are moved into
747
it, keeping their old names.
749
Note that to_name is only the last component of the new name;
750
this doesn't change the directory.
752
This returns a list of (from_path, to_path) pairs for each
756
## TODO: Option to move IDs only
757
assert not isinstance(from_paths, basestring)
759
to_abs = self.abspath(to_name)
760
if not isdir(to_abs):
761
raise BzrError("destination %r is not a directory" % to_abs)
762
if not self.has_filename(to_name):
763
raise BzrError("destination %r not in working directory" % to_abs)
764
to_dir_id = inv.path2id(to_name)
765
if to_dir_id == None and to_name != '':
766
raise BzrError("destination %r is not a versioned directory" % to_name)
767
to_dir_ie = inv[to_dir_id]
768
if to_dir_ie.kind not in ('directory', 'root_directory'):
769
raise BzrError("destination %r is not a directory" % to_abs)
771
to_idpath = inv.get_idpath(to_dir_id)
774
if not self.has_filename(f):
775
raise BzrError("%r does not exist in working tree" % f)
776
f_id = inv.path2id(f)
778
raise BzrError("%r is not versioned" % f)
779
name_tail = splitpath(f)[-1]
780
dest_path = appendpath(to_name, name_tail)
781
if self.has_filename(dest_path):
782
raise BzrError("destination %r already exists" % dest_path)
783
if f_id in to_idpath:
784
raise BzrError("can't move %r to a subdirectory of itself" % f)
786
# OK, so there's a race here, it's possible that someone will
787
# create a file in this interval and then the rename might be
788
# left half-done. But we should have caught most problems.
789
orig_inv = deepcopy(self.inventory)
792
name_tail = splitpath(f)[-1]
793
dest_path = appendpath(to_name, name_tail)
794
result.append((f, dest_path))
795
inv.rename(inv.path2id(f), to_dir_id, name_tail)
797
rename(self.abspath(f), self.abspath(dest_path))
799
raise BzrError("failed to rename %r to %r: %s" %
800
(f, dest_path, e[1]),
801
["rename rolled back"])
803
# restore the inventory on error
804
self._set_inventory(orig_inv)
806
self._write_inventory(inv)
810
def rename_one(self, from_rel, to_rel):
813
This can change the directory or the filename or both.
816
if not self.has_filename(from_rel):
817
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
818
if self.has_filename(to_rel):
819
raise BzrError("can't rename: new working file %r already exists" % to_rel)
821
file_id = inv.path2id(from_rel)
823
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
826
from_parent = entry.parent_id
827
from_name = entry.name
829
if inv.path2id(to_rel):
830
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
832
to_dir, to_tail = os.path.split(to_rel)
833
to_dir_id = inv.path2id(to_dir)
834
if to_dir_id == None and to_dir != '':
835
raise BzrError("can't determine destination directory id for %r" % to_dir)
837
mutter("rename_one:")
838
mutter(" file_id {%s}" % file_id)
839
mutter(" from_rel %r" % from_rel)
840
mutter(" to_rel %r" % to_rel)
841
mutter(" to_dir %r" % to_dir)
842
mutter(" to_dir_id {%s}" % to_dir_id)
844
inv.rename(file_id, to_dir_id, to_tail)
846
from_abs = self.abspath(from_rel)
847
to_abs = self.abspath(to_rel)
849
rename(from_abs, to_abs)
851
inv.rename(file_id, from_parent, from_name)
852
raise BzrError("failed to rename %r to %r: %s"
853
% (from_abs, to_abs, e[1]),
854
["rename rolled back"])
855
self._write_inventory(inv)
858
192
def unknowns(self):
859
"""Return all unknown files.
861
These are files in the working directory that are not versioned or
862
control files or ignored.
864
>>> from bzrlib.bzrdir import ScratchDir
865
>>> d = ScratchDir(files=['foo', 'foo~'])
866
>>> b = d.open_branch()
867
>>> tree = d.open_workingtree()
868
>>> map(str, tree.unknowns())
871
>>> list(b.unknowns())
873
>>> tree.remove('foo')
874
>>> list(b.unknowns())
877
193
for subp in self.extras():
878
194
if not self.is_ignored(subp):
881
@deprecated_method(zero_eight)
882
def iter_conflicts(self):
883
"""List all files in the tree that have text or content conflicts.
884
DEPRECATED. Use conflicts instead."""
885
return self._iter_conflicts()
887
def _iter_conflicts(self):
889
for path in (s[0] for s in self.list_files()):
890
stem = get_conflicted_stem(path)
893
if stem not in conflicted:
898
def pull(self, source, overwrite=False, stop_revision=None):
899
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
902
pp = ProgressPhase("Pull phase", 2, top_pb)
904
old_revision_history = self.branch.revision_history()
905
basis_tree = self.basis_tree()
906
count = self.branch.pull(source, overwrite, stop_revision)
907
new_revision_history = self.branch.revision_history()
908
if new_revision_history != old_revision_history:
910
if len(old_revision_history):
911
other_revision = old_revision_history[-1]
913
other_revision = None
914
repository = self.branch.repository
915
pb = bzrlib.ui.ui_factory.nested_progress_bar()
917
merge_inner(self.branch,
918
self.branch.basis_tree(),
924
self.set_last_revision(self.branch.last_revision())
930
198
def extras(self):
931
199
"""Yield all unknown files in this WorkingTree.
1021
def kind(self, file_id):
1022
return file_kind(self.id2abspath(file_id))
1025
def last_revision(self):
1026
"""Return the last revision id of this working tree.
1028
In early branch formats this was == the branch last_revision,
1029
but that cannot be relied upon - for working tree operations,
1030
always use tree.last_revision().
1032
return self.branch.last_revision()
1034
def is_locked(self):
1035
return self._control_files.is_locked()
1037
def lock_read(self):
1038
"""See Branch.lock_read, and WorkingTree.unlock."""
1039
self.branch.lock_read()
1041
return self._control_files.lock_read()
1043
self.branch.unlock()
1046
def lock_write(self):
1047
"""See Branch.lock_write, and WorkingTree.unlock."""
1048
self.branch.lock_write()
1050
return self._control_files.lock_write()
1052
self.branch.unlock()
1055
def get_physical_lock_status(self):
1056
return self._control_files.get_physical_lock_status()
1058
def _basis_inventory_name(self):
1059
return 'basis-inventory'
1062
def set_last_revision(self, new_revision):
1063
"""Change the last revision in the working tree."""
1064
if self._change_last_revision(new_revision):
1065
self._cache_basis_inventory(new_revision)
1067
def _change_last_revision(self, new_revision):
1068
"""Template method part of set_last_revision to perform the change.
1070
This is used to allow WorkingTree3 instances to not affect branch
1071
when their last revision is set.
1073
if new_revision is None:
1074
self.branch.set_revision_history([])
1076
# current format is locked in with the branch
1077
revision_history = self.branch.revision_history()
1079
position = revision_history.index(new_revision)
1081
raise errors.NoSuchRevision(self.branch, new_revision)
1082
self.branch.set_revision_history(revision_history[:position + 1])
1085
def _cache_basis_inventory(self, new_revision):
1086
"""Cache new_revision as the basis inventory."""
1088
# this double handles the inventory - unpack and repack -
1089
# but is easier to understand. We can/should put a conditional
1090
# in here based on whether the inventory is in the latest format
1091
# - perhaps we should repack all inventories on a repository
1093
inv = self.branch.repository.get_inventory(new_revision)
1094
inv.revision_id = new_revision
1095
xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1097
path = self._basis_inventory_name()
1098
self._control_files.put_utf8(path, xml)
1099
except WeaveRevisionNotPresent:
1102
def read_basis_inventory(self):
1103
"""Read the cached basis inventory."""
1104
path = self._basis_inventory_name()
1105
return self._control_files.get_utf8(path).read()
1108
def read_working_inventory(self):
1109
"""Read the working inventory."""
1110
# ElementTree does its own conversion from UTF-8, so open in
1112
result = bzrlib.xml5.serializer_v5.read_inventory(
1113
self._control_files.get('inventory'))
1114
self._set_inventory(result)
1118
def remove(self, files, verbose=False):
1119
"""Remove nominated files from the working inventory..
1121
This does not remove their text. This does not run on XXX on what? RBC
1123
TODO: Refuse to remove modified files unless --force is given?
1125
TODO: Do something useful with directories.
1127
TODO: Should this remove the text or not? Tough call; not
1128
removing may be useful and the user can just use use rm, and
1129
is the opposite of add. Removing it is consistent with most
1130
other tools. Maybe an option.
1132
## TODO: Normalize names
1133
## TODO: Remove nested loops; better scalability
1134
if isinstance(files, basestring):
1137
inv = self.inventory
1139
# do this before any modifications
1141
fid = inv.path2id(f)
1143
# TODO: Perhaps make this just a warning, and continue?
1144
# This tends to happen when
1145
raise NotVersionedError(path=f)
1146
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1148
# having remove it, it must be either ignored or unknown
1149
if self.is_ignored(f):
1153
show_status(new_status, inv[fid].kind, quotefn(f))
1156
self._write_inventory(inv)
1159
def revert(self, filenames, old_tree=None, backups=True,
1160
pb=DummyProgress()):
1161
from transform import revert
1162
from conflicts import resolve
1163
if old_tree is None:
1164
old_tree = self.basis_tree()
1165
conflicts = revert(self, old_tree, filenames, backups, pb)
1166
if not len(filenames):
1167
self.set_pending_merges([])
1170
resolve(self, filenames, ignore_misses=True)
1173
# XXX: This method should be deprecated in favour of taking in a proper
1174
# new Inventory object.
1176
def set_inventory(self, new_inventory_list):
1177
from bzrlib.inventory import (Inventory,
1182
inv = Inventory(self.get_root_id())
1183
for path, file_id, parent, kind in new_inventory_list:
1184
name = os.path.basename(path)
1187
# fixme, there should be a factory function inv,add_??
1188
if kind == 'directory':
1189
inv.add(InventoryDirectory(file_id, name, parent))
1190
elif kind == 'file':
1191
inv.add(InventoryFile(file_id, name, parent))
1192
elif kind == 'symlink':
1193
inv.add(InventoryLink(file_id, name, parent))
1195
raise BzrError("unknown kind %r" % kind)
1196
self._write_inventory(inv)
1199
def set_root_id(self, file_id):
1200
"""Set the root id for this tree."""
1201
inv = self.read_working_inventory()
1202
orig_root_id = inv.root.file_id
1203
del inv._byid[inv.root.file_id]
1204
inv.root.file_id = file_id
1205
inv._byid[inv.root.file_id] = inv.root
1208
if entry.parent_id == orig_root_id:
1209
entry.parent_id = inv.root.file_id
1210
self._write_inventory(inv)
1213
"""See Branch.unlock.
1215
WorkingTree locking just uses the Branch locking facilities.
1216
This is current because all working trees have an embedded branch
1217
within them. IF in the future, we were to make branch data shareable
1218
between multiple working trees, i.e. via shared storage, then we
1219
would probably want to lock both the local tree, and the branch.
1221
# FIXME: We want to write out the hashcache only when the last lock on
1222
# this working copy is released. Peeking at the lock count is a bit
1223
# of a nasty hack; probably it's better to have a transaction object,
1224
# which can do some finalization when it's either successfully or
1225
# unsuccessfully completed. (Denys's original patch did that.)
1226
# RBC 20060206 hookinhg into transaction will couple lock and transaction
1227
# wrongly. Hookinh into unllock on the control files object is fine though.
1229
# TODO: split this per format so there is no ugly if block
1230
if self._hashcache.needs_write and (
1231
# dedicated lock files
1232
self._control_files._lock_count==1 or
1234
(self._control_files is self.branch.control_files and
1235
self._control_files._lock_count==3)):
1236
self._hashcache.write()
1237
# reverse order of locking.
1238
result = self._control_files.unlock()
1240
self.branch.unlock()
1246
"""Update a working tree along its branch.
1248
This will update the branch if its bound too, which means we have multiple trees involved:
1249
The new basis tree of the master.
1250
The old basis tree of the branch.
1251
The old basis tree of the working tree.
1252
The current working tree state.
1253
pathologically all three may be different, and non ancestors of each other.
1254
Conceptually we want to:
1255
Preserve the wt.basis->wt.state changes
1256
Transform the wt.basis to the new master basis.
1257
Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
1258
Restore the wt.basis->wt.state changes.
1260
There isn't a single operation at the moment to do that, so we:
1261
Merge current state -> basis tree of the master w.r.t. the old tree basis.
1262
Do a 'normal' merge of the old branch basis if it is relevant.
1264
old_tip = self.branch.update()
1265
if old_tip is not None:
1266
self.add_pending_merge(old_tip)
1267
self.branch.lock_read()
1270
if self.last_revision() != self.branch.last_revision():
1271
# merge tree state up to new branch tip.
1272
basis = self.basis_tree()
1273
to_tree = self.branch.basis_tree()
1274
result += merge_inner(self.branch,
1278
self.set_last_revision(self.branch.last_revision())
1279
if old_tip and old_tip != self.last_revision():
1280
# our last revision was not the prior branch last reivison
1281
# and we have converted that last revision to a pending merge.
1282
# base is somewhere between the branch tip now
1283
# and the now pending merge
1284
from bzrlib.revision import common_ancestor
1286
base_rev_id = common_ancestor(self.branch.last_revision(),
1288
self.branch.repository)
1289
except errors.NoCommonAncestor:
1291
base_tree = self.branch.repository.revision_tree(base_rev_id)
1292
other_tree = self.branch.repository.revision_tree(old_tip)
1293
result += merge_inner(self.branch,
1299
self.branch.unlock()
1302
def _write_inventory(self, inv):
1303
"""Write inventory as the current inventory."""
1305
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1307
self._control_files.put('inventory', sio)
1308
self._set_inventory(inv)
1309
mutter('wrote working inventory')
1311
def set_conflicts(self, arg):
1312
raise UnsupportedOperation(self.set_conflicts, self)
1315
def conflicts(self):
1316
conflicts = ConflictList()
1317
for conflicted in self._iter_conflicts():
1320
if file_kind(self.abspath(conflicted)) != "file":
1323
if e.errno == errno.ENOENT:
1328
for suffix in ('.THIS', '.OTHER'):
1330
kind = file_kind(self.abspath(conflicted+suffix))
1332
if e.errno == errno.ENOENT:
1340
ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1341
conflicts.append(Conflict.factory(ctype, path=conflicted,
1342
file_id=self.path2id(conflicted)))
1346
class WorkingTree3(WorkingTree):
1347
"""This is the Format 3 working tree.
1349
This differs from the base WorkingTree by:
1350
- having its own file lock
1351
- having its own last-revision property.
1353
This is new in bzr 0.8
1357
def last_revision(self):
1358
"""See WorkingTree.last_revision."""
1360
return self._control_files.get_utf8('last-revision').read()
1364
def _change_last_revision(self, revision_id):
1365
"""See WorkingTree._change_last_revision."""
1366
if revision_id is None or revision_id == NULL_REVISION:
1368
self._control_files._transport.delete('last-revision')
1369
except errors.NoSuchFile:
1374
self.branch.revision_history().index(revision_id)
1376
raise errors.NoSuchRevision(self.branch, revision_id)
1377
self._control_files.put_utf8('last-revision', revision_id)
1381
def set_conflicts(self, conflicts):
1382
self._put_rio('conflicts', conflicts.to_stanzas(),
1386
def conflicts(self):
1388
confile = self._control_files.get('conflicts')
1390
return ConflictList()
1392
if confile.next() != CONFLICT_HEADER_1 + '\n':
1393
raise ConflictFormatError()
1394
except StopIteration:
1395
raise ConflictFormatError()
1396
return ConflictList.from_stanzas(RioReader(confile))
1399
def get_conflicted_stem(path):
1400
for suffix in CONFLICT_SUFFIXES:
1401
if path.endswith(suffix):
1402
return path[:-len(suffix)]
1404
@deprecated_function(zero_eight)
1405
def is_control_file(filename):
1406
"""See WorkingTree.is_control_filename(filename)."""
1407
## FIXME: better check
1408
filename = normpath(filename)
1409
while filename != '':
1410
head, tail = os.path.split(filename)
1411
## mutter('check %r for control file' % ((head, tail),))
1414
if filename == head:
1420
class WorkingTreeFormat(object):
1421
"""An encapsulation of the initialization and open routines for a format.
1423
Formats provide three things:
1424
* An initialization routine,
1428
Formats are placed in an dict by their format string for reference
1429
during workingtree opening. Its not required that these be instances, they
1430
can be classes themselves with class methods - it simply depends on
1431
whether state is needed for a given format or not.
1433
Once a format is deprecated, just deprecate the initialize and open
1434
methods on the format class. Do not deprecate the object, as the
1435
object will be created every time regardless.
1438
_default_format = None
1439
"""The default format used for new trees."""
1442
"""The known formats."""
1445
def find_format(klass, a_bzrdir):
1446
"""Return the format for the working tree object in a_bzrdir."""
1448
transport = a_bzrdir.get_workingtree_transport(None)
1449
format_string = transport.get("format").read()
1450
return klass._formats[format_string]
1452
raise errors.NoWorkingTree(base=transport.base)
1454
raise errors.UnknownFormatError(format_string)
1457
def get_default_format(klass):
1458
"""Return the current default format."""
1459
return klass._default_format
1461
def get_format_string(self):
1462
"""Return the ASCII format string that identifies this format."""
1463
raise NotImplementedError(self.get_format_string)
1465
def get_format_description(self):
1466
"""Return the short description for this format."""
1467
raise NotImplementedError(self.get_format_description)
1469
def is_supported(self):
1470
"""Is this format supported?
1472
Supported formats can be initialized and opened.
1473
Unsupported formats may not support initialization or committing or
1474
some other features depending on the reason for not being supported.
1479
def register_format(klass, format):
1480
klass._formats[format.get_format_string()] = format
1483
def set_default_format(klass, format):
1484
klass._default_format = format
1487
def unregister_format(klass, format):
1488
assert klass._formats[format.get_format_string()] is format
1489
del klass._formats[format.get_format_string()]
1493
class WorkingTreeFormat2(WorkingTreeFormat):
1494
"""The second working tree format.
1496
This format modified the hash cache from the format 1 hash cache.
1499
def get_format_description(self):
1500
"""See WorkingTreeFormat.get_format_description()."""
1501
return "Working tree format 2"
1503
def initialize(self, a_bzrdir, revision_id=None):
1504
"""See WorkingTreeFormat.initialize()."""
1505
if not isinstance(a_bzrdir.transport, LocalTransport):
1506
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1507
branch = a_bzrdir.open_branch()
1508
if revision_id is not None:
1511
revision_history = branch.revision_history()
1513
position = revision_history.index(revision_id)
1515
raise errors.NoSuchRevision(branch, revision_id)
1516
branch.set_revision_history(revision_history[:position + 1])
1519
revision = branch.last_revision()
1521
wt = WorkingTree(a_bzrdir.root_transport.base,
1527
wt._write_inventory(inv)
1528
wt.set_root_id(inv.root.file_id)
1529
wt.set_last_revision(revision)
1530
wt.set_pending_merges([])
1531
build_tree(wt.basis_tree(), wt)
1535
super(WorkingTreeFormat2, self).__init__()
1536
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1538
def open(self, a_bzrdir, _found=False):
1539
"""Return the WorkingTree object for a_bzrdir
1541
_found is a private parameter, do not use it. It is used to indicate
1542
if format probing has already been done.
1545
# we are being called directly and must probe.
1546
raise NotImplementedError
1547
if not isinstance(a_bzrdir.transport, LocalTransport):
1548
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1549
return WorkingTree(a_bzrdir.root_transport.base,
1555
class WorkingTreeFormat3(WorkingTreeFormat):
1556
"""The second working tree format updated to record a format marker.
1559
- exists within a metadir controlling .bzr
1560
- includes an explicit version marker for the workingtree control
1561
files, separate from the BzrDir format
1562
- modifies the hash cache format
1564
- uses a LockDir to guard access to the repository
1567
def get_format_string(self):
1568
"""See WorkingTreeFormat.get_format_string()."""
1569
return "Bazaar-NG Working Tree format 3"
1571
def get_format_description(self):
1572
"""See WorkingTreeFormat.get_format_description()."""
1573
return "Working tree format 3"
1575
_lock_file_name = 'lock'
1576
_lock_class = LockDir
1578
def _open_control_files(self, a_bzrdir):
1579
transport = a_bzrdir.get_workingtree_transport(None)
1580
return LockableFiles(transport, self._lock_file_name,
1583
def initialize(self, a_bzrdir, revision_id=None):
1584
"""See WorkingTreeFormat.initialize().
1586
revision_id allows creating a working tree at a differnet
1587
revision than the branch is at.
1589
if not isinstance(a_bzrdir.transport, LocalTransport):
1590
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1591
transport = a_bzrdir.get_workingtree_transport(self)
1592
control_files = self._open_control_files(a_bzrdir)
1593
control_files.create_lock()
1594
control_files.lock_write()
1595
control_files.put_utf8('format', self.get_format_string())
1596
branch = a_bzrdir.open_branch()
1597
if revision_id is None:
1598
revision_id = branch.last_revision()
1600
wt = WorkingTree3(a_bzrdir.root_transport.base,
1606
_control_files=control_files)
1609
wt._write_inventory(inv)
1610
wt.set_root_id(inv.root.file_id)
1611
wt.set_last_revision(revision_id)
1612
wt.set_pending_merges([])
1613
build_tree(wt.basis_tree(), wt)
1616
control_files.unlock()
1620
super(WorkingTreeFormat3, self).__init__()
1621
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1623
def open(self, a_bzrdir, _found=False):
1624
"""Return the WorkingTree object for a_bzrdir
1626
_found is a private parameter, do not use it. It is used to indicate
1627
if format probing has already been done.
1630
# we are being called directly and must probe.
1631
raise NotImplementedError
1632
if not isinstance(a_bzrdir.transport, LocalTransport):
1633
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1634
control_files = self._open_control_files(a_bzrdir)
1635
return WorkingTree3(a_bzrdir.root_transport.base,
1639
_control_files=control_files)
1642
return self.get_format_string()
1645
# formats which have no format string are not discoverable
1646
# and not independently creatable, so are not registered.
1647
__default_format = WorkingTreeFormat3()
1648
WorkingTreeFormat.register_format(__default_format)
1649
WorkingTreeFormat.set_default_format(__default_format)
1650
_legacy_formats = [WorkingTreeFormat2(),
1654
class WorkingTreeTestProviderAdapter(object):
1655
"""A tool to generate a suite testing multiple workingtree formats at once.
1657
This is done by copying the test once for each transport and injecting
1658
the transport_server, transport_readonly_server, and workingtree_format
1659
classes into each copy. Each copy is also given a new id() to make it
1663
def __init__(self, transport_server, transport_readonly_server, formats):
1664
self._transport_server = transport_server
1665
self._transport_readonly_server = transport_readonly_server
1666
self._formats = formats
1668
def adapt(self, test):
1669
from bzrlib.tests import TestSuite
1670
result = TestSuite()
1671
for workingtree_format, bzrdir_format in self._formats:
1672
new_test = deepcopy(test)
1673
new_test.transport_server = self._transport_server
1674
new_test.transport_readonly_server = self._transport_readonly_server
1675
new_test.bzrdir_format = bzrdir_format
1676
new_test.workingtree_format = workingtree_format
1677
def make_new_test_id():
1678
new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1679
return lambda: new_id
1680
new_test.id = make_new_test_id()
1681
result.addTest(new_test)
b'\\ No newline at end of file'