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 Branch.working_tree():
32
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
33
CONFLICT_HEADER_1 = "BZR conflict list format 1"
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.
35
39
# TODO: Give the workingtree sole responsibility for the working inventory;
36
40
# remove the variable and references to it from the branch. This may require
37
41
# updating the commit code so as to update the inventory within the working
38
42
# 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)
43
# At the momenthey may alias the inventory and have old copies of it in memory.
42
from binascii import hexlify
43
from copy import deepcopy
44
from cStringIO import StringIO
52
from bzrlib.atomicfile import AtomicFile
53
from bzrlib.branch import (Branch,
55
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
56
import bzrlib.bzrdir as bzrdir
57
from bzrlib.decorators import needs_read_lock, needs_write_lock
58
import bzrlib.errors as errors
59
from bzrlib.errors import (BzrCheckError,
63
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 (
92
from bzrlib.progress import DummyProgress, ProgressPhase
93
from bzrlib.revision import NULL_REVISION
94
from bzrlib.rio import RioReader, rio_file, Stanza
95
from bzrlib.symbol_versioning import *
96
from bzrlib.textui import show_status
49
from bzrlib.branch import Branch, needs_read_lock, needs_write_lock, quotefn
98
from bzrlib.transform import build_tree
99
from bzrlib.trace import mutter, note
100
from bzrlib.transport import get_transport
101
from bzrlib.transport.local import LocalTransport
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
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()
146
"""Return a new tree-root file id."""
147
return gen_file_id('TREE_ROOT')
51
from bzrlib.osutils import appendpath, file_kind, isdir, splitpath, relpath
52
from bzrlib.errors import BzrCheckError, NotVersionedError
53
from bzrlib.trace import mutter
150
55
class TreeEntry(object):
151
56
"""An entry that implements the minium interface used by commands.
226
125
(branch.base is not cross checked, because for remote branches that
227
126
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
128
from bzrlib.hashcache import HashCache
246
129
from bzrlib.trace import note, mutter
247
130
assert isinstance(basedir, basestring), \
248
131
"base directory %r is not a string" % basedir
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
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
133
branch = Branch.open(basedir)
134
assert isinstance(branch, Branch), \
135
"branch %r is not a Branch" % branch
136
self._inventory = branch.inventory
137
self.path2id = self._inventory.path2id
139
self.basedir = basedir
276
141
# update the whole cache up front and write to disk if anything changed;
277
142
# in the future we might want to do this more selectively
278
# two possible ways offer themselves : in self._unlock, write the cache
279
# if needed, or, when the cache sees a change, append it to the hash
280
# cache file, and have the parser take the most recent entry for a
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)
143
hc = self._hashcache = HashCache(basedir)
285
# is this scan needed ? it makes things kinda slow.
288
147
if hc.needs_write:
289
148
mutter("write hc")
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()
316
def _set_inventory(self, inv):
317
self._inventory = inv
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)
343
def open_containing(path=None):
344
"""Open an existing working tree which has its root about path.
346
This probes for a working tree at path and searches upwards from there.
348
Basically we keep looking up until we find the control directory or
349
run into /. If there isn't one, raises NotBranchError.
350
TODO: give this a new exception.
351
If there is one, it is returned, along with the unused portion of path.
355
control, relpath = bzrdir.BzrDir.open_containing(path)
356
return control.open_workingtree(), relpath
359
def open_downlevel(path=None):
360
"""Open an unsupported working tree.
362
Only intended for advanced situations like upgrading part of a bzrdir.
364
return WorkingTree.open(path, _unsupported=True)
153
if self._hashcache.needs_write:
154
self._hashcache.write()
366
157
def __iter__(self):
367
158
"""Iterate through file_ids for this tree.
525
211
return self.inventory.has_id(file_id)
527
213
__contains__ = has_id
529
216
def get_file_size(self, file_id):
530
217
return os.path.getsize(self.id2abspath(file_id))
533
219
def get_file_sha1(self, file_id):
534
220
path = self._inventory.id2path(file_id)
535
221
return self._hashcache.get_sha1(path)
537
224
def is_executable(self, file_id):
538
if not supports_executable():
539
226
return self._inventory[file_id].executable
541
228
path = self._inventory.id2path(file_id)
542
229
mode = os.lstat(self.abspath(path)).st_mode
543
230
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
546
def add(self, files, ids=None):
547
"""Make files versioned.
549
Note that the command line normally calls smart_add instead,
550
which can automatically recurse.
552
This adds the files to the inventory, so that they will be
553
recorded by the next commit.
556
List of paths to add, relative to the base of the tree.
559
If set, use these instead of automatically generated ids.
560
Must be the same length as the list of files, but may
561
contain None for ids that are to be autogenerated.
563
TODO: Perhaps have an option to add the ids even if the files do
566
TODO: Perhaps callback with the ids and paths as they're added.
568
# TODO: Re-adding a file that is removed in the working copy
569
# should probably put it back with the previous ID.
570
if isinstance(files, basestring):
571
assert(ids is None or isinstance(ids, basestring))
577
ids = [None] * len(files)
579
assert(len(ids) == len(files))
581
inv = self.read_working_inventory()
582
for f,file_id in zip(files, ids):
583
if self.is_control_filename(f):
584
raise BzrError("cannot add control file %s" % quotefn(f))
589
raise BzrError("cannot add top-level %r" % f)
591
fullpath = normpath(self.abspath(f))
594
kind = file_kind(fullpath)
596
if e.errno == errno.ENOENT:
597
raise NoSuchFile(fullpath)
598
# maybe something better?
599
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
601
if not InventoryEntry.versionable_kind(kind):
602
raise BzrError('cannot add: not a versionable file ('
603
'i.e. regular file, symlink or directory): %s' % quotefn(f))
606
inv.add_path(f, kind=kind)
608
inv.add_path(f, kind=kind, file_id=file_id)
610
self._write_inventory(inv)
613
def add_pending_merge(self, *revision_ids):
614
# TODO: Perhaps should check at this point that the
615
# history of the revision is actually present?
616
p = self.pending_merges()
618
for rev_id in revision_ids:
624
self.set_pending_merges(p)
627
def pending_merges(self):
628
"""Return a list of pending merges.
630
These are revisions that have been merged into the working
631
directory but not yet committed.
634
merges_file = self._control_files.get_utf8('pending-merges')
636
if e.errno != errno.ENOENT:
640
for l in merges_file.readlines():
641
p.append(l.rstrip('\n'))
645
def set_pending_merges(self, rev_list):
646
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
649
def set_merge_modified(self, modified_hashes):
651
for file_id, hash in modified_hashes.iteritems():
652
yield Stanza(file_id=file_id, hash=hash)
653
self._put_rio('merge-hashes', iter_stanzas(), MERGE_MODIFIED_HEADER_1)
656
def _put_rio(self, filename, stanzas, header):
657
my_file = rio_file(stanzas, header)
658
self._control_files.put(filename, my_file)
661
def merge_modified(self):
663
hashfile = self._control_files.get('merge-hashes')
668
if hashfile.next() != MERGE_MODIFIED_HEADER_1 + '\n':
669
raise MergeModifiedFormatError()
670
except StopIteration:
671
raise MergeModifiedFormatError()
672
for s in RioReader(hashfile):
673
file_id = s.get("file_id")
674
if file_id not in self.inventory:
677
if hash == self.get_file_sha1(file_id):
678
merge_hashes[file_id] = hash
681
232
def get_symlink_target(self, file_id):
682
233
return os.readlink(self.id2abspath(file_id))
760
310
for ff in descend(fp, f_ie.file_id, fap):
763
for f in descend(u'', inv.root.file_id, self.basedir):
313
for f in descend('', inv.root.file_id, self.basedir):
767
def move(self, from_paths, to_name):
770
to_name must exist in the inventory.
772
If to_name exists and is a directory, the files are moved into
773
it, keeping their old names.
775
Note that to_name is only the last component of the new name;
776
this doesn't change the directory.
778
This returns a list of (from_path, to_path) pairs for each
782
## TODO: Option to move IDs only
783
assert not isinstance(from_paths, basestring)
785
to_abs = self.abspath(to_name)
786
if not isdir(to_abs):
787
raise BzrError("destination %r is not a directory" % to_abs)
788
if not self.has_filename(to_name):
789
raise BzrError("destination %r not in working directory" % to_abs)
790
to_dir_id = inv.path2id(to_name)
791
if to_dir_id == None and to_name != '':
792
raise BzrError("destination %r is not a versioned directory" % to_name)
793
to_dir_ie = inv[to_dir_id]
794
if to_dir_ie.kind not in ('directory', 'root_directory'):
795
raise BzrError("destination %r is not a directory" % to_abs)
797
to_idpath = inv.get_idpath(to_dir_id)
800
if not self.has_filename(f):
801
raise BzrError("%r does not exist in working tree" % f)
802
f_id = inv.path2id(f)
804
raise BzrError("%r is not versioned" % f)
805
name_tail = splitpath(f)[-1]
806
dest_path = appendpath(to_name, name_tail)
807
if self.has_filename(dest_path):
808
raise BzrError("destination %r already exists" % dest_path)
809
if f_id in to_idpath:
810
raise BzrError("can't move %r to a subdirectory of itself" % f)
812
# OK, so there's a race here, it's possible that someone will
813
# create a file in this interval and then the rename might be
814
# left half-done. But we should have caught most problems.
815
orig_inv = deepcopy(self.inventory)
818
name_tail = splitpath(f)[-1]
819
dest_path = appendpath(to_name, name_tail)
820
result.append((f, dest_path))
821
inv.rename(inv.path2id(f), to_dir_id, name_tail)
823
rename(self.abspath(f), self.abspath(dest_path))
825
raise BzrError("failed to rename %r to %r: %s" %
826
(f, dest_path, e[1]),
827
["rename rolled back"])
829
# restore the inventory on error
830
self._set_inventory(orig_inv)
832
self._write_inventory(inv)
836
def rename_one(self, from_rel, to_rel):
839
This can change the directory or the filename or both.
842
if not self.has_filename(from_rel):
843
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
844
if self.has_filename(to_rel):
845
raise BzrError("can't rename: new working file %r already exists" % to_rel)
847
file_id = inv.path2id(from_rel)
849
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
852
from_parent = entry.parent_id
853
from_name = entry.name
855
if inv.path2id(to_rel):
856
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
858
to_dir, to_tail = os.path.split(to_rel)
859
to_dir_id = inv.path2id(to_dir)
860
if to_dir_id == None and to_dir != '':
861
raise BzrError("can't determine destination directory id for %r" % to_dir)
863
mutter("rename_one:")
864
mutter(" file_id {%s}" % file_id)
865
mutter(" from_rel %r" % from_rel)
866
mutter(" to_rel %r" % to_rel)
867
mutter(" to_dir %r" % to_dir)
868
mutter(" to_dir_id {%s}" % to_dir_id)
870
inv.rename(file_id, to_dir_id, to_tail)
872
from_abs = self.abspath(from_rel)
873
to_abs = self.abspath(to_rel)
875
rename(from_abs, to_abs)
877
inv.rename(file_id, from_parent, from_name)
878
raise BzrError("failed to rename %r to %r: %s"
879
% (from_abs, to_abs, e[1]),
880
["rename rolled back"])
881
self._write_inventory(inv)
884
318
def unknowns(self):
885
"""Return all unknown files.
887
These are files in the working directory that are not versioned or
888
control files or ignored.
890
>>> from bzrlib.bzrdir import ScratchDir
891
>>> d = ScratchDir(files=['foo', 'foo~'])
892
>>> b = d.open_branch()
893
>>> tree = d.open_workingtree()
894
>>> map(str, tree.unknowns())
897
>>> list(b.unknowns())
899
>>> tree.remove('foo')
900
>>> list(b.unknowns())
903
319
for subp in self.extras():
904
320
if not self.is_ignored(subp):
907
@deprecated_method(zero_eight)
908
323
def iter_conflicts(self):
909
"""List all files in the tree that have text or content conflicts.
910
DEPRECATED. Use conflicts instead."""
911
return self._iter_conflicts()
913
def _iter_conflicts(self):
914
324
conflicted = set()
915
325
for path in (s[0] for s in self.list_files()):
916
326
stem = get_conflicted_stem(path)
1087
401
# treat dotfiles correctly and allows * to match /.
1088
402
# 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]
405
for pat in self.get_ignore_list():
406
if '/' in pat or '\\' in pat:
408
# as a special case, you can put ./ at the start of a
409
# pattern; this is good to match in the top-level
412
if (pat[:2] == './') or (pat[:2] == '.\\'):
416
if fnmatch.fnmatchcase(filename, newpat):
419
if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
1103
424
def kind(self, file_id):
1104
425
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
427
def lock_read(self):
1120
428
"""See Branch.lock_read, and WorkingTree.unlock."""
1121
self.branch.lock_read()
1123
return self._control_files.lock_read()
1125
self.branch.unlock()
429
return self.branch.lock_read()
1128
431
def lock_write(self):
1129
432
"""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)
1181
except WeaveRevisionNotPresent:
1184
def read_basis_inventory(self):
1185
"""Read the cached basis inventory."""
1186
path = self._basis_inventory_name()
1187
return self._control_files.get_utf8(path).read()
1190
def read_working_inventory(self):
1191
"""Read the working inventory."""
1192
# ElementTree does its own conversion from UTF-8, so open in
1194
result = bzrlib.xml5.serializer_v5.read_inventory(
1195
self._control_files.get('inventory'))
1196
self._set_inventory(result)
433
return self.branch.lock_write()
1199
435
@needs_write_lock
1200
436
def remove(self, files, verbose=False):
1300
482
between multiple working trees, i.e. via shared storage, then we
1301
483
would probably want to lock both the local tree, and the branch.
1303
# FIXME: We want to write out the hashcache only when the last lock on
1304
# this working copy is released. Peeking at the lock count is a bit
1305
# of a nasty hack; probably it's better to have a transaction object,
1306
# which can do some finalization when it's either successfully or
1307
# 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)):
1318
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()
1383
def _write_inventory(self, inv):
1384
"""Write inventory as the current inventory."""
1386
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1388
self._control_files.put('inventory', sio)
1389
self._set_inventory(inv)
1390
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))
485
return self.branch.unlock()
488
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1480
489
def get_conflicted_stem(path):
1481
490
for suffix in CONFLICT_SUFFIXES:
1482
491
if path.endswith(suffix):
1483
492
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)