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
28
from bzrlib.osutils import appendpath, file_kind, isdir, splitpath
29
from bzrlib.errors import BzrCheckError
30
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')
32
129
class TreeEntry(object):
33
130
"""An entry that implements the minium interface used by commands.
93
190
It is possible for a `WorkingTree` to have a filename which is
94
191
not listed in the Inventory and vice versa.
96
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
97
224
from bzrlib.hashcache import HashCache
98
225
from bzrlib.trace import note, mutter
100
self._inventory = inv
101
self.basedir = basedir
102
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("opening 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),
104
257
# update the whole cache up front and write to disk if anything changed;
105
258
# in the future we might want to do this more selectively
106
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.
110
269
if hc.needs_write:
111
270
mutter("write hc")
116
if self._hashcache.needs_write:
117
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
282
def is_control_filename(self, filename):
283
"""True if filename is the name of a control file in this tree.
285
This is true IF and ONLY IF the filename is part of the meta data
286
that bzr controls in this tree. I.E. a random .bzr directory placed
287
on disk will not be a control file for this tree.
290
self.bzrdir.transport.relpath(self.abspath(filename))
292
except errors.PathNotChild:
296
def open(path=None, _unsupported=False):
297
"""Open an existing working tree at path.
301
path = os.path.getcwdu()
302
control = bzrdir.BzrDir.open(path, _unsupported)
303
return control.open_workingtree(_unsupported)
306
def open_containing(path=None):
307
"""Open an existing working tree which has its root about path.
309
This probes for a working tree at path and searches upwards from there.
311
Basically we keep looking up until we find the control directory or
312
run into /. If there isn't one, raises NotBranchError.
313
TODO: give this a new exception.
314
If there is one, it is returned, along with the unused portion of path.
318
control, relpath = bzrdir.BzrDir.open_containing(path)
319
return control.open_workingtree(), relpath
322
def open_downlevel(path=None):
323
"""Open an unsupported working tree.
325
Only intended for advanced situations like upgrading part of a bzrdir.
327
return WorkingTree.open(path, _unsupported=True)
120
329
def __iter__(self):
121
330
"""Iterate through file_ids for this tree.
164
471
path = inv.id2path(file_id)
165
472
return bzrlib.osutils.lexists(self.abspath(path))
474
def has_or_had_id(self, file_id):
475
if file_id == self.inventory.root.file_id:
477
return self.inventory.has_id(file_id)
168
479
__contains__ = has_id
171
481
def get_file_size(self, file_id):
172
482
return os.path.getsize(self.id2abspath(file_id))
174
485
def get_file_sha1(self, file_id):
175
486
path = self._inventory.id2path(file_id)
176
487
return self._hashcache.get_sha1(path)
179
489
def is_executable(self, file_id):
490
if not supports_executable():
181
491
return self._inventory[file_id].executable
183
493
path = self._inventory.id2path(file_id)
184
494
mode = os.lstat(self.abspath(path)).st_mode
185
495
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
498
def add(self, files, ids=None):
499
"""Make files versioned.
501
Note that the command line normally calls smart_add instead,
502
which can automatically recurse.
504
This adds the files to the inventory, so that they will be
505
recorded by the next commit.
508
List of paths to add, relative to the base of the tree.
511
If set, use these instead of automatically generated ids.
512
Must be the same length as the list of files, but may
513
contain None for ids that are to be autogenerated.
515
TODO: Perhaps have an option to add the ids even if the files do
518
TODO: Perhaps callback with the ids and paths as they're added.
520
# TODO: Re-adding a file that is removed in the working copy
521
# should probably put it back with the previous ID.
522
if isinstance(files, basestring):
523
assert(ids is None or isinstance(ids, basestring))
529
ids = [None] * len(files)
531
assert(len(ids) == len(files))
533
inv = self.read_working_inventory()
534
for f,file_id in zip(files, ids):
535
if self.is_control_filename(f):
536
raise BzrError("cannot add control file %s" % quotefn(f))
541
raise BzrError("cannot add top-level %r" % f)
543
fullpath = normpath(self.abspath(f))
546
kind = file_kind(fullpath)
548
if e.errno == errno.ENOENT:
549
raise NoSuchFile(fullpath)
550
# maybe something better?
551
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
553
if not InventoryEntry.versionable_kind(kind):
554
raise BzrError('cannot add: not a versionable file ('
555
'i.e. regular file, symlink or directory): %s' % quotefn(f))
558
file_id = gen_file_id(f)
559
inv.add_path(f, kind=kind, file_id=file_id)
561
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
562
self._write_inventory(inv)
565
def add_pending_merge(self, *revision_ids):
566
# TODO: Perhaps should check at this point that the
567
# history of the revision is actually present?
568
p = self.pending_merges()
570
for rev_id in revision_ids:
576
self.set_pending_merges(p)
579
def pending_merges(self):
580
"""Return a list of pending merges.
582
These are revisions that have been merged into the working
583
directory but not yet committed.
586
merges_file = self._control_files.get_utf8('pending-merges')
588
if e.errno != errno.ENOENT:
592
for l in merges_file.readlines():
593
p.append(l.rstrip('\n'))
597
def set_pending_merges(self, rev_list):
598
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
187
600
def get_symlink_target(self, file_id):
188
601
return os.readlink(self.id2abspath(file_id))
265
679
for ff in descend(fp, f_ie.file_id, fap):
268
for f in descend('', inv.root.file_id, self.basedir):
682
for f in descend(u'', inv.root.file_id, self.basedir):
686
def move(self, from_paths, to_name):
689
to_name must exist in the inventory.
691
If to_name exists and is a directory, the files are moved into
692
it, keeping their old names.
694
Note that to_name is only the last component of the new name;
695
this doesn't change the directory.
697
This returns a list of (from_path, to_path) pairs for each
701
## TODO: Option to move IDs only
702
assert not isinstance(from_paths, basestring)
704
to_abs = self.abspath(to_name)
705
if not isdir(to_abs):
706
raise BzrError("destination %r is not a directory" % to_abs)
707
if not self.has_filename(to_name):
708
raise BzrError("destination %r not in working directory" % to_abs)
709
to_dir_id = inv.path2id(to_name)
710
if to_dir_id == None and to_name != '':
711
raise BzrError("destination %r is not a versioned directory" % to_name)
712
to_dir_ie = inv[to_dir_id]
713
if to_dir_ie.kind not in ('directory', 'root_directory'):
714
raise BzrError("destination %r is not a directory" % to_abs)
716
to_idpath = inv.get_idpath(to_dir_id)
719
if not self.has_filename(f):
720
raise BzrError("%r does not exist in working tree" % f)
721
f_id = inv.path2id(f)
723
raise BzrError("%r is not versioned" % f)
724
name_tail = splitpath(f)[-1]
725
dest_path = appendpath(to_name, name_tail)
726
if self.has_filename(dest_path):
727
raise BzrError("destination %r already exists" % dest_path)
728
if f_id in to_idpath:
729
raise BzrError("can't move %r to a subdirectory of itself" % f)
731
# OK, so there's a race here, it's possible that someone will
732
# create a file in this interval and then the rename might be
733
# left half-done. But we should have caught most problems.
734
orig_inv = deepcopy(self.inventory)
737
name_tail = splitpath(f)[-1]
738
dest_path = appendpath(to_name, name_tail)
739
result.append((f, dest_path))
740
inv.rename(inv.path2id(f), to_dir_id, name_tail)
742
rename(self.abspath(f), self.abspath(dest_path))
744
raise BzrError("failed to rename %r to %r: %s" %
745
(f, dest_path, e[1]),
746
["rename rolled back"])
748
# restore the inventory on error
749
self._set_inventory(orig_inv)
751
self._write_inventory(inv)
755
def rename_one(self, from_rel, to_rel):
758
This can change the directory or the filename or both.
761
if not self.has_filename(from_rel):
762
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
763
if self.has_filename(to_rel):
764
raise BzrError("can't rename: new working file %r already exists" % to_rel)
766
file_id = inv.path2id(from_rel)
768
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
771
from_parent = entry.parent_id
772
from_name = entry.name
774
if inv.path2id(to_rel):
775
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
777
to_dir, to_tail = os.path.split(to_rel)
778
to_dir_id = inv.path2id(to_dir)
779
if to_dir_id == None and to_dir != '':
780
raise BzrError("can't determine destination directory id for %r" % to_dir)
782
mutter("rename_one:")
783
mutter(" file_id {%s}" % file_id)
784
mutter(" from_rel %r" % from_rel)
785
mutter(" to_rel %r" % to_rel)
786
mutter(" to_dir %r" % to_dir)
787
mutter(" to_dir_id {%s}" % to_dir_id)
789
inv.rename(file_id, to_dir_id, to_tail)
791
from_abs = self.abspath(from_rel)
792
to_abs = self.abspath(to_rel)
794
rename(from_abs, to_abs)
796
inv.rename(file_id, from_parent, from_name)
797
raise BzrError("failed to rename %r to %r: %s"
798
% (from_abs, to_abs, e[1]),
799
["rename rolled back"])
800
self._write_inventory(inv)
273
803
def unknowns(self):
804
"""Return all unknown files.
806
These are files in the working directory that are not versioned or
807
control files or ignored.
809
>>> from bzrlib.bzrdir import ScratchDir
810
>>> d = ScratchDir(files=['foo', 'foo~'])
811
>>> b = d.open_branch()
812
>>> tree = d.open_workingtree()
813
>>> map(str, tree.unknowns())
816
>>> list(b.unknowns())
818
>>> tree.remove('foo')
819
>>> list(b.unknowns())
274
822
for subp in self.extras():
275
823
if not self.is_ignored(subp):
949
def kind(self, file_id):
950
return file_kind(self.id2abspath(file_id))
953
def last_revision(self):
954
"""Return the last revision id of this working tree.
956
In early branch formats this was == the branch last_revision,
957
but that cannot be relied upon - for working tree operations,
958
always use tree.last_revision().
960
return self.branch.last_revision()
963
"""See Branch.lock_read, and WorkingTree.unlock."""
964
self.branch.lock_read()
966
return self._control_files.lock_read()
971
def lock_write(self):
972
"""See Branch.lock_write, and WorkingTree.unlock."""
973
self.branch.lock_write()
975
return self._control_files.lock_write()
980
def _basis_inventory_name(self, revision_id):
981
return 'basis-inventory.%s' % revision_id
984
def set_last_revision(self, new_revision, old_revision=None):
985
"""Change the last revision in the working tree."""
986
self._remove_old_basis(old_revision)
987
if self._change_last_revision(new_revision):
988
self._cache_basis_inventory(new_revision)
990
def _change_last_revision(self, new_revision):
991
"""Template method part of set_last_revision to perform the change."""
992
if new_revision is None:
993
self.branch.set_revision_history([])
995
# current format is locked in with the branch
996
revision_history = self.branch.revision_history()
998
position = revision_history.index(new_revision)
1000
raise errors.NoSuchRevision(self.branch, new_revision)
1001
self.branch.set_revision_history(revision_history[:position + 1])
1004
def _cache_basis_inventory(self, new_revision):
1005
"""Cache new_revision as the basis inventory."""
1007
xml = self.branch.repository.get_inventory_xml(new_revision)
1008
path = self._basis_inventory_name(new_revision)
1009
self._control_files.put_utf8(path, xml)
1010
except WeaveRevisionNotPresent:
1013
def _remove_old_basis(self, old_revision):
1014
"""Remove the old basis inventory 'old_revision'."""
1015
if old_revision is not None:
1017
path = self._basis_inventory_name(old_revision)
1018
path = self._control_files._escape(path)
1019
self._control_files._transport.delete(path)
1023
def read_basis_inventory(self, revision_id):
1024
"""Read the cached basis inventory."""
1025
path = self._basis_inventory_name(revision_id)
1026
return self._control_files.get_utf8(path).read()
1029
def read_working_inventory(self):
1030
"""Read the working inventory."""
1031
# ElementTree does its own conversion from UTF-8, so open in
1033
result = bzrlib.xml5.serializer_v5.read_inventory(
1034
self._control_files.get('inventory'))
1035
self._set_inventory(result)
1039
def remove(self, files, verbose=False):
1040
"""Remove nominated files from the working inventory..
1042
This does not remove their text. This does not run on XXX on what? RBC
1044
TODO: Refuse to remove modified files unless --force is given?
1046
TODO: Do something useful with directories.
1048
TODO: Should this remove the text or not? Tough call; not
1049
removing may be useful and the user can just use use rm, and
1050
is the opposite of add. Removing it is consistent with most
1051
other tools. Maybe an option.
1053
## TODO: Normalize names
1054
## TODO: Remove nested loops; better scalability
1055
if isinstance(files, basestring):
1058
inv = self.inventory
1060
# do this before any modifications
1062
fid = inv.path2id(f)
1064
# TODO: Perhaps make this just a warning, and continue?
1065
# This tends to happen when
1066
raise NotVersionedError(path=f)
1067
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1069
# having remove it, it must be either ignored or unknown
1070
if self.is_ignored(f):
1074
show_status(new_status, inv[fid].kind, quotefn(f))
1077
self._write_inventory(inv)
1080
def revert(self, filenames, old_tree=None, backups=True):
1081
from transform import revert
1082
if old_tree is None:
1083
old_tree = self.basis_tree()
1084
revert(self, old_tree, filenames, backups)
1085
if not len(filenames):
1086
self.set_pending_merges([])
1089
def set_inventory(self, new_inventory_list):
1090
from bzrlib.inventory import (Inventory,
1095
inv = Inventory(self.get_root_id())
1096
for path, file_id, parent, kind in new_inventory_list:
1097
name = os.path.basename(path)
1100
# fixme, there should be a factory function inv,add_??
1101
if kind == 'directory':
1102
inv.add(InventoryDirectory(file_id, name, parent))
1103
elif kind == 'file':
1104
inv.add(InventoryFile(file_id, name, parent))
1105
elif kind == 'symlink':
1106
inv.add(InventoryLink(file_id, name, parent))
1108
raise BzrError("unknown kind %r" % kind)
1109
self._write_inventory(inv)
1112
def set_root_id(self, file_id):
1113
"""Set the root id for this tree."""
1114
inv = self.read_working_inventory()
1115
orig_root_id = inv.root.file_id
1116
del inv._byid[inv.root.file_id]
1117
inv.root.file_id = file_id
1118
inv._byid[inv.root.file_id] = inv.root
1121
if entry.parent_id == orig_root_id:
1122
entry.parent_id = inv.root.file_id
1123
self._write_inventory(inv)
1126
"""See Branch.unlock.
1128
WorkingTree locking just uses the Branch locking facilities.
1129
This is current because all working trees have an embedded branch
1130
within them. IF in the future, we were to make branch data shareable
1131
between multiple working trees, i.e. via shared storage, then we
1132
would probably want to lock both the local tree, and the branch.
1134
# FIXME: We want to write out the hashcache only when the last lock on
1135
# this working copy is released. Peeking at the lock count is a bit
1136
# of a nasty hack; probably it's better to have a transaction object,
1137
# which can do some finalization when it's either successfully or
1138
# unsuccessfully completed. (Denys's original patch did that.)
1139
# RBC 20060206 hookinhg into transaction will couple lock and transaction
1140
# wrongly. Hookinh into unllock on the control files object is fine though.
1142
# TODO: split this per format so there is no ugly if block
1143
if self._hashcache.needs_write and (
1144
# dedicated lock files
1145
self._control_files._lock_count==1 or
1147
(self._control_files is self.branch.control_files and
1148
self._control_files._lock_count==3)):
1149
self._hashcache.write()
1150
# reverse order of locking.
1151
result = self._control_files.unlock()
1153
self.branch.unlock()
1159
self.branch.lock_read()
1161
if self.last_revision() == self.branch.last_revision():
1163
basis = self.basis_tree()
1164
to_tree = self.branch.basis_tree()
1165
result = merge_inner(self.branch,
1169
self.set_last_revision(self.branch.last_revision())
1172
self.branch.unlock()
1175
def _write_inventory(self, inv):
1176
"""Write inventory as the current inventory."""
1178
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1180
self._control_files.put('inventory', sio)
1181
self._set_inventory(inv)
1182
mutter('wrote working inventory')
1185
class WorkingTree3(WorkingTree):
1186
"""This is the Format 3 working tree.
1188
This differs from the base WorkingTree by:
1189
- having its own file lock
1190
- having its own last-revision property.
1194
def last_revision(self):
1195
"""See WorkingTree.last_revision."""
1197
return self._control_files.get_utf8('last-revision').read()
1201
def _change_last_revision(self, revision_id):
1202
"""See WorkingTree._change_last_revision."""
1203
if revision_id is None or revision_id == NULL_REVISION:
1205
self._control_files._transport.delete('last-revision')
1206
except errors.NoSuchFile:
1211
self.branch.revision_history().index(revision_id)
1213
raise errors.NoSuchRevision(self.branch, revision_id)
1214
self._control_files.put_utf8('last-revision', revision_id)
379
1218
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
380
1219
def get_conflicted_stem(path):
381
1220
for suffix in CONFLICT_SUFFIXES:
382
1221
if path.endswith(suffix):
383
1222
return path[:-len(suffix)]
1224
@deprecated_function(zero_eight)
1225
def is_control_file(filename):
1226
"""See WorkingTree.is_control_filename(filename)."""
1227
## FIXME: better check
1228
filename = normpath(filename)
1229
while filename != '':
1230
head, tail = os.path.split(filename)
1231
## mutter('check %r for control file' % ((head, tail),))
1234
if filename == head:
1240
class WorkingTreeFormat(object):
1241
"""An encapsulation of the initialization and open routines for a format.
1243
Formats provide three things:
1244
* An initialization routine,
1248
Formats are placed in an dict by their format string for reference
1249
during workingtree opening. Its not required that these be instances, they
1250
can be classes themselves with class methods - it simply depends on
1251
whether state is needed for a given format or not.
1253
Once a format is deprecated, just deprecate the initialize and open
1254
methods on the format class. Do not deprecate the object, as the
1255
object will be created every time regardless.
1258
_default_format = None
1259
"""The default format used for new trees."""
1262
"""The known formats."""
1265
def find_format(klass, a_bzrdir):
1266
"""Return the format for the working tree object in a_bzrdir."""
1268
transport = a_bzrdir.get_workingtree_transport(None)
1269
format_string = transport.get("format").read()
1270
return klass._formats[format_string]
1272
raise errors.NoWorkingTree(base=transport.base)
1274
raise errors.UnknownFormatError(format_string)
1277
def get_default_format(klass):
1278
"""Return the current default format."""
1279
return klass._default_format
1281
def get_format_string(self):
1282
"""Return the ASCII format string that identifies this format."""
1283
raise NotImplementedError(self.get_format_string)
1285
def is_supported(self):
1286
"""Is this format supported?
1288
Supported formats can be initialized and opened.
1289
Unsupported formats may not support initialization or committing or
1290
some other features depending on the reason for not being supported.
1295
def register_format(klass, format):
1296
klass._formats[format.get_format_string()] = format
1299
def set_default_format(klass, format):
1300
klass._default_format = format
1303
def unregister_format(klass, format):
1304
assert klass._formats[format.get_format_string()] is format
1305
del klass._formats[format.get_format_string()]
1309
class WorkingTreeFormat2(WorkingTreeFormat):
1310
"""The second working tree format.
1312
This format modified the hash cache from the format 1 hash cache.
1315
def initialize(self, a_bzrdir, revision_id=None):
1316
"""See WorkingTreeFormat.initialize()."""
1317
if not isinstance(a_bzrdir.transport, LocalTransport):
1318
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1319
branch = a_bzrdir.open_branch()
1320
if revision_id is not None:
1323
revision_history = branch.revision_history()
1325
position = revision_history.index(revision_id)
1327
raise errors.NoSuchRevision(branch, revision_id)
1328
branch.set_revision_history(revision_history[:position + 1])
1331
revision = branch.last_revision()
1333
wt = WorkingTree(a_bzrdir.root_transport.base,
1339
wt._write_inventory(inv)
1340
wt.set_root_id(inv.root.file_id)
1341
wt.set_last_revision(revision)
1342
wt.set_pending_merges([])
1343
build_tree(wt.basis_tree(), wt)
1347
super(WorkingTreeFormat2, self).__init__()
1348
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1350
def open(self, a_bzrdir, _found=False):
1351
"""Return the WorkingTree object for a_bzrdir
1353
_found is a private parameter, do not use it. It is used to indicate
1354
if format probing has already been done.
1357
# we are being called directly and must probe.
1358
raise NotImplementedError
1359
if not isinstance(a_bzrdir.transport, LocalTransport):
1360
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1361
return WorkingTree(a_bzrdir.root_transport.base,
1367
class WorkingTreeFormat3(WorkingTreeFormat):
1368
"""The second working tree format updated to record a format marker.
1370
This format modified the hash cache from the format 1 hash cache.
1373
def get_format_string(self):
1374
"""See WorkingTreeFormat.get_format_string()."""
1375
return "Bazaar-NG Working Tree format 3"
1377
def initialize(self, a_bzrdir, revision_id=None):
1378
"""See WorkingTreeFormat.initialize().
1380
revision_id allows creating a working tree at a differnet
1381
revision than the branch is at.
1383
if not isinstance(a_bzrdir.transport, LocalTransport):
1384
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1385
transport = a_bzrdir.get_workingtree_transport(self)
1386
control_files = LockableFiles(transport, 'lock')
1387
control_files.put_utf8('format', self.get_format_string())
1388
branch = a_bzrdir.open_branch()
1389
if revision_id is None:
1390
revision_id = branch.last_revision()
1392
wt = WorkingTree3(a_bzrdir.root_transport.base,
1398
wt._write_inventory(inv)
1399
wt.set_root_id(inv.root.file_id)
1400
wt.set_last_revision(revision_id)
1401
wt.set_pending_merges([])
1402
build_tree(wt.basis_tree(), wt)
1406
super(WorkingTreeFormat3, self).__init__()
1407
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1409
def open(self, a_bzrdir, _found=False):
1410
"""Return the WorkingTree object for a_bzrdir
1412
_found is a private parameter, do not use it. It is used to indicate
1413
if format probing has already been done.
1416
# we are being called directly and must probe.
1417
raise NotImplementedError
1418
if not isinstance(a_bzrdir.transport, LocalTransport):
1419
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1420
return WorkingTree3(a_bzrdir.root_transport.base,
1426
return self.get_format_string()
1429
# formats which have no format string are not discoverable
1430
# and not independently creatable, so are not registered.
1431
__default_format = WorkingTreeFormat3()
1432
WorkingTreeFormat.register_format(__default_format)
1433
WorkingTreeFormat.set_default_format(__default_format)
1434
_legacy_formats = [WorkingTreeFormat2(),
1438
class WorkingTreeTestProviderAdapter(object):
1439
"""A tool to generate a suite testing multiple workingtree formats at once.
1441
This is done by copying the test once for each transport and injecting
1442
the transport_server, transport_readonly_server, and workingtree_format
1443
classes into each copy. Each copy is also given a new id() to make it
1447
def __init__(self, transport_server, transport_readonly_server, formats):
1448
self._transport_server = transport_server
1449
self._transport_readonly_server = transport_readonly_server
1450
self._formats = formats
1452
def adapt(self, test):
1453
from bzrlib.tests import TestSuite
1454
result = TestSuite()
1455
for workingtree_format, bzrdir_format in self._formats:
1456
new_test = deepcopy(test)
1457
new_test.transport_server = self._transport_server
1458
new_test.transport_readonly_server = self._transport_readonly_server
1459
new_test.bzrdir_format = bzrdir_format
1460
new_test.workingtree_format = workingtree_format
1461
def make_new_test_id():
1462
new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1463
return lambda: new_id
1464
new_test.id = make_new_test_id()
1465
result.addTest(new_test)