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).
17
# TODO: Don't allow WorkingTrees to be constructed for remote branches.
33
19
# FIXME: I don't know if writing out the cache from the destructor is really a
34
# good idea, because destructors are considered poor taste in Python, and it's
35
# not predictable when it will be written out.
37
# TODO: Give the workingtree sole responsibility for the working inventory;
38
# remove the variable and references to it from the branch. This may require
39
# updating the commit code so as to update the inventory within the working
40
# copy, and making sure there's only one WorkingTree for any directory on disk.
41
# At the momenthey may alias the inventory and have old copies of it in memory.
43
from copy import deepcopy
44
from cStringIO import StringIO
20
# good idea, because destructors are considered poor taste in Python, and
21
# it's not predictable when it will be written out.
51
from bzrlib.atomicfile import AtomicFile
52
from bzrlib.branch import (Branch,
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,
60
WeaveRevisionNotPresent,
64
from bzrlib.inventory import InventoryEntry
65
from bzrlib.lockable_files import LockableFiles
66
from bzrlib.merge import merge_inner, transform_tree
67
from bzrlib.osutils import (appendpath,
82
from bzrlib.revision import NULL_REVISION
83
from bzrlib.symbol_versioning import *
84
from bzrlib.textui import show_status
86
from bzrlib.trace import mutter
87
from bzrlib.transport import get_transport
88
from bzrlib.transport.local import LocalTransport
92
def gen_file_id(name):
93
"""Return new file id.
95
This should probably generate proper UUIDs, but for the moment we
96
cope with just randomness because running uuidgen every time is
99
from binascii import hexlify
100
from time import time
103
idx = name.rfind('/')
105
name = name[idx+1 : ]
106
idx = name.rfind('\\')
108
name = name[idx+1 : ]
110
# make it not a hidden file
111
name = name.lstrip('.')
113
# remove any wierd characters; we don't escape them but rather
115
name = re.sub(r'[^\w.]', '', name)
117
s = hexlify(rand_bytes(8))
118
return '-'.join((name, compact_date(time()), s))
122
"""Return a new tree-root file id."""
123
return gen_file_id('TREE_ROOT')
126
class TreeEntry(object):
127
"""An entry that implements the minium interface used by commands.
129
This needs further inspection, it may be better to have
130
InventoryEntries without ids - though that seems wrong. For now,
131
this is a parallel hierarchy to InventoryEntry, and needs to become
132
one of several things: decorates to that hierarchy, children of, or
134
Another note is that these objects are currently only used when there is
135
no InventoryEntry available - i.e. for unversioned objects.
136
Perhaps they should be UnversionedEntry et al. ? - RBC 20051003
139
def __eq__(self, other):
140
# yes, this us ugly, TODO: best practice __eq__ style.
141
return (isinstance(other, TreeEntry)
142
and other.__class__ == self.__class__)
144
def kind_character(self):
148
class TreeDirectory(TreeEntry):
149
"""See TreeEntry. This is a directory in a working tree."""
151
def __eq__(self, other):
152
return (isinstance(other, TreeDirectory)
153
and other.__class__ == self.__class__)
155
def kind_character(self):
159
class TreeFile(TreeEntry):
160
"""See TreeEntry. This is a regular file in a working tree."""
162
def __eq__(self, other):
163
return (isinstance(other, TreeFile)
164
and other.__class__ == self.__class__)
166
def kind_character(self):
170
class TreeLink(TreeEntry):
171
"""See TreeEntry. This is a symlink in a working tree."""
173
def __eq__(self, other):
174
return (isinstance(other, TreeLink)
175
and other.__class__ == self.__class__)
177
def kind_character(self):
26
from errors import BzrCheckError
27
from trace import mutter
181
29
class WorkingTree(bzrlib.tree.Tree):
182
30
"""Working copy tree.
187
35
It is possible for a `WorkingTree` to have a filename which is
188
36
not listed in the Inventory and vice versa.
191
def __init__(self, basedir='.',
192
branch=DEPRECATED_PARAMETER,
198
"""Construct a WorkingTree for basedir.
200
If the branch is not supplied, it is opened automatically.
201
If the branch is supplied, it must be the branch for this basedir.
202
(branch.base is not cross checked, because for remote branches that
203
would be meaningless).
205
self._format = _format
206
self.bzrdir = _bzrdir
208
# not created via open etc.
209
warn("WorkingTree() is deprecated as of bzr version 0.8. "
210
"Please use bzrdir.open_workingtree or WorkingTree.open().",
213
wt = WorkingTree.open(basedir)
214
self.branch = wt.branch
215
self.basedir = wt.basedir
216
self._control_files = wt._control_files
217
self._hashcache = wt._hashcache
218
self._set_inventory(wt._inventory)
219
self._format = wt._format
220
self.bzrdir = wt.bzrdir
38
def __init__(self, basedir, inv):
221
39
from bzrlib.hashcache import HashCache
222
40
from bzrlib.trace import note, mutter
223
assert isinstance(basedir, basestring), \
224
"base directory %r is not a string" % basedir
225
basedir = safe_unicode(basedir)
226
mutter("openeing working tree %r", basedir)
227
if deprecated_passed(branch):
229
warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
230
" Please use bzrdir.open_workingtree().",
236
self.branch = self.bzrdir.open_branch()
237
assert isinstance(self.branch, Branch), \
238
"branch %r is not a Branch" % self.branch
239
self.basedir = realpath(basedir)
240
# if branch is at our basedir and is a format 6 or less
241
if isinstance(self._format, WorkingTreeFormat2):
242
# share control object
243
self._control_files = self.branch.control_files
244
elif _control_files is not None:
245
assert False, "not done yet"
246
# self._control_files = _control_files
248
# only ready for format 3
249
assert isinstance(self._format, WorkingTreeFormat3)
250
self._control_files = LockableFiles(
251
self.bzrdir.get_workingtree_transport(None),
43
self.basedir = basedir
44
self.path2id = inv.path2id
254
46
# update the whole cache up front and write to disk if anything changed;
255
47
# in the future we might want to do this more selectively
256
# two possible ways offer themselves : in self._unlock, write the cache
257
# if needed, or, when the cache sees a change, append it to the hash
258
# cache file, and have the parser take the most recent entry for a
260
cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
261
hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
48
hc = self._hashcache = HashCache(basedir)
263
# is this scan needed ? it makes things kinda slow.
266
52
if hc.needs_write:
267
53
mutter("write hc")
270
if _inventory is None:
271
self._set_inventory(self.read_working_inventory())
273
self._set_inventory(_inventory)
275
def _set_inventory(self, inv):
276
self._inventory = inv
277
self.path2id = self._inventory.path2id
280
def open(path=None, _unsupported=False):
281
"""Open an existing working tree at path.
285
path = os.path.getcwdu()
286
control = bzrdir.BzrDir.open(path, _unsupported)
287
return control.open_workingtree(_unsupported)
290
def open_containing(path=None):
291
"""Open an existing working tree which has its root about path.
293
This probes for a working tree at path and searches upwards from there.
295
Basically we keep looking up until we find the control directory or
296
run into /. If there isn't one, raises NotBranchError.
297
TODO: give this a new exception.
298
If there is one, it is returned, along with the unused portion of path.
302
control, relpath = bzrdir.BzrDir.open_containing(path)
303
return control.open_workingtree(), relpath
306
def open_downlevel(path=None):
307
"""Open an unsupported working tree.
309
Only intended for advanced situations like upgrading part of a bzrdir.
311
return WorkingTree.open(path, _unsupported=True)
58
if self._hashcache.needs_write:
59
self._hashcache.write()
313
62
def __iter__(self):
314
63
"""Iterate through file_ids for this tree.
392
89
def get_file_byname(self, filename):
393
90
return file(self.abspath(filename), 'rb')
395
def get_root_id(self):
396
"""Return the id of this trees root"""
397
inv = self.read_working_inventory()
398
return inv.root.file_id
400
92
def _get_store_filename(self, file_id):
401
## XXX: badly named; this is not in the store at all
402
return self.abspath(self.id2path(file_id))
405
def clone(self, to_bzrdir, revision_id=None, basis=None):
406
"""Duplicate this working tree into to_bzr, including all state.
408
Specifically modified files are kept as modified, but
409
ignored and unknown files are discarded.
411
If you want to make a new line of development, see bzrdir.sprout()
414
If not None, the cloned tree will have its last revision set to
415
revision, and and difference between the source trees last revision
416
and this one merged in.
419
If not None, a closer copy of a tree which may have some files in
420
common, and which file content should be preferentially copied from.
422
# assumes the target bzr dir format is compatible.
423
result = self._format.initialize(to_bzrdir)
424
self.copy_content_into(result, revision_id)
428
def copy_content_into(self, tree, revision_id=None):
429
"""Copy the current content and user files of this tree into tree."""
430
if revision_id is None:
431
transform_tree(tree, self)
433
# TODO now merge from tree.last_revision to revision
434
transform_tree(tree, self)
435
tree.set_last_revision(revision_id)
438
def commit(self, *args, **kwargs):
439
from bzrlib.commit import Commit
440
# args for wt.commit start at message from the Commit.commit method,
441
# but with branch a kwarg now, passing in args as is results in the
442
#message being used for the branch
443
args = (DEPRECATED_PARAMETER, ) + args
444
Commit().commit(working_tree=self, *args, **kwargs)
445
self._set_inventory(self.read_working_inventory())
447
def id2abspath(self, file_id):
448
return self.abspath(self.id2path(file_id))
93
## XXX: badly named; this isn't in the store at all
94
return self.abspath(self.id2path(file_id))
450
97
def has_id(self, file_id):
451
98
# files that have been deleted are excluded
452
99
inv = self._inventory
453
100
if not inv.has_id(file_id):
455
102
path = inv.id2path(file_id)
456
return bzrlib.osutils.lexists(self.abspath(path))
103
return os.path.exists(self.abspath(path))
458
def has_or_had_id(self, file_id):
459
if file_id == self.inventory.root.file_id:
461
return self.inventory.has_id(file_id)
463
106
__contains__ = has_id
465
109
def get_file_size(self, file_id):
466
return os.path.getsize(self.id2abspath(file_id))
110
# is this still called?
111
raise NotImplementedError()
469
114
def get_file_sha1(self, file_id):
470
115
path = self._inventory.id2path(file_id)
471
116
return self._hashcache.get_sha1(path)
473
def is_executable(self, file_id):
475
return self._inventory[file_id].executable
477
path = self._inventory.id2path(file_id)
478
mode = os.lstat(self.abspath(path)).st_mode
479
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
482
def add(self, files, ids=None):
483
"""Make files versioned.
485
Note that the command line normally calls smart_add instead,
486
which can automatically recurse.
488
This adds the files to the inventory, so that they will be
489
recorded by the next commit.
492
List of paths to add, relative to the base of the tree.
495
If set, use these instead of automatically generated ids.
496
Must be the same length as the list of files, but may
497
contain None for ids that are to be autogenerated.
499
TODO: Perhaps have an option to add the ids even if the files do
502
TODO: Perhaps callback with the ids and paths as they're added.
504
# TODO: Re-adding a file that is removed in the working copy
505
# should probably put it back with the previous ID.
506
if isinstance(files, basestring):
507
assert(ids is None or isinstance(ids, basestring))
513
ids = [None] * len(files)
515
assert(len(ids) == len(files))
517
inv = self.read_working_inventory()
518
for f,file_id in zip(files, ids):
519
if is_control_file(f):
520
raise BzrError("cannot add control file %s" % quotefn(f))
525
raise BzrError("cannot add top-level %r" % f)
527
fullpath = normpath(self.abspath(f))
530
kind = file_kind(fullpath)
532
if e.errno == errno.ENOENT:
533
raise NoSuchFile(fullpath)
534
# maybe something better?
535
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
537
if not InventoryEntry.versionable_kind(kind):
538
raise BzrError('cannot add: not a versionable file ('
539
'i.e. regular file, symlink or directory): %s' % quotefn(f))
542
file_id = gen_file_id(f)
543
inv.add_path(f, kind=kind, file_id=file_id)
545
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
546
self._write_inventory(inv)
549
def add_pending_merge(self, *revision_ids):
550
# TODO: Perhaps should check at this point that the
551
# history of the revision is actually present?
552
p = self.pending_merges()
554
for rev_id in revision_ids:
560
self.set_pending_merges(p)
563
def pending_merges(self):
564
"""Return a list of pending merges.
566
These are revisions that have been merged into the working
567
directory but not yet committed.
570
merges_file = self._control_files.get_utf8('pending-merges')
572
if e.errno != errno.ENOENT:
576
for l in merges_file.readlines():
577
p.append(l.rstrip('\n'))
581
def set_pending_merges(self, rev_list):
582
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
584
def get_symlink_target(self, file_id):
585
return os.readlink(self.id2abspath(file_id))
587
119
def file_class(self, filename):
588
120
if self.path2id(filename):
661
184
for ff in descend(fp, f_ie.file_id, fap):
664
for f in descend(u'', inv.root.file_id, self.basedir):
187
for f in descend('', inv.root.file_id, self.basedir):
668
def move(self, from_paths, to_name):
671
to_name must exist in the inventory.
673
If to_name exists and is a directory, the files are moved into
674
it, keeping their old names.
676
Note that to_name is only the last component of the new name;
677
this doesn't change the directory.
679
This returns a list of (from_path, to_path) pairs for each
683
## TODO: Option to move IDs only
684
assert not isinstance(from_paths, basestring)
686
to_abs = self.abspath(to_name)
687
if not isdir(to_abs):
688
raise BzrError("destination %r is not a directory" % to_abs)
689
if not self.has_filename(to_name):
690
raise BzrError("destination %r not in working directory" % to_abs)
691
to_dir_id = inv.path2id(to_name)
692
if to_dir_id == None and to_name != '':
693
raise BzrError("destination %r is not a versioned directory" % to_name)
694
to_dir_ie = inv[to_dir_id]
695
if to_dir_ie.kind not in ('directory', 'root_directory'):
696
raise BzrError("destination %r is not a directory" % to_abs)
698
to_idpath = inv.get_idpath(to_dir_id)
701
if not self.has_filename(f):
702
raise BzrError("%r does not exist in working tree" % f)
703
f_id = inv.path2id(f)
705
raise BzrError("%r is not versioned" % f)
706
name_tail = splitpath(f)[-1]
707
dest_path = appendpath(to_name, name_tail)
708
if self.has_filename(dest_path):
709
raise BzrError("destination %r already exists" % dest_path)
710
if f_id in to_idpath:
711
raise BzrError("can't move %r to a subdirectory of itself" % f)
713
# OK, so there's a race here, it's possible that someone will
714
# create a file in this interval and then the rename might be
715
# left half-done. But we should have caught most problems.
716
orig_inv = deepcopy(self.inventory)
719
name_tail = splitpath(f)[-1]
720
dest_path = appendpath(to_name, name_tail)
721
result.append((f, dest_path))
722
inv.rename(inv.path2id(f), to_dir_id, name_tail)
724
rename(self.abspath(f), self.abspath(dest_path))
726
raise BzrError("failed to rename %r to %r: %s" %
727
(f, dest_path, e[1]),
728
["rename rolled back"])
730
# restore the inventory on error
731
self._set_inventory(orig_inv)
733
self._write_inventory(inv)
737
def rename_one(self, from_rel, to_rel):
740
This can change the directory or the filename or both.
743
if not self.has_filename(from_rel):
744
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
745
if self.has_filename(to_rel):
746
raise BzrError("can't rename: new working file %r already exists" % to_rel)
748
file_id = inv.path2id(from_rel)
750
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
753
from_parent = entry.parent_id
754
from_name = entry.name
756
if inv.path2id(to_rel):
757
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
759
to_dir, to_tail = os.path.split(to_rel)
760
to_dir_id = inv.path2id(to_dir)
761
if to_dir_id == None and to_dir != '':
762
raise BzrError("can't determine destination directory id for %r" % to_dir)
764
mutter("rename_one:")
765
mutter(" file_id {%s}" % file_id)
766
mutter(" from_rel %r" % from_rel)
767
mutter(" to_rel %r" % to_rel)
768
mutter(" to_dir %r" % to_dir)
769
mutter(" to_dir_id {%s}" % to_dir_id)
771
inv.rename(file_id, to_dir_id, to_tail)
773
from_abs = self.abspath(from_rel)
774
to_abs = self.abspath(to_rel)
776
rename(from_abs, to_abs)
778
inv.rename(file_id, from_parent, from_name)
779
raise BzrError("failed to rename %r to %r: %s"
780
% (from_abs, to_abs, e[1]),
781
["rename rolled back"])
782
self._write_inventory(inv)
785
192
def unknowns(self):
786
"""Return all unknown files.
788
These are files in the working directory that are not versioned or
789
control files or ignored.
791
>>> from bzrlib.bzrdir import ScratchDir
792
>>> d = ScratchDir(files=['foo', 'foo~'])
793
>>> b = d.open_branch()
794
>>> tree = d.open_workingtree()
795
>>> map(str, tree.unknowns())
798
>>> list(b.unknowns())
800
>>> tree.remove('foo')
801
>>> list(b.unknowns())
804
193
for subp in self.extras():
805
194
if not self.is_ignored(subp):
808
def iter_conflicts(self):
810
for path in (s[0] for s in self.list_files()):
811
stem = get_conflicted_stem(path)
814
if stem not in conflicted:
819
def pull(self, source, overwrite=False):
822
old_revision_history = self.branch.revision_history()
823
count = self.branch.pull(source, overwrite)
824
new_revision_history = self.branch.revision_history()
825
if new_revision_history != old_revision_history:
826
if len(old_revision_history):
827
other_revision = old_revision_history[-1]
829
other_revision = None
830
repository = self.branch.repository
831
merge_inner(self.branch,
833
repository.revision_tree(other_revision),
835
self.set_last_revision(self.branch.last_revision())
840
198
def extras(self):
841
199
"""Yield all unknown files in this WorkingTree.
931
def kind(self, file_id):
932
return file_kind(self.id2abspath(file_id))
935
def last_revision(self):
936
"""Return the last revision id of this working tree.
938
In early branch formats this was == the branch last_revision,
939
but that cannot be relied upon - for working tree operations,
940
always use tree.last_revision().
942
return self.branch.last_revision()
945
"""See Branch.lock_read, and WorkingTree.unlock."""
946
self.branch.lock_read()
948
return self._control_files.lock_read()
953
def lock_write(self):
954
"""See Branch.lock_write, and WorkingTree.unlock."""
955
self.branch.lock_write()
957
return self._control_files.lock_write()
962
def _basis_inventory_name(self, revision_id):
963
return 'basis-inventory.%s' % revision_id
966
def set_last_revision(self, new_revision, old_revision=None):
967
"""Change the last revision in the working tree."""
968
self._remove_old_basis(old_revision)
969
if self._change_last_revision(new_revision):
970
self._cache_basis_inventory(new_revision)
972
def _change_last_revision(self, new_revision):
973
"""Template method part of set_last_revision to perform the change."""
974
if new_revision is None:
975
self.branch.set_revision_history([])
977
# current format is locked in with the branch
978
revision_history = self.branch.revision_history()
980
position = revision_history.index(new_revision)
982
raise errors.NoSuchRevision(self.branch, new_revision)
983
self.branch.set_revision_history(revision_history[:position + 1])
986
def _cache_basis_inventory(self, new_revision):
987
"""Cache new_revision as the basis inventory."""
989
xml = self.branch.repository.get_inventory_xml(new_revision)
990
path = self._basis_inventory_name(new_revision)
991
self._control_files.put_utf8(path, xml)
992
except WeaveRevisionNotPresent:
995
def _remove_old_basis(self, old_revision):
996
"""Remove the old basis inventory 'old_revision'."""
997
if old_revision is not None:
999
path = self._basis_inventory_name(old_revision)
1000
path = self._control_files._escape(path)
1001
self._control_files._transport.delete(path)
1005
def read_basis_inventory(self, revision_id):
1006
"""Read the cached basis inventory."""
1007
path = self._basis_inventory_name(revision_id)
1008
return self._control_files.get_utf8(path).read()
1011
def read_working_inventory(self):
1012
"""Read the working inventory."""
1013
# ElementTree does its own conversion from UTF-8, so open in
1015
result = bzrlib.xml5.serializer_v5.read_inventory(
1016
self._control_files.get('inventory'))
1017
self._set_inventory(result)
1021
def remove(self, files, verbose=False):
1022
"""Remove nominated files from the working inventory..
1024
This does not remove their text. This does not run on XXX on what? RBC
1026
TODO: Refuse to remove modified files unless --force is given?
1028
TODO: Do something useful with directories.
1030
TODO: Should this remove the text or not? Tough call; not
1031
removing may be useful and the user can just use use rm, and
1032
is the opposite of add. Removing it is consistent with most
1033
other tools. Maybe an option.
1035
## TODO: Normalize names
1036
## TODO: Remove nested loops; better scalability
1037
if isinstance(files, basestring):
1040
inv = self.inventory
1042
# do this before any modifications
1044
fid = inv.path2id(f)
1046
# TODO: Perhaps make this just a warning, and continue?
1047
# This tends to happen when
1048
raise NotVersionedError(path=f)
1049
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1051
# having remove it, it must be either ignored or unknown
1052
if self.is_ignored(f):
1056
show_status(new_status, inv[fid].kind, quotefn(f))
1059
self._write_inventory(inv)
1062
def revert(self, filenames, old_tree=None, backups=True):
1063
from bzrlib.merge import merge_inner
1064
if old_tree is None:
1065
old_tree = self.basis_tree()
1066
merge_inner(self.branch, old_tree,
1067
self, ignore_zero=True,
1068
backup_files=backups,
1069
interesting_files=filenames,
1071
if not len(filenames):
1072
self.set_pending_merges([])
1075
def set_inventory(self, new_inventory_list):
1076
from bzrlib.inventory import (Inventory,
1081
inv = Inventory(self.get_root_id())
1082
for path, file_id, parent, kind in new_inventory_list:
1083
name = os.path.basename(path)
1086
# fixme, there should be a factory function inv,add_??
1087
if kind == 'directory':
1088
inv.add(InventoryDirectory(file_id, name, parent))
1089
elif kind == 'file':
1090
inv.add(InventoryFile(file_id, name, parent))
1091
elif kind == 'symlink':
1092
inv.add(InventoryLink(file_id, name, parent))
1094
raise BzrError("unknown kind %r" % kind)
1095
self._write_inventory(inv)
1098
def set_root_id(self, file_id):
1099
"""Set the root id for this tree."""
1100
inv = self.read_working_inventory()
1101
orig_root_id = inv.root.file_id
1102
del inv._byid[inv.root.file_id]
1103
inv.root.file_id = file_id
1104
inv._byid[inv.root.file_id] = inv.root
1107
if entry.parent_id == orig_root_id:
1108
entry.parent_id = inv.root.file_id
1109
self._write_inventory(inv)
1112
"""See Branch.unlock.
1114
WorkingTree locking just uses the Branch locking facilities.
1115
This is current because all working trees have an embedded branch
1116
within them. IF in the future, we were to make branch data shareable
1117
between multiple working trees, i.e. via shared storage, then we
1118
would probably want to lock both the local tree, and the branch.
1120
# FIXME: We want to write out the hashcache only when the last lock on
1121
# this working copy is released. Peeking at the lock count is a bit
1122
# of a nasty hack; probably it's better to have a transaction object,
1123
# which can do some finalization when it's either successfully or
1124
# unsuccessfully completed. (Denys's original patch did that.)
1125
# RBC 20060206 hookinhg into transaction will couple lock and transaction
1126
# wrongly. Hookinh into unllock on the control files object is fine though.
1128
# TODO: split this per format so there is no ugly if block
1129
if self._hashcache.needs_write and (
1130
self._control_files._lock_count==1 or
1131
(self._control_files is self.branch.control_files and
1132
self._control_files._lock_count==2)):
1133
self._hashcache.write()
1134
# reverse order of locking.
1135
result = self._control_files.unlock()
1137
self.branch.unlock()
1143
self.branch.lock_read()
1145
if self.last_revision() == self.branch.last_revision():
1147
basis = self.basis_tree()
1148
to_tree = self.branch.basis_tree()
1149
result = merge_inner(self.branch,
1153
self.set_last_revision(self.branch.last_revision())
1156
self.branch.unlock()
1159
def _write_inventory(self, inv):
1160
"""Write inventory as the current inventory."""
1162
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1164
self._control_files.put('inventory', sio)
1165
self._set_inventory(inv)
1166
mutter('wrote working inventory')
1169
class WorkingTree3(WorkingTree):
1170
"""This is the Format 3 working tree.
1172
This differs from the base WorkingTree by:
1173
- having its own file lock
1174
- having its own last-revision property.
1178
def last_revision(self):
1179
"""See WorkingTree.last_revision."""
1181
return self._control_files.get_utf8('last-revision').read()
1185
def _change_last_revision(self, revision_id):
1186
"""See WorkingTree._change_last_revision."""
1187
if revision_id is None or revision_id == NULL_REVISION:
1189
self._control_files._transport.delete('last-revision')
1190
except errors.NoSuchFile:
1195
self.branch.revision_history().index(revision_id)
1197
raise errors.NoSuchRevision(self.branch, revision_id)
1198
self._control_files.put_utf8('last-revision', revision_id)
1202
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1203
def get_conflicted_stem(path):
1204
for suffix in CONFLICT_SUFFIXES:
1205
if path.endswith(suffix):
1206
return path[:-len(suffix)]
1208
def is_control_file(filename):
1209
## FIXME: better check
1210
filename = normpath(filename)
1211
while filename != '':
1212
head, tail = os.path.split(filename)
1213
## mutter('check %r for control file' % ((head, tail),))
1214
if tail == bzrlib.BZRDIR:
1216
if filename == head:
1222
class WorkingTreeFormat(object):
1223
"""An encapsulation of the initialization and open routines for a format.
1225
Formats provide three things:
1226
* An initialization routine,
1230
Formats are placed in an dict by their format string for reference
1231
during workingtree opening. Its not required that these be instances, they
1232
can be classes themselves with class methods - it simply depends on
1233
whether state is needed for a given format or not.
1235
Once a format is deprecated, just deprecate the initialize and open
1236
methods on the format class. Do not deprecate the object, as the
1237
object will be created every time regardless.
1240
_default_format = None
1241
"""The default format used for new trees."""
1244
"""The known formats."""
1247
def find_format(klass, a_bzrdir):
1248
"""Return the format for the working tree object in a_bzrdir."""
1250
transport = a_bzrdir.get_workingtree_transport(None)
1251
format_string = transport.get("format").read()
1252
return klass._formats[format_string]
1254
raise errors.NoWorkingTree(base=transport.base)
1256
raise errors.UnknownFormatError(format_string)
1259
def get_default_format(klass):
1260
"""Return the current default format."""
1261
return klass._default_format
1263
def get_format_string(self):
1264
"""Return the ASCII format string that identifies this format."""
1265
raise NotImplementedError(self.get_format_string)
1267
def is_supported(self):
1268
"""Is this format supported?
1270
Supported formats can be initialized and opened.
1271
Unsupported formats may not support initialization or committing or
1272
some other features depending on the reason for not being supported.
1277
def register_format(klass, format):
1278
klass._formats[format.get_format_string()] = format
1281
def set_default_format(klass, format):
1282
klass._default_format = format
1285
def unregister_format(klass, format):
1286
assert klass._formats[format.get_format_string()] is format
1287
del klass._formats[format.get_format_string()]
1291
class WorkingTreeFormat2(WorkingTreeFormat):
1292
"""The second working tree format.
1294
This format modified the hash cache from the format 1 hash cache.
1297
def initialize(self, a_bzrdir, revision_id=None):
1298
"""See WorkingTreeFormat.initialize()."""
1299
if not isinstance(a_bzrdir.transport, LocalTransport):
1300
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1301
branch = a_bzrdir.open_branch()
1302
if revision_id is not None:
1305
revision_history = branch.revision_history()
1307
position = revision_history.index(revision_id)
1309
raise errors.NoSuchRevision(branch, revision_id)
1310
branch.set_revision_history(revision_history[:position + 1])
1313
revision = branch.last_revision()
1314
basis_tree = branch.repository.revision_tree(revision)
1315
inv = basis_tree.inventory
1316
wt = WorkingTree(a_bzrdir.root_transport.base,
1322
wt._write_inventory(inv)
1323
wt.set_root_id(inv.root.file_id)
1324
wt.set_last_revision(revision)
1325
wt.set_pending_merges([])
1330
super(WorkingTreeFormat2, self).__init__()
1331
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1333
def open(self, a_bzrdir, _found=False):
1334
"""Return the WorkingTree object for a_bzrdir
1336
_found is a private parameter, do not use it. It is used to indicate
1337
if format probing has already been done.
1340
# we are being called directly and must probe.
1341
raise NotImplementedError
1342
if not isinstance(a_bzrdir.transport, LocalTransport):
1343
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1344
return WorkingTree(a_bzrdir.root_transport.base,
1350
class WorkingTreeFormat3(WorkingTreeFormat):
1351
"""The second working tree format updated to record a format marker.
1353
This format modified the hash cache from the format 1 hash cache.
1356
def get_format_string(self):
1357
"""See WorkingTreeFormat.get_format_string()."""
1358
return "Bazaar-NG Working Tree format 3"
1360
def initialize(self, a_bzrdir, revision_id=None):
1361
"""See WorkingTreeFormat.initialize().
1363
revision_id allows creating a working tree at a differnet
1364
revision than the branch is at.
1366
if not isinstance(a_bzrdir.transport, LocalTransport):
1367
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1368
transport = a_bzrdir.get_workingtree_transport(self)
1369
control_files = LockableFiles(transport, 'lock')
1370
control_files.put_utf8('format', self.get_format_string())
1371
branch = a_bzrdir.open_branch()
1372
if revision_id is None:
1373
revision_id = branch.last_revision()
1374
new_basis_tree = branch.repository.revision_tree(revision_id)
1375
inv = new_basis_tree.inventory
1376
wt = WorkingTree3(a_bzrdir.root_transport.base,
1382
wt._write_inventory(inv)
1383
wt.set_root_id(inv.root.file_id)
1384
wt.set_last_revision(revision_id)
1385
wt.set_pending_merges([])
1390
super(WorkingTreeFormat3, self).__init__()
1391
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1393
def open(self, a_bzrdir, _found=False):
1394
"""Return the WorkingTree object for a_bzrdir
1396
_found is a private parameter, do not use it. It is used to indicate
1397
if format probing has already been done.
1400
# we are being called directly and must probe.
1401
raise NotImplementedError
1402
if not isinstance(a_bzrdir.transport, LocalTransport):
1403
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1404
return WorkingTree3(a_bzrdir.root_transport.base,
1410
# formats which have no format string are not discoverable
1411
# and not independently creatable, so are not registered.
1412
__default_format = WorkingTreeFormat3()
1413
WorkingTreeFormat.register_format(__default_format)
1414
WorkingTreeFormat.set_default_format(__default_format)
1415
_legacy_formats = [WorkingTreeFormat2(),
1419
class WorkingTreeTestProviderAdapter(object):
1420
"""A tool to generate a suite testing multiple workingtree formats at once.
1422
This is done by copying the test once for each transport and injecting
1423
the transport_server, transport_readonly_server, and workingtree_format
1424
classes into each copy. Each copy is also given a new id() to make it
1428
def __init__(self, transport_server, transport_readonly_server, formats):
1429
self._transport_server = transport_server
1430
self._transport_readonly_server = transport_readonly_server
1431
self._formats = formats
1433
def adapt(self, test):
1434
from bzrlib.tests import TestSuite
1435
result = TestSuite()
1436
for workingtree_format, bzrdir_format in self._formats:
1437
new_test = deepcopy(test)
1438
new_test.transport_server = self._transport_server
1439
new_test.transport_readonly_server = self._transport_readonly_server
1440
new_test.bzrdir_format = bzrdir_format
1441
new_test.workingtree_format = workingtree_format
1442
def make_new_test_id():
1443
new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1444
return lambda: new_id
1445
new_test.id = make_new_test_id()
1446
result.addTest(new_test)
b'\\ No newline at end of file'