1
# Copyright (C) 2005 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
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).
33
# 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
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):
181
class WorkingTree(bzrlib.tree.Tree):
182
"""Working copy tree.
184
The inventory is held in the `Branch` working-inventory, and the
185
files are in a directory on disk.
187
It is possible for a `WorkingTree` to have a filename which is
188
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
221
from bzrlib.hashcache import HashCache
222
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("opening 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() or WorkingTree.open().",
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),
254
# update the whole cache up front and write to disk if anything changed;
255
# 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)
263
# is this scan needed ? it makes things kinda slow.
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
279
def is_control_filename(self, filename):
280
"""True if filename is the name of a control file in this tree."""
282
self.bzrdir.transport.relpath(self.abspath(filename))
284
except errors.PathNotChild:
288
def open(path=None, _unsupported=False):
289
"""Open an existing working tree at path.
293
path = os.path.getcwdu()
294
control = bzrdir.BzrDir.open(path, _unsupported)
295
return control.open_workingtree(_unsupported)
298
def open_containing(path=None):
299
"""Open an existing working tree which has its root about path.
301
This probes for a working tree at path and searches upwards from there.
303
Basically we keep looking up until we find the control directory or
304
run into /. If there isn't one, raises NotBranchError.
305
TODO: give this a new exception.
306
If there is one, it is returned, along with the unused portion of path.
310
control, relpath = bzrdir.BzrDir.open_containing(path)
311
return control.open_workingtree(), relpath
314
def open_downlevel(path=None):
315
"""Open an unsupported working tree.
317
Only intended for advanced situations like upgrading part of a bzrdir.
319
return WorkingTree.open(path, _unsupported=True)
322
"""Iterate through file_ids for this tree.
324
file_ids are in a WorkingTree if they are in the working inventory
325
and the working file exists.
327
inv = self._inventory
328
for path, ie in inv.iter_entries():
329
if bzrlib.osutils.lexists(self.abspath(path)):
333
return "<%s of %s>" % (self.__class__.__name__,
334
getattr(self, 'basedir', None))
336
def abspath(self, filename):
337
return pathjoin(self.basedir, filename)
339
def basis_tree(self):
340
"""Return RevisionTree for the current last revision."""
341
revision_id = self.last_revision()
342
if revision_id is not None:
344
xml = self.read_basis_inventory(revision_id)
345
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
346
return bzrlib.tree.RevisionTree(self.branch.repository, inv,
350
return self.branch.repository.revision_tree(revision_id)
353
@deprecated_method(zero_eight)
354
def create(branch, directory):
355
"""Create a workingtree for branch at directory.
357
If existing_directory already exists it must have a .bzr directory.
358
If it does not exist, it will be created.
360
This returns a new WorkingTree object for the new checkout.
362
TODO FIXME RBC 20060124 when we have checkout formats in place this
363
should accept an optional revisionid to checkout [and reject this if
364
checking out into the same dir as a pre-checkout-aware branch format.]
366
XXX: When BzrDir is present, these should be created through that
369
warn('delete WorkingTree.create', stacklevel=3)
370
transport = get_transport(directory)
371
if branch.bzrdir.root_transport.base == transport.base:
373
return branch.bzrdir.create_workingtree()
374
# different directory,
375
# create a branch reference
376
# and now a working tree.
377
raise NotImplementedError
380
@deprecated_method(zero_eight)
381
def create_standalone(directory):
382
"""Create a checkout and a branch and a repo at directory.
384
Directory must exist and be empty.
386
please use BzrDir.create_standalone_workingtree
388
return bzrdir.BzrDir.create_standalone_workingtree(directory)
390
def relpath(self, abs):
391
"""Return the local path portion from a given absolute path."""
392
return relpath(self.basedir, abs)
394
def has_filename(self, filename):
395
return bzrlib.osutils.lexists(self.abspath(filename))
397
def get_file(self, file_id):
398
return self.get_file_byname(self.id2path(file_id))
400
def get_file_byname(self, filename):
401
return file(self.abspath(filename), 'rb')
403
def get_root_id(self):
404
"""Return the id of this trees root"""
405
inv = self.read_working_inventory()
406
return inv.root.file_id
408
def _get_store_filename(self, file_id):
409
## XXX: badly named; this is not in the store at all
410
return self.abspath(self.id2path(file_id))
413
def clone(self, to_bzrdir, revision_id=None, basis=None):
414
"""Duplicate this working tree into to_bzr, including all state.
416
Specifically modified files are kept as modified, but
417
ignored and unknown files are discarded.
419
If you want to make a new line of development, see bzrdir.sprout()
422
If not None, the cloned tree will have its last revision set to
423
revision, and and difference between the source trees last revision
424
and this one merged in.
427
If not None, a closer copy of a tree which may have some files in
428
common, and which file content should be preferentially copied from.
430
# assumes the target bzr dir format is compatible.
431
result = self._format.initialize(to_bzrdir)
432
self.copy_content_into(result, revision_id)
436
def copy_content_into(self, tree, revision_id=None):
437
"""Copy the current content and user files of this tree into tree."""
438
if revision_id is None:
439
transform_tree(tree, self)
441
# TODO now merge from tree.last_revision to revision
442
transform_tree(tree, self)
443
tree.set_last_revision(revision_id)
446
def commit(self, *args, **kwargs):
447
from bzrlib.commit import Commit
448
# args for wt.commit start at message from the Commit.commit method,
449
# but with branch a kwarg now, passing in args as is results in the
450
#message being used for the branch
451
args = (DEPRECATED_PARAMETER, ) + args
452
Commit().commit(working_tree=self, *args, **kwargs)
453
self._set_inventory(self.read_working_inventory())
455
def id2abspath(self, file_id):
456
return self.abspath(self.id2path(file_id))
458
def has_id(self, file_id):
459
# files that have been deleted are excluded
460
inv = self._inventory
461
if not inv.has_id(file_id):
463
path = inv.id2path(file_id)
464
return bzrlib.osutils.lexists(self.abspath(path))
466
def has_or_had_id(self, file_id):
467
if file_id == self.inventory.root.file_id:
469
return self.inventory.has_id(file_id)
471
__contains__ = has_id
473
def get_file_size(self, file_id):
474
return os.path.getsize(self.id2abspath(file_id))
477
def get_file_sha1(self, file_id):
478
path = self._inventory.id2path(file_id)
479
return self._hashcache.get_sha1(path)
481
def is_executable(self, file_id):
483
return self._inventory[file_id].executable
485
path = self._inventory.id2path(file_id)
486
mode = os.lstat(self.abspath(path)).st_mode
487
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
490
def add(self, files, ids=None):
491
"""Make files versioned.
493
Note that the command line normally calls smart_add instead,
494
which can automatically recurse.
496
This adds the files to the inventory, so that they will be
497
recorded by the next commit.
500
List of paths to add, relative to the base of the tree.
503
If set, use these instead of automatically generated ids.
504
Must be the same length as the list of files, but may
505
contain None for ids that are to be autogenerated.
507
TODO: Perhaps have an option to add the ids even if the files do
510
TODO: Perhaps callback with the ids and paths as they're added.
512
# TODO: Re-adding a file that is removed in the working copy
513
# should probably put it back with the previous ID.
514
if isinstance(files, basestring):
515
assert(ids is None or isinstance(ids, basestring))
521
ids = [None] * len(files)
523
assert(len(ids) == len(files))
525
inv = self.read_working_inventory()
526
for f,file_id in zip(files, ids):
527
if self.is_control_filename(f):
528
raise BzrError("cannot add control file %s" % quotefn(f))
533
raise BzrError("cannot add top-level %r" % f)
535
fullpath = normpath(self.abspath(f))
538
kind = file_kind(fullpath)
540
if e.errno == errno.ENOENT:
541
raise NoSuchFile(fullpath)
542
# maybe something better?
543
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
545
if not InventoryEntry.versionable_kind(kind):
546
raise BzrError('cannot add: not a versionable file ('
547
'i.e. regular file, symlink or directory): %s' % quotefn(f))
550
file_id = gen_file_id(f)
551
inv.add_path(f, kind=kind, file_id=file_id)
553
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
554
self._write_inventory(inv)
557
def add_pending_merge(self, *revision_ids):
558
# TODO: Perhaps should check at this point that the
559
# history of the revision is actually present?
560
p = self.pending_merges()
562
for rev_id in revision_ids:
568
self.set_pending_merges(p)
571
def pending_merges(self):
572
"""Return a list of pending merges.
574
These are revisions that have been merged into the working
575
directory but not yet committed.
578
merges_file = self._control_files.get_utf8('pending-merges')
580
if e.errno != errno.ENOENT:
584
for l in merges_file.readlines():
585
p.append(l.rstrip('\n'))
589
def set_pending_merges(self, rev_list):
590
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
592
def get_symlink_target(self, file_id):
593
return os.readlink(self.id2abspath(file_id))
595
def file_class(self, filename):
596
if self.path2id(filename):
598
elif self.is_ignored(filename):
603
def list_files(self):
604
"""Recursively list all files as (path, class, kind, id).
606
Lists, but does not descend into unversioned directories.
608
This does not include files that have been deleted in this
611
Skips the control directory.
613
inv = self._inventory
615
def descend(from_dir_relpath, from_dir_id, dp):
619
## TODO: If we find a subdirectory with its own .bzr
620
## directory, then that is a separate tree and we
621
## should exclude it.
623
# the bzrdir for this tree
624
if self.bzrdir.transport.base.endswith(f + '/'):
628
fp = appendpath(from_dir_relpath, f)
631
fap = appendpath(dp, f)
633
f_ie = inv.get_child(from_dir_id, f)
636
elif self.is_ignored(fp):
645
raise BzrCheckError("file %r entered as kind %r id %r, "
647
% (fap, f_ie.kind, f_ie.file_id, fk))
649
# make a last minute entry
653
if fk == 'directory':
654
entry = TreeDirectory()
657
elif fk == 'symlink':
662
yield fp, c, fk, (f_ie and f_ie.file_id), entry
664
if fk != 'directory':
668
# don't descend unversioned directories
671
for ff in descend(fp, f_ie.file_id, fap):
674
for f in descend(u'', inv.root.file_id, self.basedir):
678
def move(self, from_paths, to_name):
681
to_name must exist in the inventory.
683
If to_name exists and is a directory, the files are moved into
684
it, keeping their old names.
686
Note that to_name is only the last component of the new name;
687
this doesn't change the directory.
689
This returns a list of (from_path, to_path) pairs for each
693
## TODO: Option to move IDs only
694
assert not isinstance(from_paths, basestring)
696
to_abs = self.abspath(to_name)
697
if not isdir(to_abs):
698
raise BzrError("destination %r is not a directory" % to_abs)
699
if not self.has_filename(to_name):
700
raise BzrError("destination %r not in working directory" % to_abs)
701
to_dir_id = inv.path2id(to_name)
702
if to_dir_id == None and to_name != '':
703
raise BzrError("destination %r is not a versioned directory" % to_name)
704
to_dir_ie = inv[to_dir_id]
705
if to_dir_ie.kind not in ('directory', 'root_directory'):
706
raise BzrError("destination %r is not a directory" % to_abs)
708
to_idpath = inv.get_idpath(to_dir_id)
711
if not self.has_filename(f):
712
raise BzrError("%r does not exist in working tree" % f)
713
f_id = inv.path2id(f)
715
raise BzrError("%r is not versioned" % f)
716
name_tail = splitpath(f)[-1]
717
dest_path = appendpath(to_name, name_tail)
718
if self.has_filename(dest_path):
719
raise BzrError("destination %r already exists" % dest_path)
720
if f_id in to_idpath:
721
raise BzrError("can't move %r to a subdirectory of itself" % f)
723
# OK, so there's a race here, it's possible that someone will
724
# create a file in this interval and then the rename might be
725
# left half-done. But we should have caught most problems.
726
orig_inv = deepcopy(self.inventory)
729
name_tail = splitpath(f)[-1]
730
dest_path = appendpath(to_name, name_tail)
731
result.append((f, dest_path))
732
inv.rename(inv.path2id(f), to_dir_id, name_tail)
734
rename(self.abspath(f), self.abspath(dest_path))
736
raise BzrError("failed to rename %r to %r: %s" %
737
(f, dest_path, e[1]),
738
["rename rolled back"])
740
# restore the inventory on error
741
self._set_inventory(orig_inv)
743
self._write_inventory(inv)
747
def rename_one(self, from_rel, to_rel):
750
This can change the directory or the filename or both.
753
if not self.has_filename(from_rel):
754
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
755
if self.has_filename(to_rel):
756
raise BzrError("can't rename: new working file %r already exists" % to_rel)
758
file_id = inv.path2id(from_rel)
760
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
763
from_parent = entry.parent_id
764
from_name = entry.name
766
if inv.path2id(to_rel):
767
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
769
to_dir, to_tail = os.path.split(to_rel)
770
to_dir_id = inv.path2id(to_dir)
771
if to_dir_id == None and to_dir != '':
772
raise BzrError("can't determine destination directory id for %r" % to_dir)
774
mutter("rename_one:")
775
mutter(" file_id {%s}" % file_id)
776
mutter(" from_rel %r" % from_rel)
777
mutter(" to_rel %r" % to_rel)
778
mutter(" to_dir %r" % to_dir)
779
mutter(" to_dir_id {%s}" % to_dir_id)
781
inv.rename(file_id, to_dir_id, to_tail)
783
from_abs = self.abspath(from_rel)
784
to_abs = self.abspath(to_rel)
786
rename(from_abs, to_abs)
788
inv.rename(file_id, from_parent, from_name)
789
raise BzrError("failed to rename %r to %r: %s"
790
% (from_abs, to_abs, e[1]),
791
["rename rolled back"])
792
self._write_inventory(inv)
796
"""Return all unknown files.
798
These are files in the working directory that are not versioned or
799
control files or ignored.
801
>>> from bzrlib.bzrdir import ScratchDir
802
>>> d = ScratchDir(files=['foo', 'foo~'])
803
>>> b = d.open_branch()
804
>>> tree = d.open_workingtree()
805
>>> map(str, tree.unknowns())
808
>>> list(b.unknowns())
810
>>> tree.remove('foo')
811
>>> list(b.unknowns())
814
for subp in self.extras():
815
if not self.is_ignored(subp):
818
def iter_conflicts(self):
820
for path in (s[0] for s in self.list_files()):
821
stem = get_conflicted_stem(path)
824
if stem not in conflicted:
829
def pull(self, source, overwrite=False, stop_revision=None):
832
old_revision_history = self.branch.revision_history()
833
count = self.branch.pull(source, overwrite, stop_revision)
834
new_revision_history = self.branch.revision_history()
835
if new_revision_history != old_revision_history:
836
if len(old_revision_history):
837
other_revision = old_revision_history[-1]
839
other_revision = None
840
repository = self.branch.repository
841
merge_inner(self.branch,
843
repository.revision_tree(other_revision),
845
self.set_last_revision(self.branch.last_revision())
851
"""Yield all unknown files in this WorkingTree.
853
If there are any unknown directories then only the directory is
854
returned, not all its children. But if there are unknown files
855
under a versioned subdirectory, they are returned.
857
Currently returned depth-first, sorted by name within directories.
859
## TODO: Work from given directory downwards
860
for path, dir_entry in self.inventory.directories():
861
mutter("search for unknowns in %r", path)
862
dirabs = self.abspath(path)
863
if not isdir(dirabs):
864
# e.g. directory deleted
868
for subf in os.listdir(dirabs):
870
and (subf not in dir_entry.children)):
875
subp = appendpath(path, subf)
879
def ignored_files(self):
880
"""Yield list of PATH, IGNORE_PATTERN"""
881
for subp in self.extras():
882
pat = self.is_ignored(subp)
887
def get_ignore_list(self):
888
"""Return list of ignore patterns.
890
Cached in the Tree object after the first call.
892
if hasattr(self, '_ignorelist'):
893
return self._ignorelist
895
l = bzrlib.DEFAULT_IGNORE[:]
896
if self.has_filename(bzrlib.IGNORE_FILENAME):
897
f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
898
l.extend([line.rstrip("\n\r") for line in f.readlines()])
903
def is_ignored(self, filename):
904
r"""Check whether the filename matches an ignore pattern.
906
Patterns containing '/' or '\' need to match the whole path;
907
others match against only the last component.
909
If the file is ignored, returns the pattern which caused it to
910
be ignored, otherwise None. So this can simply be used as a
911
boolean if desired."""
913
# TODO: Use '**' to match directories, and other extended
914
# globbing stuff from cvs/rsync.
916
# XXX: fnmatch is actually not quite what we want: it's only
917
# approximately the same as real Unix fnmatch, and doesn't
918
# treat dotfiles correctly and allows * to match /.
919
# Eventually it should be replaced with something more
922
for pat in self.get_ignore_list():
923
if '/' in pat or '\\' in pat:
925
# as a special case, you can put ./ at the start of a
926
# pattern; this is good to match in the top-level
929
if (pat[:2] == './') or (pat[:2] == '.\\'):
933
if fnmatch.fnmatchcase(filename, newpat):
936
if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
941
def kind(self, file_id):
942
return file_kind(self.id2abspath(file_id))
945
def last_revision(self):
946
"""Return the last revision id of this working tree.
948
In early branch formats this was == the branch last_revision,
949
but that cannot be relied upon - for working tree operations,
950
always use tree.last_revision().
952
return self.branch.last_revision()
955
"""See Branch.lock_read, and WorkingTree.unlock."""
956
self.branch.lock_read()
958
return self._control_files.lock_read()
963
def lock_write(self):
964
"""See Branch.lock_write, and WorkingTree.unlock."""
965
self.branch.lock_write()
967
return self._control_files.lock_write()
972
def _basis_inventory_name(self, revision_id):
973
return 'basis-inventory.%s' % revision_id
976
def set_last_revision(self, new_revision, old_revision=None):
977
"""Change the last revision in the working tree."""
978
self._remove_old_basis(old_revision)
979
if self._change_last_revision(new_revision):
980
self._cache_basis_inventory(new_revision)
982
def _change_last_revision(self, new_revision):
983
"""Template method part of set_last_revision to perform the change."""
984
if new_revision is None:
985
self.branch.set_revision_history([])
987
# current format is locked in with the branch
988
revision_history = self.branch.revision_history()
990
position = revision_history.index(new_revision)
992
raise errors.NoSuchRevision(self.branch, new_revision)
993
self.branch.set_revision_history(revision_history[:position + 1])
996
def _cache_basis_inventory(self, new_revision):
997
"""Cache new_revision as the basis inventory."""
999
xml = self.branch.repository.get_inventory_xml(new_revision)
1000
path = self._basis_inventory_name(new_revision)
1001
self._control_files.put_utf8(path, xml)
1002
except WeaveRevisionNotPresent:
1005
def _remove_old_basis(self, old_revision):
1006
"""Remove the old basis inventory 'old_revision'."""
1007
if old_revision is not None:
1009
path = self._basis_inventory_name(old_revision)
1010
path = self._control_files._escape(path)
1011
self._control_files._transport.delete(path)
1015
def read_basis_inventory(self, revision_id):
1016
"""Read the cached basis inventory."""
1017
path = self._basis_inventory_name(revision_id)
1018
return self._control_files.get_utf8(path).read()
1021
def read_working_inventory(self):
1022
"""Read the working inventory."""
1023
# ElementTree does its own conversion from UTF-8, so open in
1025
result = bzrlib.xml5.serializer_v5.read_inventory(
1026
self._control_files.get('inventory'))
1027
self._set_inventory(result)
1031
def remove(self, files, verbose=False):
1032
"""Remove nominated files from the working inventory..
1034
This does not remove their text. This does not run on XXX on what? RBC
1036
TODO: Refuse to remove modified files unless --force is given?
1038
TODO: Do something useful with directories.
1040
TODO: Should this remove the text or not? Tough call; not
1041
removing may be useful and the user can just use use rm, and
1042
is the opposite of add. Removing it is consistent with most
1043
other tools. Maybe an option.
1045
## TODO: Normalize names
1046
## TODO: Remove nested loops; better scalability
1047
if isinstance(files, basestring):
1050
inv = self.inventory
1052
# do this before any modifications
1054
fid = inv.path2id(f)
1056
# TODO: Perhaps make this just a warning, and continue?
1057
# This tends to happen when
1058
raise NotVersionedError(path=f)
1059
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1061
# having remove it, it must be either ignored or unknown
1062
if self.is_ignored(f):
1066
show_status(new_status, inv[fid].kind, quotefn(f))
1069
self._write_inventory(inv)
1072
def revert(self, filenames, old_tree=None, backups=True):
1073
from bzrlib.merge import merge_inner
1074
if old_tree is None:
1075
old_tree = self.basis_tree()
1076
merge_inner(self.branch, old_tree,
1077
self, ignore_zero=True,
1078
backup_files=backups,
1079
interesting_files=filenames,
1081
if not len(filenames):
1082
self.set_pending_merges([])
1085
def set_inventory(self, new_inventory_list):
1086
from bzrlib.inventory import (Inventory,
1091
inv = Inventory(self.get_root_id())
1092
for path, file_id, parent, kind in new_inventory_list:
1093
name = os.path.basename(path)
1096
# fixme, there should be a factory function inv,add_??
1097
if kind == 'directory':
1098
inv.add(InventoryDirectory(file_id, name, parent))
1099
elif kind == 'file':
1100
inv.add(InventoryFile(file_id, name, parent))
1101
elif kind == 'symlink':
1102
inv.add(InventoryLink(file_id, name, parent))
1104
raise BzrError("unknown kind %r" % kind)
1105
self._write_inventory(inv)
1108
def set_root_id(self, file_id):
1109
"""Set the root id for this tree."""
1110
inv = self.read_working_inventory()
1111
orig_root_id = inv.root.file_id
1112
del inv._byid[inv.root.file_id]
1113
inv.root.file_id = file_id
1114
inv._byid[inv.root.file_id] = inv.root
1117
if entry.parent_id == orig_root_id:
1118
entry.parent_id = inv.root.file_id
1119
self._write_inventory(inv)
1122
"""See Branch.unlock.
1124
WorkingTree locking just uses the Branch locking facilities.
1125
This is current because all working trees have an embedded branch
1126
within them. IF in the future, we were to make branch data shareable
1127
between multiple working trees, i.e. via shared storage, then we
1128
would probably want to lock both the local tree, and the branch.
1130
# FIXME: We want to write out the hashcache only when the last lock on
1131
# this working copy is released. Peeking at the lock count is a bit
1132
# of a nasty hack; probably it's better to have a transaction object,
1133
# which can do some finalization when it's either successfully or
1134
# unsuccessfully completed. (Denys's original patch did that.)
1135
# RBC 20060206 hookinhg into transaction will couple lock and transaction
1136
# wrongly. Hookinh into unllock on the control files object is fine though.
1138
# TODO: split this per format so there is no ugly if block
1139
if self._hashcache.needs_write and (
1140
# dedicated lock files
1141
self._control_files._lock_count==1 or
1143
(self._control_files is self.branch.control_files and
1144
self._control_files._lock_count==3)):
1145
self._hashcache.write()
1146
# reverse order of locking.
1147
result = self._control_files.unlock()
1149
self.branch.unlock()
1155
self.branch.lock_read()
1157
if self.last_revision() == self.branch.last_revision():
1159
basis = self.basis_tree()
1160
to_tree = self.branch.basis_tree()
1161
result = merge_inner(self.branch,
1165
self.set_last_revision(self.branch.last_revision())
1168
self.branch.unlock()
1171
def _write_inventory(self, inv):
1172
"""Write inventory as the current inventory."""
1174
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1176
self._control_files.put('inventory', sio)
1177
self._set_inventory(inv)
1178
mutter('wrote working inventory')
1181
class WorkingTree3(WorkingTree):
1182
"""This is the Format 3 working tree.
1184
This differs from the base WorkingTree by:
1185
- having its own file lock
1186
- having its own last-revision property.
1190
def last_revision(self):
1191
"""See WorkingTree.last_revision."""
1193
return self._control_files.get_utf8('last-revision').read()
1197
def _change_last_revision(self, revision_id):
1198
"""See WorkingTree._change_last_revision."""
1199
if revision_id is None or revision_id == NULL_REVISION:
1201
self._control_files._transport.delete('last-revision')
1202
except errors.NoSuchFile:
1207
self.branch.revision_history().index(revision_id)
1209
raise errors.NoSuchRevision(self.branch, revision_id)
1210
self._control_files.put_utf8('last-revision', revision_id)
1214
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1215
def get_conflicted_stem(path):
1216
for suffix in CONFLICT_SUFFIXES:
1217
if path.endswith(suffix):
1218
return path[:-len(suffix)]
1220
@deprecated_function(zero_eight)
1221
def is_control_file(filename):
1222
"""See WorkingTree.is_control_filename(filename)."""
1223
## FIXME: better check
1224
filename = normpath(filename)
1225
while filename != '':
1226
head, tail = os.path.split(filename)
1227
## mutter('check %r for control file' % ((head, tail),))
1230
if filename == head:
1236
class WorkingTreeFormat(object):
1237
"""An encapsulation of the initialization and open routines for a format.
1239
Formats provide three things:
1240
* An initialization routine,
1244
Formats are placed in an dict by their format string for reference
1245
during workingtree opening. Its not required that these be instances, they
1246
can be classes themselves with class methods - it simply depends on
1247
whether state is needed for a given format or not.
1249
Once a format is deprecated, just deprecate the initialize and open
1250
methods on the format class. Do not deprecate the object, as the
1251
object will be created every time regardless.
1254
_default_format = None
1255
"""The default format used for new trees."""
1258
"""The known formats."""
1261
def find_format(klass, a_bzrdir):
1262
"""Return the format for the working tree object in a_bzrdir."""
1264
transport = a_bzrdir.get_workingtree_transport(None)
1265
format_string = transport.get("format").read()
1266
return klass._formats[format_string]
1268
raise errors.NoWorkingTree(base=transport.base)
1270
raise errors.UnknownFormatError(format_string)
1273
def get_default_format(klass):
1274
"""Return the current default format."""
1275
return klass._default_format
1277
def get_format_string(self):
1278
"""Return the ASCII format string that identifies this format."""
1279
raise NotImplementedError(self.get_format_string)
1281
def is_supported(self):
1282
"""Is this format supported?
1284
Supported formats can be initialized and opened.
1285
Unsupported formats may not support initialization or committing or
1286
some other features depending on the reason for not being supported.
1291
def register_format(klass, format):
1292
klass._formats[format.get_format_string()] = format
1295
def set_default_format(klass, format):
1296
klass._default_format = format
1299
def unregister_format(klass, format):
1300
assert klass._formats[format.get_format_string()] is format
1301
del klass._formats[format.get_format_string()]
1305
class WorkingTreeFormat2(WorkingTreeFormat):
1306
"""The second working tree format.
1308
This format modified the hash cache from the format 1 hash cache.
1311
def initialize(self, a_bzrdir, revision_id=None):
1312
"""See WorkingTreeFormat.initialize()."""
1313
if not isinstance(a_bzrdir.transport, LocalTransport):
1314
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1315
branch = a_bzrdir.open_branch()
1316
if revision_id is not None:
1319
revision_history = branch.revision_history()
1321
position = revision_history.index(revision_id)
1323
raise errors.NoSuchRevision(branch, revision_id)
1324
branch.set_revision_history(revision_history[:position + 1])
1327
revision = branch.last_revision()
1328
basis_tree = branch.repository.revision_tree(revision)
1329
inv = basis_tree.inventory
1330
wt = WorkingTree(a_bzrdir.root_transport.base,
1336
wt._write_inventory(inv)
1337
wt.set_root_id(inv.root.file_id)
1338
wt.set_last_revision(revision)
1339
wt.set_pending_merges([])
1344
super(WorkingTreeFormat2, self).__init__()
1345
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1347
def open(self, a_bzrdir, _found=False):
1348
"""Return the WorkingTree object for a_bzrdir
1350
_found is a private parameter, do not use it. It is used to indicate
1351
if format probing has already been done.
1354
# we are being called directly and must probe.
1355
raise NotImplementedError
1356
if not isinstance(a_bzrdir.transport, LocalTransport):
1357
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1358
return WorkingTree(a_bzrdir.root_transport.base,
1364
class WorkingTreeFormat3(WorkingTreeFormat):
1365
"""The second working tree format updated to record a format marker.
1367
This format modified the hash cache from the format 1 hash cache.
1370
def get_format_string(self):
1371
"""See WorkingTreeFormat.get_format_string()."""
1372
return "Bazaar-NG Working Tree format 3"
1374
def initialize(self, a_bzrdir, revision_id=None):
1375
"""See WorkingTreeFormat.initialize().
1377
revision_id allows creating a working tree at a differnet
1378
revision than the branch is at.
1380
if not isinstance(a_bzrdir.transport, LocalTransport):
1381
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1382
transport = a_bzrdir.get_workingtree_transport(self)
1383
control_files = LockableFiles(transport, 'lock')
1384
control_files.put_utf8('format', self.get_format_string())
1385
branch = a_bzrdir.open_branch()
1386
if revision_id is None:
1387
revision_id = branch.last_revision()
1388
new_basis_tree = branch.repository.revision_tree(revision_id)
1389
inv = new_basis_tree.inventory
1390
wt = WorkingTree3(a_bzrdir.root_transport.base,
1396
wt._write_inventory(inv)
1397
wt.set_root_id(inv.root.file_id)
1398
wt.set_last_revision(revision_id)
1399
wt.set_pending_merges([])
1404
super(WorkingTreeFormat3, self).__init__()
1405
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1407
def open(self, a_bzrdir, _found=False):
1408
"""Return the WorkingTree object for a_bzrdir
1410
_found is a private parameter, do not use it. It is used to indicate
1411
if format probing has already been done.
1414
# we are being called directly and must probe.
1415
raise NotImplementedError
1416
if not isinstance(a_bzrdir.transport, LocalTransport):
1417
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1418
return WorkingTree3(a_bzrdir.root_transport.base,
1424
return self.get_format_string()
1427
# formats which have no format string are not discoverable
1428
# and not independently creatable, so are not registered.
1429
__default_format = WorkingTreeFormat3()
1430
WorkingTreeFormat.register_format(__default_format)
1431
WorkingTreeFormat.set_default_format(__default_format)
1432
_legacy_formats = [WorkingTreeFormat2(),
1436
class WorkingTreeTestProviderAdapter(object):
1437
"""A tool to generate a suite testing multiple workingtree formats at once.
1439
This is done by copying the test once for each transport and injecting
1440
the transport_server, transport_readonly_server, and workingtree_format
1441
classes into each copy. Each copy is also given a new id() to make it
1445
def __init__(self, transport_server, transport_readonly_server, formats):
1446
self._transport_server = transport_server
1447
self._transport_readonly_server = transport_readonly_server
1448
self._formats = formats
1450
def adapt(self, test):
1451
from bzrlib.tests import TestSuite
1452
result = TestSuite()
1453
for workingtree_format, bzrdir_format in self._formats:
1454
new_test = deepcopy(test)
1455
new_test.transport_server = self._transport_server
1456
new_test.transport_readonly_server = self._transport_readonly_server
1457
new_test.bzrdir_format = bzrdir_format
1458
new_test.workingtree_format = workingtree_format
1459
def make_new_test_id():
1460
new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1461
return lambda: new_id
1462
new_test.id = make_new_test_id()
1463
result.addTest(new_test)