199
93
It is possible for a `WorkingTree` to have a filename which is
200
94
not listed in the Inventory and vice versa.
203
def __init__(self, basedir='.',
204
branch=DEPRECATED_PARAMETER,
210
"""Construct a WorkingTree for basedir.
212
If the branch is not supplied, it is opened automatically.
213
If the branch is supplied, it must be the branch for this basedir.
214
(branch.base is not cross checked, because for remote branches that
215
would be meaningless).
217
self._format = _format
218
self.bzrdir = _bzrdir
220
# not created via open etc.
221
warn("WorkingTree() is deprecated as of bzr version 0.8. "
222
"Please use bzrdir.open_workingtree or WorkingTree.open().",
225
wt = WorkingTree.open(basedir)
226
self._branch = wt.branch
227
self.basedir = wt.basedir
228
self._control_files = wt._control_files
229
self._hashcache = wt._hashcache
230
self._set_inventory(wt._inventory)
231
self._format = wt._format
232
self.bzrdir = wt.bzrdir
96
def __init__(self, basedir, inv):
233
97
from bzrlib.hashcache import HashCache
234
98
from bzrlib.trace import note, mutter
235
assert isinstance(basedir, basestring), \
236
"base directory %r is not a string" % basedir
237
basedir = safe_unicode(basedir)
238
mutter("opening working tree %r", basedir)
239
if deprecated_passed(branch):
241
warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
242
" Please use bzrdir.open_workingtree() or"
243
" WorkingTree.open().",
247
self._branch = branch
249
self._branch = self.bzrdir.open_branch()
250
assert isinstance(self.branch, Branch), \
251
"branch %r is not a Branch" % self.branch
252
self.basedir = realpath(basedir)
253
# if branch is at our basedir and is a format 6 or less
254
if isinstance(self._format, WorkingTreeFormat2):
255
# share control object
256
self._control_files = self.branch.control_files
258
# only ready for format 3
259
assert isinstance(self._format, WorkingTreeFormat3)
260
assert isinstance(_control_files, LockableFiles), \
261
"_control_files must be a LockableFiles, not %r" \
263
self._control_files = _control_files
100
self._inventory = inv
101
self.basedir = basedir
102
self.path2id = inv.path2id
264
104
# update the whole cache up front and write to disk if anything changed;
265
105
# in the future we might want to do this more selectively
266
# two possible ways offer themselves : in self._unlock, write the cache
267
# if needed, or, when the cache sees a change, append it to the hash
268
# cache file, and have the parser take the most recent entry for a
270
cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
271
hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
106
hc = self._hashcache = HashCache(basedir)
273
# is this scan needed ? it makes things kinda slow.
276
110
if hc.needs_write:
277
111
mutter("write hc")
280
if _inventory is None:
281
self._set_inventory(self.read_working_inventory())
283
self._set_inventory(_inventory)
286
fget=lambda self: self._branch,
287
doc="""The branch this WorkingTree is connected to.
289
This cannot be set - it is reflective of the actual disk structure
290
the working tree has been constructed from.
293
def _set_inventory(self, inv):
294
self._inventory = inv
295
self.path2id = self._inventory.path2id
297
def is_control_filename(self, filename):
298
"""True if filename is the name of a control file in this tree.
300
This is true IF and ONLY IF the filename is part of the meta data
301
that bzr controls in this tree. I.E. a random .bzr directory placed
302
on disk will not be a control file for this tree.
305
self.bzrdir.transport.relpath(self.abspath(filename))
307
except errors.PathNotChild:
311
def open(path=None, _unsupported=False):
312
"""Open an existing working tree at path.
316
path = os.path.getcwdu()
317
control = bzrdir.BzrDir.open(path, _unsupported)
318
return control.open_workingtree(_unsupported)
321
def open_containing(path=None):
322
"""Open an existing working tree which has its root about path.
324
This probes for a working tree at path and searches upwards from there.
326
Basically we keep looking up until we find the control directory or
327
run into /. If there isn't one, raises NotBranchError.
328
TODO: give this a new exception.
329
If there is one, it is returned, along with the unused portion of path.
333
control, relpath = bzrdir.BzrDir.open_containing(path)
334
return control.open_workingtree(), relpath
337
def open_downlevel(path=None):
338
"""Open an unsupported working tree.
340
Only intended for advanced situations like upgrading part of a bzrdir.
342
return WorkingTree.open(path, _unsupported=True)
116
if self._hashcache.needs_write:
117
self._hashcache.write()
344
120
def __iter__(self):
345
121
"""Iterate through file_ids for this tree.
493
164
path = inv.id2path(file_id)
494
165
return bzrlib.osutils.lexists(self.abspath(path))
496
def has_or_had_id(self, file_id):
497
if file_id == self.inventory.root.file_id:
499
return self.inventory.has_id(file_id)
501
168
__contains__ = has_id
503
171
def get_file_size(self, file_id):
504
172
return os.path.getsize(self.id2abspath(file_id))
507
174
def get_file_sha1(self, file_id):
508
175
path = self._inventory.id2path(file_id)
509
176
return self._hashcache.get_sha1(path)
511
179
def is_executable(self, file_id):
512
if not supports_executable():
513
181
return self._inventory[file_id].executable
515
183
path = self._inventory.id2path(file_id)
516
184
mode = os.lstat(self.abspath(path)).st_mode
517
185
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
520
def add(self, files, ids=None):
521
"""Make files versioned.
523
Note that the command line normally calls smart_add instead,
524
which can automatically recurse.
526
This adds the files to the inventory, so that they will be
527
recorded by the next commit.
530
List of paths to add, relative to the base of the tree.
533
If set, use these instead of automatically generated ids.
534
Must be the same length as the list of files, but may
535
contain None for ids that are to be autogenerated.
537
TODO: Perhaps have an option to add the ids even if the files do
540
TODO: Perhaps callback with the ids and paths as they're added.
542
# TODO: Re-adding a file that is removed in the working copy
543
# should probably put it back with the previous ID.
544
if isinstance(files, basestring):
545
assert(ids is None or isinstance(ids, basestring))
551
ids = [None] * len(files)
553
assert(len(ids) == len(files))
555
inv = self.read_working_inventory()
556
for f,file_id in zip(files, ids):
557
if self.is_control_filename(f):
558
raise BzrError("cannot add control file %s" % quotefn(f))
563
raise BzrError("cannot add top-level %r" % f)
565
fullpath = normpath(self.abspath(f))
568
kind = file_kind(fullpath)
570
if e.errno == errno.ENOENT:
571
raise NoSuchFile(fullpath)
572
# maybe something better?
573
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
575
if not InventoryEntry.versionable_kind(kind):
576
raise BzrError('cannot add: not a versionable file ('
577
'i.e. regular file, symlink or directory): %s' % quotefn(f))
580
file_id = gen_file_id(f)
581
inv.add_path(f, kind=kind, file_id=file_id)
583
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
584
self._write_inventory(inv)
587
def add_pending_merge(self, *revision_ids):
588
# TODO: Perhaps should check at this point that the
589
# history of the revision is actually present?
590
p = self.pending_merges()
592
for rev_id in revision_ids:
598
self.set_pending_merges(p)
601
def pending_merges(self):
602
"""Return a list of pending merges.
604
These are revisions that have been merged into the working
605
directory but not yet committed.
608
merges_file = self._control_files.get_utf8('pending-merges')
610
if e.errno != errno.ENOENT:
614
for l in merges_file.readlines():
615
p.append(l.rstrip('\n'))
619
def set_pending_merges(self, rev_list):
620
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
623
def set_merge_modified(self, modified_hashes):
625
for file_id, hash in modified_hashes.iteritems():
626
yield Stanza(file_id=file_id, hash=hash)
627
self._put_rio('merge-hashes', iter_stanzas(), MERGE_MODIFIED_HEADER_1)
630
def _put_rio(self, filename, stanzas, header):
631
my_file = rio_file(stanzas, header)
632
self._control_files.put(filename, my_file)
635
def merge_modified(self):
637
hashfile = self._control_files.get('merge-hashes')
642
if hashfile.next() != MERGE_MODIFIED_HEADER_1 + '\n':
643
raise MergeModifiedFormatError()
644
except StopIteration:
645
raise MergeModifiedFormatError()
646
for s in RioReader(hashfile):
647
file_id = s.get("file_id")
648
if file_id not in self.inventory:
651
if hash == self.get_file_sha1(file_id):
652
merge_hashes[file_id] = hash
655
187
def get_symlink_target(self, file_id):
656
188
return os.readlink(self.id2abspath(file_id))
734
265
for ff in descend(fp, f_ie.file_id, fap):
737
for f in descend(u'', inv.root.file_id, self.basedir):
268
for f in descend('', inv.root.file_id, self.basedir):
741
def move(self, from_paths, to_name):
744
to_name must exist in the inventory.
746
If to_name exists and is a directory, the files are moved into
747
it, keeping their old names.
749
Note that to_name is only the last component of the new name;
750
this doesn't change the directory.
752
This returns a list of (from_path, to_path) pairs for each
756
## TODO: Option to move IDs only
757
assert not isinstance(from_paths, basestring)
759
to_abs = self.abspath(to_name)
760
if not isdir(to_abs):
761
raise BzrError("destination %r is not a directory" % to_abs)
762
if not self.has_filename(to_name):
763
raise BzrError("destination %r not in working directory" % to_abs)
764
to_dir_id = inv.path2id(to_name)
765
if to_dir_id == None and to_name != '':
766
raise BzrError("destination %r is not a versioned directory" % to_name)
767
to_dir_ie = inv[to_dir_id]
768
if to_dir_ie.kind not in ('directory', 'root_directory'):
769
raise BzrError("destination %r is not a directory" % to_abs)
771
to_idpath = inv.get_idpath(to_dir_id)
774
if not self.has_filename(f):
775
raise BzrError("%r does not exist in working tree" % f)
776
f_id = inv.path2id(f)
778
raise BzrError("%r is not versioned" % f)
779
name_tail = splitpath(f)[-1]
780
dest_path = appendpath(to_name, name_tail)
781
if self.has_filename(dest_path):
782
raise BzrError("destination %r already exists" % dest_path)
783
if f_id in to_idpath:
784
raise BzrError("can't move %r to a subdirectory of itself" % f)
786
# OK, so there's a race here, it's possible that someone will
787
# create a file in this interval and then the rename might be
788
# left half-done. But we should have caught most problems.
789
orig_inv = deepcopy(self.inventory)
792
name_tail = splitpath(f)[-1]
793
dest_path = appendpath(to_name, name_tail)
794
result.append((f, dest_path))
795
inv.rename(inv.path2id(f), to_dir_id, name_tail)
797
rename(self.abspath(f), self.abspath(dest_path))
799
raise BzrError("failed to rename %r to %r: %s" %
800
(f, dest_path, e[1]),
801
["rename rolled back"])
803
# restore the inventory on error
804
self._set_inventory(orig_inv)
806
self._write_inventory(inv)
810
def rename_one(self, from_rel, to_rel):
813
This can change the directory or the filename or both.
816
if not self.has_filename(from_rel):
817
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
818
if self.has_filename(to_rel):
819
raise BzrError("can't rename: new working file %r already exists" % to_rel)
821
file_id = inv.path2id(from_rel)
823
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
826
from_parent = entry.parent_id
827
from_name = entry.name
829
if inv.path2id(to_rel):
830
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
832
to_dir, to_tail = os.path.split(to_rel)
833
to_dir_id = inv.path2id(to_dir)
834
if to_dir_id == None and to_dir != '':
835
raise BzrError("can't determine destination directory id for %r" % to_dir)
837
mutter("rename_one:")
838
mutter(" file_id {%s}" % file_id)
839
mutter(" from_rel %r" % from_rel)
840
mutter(" to_rel %r" % to_rel)
841
mutter(" to_dir %r" % to_dir)
842
mutter(" to_dir_id {%s}" % to_dir_id)
844
inv.rename(file_id, to_dir_id, to_tail)
846
from_abs = self.abspath(from_rel)
847
to_abs = self.abspath(to_rel)
849
rename(from_abs, to_abs)
851
inv.rename(file_id, from_parent, from_name)
852
raise BzrError("failed to rename %r to %r: %s"
853
% (from_abs, to_abs, e[1]),
854
["rename rolled back"])
855
self._write_inventory(inv)
858
273
def unknowns(self):
859
"""Return all unknown files.
861
These are files in the working directory that are not versioned or
862
control files or ignored.
864
>>> from bzrlib.bzrdir import ScratchDir
865
>>> d = ScratchDir(files=['foo', 'foo~'])
866
>>> b = d.open_branch()
867
>>> tree = d.open_workingtree()
868
>>> map(str, tree.unknowns())
871
>>> list(b.unknowns())
873
>>> tree.remove('foo')
874
>>> list(b.unknowns())
877
274
for subp in self.extras():
878
275
if not self.is_ignored(subp):
881
@deprecated_method(zero_eight)
882
278
def iter_conflicts(self):
883
"""List all files in the tree that have text or content conflicts.
884
DEPRECATED. Use conflicts instead."""
885
return self._iter_conflicts()
887
def _iter_conflicts(self):
888
279
conflicted = set()
889
280
for path in (s[0] for s in self.list_files()):
890
281
stem = get_conflicted_stem(path)
1021
def kind(self, file_id):
1022
return file_kind(self.id2abspath(file_id))
1025
def last_revision(self):
1026
"""Return the last revision id of this working tree.
1028
In early branch formats this was == the branch last_revision,
1029
but that cannot be relied upon - for working tree operations,
1030
always use tree.last_revision().
1032
return self.branch.last_revision()
1034
def is_locked(self):
1035
return self._control_files.is_locked()
1037
def lock_read(self):
1038
"""See Branch.lock_read, and WorkingTree.unlock."""
1039
self.branch.lock_read()
1041
return self._control_files.lock_read()
1043
self.branch.unlock()
1046
def lock_write(self):
1047
"""See Branch.lock_write, and WorkingTree.unlock."""
1048
self.branch.lock_write()
1050
return self._control_files.lock_write()
1052
self.branch.unlock()
1055
def get_physical_lock_status(self):
1056
return self._control_files.get_physical_lock_status()
1058
def _basis_inventory_name(self):
1059
return 'basis-inventory'
1062
def set_last_revision(self, new_revision):
1063
"""Change the last revision in the working tree."""
1064
if self._change_last_revision(new_revision):
1065
self._cache_basis_inventory(new_revision)
1067
def _change_last_revision(self, new_revision):
1068
"""Template method part of set_last_revision to perform the change.
1070
This is used to allow WorkingTree3 instances to not affect branch
1071
when their last revision is set.
1073
if new_revision is None:
1074
self.branch.set_revision_history([])
1076
# current format is locked in with the branch
1077
revision_history = self.branch.revision_history()
1079
position = revision_history.index(new_revision)
1081
raise errors.NoSuchRevision(self.branch, new_revision)
1082
self.branch.set_revision_history(revision_history[:position + 1])
1085
def _cache_basis_inventory(self, new_revision):
1086
"""Cache new_revision as the basis inventory."""
1088
# this double handles the inventory - unpack and repack -
1089
# but is easier to understand. We can/should put a conditional
1090
# in here based on whether the inventory is in the latest format
1091
# - perhaps we should repack all inventories on a repository
1093
inv = self.branch.repository.get_inventory(new_revision)
1094
inv.revision_id = new_revision
1095
xml = bzrlib.xml5.serializer_v5.write_inventory_to_string(inv)
1097
path = self._basis_inventory_name()
1098
self._control_files.put_utf8(path, xml)
1099
except WeaveRevisionNotPresent:
1102
def read_basis_inventory(self):
1103
"""Read the cached basis inventory."""
1104
path = self._basis_inventory_name()
1105
return self._control_files.get_utf8(path).read()
1108
def read_working_inventory(self):
1109
"""Read the working inventory."""
1110
# ElementTree does its own conversion from UTF-8, so open in
1112
result = bzrlib.xml5.serializer_v5.read_inventory(
1113
self._control_files.get('inventory'))
1114
self._set_inventory(result)
1118
def remove(self, files, verbose=False):
1119
"""Remove nominated files from the working inventory..
1121
This does not remove their text. This does not run on XXX on what? RBC
1123
TODO: Refuse to remove modified files unless --force is given?
1125
TODO: Do something useful with directories.
1127
TODO: Should this remove the text or not? Tough call; not
1128
removing may be useful and the user can just use use rm, and
1129
is the opposite of add. Removing it is consistent with most
1130
other tools. Maybe an option.
1132
## TODO: Normalize names
1133
## TODO: Remove nested loops; better scalability
1134
if isinstance(files, basestring):
1137
inv = self.inventory
1139
# do this before any modifications
1141
fid = inv.path2id(f)
1143
# TODO: Perhaps make this just a warning, and continue?
1144
# This tends to happen when
1145
raise NotVersionedError(path=f)
1146
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1148
# having remove it, it must be either ignored or unknown
1149
if self.is_ignored(f):
1153
show_status(new_status, inv[fid].kind, quotefn(f))
1156
self._write_inventory(inv)
1159
def revert(self, filenames, old_tree=None, backups=True,
1160
pb=DummyProgress()):
1161
from transform import revert
1162
from conflicts import resolve
1163
if old_tree is None:
1164
old_tree = self.basis_tree()
1165
conflicts = revert(self, old_tree, filenames, backups, pb)
1166
if not len(filenames):
1167
self.set_pending_merges([])
1170
resolve(self, filenames, ignore_misses=True)
1173
# XXX: This method should be deprecated in favour of taking in a proper
1174
# new Inventory object.
1176
def set_inventory(self, new_inventory_list):
1177
from bzrlib.inventory import (Inventory,
1182
inv = Inventory(self.get_root_id())
1183
for path, file_id, parent, kind in new_inventory_list:
1184
name = os.path.basename(path)
1187
# fixme, there should be a factory function inv,add_??
1188
if kind == 'directory':
1189
inv.add(InventoryDirectory(file_id, name, parent))
1190
elif kind == 'file':
1191
inv.add(InventoryFile(file_id, name, parent))
1192
elif kind == 'symlink':
1193
inv.add(InventoryLink(file_id, name, parent))
1195
raise BzrError("unknown kind %r" % kind)
1196
self._write_inventory(inv)
1199
def set_root_id(self, file_id):
1200
"""Set the root id for this tree."""
1201
inv = self.read_working_inventory()
1202
orig_root_id = inv.root.file_id
1203
del inv._byid[inv.root.file_id]
1204
inv.root.file_id = file_id
1205
inv._byid[inv.root.file_id] = inv.root
1208
if entry.parent_id == orig_root_id:
1209
entry.parent_id = inv.root.file_id
1210
self._write_inventory(inv)
1213
"""See Branch.unlock.
1215
WorkingTree locking just uses the Branch locking facilities.
1216
This is current because all working trees have an embedded branch
1217
within them. IF in the future, we were to make branch data shareable
1218
between multiple working trees, i.e. via shared storage, then we
1219
would probably want to lock both the local tree, and the branch.
1221
# FIXME: We want to write out the hashcache only when the last lock on
1222
# this working copy is released. Peeking at the lock count is a bit
1223
# of a nasty hack; probably it's better to have a transaction object,
1224
# which can do some finalization when it's either successfully or
1225
# unsuccessfully completed. (Denys's original patch did that.)
1226
# RBC 20060206 hookinhg into transaction will couple lock and transaction
1227
# wrongly. Hookinh into unllock on the control files object is fine though.
1229
# TODO: split this per format so there is no ugly if block
1230
if self._hashcache.needs_write and (
1231
# dedicated lock files
1232
self._control_files._lock_count==1 or
1234
(self._control_files is self.branch.control_files and
1235
self._control_files._lock_count==3)):
1236
self._hashcache.write()
1237
# reverse order of locking.
1238
result = self._control_files.unlock()
1240
self.branch.unlock()
1246
"""Update a working tree along its branch.
1248
This will update the branch if its bound too, which means we have multiple trees involved:
1249
The new basis tree of the master.
1250
The old basis tree of the branch.
1251
The old basis tree of the working tree.
1252
The current working tree state.
1253
pathologically all three may be different, and non ancestors of each other.
1254
Conceptually we want to:
1255
Preserve the wt.basis->wt.state changes
1256
Transform the wt.basis to the new master basis.
1257
Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
1258
Restore the wt.basis->wt.state changes.
1260
There isn't a single operation at the moment to do that, so we:
1261
Merge current state -> basis tree of the master w.r.t. the old tree basis.
1262
Do a 'normal' merge of the old branch basis if it is relevant.
1264
old_tip = self.branch.update()
1265
if old_tip is not None:
1266
self.add_pending_merge(old_tip)
1267
self.branch.lock_read()
1270
if self.last_revision() != self.branch.last_revision():
1271
# merge tree state up to new branch tip.
1272
basis = self.basis_tree()
1273
to_tree = self.branch.basis_tree()
1274
result += merge_inner(self.branch,
1278
self.set_last_revision(self.branch.last_revision())
1279
if old_tip and old_tip != self.last_revision():
1280
# our last revision was not the prior branch last reivison
1281
# and we have converted that last revision to a pending merge.
1282
# base is somewhere between the branch tip now
1283
# and the now pending merge
1284
from bzrlib.revision import common_ancestor
1286
base_rev_id = common_ancestor(self.branch.last_revision(),
1288
self.branch.repository)
1289
except errors.NoCommonAncestor:
1291
base_tree = self.branch.repository.revision_tree(base_rev_id)
1292
other_tree = self.branch.repository.revision_tree(old_tip)
1293
result += merge_inner(self.branch,
1299
self.branch.unlock()
1302
def _write_inventory(self, inv):
1303
"""Write inventory as the current inventory."""
1305
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1307
self._control_files.put('inventory', sio)
1308
self._set_inventory(inv)
1309
mutter('wrote working inventory')
1311
def set_conflicts(self, arg):
1312
raise UnsupportedOperation(self.set_conflicts, self)
1315
def conflicts(self):
1316
conflicts = ConflictList()
1317
for conflicted in self._iter_conflicts():
1320
if file_kind(self.abspath(conflicted)) != "file":
1323
if e.errno == errno.ENOENT:
1328
for suffix in ('.THIS', '.OTHER'):
1330
kind = file_kind(self.abspath(conflicted+suffix))
1332
if e.errno == errno.ENOENT:
1340
ctype = {True: 'text conflict', False: 'contents conflict'}[text]
1341
conflicts.append(Conflict.factory(ctype, path=conflicted,
1342
file_id=self.path2id(conflicted)))
1346
class WorkingTree3(WorkingTree):
1347
"""This is the Format 3 working tree.
1349
This differs from the base WorkingTree by:
1350
- having its own file lock
1351
- having its own last-revision property.
1353
This is new in bzr 0.8
1357
def last_revision(self):
1358
"""See WorkingTree.last_revision."""
1360
return self._control_files.get_utf8('last-revision').read()
1364
def _change_last_revision(self, revision_id):
1365
"""See WorkingTree._change_last_revision."""
1366
if revision_id is None or revision_id == NULL_REVISION:
1368
self._control_files._transport.delete('last-revision')
1369
except errors.NoSuchFile:
1374
self.branch.revision_history().index(revision_id)
1376
raise errors.NoSuchRevision(self.branch, revision_id)
1377
self._control_files.put_utf8('last-revision', revision_id)
1381
def set_conflicts(self, conflicts):
1382
self._put_rio('conflicts', conflicts.to_stanzas(),
1386
def conflicts(self):
1388
confile = self._control_files.get('conflicts')
1390
return ConflictList()
1392
if confile.next() != CONFLICT_HEADER_1 + '\n':
1393
raise ConflictFormatError()
1394
except StopIteration:
1395
raise ConflictFormatError()
1396
return ConflictList.from_stanzas(RioReader(confile))
379
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1399
380
def get_conflicted_stem(path):
1400
381
for suffix in CONFLICT_SUFFIXES:
1401
382
if path.endswith(suffix):
1402
383
return path[:-len(suffix)]
1404
@deprecated_function(zero_eight)
1405
def is_control_file(filename):
1406
"""See WorkingTree.is_control_filename(filename)."""
1407
## FIXME: better check
1408
filename = normpath(filename)
1409
while filename != '':
1410
head, tail = os.path.split(filename)
1411
## mutter('check %r for control file' % ((head, tail),))
1414
if filename == head:
1420
class WorkingTreeFormat(object):
1421
"""An encapsulation of the initialization and open routines for a format.
1423
Formats provide three things:
1424
* An initialization routine,
1428
Formats are placed in an dict by their format string for reference
1429
during workingtree opening. Its not required that these be instances, they
1430
can be classes themselves with class methods - it simply depends on
1431
whether state is needed for a given format or not.
1433
Once a format is deprecated, just deprecate the initialize and open
1434
methods on the format class. Do not deprecate the object, as the
1435
object will be created every time regardless.
1438
_default_format = None
1439
"""The default format used for new trees."""
1442
"""The known formats."""
1445
def find_format(klass, a_bzrdir):
1446
"""Return the format for the working tree object in a_bzrdir."""
1448
transport = a_bzrdir.get_workingtree_transport(None)
1449
format_string = transport.get("format").read()
1450
return klass._formats[format_string]
1452
raise errors.NoWorkingTree(base=transport.base)
1454
raise errors.UnknownFormatError(format_string)
1457
def get_default_format(klass):
1458
"""Return the current default format."""
1459
return klass._default_format
1461
def get_format_string(self):
1462
"""Return the ASCII format string that identifies this format."""
1463
raise NotImplementedError(self.get_format_string)
1465
def get_format_description(self):
1466
"""Return the short description for this format."""
1467
raise NotImplementedError(self.get_format_description)
1469
def is_supported(self):
1470
"""Is this format supported?
1472
Supported formats can be initialized and opened.
1473
Unsupported formats may not support initialization or committing or
1474
some other features depending on the reason for not being supported.
1479
def register_format(klass, format):
1480
klass._formats[format.get_format_string()] = format
1483
def set_default_format(klass, format):
1484
klass._default_format = format
1487
def unregister_format(klass, format):
1488
assert klass._formats[format.get_format_string()] is format
1489
del klass._formats[format.get_format_string()]
1493
class WorkingTreeFormat2(WorkingTreeFormat):
1494
"""The second working tree format.
1496
This format modified the hash cache from the format 1 hash cache.
1499
def get_format_description(self):
1500
"""See WorkingTreeFormat.get_format_description()."""
1501
return "Working tree format 2"
1503
def initialize(self, a_bzrdir, revision_id=None):
1504
"""See WorkingTreeFormat.initialize()."""
1505
if not isinstance(a_bzrdir.transport, LocalTransport):
1506
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1507
branch = a_bzrdir.open_branch()
1508
if revision_id is not None:
1511
revision_history = branch.revision_history()
1513
position = revision_history.index(revision_id)
1515
raise errors.NoSuchRevision(branch, revision_id)
1516
branch.set_revision_history(revision_history[:position + 1])
1519
revision = branch.last_revision()
1521
wt = WorkingTree(a_bzrdir.root_transport.base,
1527
wt._write_inventory(inv)
1528
wt.set_root_id(inv.root.file_id)
1529
wt.set_last_revision(revision)
1530
wt.set_pending_merges([])
1531
build_tree(wt.basis_tree(), wt)
1535
super(WorkingTreeFormat2, self).__init__()
1536
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1538
def open(self, a_bzrdir, _found=False):
1539
"""Return the WorkingTree object for a_bzrdir
1541
_found is a private parameter, do not use it. It is used to indicate
1542
if format probing has already been done.
1545
# we are being called directly and must probe.
1546
raise NotImplementedError
1547
if not isinstance(a_bzrdir.transport, LocalTransport):
1548
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1549
return WorkingTree(a_bzrdir.root_transport.base,
1555
class WorkingTreeFormat3(WorkingTreeFormat):
1556
"""The second working tree format updated to record a format marker.
1559
- exists within a metadir controlling .bzr
1560
- includes an explicit version marker for the workingtree control
1561
files, separate from the BzrDir format
1562
- modifies the hash cache format
1564
- uses a LockDir to guard access to the repository
1567
def get_format_string(self):
1568
"""See WorkingTreeFormat.get_format_string()."""
1569
return "Bazaar-NG Working Tree format 3"
1571
def get_format_description(self):
1572
"""See WorkingTreeFormat.get_format_description()."""
1573
return "Working tree format 3"
1575
_lock_file_name = 'lock'
1576
_lock_class = LockDir
1578
def _open_control_files(self, a_bzrdir):
1579
transport = a_bzrdir.get_workingtree_transport(None)
1580
return LockableFiles(transport, self._lock_file_name,
1583
def initialize(self, a_bzrdir, revision_id=None):
1584
"""See WorkingTreeFormat.initialize().
1586
revision_id allows creating a working tree at a differnet
1587
revision than the branch is at.
1589
if not isinstance(a_bzrdir.transport, LocalTransport):
1590
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1591
transport = a_bzrdir.get_workingtree_transport(self)
1592
control_files = self._open_control_files(a_bzrdir)
1593
control_files.create_lock()
1594
control_files.lock_write()
1595
control_files.put_utf8('format', self.get_format_string())
1596
branch = a_bzrdir.open_branch()
1597
if revision_id is None:
1598
revision_id = branch.last_revision()
1600
wt = WorkingTree3(a_bzrdir.root_transport.base,
1606
_control_files=control_files)
1609
wt._write_inventory(inv)
1610
wt.set_root_id(inv.root.file_id)
1611
wt.set_last_revision(revision_id)
1612
wt.set_pending_merges([])
1613
build_tree(wt.basis_tree(), wt)
1616
control_files.unlock()
1620
super(WorkingTreeFormat3, self).__init__()
1621
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1623
def open(self, a_bzrdir, _found=False):
1624
"""Return the WorkingTree object for a_bzrdir
1626
_found is a private parameter, do not use it. It is used to indicate
1627
if format probing has already been done.
1630
# we are being called directly and must probe.
1631
raise NotImplementedError
1632
if not isinstance(a_bzrdir.transport, LocalTransport):
1633
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1634
control_files = self._open_control_files(a_bzrdir)
1635
return WorkingTree3(a_bzrdir.root_transport.base,
1639
_control_files=control_files)
1642
return self.get_format_string()
1645
# formats which have no format string are not discoverable
1646
# and not independently creatable, so are not registered.
1647
__default_format = WorkingTreeFormat3()
1648
WorkingTreeFormat.register_format(__default_format)
1649
WorkingTreeFormat.set_default_format(__default_format)
1650
_legacy_formats = [WorkingTreeFormat2(),
1654
class WorkingTreeTestProviderAdapter(object):
1655
"""A tool to generate a suite testing multiple workingtree formats at once.
1657
This is done by copying the test once for each transport and injecting
1658
the transport_server, transport_readonly_server, and workingtree_format
1659
classes into each copy. Each copy is also given a new id() to make it
1663
def __init__(self, transport_server, transport_readonly_server, formats):
1664
self._transport_server = transport_server
1665
self._transport_readonly_server = transport_readonly_server
1666
self._formats = formats
1668
def adapt(self, test):
1669
from bzrlib.tests import TestSuite
1670
result = TestSuite()
1671
for workingtree_format, bzrdir_format in self._formats:
1672
new_test = deepcopy(test)
1673
new_test.transport_server = self._transport_server
1674
new_test.transport_readonly_server = self._transport_readonly_server
1675
new_test.bzrdir_format = bzrdir_format
1676
new_test.workingtree_format = workingtree_format
1677
def make_new_test_id():
1678
new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1679
return lambda: new_id
1680
new_test.id = make_new_test_id()
1681
result.addTest(new_test)