14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# TODO: Don't allow WorkingTrees to be constructed for remote branches.
17
"""WorkingTree object and friends.
19
A WorkingTree represents the editable working copy of a branch.
20
Operations which represent the WorkingTree are also done here,
21
such as renaming or adding files. The WorkingTree has an inventory
22
which is updated by these operations. A commit produces a
23
new revision based on the workingtree and its inventory.
25
At the moment every WorkingTree has its own branch. Remote
26
WorkingTrees aren't supported.
28
To get a WorkingTree, call bzrdir.open_workingtree() or
29
WorkingTree.open(dir).
19
33
# 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.
34
# good idea, because destructors are considered poor taste in Python, and it's
35
# not predictable when it will be written out.
37
# TODO: Give the workingtree sole responsibility for the working inventory;
38
# remove the variable and references to it from the branch. This may require
39
# updating the commit code so as to update the inventory within the working
40
# copy, and making sure there's only one WorkingTree for any directory on disk.
41
# At the momenthey may alias the inventory and have old copies of it in memory.
43
from copy import deepcopy
44
from cStringIO import StringIO
51
from bzrlib.atomicfile import AtomicFile
52
from bzrlib.branch import (Branch,
54
import bzrlib.bzrdir as bzrdir
55
from bzrlib.decorators import needs_read_lock, needs_write_lock
56
import bzrlib.errors as errors
57
from bzrlib.errors import (BzrCheckError,
60
WeaveRevisionNotPresent,
64
from bzrlib.inventory import InventoryEntry, Inventory
65
from bzrlib.lockable_files import LockableFiles
66
from bzrlib.merge import merge_inner, transform_tree
67
from bzrlib.osutils import (appendpath,
84
from bzrlib.revision import NULL_REVISION
85
from bzrlib.symbol_versioning import *
86
from bzrlib.textui import show_status
26
from errors import BzrCheckError
27
from trace import mutter
88
from bzrlib.trace import mutter
89
from bzrlib.transform import build_tree
90
from bzrlib.transport import get_transport
91
from bzrlib.transport.local import LocalTransport
95
def gen_file_id(name):
96
"""Return new file id.
98
This should probably generate proper UUIDs, but for the moment we
99
cope with just randomness because running uuidgen every time is
102
from binascii import hexlify
103
from time import time
106
idx = name.rfind('/')
108
name = name[idx+1 : ]
109
idx = name.rfind('\\')
111
name = name[idx+1 : ]
113
# make it not a hidden file
114
name = name.lstrip('.')
116
# remove any wierd characters; we don't escape them but rather
118
name = re.sub(r'[^\w.]', '', name)
120
s = hexlify(rand_bytes(8))
121
return '-'.join((name, compact_date(time()), s))
125
"""Return a new tree-root file id."""
126
return gen_file_id('TREE_ROOT')
129
class TreeEntry(object):
130
"""An entry that implements the minium interface used by commands.
132
This needs further inspection, it may be better to have
133
InventoryEntries without ids - though that seems wrong. For now,
134
this is a parallel hierarchy to InventoryEntry, and needs to become
135
one of several things: decorates to that hierarchy, children of, or
137
Another note is that these objects are currently only used when there is
138
no InventoryEntry available - i.e. for unversioned objects.
139
Perhaps they should be UnversionedEntry et al. ? - RBC 20051003
142
def __eq__(self, other):
143
# yes, this us ugly, TODO: best practice __eq__ style.
144
return (isinstance(other, TreeEntry)
145
and other.__class__ == self.__class__)
147
def kind_character(self):
151
class TreeDirectory(TreeEntry):
152
"""See TreeEntry. This is a directory in a working tree."""
154
def __eq__(self, other):
155
return (isinstance(other, TreeDirectory)
156
and other.__class__ == self.__class__)
158
def kind_character(self):
162
class TreeFile(TreeEntry):
163
"""See TreeEntry. This is a regular file in a working tree."""
165
def __eq__(self, other):
166
return (isinstance(other, TreeFile)
167
and other.__class__ == self.__class__)
169
def kind_character(self):
173
class TreeLink(TreeEntry):
174
"""See TreeEntry. This is a symlink in a working tree."""
176
def __eq__(self, other):
177
return (isinstance(other, TreeLink)
178
and other.__class__ == self.__class__)
180
def kind_character(self):
29
184
class WorkingTree(bzrlib.tree.Tree):
30
185
"""Working copy tree.
35
190
It is possible for a `WorkingTree` to have a filename which is
36
191
not listed in the Inventory and vice versa.
38
def __init__(self, basedir, inv):
194
def __init__(self, basedir='.',
195
branch=DEPRECATED_PARAMETER,
201
"""Construct a WorkingTree for basedir.
203
If the branch is not supplied, it is opened automatically.
204
If the branch is supplied, it must be the branch for this basedir.
205
(branch.base is not cross checked, because for remote branches that
206
would be meaningless).
208
self._format = _format
209
self.bzrdir = _bzrdir
211
# not created via open etc.
212
warn("WorkingTree() is deprecated as of bzr version 0.8. "
213
"Please use bzrdir.open_workingtree or WorkingTree.open().",
216
wt = WorkingTree.open(basedir)
217
self.branch = wt.branch
218
self.basedir = wt.basedir
219
self._control_files = wt._control_files
220
self._hashcache = wt._hashcache
221
self._set_inventory(wt._inventory)
222
self._format = wt._format
223
self.bzrdir = wt.bzrdir
39
224
from bzrlib.hashcache import HashCache
40
225
from bzrlib.trace import note, mutter
43
self.basedir = basedir
44
self.path2id = inv.path2id
226
assert isinstance(basedir, basestring), \
227
"base directory %r is not a string" % basedir
228
basedir = safe_unicode(basedir)
229
mutter("openeing working tree %r", basedir)
230
if deprecated_passed(branch):
232
warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
233
" Please use bzrdir.open_workingtree() or WorkingTree.open().",
239
self.branch = self.bzrdir.open_branch()
240
assert isinstance(self.branch, Branch), \
241
"branch %r is not a Branch" % self.branch
242
self.basedir = realpath(basedir)
243
# if branch is at our basedir and is a format 6 or less
244
if isinstance(self._format, WorkingTreeFormat2):
245
# share control object
246
self._control_files = self.branch.control_files
247
elif _control_files is not None:
248
assert False, "not done yet"
249
# self._control_files = _control_files
251
# only ready for format 3
252
assert isinstance(self._format, WorkingTreeFormat3)
253
self._control_files = LockableFiles(
254
self.bzrdir.get_workingtree_transport(None),
46
257
# update the whole cache up front and write to disk if anything changed;
47
258
# in the future we might want to do this more selectively
48
hc = self._hashcache = HashCache(basedir)
259
# two possible ways offer themselves : in self._unlock, write the cache
260
# if needed, or, when the cache sees a change, append it to the hash
261
# cache file, and have the parser take the most recent entry for a
263
cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
264
hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
266
# is this scan needed ? it makes things kinda slow.
52
269
if hc.needs_write:
53
270
mutter("write hc")
58
if self._hashcache.needs_write:
59
self._hashcache.write()
273
if _inventory is None:
274
self._set_inventory(self.read_working_inventory())
276
self._set_inventory(_inventory)
278
def _set_inventory(self, inv):
279
self._inventory = inv
280
self.path2id = self._inventory.path2id
283
def open(path=None, _unsupported=False):
284
"""Open an existing working tree at path.
288
path = os.path.getcwdu()
289
control = bzrdir.BzrDir.open(path, _unsupported)
290
return control.open_workingtree(_unsupported)
293
def open_containing(path=None):
294
"""Open an existing working tree which has its root about path.
296
This probes for a working tree at path and searches upwards from there.
298
Basically we keep looking up until we find the control directory or
299
run into /. If there isn't one, raises NotBranchError.
300
TODO: give this a new exception.
301
If there is one, it is returned, along with the unused portion of path.
305
control, relpath = bzrdir.BzrDir.open_containing(path)
306
return control.open_workingtree(), relpath
309
def open_downlevel(path=None):
310
"""Open an unsupported working tree.
312
Only intended for advanced situations like upgrading part of a bzrdir.
314
return WorkingTree.open(path, _unsupported=True)
62
316
def __iter__(self):
63
317
"""Iterate through file_ids for this tree.
89
395
def get_file_byname(self, filename):
90
396
return file(self.abspath(filename), 'rb')
398
def get_root_id(self):
399
"""Return the id of this trees root"""
400
inv = self.read_working_inventory()
401
return inv.root.file_id
92
403
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))
404
## XXX: badly named; this is not in the store at all
405
return self.abspath(self.id2path(file_id))
408
def clone(self, to_bzrdir, revision_id=None, basis=None):
409
"""Duplicate this working tree into to_bzr, including all state.
411
Specifically modified files are kept as modified, but
412
ignored and unknown files are discarded.
414
If you want to make a new line of development, see bzrdir.sprout()
417
If not None, the cloned tree will have its last revision set to
418
revision, and and difference between the source trees last revision
419
and this one merged in.
422
If not None, a closer copy of a tree which may have some files in
423
common, and which file content should be preferentially copied from.
425
# assumes the target bzr dir format is compatible.
426
result = self._format.initialize(to_bzrdir)
427
self.copy_content_into(result, revision_id)
431
def copy_content_into(self, tree, revision_id=None):
432
"""Copy the current content and user files of this tree into tree."""
433
if revision_id is None:
434
transform_tree(tree, self)
436
# TODO now merge from tree.last_revision to revision
437
transform_tree(tree, self)
438
tree.set_last_revision(revision_id)
441
def commit(self, *args, **kwargs):
442
from bzrlib.commit import Commit
443
# args for wt.commit start at message from the Commit.commit method,
444
# but with branch a kwarg now, passing in args as is results in the
445
#message being used for the branch
446
args = (DEPRECATED_PARAMETER, ) + args
447
Commit().commit(working_tree=self, *args, **kwargs)
448
self._set_inventory(self.read_working_inventory())
450
def id2abspath(self, file_id):
451
return self.abspath(self.id2path(file_id))
97
453
def has_id(self, file_id):
98
454
# files that have been deleted are excluded
99
455
inv = self._inventory
100
456
if not inv.has_id(file_id):
102
458
path = inv.id2path(file_id)
103
return os.path.exists(self.abspath(path))
459
return bzrlib.osutils.lexists(self.abspath(path))
461
def has_or_had_id(self, file_id):
462
if file_id == self.inventory.root.file_id:
464
return self.inventory.has_id(file_id)
106
466
__contains__ = has_id
109
468
def get_file_size(self, file_id):
110
# is this still called?
111
raise NotImplementedError()
469
return os.path.getsize(self.id2abspath(file_id))
114
472
def get_file_sha1(self, file_id):
115
473
path = self._inventory.id2path(file_id)
116
474
return self._hashcache.get_sha1(path)
476
def is_executable(self, file_id):
477
if not supports_executable():
478
return self._inventory[file_id].executable
480
path = self._inventory.id2path(file_id)
481
mode = os.lstat(self.abspath(path)).st_mode
482
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
485
def add(self, files, ids=None):
486
"""Make files versioned.
488
Note that the command line normally calls smart_add instead,
489
which can automatically recurse.
491
This adds the files to the inventory, so that they will be
492
recorded by the next commit.
495
List of paths to add, relative to the base of the tree.
498
If set, use these instead of automatically generated ids.
499
Must be the same length as the list of files, but may
500
contain None for ids that are to be autogenerated.
502
TODO: Perhaps have an option to add the ids even if the files do
505
TODO: Perhaps callback with the ids and paths as they're added.
507
# TODO: Re-adding a file that is removed in the working copy
508
# should probably put it back with the previous ID.
509
if isinstance(files, basestring):
510
assert(ids is None or isinstance(ids, basestring))
516
ids = [None] * len(files)
518
assert(len(ids) == len(files))
520
inv = self.read_working_inventory()
521
for f,file_id in zip(files, ids):
522
if is_control_file(f):
523
raise BzrError("cannot add control file %s" % quotefn(f))
528
raise BzrError("cannot add top-level %r" % f)
530
fullpath = normpath(self.abspath(f))
533
kind = file_kind(fullpath)
535
if e.errno == errno.ENOENT:
536
raise NoSuchFile(fullpath)
537
# maybe something better?
538
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
540
if not InventoryEntry.versionable_kind(kind):
541
raise BzrError('cannot add: not a versionable file ('
542
'i.e. regular file, symlink or directory): %s' % quotefn(f))
545
file_id = gen_file_id(f)
546
inv.add_path(f, kind=kind, file_id=file_id)
548
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
549
self._write_inventory(inv)
552
def add_pending_merge(self, *revision_ids):
553
# TODO: Perhaps should check at this point that the
554
# history of the revision is actually present?
555
p = self.pending_merges()
557
for rev_id in revision_ids:
563
self.set_pending_merges(p)
566
def pending_merges(self):
567
"""Return a list of pending merges.
569
These are revisions that have been merged into the working
570
directory but not yet committed.
573
merges_file = self._control_files.get_utf8('pending-merges')
575
if e.errno != errno.ENOENT:
579
for l in merges_file.readlines():
580
p.append(l.rstrip('\n'))
584
def set_pending_merges(self, rev_list):
585
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
587
def get_symlink_target(self, file_id):
588
return os.readlink(self.id2abspath(file_id))
119
590
def file_class(self, filename):
120
591
if self.path2id(filename):
184
664
for ff in descend(fp, f_ie.file_id, fap):
187
for f in descend('', inv.root.file_id, self.basedir):
667
for f in descend(u'', inv.root.file_id, self.basedir):
671
def move(self, from_paths, to_name):
674
to_name must exist in the inventory.
676
If to_name exists and is a directory, the files are moved into
677
it, keeping their old names.
679
Note that to_name is only the last component of the new name;
680
this doesn't change the directory.
682
This returns a list of (from_path, to_path) pairs for each
686
## TODO: Option to move IDs only
687
assert not isinstance(from_paths, basestring)
689
to_abs = self.abspath(to_name)
690
if not isdir(to_abs):
691
raise BzrError("destination %r is not a directory" % to_abs)
692
if not self.has_filename(to_name):
693
raise BzrError("destination %r not in working directory" % to_abs)
694
to_dir_id = inv.path2id(to_name)
695
if to_dir_id == None and to_name != '':
696
raise BzrError("destination %r is not a versioned directory" % to_name)
697
to_dir_ie = inv[to_dir_id]
698
if to_dir_ie.kind not in ('directory', 'root_directory'):
699
raise BzrError("destination %r is not a directory" % to_abs)
701
to_idpath = inv.get_idpath(to_dir_id)
704
if not self.has_filename(f):
705
raise BzrError("%r does not exist in working tree" % f)
706
f_id = inv.path2id(f)
708
raise BzrError("%r is not versioned" % f)
709
name_tail = splitpath(f)[-1]
710
dest_path = appendpath(to_name, name_tail)
711
if self.has_filename(dest_path):
712
raise BzrError("destination %r already exists" % dest_path)
713
if f_id in to_idpath:
714
raise BzrError("can't move %r to a subdirectory of itself" % f)
716
# OK, so there's a race here, it's possible that someone will
717
# create a file in this interval and then the rename might be
718
# left half-done. But we should have caught most problems.
719
orig_inv = deepcopy(self.inventory)
722
name_tail = splitpath(f)[-1]
723
dest_path = appendpath(to_name, name_tail)
724
result.append((f, dest_path))
725
inv.rename(inv.path2id(f), to_dir_id, name_tail)
727
rename(self.abspath(f), self.abspath(dest_path))
729
raise BzrError("failed to rename %r to %r: %s" %
730
(f, dest_path, e[1]),
731
["rename rolled back"])
733
# restore the inventory on error
734
self._set_inventory(orig_inv)
736
self._write_inventory(inv)
740
def rename_one(self, from_rel, to_rel):
743
This can change the directory or the filename or both.
746
if not self.has_filename(from_rel):
747
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
748
if self.has_filename(to_rel):
749
raise BzrError("can't rename: new working file %r already exists" % to_rel)
751
file_id = inv.path2id(from_rel)
753
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
756
from_parent = entry.parent_id
757
from_name = entry.name
759
if inv.path2id(to_rel):
760
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
762
to_dir, to_tail = os.path.split(to_rel)
763
to_dir_id = inv.path2id(to_dir)
764
if to_dir_id == None and to_dir != '':
765
raise BzrError("can't determine destination directory id for %r" % to_dir)
767
mutter("rename_one:")
768
mutter(" file_id {%s}" % file_id)
769
mutter(" from_rel %r" % from_rel)
770
mutter(" to_rel %r" % to_rel)
771
mutter(" to_dir %r" % to_dir)
772
mutter(" to_dir_id {%s}" % to_dir_id)
774
inv.rename(file_id, to_dir_id, to_tail)
776
from_abs = self.abspath(from_rel)
777
to_abs = self.abspath(to_rel)
779
rename(from_abs, to_abs)
781
inv.rename(file_id, from_parent, from_name)
782
raise BzrError("failed to rename %r to %r: %s"
783
% (from_abs, to_abs, e[1]),
784
["rename rolled back"])
785
self._write_inventory(inv)
192
788
def unknowns(self):
789
"""Return all unknown files.
791
These are files in the working directory that are not versioned or
792
control files or ignored.
794
>>> from bzrlib.bzrdir import ScratchDir
795
>>> d = ScratchDir(files=['foo', 'foo~'])
796
>>> b = d.open_branch()
797
>>> tree = d.open_workingtree()
798
>>> map(str, tree.unknowns())
801
>>> list(b.unknowns())
803
>>> tree.remove('foo')
804
>>> list(b.unknowns())
193
807
for subp in self.extras():
194
808
if not self.is_ignored(subp):
811
def iter_conflicts(self):
813
for path in (s[0] for s in self.list_files()):
814
stem = get_conflicted_stem(path)
817
if stem not in conflicted:
822
def pull(self, source, overwrite=False, stop_revision=None):
825
old_revision_history = self.branch.revision_history()
826
count = self.branch.pull(source, overwrite, stop_revision)
827
new_revision_history = self.branch.revision_history()
828
if new_revision_history != old_revision_history:
829
if len(old_revision_history):
830
other_revision = old_revision_history[-1]
832
other_revision = None
833
repository = self.branch.repository
834
merge_inner(self.branch,
836
repository.revision_tree(other_revision),
838
self.set_last_revision(self.branch.last_revision())
198
843
def extras(self):
199
844
"""Yield all unknown files in this WorkingTree.
b'\\ No newline at end of file'
934
def kind(self, file_id):
935
return file_kind(self.id2abspath(file_id))
938
def last_revision(self):
939
"""Return the last revision id of this working tree.
941
In early branch formats this was == the branch last_revision,
942
but that cannot be relied upon - for working tree operations,
943
always use tree.last_revision().
945
return self.branch.last_revision()
948
"""See Branch.lock_read, and WorkingTree.unlock."""
949
self.branch.lock_read()
951
return self._control_files.lock_read()
956
def lock_write(self):
957
"""See Branch.lock_write, and WorkingTree.unlock."""
958
self.branch.lock_write()
960
return self._control_files.lock_write()
965
def _basis_inventory_name(self, revision_id):
966
return 'basis-inventory.%s' % revision_id
969
def set_last_revision(self, new_revision, old_revision=None):
970
"""Change the last revision in the working tree."""
971
self._remove_old_basis(old_revision)
972
if self._change_last_revision(new_revision):
973
self._cache_basis_inventory(new_revision)
975
def _change_last_revision(self, new_revision):
976
"""Template method part of set_last_revision to perform the change."""
977
if new_revision is None:
978
self.branch.set_revision_history([])
980
# current format is locked in with the branch
981
revision_history = self.branch.revision_history()
983
position = revision_history.index(new_revision)
985
raise errors.NoSuchRevision(self.branch, new_revision)
986
self.branch.set_revision_history(revision_history[:position + 1])
989
def _cache_basis_inventory(self, new_revision):
990
"""Cache new_revision as the basis inventory."""
992
xml = self.branch.repository.get_inventory_xml(new_revision)
993
path = self._basis_inventory_name(new_revision)
994
self._control_files.put_utf8(path, xml)
995
except WeaveRevisionNotPresent:
998
def _remove_old_basis(self, old_revision):
999
"""Remove the old basis inventory 'old_revision'."""
1000
if old_revision is not None:
1002
path = self._basis_inventory_name(old_revision)
1003
path = self._control_files._escape(path)
1004
self._control_files._transport.delete(path)
1008
def read_basis_inventory(self, revision_id):
1009
"""Read the cached basis inventory."""
1010
path = self._basis_inventory_name(revision_id)
1011
return self._control_files.get_utf8(path).read()
1014
def read_working_inventory(self):
1015
"""Read the working inventory."""
1016
# ElementTree does its own conversion from UTF-8, so open in
1018
result = bzrlib.xml5.serializer_v5.read_inventory(
1019
self._control_files.get('inventory'))
1020
self._set_inventory(result)
1024
def remove(self, files, verbose=False):
1025
"""Remove nominated files from the working inventory..
1027
This does not remove their text. This does not run on XXX on what? RBC
1029
TODO: Refuse to remove modified files unless --force is given?
1031
TODO: Do something useful with directories.
1033
TODO: Should this remove the text or not? Tough call; not
1034
removing may be useful and the user can just use use rm, and
1035
is the opposite of add. Removing it is consistent with most
1036
other tools. Maybe an option.
1038
## TODO: Normalize names
1039
## TODO: Remove nested loops; better scalability
1040
if isinstance(files, basestring):
1043
inv = self.inventory
1045
# do this before any modifications
1047
fid = inv.path2id(f)
1049
# TODO: Perhaps make this just a warning, and continue?
1050
# This tends to happen when
1051
raise NotVersionedError(path=f)
1052
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1054
# having remove it, it must be either ignored or unknown
1055
if self.is_ignored(f):
1059
show_status(new_status, inv[fid].kind, quotefn(f))
1062
self._write_inventory(inv)
1065
def revert(self, filenames, old_tree=None, backups=True):
1066
from transform import revert
1067
if old_tree is None:
1068
old_tree = self.basis_tree()
1069
revert(self, old_tree, filenames, backups)
1070
if not len(filenames):
1071
self.set_pending_merges([])
1074
def set_inventory(self, new_inventory_list):
1075
from bzrlib.inventory import (Inventory,
1080
inv = Inventory(self.get_root_id())
1081
for path, file_id, parent, kind in new_inventory_list:
1082
name = os.path.basename(path)
1085
# fixme, there should be a factory function inv,add_??
1086
if kind == 'directory':
1087
inv.add(InventoryDirectory(file_id, name, parent))
1088
elif kind == 'file':
1089
inv.add(InventoryFile(file_id, name, parent))
1090
elif kind == 'symlink':
1091
inv.add(InventoryLink(file_id, name, parent))
1093
raise BzrError("unknown kind %r" % kind)
1094
self._write_inventory(inv)
1097
def set_root_id(self, file_id):
1098
"""Set the root id for this tree."""
1099
inv = self.read_working_inventory()
1100
orig_root_id = inv.root.file_id
1101
del inv._byid[inv.root.file_id]
1102
inv.root.file_id = file_id
1103
inv._byid[inv.root.file_id] = inv.root
1106
if entry.parent_id == orig_root_id:
1107
entry.parent_id = inv.root.file_id
1108
self._write_inventory(inv)
1111
"""See Branch.unlock.
1113
WorkingTree locking just uses the Branch locking facilities.
1114
This is current because all working trees have an embedded branch
1115
within them. IF in the future, we were to make branch data shareable
1116
between multiple working trees, i.e. via shared storage, then we
1117
would probably want to lock both the local tree, and the branch.
1119
# FIXME: We want to write out the hashcache only when the last lock on
1120
# this working copy is released. Peeking at the lock count is a bit
1121
# of a nasty hack; probably it's better to have a transaction object,
1122
# which can do some finalization when it's either successfully or
1123
# unsuccessfully completed. (Denys's original patch did that.)
1124
# RBC 20060206 hookinhg into transaction will couple lock and transaction
1125
# wrongly. Hookinh into unllock on the control files object is fine though.
1127
# TODO: split this per format so there is no ugly if block
1128
if self._hashcache.needs_write and (
1129
self._control_files._lock_count==1 or
1130
(self._control_files is self.branch.control_files and
1131
self._control_files._lock_count==2)):
1132
self._hashcache.write()
1133
# reverse order of locking.
1134
result = self._control_files.unlock()
1136
self.branch.unlock()
1142
self.branch.lock_read()
1144
if self.last_revision() == self.branch.last_revision():
1146
basis = self.basis_tree()
1147
to_tree = self.branch.basis_tree()
1148
result = merge_inner(self.branch,
1152
self.set_last_revision(self.branch.last_revision())
1155
self.branch.unlock()
1158
def _write_inventory(self, inv):
1159
"""Write inventory as the current inventory."""
1161
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1163
self._control_files.put('inventory', sio)
1164
self._set_inventory(inv)
1165
mutter('wrote working inventory')
1168
class WorkingTree3(WorkingTree):
1169
"""This is the Format 3 working tree.
1171
This differs from the base WorkingTree by:
1172
- having its own file lock
1173
- having its own last-revision property.
1177
def last_revision(self):
1178
"""See WorkingTree.last_revision."""
1180
return self._control_files.get_utf8('last-revision').read()
1184
def _change_last_revision(self, revision_id):
1185
"""See WorkingTree._change_last_revision."""
1186
if revision_id is None or revision_id == NULL_REVISION:
1188
self._control_files._transport.delete('last-revision')
1189
except errors.NoSuchFile:
1194
self.branch.revision_history().index(revision_id)
1196
raise errors.NoSuchRevision(self.branch, revision_id)
1197
self._control_files.put_utf8('last-revision', revision_id)
1201
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1202
def get_conflicted_stem(path):
1203
for suffix in CONFLICT_SUFFIXES:
1204
if path.endswith(suffix):
1205
return path[:-len(suffix)]
1207
def is_control_file(filename):
1208
## FIXME: better check
1209
filename = normpath(filename)
1210
while filename != '':
1211
head, tail = os.path.split(filename)
1212
## mutter('check %r for control file' % ((head, tail),))
1213
if tail == bzrlib.BZRDIR:
1215
if filename == head:
1221
class WorkingTreeFormat(object):
1222
"""An encapsulation of the initialization and open routines for a format.
1224
Formats provide three things:
1225
* An initialization routine,
1229
Formats are placed in an dict by their format string for reference
1230
during workingtree opening. Its not required that these be instances, they
1231
can be classes themselves with class methods - it simply depends on
1232
whether state is needed for a given format or not.
1234
Once a format is deprecated, just deprecate the initialize and open
1235
methods on the format class. Do not deprecate the object, as the
1236
object will be created every time regardless.
1239
_default_format = None
1240
"""The default format used for new trees."""
1243
"""The known formats."""
1246
def find_format(klass, a_bzrdir):
1247
"""Return the format for the working tree object in a_bzrdir."""
1249
transport = a_bzrdir.get_workingtree_transport(None)
1250
format_string = transport.get("format").read()
1251
return klass._formats[format_string]
1253
raise errors.NoWorkingTree(base=transport.base)
1255
raise errors.UnknownFormatError(format_string)
1258
def get_default_format(klass):
1259
"""Return the current default format."""
1260
return klass._default_format
1262
def get_format_string(self):
1263
"""Return the ASCII format string that identifies this format."""
1264
raise NotImplementedError(self.get_format_string)
1266
def is_supported(self):
1267
"""Is this format supported?
1269
Supported formats can be initialized and opened.
1270
Unsupported formats may not support initialization or committing or
1271
some other features depending on the reason for not being supported.
1276
def register_format(klass, format):
1277
klass._formats[format.get_format_string()] = format
1280
def set_default_format(klass, format):
1281
klass._default_format = format
1284
def unregister_format(klass, format):
1285
assert klass._formats[format.get_format_string()] is format
1286
del klass._formats[format.get_format_string()]
1290
class WorkingTreeFormat2(WorkingTreeFormat):
1291
"""The second working tree format.
1293
This format modified the hash cache from the format 1 hash cache.
1296
def initialize(self, a_bzrdir, revision_id=None):
1297
"""See WorkingTreeFormat.initialize()."""
1298
if not isinstance(a_bzrdir.transport, LocalTransport):
1299
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1300
branch = a_bzrdir.open_branch()
1301
if revision_id is not None:
1304
revision_history = branch.revision_history()
1306
position = revision_history.index(revision_id)
1308
raise errors.NoSuchRevision(branch, revision_id)
1309
branch.set_revision_history(revision_history[:position + 1])
1312
revision = branch.last_revision()
1314
wt = WorkingTree(a_bzrdir.root_transport.base,
1320
wt._write_inventory(inv)
1321
wt.set_root_id(inv.root.file_id)
1322
wt.set_last_revision(revision)
1323
wt.set_pending_merges([])
1324
build_tree(wt.basis_tree(), wt)
1328
super(WorkingTreeFormat2, self).__init__()
1329
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1331
def open(self, a_bzrdir, _found=False):
1332
"""Return the WorkingTree object for a_bzrdir
1334
_found is a private parameter, do not use it. It is used to indicate
1335
if format probing has already been done.
1338
# we are being called directly and must probe.
1339
raise NotImplementedError
1340
if not isinstance(a_bzrdir.transport, LocalTransport):
1341
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1342
return WorkingTree(a_bzrdir.root_transport.base,
1348
class WorkingTreeFormat3(WorkingTreeFormat):
1349
"""The second working tree format updated to record a format marker.
1351
This format modified the hash cache from the format 1 hash cache.
1354
def get_format_string(self):
1355
"""See WorkingTreeFormat.get_format_string()."""
1356
return "Bazaar-NG Working Tree format 3"
1358
def initialize(self, a_bzrdir, revision_id=None):
1359
"""See WorkingTreeFormat.initialize().
1361
revision_id allows creating a working tree at a differnet
1362
revision than the branch is at.
1364
if not isinstance(a_bzrdir.transport, LocalTransport):
1365
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1366
transport = a_bzrdir.get_workingtree_transport(self)
1367
control_files = LockableFiles(transport, 'lock')
1368
control_files.put_utf8('format', self.get_format_string())
1369
branch = a_bzrdir.open_branch()
1370
if revision_id is None:
1371
revision_id = branch.last_revision()
1373
wt = WorkingTree3(a_bzrdir.root_transport.base,
1379
wt._write_inventory(inv)
1380
wt.set_root_id(inv.root.file_id)
1381
wt.set_last_revision(revision_id)
1382
wt.set_pending_merges([])
1383
build_tree(wt.basis_tree(), wt)
1387
super(WorkingTreeFormat3, self).__init__()
1388
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1390
def open(self, a_bzrdir, _found=False):
1391
"""Return the WorkingTree object for a_bzrdir
1393
_found is a private parameter, do not use it. It is used to indicate
1394
if format probing has already been done.
1397
# we are being called directly and must probe.
1398
raise NotImplementedError
1399
if not isinstance(a_bzrdir.transport, LocalTransport):
1400
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1401
return WorkingTree3(a_bzrdir.root_transport.base,
1407
# formats which have no format string are not discoverable
1408
# and not independently creatable, so are not registered.
1409
__default_format = WorkingTreeFormat3()
1410
WorkingTreeFormat.register_format(__default_format)
1411
WorkingTreeFormat.set_default_format(__default_format)
1412
_legacy_formats = [WorkingTreeFormat2(),
1416
class WorkingTreeTestProviderAdapter(object):
1417
"""A tool to generate a suite testing multiple workingtree formats at once.
1419
This is done by copying the test once for each transport and injecting
1420
the transport_server, transport_readonly_server, and workingtree_format
1421
classes into each copy. Each copy is also given a new id() to make it
1425
def __init__(self, transport_server, transport_readonly_server, formats):
1426
self._transport_server = transport_server
1427
self._transport_readonly_server = transport_readonly_server
1428
self._formats = formats
1430
def adapt(self, test):
1431
from bzrlib.tests import TestSuite
1432
result = TestSuite()
1433
for workingtree_format, bzrdir_format in self._formats:
1434
new_test = deepcopy(test)
1435
new_test.transport_server = self._transport_server
1436
new_test.transport_readonly_server = self._transport_readonly_server
1437
new_test.bzrdir_format = bzrdir_format
1438
new_test.workingtree_format = workingtree_format
1439
def make_new_test_id():
1440
new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1441
return lambda: new_id
1442
new_test.id = make_new_test_id()
1443
result.addTest(new_test)