25
25
At the moment every WorkingTree has its own branch. Remote
26
26
WorkingTrees aren't supported.
28
To get a WorkingTree, call Branch.working_tree():
28
To get a WorkingTree, call bzrdir.open_workingtree() or
29
WorkingTree.open(dir).
32
# TODO: Don't allow WorkingTrees to be constructed for remote branches if
35
# FIXME: I don't know if writing out the cache from the destructor is really a
36
# good idea, because destructors are considered poor taste in Python, and it's
37
# not predictable when it will be written out.
32
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
33
CONFLICT_HEADER_1 = "BZR conflict list format 1"
39
35
# TODO: Give the workingtree sole responsibility for the working inventory;
40
36
# remove the variable and references to it from the branch. This may require
41
37
# updating the commit code so as to update the inventory within the working
42
38
# copy, and making sure there's only one WorkingTree for any directory on disk.
43
# At the momenthey may alias the inventory and have old copies of it in memory.
39
# At the moment they may alias the inventory and have old copies of it in
40
# memory. (Now done? -- mbp 20060309)
42
from binascii import hexlify
45
44
from copy import deepcopy
45
from cStringIO import StringIO
53
from bzrlib.atomicfile import AtomicFile
50
54
from bzrlib.branch import (Branch,
56
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
57
import bzrlib.bzrdir as bzrdir
58
from bzrlib.decorators import needs_read_lock, needs_write_lock
59
import bzrlib.errors as errors
55
60
from bzrlib.errors import (BzrCheckError,
58
63
WeaveRevisionNotPresent,
61
from bzrlib.inventory import InventoryEntry
62
from bzrlib.osutils import (appendpath,
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 (
91
from bzrlib.progress import DummyProgress, ProgressPhase
92
from bzrlib.revision import NULL_REVISION
93
from bzrlib.rio import RioReader, rio_file, Stanza
94
from bzrlib.symbol_versioning import *
76
95
from bzrlib.textui import show_status
78
from bzrlib.trace import mutter
97
from bzrlib.transform import build_tree
98
from bzrlib.trace import mutter, note
99
from bzrlib.transport import get_transport
100
from bzrlib.transport.local import LocalTransport
101
import bzrlib.urlutils as urlutils
79
103
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)
82
133
def gen_file_id(name):
83
"""Return new file id.
85
This should probably generate proper UUIDs, but for the moment we
86
cope with just randomness because running uuidgen every time is
89
from binascii import hexlify
96
idx = name.rfind('\\')
100
# make it not a hidden file
101
name = name.lstrip('.')
103
# remove any wierd characters; we don't escape them but rather
105
name = re.sub(r'[^\w.]', '', name)
107
s = hexlify(rand_bytes(8))
108
return '-'.join((name, compact_date(time()), s))
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()
111
145
def gen_root_id():
186
226
(branch.base is not cross checked, because for remote branches that
187
227
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
189
245
from bzrlib.hashcache import HashCache
190
246
from bzrlib.trace import note, mutter
191
247
assert isinstance(basedir, basestring), \
192
248
"base directory %r is not a string" % basedir
194
branch = Branch.open(basedir)
195
assert isinstance(branch, Branch), \
196
"branch %r is not a Branch" % branch
249
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
198
264
self.basedir = realpath(basedir)
265
# if branch is at our basedir and is a format 6 or less
266
if isinstance(self._format, WorkingTreeFormat2):
267
# share control object
268
self._control_files = self.branch.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
200
276
# update the whole cache up front and write to disk if anything changed;
201
277
# in the future we might want to do this more selectively
202
278
# two possible ways offer themselves : in self._unlock, write the cache
203
279
# if needed, or, when the cache sees a change, append it to the hash
204
280
# cache file, and have the parser take the most recent entry for a
205
281
# given path only.
206
hc = self._hashcache = HashCache(basedir)
282
cache_filename = self.bzrdir.get_workingtree_transport(None).local_abspath('stat-cache')
283
hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
285
# is this scan needed ? it makes things kinda slow.
210
288
if hc.needs_write:
211
289
mutter("write hc")
214
self._set_inventory(self.read_working_inventory())
292
if _inventory is None:
293
self._set_inventory(self.read_working_inventory())
295
self._set_inventory(_inventory)
298
fget=lambda self: self._branch,
299
doc="""The branch this WorkingTree is connected to.
301
This cannot be set - it is reflective of the actual disk structure
302
the working tree has been constructed from.
305
def break_lock(self):
306
"""Break a lock if one is present from another instance.
308
Uses the ui factory to ask for confirmation if the lock may be from
311
This will probe the repository for its lock as well.
313
self._control_files.break_lock()
314
self.branch.break_lock()
216
316
def _set_inventory(self, inv):
217
317
self._inventory = inv
218
318
self.path2id = self._inventory.path2id
320
def is_control_filename(self, filename):
321
"""True if filename is the name of a control file in this tree.
323
:param filename: A filename within the tree. This is a relative path
324
from the root of this tree.
326
This is true IF and ONLY IF the filename is part of the meta data
327
that bzr controls in this tree. I.E. a random .bzr directory placed
328
on disk will not be a control file for this tree.
330
return self.bzrdir.is_control_filename(filename)
333
def open(path=None, _unsupported=False):
334
"""Open an existing working tree at path.
338
path = os.path.getcwdu()
339
control = bzrdir.BzrDir.open(path, _unsupported)
340
return control.open_workingtree(_unsupported)
221
343
def open_containing(path=None):
222
344
"""Open an existing working tree which has its root about path.
753
1134
# treat dotfiles correctly and allows * to match /.
754
1135
# Eventually it should be replaced with something more
757
for pat in self.get_ignore_list():
758
if '/' in pat or '\\' in pat:
760
# as a special case, you can put ./ at the start of a
761
# pattern; this is good to match in the top-level
764
if (pat[:2] == './') or (pat[:2] == '.\\'):
768
if fnmatch.fnmatchcase(filename, newpat):
771
if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
1138
rules = self._get_ignore_rules_as_regex()
1139
for regex, mapping in rules:
1140
match = regex.match(filename)
1141
if match is not None:
1142
# one or more of the groups in mapping will have a non-None group
1144
groups = match.groups()
1145
rules = [mapping[group] for group in
1146
mapping if groups[group] is not None]
776
1150
def kind(self, file_id):
777
1151
return file_kind(self.id2abspath(file_id))
1154
def last_revision(self):
1155
"""Return the last revision id of this working tree.
1157
In early branch formats this was == the branch last_revision,
1158
but that cannot be relied upon - for working tree operations,
1159
always use tree.last_revision().
1161
return self.branch.last_revision()
1163
def is_locked(self):
1164
return self._control_files.is_locked()
779
1166
def lock_read(self):
780
1167
"""See Branch.lock_read, and WorkingTree.unlock."""
781
return self.branch.lock_read()
1168
self.branch.lock_read()
1170
return self._control_files.lock_read()
1172
self.branch.unlock()
783
1175
def lock_write(self):
784
1176
"""See Branch.lock_write, and WorkingTree.unlock."""
785
return self.branch.lock_write()
787
def _basis_inventory_name(self, revision_id):
788
return 'basis-inventory.%s' % revision_id
790
def set_last_revision(self, new_revision, old_revision=None):
793
path = self._basis_inventory_name(old_revision)
794
path = self.branch._rel_controlfilename(path)
795
self.branch._transport.delete(path)
799
xml = self.branch.get_inventory_xml(new_revision)
800
path = self._basis_inventory_name(new_revision)
801
self.branch.put_controlfile(path, xml)
1177
self.branch.lock_write()
1179
return self._control_files.lock_write()
1181
self.branch.unlock()
1184
def get_physical_lock_status(self):
1185
return self._control_files.get_physical_lock_status()
1187
def _basis_inventory_name(self):
1188
return 'basis-inventory'
1191
def set_last_revision(self, new_revision):
1192
"""Change the last revision in the working tree."""
1193
if self._change_last_revision(new_revision):
1194
self._cache_basis_inventory(new_revision)
1196
def _change_last_revision(self, new_revision):
1197
"""Template method part of set_last_revision to perform the change.
1199
This is used to allow WorkingTree3 instances to not affect branch
1200
when their last revision is set.
1202
if new_revision is None:
1203
self.branch.set_revision_history([])
1205
# current format is locked in with the branch
1206
revision_history = self.branch.revision_history()
1208
position = revision_history.index(new_revision)
1210
raise errors.NoSuchRevision(self.branch, new_revision)
1211
self.branch.set_revision_history(revision_history[:position + 1])
1214
def _cache_basis_inventory(self, new_revision):
1215
"""Cache new_revision as the basis inventory."""
1216
# TODO: this should allow the ready-to-use inventory to be passed in,
1217
# as commit already has that ready-to-use [while the format is the
1220
# this double handles the inventory - unpack and repack -
1221
# but is easier to understand. We can/should put a conditional
1222
# in here based on whether the inventory is in the latest format
1223
# - perhaps we should repack all inventories on a repository
1225
# the fast path is to copy the raw xml from the repository. If the
1226
# xml contains 'revision_id="', then we assume the right
1227
# revision_id is set. We must check for this full string, because a
1228
# root node id can legitimately look like 'revision_id' but cannot
1230
xml = self.branch.repository.get_inventory_xml(new_revision)
1231
if not 'revision_id="' in xml.split('\n', 1)[0]:
1232
inv = self.branch.repository.deserialise_inventory(
1234
inv.revision_id = new_revision
1235
xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1236
assert isinstance(xml, str), 'serialised xml must be bytestring.'
1237
path = self._basis_inventory_name()
1239
self._control_files.put(path, sio)
802
1240
except WeaveRevisionNotPresent:
805
def read_basis_inventory(self, revision_id):
1243
def read_basis_inventory(self):
806
1244
"""Read the cached basis inventory."""
807
path = self._basis_inventory_name(revision_id)
808
return self.branch.controlfile(path, 'r').read()
1245
path = self._basis_inventory_name()
1246
return self._control_files.get(path).read()
810
1248
@needs_read_lock
811
1249
def read_working_inventory(self):
812
1250
"""Read the working inventory."""
813
1251
# ElementTree does its own conversion from UTF-8, so open in
815
f = self.branch.controlfile('inventory', 'rb')
816
return bzrlib.xml5.serializer_v5.read_inventory(f)
1253
result = bzrlib.xml5.serializer_v5.read_inventory(
1254
self._control_files.get('inventory'))
1255
self._set_inventory(result)
818
1258
@needs_write_lock
819
def remove(self, files, verbose=False):
1259
def remove(self, files, verbose=False, to_file=None):
820
1260
"""Remove nominated files from the working inventory..
822
1262
This does not remove their text. This does not run on XXX on what? RBC
914
1359
between multiple working trees, i.e. via shared storage, then we
915
1360
would probably want to lock both the local tree, and the branch.
917
if self._hashcache.needs_write:
1362
# FIXME: We want to write out the hashcache only when the last lock on
1363
# this working copy is released. Peeking at the lock count is a bit
1364
# of a nasty hack; probably it's better to have a transaction object,
1365
# which can do some finalization when it's either successfully or
1366
# unsuccessfully completed. (Denys's original patch did that.)
1367
# RBC 20060206 hooking into transaction will couple lock and transaction
1368
# wrongly. Hooking into unlock on the control files object is fine though.
1370
# TODO: split this per format so there is no ugly if block
1371
if self._hashcache.needs_write and (
1372
# dedicated lock files
1373
self._control_files._lock_count==1 or
1375
(self._control_files is self.branch.control_files and
1376
self._control_files._lock_count==3)):
918
1377
self._hashcache.write()
919
return self.branch.unlock()
1378
# reverse order of locking.
1380
return self._control_files.unlock()
1382
self.branch.unlock()
1386
"""Update a working tree along its branch.
1388
This will update the branch if its bound too, which means we have multiple trees involved:
1389
The new basis tree of the master.
1390
The old basis tree of the branch.
1391
The old basis tree of the working tree.
1392
The current working tree state.
1393
pathologically all three may be different, and non ancestors of each other.
1394
Conceptually we want to:
1395
Preserve the wt.basis->wt.state changes
1396
Transform the wt.basis to the new master basis.
1397
Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
1398
Restore the wt.basis->wt.state changes.
1400
There isn't a single operation at the moment to do that, so we:
1401
Merge current state -> basis tree of the master w.r.t. the old tree basis.
1402
Do a 'normal' merge of the old branch basis if it is relevant.
1404
old_tip = self.branch.update()
1405
if old_tip is not None:
1406
self.add_pending_merge(old_tip)
1407
self.branch.lock_read()
1410
if self.last_revision() != self.branch.last_revision():
1411
# merge tree state up to new branch tip.
1412
basis = self.basis_tree()
1413
to_tree = self.branch.basis_tree()
1414
result += merge_inner(self.branch,
1418
self.set_last_revision(self.branch.last_revision())
1419
if old_tip and old_tip != self.last_revision():
1420
# our last revision was not the prior branch last revision
1421
# and we have converted that last revision to a pending merge.
1422
# base is somewhere between the branch tip now
1423
# and the now pending merge
1424
from bzrlib.revision import common_ancestor
1426
base_rev_id = common_ancestor(self.branch.last_revision(),
1428
self.branch.repository)
1429
except errors.NoCommonAncestor:
1431
base_tree = self.branch.repository.revision_tree(base_rev_id)
1432
other_tree = self.branch.repository.revision_tree(old_tip)
1433
result += merge_inner(self.branch,
1439
self.branch.unlock()
921
1441
@needs_write_lock
922
1442
def _write_inventory(self, inv):
923
1443
"""Write inventory as the current inventory."""
924
from cStringIO import StringIO
925
from bzrlib.atomicfile import AtomicFile
926
1444
sio = StringIO()
927
1445
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
929
f = AtomicFile(self.branch.controlfilename('inventory'))
1447
self._control_files.put('inventory', sio)
935
1448
self._set_inventory(inv)
936
1449
mutter('wrote working inventory')
939
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1451
def set_conflicts(self, arg):
1452
raise UnsupportedOperation(self.set_conflicts, self)
1455
def conflicts(self):
1456
conflicts = ConflictList()
1457
for conflicted in self._iter_conflicts():
1460
if file_kind(self.abspath(conflicted)) != "file":
1462
except errors.NoSuchFile:
1465
for suffix in ('.THIS', '.OTHER'):
1467
kind = file_kind(self.abspath(conflicted+suffix))
1470
except errors.NoSuchFile:
1474
ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1475
conflicts.append(Conflict.factory(ctype, path=conflicted,
1476
file_id=self.path2id(conflicted)))
1480
class WorkingTree3(WorkingTree):
1481
"""This is the Format 3 working tree.
1483
This differs from the base WorkingTree by:
1484
- having its own file lock
1485
- having its own last-revision property.
1487
This is new in bzr 0.8
1491
def last_revision(self):
1492
"""See WorkingTree.last_revision."""
1494
return self._control_files.get_utf8('last-revision').read()
1498
def _change_last_revision(self, revision_id):
1499
"""See WorkingTree._change_last_revision."""
1500
if revision_id is None or revision_id == NULL_REVISION:
1502
self._control_files._transport.delete('last-revision')
1503
except errors.NoSuchFile:
1508
self.branch.revision_history().index(revision_id)
1510
raise errors.NoSuchRevision(self.branch, revision_id)
1511
self._control_files.put_utf8('last-revision', revision_id)
1515
def set_conflicts(self, conflicts):
1516
self._put_rio('conflicts', conflicts.to_stanzas(),
1520
def conflicts(self):
1522
confile = self._control_files.get('conflicts')
1524
return ConflictList()
1526
if confile.next() != CONFLICT_HEADER_1 + '\n':
1527
raise ConflictFormatError()
1528
except StopIteration:
1529
raise ConflictFormatError()
1530
return ConflictList.from_stanzas(RioReader(confile))
940
1533
def get_conflicted_stem(path):
941
1534
for suffix in CONFLICT_SUFFIXES:
942
1535
if path.endswith(suffix):
943
1536
return path[:-len(suffix)]
1538
@deprecated_function(zero_eight)
1539
def is_control_file(filename):
1540
"""See WorkingTree.is_control_filename(filename)."""
1541
## FIXME: better check
1542
filename = normpath(filename)
1543
while filename != '':
1544
head, tail = os.path.split(filename)
1545
## mutter('check %r for control file' % ((head, tail),))
1548
if filename == head:
1554
class WorkingTreeFormat(object):
1555
"""An encapsulation of the initialization and open routines for a format.
1557
Formats provide three things:
1558
* An initialization routine,
1562
Formats are placed in an dict by their format string for reference
1563
during workingtree opening. Its not required that these be instances, they
1564
can be classes themselves with class methods - it simply depends on
1565
whether state is needed for a given format or not.
1567
Once a format is deprecated, just deprecate the initialize and open
1568
methods on the format class. Do not deprecate the object, as the
1569
object will be created every time regardless.
1572
_default_format = None
1573
"""The default format used for new trees."""
1576
"""The known formats."""
1579
def find_format(klass, a_bzrdir):
1580
"""Return the format for the working tree object in a_bzrdir."""
1582
transport = a_bzrdir.get_workingtree_transport(None)
1583
format_string = transport.get("format").read()
1584
return klass._formats[format_string]
1586
raise errors.NoWorkingTree(base=transport.base)
1588
raise errors.UnknownFormatError(format=format_string)
1591
def get_default_format(klass):
1592
"""Return the current default format."""
1593
return klass._default_format
1595
def get_format_string(self):
1596
"""Return the ASCII format string that identifies this format."""
1597
raise NotImplementedError(self.get_format_string)
1599
def get_format_description(self):
1600
"""Return the short description for this format."""
1601
raise NotImplementedError(self.get_format_description)
1603
def is_supported(self):
1604
"""Is this format supported?
1606
Supported formats can be initialized and opened.
1607
Unsupported formats may not support initialization or committing or
1608
some other features depending on the reason for not being supported.
1613
def register_format(klass, format):
1614
klass._formats[format.get_format_string()] = format
1617
def set_default_format(klass, format):
1618
klass._default_format = format
1621
def unregister_format(klass, format):
1622
assert klass._formats[format.get_format_string()] is format
1623
del klass._formats[format.get_format_string()]
1627
class WorkingTreeFormat2(WorkingTreeFormat):
1628
"""The second working tree format.
1630
This format modified the hash cache from the format 1 hash cache.
1633
def get_format_description(self):
1634
"""See WorkingTreeFormat.get_format_description()."""
1635
return "Working tree format 2"
1637
def stub_initialize_remote(self, control_files):
1638
"""As a special workaround create critical control files for a remote working tree
1640
This ensures that it can later be updated and dealt with locally,
1641
since BzrDirFormat6 and BzrDirFormat5 cannot represent dirs with
1642
no working tree. (See bug #43064).
1646
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1648
control_files.put('inventory', sio)
1650
control_files.put_utf8('pending-merges', '')
1653
def initialize(self, a_bzrdir, revision_id=None):
1654
"""See WorkingTreeFormat.initialize()."""
1655
if not isinstance(a_bzrdir.transport, LocalTransport):
1656
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1657
branch = a_bzrdir.open_branch()
1658
if revision_id is not None:
1661
revision_history = branch.revision_history()
1663
position = revision_history.index(revision_id)
1665
raise errors.NoSuchRevision(branch, revision_id)
1666
branch.set_revision_history(revision_history[:position + 1])
1669
revision = branch.last_revision()
1671
wt = WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
1677
wt._write_inventory(inv)
1678
wt.set_root_id(inv.root.file_id)
1679
wt.set_last_revision(revision)
1680
wt.set_pending_merges([])
1681
build_tree(wt.basis_tree(), wt)
1685
super(WorkingTreeFormat2, self).__init__()
1686
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1688
def open(self, a_bzrdir, _found=False):
1689
"""Return the WorkingTree object for a_bzrdir
1691
_found is a private parameter, do not use it. It is used to indicate
1692
if format probing has already been done.
1695
# we are being called directly and must probe.
1696
raise NotImplementedError
1697
if not isinstance(a_bzrdir.transport, LocalTransport):
1698
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1699
return WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
1705
class WorkingTreeFormat3(WorkingTreeFormat):
1706
"""The second working tree format updated to record a format marker.
1709
- exists within a metadir controlling .bzr
1710
- includes an explicit version marker for the workingtree control
1711
files, separate from the BzrDir format
1712
- modifies the hash cache format
1714
- uses a LockDir to guard access to the repository
1717
def get_format_string(self):
1718
"""See WorkingTreeFormat.get_format_string()."""
1719
return "Bazaar-NG Working Tree format 3"
1721
def get_format_description(self):
1722
"""See WorkingTreeFormat.get_format_description()."""
1723
return "Working tree format 3"
1725
_lock_file_name = 'lock'
1726
_lock_class = LockDir
1728
def _open_control_files(self, a_bzrdir):
1729
transport = a_bzrdir.get_workingtree_transport(None)
1730
return LockableFiles(transport, self._lock_file_name,
1733
def initialize(self, a_bzrdir, revision_id=None):
1734
"""See WorkingTreeFormat.initialize().
1736
revision_id allows creating a working tree at a different
1737
revision than the branch is at.
1739
if not isinstance(a_bzrdir.transport, LocalTransport):
1740
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1741
transport = a_bzrdir.get_workingtree_transport(self)
1742
control_files = self._open_control_files(a_bzrdir)
1743
control_files.create_lock()
1744
control_files.lock_write()
1745
control_files.put_utf8('format', self.get_format_string())
1746
branch = a_bzrdir.open_branch()
1747
if revision_id is None:
1748
revision_id = branch.last_revision()
1750
wt = WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1756
_control_files=control_files)
1759
wt._write_inventory(inv)
1760
wt.set_root_id(inv.root.file_id)
1761
wt.set_last_revision(revision_id)
1762
wt.set_pending_merges([])
1763
build_tree(wt.basis_tree(), wt)
1766
control_files.unlock()
1770
super(WorkingTreeFormat3, self).__init__()
1771
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1773
def open(self, a_bzrdir, _found=False):
1774
"""Return the WorkingTree object for a_bzrdir
1776
_found is a private parameter, do not use it. It is used to indicate
1777
if format probing has already been done.
1780
# we are being called directly and must probe.
1781
raise NotImplementedError
1782
if not isinstance(a_bzrdir.transport, LocalTransport):
1783
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1784
control_files = self._open_control_files(a_bzrdir)
1785
return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1789
_control_files=control_files)
1792
return self.get_format_string()
1795
# formats which have no format string are not discoverable
1796
# and not independently creatable, so are not registered.
1797
__default_format = WorkingTreeFormat3()
1798
WorkingTreeFormat.register_format(__default_format)
1799
WorkingTreeFormat.set_default_format(__default_format)
1800
_legacy_formats = [WorkingTreeFormat2(),
1804
class WorkingTreeTestProviderAdapter(object):
1805
"""A tool to generate a suite testing multiple workingtree formats at once.
1807
This is done by copying the test once for each transport and injecting
1808
the transport_server, transport_readonly_server, and workingtree_format
1809
classes into each copy. Each copy is also given a new id() to make it
1813
def __init__(self, transport_server, transport_readonly_server, formats):
1814
self._transport_server = transport_server
1815
self._transport_readonly_server = transport_readonly_server
1816
self._formats = formats
1818
def adapt(self, test):
1819
from bzrlib.tests import TestSuite
1820
result = TestSuite()
1821
for workingtree_format, bzrdir_format in self._formats:
1822
new_test = deepcopy(test)
1823
new_test.transport_server = self._transport_server
1824
new_test.transport_readonly_server = self._transport_readonly_server
1825
new_test.bzrdir_format = bzrdir_format
1826
new_test.workingtree_format = workingtree_format
1827
def make_new_test_id():
1828
new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1829
return lambda: new_id
1830
new_test.id = make_new_test_id()
1831
result.addTest(new_test)