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
44
42
from copy import deepcopy
45
43
from cStringIO import StringIO
53
50
from bzrlib.atomicfile import AtomicFile
54
51
from bzrlib.branch import (Branch,
56
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
57
import bzrlib.bzrdir as bzrdir
58
57
from bzrlib.decorators import needs_read_lock, needs_write_lock
59
import bzrlib.errors as errors
60
58
from bzrlib.errors import (BzrCheckError,
64
61
WeaveRevisionNotPresent,
68
MergeModifiedFormatError,
71
from bzrlib.inventory import InventoryEntry, Inventory
72
from bzrlib.lockable_files import LockableFiles, TransportLock
73
from bzrlib.lockdir import LockDir
74
from bzrlib.merge import merge_inner, transform_tree
75
from bzrlib.osutils import (
65
from bzrlib.inventory import InventoryEntry
66
from bzrlib.lockable_files import LockableFiles
67
from bzrlib.osutils import (appendpath,
92
from bzrlib.progress import DummyProgress, ProgressPhase
93
from bzrlib.revision import NULL_REVISION
94
from bzrlib.rio import RioReader, rio_file, Stanza
95
82
from bzrlib.symbol_versioning import *
96
83
from bzrlib.textui import show_status
98
from bzrlib.transform import build_tree
99
from bzrlib.trace import mutter, note
85
from bzrlib.trace import mutter
100
86
from bzrlib.transport import get_transport
101
from bzrlib.transport.local import LocalTransport
102
import bzrlib.urlutils as urlutils
104
87
import bzrlib.xml5
107
# the regex here does the following:
108
# 1) remove any weird characters; we don't escape them but rather
110
# 2) match leading '.'s to make it not hidden
111
_gen_file_id_re = re.compile(r'[^\w.]|(^\.*)')
112
_gen_id_suffix = None
116
def _next_id_suffix():
117
"""Create a new file id suffix that is reasonably unique.
119
On the first call we combine the current time with 64 bits of randomness
120
to give a highly probably globally unique number. Then each call in the same
121
process adds 1 to a serial number we append to that unique value.
123
# XXX TODO: change bzrlib.add.smart_add to call workingtree.add() rather
124
# than having to move the id randomness out of the inner loop like this.
125
# XXX TODO: for the global randomness this uses we should add the thread-id
126
# before the serial #.
127
global _gen_id_suffix, _gen_id_serial
128
if _gen_id_suffix is None:
129
_gen_id_suffix = "-%s-%s-" % (compact_date(time()), rand_chars(16))
131
return _gen_id_suffix + str(_gen_id_serial)
134
90
def gen_file_id(name):
135
"""Return new file id for the basename 'name'.
137
The uniqueness is supplied from _next_id_suffix.
139
# XXX TODO: squash the filename to lowercase.
140
# XXX TODO: truncate the filename to something like 20 or 30 chars.
141
# XXX TODO: consider what to do with ids that look like illegal filepaths
142
# on platforms we support.
143
return _gen_file_id_re.sub('', name) + _next_id_suffix()
91
"""Return new file id.
93
This should probably generate proper UUIDs, but for the moment we
94
cope with just randomness because running uuidgen every time is
97
from binascii import hexlify
101
idx = name.rfind('/')
103
name = name[idx+1 : ]
104
idx = name.rfind('\\')
106
name = name[idx+1 : ]
108
# make it not a hidden file
109
name = name.lstrip('.')
111
# remove any wierd characters; we don't escape them but rather
113
name = re.sub(r'[^\w.]', '', name)
115
s = hexlify(rand_bytes(8))
116
return '-'.join((name, compact_date(time()), s))
146
119
def gen_root_id():
227
194
(branch.base is not cross checked, because for remote branches that
228
195
would be meaningless).
230
self._format = _format
231
self.bzrdir = _bzrdir
233
# not created via open etc.
234
warn("WorkingTree() is deprecated as of bzr version 0.8. "
235
"Please use bzrdir.open_workingtree or WorkingTree.open().",
238
wt = WorkingTree.open(basedir)
239
self._branch = wt.branch
240
self.basedir = wt.basedir
241
self._control_files = wt._control_files
242
self._hashcache = wt._hashcache
243
self._set_inventory(wt._inventory)
244
self._format = wt._format
245
self.bzrdir = wt.bzrdir
246
197
from bzrlib.hashcache import HashCache
247
198
from bzrlib.trace import note, mutter
248
199
assert isinstance(basedir, basestring), \
249
200
"base directory %r is not a string" % basedir
250
201
basedir = safe_unicode(basedir)
251
mutter("opening working tree %r", basedir)
252
if deprecated_passed(branch):
254
warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
255
" Please use bzrdir.open_workingtree() or"
256
" WorkingTree.open().",
260
self._branch = branch
262
self._branch = self.bzrdir.open_branch()
263
assert isinstance(self.branch, Branch), \
264
"branch %r is not a Branch" % self.branch
202
mutter("openeing working tree %r", basedir)
204
branch = Branch.open(basedir)
205
assert isinstance(branch, Branch), \
206
"branch %r is not a Branch" % branch
265
208
self.basedir = realpath(basedir)
266
209
# if branch is at our basedir and is a format 6 or less
267
if isinstance(self._format, WorkingTreeFormat2):
268
# share control object
210
if (isinstance(self.branch._branch_format,
211
(BzrBranchFormat4, BzrBranchFormat5, BzrBranchFormat6))
212
# might be able to share control object
213
and self.branch.base.split('/')[-2] == self.basedir.split('/')[-1]):
269
214
self._control_files = self.branch.control_files
215
elif _control_files is not None:
216
assert False, "not done yet"
217
# self._control_files = _control_files
271
# only ready for format 3
272
assert isinstance(self._format, WorkingTreeFormat3)
273
assert isinstance(_control_files, LockableFiles), \
274
"_control_files must be a LockableFiles, not %r" \
276
self._control_files = _control_files
219
self._control_files = LockableFiles(
220
get_transport(self.basedir).clone(bzrlib.BZRDIR), 'branch-lock')
277
222
# update the whole cache up front and write to disk if anything changed;
278
223
# in the future we might want to do this more selectively
279
224
# two possible ways offer themselves : in self._unlock, write the cache
280
225
# if needed, or, when the cache sees a change, append it to the hash
281
226
# cache file, and have the parser take the most recent entry for a
282
227
# given path only.
283
cache_filename = self.bzrdir.get_workingtree_transport(None).local_abspath('stat-cache')
284
hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
228
hc = self._hashcache = HashCache(basedir)
286
# is this scan needed ? it makes things kinda slow.
289
232
if hc.needs_write:
290
233
mutter("write hc")
464
359
## XXX: badly named; this is not in the store at all
465
360
return self.abspath(self.id2path(file_id))
468
def clone(self, to_bzrdir, revision_id=None, basis=None):
469
"""Duplicate this working tree into to_bzr, including all state.
471
Specifically modified files are kept as modified, but
472
ignored and unknown files are discarded.
474
If you want to make a new line of development, see bzrdir.sprout()
477
If not None, the cloned tree will have its last revision set to
478
revision, and and difference between the source trees last revision
479
and this one merged in.
482
If not None, a closer copy of a tree which may have some files in
483
common, and which file content should be preferentially copied from.
485
# assumes the target bzr dir format is compatible.
486
result = self._format.initialize(to_bzrdir)
487
self.copy_content_into(result, revision_id)
491
def copy_content_into(self, tree, revision_id=None):
492
"""Copy the current content and user files of this tree into tree."""
493
if revision_id is None:
494
transform_tree(tree, self)
496
# TODO now merge from tree.last_revision to revision
497
transform_tree(tree, self)
498
tree.set_last_revision(revision_id)
500
362
@needs_write_lock
501
def commit(self, message=None, revprops=None, *args, **kwargs):
502
# avoid circular imports
363
def commit(self, *args, **kwargs):
503
364
from bzrlib.commit import Commit
506
if not 'branch-nick' in revprops:
507
revprops['branch-nick'] = self.branch.nick
508
365
# args for wt.commit start at message from the Commit.commit method,
509
366
# but with branch a kwarg now, passing in args as is results in the
510
367
#message being used for the branch
511
args = (DEPRECATED_PARAMETER, message, ) + args
512
Commit().commit(working_tree=self, revprops=revprops, *args, **kwargs)
368
args = (deprecated_nonce, ) + args
369
Commit().commit(working_tree=self, *args, **kwargs)
513
370
self._set_inventory(self.read_working_inventory())
515
372
def id2abspath(self, file_id):
1119
832
# treat dotfiles correctly and allows * to match /.
1120
833
# Eventually it should be replaced with something more
1123
rules = self._get_ignore_rules_as_regex()
1124
for regex, mapping in rules:
1125
match = regex.match(filename)
1126
if match is not None:
1127
# one or more of the groups in mapping will have a non-None group
1129
groups = match.groups()
1130
rules = [mapping[group] for group in
1131
mapping if groups[group] is not None]
836
for pat in self.get_ignore_list():
837
if '/' in pat or '\\' in pat:
839
# as a special case, you can put ./ at the start of a
840
# pattern; this is good to match in the top-level
843
if (pat[:2] == './') or (pat[:2] == '.\\'):
847
if fnmatch.fnmatchcase(filename, newpat):
850
if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
1135
855
def kind(self, file_id):
1136
856
return file_kind(self.id2abspath(file_id))
1139
def last_revision(self):
1140
"""Return the last revision id of this working tree.
1142
In early branch formats this was == the branch last_revision,
1143
but that cannot be relied upon - for working tree operations,
1144
always use tree.last_revision().
1146
return self.branch.last_revision()
1148
def is_locked(self):
1149
return self._control_files.is_locked()
1151
858
def lock_read(self):
1152
859
"""See Branch.lock_read, and WorkingTree.unlock."""
1153
self.branch.lock_read()
1155
return self._control_files.lock_read()
1157
self.branch.unlock()
860
return self.branch.lock_read()
1160
862
def lock_write(self):
1161
863
"""See Branch.lock_write, and WorkingTree.unlock."""
1162
self.branch.lock_write()
1164
return self._control_files.lock_write()
1166
self.branch.unlock()
1169
def get_physical_lock_status(self):
1170
return self._control_files.get_physical_lock_status()
1172
def _basis_inventory_name(self):
1173
return 'basis-inventory'
1176
def set_last_revision(self, new_revision):
1177
"""Change the last revision in the working tree."""
1178
if self._change_last_revision(new_revision):
1179
self._cache_basis_inventory(new_revision)
1181
def _change_last_revision(self, new_revision):
1182
"""Template method part of set_last_revision to perform the change.
1184
This is used to allow WorkingTree3 instances to not affect branch
1185
when their last revision is set.
1187
if new_revision is None:
1188
self.branch.set_revision_history([])
1190
# current format is locked in with the branch
1191
revision_history = self.branch.revision_history()
1193
position = revision_history.index(new_revision)
1195
raise errors.NoSuchRevision(self.branch, new_revision)
1196
self.branch.set_revision_history(revision_history[:position + 1])
1199
def _cache_basis_inventory(self, new_revision):
1200
"""Cache new_revision as the basis inventory."""
1201
# TODO: this should allow the ready-to-use inventory to be passed in,
1202
# as commit already has that ready-to-use [while the format is the
1205
# this double handles the inventory - unpack and repack -
1206
# but is easier to understand. We can/should put a conditional
1207
# in here based on whether the inventory is in the latest format
1208
# - perhaps we should repack all inventories on a repository
1210
# the fast path is to copy the raw xml from the repository. If the
1211
# xml contains 'revision_id="', then we assume the right
1212
# revision_id is set. We must check for this full string, because a
1213
# root node id can legitimately look like 'revision_id' but cannot
864
return self.branch.lock_write()
866
def _basis_inventory_name(self, revision_id):
867
return 'basis-inventory.%s' % revision_id
869
def set_last_revision(self, new_revision, old_revision=None):
870
if old_revision is not None:
872
path = self._basis_inventory_name(old_revision)
873
path = self.branch.control_files._escape(path)
874
self.branch.control_files._transport.delete(path)
1215
878
xml = self.branch.repository.get_inventory_xml(new_revision)
1216
if not 'revision_id="' in xml.split('\n', 1)[0]:
1217
inv = self.branch.repository.deserialise_inventory(
1219
inv.revision_id = new_revision
1220
xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1221
assert isinstance(xml, str), 'serialised xml must be bytestring.'
1222
path = self._basis_inventory_name()
1224
self._control_files.put(path, sio)
879
path = self._basis_inventory_name(new_revision)
880
self.branch.control_files.put_utf8(path, xml)
1225
881
except WeaveRevisionNotPresent:
1228
def read_basis_inventory(self):
884
def read_basis_inventory(self, revision_id):
1229
885
"""Read the cached basis inventory."""
1230
path = self._basis_inventory_name()
1231
return self._control_files.get(path).read()
886
path = self._basis_inventory_name(revision_id)
887
return self.branch.control_files.get_utf8(path).read()
1233
889
@needs_read_lock
1234
890
def read_working_inventory(self):
1235
891
"""Read the working inventory."""
1236
892
# ElementTree does its own conversion from UTF-8, so open in
1238
result = bzrlib.xml5.serializer_v5.read_inventory(
894
return bzrlib.xml5.serializer_v5.read_inventory(
1239
895
self._control_files.get('inventory'))
1240
self._set_inventory(result)
1243
897
@needs_write_lock
1244
def remove(self, files, verbose=False, to_file=None):
898
def remove(self, files, verbose=False):
1245
899
"""Remove nominated files from the working inventory..
1247
901
This does not remove their text. This does not run on XXX on what? RBC
1349
999
# of a nasty hack; probably it's better to have a transaction object,
1350
1000
# which can do some finalization when it's either successfully or
1351
1001
# unsuccessfully completed. (Denys's original patch did that.)
1352
# RBC 20060206 hooking into transaction will couple lock and transaction
1353
# wrongly. Hooking into unlock on the control files object is fine though.
1355
# TODO: split this per format so there is no ugly if block
1356
if self._hashcache.needs_write and (
1357
# dedicated lock files
1358
self._control_files._lock_count==1 or
1360
(self._control_files is self.branch.control_files and
1361
self._control_files._lock_count==3)):
1002
if self._hashcache.needs_write and self.branch.control_files._lock_count==1:
1362
1003
self._hashcache.write()
1363
# reverse order of locking.
1365
return self._control_files.unlock()
1367
self.branch.unlock()
1371
"""Update a working tree along its branch.
1373
This will update the branch if its bound too, which means we have multiple trees involved:
1374
The new basis tree of the master.
1375
The old basis tree of the branch.
1376
The old basis tree of the working tree.
1377
The current working tree state.
1378
pathologically all three may be different, and non ancestors of each other.
1379
Conceptually we want to:
1380
Preserve the wt.basis->wt.state changes
1381
Transform the wt.basis to the new master basis.
1382
Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
1383
Restore the wt.basis->wt.state changes.
1385
There isn't a single operation at the moment to do that, so we:
1386
Merge current state -> basis tree of the master w.r.t. the old tree basis.
1387
Do a 'normal' merge of the old branch basis if it is relevant.
1389
old_tip = self.branch.update()
1390
if old_tip is not None:
1391
self.add_pending_merge(old_tip)
1392
self.branch.lock_read()
1395
if self.last_revision() != self.branch.last_revision():
1396
# merge tree state up to new branch tip.
1397
basis = self.basis_tree()
1398
to_tree = self.branch.basis_tree()
1399
result += merge_inner(self.branch,
1403
self.set_last_revision(self.branch.last_revision())
1404
if old_tip and old_tip != self.last_revision():
1405
# our last revision was not the prior branch last revision
1406
# and we have converted that last revision to a pending merge.
1407
# base is somewhere between the branch tip now
1408
# and the now pending merge
1409
from bzrlib.revision import common_ancestor
1411
base_rev_id = common_ancestor(self.branch.last_revision(),
1413
self.branch.repository)
1414
except errors.NoCommonAncestor:
1416
base_tree = self.branch.repository.revision_tree(base_rev_id)
1417
other_tree = self.branch.repository.revision_tree(old_tip)
1418
result += merge_inner(self.branch,
1424
self.branch.unlock()
1004
return self.branch.unlock()
1426
1006
@needs_write_lock
1427
1007
def _write_inventory(self, inv):
1432
1012
self._control_files.put('inventory', sio)
1433
1013
self._set_inventory(inv)
1434
1014
mutter('wrote working inventory')
1436
def set_conflicts(self, arg):
1437
raise UnsupportedOperation(self.set_conflicts, self)
1440
def conflicts(self):
1441
conflicts = ConflictList()
1442
for conflicted in self._iter_conflicts():
1445
if file_kind(self.abspath(conflicted)) != "file":
1447
except errors.NoSuchFile:
1450
for suffix in ('.THIS', '.OTHER'):
1452
kind = file_kind(self.abspath(conflicted+suffix))
1455
except errors.NoSuchFile:
1459
ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1460
conflicts.append(Conflict.factory(ctype, path=conflicted,
1461
file_id=self.path2id(conflicted)))
1465
class WorkingTree3(WorkingTree):
1466
"""This is the Format 3 working tree.
1468
This differs from the base WorkingTree by:
1469
- having its own file lock
1470
- having its own last-revision property.
1472
This is new in bzr 0.8
1476
def last_revision(self):
1477
"""See WorkingTree.last_revision."""
1479
return self._control_files.get_utf8('last-revision').read()
1483
def _change_last_revision(self, revision_id):
1484
"""See WorkingTree._change_last_revision."""
1485
if revision_id is None or revision_id == NULL_REVISION:
1487
self._control_files._transport.delete('last-revision')
1488
except errors.NoSuchFile:
1493
self.branch.revision_history().index(revision_id)
1495
raise errors.NoSuchRevision(self.branch, revision_id)
1496
self._control_files.put_utf8('last-revision', revision_id)
1500
def set_conflicts(self, conflicts):
1501
self._put_rio('conflicts', conflicts.to_stanzas(),
1505
def conflicts(self):
1507
confile = self._control_files.get('conflicts')
1509
return ConflictList()
1511
if confile.next() != CONFLICT_HEADER_1 + '\n':
1512
raise ConflictFormatError()
1513
except StopIteration:
1514
raise ConflictFormatError()
1515
return ConflictList.from_stanzas(RioReader(confile))
1017
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1518
1018
def get_conflicted_stem(path):
1519
1019
for suffix in CONFLICT_SUFFIXES:
1520
1020
if path.endswith(suffix):
1521
1021
return path[:-len(suffix)]
1523
@deprecated_function(zero_eight)
1524
def is_control_file(filename):
1525
"""See WorkingTree.is_control_filename(filename)."""
1526
## FIXME: better check
1527
filename = normpath(filename)
1528
while filename != '':
1529
head, tail = os.path.split(filename)
1530
## mutter('check %r for control file' % ((head, tail),))
1533
if filename == head:
1539
class WorkingTreeFormat(object):
1540
"""An encapsulation of the initialization and open routines for a format.
1542
Formats provide three things:
1543
* An initialization routine,
1547
Formats are placed in an dict by their format string for reference
1548
during workingtree opening. Its not required that these be instances, they
1549
can be classes themselves with class methods - it simply depends on
1550
whether state is needed for a given format or not.
1552
Once a format is deprecated, just deprecate the initialize and open
1553
methods on the format class. Do not deprecate the object, as the
1554
object will be created every time regardless.
1557
_default_format = None
1558
"""The default format used for new trees."""
1561
"""The known formats."""
1564
def find_format(klass, a_bzrdir):
1565
"""Return the format for the working tree object in a_bzrdir."""
1567
transport = a_bzrdir.get_workingtree_transport(None)
1568
format_string = transport.get("format").read()
1569
return klass._formats[format_string]
1571
raise errors.NoWorkingTree(base=transport.base)
1573
raise errors.UnknownFormatError(format_string)
1576
def get_default_format(klass):
1577
"""Return the current default format."""
1578
return klass._default_format
1580
def get_format_string(self):
1581
"""Return the ASCII format string that identifies this format."""
1582
raise NotImplementedError(self.get_format_string)
1584
def get_format_description(self):
1585
"""Return the short description for this format."""
1586
raise NotImplementedError(self.get_format_description)
1588
def is_supported(self):
1589
"""Is this format supported?
1591
Supported formats can be initialized and opened.
1592
Unsupported formats may not support initialization or committing or
1593
some other features depending on the reason for not being supported.
1598
def register_format(klass, format):
1599
klass._formats[format.get_format_string()] = format
1602
def set_default_format(klass, format):
1603
klass._default_format = format
1606
def unregister_format(klass, format):
1607
assert klass._formats[format.get_format_string()] is format
1608
del klass._formats[format.get_format_string()]
1612
class WorkingTreeFormat2(WorkingTreeFormat):
1613
"""The second working tree format.
1615
This format modified the hash cache from the format 1 hash cache.
1618
def get_format_description(self):
1619
"""See WorkingTreeFormat.get_format_description()."""
1620
return "Working tree format 2"
1622
def stub_initialize_remote(self, control_files):
1623
"""As a special workaround create critical control files for a remote working tree
1625
This ensures that it can later be updated and dealt with locally,
1626
since BzrDirFormat6 and BzrDirFormat5 cannot represent dirs with
1627
no working tree. (See bug #43064).
1631
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1633
control_files.put('inventory', sio)
1635
control_files.put_utf8('pending-merges', '')
1638
def initialize(self, a_bzrdir, revision_id=None):
1639
"""See WorkingTreeFormat.initialize()."""
1640
if not isinstance(a_bzrdir.transport, LocalTransport):
1641
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1642
branch = a_bzrdir.open_branch()
1643
if revision_id is not None:
1646
revision_history = branch.revision_history()
1648
position = revision_history.index(revision_id)
1650
raise errors.NoSuchRevision(branch, revision_id)
1651
branch.set_revision_history(revision_history[:position + 1])
1654
revision = branch.last_revision()
1656
wt = WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
1662
wt._write_inventory(inv)
1663
wt.set_root_id(inv.root.file_id)
1664
wt.set_last_revision(revision)
1665
wt.set_pending_merges([])
1666
build_tree(wt.basis_tree(), wt)
1670
super(WorkingTreeFormat2, self).__init__()
1671
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1673
def open(self, a_bzrdir, _found=False):
1674
"""Return the WorkingTree object for a_bzrdir
1676
_found is a private parameter, do not use it. It is used to indicate
1677
if format probing has already been done.
1680
# we are being called directly and must probe.
1681
raise NotImplementedError
1682
if not isinstance(a_bzrdir.transport, LocalTransport):
1683
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1684
return WorkingTree(a_bzrdir.root_transport.local_abspath('.'),
1690
class WorkingTreeFormat3(WorkingTreeFormat):
1691
"""The second working tree format updated to record a format marker.
1694
- exists within a metadir controlling .bzr
1695
- includes an explicit version marker for the workingtree control
1696
files, separate from the BzrDir format
1697
- modifies the hash cache format
1699
- uses a LockDir to guard access to the repository
1702
def get_format_string(self):
1703
"""See WorkingTreeFormat.get_format_string()."""
1704
return "Bazaar-NG Working Tree format 3"
1706
def get_format_description(self):
1707
"""See WorkingTreeFormat.get_format_description()."""
1708
return "Working tree format 3"
1710
_lock_file_name = 'lock'
1711
_lock_class = LockDir
1713
def _open_control_files(self, a_bzrdir):
1714
transport = a_bzrdir.get_workingtree_transport(None)
1715
return LockableFiles(transport, self._lock_file_name,
1718
def initialize(self, a_bzrdir, revision_id=None):
1719
"""See WorkingTreeFormat.initialize().
1721
revision_id allows creating a working tree at a different
1722
revision than the branch is at.
1724
if not isinstance(a_bzrdir.transport, LocalTransport):
1725
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1726
transport = a_bzrdir.get_workingtree_transport(self)
1727
control_files = self._open_control_files(a_bzrdir)
1728
control_files.create_lock()
1729
control_files.lock_write()
1730
control_files.put_utf8('format', self.get_format_string())
1731
branch = a_bzrdir.open_branch()
1732
if revision_id is None:
1733
revision_id = branch.last_revision()
1735
wt = WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1741
_control_files=control_files)
1744
wt._write_inventory(inv)
1745
wt.set_root_id(inv.root.file_id)
1746
wt.set_last_revision(revision_id)
1747
wt.set_pending_merges([])
1748
build_tree(wt.basis_tree(), wt)
1751
control_files.unlock()
1755
super(WorkingTreeFormat3, self).__init__()
1756
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1758
def open(self, a_bzrdir, _found=False):
1759
"""Return the WorkingTree object for a_bzrdir
1761
_found is a private parameter, do not use it. It is used to indicate
1762
if format probing has already been done.
1765
# we are being called directly and must probe.
1766
raise NotImplementedError
1767
if not isinstance(a_bzrdir.transport, LocalTransport):
1768
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1769
control_files = self._open_control_files(a_bzrdir)
1770
return WorkingTree3(a_bzrdir.root_transport.local_abspath('.'),
1774
_control_files=control_files)
1777
return self.get_format_string()
1780
# formats which have no format string are not discoverable
1781
# and not independently creatable, so are not registered.
1782
__default_format = WorkingTreeFormat3()
1783
WorkingTreeFormat.register_format(__default_format)
1784
WorkingTreeFormat.set_default_format(__default_format)
1785
_legacy_formats = [WorkingTreeFormat2(),
1789
class WorkingTreeTestProviderAdapter(object):
1790
"""A tool to generate a suite testing multiple workingtree formats at once.
1792
This is done by copying the test once for each transport and injecting
1793
the transport_server, transport_readonly_server, and workingtree_format
1794
classes into each copy. Each copy is also given a new id() to make it
1798
def __init__(self, transport_server, transport_readonly_server, formats):
1799
self._transport_server = transport_server
1800
self._transport_readonly_server = transport_readonly_server
1801
self._formats = formats
1803
def adapt(self, test):
1804
from bzrlib.tests import TestSuite
1805
result = TestSuite()
1806
for workingtree_format, bzrdir_format in self._formats:
1807
new_test = deepcopy(test)
1808
new_test.transport_server = self._transport_server
1809
new_test.transport_readonly_server = self._transport_readonly_server
1810
new_test.bzrdir_format = bzrdir_format
1811
new_test.workingtree_format = workingtree_format
1812
def make_new_test_id():
1813
new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1814
return lambda: new_id
1815
new_test.id = make_new_test_id()
1816
result.addTest(new_test)