42
40
# copy, and making sure there's only one WorkingTree for any directory on disk.
43
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
49
from bzrlib.branch import Branch, needs_read_lock, needs_write_lock, quotefn
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
51
67
from bzrlib.osutils import (appendpath,
57
from bzrlib.errors import BzrCheckError, DivergedBranches, NotVersionedError
82
from bzrlib.revision import NULL_REVISION
83
from bzrlib.symbol_versioning import *
84
from bzrlib.textui import show_status
58
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')
62
126
class TreeEntry(object):
63
127
"""An entry that implements the minium interface used by commands.
132
202
(branch.base is not cross checked, because for remote branches that
133
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
135
221
from bzrlib.hashcache import HashCache
136
222
from bzrlib.trace import note, mutter
137
223
assert isinstance(basedir, basestring), \
138
224
"base directory %r is not a string" % basedir
140
branch = Branch.open(basedir)
141
assert isinstance(branch, Branch), \
142
"branch %r is not a Branch" % branch
144
self.basedir = basedir
145
self._inventory = self.read_working_inventory()
146
self.path2id = self._inventory.path2id
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),
148
254
# update the whole cache up front and write to disk if anything changed;
149
255
# in the future we might want to do this more selectively
170
321
if bzrlib.osutils.lexists(self.abspath(path)):
174
324
def __repr__(self):
175
325
return "<%s of %s>" % (self.__class__.__name__,
176
326
getattr(self, 'basedir', None))
180
328
def abspath(self, filename):
181
return os.path.join(self.basedir, filename)
183
def relpath(self, abspath):
329
return pathjoin(self.basedir, filename)
331
def basis_tree(self):
332
"""Return RevisionTree for the current last revision."""
333
revision_id = self.last_revision()
334
if revision_id is not None:
336
xml = self.read_basis_inventory(revision_id)
337
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
338
return bzrlib.tree.RevisionTree(self.branch.repository, inv,
342
return self.branch.repository.revision_tree(revision_id)
345
@deprecated_method(zero_eight)
346
def create(branch, directory):
347
"""Create a workingtree for branch at directory.
349
If existing_directory already exists it must have a .bzr directory.
350
If it does not exist, it will be created.
352
This returns a new WorkingTree object for the new checkout.
354
TODO FIXME RBC 20060124 when we have checkout formats in place this
355
should accept an optional revisionid to checkout [and reject this if
356
checking out into the same dir as a pre-checkout-aware branch format.]
358
XXX: When BzrDir is present, these should be created through that
361
warn('delete WorkingTree.create', stacklevel=3)
362
transport = get_transport(directory)
363
if branch.bzrdir.root_transport.base == transport.base:
365
return branch.bzrdir.create_workingtree()
366
# different directory,
367
# create a branch reference
368
# and now a working tree.
369
raise NotImplementedError
372
@deprecated_method(zero_eight)
373
def create_standalone(directory):
374
"""Create a checkout and a branch and a repo at directory.
376
Directory must exist and be empty.
378
please use BzrDir.create_standalone_workingtree
380
return bzrdir.BzrDir.create_standalone_workingtree(directory)
382
def relpath(self, abs):
184
383
"""Return the local path portion from a given absolute path."""
185
return relpath(self.basedir, abspath)
384
return relpath(self.basedir, abs)
187
386
def has_filename(self, filename):
188
387
return bzrlib.osutils.lexists(self.abspath(filename))
199
398
return inv.root.file_id
201
400
def _get_store_filename(self, file_id):
202
## XXX: badly named; this isn't in the store at all
401
## XXX: badly named; this is not in the store at all
203
402
return self.abspath(self.id2path(file_id))
405
def clone(self, to_bzrdir, revision_id=None, basis=None):
406
"""Duplicate this working tree into to_bzr, including all state.
408
Specifically modified files are kept as modified, but
409
ignored and unknown files are discarded.
411
If you want to make a new line of development, see bzrdir.sprout()
414
If not None, the cloned tree will have its last revision set to
415
revision, and and difference between the source trees last revision
416
and this one merged in.
419
If not None, a closer copy of a tree which may have some files in
420
common, and which file content should be preferentially copied from.
422
# assumes the target bzr dir format is compatible.
423
result = self._format.initialize(to_bzrdir)
424
self.copy_content_into(result, revision_id)
428
def copy_content_into(self, tree, revision_id=None):
429
"""Copy the current content and user files of this tree into tree."""
430
if revision_id is None:
431
transform_tree(tree, self)
433
# TODO now merge from tree.last_revision to revision
434
transform_tree(tree, self)
435
tree.set_last_revision(revision_id)
205
437
@needs_write_lock
206
def commit(self, *args, **kw):
438
def commit(self, *args, **kwargs):
207
439
from bzrlib.commit import Commit
208
Commit().commit(self.branch, *args, **kw)
209
self._inventory = self.read_working_inventory()
440
# args for wt.commit start at message from the Commit.commit method,
441
# but with branch a kwarg now, passing in args as is results in the
442
#message being used for the branch
443
args = (DEPRECATED_PARAMETER, ) + args
444
Commit().commit(working_tree=self, *args, **kwargs)
445
self._set_inventory(self.read_working_inventory())
211
447
def id2abspath(self, file_id):
212
448
return self.abspath(self.id2path(file_id))
215
450
def has_id(self, file_id):
216
451
# files that have been deleted are excluded
217
452
inv = self._inventory
245
479
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
247
481
@needs_write_lock
482
def add(self, files, ids=None):
483
"""Make files versioned.
485
Note that the command line normally calls smart_add instead,
486
which can automatically recurse.
488
This adds the files to the inventory, so that they will be
489
recorded by the next commit.
492
List of paths to add, relative to the base of the tree.
495
If set, use these instead of automatically generated ids.
496
Must be the same length as the list of files, but may
497
contain None for ids that are to be autogenerated.
499
TODO: Perhaps have an option to add the ids even if the files do
502
TODO: Perhaps callback with the ids and paths as they're added.
504
# TODO: Re-adding a file that is removed in the working copy
505
# should probably put it back with the previous ID.
506
if isinstance(files, basestring):
507
assert(ids is None or isinstance(ids, basestring))
513
ids = [None] * len(files)
515
assert(len(ids) == len(files))
517
inv = self.read_working_inventory()
518
for f,file_id in zip(files, ids):
519
if is_control_file(f):
520
raise BzrError("cannot add control file %s" % quotefn(f))
525
raise BzrError("cannot add top-level %r" % f)
527
fullpath = normpath(self.abspath(f))
530
kind = file_kind(fullpath)
532
if e.errno == errno.ENOENT:
533
raise NoSuchFile(fullpath)
534
# maybe something better?
535
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
537
if not InventoryEntry.versionable_kind(kind):
538
raise BzrError('cannot add: not a versionable file ('
539
'i.e. regular file, symlink or directory): %s' % quotefn(f))
542
file_id = gen_file_id(f)
543
inv.add_path(f, kind=kind, file_id=file_id)
545
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
546
self._write_inventory(inv)
248
549
def add_pending_merge(self, *revision_ids):
249
550
# TODO: Perhaps should check at this point that the
250
551
# history of the revision is actually present?
357
661
for ff in descend(fp, f_ie.file_id, fap):
360
for f in descend('', inv.root.file_id, self.basedir):
664
for f in descend(u'', inv.root.file_id, self.basedir):
668
def move(self, from_paths, to_name):
671
to_name must exist in the inventory.
673
If to_name exists and is a directory, the files are moved into
674
it, keeping their old names.
676
Note that to_name is only the last component of the new name;
677
this doesn't change the directory.
679
This returns a list of (from_path, to_path) pairs for each
683
## TODO: Option to move IDs only
684
assert not isinstance(from_paths, basestring)
686
to_abs = self.abspath(to_name)
687
if not isdir(to_abs):
688
raise BzrError("destination %r is not a directory" % to_abs)
689
if not self.has_filename(to_name):
690
raise BzrError("destination %r not in working directory" % to_abs)
691
to_dir_id = inv.path2id(to_name)
692
if to_dir_id == None and to_name != '':
693
raise BzrError("destination %r is not a versioned directory" % to_name)
694
to_dir_ie = inv[to_dir_id]
695
if to_dir_ie.kind not in ('directory', 'root_directory'):
696
raise BzrError("destination %r is not a directory" % to_abs)
698
to_idpath = inv.get_idpath(to_dir_id)
701
if not self.has_filename(f):
702
raise BzrError("%r does not exist in working tree" % f)
703
f_id = inv.path2id(f)
705
raise BzrError("%r is not versioned" % f)
706
name_tail = splitpath(f)[-1]
707
dest_path = appendpath(to_name, name_tail)
708
if self.has_filename(dest_path):
709
raise BzrError("destination %r already exists" % dest_path)
710
if f_id in to_idpath:
711
raise BzrError("can't move %r to a subdirectory of itself" % f)
713
# OK, so there's a race here, it's possible that someone will
714
# create a file in this interval and then the rename might be
715
# left half-done. But we should have caught most problems.
716
orig_inv = deepcopy(self.inventory)
719
name_tail = splitpath(f)[-1]
720
dest_path = appendpath(to_name, name_tail)
721
result.append((f, dest_path))
722
inv.rename(inv.path2id(f), to_dir_id, name_tail)
724
rename(self.abspath(f), self.abspath(dest_path))
726
raise BzrError("failed to rename %r to %r: %s" %
727
(f, dest_path, e[1]),
728
["rename rolled back"])
730
# restore the inventory on error
731
self._set_inventory(orig_inv)
733
self._write_inventory(inv)
737
def rename_one(self, from_rel, to_rel):
740
This can change the directory or the filename or both.
743
if not self.has_filename(from_rel):
744
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
745
if self.has_filename(to_rel):
746
raise BzrError("can't rename: new working file %r already exists" % to_rel)
748
file_id = inv.path2id(from_rel)
750
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
753
from_parent = entry.parent_id
754
from_name = entry.name
756
if inv.path2id(to_rel):
757
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
759
to_dir, to_tail = os.path.split(to_rel)
760
to_dir_id = inv.path2id(to_dir)
761
if to_dir_id == None and to_dir != '':
762
raise BzrError("can't determine destination directory id for %r" % to_dir)
764
mutter("rename_one:")
765
mutter(" file_id {%s}" % file_id)
766
mutter(" from_rel %r" % from_rel)
767
mutter(" to_rel %r" % to_rel)
768
mutter(" to_dir %r" % to_dir)
769
mutter(" to_dir_id {%s}" % to_dir_id)
771
inv.rename(file_id, to_dir_id, to_tail)
773
from_abs = self.abspath(from_rel)
774
to_abs = self.abspath(to_rel)
776
rename(from_abs, to_abs)
778
inv.rename(file_id, from_parent, from_name)
779
raise BzrError("failed to rename %r to %r: %s"
780
% (from_abs, to_abs, e[1]),
781
["rename rolled back"])
782
self._write_inventory(inv)
365
785
def unknowns(self):
786
"""Return all unknown files.
788
These are files in the working directory that are not versioned or
789
control files or ignored.
791
>>> from bzrlib.bzrdir import ScratchDir
792
>>> d = ScratchDir(files=['foo', 'foo~'])
793
>>> b = d.open_branch()
794
>>> tree = d.open_workingtree()
795
>>> map(str, tree.unknowns())
798
>>> list(b.unknowns())
800
>>> tree.remove('foo')
801
>>> list(b.unknowns())
366
804
for subp in self.extras():
367
805
if not self.is_ignored(subp):
490
931
def kind(self, file_id):
491
932
return file_kind(self.id2abspath(file_id))
935
def last_revision(self):
936
"""Return the last revision id of this working tree.
938
In early branch formats this was == the branch last_revision,
939
but that cannot be relied upon - for working tree operations,
940
always use tree.last_revision().
942
return self.branch.last_revision()
493
944
def lock_read(self):
494
945
"""See Branch.lock_read, and WorkingTree.unlock."""
495
return self.branch.lock_read()
946
self.branch.lock_read()
948
return self._control_files.lock_read()
497
953
def lock_write(self):
498
954
"""See Branch.lock_write, and WorkingTree.unlock."""
499
return self.branch.lock_write()
955
self.branch.lock_write()
957
return self._control_files.lock_write()
962
def _basis_inventory_name(self, revision_id):
963
return 'basis-inventory.%s' % revision_id
966
def set_last_revision(self, new_revision, old_revision=None):
967
"""Change the last revision in the working tree."""
968
self._remove_old_basis(old_revision)
969
if self._change_last_revision(new_revision):
970
self._cache_basis_inventory(new_revision)
972
def _change_last_revision(self, new_revision):
973
"""Template method part of set_last_revision to perform the change."""
974
if new_revision is None:
975
self.branch.set_revision_history([])
977
# current format is locked in with the branch
978
revision_history = self.branch.revision_history()
980
position = revision_history.index(new_revision)
982
raise errors.NoSuchRevision(self.branch, new_revision)
983
self.branch.set_revision_history(revision_history[:position + 1])
986
def _cache_basis_inventory(self, new_revision):
987
"""Cache new_revision as the basis inventory."""
989
xml = self.branch.repository.get_inventory_xml(new_revision)
990
path = self._basis_inventory_name(new_revision)
991
self._control_files.put_utf8(path, xml)
992
except WeaveRevisionNotPresent:
995
def _remove_old_basis(self, old_revision):
996
"""Remove the old basis inventory 'old_revision'."""
997
if old_revision is not None:
999
path = self._basis_inventory_name(old_revision)
1000
path = self._control_files._escape(path)
1001
self._control_files._transport.delete(path)
1005
def read_basis_inventory(self, revision_id):
1006
"""Read the cached basis inventory."""
1007
path = self._basis_inventory_name(revision_id)
1008
return self._control_files.get_utf8(path).read()
501
1010
@needs_read_lock
502
1011
def read_working_inventory(self):
503
1012
"""Read the working inventory."""
504
1013
# ElementTree does its own conversion from UTF-8, so open in
506
f = self.branch.controlfile('inventory', 'rb')
507
return bzrlib.xml5.serializer_v5.read_inventory(f)
1015
result = bzrlib.xml5.serializer_v5.read_inventory(
1016
self._control_files.get('inventory'))
1017
self._set_inventory(result)
509
1020
@needs_write_lock
510
1021
def remove(self, files, verbose=False):
605
1117
between multiple working trees, i.e. via shared storage, then we
606
1118
would probably want to lock both the local tree, and the branch.
608
return self.branch.unlock()
1120
# FIXME: We want to write out the hashcache only when the last lock on
1121
# this working copy is released. Peeking at the lock count is a bit
1122
# of a nasty hack; probably it's better to have a transaction object,
1123
# which can do some finalization when it's either successfully or
1124
# unsuccessfully completed. (Denys's original patch did that.)
1125
# RBC 20060206 hookinhg into transaction will couple lock and transaction
1126
# wrongly. Hookinh into unllock on the control files object is fine though.
1128
# TODO: split this per format so there is no ugly if block
1129
if self._hashcache.needs_write and (
1130
# dedicated lock files
1131
self._control_files._lock_count==1 or
1133
(self._control_files is self.branch.control_files and
1134
self._control_files._lock_count==3)):
1135
self._hashcache.write()
1136
# reverse order of locking.
1137
result = self._control_files.unlock()
1139
self.branch.unlock()
1145
self.branch.lock_read()
1147
if self.last_revision() == self.branch.last_revision():
1149
basis = self.basis_tree()
1150
to_tree = self.branch.basis_tree()
1151
result = merge_inner(self.branch,
1155
self.set_last_revision(self.branch.last_revision())
1158
self.branch.unlock()
610
1160
@needs_write_lock
611
1161
def _write_inventory(self, inv):
612
1162
"""Write inventory as the current inventory."""
613
from cStringIO import StringIO
614
from bzrlib.atomicfile import AtomicFile
615
1163
sio = StringIO()
616
1164
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
618
f = AtomicFile(self.branch.controlfilename('inventory'))
1166
self._control_files.put('inventory', sio)
1167
self._set_inventory(inv)
624
1168
mutter('wrote working inventory')
1171
class WorkingTree3(WorkingTree):
1172
"""This is the Format 3 working tree.
1174
This differs from the base WorkingTree by:
1175
- having its own file lock
1176
- having its own last-revision property.
1180
def last_revision(self):
1181
"""See WorkingTree.last_revision."""
1183
return self._control_files.get_utf8('last-revision').read()
1187
def _change_last_revision(self, revision_id):
1188
"""See WorkingTree._change_last_revision."""
1189
if revision_id is None or revision_id == NULL_REVISION:
1191
self._control_files._transport.delete('last-revision')
1192
except errors.NoSuchFile:
1197
self.branch.revision_history().index(revision_id)
1199
raise errors.NoSuchRevision(self.branch, revision_id)
1200
self._control_files.put_utf8('last-revision', revision_id)
627
1204
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
628
1205
def get_conflicted_stem(path):
629
1206
for suffix in CONFLICT_SUFFIXES:
630
1207
if path.endswith(suffix):
631
1208
return path[:-len(suffix)]
1210
def is_control_file(filename):
1211
## FIXME: better check
1212
filename = normpath(filename)
1213
while filename != '':
1214
head, tail = os.path.split(filename)
1215
## mutter('check %r for control file' % ((head, tail),))
1216
if tail == bzrlib.BZRDIR:
1218
if filename == head:
1224
class WorkingTreeFormat(object):
1225
"""An encapsulation of the initialization and open routines for a format.
1227
Formats provide three things:
1228
* An initialization routine,
1232
Formats are placed in an dict by their format string for reference
1233
during workingtree opening. Its not required that these be instances, they
1234
can be classes themselves with class methods - it simply depends on
1235
whether state is needed for a given format or not.
1237
Once a format is deprecated, just deprecate the initialize and open
1238
methods on the format class. Do not deprecate the object, as the
1239
object will be created every time regardless.
1242
_default_format = None
1243
"""The default format used for new trees."""
1246
"""The known formats."""
1249
def find_format(klass, a_bzrdir):
1250
"""Return the format for the working tree object in a_bzrdir."""
1252
transport = a_bzrdir.get_workingtree_transport(None)
1253
format_string = transport.get("format").read()
1254
return klass._formats[format_string]
1256
raise errors.NoWorkingTree(base=transport.base)
1258
raise errors.UnknownFormatError(format_string)
1261
def get_default_format(klass):
1262
"""Return the current default format."""
1263
return klass._default_format
1265
def get_format_string(self):
1266
"""Return the ASCII format string that identifies this format."""
1267
raise NotImplementedError(self.get_format_string)
1269
def is_supported(self):
1270
"""Is this format supported?
1272
Supported formats can be initialized and opened.
1273
Unsupported formats may not support initialization or committing or
1274
some other features depending on the reason for not being supported.
1279
def register_format(klass, format):
1280
klass._formats[format.get_format_string()] = format
1283
def set_default_format(klass, format):
1284
klass._default_format = format
1287
def unregister_format(klass, format):
1288
assert klass._formats[format.get_format_string()] is format
1289
del klass._formats[format.get_format_string()]
1293
class WorkingTreeFormat2(WorkingTreeFormat):
1294
"""The second working tree format.
1296
This format modified the hash cache from the format 1 hash cache.
1299
def initialize(self, a_bzrdir, revision_id=None):
1300
"""See WorkingTreeFormat.initialize()."""
1301
if not isinstance(a_bzrdir.transport, LocalTransport):
1302
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1303
branch = a_bzrdir.open_branch()
1304
if revision_id is not None:
1307
revision_history = branch.revision_history()
1309
position = revision_history.index(revision_id)
1311
raise errors.NoSuchRevision(branch, revision_id)
1312
branch.set_revision_history(revision_history[:position + 1])
1315
revision = branch.last_revision()
1316
basis_tree = branch.repository.revision_tree(revision)
1317
inv = basis_tree.inventory
1318
wt = WorkingTree(a_bzrdir.root_transport.base,
1324
wt._write_inventory(inv)
1325
wt.set_root_id(inv.root.file_id)
1326
wt.set_last_revision(revision)
1327
wt.set_pending_merges([])
1332
super(WorkingTreeFormat2, self).__init__()
1333
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1335
def open(self, a_bzrdir, _found=False):
1336
"""Return the WorkingTree object for a_bzrdir
1338
_found is a private parameter, do not use it. It is used to indicate
1339
if format probing has already been done.
1342
# we are being called directly and must probe.
1343
raise NotImplementedError
1344
if not isinstance(a_bzrdir.transport, LocalTransport):
1345
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1346
return WorkingTree(a_bzrdir.root_transport.base,
1352
class WorkingTreeFormat3(WorkingTreeFormat):
1353
"""The second working tree format updated to record a format marker.
1355
This format modified the hash cache from the format 1 hash cache.
1358
def get_format_string(self):
1359
"""See WorkingTreeFormat.get_format_string()."""
1360
return "Bazaar-NG Working Tree format 3"
1362
def initialize(self, a_bzrdir, revision_id=None):
1363
"""See WorkingTreeFormat.initialize().
1365
revision_id allows creating a working tree at a differnet
1366
revision than the branch is at.
1368
if not isinstance(a_bzrdir.transport, LocalTransport):
1369
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1370
transport = a_bzrdir.get_workingtree_transport(self)
1371
control_files = LockableFiles(transport, 'lock')
1372
control_files.put_utf8('format', self.get_format_string())
1373
branch = a_bzrdir.open_branch()
1374
if revision_id is None:
1375
revision_id = branch.last_revision()
1376
new_basis_tree = branch.repository.revision_tree(revision_id)
1377
inv = new_basis_tree.inventory
1378
wt = WorkingTree3(a_bzrdir.root_transport.base,
1384
wt._write_inventory(inv)
1385
wt.set_root_id(inv.root.file_id)
1386
wt.set_last_revision(revision_id)
1387
wt.set_pending_merges([])
1392
super(WorkingTreeFormat3, self).__init__()
1393
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1395
def open(self, a_bzrdir, _found=False):
1396
"""Return the WorkingTree object for a_bzrdir
1398
_found is a private parameter, do not use it. It is used to indicate
1399
if format probing has already been done.
1402
# we are being called directly and must probe.
1403
raise NotImplementedError
1404
if not isinstance(a_bzrdir.transport, LocalTransport):
1405
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1406
return WorkingTree3(a_bzrdir.root_transport.base,
1412
return self.get_format_string()
1415
# formats which have no format string are not discoverable
1416
# and not independently creatable, so are not registered.
1417
__default_format = WorkingTreeFormat3()
1418
WorkingTreeFormat.register_format(__default_format)
1419
WorkingTreeFormat.set_default_format(__default_format)
1420
_legacy_formats = [WorkingTreeFormat2(),
1424
class WorkingTreeTestProviderAdapter(object):
1425
"""A tool to generate a suite testing multiple workingtree formats at once.
1427
This is done by copying the test once for each transport and injecting
1428
the transport_server, transport_readonly_server, and workingtree_format
1429
classes into each copy. Each copy is also given a new id() to make it
1433
def __init__(self, transport_server, transport_readonly_server, formats):
1434
self._transport_server = transport_server
1435
self._transport_readonly_server = transport_readonly_server
1436
self._formats = formats
1438
def adapt(self, test):
1439
from bzrlib.tests import TestSuite
1440
result = TestSuite()
1441
for workingtree_format, bzrdir_format in self._formats:
1442
new_test = deepcopy(test)
1443
new_test.transport_server = self._transport_server
1444
new_test.transport_readonly_server = self._transport_readonly_server
1445
new_test.bzrdir_format = bzrdir_format
1446
new_test.workingtree_format = workingtree_format
1447
def make_new_test_id():
1448
new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1449
return lambda: new_id
1450
new_test.id = make_new_test_id()
1451
result.addTest(new_test)