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
65
from bzrlib.lockable_files import LockableFiles
66
from bzrlib.merge import merge_inner, transform_tree
67
from bzrlib.osutils import (appendpath,
82
from bzrlib.revision import NULL_REVISION
83
from bzrlib.symbol_versioning import *
84
from bzrlib.textui import show_status
28
from bzrlib.osutils import appendpath, file_kind, isdir, splitpath
29
from bzrlib.errors import BzrCheckError
30
86
from bzrlib.trace import mutter
87
from bzrlib.transport import get_transport
88
from bzrlib.transport.local import LocalTransport
92
def gen_file_id(name):
93
"""Return new file id.
95
This should probably generate proper UUIDs, but for the moment we
96
cope with just randomness because running uuidgen every time is
99
from binascii import hexlify
100
from time import time
103
idx = name.rfind('/')
105
name = name[idx+1 : ]
106
idx = name.rfind('\\')
108
name = name[idx+1 : ]
110
# make it not a hidden file
111
name = name.lstrip('.')
113
# remove any wierd characters; we don't escape them but rather
115
name = re.sub(r'[^\w.]', '', name)
117
s = hexlify(rand_bytes(8))
118
return '-'.join((name, compact_date(time()), s))
122
"""Return a new tree-root file id."""
123
return gen_file_id('TREE_ROOT')
32
126
class TreeEntry(object):
33
127
"""An entry that implements the minium interface used by commands.
93
187
It is possible for a `WorkingTree` to have a filename which is
94
188
not listed in the Inventory and vice versa.
96
def __init__(self, basedir, inv):
191
def __init__(self, basedir='.',
192
branch=DEPRECATED_PARAMETER,
198
"""Construct a WorkingTree for basedir.
200
If the branch is not supplied, it is opened automatically.
201
If the branch is supplied, it must be the branch for this basedir.
202
(branch.base is not cross checked, because for remote branches that
203
would be meaningless).
205
self._format = _format
206
self.bzrdir = _bzrdir
208
# not created via open etc.
209
warn("WorkingTree() is deprecated as of bzr version 0.8. "
210
"Please use bzrdir.open_workingtree or WorkingTree.open().",
213
wt = WorkingTree.open(basedir)
214
self.branch = wt.branch
215
self.basedir = wt.basedir
216
self._control_files = wt._control_files
217
self._hashcache = wt._hashcache
218
self._set_inventory(wt._inventory)
219
self._format = wt._format
220
self.bzrdir = wt.bzrdir
97
221
from bzrlib.hashcache import HashCache
98
222
from bzrlib.trace import note, mutter
100
self._inventory = inv
101
self.basedir = basedir
102
self.path2id = inv.path2id
223
assert isinstance(basedir, basestring), \
224
"base directory %r is not a string" % basedir
225
basedir = safe_unicode(basedir)
226
mutter("opening working tree %r", basedir)
227
if deprecated_passed(branch):
229
warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
230
" Please use bzrdir.open_workingtree() or WorkingTree.open().",
236
self.branch = self.bzrdir.open_branch()
237
assert isinstance(self.branch, Branch), \
238
"branch %r is not a Branch" % self.branch
239
self.basedir = realpath(basedir)
240
# if branch is at our basedir and is a format 6 or less
241
if isinstance(self._format, WorkingTreeFormat2):
242
# share control object
243
self._control_files = self.branch.control_files
244
elif _control_files is not None:
245
assert False, "not done yet"
246
# self._control_files = _control_files
248
# only ready for format 3
249
assert isinstance(self._format, WorkingTreeFormat3)
250
self._control_files = LockableFiles(
251
self.bzrdir.get_workingtree_transport(None),
104
254
# update the whole cache up front and write to disk if anything changed;
105
255
# in the future we might want to do this more selectively
106
hc = self._hashcache = HashCache(basedir)
256
# two possible ways offer themselves : in self._unlock, write the cache
257
# if needed, or, when the cache sees a change, append it to the hash
258
# cache file, and have the parser take the most recent entry for a
260
cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
261
hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
263
# is this scan needed ? it makes things kinda slow.
110
266
if hc.needs_write:
111
267
mutter("write hc")
116
if self._hashcache.needs_write:
117
self._hashcache.write()
270
if _inventory is None:
271
self._set_inventory(self.read_working_inventory())
273
self._set_inventory(_inventory)
275
def _set_inventory(self, inv):
276
self._inventory = inv
277
self.path2id = self._inventory.path2id
279
def is_control_filename(self, filename):
280
"""True if filename is the name of a control file in this tree.
282
This is true IF and ONLY IF the filename is part of the meta data
283
that bzr controls in this tree. I.E. a random .bzr directory placed
284
on disk will not be a control file for this tree.
287
self.bzrdir.transport.relpath(self.abspath(filename))
289
except errors.PathNotChild:
293
def open(path=None, _unsupported=False):
294
"""Open an existing working tree at path.
298
path = os.path.getcwdu()
299
control = bzrdir.BzrDir.open(path, _unsupported)
300
return control.open_workingtree(_unsupported)
303
def open_containing(path=None):
304
"""Open an existing working tree which has its root about path.
306
This probes for a working tree at path and searches upwards from there.
308
Basically we keep looking up until we find the control directory or
309
run into /. If there isn't one, raises NotBranchError.
310
TODO: give this a new exception.
311
If there is one, it is returned, along with the unused portion of path.
315
control, relpath = bzrdir.BzrDir.open_containing(path)
316
return control.open_workingtree(), relpath
319
def open_downlevel(path=None):
320
"""Open an unsupported working tree.
322
Only intended for advanced situations like upgrading part of a bzrdir.
324
return WorkingTree.open(path, _unsupported=True)
120
326
def __iter__(self):
121
327
"""Iterate through file_ids for this tree.
184
491
mode = os.lstat(self.abspath(path)).st_mode
185
492
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
495
def add(self, files, ids=None):
496
"""Make files versioned.
498
Note that the command line normally calls smart_add instead,
499
which can automatically recurse.
501
This adds the files to the inventory, so that they will be
502
recorded by the next commit.
505
List of paths to add, relative to the base of the tree.
508
If set, use these instead of automatically generated ids.
509
Must be the same length as the list of files, but may
510
contain None for ids that are to be autogenerated.
512
TODO: Perhaps have an option to add the ids even if the files do
515
TODO: Perhaps callback with the ids and paths as they're added.
517
# TODO: Re-adding a file that is removed in the working copy
518
# should probably put it back with the previous ID.
519
if isinstance(files, basestring):
520
assert(ids is None or isinstance(ids, basestring))
526
ids = [None] * len(files)
528
assert(len(ids) == len(files))
530
inv = self.read_working_inventory()
531
for f,file_id in zip(files, ids):
532
if self.is_control_filename(f):
533
raise BzrError("cannot add control file %s" % quotefn(f))
538
raise BzrError("cannot add top-level %r" % f)
540
fullpath = normpath(self.abspath(f))
543
kind = file_kind(fullpath)
545
if e.errno == errno.ENOENT:
546
raise NoSuchFile(fullpath)
547
# maybe something better?
548
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
550
if not InventoryEntry.versionable_kind(kind):
551
raise BzrError('cannot add: not a versionable file ('
552
'i.e. regular file, symlink or directory): %s' % quotefn(f))
555
file_id = gen_file_id(f)
556
inv.add_path(f, kind=kind, file_id=file_id)
558
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
559
self._write_inventory(inv)
562
def add_pending_merge(self, *revision_ids):
563
# TODO: Perhaps should check at this point that the
564
# history of the revision is actually present?
565
p = self.pending_merges()
567
for rev_id in revision_ids:
573
self.set_pending_merges(p)
576
def pending_merges(self):
577
"""Return a list of pending merges.
579
These are revisions that have been merged into the working
580
directory but not yet committed.
583
merges_file = self._control_files.get_utf8('pending-merges')
585
if e.errno != errno.ENOENT:
589
for l in merges_file.readlines():
590
p.append(l.rstrip('\n'))
594
def set_pending_merges(self, rev_list):
595
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
187
597
def get_symlink_target(self, file_id):
188
598
return os.readlink(self.id2abspath(file_id))
265
676
for ff in descend(fp, f_ie.file_id, fap):
268
for f in descend('', inv.root.file_id, self.basedir):
679
for f in descend(u'', inv.root.file_id, self.basedir):
683
def move(self, from_paths, to_name):
686
to_name must exist in the inventory.
688
If to_name exists and is a directory, the files are moved into
689
it, keeping their old names.
691
Note that to_name is only the last component of the new name;
692
this doesn't change the directory.
694
This returns a list of (from_path, to_path) pairs for each
698
## TODO: Option to move IDs only
699
assert not isinstance(from_paths, basestring)
701
to_abs = self.abspath(to_name)
702
if not isdir(to_abs):
703
raise BzrError("destination %r is not a directory" % to_abs)
704
if not self.has_filename(to_name):
705
raise BzrError("destination %r not in working directory" % to_abs)
706
to_dir_id = inv.path2id(to_name)
707
if to_dir_id == None and to_name != '':
708
raise BzrError("destination %r is not a versioned directory" % to_name)
709
to_dir_ie = inv[to_dir_id]
710
if to_dir_ie.kind not in ('directory', 'root_directory'):
711
raise BzrError("destination %r is not a directory" % to_abs)
713
to_idpath = inv.get_idpath(to_dir_id)
716
if not self.has_filename(f):
717
raise BzrError("%r does not exist in working tree" % f)
718
f_id = inv.path2id(f)
720
raise BzrError("%r is not versioned" % f)
721
name_tail = splitpath(f)[-1]
722
dest_path = appendpath(to_name, name_tail)
723
if self.has_filename(dest_path):
724
raise BzrError("destination %r already exists" % dest_path)
725
if f_id in to_idpath:
726
raise BzrError("can't move %r to a subdirectory of itself" % f)
728
# OK, so there's a race here, it's possible that someone will
729
# create a file in this interval and then the rename might be
730
# left half-done. But we should have caught most problems.
731
orig_inv = deepcopy(self.inventory)
734
name_tail = splitpath(f)[-1]
735
dest_path = appendpath(to_name, name_tail)
736
result.append((f, dest_path))
737
inv.rename(inv.path2id(f), to_dir_id, name_tail)
739
rename(self.abspath(f), self.abspath(dest_path))
741
raise BzrError("failed to rename %r to %r: %s" %
742
(f, dest_path, e[1]),
743
["rename rolled back"])
745
# restore the inventory on error
746
self._set_inventory(orig_inv)
748
self._write_inventory(inv)
752
def rename_one(self, from_rel, to_rel):
755
This can change the directory or the filename or both.
758
if not self.has_filename(from_rel):
759
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
760
if self.has_filename(to_rel):
761
raise BzrError("can't rename: new working file %r already exists" % to_rel)
763
file_id = inv.path2id(from_rel)
765
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
768
from_parent = entry.parent_id
769
from_name = entry.name
771
if inv.path2id(to_rel):
772
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
774
to_dir, to_tail = os.path.split(to_rel)
775
to_dir_id = inv.path2id(to_dir)
776
if to_dir_id == None and to_dir != '':
777
raise BzrError("can't determine destination directory id for %r" % to_dir)
779
mutter("rename_one:")
780
mutter(" file_id {%s}" % file_id)
781
mutter(" from_rel %r" % from_rel)
782
mutter(" to_rel %r" % to_rel)
783
mutter(" to_dir %r" % to_dir)
784
mutter(" to_dir_id {%s}" % to_dir_id)
786
inv.rename(file_id, to_dir_id, to_tail)
788
from_abs = self.abspath(from_rel)
789
to_abs = self.abspath(to_rel)
791
rename(from_abs, to_abs)
793
inv.rename(file_id, from_parent, from_name)
794
raise BzrError("failed to rename %r to %r: %s"
795
% (from_abs, to_abs, e[1]),
796
["rename rolled back"])
797
self._write_inventory(inv)
273
800
def unknowns(self):
801
"""Return all unknown files.
803
These are files in the working directory that are not versioned or
804
control files or ignored.
806
>>> from bzrlib.bzrdir import ScratchDir
807
>>> d = ScratchDir(files=['foo', 'foo~'])
808
>>> b = d.open_branch()
809
>>> tree = d.open_workingtree()
810
>>> map(str, tree.unknowns())
813
>>> list(b.unknowns())
815
>>> tree.remove('foo')
816
>>> list(b.unknowns())
274
819
for subp in self.extras():
275
820
if not self.is_ignored(subp):
946
def kind(self, file_id):
947
return file_kind(self.id2abspath(file_id))
950
def last_revision(self):
951
"""Return the last revision id of this working tree.
953
In early branch formats this was == the branch last_revision,
954
but that cannot be relied upon - for working tree operations,
955
always use tree.last_revision().
957
return self.branch.last_revision()
960
"""See Branch.lock_read, and WorkingTree.unlock."""
961
self.branch.lock_read()
963
return self._control_files.lock_read()
968
def lock_write(self):
969
"""See Branch.lock_write, and WorkingTree.unlock."""
970
self.branch.lock_write()
972
return self._control_files.lock_write()
977
def _basis_inventory_name(self, revision_id):
978
return 'basis-inventory.%s' % revision_id
981
def set_last_revision(self, new_revision, old_revision=None):
982
"""Change the last revision in the working tree."""
983
self._remove_old_basis(old_revision)
984
if self._change_last_revision(new_revision):
985
self._cache_basis_inventory(new_revision)
987
def _change_last_revision(self, new_revision):
988
"""Template method part of set_last_revision to perform the change."""
989
if new_revision is None:
990
self.branch.set_revision_history([])
992
# current format is locked in with the branch
993
revision_history = self.branch.revision_history()
995
position = revision_history.index(new_revision)
997
raise errors.NoSuchRevision(self.branch, new_revision)
998
self.branch.set_revision_history(revision_history[:position + 1])
1001
def _cache_basis_inventory(self, new_revision):
1002
"""Cache new_revision as the basis inventory."""
1004
xml = self.branch.repository.get_inventory_xml(new_revision)
1005
path = self._basis_inventory_name(new_revision)
1006
self._control_files.put_utf8(path, xml)
1007
except WeaveRevisionNotPresent:
1010
def _remove_old_basis(self, old_revision):
1011
"""Remove the old basis inventory 'old_revision'."""
1012
if old_revision is not None:
1014
path = self._basis_inventory_name(old_revision)
1015
path = self._control_files._escape(path)
1016
self._control_files._transport.delete(path)
1020
def read_basis_inventory(self, revision_id):
1021
"""Read the cached basis inventory."""
1022
path = self._basis_inventory_name(revision_id)
1023
return self._control_files.get_utf8(path).read()
1026
def read_working_inventory(self):
1027
"""Read the working inventory."""
1028
# ElementTree does its own conversion from UTF-8, so open in
1030
result = bzrlib.xml5.serializer_v5.read_inventory(
1031
self._control_files.get('inventory'))
1032
self._set_inventory(result)
1036
def remove(self, files, verbose=False):
1037
"""Remove nominated files from the working inventory..
1039
This does not remove their text. This does not run on XXX on what? RBC
1041
TODO: Refuse to remove modified files unless --force is given?
1043
TODO: Do something useful with directories.
1045
TODO: Should this remove the text or not? Tough call; not
1046
removing may be useful and the user can just use use rm, and
1047
is the opposite of add. Removing it is consistent with most
1048
other tools. Maybe an option.
1050
## TODO: Normalize names
1051
## TODO: Remove nested loops; better scalability
1052
if isinstance(files, basestring):
1055
inv = self.inventory
1057
# do this before any modifications
1059
fid = inv.path2id(f)
1061
# TODO: Perhaps make this just a warning, and continue?
1062
# This tends to happen when
1063
raise NotVersionedError(path=f)
1064
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1066
# having remove it, it must be either ignored or unknown
1067
if self.is_ignored(f):
1071
show_status(new_status, inv[fid].kind, quotefn(f))
1074
self._write_inventory(inv)
1077
def revert(self, filenames, old_tree=None, backups=True):
1078
from bzrlib.merge import merge_inner
1079
if old_tree is None:
1080
old_tree = self.basis_tree()
1081
merge_inner(self.branch, old_tree,
1082
self, ignore_zero=True,
1083
backup_files=backups,
1084
interesting_files=filenames,
1086
if not len(filenames):
1087
self.set_pending_merges([])
1090
def set_inventory(self, new_inventory_list):
1091
from bzrlib.inventory import (Inventory,
1096
inv = Inventory(self.get_root_id())
1097
for path, file_id, parent, kind in new_inventory_list:
1098
name = os.path.basename(path)
1101
# fixme, there should be a factory function inv,add_??
1102
if kind == 'directory':
1103
inv.add(InventoryDirectory(file_id, name, parent))
1104
elif kind == 'file':
1105
inv.add(InventoryFile(file_id, name, parent))
1106
elif kind == 'symlink':
1107
inv.add(InventoryLink(file_id, name, parent))
1109
raise BzrError("unknown kind %r" % kind)
1110
self._write_inventory(inv)
1113
def set_root_id(self, file_id):
1114
"""Set the root id for this tree."""
1115
inv = self.read_working_inventory()
1116
orig_root_id = inv.root.file_id
1117
del inv._byid[inv.root.file_id]
1118
inv.root.file_id = file_id
1119
inv._byid[inv.root.file_id] = inv.root
1122
if entry.parent_id == orig_root_id:
1123
entry.parent_id = inv.root.file_id
1124
self._write_inventory(inv)
1127
"""See Branch.unlock.
1129
WorkingTree locking just uses the Branch locking facilities.
1130
This is current because all working trees have an embedded branch
1131
within them. IF in the future, we were to make branch data shareable
1132
between multiple working trees, i.e. via shared storage, then we
1133
would probably want to lock both the local tree, and the branch.
1135
# FIXME: We want to write out the hashcache only when the last lock on
1136
# this working copy is released. Peeking at the lock count is a bit
1137
# of a nasty hack; probably it's better to have a transaction object,
1138
# which can do some finalization when it's either successfully or
1139
# unsuccessfully completed. (Denys's original patch did that.)
1140
# RBC 20060206 hookinhg into transaction will couple lock and transaction
1141
# wrongly. Hookinh into unllock on the control files object is fine though.
1143
# TODO: split this per format so there is no ugly if block
1144
if self._hashcache.needs_write and (
1145
# dedicated lock files
1146
self._control_files._lock_count==1 or
1148
(self._control_files is self.branch.control_files and
1149
self._control_files._lock_count==3)):
1150
self._hashcache.write()
1151
# reverse order of locking.
1152
result = self._control_files.unlock()
1154
self.branch.unlock()
1160
self.branch.lock_read()
1162
if self.last_revision() == self.branch.last_revision():
1164
basis = self.basis_tree()
1165
to_tree = self.branch.basis_tree()
1166
result = merge_inner(self.branch,
1170
self.set_last_revision(self.branch.last_revision())
1173
self.branch.unlock()
1176
def _write_inventory(self, inv):
1177
"""Write inventory as the current inventory."""
1179
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1181
self._control_files.put('inventory', sio)
1182
self._set_inventory(inv)
1183
mutter('wrote working inventory')
1186
class WorkingTree3(WorkingTree):
1187
"""This is the Format 3 working tree.
1189
This differs from the base WorkingTree by:
1190
- having its own file lock
1191
- having its own last-revision property.
1195
def last_revision(self):
1196
"""See WorkingTree.last_revision."""
1198
return self._control_files.get_utf8('last-revision').read()
1202
def _change_last_revision(self, revision_id):
1203
"""See WorkingTree._change_last_revision."""
1204
if revision_id is None or revision_id == NULL_REVISION:
1206
self._control_files._transport.delete('last-revision')
1207
except errors.NoSuchFile:
1212
self.branch.revision_history().index(revision_id)
1214
raise errors.NoSuchRevision(self.branch, revision_id)
1215
self._control_files.put_utf8('last-revision', revision_id)
379
1219
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
380
1220
def get_conflicted_stem(path):
381
1221
for suffix in CONFLICT_SUFFIXES:
382
1222
if path.endswith(suffix):
383
1223
return path[:-len(suffix)]
1225
@deprecated_function(zero_eight)
1226
def is_control_file(filename):
1227
"""See WorkingTree.is_control_filename(filename)."""
1228
## FIXME: better check
1229
filename = normpath(filename)
1230
while filename != '':
1231
head, tail = os.path.split(filename)
1232
## mutter('check %r for control file' % ((head, tail),))
1235
if filename == head:
1241
class WorkingTreeFormat(object):
1242
"""An encapsulation of the initialization and open routines for a format.
1244
Formats provide three things:
1245
* An initialization routine,
1249
Formats are placed in an dict by their format string for reference
1250
during workingtree opening. Its not required that these be instances, they
1251
can be classes themselves with class methods - it simply depends on
1252
whether state is needed for a given format or not.
1254
Once a format is deprecated, just deprecate the initialize and open
1255
methods on the format class. Do not deprecate the object, as the
1256
object will be created every time regardless.
1259
_default_format = None
1260
"""The default format used for new trees."""
1263
"""The known formats."""
1266
def find_format(klass, a_bzrdir):
1267
"""Return the format for the working tree object in a_bzrdir."""
1269
transport = a_bzrdir.get_workingtree_transport(None)
1270
format_string = transport.get("format").read()
1271
return klass._formats[format_string]
1273
raise errors.NoWorkingTree(base=transport.base)
1275
raise errors.UnknownFormatError(format_string)
1278
def get_default_format(klass):
1279
"""Return the current default format."""
1280
return klass._default_format
1282
def get_format_string(self):
1283
"""Return the ASCII format string that identifies this format."""
1284
raise NotImplementedError(self.get_format_string)
1286
def is_supported(self):
1287
"""Is this format supported?
1289
Supported formats can be initialized and opened.
1290
Unsupported formats may not support initialization or committing or
1291
some other features depending on the reason for not being supported.
1296
def register_format(klass, format):
1297
klass._formats[format.get_format_string()] = format
1300
def set_default_format(klass, format):
1301
klass._default_format = format
1304
def unregister_format(klass, format):
1305
assert klass._formats[format.get_format_string()] is format
1306
del klass._formats[format.get_format_string()]
1310
class WorkingTreeFormat2(WorkingTreeFormat):
1311
"""The second working tree format.
1313
This format modified the hash cache from the format 1 hash cache.
1316
def initialize(self, a_bzrdir, revision_id=None):
1317
"""See WorkingTreeFormat.initialize()."""
1318
if not isinstance(a_bzrdir.transport, LocalTransport):
1319
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1320
branch = a_bzrdir.open_branch()
1321
if revision_id is not None:
1324
revision_history = branch.revision_history()
1326
position = revision_history.index(revision_id)
1328
raise errors.NoSuchRevision(branch, revision_id)
1329
branch.set_revision_history(revision_history[:position + 1])
1332
revision = branch.last_revision()
1333
basis_tree = branch.repository.revision_tree(revision)
1334
inv = basis_tree.inventory
1335
wt = WorkingTree(a_bzrdir.root_transport.base,
1341
wt._write_inventory(inv)
1342
wt.set_root_id(inv.root.file_id)
1343
wt.set_last_revision(revision)
1344
wt.set_pending_merges([])
1349
super(WorkingTreeFormat2, self).__init__()
1350
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1352
def open(self, a_bzrdir, _found=False):
1353
"""Return the WorkingTree object for a_bzrdir
1355
_found is a private parameter, do not use it. It is used to indicate
1356
if format probing has already been done.
1359
# we are being called directly and must probe.
1360
raise NotImplementedError
1361
if not isinstance(a_bzrdir.transport, LocalTransport):
1362
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1363
return WorkingTree(a_bzrdir.root_transport.base,
1369
class WorkingTreeFormat3(WorkingTreeFormat):
1370
"""The second working tree format updated to record a format marker.
1372
This format modified the hash cache from the format 1 hash cache.
1375
def get_format_string(self):
1376
"""See WorkingTreeFormat.get_format_string()."""
1377
return "Bazaar-NG Working Tree format 3"
1379
def initialize(self, a_bzrdir, revision_id=None):
1380
"""See WorkingTreeFormat.initialize().
1382
revision_id allows creating a working tree at a differnet
1383
revision than the branch is at.
1385
if not isinstance(a_bzrdir.transport, LocalTransport):
1386
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1387
transport = a_bzrdir.get_workingtree_transport(self)
1388
control_files = LockableFiles(transport, 'lock')
1389
control_files.put_utf8('format', self.get_format_string())
1390
branch = a_bzrdir.open_branch()
1391
if revision_id is None:
1392
revision_id = branch.last_revision()
1393
new_basis_tree = branch.repository.revision_tree(revision_id)
1394
inv = new_basis_tree.inventory
1395
wt = WorkingTree3(a_bzrdir.root_transport.base,
1401
wt._write_inventory(inv)
1402
wt.set_root_id(inv.root.file_id)
1403
wt.set_last_revision(revision_id)
1404
wt.set_pending_merges([])
1409
super(WorkingTreeFormat3, self).__init__()
1410
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1412
def open(self, a_bzrdir, _found=False):
1413
"""Return the WorkingTree object for a_bzrdir
1415
_found is a private parameter, do not use it. It is used to indicate
1416
if format probing has already been done.
1419
# we are being called directly and must probe.
1420
raise NotImplementedError
1421
if not isinstance(a_bzrdir.transport, LocalTransport):
1422
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1423
return WorkingTree3(a_bzrdir.root_transport.base,
1429
return self.get_format_string()
1432
# formats which have no format string are not discoverable
1433
# and not independently creatable, so are not registered.
1434
__default_format = WorkingTreeFormat3()
1435
WorkingTreeFormat.register_format(__default_format)
1436
WorkingTreeFormat.set_default_format(__default_format)
1437
_legacy_formats = [WorkingTreeFormat2(),
1441
class WorkingTreeTestProviderAdapter(object):
1442
"""A tool to generate a suite testing multiple workingtree formats at once.
1444
This is done by copying the test once for each transport and injecting
1445
the transport_server, transport_readonly_server, and workingtree_format
1446
classes into each copy. Each copy is also given a new id() to make it
1450
def __init__(self, transport_server, transport_readonly_server, formats):
1451
self._transport_server = transport_server
1452
self._transport_readonly_server = transport_readonly_server
1453
self._formats = formats
1455
def adapt(self, test):
1456
from bzrlib.tests import TestSuite
1457
result = TestSuite()
1458
for workingtree_format, bzrdir_format in self._formats:
1459
new_test = deepcopy(test)
1460
new_test.transport_server = self._transport_server
1461
new_test.transport_readonly_server = self._transport_readonly_server
1462
new_test.bzrdir_format = bzrdir_format
1463
new_test.workingtree_format = workingtree_format
1464
def make_new_test_id():
1465
new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1466
return lambda: new_id
1467
new_test.id = make_new_test_id()
1468
result.addTest(new_test)