14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""WorkingTree object and friends.
19
A WorkingTree represents the editable working copy of a branch.
20
Operations which represent the WorkingTree are also done here,
21
such as renaming or adding files. The WorkingTree has an inventory
22
which is updated by these operations. A commit produces a
23
new revision based on the workingtree and its inventory.
25
At the moment every WorkingTree has its own branch. Remote
26
WorkingTrees aren't supported.
28
To get a WorkingTree, call bzrdir.open_workingtree() or
29
WorkingTree.open(dir).
32
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
33
CONFLICT_HEADER_1 = "BZR conflict list format 1"
35
# TODO: Give the workingtree sole responsibility for the working inventory;
36
# remove the variable and references to it from the branch. This may require
37
# updating the commit code so as to update the inventory within the working
38
# 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)
42
from copy import deepcopy
43
from cStringIO import StringIO
17
# TODO: Don't allow WorkingTrees to be constructed for remote branches.
19
# FIXME: I don't know if writing out the cache from the destructor is really a
20
# good idea, because destructors are considered poor taste in Python, and
21
# it's not predictable when it will be written out.
50
from bzrlib.atomicfile import AtomicFile
51
from bzrlib.branch import (Branch,
53
from bzrlib.conflicts import Conflict, ConflictList, CONFLICT_SUFFIXES
54
import bzrlib.bzrdir as bzrdir
55
from bzrlib.decorators import needs_read_lock, needs_write_lock
56
import bzrlib.errors as errors
57
from bzrlib.errors import (BzrCheckError,
61
WeaveRevisionNotPresent,
65
MergeModifiedFormatError,
68
from bzrlib.inventory import InventoryEntry, Inventory
69
from bzrlib.lockable_files import LockableFiles, TransportLock
70
from bzrlib.lockdir import LockDir
71
from bzrlib.merge import merge_inner, transform_tree
72
from bzrlib.osutils import (
90
from bzrlib.progress import DummyProgress, ProgressPhase
91
from bzrlib.revision import NULL_REVISION
92
from bzrlib.rio import RioReader, rio_file, Stanza
93
from bzrlib.symbol_versioning import *
94
from bzrlib.textui import show_status
27
from bzrlib.branch import Branch, needs_read_lock, needs_write_lock, quotefn
96
from bzrlib.transform import build_tree
97
from bzrlib.trace import mutter, note
98
from bzrlib.transport import get_transport
99
from bzrlib.transport.local import LocalTransport
104
def gen_file_id(name):
105
"""Return new file id.
107
This should probably generate proper UUIDs, but for the moment we
108
cope with just randomness because running uuidgen every time is
111
from binascii import hexlify
112
from time import time
115
idx = name.rfind('/')
117
name = name[idx+1 : ]
118
idx = name.rfind('\\')
120
name = name[idx+1 : ]
122
# make it not a hidden file
123
name = name.lstrip('.')
125
# remove any wierd characters; we don't escape them but rather
127
name = re.sub(r'[^\w.]', '', name)
129
s = hexlify(rand_bytes(8))
130
return '-'.join((name, compact_date(time()), s))
134
"""Return a new tree-root file id."""
135
return gen_file_id('TREE_ROOT')
29
from bzrlib.osutils import appendpath, file_kind, isdir, splitpath, relpath
30
from bzrlib.errors import BzrCheckError
31
from bzrlib.trace import mutter
138
33
class TreeEntry(object):
139
34
"""An entry that implements the minium interface used by commands.
214
103
(branch.base is not cross checked, because for remote branches that
215
104
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
233
106
from bzrlib.hashcache import HashCache
234
107
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
110
branch = Branch.open(basedir)
111
self._inventory = branch.inventory
112
self.path2id = self._inventory.path2id
114
self.basedir = basedir
264
116
# update the whole cache up front and write to disk if anything changed;
265
117
# 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)
118
hc = self._hashcache = HashCache(basedir)
273
# is this scan needed ? it makes things kinda slow.
276
122
if hc.needs_write:
277
123
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)
128
if self._hashcache.needs_write:
129
self._hashcache.write()
344
132
def __iter__(self):
345
133
"""Iterate through file_ids for this tree.
425
163
def get_file_byname(self, filename):
426
164
return file(self.abspath(filename), 'rb')
428
def get_root_id(self):
429
"""Return the id of this trees root"""
430
inv = self.read_working_inventory()
431
return inv.root.file_id
433
166
def _get_store_filename(self, file_id):
434
## XXX: badly named; this is not in the store at all
167
## XXX: badly named; this isn't in the store at all
435
168
return self.abspath(self.id2path(file_id))
438
def clone(self, to_bzrdir, revision_id=None, basis=None):
439
"""Duplicate this working tree into to_bzr, including all state.
441
Specifically modified files are kept as modified, but
442
ignored and unknown files are discarded.
444
If you want to make a new line of development, see bzrdir.sprout()
447
If not None, the cloned tree will have its last revision set to
448
revision, and and difference between the source trees last revision
449
and this one merged in.
452
If not None, a closer copy of a tree which may have some files in
453
common, and which file content should be preferentially copied from.
455
# assumes the target bzr dir format is compatible.
456
result = self._format.initialize(to_bzrdir)
457
self.copy_content_into(result, revision_id)
461
def copy_content_into(self, tree, revision_id=None):
462
"""Copy the current content and user files of this tree into tree."""
463
if revision_id is None:
464
transform_tree(tree, self)
466
# TODO now merge from tree.last_revision to revision
467
transform_tree(tree, self)
468
tree.set_last_revision(revision_id)
471
def commit(self, message=None, revprops=None, *args, **kwargs):
472
# avoid circular imports
473
from bzrlib.commit import Commit
476
if not 'branch-nick' in revprops:
477
revprops['branch-nick'] = self.branch.nick
478
# args for wt.commit start at message from the Commit.commit method,
479
# but with branch a kwarg now, passing in args as is results in the
480
#message being used for the branch
481
args = (DEPRECATED_PARAMETER, message, ) + args
482
Commit().commit(working_tree=self, revprops=revprops, *args, **kwargs)
483
self._set_inventory(self.read_working_inventory())
485
171
def id2abspath(self, file_id):
486
172
return self.abspath(self.id2path(file_id))
488
175
def has_id(self, file_id):
489
176
# files that have been deleted are excluded
490
177
inv = self._inventory
499
186
return self.inventory.has_id(file_id)
501
188
__contains__ = has_id
503
191
def get_file_size(self, file_id):
504
192
return os.path.getsize(self.id2abspath(file_id))
507
194
def get_file_sha1(self, file_id):
508
195
path = self._inventory.id2path(file_id)
509
196
return self._hashcache.get_sha1(path)
511
199
def is_executable(self, file_id):
512
if not supports_executable():
513
201
return self._inventory[file_id].executable
515
203
path = self._inventory.id2path(file_id)
516
204
mode = os.lstat(self.abspath(path)).st_mode
517
205
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
207
def get_symlink_target(self, file_id):
656
208
return os.readlink(self.id2abspath(file_id))
734
285
for ff in descend(fp, f_ie.file_id, fap):
737
for f in descend(u'', inv.root.file_id, self.basedir):
288
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
293
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
294
for subp in self.extras():
878
295
if not self.is_ignored(subp):
881
@deprecated_method(zero_eight)
882
298
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
299
conflicted = set()
889
300
for path in (s[0] for s in self.list_files()):
890
301
stem = get_conflicted_stem(path)
1021
399
def kind(self, file_id):
1022
400
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
402
def lock_read(self):
1038
403
"""See Branch.lock_read, and WorkingTree.unlock."""
1039
self.branch.lock_read()
1041
return self._control_files.lock_read()
1043
self.branch.unlock()
404
return self.branch.lock_read()
1046
406
def lock_write(self):
1047
407
"""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)
408
return self.branch.lock_write()
1117
410
@needs_write_lock
1118
411
def remove(self, files, verbose=False):
1218
455
between multiple working trees, i.e. via shared storage, then we
1219
456
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))
458
return self.branch.unlock()
461
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1399
462
def get_conflicted_stem(path):
1400
463
for suffix in CONFLICT_SUFFIXES:
1401
464
if path.endswith(suffix):
1402
465
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)