14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# TODO: Don't allow WorkingTrees to be constructed for remote branches.
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.
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
51
from bzrlib.atomicfile import AtomicFile
52
from bzrlib.branch import (Branch,
54
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
55
import bzrlib.bzrdir as bzrdir
56
from bzrlib.decorators import needs_read_lock, needs_write_lock
57
import bzrlib.errors as errors
58
from bzrlib.errors import (BzrCheckError,
62
WeaveRevisionNotPresent,
66
MergeModifiedFormatError,
69
from bzrlib.inventory import InventoryEntry, Inventory
70
from bzrlib.lockable_files import LockableFiles, TransportLock
71
from bzrlib.lockdir import LockDir
72
from bzrlib.merge import merge_inner, transform_tree
73
from bzrlib.osutils import (
91
from bzrlib.progress import DummyProgress, ProgressPhase
92
from bzrlib.revision import NULL_REVISION
93
from bzrlib.rio import RioReader, rio_file, Stanza
94
from bzrlib.symbol_versioning import *
95
from bzrlib.textui import show_status
26
from errors import BzrCheckError
27
from trace import mutter
97
from bzrlib.transform import build_tree
98
from bzrlib.trace import mutter, note
99
from bzrlib.transport import get_transport
100
from bzrlib.transport.local import LocalTransport
101
import bzrlib.urlutils as urlutils
107
def _get_non_word_re():
108
"""Get the compiled regular expression for non-unicode words."""
110
if _non_word_re is None:
112
# TODO: jam 20060106 Currently the BZR codebase can't really handle
113
# unicode ids. There are a lot of code paths which don't
114
# expect them. And we need to do more serious testing
115
# before we enable unicode in ids.
116
#_non_word_re = re.compile(r'[^\w.]', re.UNICODE)
117
_non_word_re = re.compile(r'[^\w.]')
121
def gen_file_id(name):
122
"""Return new file id.
124
This should probably generate proper UUIDs, but for the moment we
125
cope with just randomness because running uuidgen every time is
128
from binascii import hexlify
129
from time import time
132
idx = name.rfind('/')
134
name = name[idx+1 : ]
135
idx = name.rfind('\\')
137
name = name[idx+1 : ]
139
# make it not a hidden file
140
name = name.lstrip('.')
142
# remove any wierd characters; we don't escape them but rather
144
non_word = _get_non_word_re()
145
name = non_word.sub('', name)
147
s = hexlify(rand_bytes(8))
148
return '-'.join((name, compact_date(time()), s))
152
"""Return a new tree-root file id."""
153
return gen_file_id('TREE_ROOT')
156
class TreeEntry(object):
157
"""An entry that implements the minium interface used by commands.
159
This needs further inspection, it may be better to have
160
InventoryEntries without ids - though that seems wrong. For now,
161
this is a parallel hierarchy to InventoryEntry, and needs to become
162
one of several things: decorates to that hierarchy, children of, or
164
Another note is that these objects are currently only used when there is
165
no InventoryEntry available - i.e. for unversioned objects.
166
Perhaps they should be UnversionedEntry et al. ? - RBC 20051003
169
def __eq__(self, other):
170
# yes, this us ugly, TODO: best practice __eq__ style.
171
return (isinstance(other, TreeEntry)
172
and other.__class__ == self.__class__)
174
def kind_character(self):
178
class TreeDirectory(TreeEntry):
179
"""See TreeEntry. This is a directory in a working tree."""
181
def __eq__(self, other):
182
return (isinstance(other, TreeDirectory)
183
and other.__class__ == self.__class__)
185
def kind_character(self):
189
class TreeFile(TreeEntry):
190
"""See TreeEntry. This is a regular file in a working tree."""
192
def __eq__(self, other):
193
return (isinstance(other, TreeFile)
194
and other.__class__ == self.__class__)
196
def kind_character(self):
200
class TreeLink(TreeEntry):
201
"""See TreeEntry. This is a symlink in a working tree."""
203
def __eq__(self, other):
204
return (isinstance(other, TreeLink)
205
and other.__class__ == self.__class__)
207
def kind_character(self):
29
211
class WorkingTree(bzrlib.tree.Tree):
30
212
"""Working copy tree.
35
217
It is possible for a `WorkingTree` to have a filename which is
36
218
not listed in the Inventory and vice versa.
38
def __init__(self, basedir, inv):
221
def __init__(self, basedir='.',
222
branch=DEPRECATED_PARAMETER,
228
"""Construct a WorkingTree for basedir.
230
If the branch is not supplied, it is opened automatically.
231
If the branch is supplied, it must be the branch for this basedir.
232
(branch.base is not cross checked, because for remote branches that
233
would be meaningless).
235
self._format = _format
236
self.bzrdir = _bzrdir
238
# not created via open etc.
239
warn("WorkingTree() is deprecated as of bzr version 0.8. "
240
"Please use bzrdir.open_workingtree or WorkingTree.open().",
243
wt = WorkingTree.open(basedir)
244
self._branch = wt.branch
245
self.basedir = wt.basedir
246
self._control_files = wt._control_files
247
self._hashcache = wt._hashcache
248
self._set_inventory(wt._inventory)
249
self._format = wt._format
250
self.bzrdir = wt.bzrdir
39
251
from bzrlib.hashcache import HashCache
40
252
from bzrlib.trace import note, mutter
43
self.basedir = basedir
44
self.path2id = inv.path2id
253
assert isinstance(basedir, basestring), \
254
"base directory %r is not a string" % basedir
255
basedir = safe_unicode(basedir)
256
mutter("opening working tree %r", basedir)
257
if deprecated_passed(branch):
259
warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
260
" Please use bzrdir.open_workingtree() or"
261
" WorkingTree.open().",
265
self._branch = branch
267
self._branch = self.bzrdir.open_branch()
268
assert isinstance(self.branch, Branch), \
269
"branch %r is not a Branch" % self.branch
270
self.basedir = realpath(basedir)
271
# if branch is at our basedir and is a format 6 or less
272
if isinstance(self._format, WorkingTreeFormat2):
273
# share control object
274
self._control_files = self.branch.control_files
276
# only ready for format 3
277
assert isinstance(self._format, WorkingTreeFormat3)
278
assert isinstance(_control_files, LockableFiles), \
279
"_control_files must be a LockableFiles, not %r" \
281
self._control_files = _control_files
46
282
# update the whole cache up front and write to disk if anything changed;
47
283
# in the future we might want to do this more selectively
48
hc = self._hashcache = HashCache(basedir)
284
# two possible ways offer themselves : in self._unlock, write the cache
285
# if needed, or, when the cache sees a change, append it to the hash
286
# cache file, and have the parser take the most recent entry for a
288
cache_filename = self.bzrdir.get_workingtree_transport(None).local_abspath('stat-cache')
289
hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
291
# is this scan needed ? it makes things kinda slow.
52
294
if hc.needs_write:
53
295
mutter("write hc")
58
if self._hashcache.needs_write:
59
self._hashcache.write()
298
if _inventory is None:
299
self._set_inventory(self.read_working_inventory())
301
self._set_inventory(_inventory)
304
fget=lambda self: self._branch,
305
doc="""The branch this WorkingTree is connected to.
307
This cannot be set - it is reflective of the actual disk structure
308
the working tree has been constructed from.
311
def break_lock(self):
312
"""Break a lock if one is present from another instance.
314
Uses the ui factory to ask for confirmation if the lock may be from
317
This will probe the repository for its lock as well.
319
self._control_files.break_lock()
320
self.branch.break_lock()
322
def _set_inventory(self, inv):
323
self._inventory = inv
324
self.path2id = self._inventory.path2id
326
def is_control_filename(self, filename):
327
"""True if filename is the name of a control file in this tree.
329
This is true IF and ONLY IF the filename is part of the meta data
330
that bzr controls in this tree. I.E. a random .bzr directory placed
331
on disk will not be a control file for this tree.
334
url = urlutils.local_path_to_url(self.abspath(filename))
335
self.bzrdir.transport.relpath(url)
337
except errors.PathNotChild:
341
def open(path=None, _unsupported=False):
342
"""Open an existing working tree at path.
346
path = os.path.getcwdu()
347
control = bzrdir.BzrDir.open(path, _unsupported)
348
return control.open_workingtree(_unsupported)
351
def open_containing(path=None):
352
"""Open an existing working tree which has its root about path.
354
This probes for a working tree at path and searches upwards from there.
356
Basically we keep looking up until we find the control directory or
357
run into /. If there isn't one, raises NotBranchError.
358
TODO: give this a new exception.
359
If there is one, it is returned, along with the unused portion of path.
361
:return: The WorkingTree that contains 'path', and the rest of path
365
control, relpath = bzrdir.BzrDir.open_containing(path)
367
return control.open_workingtree(), relpath
370
def open_downlevel(path=None):
371
"""Open an unsupported working tree.
373
Only intended for advanced situations like upgrading part of a bzrdir.
375
return WorkingTree.open(path, _unsupported=True)
62
377
def __iter__(self):
63
378
"""Iterate through file_ids for this tree.
89
458
def get_file_byname(self, filename):
90
459
return file(self.abspath(filename), 'rb')
461
def get_root_id(self):
462
"""Return the id of this trees root"""
463
inv = self.read_working_inventory()
464
return inv.root.file_id
92
466
def _get_store_filename(self, file_id):
93
## XXX: badly named; this isn't in the store at all
94
return self.abspath(self.id2path(file_id))
467
## XXX: badly named; this is not in the store at all
468
return self.abspath(self.id2path(file_id))
471
def clone(self, to_bzrdir, revision_id=None, basis=None):
472
"""Duplicate this working tree into to_bzr, including all state.
474
Specifically modified files are kept as modified, but
475
ignored and unknown files are discarded.
477
If you want to make a new line of development, see bzrdir.sprout()
480
If not None, the cloned tree will have its last revision set to
481
revision, and and difference between the source trees last revision
482
and this one merged in.
485
If not None, a closer copy of a tree which may have some files in
486
common, and which file content should be preferentially copied from.
488
# assumes the target bzr dir format is compatible.
489
result = self._format.initialize(to_bzrdir)
490
self.copy_content_into(result, revision_id)
494
def copy_content_into(self, tree, revision_id=None):
495
"""Copy the current content and user files of this tree into tree."""
496
if revision_id is None:
497
transform_tree(tree, self)
499
# TODO now merge from tree.last_revision to revision
500
transform_tree(tree, self)
501
tree.set_last_revision(revision_id)
504
def commit(self, message=None, revprops=None, *args, **kwargs):
505
# avoid circular imports
506
from bzrlib.commit import Commit
509
if not 'branch-nick' in revprops:
510
revprops['branch-nick'] = self.branch.nick
511
# args for wt.commit start at message from the Commit.commit method,
512
# but with branch a kwarg now, passing in args as is results in the
513
#message being used for the branch
514
args = (DEPRECATED_PARAMETER, message, ) + args
515
Commit().commit(working_tree=self, revprops=revprops, *args, **kwargs)
516
self._set_inventory(self.read_working_inventory())
518
def id2abspath(self, file_id):
519
return self.abspath(self.id2path(file_id))
97
521
def has_id(self, file_id):
98
522
# files that have been deleted are excluded
99
523
inv = self._inventory
100
524
if not inv.has_id(file_id):
102
526
path = inv.id2path(file_id)
103
return os.path.exists(self.abspath(path))
527
return bzrlib.osutils.lexists(self.abspath(path))
529
def has_or_had_id(self, file_id):
530
if file_id == self.inventory.root.file_id:
532
return self.inventory.has_id(file_id)
106
534
__contains__ = has_id
109
536
def get_file_size(self, file_id):
110
# is this still called?
111
raise NotImplementedError()
537
return os.path.getsize(self.id2abspath(file_id))
114
540
def get_file_sha1(self, file_id):
115
541
path = self._inventory.id2path(file_id)
116
542
return self._hashcache.get_sha1(path)
544
def is_executable(self, file_id):
545
if not supports_executable():
546
return self._inventory[file_id].executable
548
path = self._inventory.id2path(file_id)
549
mode = os.lstat(self.abspath(path)).st_mode
550
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
553
def add(self, files, ids=None):
554
"""Make files versioned.
556
Note that the command line normally calls smart_add instead,
557
which can automatically recurse.
559
This adds the files to the inventory, so that they will be
560
recorded by the next commit.
563
List of paths to add, relative to the base of the tree.
566
If set, use these instead of automatically generated ids.
567
Must be the same length as the list of files, but may
568
contain None for ids that are to be autogenerated.
570
TODO: Perhaps have an option to add the ids even if the files do
573
TODO: Perhaps callback with the ids and paths as they're added.
575
# TODO: Re-adding a file that is removed in the working copy
576
# should probably put it back with the previous ID.
577
if isinstance(files, basestring):
578
assert(ids is None or isinstance(ids, basestring))
584
ids = [None] * len(files)
586
assert(len(ids) == len(files))
588
inv = self.read_working_inventory()
589
for f,file_id in zip(files, ids):
590
if self.is_control_filename(f):
591
raise BzrError("cannot add control file %s" % quotefn(f))
596
raise BzrError("cannot add top-level %r" % f)
598
fullpath = normpath(self.abspath(f))
601
kind = file_kind(fullpath)
603
if e.errno == errno.ENOENT:
604
raise NoSuchFile(fullpath)
605
# maybe something better?
606
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
608
if not InventoryEntry.versionable_kind(kind):
609
raise BzrError('cannot add: not a versionable file ('
610
'i.e. regular file, symlink or directory): %s' % quotefn(f))
613
file_id = gen_file_id(f)
614
inv.add_path(f, kind=kind, file_id=file_id)
616
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
617
self._write_inventory(inv)
620
def add_pending_merge(self, *revision_ids):
621
# TODO: Perhaps should check at this point that the
622
# history of the revision is actually present?
623
p = self.pending_merges()
625
for rev_id in revision_ids:
631
self.set_pending_merges(p)
634
def pending_merges(self):
635
"""Return a list of pending merges.
637
These are revisions that have been merged into the working
638
directory but not yet committed.
641
merges_file = self._control_files.get_utf8('pending-merges')
643
if e.errno != errno.ENOENT:
647
for l in merges_file.readlines():
648
p.append(l.rstrip('\n'))
652
def set_pending_merges(self, rev_list):
653
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
656
def set_merge_modified(self, modified_hashes):
658
for file_id, hash in modified_hashes.iteritems():
659
yield Stanza(file_id=file_id, hash=hash)
660
self._put_rio('merge-hashes', iter_stanzas(), MERGE_MODIFIED_HEADER_1)
663
def _put_rio(self, filename, stanzas, header):
664
my_file = rio_file(stanzas, header)
665
self._control_files.put(filename, my_file)
668
def merge_modified(self):
670
hashfile = self._control_files.get('merge-hashes')
675
if hashfile.next() != MERGE_MODIFIED_HEADER_1 + '\n':
676
raise MergeModifiedFormatError()
677
except StopIteration:
678
raise MergeModifiedFormatError()
679
for s in RioReader(hashfile):
680
file_id = s.get("file_id")
681
if file_id not in self.inventory:
684
if hash == self.get_file_sha1(file_id):
685
merge_hashes[file_id] = hash
688
def get_symlink_target(self, file_id):
689
return os.readlink(self.id2abspath(file_id))
119
691
def file_class(self, filename):
120
692
if self.path2id(filename):
184
769
for ff in descend(fp, f_ie.file_id, fap):
187
for f in descend('', inv.root.file_id, self.basedir):
772
for f in descend(u'', inv.root.file_id, self.basedir):
776
def move(self, from_paths, to_name):
779
to_name must exist in the inventory.
781
If to_name exists and is a directory, the files are moved into
782
it, keeping their old names.
784
Note that to_name is only the last component of the new name;
785
this doesn't change the directory.
787
This returns a list of (from_path, to_path) pairs for each
791
## TODO: Option to move IDs only
792
assert not isinstance(from_paths, basestring)
794
to_abs = self.abspath(to_name)
795
if not isdir(to_abs):
796
raise BzrError("destination %r is not a directory" % to_abs)
797
if not self.has_filename(to_name):
798
raise BzrError("destination %r not in working directory" % to_abs)
799
to_dir_id = inv.path2id(to_name)
800
if to_dir_id == None and to_name != '':
801
raise BzrError("destination %r is not a versioned directory" % to_name)
802
to_dir_ie = inv[to_dir_id]
803
if to_dir_ie.kind not in ('directory', 'root_directory'):
804
raise BzrError("destination %r is not a directory" % to_abs)
806
to_idpath = inv.get_idpath(to_dir_id)
809
if not self.has_filename(f):
810
raise BzrError("%r does not exist in working tree" % f)
811
f_id = inv.path2id(f)
813
raise BzrError("%r is not versioned" % f)
814
name_tail = splitpath(f)[-1]
815
dest_path = appendpath(to_name, name_tail)
816
if self.has_filename(dest_path):
817
raise BzrError("destination %r already exists" % dest_path)
818
if f_id in to_idpath:
819
raise BzrError("can't move %r to a subdirectory of itself" % f)
821
# OK, so there's a race here, it's possible that someone will
822
# create a file in this interval and then the rename might be
823
# left half-done. But we should have caught most problems.
824
orig_inv = deepcopy(self.inventory)
827
name_tail = splitpath(f)[-1]
828
dest_path = appendpath(to_name, name_tail)
829
result.append((f, dest_path))
830
inv.rename(inv.path2id(f), to_dir_id, name_tail)
832
rename(self.abspath(f), self.abspath(dest_path))
834
raise BzrError("failed to rename %r to %r: %s" %
835
(f, dest_path, e[1]),
836
["rename rolled back"])
838
# restore the inventory on error
839
self._set_inventory(orig_inv)
841
self._write_inventory(inv)
845
def rename_one(self, from_rel, to_rel):
848
This can change the directory or the filename or both.
851
if not self.has_filename(from_rel):
852
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
853
if self.has_filename(to_rel):
854
raise BzrError("can't rename: new working file %r already exists" % to_rel)
856
file_id = inv.path2id(from_rel)
858
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
861
from_parent = entry.parent_id
862
from_name = entry.name
864
if inv.path2id(to_rel):
865
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
867
to_dir, to_tail = os.path.split(to_rel)
868
to_dir_id = inv.path2id(to_dir)
869
if to_dir_id == None and to_dir != '':
870
raise BzrError("can't determine destination directory id for %r" % to_dir)
872
mutter("rename_one:")
873
mutter(" file_id {%s}" % file_id)
874
mutter(" from_rel %r" % from_rel)
875
mutter(" to_rel %r" % to_rel)
876
mutter(" to_dir %r" % to_dir)
877
mutter(" to_dir_id {%s}" % to_dir_id)
879
inv.rename(file_id, to_dir_id, to_tail)
881
from_abs = self.abspath(from_rel)
882
to_abs = self.abspath(to_rel)
884
rename(from_abs, to_abs)
886
inv.rename(file_id, from_parent, from_name)
887
raise BzrError("failed to rename %r to %r: %s"
888
% (from_abs, to_abs, e[1]),
889
["rename rolled back"])
890
self._write_inventory(inv)
192
893
def unknowns(self):
894
"""Return all unknown files.
896
These are files in the working directory that are not versioned or
897
control files or ignored.
899
>>> from bzrlib.bzrdir import ScratchDir
900
>>> d = ScratchDir(files=['foo', 'foo~'])
901
>>> b = d.open_branch()
902
>>> tree = d.open_workingtree()
903
>>> map(str, tree.unknowns())
906
>>> list(b.unknowns())
908
>>> tree.remove('foo')
909
>>> list(b.unknowns())
193
912
for subp in self.extras():
194
913
if not self.is_ignored(subp):
916
@deprecated_method(zero_eight)
917
def iter_conflicts(self):
918
"""List all files in the tree that have text or content conflicts.
919
DEPRECATED. Use conflicts instead."""
920
return self._iter_conflicts()
922
def _iter_conflicts(self):
924
for path in (s[0] for s in self.list_files()):
925
stem = get_conflicted_stem(path)
928
if stem not in conflicted:
933
def pull(self, source, overwrite=False, stop_revision=None):
934
top_pb = bzrlib.ui.ui_factory.nested_progress_bar()
937
pp = ProgressPhase("Pull phase", 2, top_pb)
939
old_revision_history = self.branch.revision_history()
940
basis_tree = self.basis_tree()
941
count = self.branch.pull(source, overwrite, stop_revision)
942
new_revision_history = self.branch.revision_history()
943
if new_revision_history != old_revision_history:
945
if len(old_revision_history):
946
other_revision = old_revision_history[-1]
948
other_revision = None
949
repository = self.branch.repository
950
pb = bzrlib.ui.ui_factory.nested_progress_bar()
952
merge_inner(self.branch,
953
self.branch.basis_tree(),
959
self.set_last_revision(self.branch.last_revision())
198
965
def extras(self):
199
966
"""Yield all unknown files in this WorkingTree.
b'\\ No newline at end of file'
1061
def kind(self, file_id):
1062
return file_kind(self.id2abspath(file_id))
1065
def last_revision(self):
1066
"""Return the last revision id of this working tree.
1068
In early branch formats this was == the branch last_revision,
1069
but that cannot be relied upon - for working tree operations,
1070
always use tree.last_revision().
1072
return self.branch.last_revision()
1074
def is_locked(self):
1075
return self._control_files.is_locked()
1077
def lock_read(self):
1078
"""See Branch.lock_read, and WorkingTree.unlock."""
1079
self.branch.lock_read()
1081
return self._control_files.lock_read()
1083
self.branch.unlock()
1086
def lock_write(self):
1087
"""See Branch.lock_write, and WorkingTree.unlock."""
1088
self.branch.lock_write()
1090
return self._control_files.lock_write()
1092
self.branch.unlock()
1095
def get_physical_lock_status(self):
1096
return self._control_files.get_physical_lock_status()
1098
def _basis_inventory_name(self):
1099
return 'basis-inventory'
1102
def set_last_revision(self, new_revision):
1103
"""Change the last revision in the working tree."""
1104
if self._change_last_revision(new_revision):
1105
self._cache_basis_inventory(new_revision)
1107
def _change_last_revision(self, new_revision):
1108
"""Template method part of set_last_revision to perform the change.
1110
This is used to allow WorkingTree3 instances to not affect branch
1111
when their last revision is set.
1113
if new_revision is None:
1114
self.branch.set_revision_history([])
1116
# current format is locked in with the branch
1117
revision_history = self.branch.revision_history()
1119
position = revision_history.index(new_revision)
1121
raise errors.NoSuchRevision(self.branch, new_revision)
1122
self.branch.set_revision_history(revision_history[:position + 1])
1125
def _cache_basis_inventory(self, new_revision):
1126
"""Cache new_revision as the basis inventory."""
1128
# this double handles the inventory - unpack and repack -
1129
# but is easier to understand. We can/should put a conditional
1130
# in here based on whether the inventory is in the latest format
1131
# - perhaps we should repack all inventories on a repository
1133
inv = self.branch.repository.get_inventory(new_revision)
1134
inv.revision_id = new_revision
1135
xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1137
path = self._basis_inventory_name()
1138
self._control_files.put_utf8(path, xml)
1139
except WeaveRevisionNotPresent:
1142
def read_basis_inventory(self):
1143
"""Read the cached basis inventory."""
1144
path = self._basis_inventory_name()
1145
return self._control_files.get_utf8(path).read()
1148
def read_working_inventory(self):
1149
"""Read the working inventory."""
1150
# ElementTree does its own conversion from UTF-8, so open in
1152
result = bzrlib.xml5.serializer_v5.read_inventory(
1153
self._control_files.get('inventory'))
1154
self._set_inventory(result)
1158
def remove(self, files, verbose=False):
1159
"""Remove nominated files from the working inventory..
1161
This does not remove their text. This does not run on XXX on what? RBC
1163
TODO: Refuse to remove modified files unless --force is given?
1165
TODO: Do something useful with directories.
1167
TODO: Should this remove the text or not? Tough call; not
1168
removing may be useful and the user can just use use rm, and
1169
is the opposite of add. Removing it is consistent with most
1170
other tools. Maybe an option.
1172
## TODO: Normalize names
1173
## TODO: Remove nested loops; better scalability
1174
if isinstance(files, basestring):
1177
inv = self.inventory
1179
# do this before any modifications
1181
fid = inv.path2id(f)
1183
# TODO: Perhaps make this just a warning, and continue?
1184
# This tends to happen when
1185
raise NotVersionedError(path=f)
1186
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1188
# having remove it, it must be either ignored or unknown
1189
if self.is_ignored(f):
1193
show_status(new_status, inv[fid].kind, quotefn(f))
1196
self._write_inventory(inv)
1199
def revert(self, filenames, old_tree=None, backups=True,
1200
pb=DummyProgress()):
1201
from transform import revert
1202
from conflicts import resolve
1203
if old_tree is None:
1204
old_tree = self.basis_tree()
1205
conflicts = revert(self, old_tree, filenames, backups, pb)
1206
if not len(filenames):
1207
self.set_pending_merges([])
1210
resolve(self, filenames, ignore_misses=True)
1213
# XXX: This method should be deprecated in favour of taking in a proper
1214
# new Inventory object.
1216
def set_inventory(self, new_inventory_list):
1217
from bzrlib.inventory import (Inventory,
1222
inv = Inventory(self.get_root_id())
1223
for path, file_id, parent, kind in new_inventory_list:
1224
name = os.path.basename(path)
1227
# fixme, there should be a factory function inv,add_??
1228
if kind == 'directory':
1229
inv.add(InventoryDirectory(file_id, name, parent))
1230
elif kind == 'file':
1231
inv.add(InventoryFile(file_id, name, parent))
1232
elif kind == 'symlink':
1233
inv.add(InventoryLink(file_id, name, parent))
1235
raise BzrError("unknown kind %r" % kind)
1236
self._write_inventory(inv)
1239
def set_root_id(self, file_id):
1240
"""Set the root id for this tree."""
1241
inv = self.read_working_inventory()
1242
orig_root_id = inv.root.file_id
1243
del inv._byid[inv.root.file_id]
1244
inv.root.file_id = file_id
1245
inv._byid[inv.root.file_id] = inv.root
1248
if entry.parent_id == orig_root_id:
1249
entry.parent_id = inv.root.file_id
1250
self._write_inventory(inv)
1253
"""See Branch.unlock.
1255
WorkingTree locking just uses the Branch locking facilities.
1256
This is current because all working trees have an embedded branch
1257
within them. IF in the future, we were to make branch data shareable
1258
between multiple working trees, i.e. via shared storage, then we
1259
would probably want to lock both the local tree, and the branch.
1261
# FIXME: We want to write out the hashcache only when the last lock on
1262
# this working copy is released. Peeking at the lock count is a bit
1263
# of a nasty hack; probably it's better to have a transaction object,
1264
# which can do some finalization when it's either successfully or
1265
# unsuccessfully completed. (Denys's original patch did that.)
1266
# RBC 20060206 hookinhg into transaction will couple lock and transaction
1267
# wrongly. Hookinh into unllock on the control files object is fine though.
1269
# TODO: split this per format so there is no ugly if block
1270
if self._hashcache.needs_write and (
1271
# dedicated lock files
1272
self._control_files._lock_count==1 or
1274
(self._control_files is self.branch.control_files and
1275
self._control_files._lock_count==3)):
1276
self._hashcache.write()
1277
# reverse order of locking.
1279
return self._control_files.unlock()
1281
self.branch.unlock()
1285
"""Update a working tree along its branch.
1287
This will update the branch if its bound too, which means we have multiple trees involved:
1288
The new basis tree of the master.
1289
The old basis tree of the branch.
1290
The old basis tree of the working tree.
1291
The current working tree state.
1292
pathologically all three may be different, and non ancestors of each other.
1293
Conceptually we want to:
1294
Preserve the wt.basis->wt.state changes
1295
Transform the wt.basis to the new master basis.
1296
Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
1297
Restore the wt.basis->wt.state changes.
1299
There isn't a single operation at the moment to do that, so we:
1300
Merge current state -> basis tree of the master w.r.t. the old tree basis.
1301
Do a 'normal' merge of the old branch basis if it is relevant.
1303
old_tip = self.branch.update()
1304
if old_tip is not None:
1305
self.add_pending_merge(old_tip)
1306
self.branch.lock_read()
1309
if self.last_revision() != self.branch.last_revision():
1310
# merge tree state up to new branch tip.
1311
basis = self.basis_tree()
1312
to_tree = self.branch.basis_tree()
1313
result += merge_inner(self.branch,
1317
self.set_last_revision(self.branch.last_revision())
1318
if old_tip and old_tip != self.last_revision():
1319
# our last revision was not the prior branch last reivison
1320
# and we have converted that last revision to a pending merge.
1321
# base is somewhere between the branch tip now
1322
# and the now pending merge
1323
from bzrlib.revision import common_ancestor
1325
base_rev_id = common_ancestor(self.branch.last_revision(),
1327
self.branch.repository)
1328
except errors.NoCommonAncestor:
1330
base_tree = self.branch.repository.revision_tree(base_rev_id)
1331
other_tree = self.branch.repository.revision_tree(old_tip)
1332
result += merge_inner(self.branch,
1338
self.branch.unlock()
1341
def _write_inventory(self, inv):
1342
"""Write inventory as the current inventory."""
1344
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1346
self._control_files.put('inventory', sio)
1347
self._set_inventory(inv)
1348
mutter('wrote working inventory')
1350
def set_conflicts(self, arg):
1351
raise UnsupportedOperation(self.set_conflicts, self)
1354
def conflicts(self):
1355
conflicts = ConflictList()
1356
for conflicted in self._iter_conflicts():
1359
if file_kind(self.abspath(conflicted)) != "file":
1362
if e.errno == errno.ENOENT:
1367
for suffix in ('.THIS', '.OTHER'):
1369
kind = file_kind(self.abspath(conflicted+suffix))
1371
if e.errno == errno.ENOENT:
1379
ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1380
conflicts.append(Conflict.factory(ctype, path=conflicted,
1381
file_id=self.path2id(conflicted)))
1385
class WorkingTree3(WorkingTree):
1386
"""This is the Format 3 working tree.
1388
This differs from the base WorkingTree by:
1389
- having its own file lock
1390
- having its own last-revision property.
1392
This is new in bzr 0.8
1396
def last_revision(self):
1397
"""See WorkingTree.last_revision."""
1399
return self._control_files.get_utf8('last-revision').read()
1403
def _change_last_revision(self, revision_id):
1404
"""See WorkingTree._change_last_revision."""
1405
if revision_id is None or revision_id == NULL_REVISION:
1407
self._control_files._transport.delete('last-revision')
1408
except errors.NoSuchFile:
1413
self.branch.revision_history().index(revision_id)
1415
raise errors.NoSuchRevision(self.branch, revision_id)
1416
self._control_files.put_utf8('last-revision', revision_id)
1420
def set_conflicts(self, conflicts):
1421
self._put_rio('conflicts', conflicts.to_stanzas(),
1425
def conflicts(self):
1427
confile = self._control_files.get('conflicts')
1429
return ConflictList()
1431
if confile.next() != CONFLICT_HEADER_1 + '\n':
1432
raise ConflictFormatError()
1433
except StopIteration:
1434
raise ConflictFormatError()
1435
return ConflictList.from_stanzas(RioReader(confile))
1438
def get_conflicted_stem(path):
1439
for suffix in CONFLICT_SUFFIXES:
1440
if path.endswith(suffix):
1441
return path[:-len(suffix)]
1443
@deprecated_function(zero_eight)
1444
def is_control_file(filename):
1445
"""See WorkingTree.is_control_filename(filename)."""
1446
## FIXME: better check
1447
filename = normpath(filename)
1448
while filename != '':
1449
head, tail = os.path.split(filename)
1450
## mutter('check %r for control file' % ((head, tail),))
1453
if filename == head:
1459
class WorkingTreeFormat(object):
1460
"""An encapsulation of the initialization and open routines for a format.
1462
Formats provide three things:
1463
* An initialization routine,
1467
Formats are placed in an dict by their format string for reference
1468
during workingtree opening. Its not required that these be instances, they
1469
can be classes themselves with class methods - it simply depends on
1470
whether state is needed for a given format or not.
1472
Once a format is deprecated, just deprecate the initialize and open
1473
methods on the format class. Do not deprecate the object, as the
1474
object will be created every time regardless.
1477
_default_format = None
1478
"""The default format used for new trees."""
1481
"""The known formats."""
1484
def find_format(klass, a_bzrdir):
1485
"""Return the format for the working tree object in a_bzrdir."""
1487
transport = a_bzrdir.get_workingtree_transport(None)
1488
format_string = transport.get("format").read()
1489
return klass._formats[format_string]
1491
raise errors.NoWorkingTree(base=transport.base)
1493
raise errors.UnknownFormatError(format_string)
1496
def get_default_format(klass):
1497
"""Return the current default format."""
1498
return klass._default_format
1500
def get_format_string(self):
1501
"""Return the ASCII format string that identifies this format."""
1502
raise NotImplementedError(self.get_format_string)
1504
def get_format_description(self):
1505
"""Return the short description for this format."""
1506
raise NotImplementedError(self.get_format_description)
1508
def is_supported(self):
1509
"""Is this format supported?
1511
Supported formats can be initialized and opened.
1512
Unsupported formats may not support initialization or committing or
1513
some other features depending on the reason for not being supported.
1518
def register_format(klass, format):
1519
klass._formats[format.get_format_string()] = format
1522
def set_default_format(klass, format):
1523
klass._default_format = format
1526
def unregister_format(klass, format):
1527
assert klass._formats[format.get_format_string()] is format
1528
del klass._formats[format.get_format_string()]
1532
class WorkingTreeFormat2(WorkingTreeFormat):
1533
"""The second working tree format.
1535
This format modified the hash cache from the format 1 hash cache.
1538
def get_format_description(self):
1539
"""See WorkingTreeFormat.get_format_description()."""
1540
return "Working tree format 2"
1542
def stub_initialize_remote(self, control_files):
1543
"""As a special workaround create critical control files for a remote working tree
1545
This ensures that it can later be updated and dealt with locally,
1546
since BzrDirFormat6 and BzrDirFormat5 cannot represent dirs with
1547
no working tree. (See bug #43064).
1551
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1553
control_files.put('inventory', sio)
1555
control_files.put_utf8('pending-merges', '')
1558
def initialize(self, a_bzrdir, revision_id=None):
1559
"""See WorkingTreeFormat.initialize()."""
1560
if not isinstance(a_bzrdir.transport, LocalTransport):
1561
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1562
branch = a_bzrdir.open_branch()
1563
if revision_id is not None:
1566
revision_history = branch.revision_history()
1568
position = revision_history.index(revision_id)
1570
raise errors.NoSuchRevision(branch, revision_id)
1571
branch.set_revision_history(revision_history[:position + 1])
1574
revision = branch.last_revision()
1576
wt = WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
1582
wt._write_inventory(inv)
1583
wt.set_root_id(inv.root.file_id)
1584
wt.set_last_revision(revision)
1585
wt.set_pending_merges([])
1586
build_tree(wt.basis_tree(), wt)
1590
super(WorkingTreeFormat2, self).__init__()
1591
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1593
def open(self, a_bzrdir, _found=False):
1594
"""Return the WorkingTree object for a_bzrdir
1596
_found is a private parameter, do not use it. It is used to indicate
1597
if format probing has already been done.
1600
# we are being called directly and must probe.
1601
raise NotImplementedError
1602
if not isinstance(a_bzrdir.transport, LocalTransport):
1603
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1604
return WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
1610
class WorkingTreeFormat3(WorkingTreeFormat):
1611
"""The second working tree format updated to record a format marker.
1614
- exists within a metadir controlling .bzr
1615
- includes an explicit version marker for the workingtree control
1616
files, separate from the BzrDir format
1617
- modifies the hash cache format
1619
- uses a LockDir to guard access to the repository
1622
def get_format_string(self):
1623
"""See WorkingTreeFormat.get_format_string()."""
1624
return "Bazaar-NG Working Tree format 3"
1626
def get_format_description(self):
1627
"""See WorkingTreeFormat.get_format_description()."""
1628
return "Working tree format 3"
1630
_lock_file_name = 'lock'
1631
_lock_class = LockDir
1633
def _open_control_files(self, a_bzrdir):
1634
transport = a_bzrdir.get_workingtree_transport(None)
1635
return LockableFiles(transport, self._lock_file_name,
1638
def initialize(self, a_bzrdir, revision_id=None):
1639
"""See WorkingTreeFormat.initialize().
1641
revision_id allows creating a working tree at a differnet
1642
revision than the branch is at.
1644
if not isinstance(a_bzrdir.transport, LocalTransport):
1645
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1646
transport = a_bzrdir.get_workingtree_transport(self)
1647
control_files = self._open_control_files(a_bzrdir)
1648
control_files.create_lock()
1649
control_files.lock_write()
1650
control_files.put_utf8('format', self.get_format_string())
1651
branch = a_bzrdir.open_branch()
1652
if revision_id is None:
1653
revision_id = branch.last_revision()
1655
wt = WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1661
_control_files=control_files)
1664
wt._write_inventory(inv)
1665
wt.set_root_id(inv.root.file_id)
1666
wt.set_last_revision(revision_id)
1667
wt.set_pending_merges([])
1668
build_tree(wt.basis_tree(), wt)
1671
control_files.unlock()
1675
super(WorkingTreeFormat3, self).__init__()
1676
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1678
def open(self, a_bzrdir, _found=False):
1679
"""Return the WorkingTree object for a_bzrdir
1681
_found is a private parameter, do not use it. It is used to indicate
1682
if format probing has already been done.
1685
# we are being called directly and must probe.
1686
raise NotImplementedError
1687
if not isinstance(a_bzrdir.transport, LocalTransport):
1688
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1689
control_files = self._open_control_files(a_bzrdir)
1690
return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1694
_control_files=control_files)
1697
return self.get_format_string()
1700
# formats which have no format string are not discoverable
1701
# and not independently creatable, so are not registered.
1702
__default_format = WorkingTreeFormat3()
1703
WorkingTreeFormat.register_format(__default_format)
1704
WorkingTreeFormat.set_default_format(__default_format)
1705
_legacy_formats = [WorkingTreeFormat2(),
1709
class WorkingTreeTestProviderAdapter(object):
1710
"""A tool to generate a suite testing multiple workingtree formats at once.
1712
This is done by copying the test once for each transport and injecting
1713
the transport_server, transport_readonly_server, and workingtree_format
1714
classes into each copy. Each copy is also given a new id() to make it
1718
def __init__(self, transport_server, transport_readonly_server, formats):
1719
self._transport_server = transport_server
1720
self._transport_readonly_server = transport_readonly_server
1721
self._formats = formats
1723
def adapt(self, test):
1724
from bzrlib.tests import TestSuite
1725
result = TestSuite()
1726
for workingtree_format, bzrdir_format in self._formats:
1727
new_test = deepcopy(test)
1728
new_test.transport_server = self._transport_server
1729
new_test.transport_readonly_server = self._transport_readonly_server
1730
new_test.bzrdir_format = bzrdir_format
1731
new_test.workingtree_format = workingtree_format
1732
def make_new_test_id():
1733
new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1734
return lambda: new_id
1735
new_test.id = make_new_test_id()
1736
result.addTest(new_test)