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,
55
import bzrlib.bzrdir as bzrdir
56
from bzrlib.decorators import needs_read_lock, needs_write_lock
57
import bzrlib.errors as errors
58
from bzrlib.errors import (BzrCheckError,
61
WeaveRevisionNotPresent,
65
from bzrlib.inventory import InventoryEntry
66
from bzrlib.lockable_files import LockableFiles
67
from bzrlib.osutils import (appendpath,
82
from bzrlib.symbol_versioning import *
83
from bzrlib.textui import show_status
85
from bzrlib.trace import mutter
86
from bzrlib.transport import get_transport
87
from bzrlib.transport.local import LocalTransport
91
def gen_file_id(name):
92
"""Return new file id.
94
This should probably generate proper UUIDs, but for the moment we
95
cope with just randomness because running uuidgen every time is
98
from binascii import hexlify
102
idx = name.rfind('/')
104
name = name[idx+1 : ]
105
idx = name.rfind('\\')
107
name = name[idx+1 : ]
109
# make it not a hidden file
110
name = name.lstrip('.')
112
# remove any wierd characters; we don't escape them but rather
114
name = re.sub(r'[^\w.]', '', name)
116
s = hexlify(rand_bytes(8))
117
return '-'.join((name, compact_date(time()), s))
121
"""Return a new tree-root file id."""
122
return gen_file_id('TREE_ROOT')
125
class TreeEntry(object):
126
"""An entry that implements the minium interface used by commands.
128
This needs further inspection, it may be better to have
129
InventoryEntries without ids - though that seems wrong. For now,
130
this is a parallel hierarchy to InventoryEntry, and needs to become
131
one of several things: decorates to that hierarchy, children of, or
133
Another note is that these objects are currently only used when there is
134
no InventoryEntry available - i.e. for unversioned objects.
135
Perhaps they should be UnversionedEntry et al. ? - RBC 20051003
138
def __eq__(self, other):
139
# yes, this us ugly, TODO: best practice __eq__ style.
140
return (isinstance(other, TreeEntry)
141
and other.__class__ == self.__class__)
143
def kind_character(self):
147
class TreeDirectory(TreeEntry):
148
"""See TreeEntry. This is a directory in a working tree."""
150
def __eq__(self, other):
151
return (isinstance(other, TreeDirectory)
152
and other.__class__ == self.__class__)
154
def kind_character(self):
158
class TreeFile(TreeEntry):
159
"""See TreeEntry. This is a regular file in a working tree."""
161
def __eq__(self, other):
162
return (isinstance(other, TreeFile)
163
and other.__class__ == self.__class__)
165
def kind_character(self):
169
class TreeLink(TreeEntry):
170
"""See TreeEntry. This is a symlink in a working tree."""
172
def __eq__(self, other):
173
return (isinstance(other, TreeLink)
174
and other.__class__ == self.__class__)
176
def kind_character(self):
180
class WorkingTree(bzrlib.tree.Tree):
181
"""Working copy tree.
183
The inventory is held in the `Branch` working-inventory, and the
184
files are in a directory on disk.
186
It is possible for a `WorkingTree` to have a filename which is
187
not listed in the Inventory and vice versa.
190
def __init__(self, basedir='.', branch=None, _inventory=None, _control_files=None, _internal=False):
191
"""Construct a WorkingTree for basedir.
193
If the branch is not supplied, it is opened automatically.
194
If the branch is supplied, it must be the branch for this basedir.
195
(branch.base is not cross checked, because for remote branches that
196
would be meaningless).
199
# created via open etc.
200
wt = WorkingTree.open(basedir, branch)
201
self.branch = wt.branch
202
self.basedir = wt.basedir
203
self._control_files = wt._control_files
204
self._hashcache = wt._hashcache
205
self._set_inventory(wt._inventory)
206
from bzrlib.hashcache import HashCache
207
from bzrlib.trace import note, mutter
208
assert isinstance(basedir, basestring), \
209
"base directory %r is not a string" % basedir
210
basedir = safe_unicode(basedir)
211
mutter("openeing working tree %r", basedir)
213
branch = Branch.open(basedir)
214
assert isinstance(branch, Branch), \
215
"branch %r is not a Branch" % branch
217
self.basedir = realpath(basedir)
218
# if branch is at our basedir and is a format 6 or less
219
if (isinstance(self.branch._format,
221
# might be able to share control object
222
and self.branch.base.split('/')[-2] == self.basedir.split('/')[-1]):
223
self._control_files = self.branch.control_files
224
elif _control_files is not None:
225
assert False, "not done yet"
226
# self._control_files = _control_files
228
# FIXME old format use the bzrdir control files.
229
self._control_files = LockableFiles(
230
get_transport(self.basedir).clone(bzrlib.BZRDIR), 'branch-lock')
232
# update the whole cache up front and write to disk if anything changed;
233
# in the future we might want to do this more selectively
234
# two possible ways offer themselves : in self._unlock, write the cache
235
# if needed, or, when the cache sees a change, append it to the hash
236
# cache file, and have the parser take the most recent entry for a
238
hc = self._hashcache = HashCache(basedir)
246
if _inventory is None:
247
self._set_inventory(self.read_working_inventory())
249
self._set_inventory(_inventory)
251
def _set_inventory(self, inv):
252
self._inventory = inv
253
self.path2id = self._inventory.path2id
256
def open(path=None, _unsupported=False):
257
"""Open an existing working tree at path.
261
path = os.path.getcwdu()
262
control = bzrdir.BzrDir.open(path, _unsupported)
263
return control.open_workingtree(_unsupported)
266
def open_containing(path=None):
267
"""Open an existing working tree which has its root about path.
269
This probes for a working tree at path and searches upwards from there.
271
Basically we keep looking up until we find the control directory or
272
run into /. If there isn't one, raises NotBranchError.
273
TODO: give this a new exception.
274
If there is one, it is returned, along with the unused portion of path.
278
control, relpath = bzrdir.BzrDir.open_containing(path)
279
return control.open_workingtree(), relpath
282
def open_downlevel(path=None):
283
"""Open an unsupported working tree.
285
Only intended for advanced situations like upgrading part of a bzrdir.
287
return WorkingTree.open(path, _unsupported=True)
290
"""Iterate through file_ids for this tree.
292
file_ids are in a WorkingTree if they are in the working inventory
293
and the working file exists.
295
inv = self._inventory
296
for path, ie in inv.iter_entries():
297
if bzrlib.osutils.lexists(self.abspath(path)):
301
return "<%s of %s>" % (self.__class__.__name__,
302
getattr(self, 'basedir', None))
304
def abspath(self, filename):
305
return pathjoin(self.basedir, filename)
307
def basis_tree(self):
308
"""Return RevisionTree for the current last revision."""
309
revision_id = self.last_revision()
310
if revision_id is not None:
312
xml = self.read_basis_inventory(revision_id)
313
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
314
return bzrlib.tree.RevisionTree(self.branch.repository, inv,
318
return self.branch.repository.revision_tree(revision_id)
321
@deprecated_method(zero_eight)
322
def create(branch, directory):
323
"""Create a workingtree for branch at directory.
325
If existing_directory already exists it must have a .bzr directory.
326
If it does not exist, it will be created.
328
This returns a new WorkingTree object for the new checkout.
330
TODO FIXME RBC 20060124 when we have checkout formats in place this
331
should accept an optional revisionid to checkout [and reject this if
332
checking out into the same dir as a pre-checkout-aware branch format.]
334
XXX: When BzrDir is present, these should be created through that
337
warn('delete WorkingTree.create', stacklevel=3)
338
transport = get_transport(directory)
339
if branch.bzrdir.transport.clone('..').base == transport.base:
341
return branch.bzrdir.create_workingtree()
342
# different directory,
343
# create a branch reference
344
# and now a working tree.
345
raise NotImplementedError
348
@deprecated_method(zero_eight)
349
def create_standalone(directory):
350
"""Create a checkout and a branch and a repo at directory.
352
Directory must exist and be empty.
354
please use BzrDir.create_standalone_workingtree
356
return bzrdir.BzrDir.create_standalone_workingtree(directory)
358
def relpath(self, abs):
359
"""Return the local path portion from a given absolute path."""
360
return relpath(self.basedir, abs)
362
def has_filename(self, filename):
363
return bzrlib.osutils.lexists(self.abspath(filename))
365
def get_file(self, file_id):
366
return self.get_file_byname(self.id2path(file_id))
368
def get_file_byname(self, filename):
369
return file(self.abspath(filename), 'rb')
371
def get_root_id(self):
372
"""Return the id of this trees root"""
373
inv = self.read_working_inventory()
374
return inv.root.file_id
376
def _get_store_filename(self, file_id):
377
## XXX: badly named; this is not in the store at all
378
return self.abspath(self.id2path(file_id))
381
def clone(self, to_directory, revision=None):
382
"""Copy this working tree to a new directory.
384
Currently this will make a new standalone branch at to_directory,
385
but it is planned to change this to use the same branch style that this
386
current tree uses (standalone if standalone, repository if repository)
387
- so that this really is a clone. FIXME RBC 20060127 do this.
388
FIXME MORE RBC 20060127 failed to reach consensus on this in #bzr.
390
If you want a standalone branch, please use branch.clone(to_directory)
391
followed by WorkingTree.create(cloned_branch, to_directory) which is
392
the supported api to produce that.
395
If not None, the cloned tree will have its last revision set to
396
revision, and if a branch is being copied it will be informed
397
of the revision to result in.
399
to_directory -- The destination directory: Must not exist.
401
to_directory = safe_unicode(to_directory)
402
os.mkdir(to_directory)
403
# FIXME here is where the decision to clone the branch should happen.
405
revision = self.last_revision()
406
cloned_branch = self.branch.clone(to_directory, revision)
407
return WorkingTree.create(cloned_branch, to_directory)
410
def commit(self, *args, **kwargs):
411
from bzrlib.commit import Commit
412
# args for wt.commit start at message from the Commit.commit method,
413
# but with branch a kwarg now, passing in args as is results in the
414
#message being used for the branch
415
args = (DEPRECATED_PARAMETER, ) + args
416
Commit().commit(working_tree=self, *args, **kwargs)
417
self._set_inventory(self.read_working_inventory())
419
def id2abspath(self, file_id):
420
return self.abspath(self.id2path(file_id))
422
def has_id(self, file_id):
423
# files that have been deleted are excluded
424
inv = self._inventory
425
if not inv.has_id(file_id):
427
path = inv.id2path(file_id)
428
return bzrlib.osutils.lexists(self.abspath(path))
430
def has_or_had_id(self, file_id):
431
if file_id == self.inventory.root.file_id:
433
return self.inventory.has_id(file_id)
435
__contains__ = has_id
437
def get_file_size(self, file_id):
438
return os.path.getsize(self.id2abspath(file_id))
441
def get_file_sha1(self, file_id):
442
path = self._inventory.id2path(file_id)
443
return self._hashcache.get_sha1(path)
445
def is_executable(self, file_id):
447
return self._inventory[file_id].executable
449
path = self._inventory.id2path(file_id)
450
mode = os.lstat(self.abspath(path)).st_mode
451
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
454
def add(self, files, ids=None):
455
"""Make files versioned.
457
Note that the command line normally calls smart_add instead,
458
which can automatically recurse.
460
This adds the files to the inventory, so that they will be
461
recorded by the next commit.
464
List of paths to add, relative to the base of the tree.
467
If set, use these instead of automatically generated ids.
468
Must be the same length as the list of files, but may
469
contain None for ids that are to be autogenerated.
471
TODO: Perhaps have an option to add the ids even if the files do
474
TODO: Perhaps callback with the ids and paths as they're added.
476
# TODO: Re-adding a file that is removed in the working copy
477
# should probably put it back with the previous ID.
478
if isinstance(files, basestring):
479
assert(ids is None or isinstance(ids, basestring))
485
ids = [None] * len(files)
487
assert(len(ids) == len(files))
489
inv = self.read_working_inventory()
490
for f,file_id in zip(files, ids):
491
if is_control_file(f):
492
raise BzrError("cannot add control file %s" % quotefn(f))
497
raise BzrError("cannot add top-level %r" % f)
499
fullpath = normpath(self.abspath(f))
502
kind = file_kind(fullpath)
504
if e.errno == errno.ENOENT:
505
raise NoSuchFile(fullpath)
506
# maybe something better?
507
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
509
if not InventoryEntry.versionable_kind(kind):
510
raise BzrError('cannot add: not a versionable file ('
511
'i.e. regular file, symlink or directory): %s' % quotefn(f))
514
file_id = gen_file_id(f)
515
inv.add_path(f, kind=kind, file_id=file_id)
517
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
518
self._write_inventory(inv)
521
def add_pending_merge(self, *revision_ids):
522
# TODO: Perhaps should check at this point that the
523
# history of the revision is actually present?
524
p = self.pending_merges()
526
for rev_id in revision_ids:
532
self.set_pending_merges(p)
535
def pending_merges(self):
536
"""Return a list of pending merges.
538
These are revisions that have been merged into the working
539
directory but not yet committed.
542
merges_file = self._control_files.get_utf8('pending-merges')
544
if e.errno != errno.ENOENT:
548
for l in merges_file.readlines():
549
p.append(l.rstrip('\n'))
553
def set_pending_merges(self, rev_list):
554
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
556
def get_symlink_target(self, file_id):
557
return os.readlink(self.id2abspath(file_id))
559
def file_class(self, filename):
560
if self.path2id(filename):
562
elif self.is_ignored(filename):
568
def list_files(self):
569
"""Recursively list all files as (path, class, kind, id).
571
Lists, but does not descend into unversioned directories.
573
This does not include files that have been deleted in this
576
Skips the control directory.
578
inv = self._inventory
580
def descend(from_dir_relpath, from_dir_id, dp):
584
## TODO: If we find a subdirectory with its own .bzr
585
## directory, then that is a separate tree and we
586
## should exclude it.
587
if bzrlib.BZRDIR == f:
591
fp = appendpath(from_dir_relpath, f)
594
fap = appendpath(dp, f)
596
f_ie = inv.get_child(from_dir_id, f)
599
elif self.is_ignored(fp):
608
raise BzrCheckError("file %r entered as kind %r id %r, "
610
% (fap, f_ie.kind, f_ie.file_id, fk))
612
# make a last minute entry
616
if fk == 'directory':
617
entry = TreeDirectory()
620
elif fk == 'symlink':
625
yield fp, c, fk, (f_ie and f_ie.file_id), entry
627
if fk != 'directory':
631
# don't descend unversioned directories
634
for ff in descend(fp, f_ie.file_id, fap):
637
for f in descend(u'', inv.root.file_id, self.basedir):
641
def move(self, from_paths, to_name):
644
to_name must exist in the inventory.
646
If to_name exists and is a directory, the files are moved into
647
it, keeping their old names.
649
Note that to_name is only the last component of the new name;
650
this doesn't change the directory.
652
This returns a list of (from_path, to_path) pairs for each
656
## TODO: Option to move IDs only
657
assert not isinstance(from_paths, basestring)
659
to_abs = self.abspath(to_name)
660
if not isdir(to_abs):
661
raise BzrError("destination %r is not a directory" % to_abs)
662
if not self.has_filename(to_name):
663
raise BzrError("destination %r not in working directory" % to_abs)
664
to_dir_id = inv.path2id(to_name)
665
if to_dir_id == None and to_name != '':
666
raise BzrError("destination %r is not a versioned directory" % to_name)
667
to_dir_ie = inv[to_dir_id]
668
if to_dir_ie.kind not in ('directory', 'root_directory'):
669
raise BzrError("destination %r is not a directory" % to_abs)
671
to_idpath = inv.get_idpath(to_dir_id)
674
if not self.has_filename(f):
675
raise BzrError("%r does not exist in working tree" % f)
676
f_id = inv.path2id(f)
678
raise BzrError("%r is not versioned" % f)
679
name_tail = splitpath(f)[-1]
680
dest_path = appendpath(to_name, name_tail)
681
if self.has_filename(dest_path):
682
raise BzrError("destination %r already exists" % dest_path)
683
if f_id in to_idpath:
684
raise BzrError("can't move %r to a subdirectory of itself" % f)
686
# OK, so there's a race here, it's possible that someone will
687
# create a file in this interval and then the rename might be
688
# left half-done. But we should have caught most problems.
689
orig_inv = deepcopy(self.inventory)
692
name_tail = splitpath(f)[-1]
693
dest_path = appendpath(to_name, name_tail)
694
result.append((f, dest_path))
695
inv.rename(inv.path2id(f), to_dir_id, name_tail)
697
rename(self.abspath(f), self.abspath(dest_path))
699
raise BzrError("failed to rename %r to %r: %s" %
700
(f, dest_path, e[1]),
701
["rename rolled back"])
703
# restore the inventory on error
704
self._set_inventory(orig_inv)
706
self._write_inventory(inv)
710
def rename_one(self, from_rel, to_rel):
713
This can change the directory or the filename or both.
716
if not self.has_filename(from_rel):
717
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
718
if self.has_filename(to_rel):
719
raise BzrError("can't rename: new working file %r already exists" % to_rel)
721
file_id = inv.path2id(from_rel)
723
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
726
from_parent = entry.parent_id
727
from_name = entry.name
729
if inv.path2id(to_rel):
730
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
732
to_dir, to_tail = os.path.split(to_rel)
733
to_dir_id = inv.path2id(to_dir)
734
if to_dir_id == None and to_dir != '':
735
raise BzrError("can't determine destination directory id for %r" % to_dir)
737
mutter("rename_one:")
738
mutter(" file_id {%s}" % file_id)
739
mutter(" from_rel %r" % from_rel)
740
mutter(" to_rel %r" % to_rel)
741
mutter(" to_dir %r" % to_dir)
742
mutter(" to_dir_id {%s}" % to_dir_id)
744
inv.rename(file_id, to_dir_id, to_tail)
746
from_abs = self.abspath(from_rel)
747
to_abs = self.abspath(to_rel)
749
rename(from_abs, to_abs)
751
inv.rename(file_id, from_parent, from_name)
752
raise BzrError("failed to rename %r to %r: %s"
753
% (from_abs, to_abs, e[1]),
754
["rename rolled back"])
755
self._write_inventory(inv)
759
"""Return all unknown files.
761
These are files in the working directory that are not versioned or
762
control files or ignored.
764
>>> from bzrlib.bzrdir import ScratchDir
765
>>> d = ScratchDir(files=['foo', 'foo~'])
766
>>> b = d.open_branch()
767
>>> tree = WorkingTree(b.base, b)
768
>>> map(str, tree.unknowns())
771
>>> list(b.unknowns())
773
>>> tree.remove('foo')
774
>>> list(b.unknowns())
777
for subp in self.extras():
778
if not self.is_ignored(subp):
781
def iter_conflicts(self):
783
for path in (s[0] for s in self.list_files()):
784
stem = get_conflicted_stem(path)
787
if stem not in conflicted:
792
def pull(self, source, overwrite=False):
793
from bzrlib.merge import merge_inner
796
old_revision_history = self.branch.revision_history()
797
count = self.branch.pull(source, overwrite)
798
new_revision_history = self.branch.revision_history()
799
if new_revision_history != old_revision_history:
800
if len(old_revision_history):
801
other_revision = old_revision_history[-1]
803
other_revision = None
804
repository = self.branch.repository
805
merge_inner(self.branch,
807
repository.revision_tree(other_revision),
809
self.set_last_revision(self.branch.last_revision())
815
"""Yield all unknown files in this WorkingTree.
817
If there are any unknown directories then only the directory is
818
returned, not all its children. But if there are unknown files
819
under a versioned subdirectory, they are returned.
821
Currently returned depth-first, sorted by name within directories.
823
## TODO: Work from given directory downwards
824
for path, dir_entry in self.inventory.directories():
825
mutter("search for unknowns in %r", path)
826
dirabs = self.abspath(path)
827
if not isdir(dirabs):
828
# e.g. directory deleted
832
for subf in os.listdir(dirabs):
834
and (subf not in dir_entry.children)):
839
subp = appendpath(path, subf)
843
def ignored_files(self):
844
"""Yield list of PATH, IGNORE_PATTERN"""
845
for subp in self.extras():
846
pat = self.is_ignored(subp)
851
def get_ignore_list(self):
852
"""Return list of ignore patterns.
854
Cached in the Tree object after the first call.
856
if hasattr(self, '_ignorelist'):
857
return self._ignorelist
859
l = bzrlib.DEFAULT_IGNORE[:]
860
if self.has_filename(bzrlib.IGNORE_FILENAME):
861
f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
862
l.extend([line.rstrip("\n\r") for line in f.readlines()])
867
def is_ignored(self, filename):
868
r"""Check whether the filename matches an ignore pattern.
870
Patterns containing '/' or '\' need to match the whole path;
871
others match against only the last component.
873
If the file is ignored, returns the pattern which caused it to
874
be ignored, otherwise None. So this can simply be used as a
875
boolean if desired."""
877
# TODO: Use '**' to match directories, and other extended
878
# globbing stuff from cvs/rsync.
880
# XXX: fnmatch is actually not quite what we want: it's only
881
# approximately the same as real Unix fnmatch, and doesn't
882
# treat dotfiles correctly and allows * to match /.
883
# Eventually it should be replaced with something more
886
for pat in self.get_ignore_list():
887
if '/' in pat or '\\' in pat:
889
# as a special case, you can put ./ at the start of a
890
# pattern; this is good to match in the top-level
893
if (pat[:2] == './') or (pat[:2] == '.\\'):
897
if fnmatch.fnmatchcase(filename, newpat):
900
if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
905
def kind(self, file_id):
906
return file_kind(self.id2abspath(file_id))
908
def last_revision(self):
909
"""Return the last revision id of this working tree.
911
In early branch formats this was == the branch last_revision,
912
but that cannot be relied upon - for working tree operations,
913
always use tree.last_revision().
915
return self.branch.last_revision()
918
"""See Branch.lock_read, and WorkingTree.unlock."""
919
return self.branch.lock_read()
921
def lock_write(self):
922
"""See Branch.lock_write, and WorkingTree.unlock."""
923
return self.branch.lock_write()
925
def _basis_inventory_name(self, revision_id):
926
return 'basis-inventory.%s' % revision_id
928
def set_last_revision(self, new_revision, old_revision=None):
929
if old_revision is not None:
931
path = self._basis_inventory_name(old_revision)
932
path = self._control_files._escape(path)
933
self._control_files._transport.delete(path)
936
if new_revision is None:
937
self.branch.set_revision_history([])
939
# current format is locked in with the branch
940
revision_history = self.branch.revision_history()
942
position = revision_history.index(new_revision)
944
raise errors.NoSuchRevision(self.branch, new_revision)
945
self.branch.set_revision_history(revision_history[:position + 1])
947
xml = self.branch.repository.get_inventory_xml(new_revision)
948
path = self._basis_inventory_name(new_revision)
949
self._control_files.put_utf8(path, xml)
950
except WeaveRevisionNotPresent:
953
def read_basis_inventory(self, revision_id):
954
"""Read the cached basis inventory."""
955
path = self._basis_inventory_name(revision_id)
956
return self._control_files.get_utf8(path).read()
959
def read_working_inventory(self):
960
"""Read the working inventory."""
961
# ElementTree does its own conversion from UTF-8, so open in
963
result = bzrlib.xml5.serializer_v5.read_inventory(
964
self._control_files.get('inventory'))
965
self._set_inventory(result)
969
def remove(self, files, verbose=False):
970
"""Remove nominated files from the working inventory..
972
This does not remove their text. This does not run on XXX on what? RBC
974
TODO: Refuse to remove modified files unless --force is given?
976
TODO: Do something useful with directories.
978
TODO: Should this remove the text or not? Tough call; not
979
removing may be useful and the user can just use use rm, and
980
is the opposite of add. Removing it is consistent with most
981
other tools. Maybe an option.
983
## TODO: Normalize names
984
## TODO: Remove nested loops; better scalability
985
if isinstance(files, basestring):
990
# do this before any modifications
994
# TODO: Perhaps make this just a warning, and continue?
995
# This tends to happen when
996
raise NotVersionedError(path=f)
997
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
999
# having remove it, it must be either ignored or unknown
1000
if self.is_ignored(f):
1004
show_status(new_status, inv[fid].kind, quotefn(f))
1007
self._write_inventory(inv)
1010
def revert(self, filenames, old_tree=None, backups=True):
1011
from bzrlib.merge import merge_inner
1012
if old_tree is None:
1013
old_tree = self.basis_tree()
1014
merge_inner(self.branch, old_tree,
1015
self, ignore_zero=True,
1016
backup_files=backups,
1017
interesting_files=filenames,
1019
if not len(filenames):
1020
self.set_pending_merges([])
1023
def set_inventory(self, new_inventory_list):
1024
from bzrlib.inventory import (Inventory,
1029
inv = Inventory(self.get_root_id())
1030
for path, file_id, parent, kind in new_inventory_list:
1031
name = os.path.basename(path)
1034
# fixme, there should be a factory function inv,add_??
1035
if kind == 'directory':
1036
inv.add(InventoryDirectory(file_id, name, parent))
1037
elif kind == 'file':
1038
inv.add(InventoryFile(file_id, name, parent))
1039
elif kind == 'symlink':
1040
inv.add(InventoryLink(file_id, name, parent))
1042
raise BzrError("unknown kind %r" % kind)
1043
self._write_inventory(inv)
1046
def set_root_id(self, file_id):
1047
"""Set the root id for this tree."""
1048
inv = self.read_working_inventory()
1049
orig_root_id = inv.root.file_id
1050
del inv._byid[inv.root.file_id]
1051
inv.root.file_id = file_id
1052
inv._byid[inv.root.file_id] = inv.root
1055
if entry.parent_id == orig_root_id:
1056
entry.parent_id = inv.root.file_id
1057
self._write_inventory(inv)
1060
"""See Branch.unlock.
1062
WorkingTree locking just uses the Branch locking facilities.
1063
This is current because all working trees have an embedded branch
1064
within them. IF in the future, we were to make branch data shareable
1065
between multiple working trees, i.e. via shared storage, then we
1066
would probably want to lock both the local tree, and the branch.
1068
# FIXME: We want to write out the hashcache only when the last lock on
1069
# this working copy is released. Peeking at the lock count is a bit
1070
# of a nasty hack; probably it's better to have a transaction object,
1071
# which can do some finalization when it's either successfully or
1072
# unsuccessfully completed. (Denys's original patch did that.)
1073
if self._hashcache.needs_write and self._control_files._lock_count==1:
1074
self._hashcache.write()
1075
return self.branch.unlock()
1078
def _write_inventory(self, inv):
1079
"""Write inventory as the current inventory."""
1081
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1083
self._control_files.put('inventory', sio)
1084
self._set_inventory(inv)
1085
mutter('wrote working inventory')
1088
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1089
def get_conflicted_stem(path):
1090
for suffix in CONFLICT_SUFFIXES:
1091
if path.endswith(suffix):
1092
return path[:-len(suffix)]
1094
def is_control_file(filename):
1095
## FIXME: better check
1096
filename = normpath(filename)
1097
while filename != '':
1098
head, tail = os.path.split(filename)
1099
## mutter('check %r for control file' % ((head, tail),))
1100
if tail == bzrlib.BZRDIR:
1102
if filename == head:
1108
class WorkingTreeFormat(object):
1109
"""An encapsulation of the initialization and open routines for a format.
1111
Formats provide three things:
1112
* An initialization routine,
1116
Formats are placed in an dict by their format string for reference
1117
during workingtree opening. Its not required that these be instances, they
1118
can be classes themselves with class methods - it simply depends on
1119
whether state is needed for a given format or not.
1121
Once a format is deprecated, just deprecate the initialize and open
1122
methods on the format class. Do not deprecate the object, as the
1123
object will be created every time regardless.
1126
_default_format = None
1127
"""The default format used for new trees."""
1130
"""The known formats."""
1133
def find_format(klass, a_bzrdir):
1134
"""Return the format for the working tree object in a_bzrdir."""
1136
transport = a_bzrdir.get_workingtree_transport(None)
1137
format_string = transport.get("format").read()
1138
return klass._formats[format_string]
1140
raise errors.NotBranchError(path=transport.base)
1142
raise errors.UnknownFormatError(format_string)
1145
def get_default_format(klass):
1146
"""Return the current default format."""
1147
return klass._default_format
1149
def get_format_string(self):
1150
"""Return the ASCII format string that identifies this format."""
1151
raise NotImplementedError(self.get_format_string)
1153
def is_supported(self):
1154
"""Is this format supported?
1156
Supported formats can be initialized and opened.
1157
Unsupported formats may not support initialization or committing or
1158
some other features depending on the reason for not being supported.
1163
def register_format(klass, format):
1164
klass._formats[format.get_format_string()] = format
1167
def set_default_format(klass, format):
1168
klass._default_format = format
1171
def unregister_format(klass, format):
1172
assert klass._formats[format.get_format_string()] is format
1173
del klass._formats[format.get_format_string()]
1177
class WorkingTreeFormat2(WorkingTreeFormat):
1178
"""The second working tree format.
1180
This format modified the hash cache from the format 1 hash cache.
1183
def initialize(self, a_bzrdir):
1184
"""See WorkingTreeFormat.initialize()."""
1185
if not isinstance(a_bzrdir.transport, LocalTransport):
1186
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1187
b = a_bzrdir.open_branch()
1188
r = a_bzrdir.open_repository()
1189
revision = b.last_revision()
1190
basis_tree = r.revision_tree(revision)
1191
inv = basis_tree.inventory
1192
wt = WorkingTree(a_bzrdir.transport.clone('..').base, b, inv, _internal=True)
1193
wt._write_inventory(inv)
1194
wt.set_root_id(inv.root.file_id)
1195
wt.set_last_revision(revision)
1196
wt.set_pending_merges([])
1198
wt.bzrdir = a_bzrdir
1203
super(WorkingTreeFormat2, self).__init__()
1204
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1206
def open(self, a_bzrdir, _found=False):
1207
"""Return the WorkingTree object for a_bzrdir
1209
_found is a private parameter, do not use it. It is used to indicate
1210
if format probing has already been done.
1213
# we are being called directly and must probe.
1214
raise NotImplementedError
1215
if not isinstance(a_bzrdir.transport, LocalTransport):
1216
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1217
result = WorkingTree(a_bzrdir.transport.clone('..').base, _internal=True)
1218
result.bzrdir = a_bzrdir
1219
result._format = self
1223
class WorkingTreeFormat3(WorkingTreeFormat):
1224
"""The second working tree format updated to record a format marker.
1226
This format modified the hash cache from the format 1 hash cache.
1229
def get_format_string(self):
1230
"""See WorkingTreeFormat.get_format_string()."""
1231
return "Bazaar-NG Working Tree format 3"
1233
def initialize(self, a_bzrdir):
1234
"""See WorkingTreeFormat.initialize()."""
1235
if not isinstance(a_bzrdir.transport, LocalTransport):
1236
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1237
transport = a_bzrdir.get_workingtree_transport(self)
1238
control_files = LockableFiles(transport, 'lock')
1239
control_files.put_utf8('format', self.get_format_string())
1240
b = a_bzrdir.open_branch()
1241
r = a_bzrdir.open_repository()
1242
revision = b.last_revision()
1243
basis_tree = r.revision_tree(revision)
1244
inv = basis_tree.inventory
1245
wt = WorkingTree(a_bzrdir.transport.clone('..').base, b, inv, _internal=True)
1246
wt._write_inventory(inv)
1247
wt.set_root_id(inv.root.file_id)
1248
wt.set_last_revision(revision)
1249
wt.set_pending_merges([])
1251
wt.bzrdir = a_bzrdir
1256
super(WorkingTreeFormat3, self).__init__()
1257
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1259
def open(self, a_bzrdir, _found=False):
1260
"""Return the WorkingTree object for a_bzrdir
1262
_found is a private parameter, do not use it. It is used to indicate
1263
if format probing has already been done.
1266
# we are being called directly and must probe.
1267
raise NotImplementedError
1268
if not isinstance(a_bzrdir.transport, LocalTransport):
1269
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1270
result = WorkingTree(a_bzrdir.transport.clone('..').base, _internal=True)
1271
result.bzrdir = a_bzrdir
1272
result._format = self
1276
# formats which have no format string are not discoverable
1277
# and not independently creatable, so are not registered.
1278
__default_format = WorkingTreeFormat3()
1279
WorkingTreeFormat.register_format(__default_format)
1280
WorkingTreeFormat.set_default_format(__default_format)
1281
_legacy_formats = [WorkingTreeFormat2(),
1285
class WorkingTreeTestProviderAdapter(object):
1286
"""A tool to generate a suite testing multiple workingtree formats at once.
1288
This is done by copying the test once for each transport and injecting
1289
the transport_server, transport_readonly_server, and workingtree_format
1290
classes into each copy. Each copy is also given a new id() to make it
1294
def __init__(self, transport_server, transport_readonly_server, formats):
1295
self._transport_server = transport_server
1296
self._transport_readonly_server = transport_readonly_server
1297
self._formats = formats
1299
def adapt(self, test):
1300
from bzrlib.tests import TestSuite
1301
result = TestSuite()
1302
for workingtree_format, bzrdir_format in self._formats:
1303
new_test = deepcopy(test)
1304
new_test.transport_server = self._transport_server
1305
new_test.transport_readonly_server = self._transport_readonly_server
1306
new_test.bzrdir_format = bzrdir_format
1307
new_test.workingtree_format = workingtree_format
1308
def make_new_test_id():
1309
new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1310
return lambda: new_id
1311
new_test.id = make_new_test_id()
1312
result.addTest(new_test)