25
25
At the moment every WorkingTree has its own branch. Remote
26
26
WorkingTrees aren't supported.
28
To get a WorkingTree, call bzrdir.open_workingtree() or
29
WorkingTree.open(dir).
28
To get a WorkingTree, call WorkingTree(dir[, branch])
32
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
33
CONFLICT_HEADER_1 = "BZR conflict list format 1"
32
# FIXME: I don't know if writing out the cache from the destructor is really a
33
# good idea, because destructors are considered poor taste in Python, and it's
34
# not predictable when it will be written out.
35
36
# TODO: Give the workingtree sole responsibility for the working inventory;
36
37
# remove the variable and references to it from the branch. This may require
37
38
# updating the commit code so as to update the inventory within the working
38
39
# copy, and making sure there's only one WorkingTree for any directory on disk.
39
# At the moment they may alias the inventory and have old copies of it in
40
# memory. (Now done? -- mbp 20060309)
40
# At the momenthey may alias the inventory and have old copies of it in memory.
42
from binascii import hexlify
43
42
from copy import deepcopy
44
43
from cStringIO import StringIO
52
50
from bzrlib.atomicfile import AtomicFile
53
51
from bzrlib.branch import (Branch,
55
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
56
import bzrlib.bzrdir as bzrdir
57
57
from bzrlib.decorators import needs_read_lock, needs_write_lock
58
import bzrlib.errors as errors
59
58
from bzrlib.errors import (BzrCheckError,
63
61
WeaveRevisionNotPresent,
67
MergeModifiedFormatError,
70
from bzrlib.inventory import InventoryEntry, Inventory
71
from bzrlib.lockable_files import LockableFiles, TransportLock
72
from bzrlib.lockdir import LockDir
73
from bzrlib.merge import merge_inner, transform_tree
74
from bzrlib.osutils import (
65
from bzrlib.inventory import InventoryEntry
66
from bzrlib.lockable_files import LockableFiles
67
from bzrlib.osutils import (appendpath,
90
82
supports_executable,
92
from bzrlib.progress import DummyProgress, ProgressPhase
93
from bzrlib.revision import NULL_REVISION
94
from bzrlib.rio import RioReader, rio_file, Stanza
95
84
from bzrlib.symbol_versioning import *
96
85
from bzrlib.textui import show_status
98
from bzrlib.transform import build_tree
99
from bzrlib.trace import mutter, note
87
from bzrlib.trace import mutter
100
88
from bzrlib.transport import get_transport
101
from bzrlib.transport.local import LocalTransport
103
89
import bzrlib.xml5
106
# the regex here does the following:
107
# 1) remove any weird characters; we don't escape them but rather
109
# 2) match leading '.'s to make it not hidden
110
_gen_file_id_re = re.compile(r'[^\w.]|(^\.*)')
111
_gen_id_suffix = None
115
def _next_id_suffix():
116
"""Create a new file id suffix that is reasonably unique.
118
On the first call we combine the current time with 64 bits of randomness
119
to give a highly probably globally unique number. Then each call in the same
120
process adds 1 to a serial number we append to that unique value.
122
# XXX TODO: change bzrlib.add.smart_add to call workingtree.add() rather
123
# than having to move the id randomness out of the inner loop like this.
124
# XXX TODO: for the global randomness this uses we should add the thread-id
125
# before the serial #.
126
global _gen_id_suffix, _gen_id_serial
127
if _gen_id_suffix is None:
128
_gen_id_suffix = "-%s-%s-" % (compact_date(time()), rand_chars(16))
130
return _gen_id_suffix + str(_gen_id_serial)
133
92
def gen_file_id(name):
134
"""Return new file id for the basename 'name'.
136
The uniqueness is supplied from _next_id_suffix.
138
# XXX TODO: squash the filename to lowercase.
139
# XXX TODO: truncate the filename to something like 20 or 30 chars.
140
# XXX TODO: consider what to do with ids that look like illegal filepaths
141
# on platforms we support.
142
return _gen_file_id_re.sub('', name) + _next_id_suffix()
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))
145
121
def gen_root_id():
226
196
(branch.base is not cross checked, because for remote branches that
227
197
would be meaningless).
229
self._format = _format
230
self.bzrdir = _bzrdir
232
# not created via open etc.
233
warn("WorkingTree() is deprecated as of bzr version 0.8. "
234
"Please use bzrdir.open_workingtree or WorkingTree.open().",
237
wt = WorkingTree.open(basedir)
238
self._branch = wt.branch
239
self.basedir = wt.basedir
240
self._control_files = wt._control_files
241
self._hashcache = wt._hashcache
242
self._set_inventory(wt._inventory)
243
self._format = wt._format
244
self.bzrdir = wt.bzrdir
245
199
from bzrlib.hashcache import HashCache
246
200
from bzrlib.trace import note, mutter
247
201
assert isinstance(basedir, basestring), \
248
202
"base directory %r is not a string" % basedir
249
203
basedir = safe_unicode(basedir)
250
mutter("opening working tree %r", basedir)
251
if deprecated_passed(branch):
253
warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
254
" Please use bzrdir.open_workingtree() or"
255
" WorkingTree.open().",
259
self._branch = branch
261
self._branch = self.bzrdir.open_branch()
262
assert isinstance(self.branch, Branch), \
263
"branch %r is not a Branch" % self.branch
204
mutter("openeing working tree %r", basedir)
206
branch = Branch.open(basedir)
207
assert isinstance(branch, Branch), \
208
"branch %r is not a Branch" % branch
264
210
self.basedir = realpath(basedir)
265
211
# if branch is at our basedir and is a format 6 or less
266
if isinstance(self._format, WorkingTreeFormat2):
267
# share control object
212
if (isinstance(self.branch._branch_format,
213
(BzrBranchFormat4, BzrBranchFormat5, BzrBranchFormat6))
214
# might be able to share control object
215
and self.branch.base.split('/')[-2] == self.basedir.split('/')[-1]):
268
216
self._control_files = self.branch.control_files
217
elif _control_files is not None:
218
assert False, "not done yet"
219
# self._control_files = _control_files
270
# only ready for format 3
271
assert isinstance(self._format, WorkingTreeFormat3)
272
assert isinstance(_control_files, LockableFiles), \
273
"_control_files must be a LockableFiles, not %r" \
275
self._control_files = _control_files
221
self._control_files = LockableFiles(
222
get_transport(self.basedir).clone(bzrlib.BZRDIR), 'branch-lock')
276
224
# update the whole cache up front and write to disk if anything changed;
277
225
# in the future we might want to do this more selectively
278
226
# two possible ways offer themselves : in self._unlock, write the cache
279
227
# if needed, or, when the cache sees a change, append it to the hash
280
228
# cache file, and have the parser take the most recent entry for a
281
229
# given path only.
282
cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
283
hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
230
hc = self._hashcache = HashCache(basedir)
285
# is this scan needed ? it makes things kinda slow.
288
234
if hc.needs_write:
460
367
## XXX: badly named; this is not in the store at all
461
368
return self.abspath(self.id2path(file_id))
464
def clone(self, to_bzrdir, revision_id=None, basis=None):
465
"""Duplicate this working tree into to_bzr, including all state.
467
Specifically modified files are kept as modified, but
468
ignored and unknown files are discarded.
470
If you want to make a new line of development, see bzrdir.sprout()
473
If not None, the cloned tree will have its last revision set to
474
revision, and and difference between the source trees last revision
475
and this one merged in.
478
If not None, a closer copy of a tree which may have some files in
479
common, and which file content should be preferentially copied from.
481
# assumes the target bzr dir format is compatible.
482
result = self._format.initialize(to_bzrdir)
483
self.copy_content_into(result, revision_id)
487
def copy_content_into(self, tree, revision_id=None):
488
"""Copy the current content and user files of this tree into tree."""
489
if revision_id is None:
490
transform_tree(tree, self)
492
# TODO now merge from tree.last_revision to revision
493
transform_tree(tree, self)
494
tree.set_last_revision(revision_id)
496
370
@needs_write_lock
497
def commit(self, message=None, revprops=None, *args, **kwargs):
498
# avoid circular imports
371
def commit(self, *args, **kwargs):
499
372
from bzrlib.commit import Commit
502
if not 'branch-nick' in revprops:
503
revprops['branch-nick'] = self.branch.nick
504
373
# args for wt.commit start at message from the Commit.commit method,
505
374
# but with branch a kwarg now, passing in args as is results in the
506
375
#message being used for the branch
507
args = (DEPRECATED_PARAMETER, message, ) + args
508
Commit().commit(working_tree=self, revprops=revprops, *args, **kwargs)
376
args = (DEPRECATED_PARAMETER, ) + args
377
Commit().commit(working_tree=self, *args, **kwargs)
509
378
self._set_inventory(self.read_working_inventory())
511
380
def id2abspath(self, file_id):
1087
840
# treat dotfiles correctly and allows * to match /.
1088
841
# Eventually it should be replaced with something more
1091
rules = self._get_ignore_rules_as_regex()
1092
for regex, mapping in rules:
1093
match = regex.match(filename)
1094
if match is not None:
1095
# one or more of the groups in mapping will have a non-None group
1097
groups = match.groups()
1098
rules = [mapping[group] for group in
1099
mapping if groups[group] is not None]
844
for pat in self.get_ignore_list():
845
if '/' in pat or '\\' in pat:
847
# as a special case, you can put ./ at the start of a
848
# pattern; this is good to match in the top-level
851
if (pat[:2] == './') or (pat[:2] == '.\\'):
855
if fnmatch.fnmatchcase(filename, newpat):
858
if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
1103
863
def kind(self, file_id):
1104
864
return file_kind(self.id2abspath(file_id))
1107
def last_revision(self):
1108
"""Return the last revision id of this working tree.
1110
In early branch formats this was == the branch last_revision,
1111
but that cannot be relied upon - for working tree operations,
1112
always use tree.last_revision().
1114
return self.branch.last_revision()
1116
def is_locked(self):
1117
return self._control_files.is_locked()
1119
866
def lock_read(self):
1120
867
"""See Branch.lock_read, and WorkingTree.unlock."""
1121
self.branch.lock_read()
1123
return self._control_files.lock_read()
1125
self.branch.unlock()
868
return self.branch.lock_read()
1128
870
def lock_write(self):
1129
871
"""See Branch.lock_write, and WorkingTree.unlock."""
1130
self.branch.lock_write()
1132
return self._control_files.lock_write()
1134
self.branch.unlock()
1137
def get_physical_lock_status(self):
1138
return self._control_files.get_physical_lock_status()
1140
def _basis_inventory_name(self):
1141
return 'basis-inventory'
1144
def set_last_revision(self, new_revision):
1145
"""Change the last revision in the working tree."""
1146
if self._change_last_revision(new_revision):
1147
self._cache_basis_inventory(new_revision)
1149
def _change_last_revision(self, new_revision):
1150
"""Template method part of set_last_revision to perform the change.
1152
This is used to allow WorkingTree3 instances to not affect branch
1153
when their last revision is set.
1155
if new_revision is None:
1156
self.branch.set_revision_history([])
1158
# current format is locked in with the branch
1159
revision_history = self.branch.revision_history()
1161
position = revision_history.index(new_revision)
1163
raise errors.NoSuchRevision(self.branch, new_revision)
1164
self.branch.set_revision_history(revision_history[:position + 1])
1167
def _cache_basis_inventory(self, new_revision):
1168
"""Cache new_revision as the basis inventory."""
1170
# this double handles the inventory - unpack and repack -
1171
# but is easier to understand. We can/should put a conditional
1172
# in here based on whether the inventory is in the latest format
1173
# - perhaps we should repack all inventories on a repository
1175
inv = self.branch.repository.get_inventory(new_revision)
1176
inv.revision_id = new_revision
1177
xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1179
path = self._basis_inventory_name()
1180
self._control_files.put_utf8(path, xml)
872
return self.branch.lock_write()
874
def _basis_inventory_name(self, revision_id):
875
return 'basis-inventory.%s' % revision_id
877
def set_last_revision(self, new_revision, old_revision=None):
878
if old_revision is not None:
880
path = self._basis_inventory_name(old_revision)
881
path = self.branch.control_files._escape(path)
882
self.branch.control_files._transport.delete(path)
886
xml = self.branch.repository.get_inventory_xml(new_revision)
887
path = self._basis_inventory_name(new_revision)
888
self.branch.control_files.put_utf8(path, xml)
1181
889
except WeaveRevisionNotPresent:
1184
def read_basis_inventory(self):
892
def read_basis_inventory(self, revision_id):
1185
893
"""Read the cached basis inventory."""
1186
path = self._basis_inventory_name()
1187
return self._control_files.get_utf8(path).read()
894
path = self._basis_inventory_name(revision_id)
895
return self.branch.control_files.get_utf8(path).read()
1189
897
@needs_read_lock
1190
898
def read_working_inventory(self):
1191
899
"""Read the working inventory."""
1192
900
# ElementTree does its own conversion from UTF-8, so open in
1194
result = bzrlib.xml5.serializer_v5.read_inventory(
902
return bzrlib.xml5.serializer_v5.read_inventory(
1195
903
self._control_files.get('inventory'))
1196
self._set_inventory(result)
1199
905
@needs_write_lock
1200
906
def remove(self, files, verbose=False):
1305
1003
# of a nasty hack; probably it's better to have a transaction object,
1306
1004
# which can do some finalization when it's either successfully or
1307
1005
# unsuccessfully completed. (Denys's original patch did that.)
1308
# RBC 20060206 hookinhg into transaction will couple lock and transaction
1309
# wrongly. Hookinh into unllock on the control files object is fine though.
1311
# TODO: split this per format so there is no ugly if block
1312
if self._hashcache.needs_write and (
1313
# dedicated lock files
1314
self._control_files._lock_count==1 or
1316
(self._control_files is self.branch.control_files and
1317
self._control_files._lock_count==3)):
1006
if self._hashcache.needs_write and self.branch.control_files._lock_count==1:
1318
1007
self._hashcache.write()
1319
# reverse order of locking.
1321
return self._control_files.unlock()
1323
self.branch.unlock()
1327
"""Update a working tree along its branch.
1329
This will update the branch if its bound too, which means we have multiple trees involved:
1330
The new basis tree of the master.
1331
The old basis tree of the branch.
1332
The old basis tree of the working tree.
1333
The current working tree state.
1334
pathologically all three may be different, and non ancestors of each other.
1335
Conceptually we want to:
1336
Preserve the wt.basis->wt.state changes
1337
Transform the wt.basis to the new master basis.
1338
Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
1339
Restore the wt.basis->wt.state changes.
1341
There isn't a single operation at the moment to do that, so we:
1342
Merge current state -> basis tree of the master w.r.t. the old tree basis.
1343
Do a 'normal' merge of the old branch basis if it is relevant.
1345
old_tip = self.branch.update()
1346
if old_tip is not None:
1347
self.add_pending_merge(old_tip)
1348
self.branch.lock_read()
1351
if self.last_revision() != self.branch.last_revision():
1352
# merge tree state up to new branch tip.
1353
basis = self.basis_tree()
1354
to_tree = self.branch.basis_tree()
1355
result += merge_inner(self.branch,
1359
self.set_last_revision(self.branch.last_revision())
1360
if old_tip and old_tip != self.last_revision():
1361
# our last revision was not the prior branch last reivison
1362
# and we have converted that last revision to a pending merge.
1363
# base is somewhere between the branch tip now
1364
# and the now pending merge
1365
from bzrlib.revision import common_ancestor
1367
base_rev_id = common_ancestor(self.branch.last_revision(),
1369
self.branch.repository)
1370
except errors.NoCommonAncestor:
1372
base_tree = self.branch.repository.revision_tree(base_rev_id)
1373
other_tree = self.branch.repository.revision_tree(old_tip)
1374
result += merge_inner(self.branch,
1380
self.branch.unlock()
1008
return self.branch.unlock()
1382
1010
@needs_write_lock
1383
1011
def _write_inventory(self, inv):
1388
1016
self._control_files.put('inventory', sio)
1389
1017
self._set_inventory(inv)
1390
1018
mutter('wrote working inventory')
1392
def set_conflicts(self, arg):
1393
raise UnsupportedOperation(self.set_conflicts, self)
1396
def conflicts(self):
1397
conflicts = ConflictList()
1398
for conflicted in self._iter_conflicts():
1401
if file_kind(self.abspath(conflicted)) != "file":
1404
if e.errno == errno.ENOENT:
1409
for suffix in ('.THIS', '.OTHER'):
1411
kind = file_kind(self.abspath(conflicted+suffix))
1413
if e.errno == errno.ENOENT:
1421
ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1422
conflicts.append(Conflict.factory(ctype, path=conflicted,
1423
file_id=self.path2id(conflicted)))
1427
class WorkingTree3(WorkingTree):
1428
"""This is the Format 3 working tree.
1430
This differs from the base WorkingTree by:
1431
- having its own file lock
1432
- having its own last-revision property.
1434
This is new in bzr 0.8
1438
def last_revision(self):
1439
"""See WorkingTree.last_revision."""
1441
return self._control_files.get_utf8('last-revision').read()
1445
def _change_last_revision(self, revision_id):
1446
"""See WorkingTree._change_last_revision."""
1447
if revision_id is None or revision_id == NULL_REVISION:
1449
self._control_files._transport.delete('last-revision')
1450
except errors.NoSuchFile:
1455
self.branch.revision_history().index(revision_id)
1457
raise errors.NoSuchRevision(self.branch, revision_id)
1458
self._control_files.put_utf8('last-revision', revision_id)
1462
def set_conflicts(self, conflicts):
1463
self._put_rio('conflicts', conflicts.to_stanzas(),
1467
def conflicts(self):
1469
confile = self._control_files.get('conflicts')
1471
return ConflictList()
1473
if confile.next() != CONFLICT_HEADER_1 + '\n':
1474
raise ConflictFormatError()
1475
except StopIteration:
1476
raise ConflictFormatError()
1477
return ConflictList.from_stanzas(RioReader(confile))
1021
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1480
1022
def get_conflicted_stem(path):
1481
1023
for suffix in CONFLICT_SUFFIXES:
1482
1024
if path.endswith(suffix):
1483
1025
return path[:-len(suffix)]
1485
@deprecated_function(zero_eight)
1486
def is_control_file(filename):
1487
"""See WorkingTree.is_control_filename(filename)."""
1488
## FIXME: better check
1489
filename = normpath(filename)
1490
while filename != '':
1491
head, tail = os.path.split(filename)
1492
## mutter('check %r for control file' % ((head, tail),))
1495
if filename == head:
1501
class WorkingTreeFormat(object):
1502
"""An encapsulation of the initialization and open routines for a format.
1504
Formats provide three things:
1505
* An initialization routine,
1509
Formats are placed in an dict by their format string for reference
1510
during workingtree opening. Its not required that these be instances, they
1511
can be classes themselves with class methods - it simply depends on
1512
whether state is needed for a given format or not.
1514
Once a format is deprecated, just deprecate the initialize and open
1515
methods on the format class. Do not deprecate the object, as the
1516
object will be created every time regardless.
1519
_default_format = None
1520
"""The default format used for new trees."""
1523
"""The known formats."""
1526
def find_format(klass, a_bzrdir):
1527
"""Return the format for the working tree object in a_bzrdir."""
1529
transport = a_bzrdir.get_workingtree_transport(None)
1530
format_string = transport.get("format").read()
1531
return klass._formats[format_string]
1533
raise errors.NoWorkingTree(base=transport.base)
1535
raise errors.UnknownFormatError(format_string)
1538
def get_default_format(klass):
1539
"""Return the current default format."""
1540
return klass._default_format
1542
def get_format_string(self):
1543
"""Return the ASCII format string that identifies this format."""
1544
raise NotImplementedError(self.get_format_string)
1546
def get_format_description(self):
1547
"""Return the short description for this format."""
1548
raise NotImplementedError(self.get_format_description)
1550
def is_supported(self):
1551
"""Is this format supported?
1553
Supported formats can be initialized and opened.
1554
Unsupported formats may not support initialization or committing or
1555
some other features depending on the reason for not being supported.
1560
def register_format(klass, format):
1561
klass._formats[format.get_format_string()] = format
1564
def set_default_format(klass, format):
1565
klass._default_format = format
1568
def unregister_format(klass, format):
1569
assert klass._formats[format.get_format_string()] is format
1570
del klass._formats[format.get_format_string()]
1574
class WorkingTreeFormat2(WorkingTreeFormat):
1575
"""The second working tree format.
1577
This format modified the hash cache from the format 1 hash cache.
1580
def get_format_description(self):
1581
"""See WorkingTreeFormat.get_format_description()."""
1582
return "Working tree format 2"
1584
def stub_initialize_remote(self, control_files):
1585
"""As a special workaround create critical control files for a remote working tree
1587
This ensures that it can later be updated and dealt with locally,
1588
since BzrDirFormat6 and BzrDirFormat5 cannot represent dirs with
1589
no working tree. (See bug #43064).
1593
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1595
control_files.put('inventory', sio)
1597
control_files.put_utf8('pending-merges', '')
1600
def initialize(self, a_bzrdir, revision_id=None):
1601
"""See WorkingTreeFormat.initialize()."""
1602
if not isinstance(a_bzrdir.transport, LocalTransport):
1603
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1604
branch = a_bzrdir.open_branch()
1605
if revision_id is not None:
1608
revision_history = branch.revision_history()
1610
position = revision_history.index(revision_id)
1612
raise errors.NoSuchRevision(branch, revision_id)
1613
branch.set_revision_history(revision_history[:position + 1])
1616
revision = branch.last_revision()
1618
wt = WorkingTree(a_bzrdir.root_transport.base,
1624
wt._write_inventory(inv)
1625
wt.set_root_id(inv.root.file_id)
1626
wt.set_last_revision(revision)
1627
wt.set_pending_merges([])
1628
build_tree(wt.basis_tree(), wt)
1632
super(WorkingTreeFormat2, self).__init__()
1633
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1635
def open(self, a_bzrdir, _found=False):
1636
"""Return the WorkingTree object for a_bzrdir
1638
_found is a private parameter, do not use it. It is used to indicate
1639
if format probing has already been done.
1642
# we are being called directly and must probe.
1643
raise NotImplementedError
1644
if not isinstance(a_bzrdir.transport, LocalTransport):
1645
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1646
return WorkingTree(a_bzrdir.root_transport.base,
1652
class WorkingTreeFormat3(WorkingTreeFormat):
1653
"""The second working tree format updated to record a format marker.
1656
- exists within a metadir controlling .bzr
1657
- includes an explicit version marker for the workingtree control
1658
files, separate from the BzrDir format
1659
- modifies the hash cache format
1661
- uses a LockDir to guard access to the repository
1664
def get_format_string(self):
1665
"""See WorkingTreeFormat.get_format_string()."""
1666
return "Bazaar-NG Working Tree format 3"
1668
def get_format_description(self):
1669
"""See WorkingTreeFormat.get_format_description()."""
1670
return "Working tree format 3"
1672
_lock_file_name = 'lock'
1673
_lock_class = LockDir
1675
def _open_control_files(self, a_bzrdir):
1676
transport = a_bzrdir.get_workingtree_transport(None)
1677
return LockableFiles(transport, self._lock_file_name,
1680
def initialize(self, a_bzrdir, revision_id=None):
1681
"""See WorkingTreeFormat.initialize().
1683
revision_id allows creating a working tree at a differnet
1684
revision than the branch is at.
1686
if not isinstance(a_bzrdir.transport, LocalTransport):
1687
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1688
transport = a_bzrdir.get_workingtree_transport(self)
1689
control_files = self._open_control_files(a_bzrdir)
1690
control_files.create_lock()
1691
control_files.lock_write()
1692
control_files.put_utf8('format', self.get_format_string())
1693
branch = a_bzrdir.open_branch()
1694
if revision_id is None:
1695
revision_id = branch.last_revision()
1697
wt = WorkingTree3(a_bzrdir.root_transport.base,
1703
_control_files=control_files)
1706
wt._write_inventory(inv)
1707
wt.set_root_id(inv.root.file_id)
1708
wt.set_last_revision(revision_id)
1709
wt.set_pending_merges([])
1710
build_tree(wt.basis_tree(), wt)
1713
control_files.unlock()
1717
super(WorkingTreeFormat3, self).__init__()
1718
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1720
def open(self, a_bzrdir, _found=False):
1721
"""Return the WorkingTree object for a_bzrdir
1723
_found is a private parameter, do not use it. It is used to indicate
1724
if format probing has already been done.
1727
# we are being called directly and must probe.
1728
raise NotImplementedError
1729
if not isinstance(a_bzrdir.transport, LocalTransport):
1730
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1731
control_files = self._open_control_files(a_bzrdir)
1732
return WorkingTree3(a_bzrdir.root_transport.base,
1736
_control_files=control_files)
1739
return self.get_format_string()
1742
# formats which have no format string are not discoverable
1743
# and not independently creatable, so are not registered.
1744
__default_format = WorkingTreeFormat3()
1745
WorkingTreeFormat.register_format(__default_format)
1746
WorkingTreeFormat.set_default_format(__default_format)
1747
_legacy_formats = [WorkingTreeFormat2(),
1751
class WorkingTreeTestProviderAdapter(object):
1752
"""A tool to generate a suite testing multiple workingtree formats at once.
1754
This is done by copying the test once for each transport and injecting
1755
the transport_server, transport_readonly_server, and workingtree_format
1756
classes into each copy. Each copy is also given a new id() to make it
1760
def __init__(self, transport_server, transport_readonly_server, formats):
1761
self._transport_server = transport_server
1762
self._transport_readonly_server = transport_readonly_server
1763
self._formats = formats
1765
def adapt(self, test):
1766
from bzrlib.tests import TestSuite
1767
result = TestSuite()
1768
for workingtree_format, bzrdir_format in self._formats:
1769
new_test = deepcopy(test)
1770
new_test.transport_server = self._transport_server
1771
new_test.transport_readonly_server = self._transport_readonly_server
1772
new_test.bzrdir_format = bzrdir_format
1773
new_test.workingtree_format = workingtree_format
1774
def make_new_test_id():
1775
new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1776
return lambda: new_id
1777
new_test.id = make_new_test_id()
1778
result.addTest(new_test)