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
def create(branch, directory):
322
"""Create a workingtree for branch at directory.
324
If existing_directory already exists it must have a .bzr directory.
325
If it does not exist, it will be created.
327
This returns a new WorkingTree object for the new checkout.
329
TODO FIXME RBC 20060124 when we have checkout formats in place this
330
should accept an optional revisionid to checkout [and reject this if
331
checking out into the same dir as a pre-checkout-aware branch format.]
333
XXX: When BzrDir is present, these should be created through that
336
transport = get_transport(directory)
337
if branch.bzrdir.transport.clone('..').base == transport.base:
339
return branch.bzrdir.create_workingtree()
340
# different directory,
341
# create a branch reference
342
# and now a working tree.
343
raise NotImplementedError
347
if e.errno != errno.EEXIST:
350
os.mkdir(pathjoin(directory, '.bzr'))
352
if e.errno != errno.EEXIST:
354
revision_tree = branch.repository.revision_tree(branch.last_revision())
355
inv = revision_tree.inventory
356
wt = WorkingTree(directory, branch, inv, _internal=True)
357
wt._write_inventory(inv)
358
wt.set_root_id(revision_tree.inventory.root.file_id)
359
if branch.last_revision() is not None:
360
wt.set_last_revision(branch.last_revision())
361
wt.set_pending_merges([])
366
@deprecated_method(zero_eight)
367
def create_standalone(directory):
368
"""Create a checkout and a branch and a repo at directory.
370
Directory must exist and be empty.
372
please use BzrDir.create_standalone_workingtree
374
return bzrdir.BzrDir.create_standalone_workingtree(directory)
376
def relpath(self, abs):
377
"""Return the local path portion from a given absolute path."""
378
return relpath(self.basedir, abs)
380
def has_filename(self, filename):
381
return bzrlib.osutils.lexists(self.abspath(filename))
383
def get_file(self, file_id):
384
return self.get_file_byname(self.id2path(file_id))
386
def get_file_byname(self, filename):
387
return file(self.abspath(filename), 'rb')
389
def get_root_id(self):
390
"""Return the id of this trees root"""
391
inv = self.read_working_inventory()
392
return inv.root.file_id
394
def _get_store_filename(self, file_id):
395
## XXX: badly named; this is not in the store at all
396
return self.abspath(self.id2path(file_id))
399
def clone(self, to_directory, revision=None):
400
"""Copy this working tree to a new directory.
402
Currently this will make a new standalone branch at to_directory,
403
but it is planned to change this to use the same branch style that this
404
current tree uses (standalone if standalone, repository if repository)
405
- so that this really is a clone. FIXME RBC 20060127 do this.
406
FIXME MORE RBC 20060127 failed to reach consensus on this in #bzr.
408
If you want a standalone branch, please use branch.clone(to_directory)
409
followed by WorkingTree.create(cloned_branch, to_directory) which is
410
the supported api to produce that.
413
If not None, the cloned tree will have its last revision set to
414
revision, and if a branch is being copied it will be informed
415
of the revision to result in.
417
to_directory -- The destination directory: Must not exist.
419
to_directory = safe_unicode(to_directory)
420
os.mkdir(to_directory)
421
# FIXME here is where the decision to clone the branch should happen.
423
revision = self.last_revision()
424
cloned_branch = self.branch.clone(to_directory, revision)
425
return WorkingTree.create(cloned_branch, to_directory)
428
def commit(self, *args, **kwargs):
429
from bzrlib.commit import Commit
430
# args for wt.commit start at message from the Commit.commit method,
431
# but with branch a kwarg now, passing in args as is results in the
432
#message being used for the branch
433
args = (DEPRECATED_PARAMETER, ) + args
434
Commit().commit(working_tree=self, *args, **kwargs)
435
self._set_inventory(self.read_working_inventory())
437
def id2abspath(self, file_id):
438
return self.abspath(self.id2path(file_id))
440
def has_id(self, file_id):
441
# files that have been deleted are excluded
442
inv = self._inventory
443
if not inv.has_id(file_id):
445
path = inv.id2path(file_id)
446
return bzrlib.osutils.lexists(self.abspath(path))
448
def has_or_had_id(self, file_id):
449
if file_id == self.inventory.root.file_id:
451
return self.inventory.has_id(file_id)
453
__contains__ = has_id
455
def get_file_size(self, file_id):
456
return os.path.getsize(self.id2abspath(file_id))
459
def get_file_sha1(self, file_id):
460
path = self._inventory.id2path(file_id)
461
return self._hashcache.get_sha1(path)
463
def is_executable(self, file_id):
465
return self._inventory[file_id].executable
467
path = self._inventory.id2path(file_id)
468
mode = os.lstat(self.abspath(path)).st_mode
469
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
472
def add(self, files, ids=None):
473
"""Make files versioned.
475
Note that the command line normally calls smart_add instead,
476
which can automatically recurse.
478
This adds the files to the inventory, so that they will be
479
recorded by the next commit.
482
List of paths to add, relative to the base of the tree.
485
If set, use these instead of automatically generated ids.
486
Must be the same length as the list of files, but may
487
contain None for ids that are to be autogenerated.
489
TODO: Perhaps have an option to add the ids even if the files do
492
TODO: Perhaps callback with the ids and paths as they're added.
494
# TODO: Re-adding a file that is removed in the working copy
495
# should probably put it back with the previous ID.
496
if isinstance(files, basestring):
497
assert(ids is None or isinstance(ids, basestring))
503
ids = [None] * len(files)
505
assert(len(ids) == len(files))
507
inv = self.read_working_inventory()
508
for f,file_id in zip(files, ids):
509
if is_control_file(f):
510
raise BzrError("cannot add control file %s" % quotefn(f))
515
raise BzrError("cannot add top-level %r" % f)
517
fullpath = normpath(self.abspath(f))
520
kind = file_kind(fullpath)
522
if e.errno == errno.ENOENT:
523
raise NoSuchFile(fullpath)
524
# maybe something better?
525
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
527
if not InventoryEntry.versionable_kind(kind):
528
raise BzrError('cannot add: not a versionable file ('
529
'i.e. regular file, symlink or directory): %s' % quotefn(f))
532
file_id = gen_file_id(f)
533
inv.add_path(f, kind=kind, file_id=file_id)
535
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
536
self._write_inventory(inv)
539
def add_pending_merge(self, *revision_ids):
540
# TODO: Perhaps should check at this point that the
541
# history of the revision is actually present?
542
p = self.pending_merges()
544
for rev_id in revision_ids:
550
self.set_pending_merges(p)
553
def pending_merges(self):
554
"""Return a list of pending merges.
556
These are revisions that have been merged into the working
557
directory but not yet committed.
560
merges_file = self._control_files.get_utf8('pending-merges')
562
if e.errno != errno.ENOENT:
566
for l in merges_file.readlines():
567
p.append(l.rstrip('\n'))
571
def set_pending_merges(self, rev_list):
572
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
574
def get_symlink_target(self, file_id):
575
return os.readlink(self.id2abspath(file_id))
577
def file_class(self, filename):
578
if self.path2id(filename):
580
elif self.is_ignored(filename):
586
def list_files(self):
587
"""Recursively list all files as (path, class, kind, id).
589
Lists, but does not descend into unversioned directories.
591
This does not include files that have been deleted in this
594
Skips the control directory.
596
inv = self._inventory
598
def descend(from_dir_relpath, from_dir_id, dp):
602
## TODO: If we find a subdirectory with its own .bzr
603
## directory, then that is a separate tree and we
604
## should exclude it.
605
if bzrlib.BZRDIR == f:
609
fp = appendpath(from_dir_relpath, f)
612
fap = appendpath(dp, f)
614
f_ie = inv.get_child(from_dir_id, f)
617
elif self.is_ignored(fp):
626
raise BzrCheckError("file %r entered as kind %r id %r, "
628
% (fap, f_ie.kind, f_ie.file_id, fk))
630
# make a last minute entry
634
if fk == 'directory':
635
entry = TreeDirectory()
638
elif fk == 'symlink':
643
yield fp, c, fk, (f_ie and f_ie.file_id), entry
645
if fk != 'directory':
649
# don't descend unversioned directories
652
for ff in descend(fp, f_ie.file_id, fap):
655
for f in descend(u'', inv.root.file_id, self.basedir):
659
def move(self, from_paths, to_name):
662
to_name must exist in the inventory.
664
If to_name exists and is a directory, the files are moved into
665
it, keeping their old names.
667
Note that to_name is only the last component of the new name;
668
this doesn't change the directory.
670
This returns a list of (from_path, to_path) pairs for each
674
## TODO: Option to move IDs only
675
assert not isinstance(from_paths, basestring)
677
to_abs = self.abspath(to_name)
678
if not isdir(to_abs):
679
raise BzrError("destination %r is not a directory" % to_abs)
680
if not self.has_filename(to_name):
681
raise BzrError("destination %r not in working directory" % to_abs)
682
to_dir_id = inv.path2id(to_name)
683
if to_dir_id == None and to_name != '':
684
raise BzrError("destination %r is not a versioned directory" % to_name)
685
to_dir_ie = inv[to_dir_id]
686
if to_dir_ie.kind not in ('directory', 'root_directory'):
687
raise BzrError("destination %r is not a directory" % to_abs)
689
to_idpath = inv.get_idpath(to_dir_id)
692
if not self.has_filename(f):
693
raise BzrError("%r does not exist in working tree" % f)
694
f_id = inv.path2id(f)
696
raise BzrError("%r is not versioned" % f)
697
name_tail = splitpath(f)[-1]
698
dest_path = appendpath(to_name, name_tail)
699
if self.has_filename(dest_path):
700
raise BzrError("destination %r already exists" % dest_path)
701
if f_id in to_idpath:
702
raise BzrError("can't move %r to a subdirectory of itself" % f)
704
# OK, so there's a race here, it's possible that someone will
705
# create a file in this interval and then the rename might be
706
# left half-done. But we should have caught most problems.
707
orig_inv = deepcopy(self.inventory)
710
name_tail = splitpath(f)[-1]
711
dest_path = appendpath(to_name, name_tail)
712
result.append((f, dest_path))
713
inv.rename(inv.path2id(f), to_dir_id, name_tail)
715
rename(self.abspath(f), self.abspath(dest_path))
717
raise BzrError("failed to rename %r to %r: %s" %
718
(f, dest_path, e[1]),
719
["rename rolled back"])
721
# restore the inventory on error
722
self._set_inventory(orig_inv)
724
self._write_inventory(inv)
728
def rename_one(self, from_rel, to_rel):
731
This can change the directory or the filename or both.
734
if not self.has_filename(from_rel):
735
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
736
if self.has_filename(to_rel):
737
raise BzrError("can't rename: new working file %r already exists" % to_rel)
739
file_id = inv.path2id(from_rel)
741
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
744
from_parent = entry.parent_id
745
from_name = entry.name
747
if inv.path2id(to_rel):
748
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
750
to_dir, to_tail = os.path.split(to_rel)
751
to_dir_id = inv.path2id(to_dir)
752
if to_dir_id == None and to_dir != '':
753
raise BzrError("can't determine destination directory id for %r" % to_dir)
755
mutter("rename_one:")
756
mutter(" file_id {%s}" % file_id)
757
mutter(" from_rel %r" % from_rel)
758
mutter(" to_rel %r" % to_rel)
759
mutter(" to_dir %r" % to_dir)
760
mutter(" to_dir_id {%s}" % to_dir_id)
762
inv.rename(file_id, to_dir_id, to_tail)
764
from_abs = self.abspath(from_rel)
765
to_abs = self.abspath(to_rel)
767
rename(from_abs, to_abs)
769
inv.rename(file_id, from_parent, from_name)
770
raise BzrError("failed to rename %r to %r: %s"
771
% (from_abs, to_abs, e[1]),
772
["rename rolled back"])
773
self._write_inventory(inv)
777
"""Return all unknown files.
779
These are files in the working directory that are not versioned or
780
control files or ignored.
782
>>> from bzrlib.bzrdir import ScratchDir
783
>>> d = ScratchDir(files=['foo', 'foo~'])
784
>>> b = d.open_branch()
785
>>> tree = WorkingTree(b.base, b)
786
>>> map(str, tree.unknowns())
789
>>> list(b.unknowns())
791
>>> tree.remove('foo')
792
>>> list(b.unknowns())
795
for subp in self.extras():
796
if not self.is_ignored(subp):
799
def iter_conflicts(self):
801
for path in (s[0] for s in self.list_files()):
802
stem = get_conflicted_stem(path)
805
if stem not in conflicted:
810
def pull(self, source, overwrite=False):
811
from bzrlib.merge import merge_inner
814
old_revision_history = self.branch.revision_history()
815
count = self.branch.pull(source, overwrite)
816
new_revision_history = self.branch.revision_history()
817
if new_revision_history != old_revision_history:
818
if len(old_revision_history):
819
other_revision = old_revision_history[-1]
821
other_revision = None
822
repository = self.branch.repository
823
merge_inner(self.branch,
825
repository.revision_tree(other_revision),
827
self.set_last_revision(self.branch.last_revision())
833
"""Yield all unknown files in this WorkingTree.
835
If there are any unknown directories then only the directory is
836
returned, not all its children. But if there are unknown files
837
under a versioned subdirectory, they are returned.
839
Currently returned depth-first, sorted by name within directories.
841
## TODO: Work from given directory downwards
842
for path, dir_entry in self.inventory.directories():
843
mutter("search for unknowns in %r", path)
844
dirabs = self.abspath(path)
845
if not isdir(dirabs):
846
# e.g. directory deleted
850
for subf in os.listdir(dirabs):
852
and (subf not in dir_entry.children)):
857
subp = appendpath(path, subf)
861
def ignored_files(self):
862
"""Yield list of PATH, IGNORE_PATTERN"""
863
for subp in self.extras():
864
pat = self.is_ignored(subp)
869
def get_ignore_list(self):
870
"""Return list of ignore patterns.
872
Cached in the Tree object after the first call.
874
if hasattr(self, '_ignorelist'):
875
return self._ignorelist
877
l = bzrlib.DEFAULT_IGNORE[:]
878
if self.has_filename(bzrlib.IGNORE_FILENAME):
879
f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
880
l.extend([line.rstrip("\n\r") for line in f.readlines()])
885
def is_ignored(self, filename):
886
r"""Check whether the filename matches an ignore pattern.
888
Patterns containing '/' or '\' need to match the whole path;
889
others match against only the last component.
891
If the file is ignored, returns the pattern which caused it to
892
be ignored, otherwise None. So this can simply be used as a
893
boolean if desired."""
895
# TODO: Use '**' to match directories, and other extended
896
# globbing stuff from cvs/rsync.
898
# XXX: fnmatch is actually not quite what we want: it's only
899
# approximately the same as real Unix fnmatch, and doesn't
900
# treat dotfiles correctly and allows * to match /.
901
# Eventually it should be replaced with something more
904
for pat in self.get_ignore_list():
905
if '/' in pat or '\\' in pat:
907
# as a special case, you can put ./ at the start of a
908
# pattern; this is good to match in the top-level
911
if (pat[:2] == './') or (pat[:2] == '.\\'):
915
if fnmatch.fnmatchcase(filename, newpat):
918
if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
923
def kind(self, file_id):
924
return file_kind(self.id2abspath(file_id))
926
def last_revision(self):
927
"""Return the last revision id of this working tree.
929
In early branch formats this was == the branch last_revision,
930
but that cannot be relied upon - for working tree operations,
931
always use tree.last_revision().
933
return self.branch.last_revision()
936
"""See Branch.lock_read, and WorkingTree.unlock."""
937
return self.branch.lock_read()
939
def lock_write(self):
940
"""See Branch.lock_write, and WorkingTree.unlock."""
941
return self.branch.lock_write()
943
def _basis_inventory_name(self, revision_id):
944
return 'basis-inventory.%s' % revision_id
946
def set_last_revision(self, new_revision, old_revision=None):
947
if old_revision is not None:
949
path = self._basis_inventory_name(old_revision)
950
path = self._control_files._escape(path)
951
self._control_files._transport.delete(path)
954
if new_revision is None:
955
self.branch.set_revision_history([])
957
# current format is locked in with the branch
958
revision_history = self.branch.revision_history()
960
position = revision_history.index(new_revision)
962
raise errors.NoSuchRevision(self.branch, new_revision)
963
self.branch.set_revision_history(revision_history[:position + 1])
965
xml = self.branch.repository.get_inventory_xml(new_revision)
966
path = self._basis_inventory_name(new_revision)
967
self._control_files.put_utf8(path, xml)
968
except WeaveRevisionNotPresent:
971
def read_basis_inventory(self, revision_id):
972
"""Read the cached basis inventory."""
973
path = self._basis_inventory_name(revision_id)
974
return self._control_files.get_utf8(path).read()
977
def read_working_inventory(self):
978
"""Read the working inventory."""
979
# ElementTree does its own conversion from UTF-8, so open in
981
result = bzrlib.xml5.serializer_v5.read_inventory(
982
self._control_files.get('inventory'))
983
self._set_inventory(result)
987
def remove(self, files, verbose=False):
988
"""Remove nominated files from the working inventory..
990
This does not remove their text. This does not run on XXX on what? RBC
992
TODO: Refuse to remove modified files unless --force is given?
994
TODO: Do something useful with directories.
996
TODO: Should this remove the text or not? Tough call; not
997
removing may be useful and the user can just use use rm, and
998
is the opposite of add. Removing it is consistent with most
999
other tools. Maybe an option.
1001
## TODO: Normalize names
1002
## TODO: Remove nested loops; better scalability
1003
if isinstance(files, basestring):
1006
inv = self.inventory
1008
# do this before any modifications
1010
fid = inv.path2id(f)
1012
# TODO: Perhaps make this just a warning, and continue?
1013
# This tends to happen when
1014
raise NotVersionedError(path=f)
1015
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1017
# having remove it, it must be either ignored or unknown
1018
if self.is_ignored(f):
1022
show_status(new_status, inv[fid].kind, quotefn(f))
1025
self._write_inventory(inv)
1028
def revert(self, filenames, old_tree=None, backups=True):
1029
from bzrlib.merge import merge_inner
1030
if old_tree is None:
1031
old_tree = self.basis_tree()
1032
merge_inner(self.branch, old_tree,
1033
self, ignore_zero=True,
1034
backup_files=backups,
1035
interesting_files=filenames,
1037
if not len(filenames):
1038
self.set_pending_merges([])
1041
def set_inventory(self, new_inventory_list):
1042
from bzrlib.inventory import (Inventory,
1047
inv = Inventory(self.get_root_id())
1048
for path, file_id, parent, kind in new_inventory_list:
1049
name = os.path.basename(path)
1052
# fixme, there should be a factory function inv,add_??
1053
if kind == 'directory':
1054
inv.add(InventoryDirectory(file_id, name, parent))
1055
elif kind == 'file':
1056
inv.add(InventoryFile(file_id, name, parent))
1057
elif kind == 'symlink':
1058
inv.add(InventoryLink(file_id, name, parent))
1060
raise BzrError("unknown kind %r" % kind)
1061
self._write_inventory(inv)
1064
def set_root_id(self, file_id):
1065
"""Set the root id for this tree."""
1066
inv = self.read_working_inventory()
1067
orig_root_id = inv.root.file_id
1068
del inv._byid[inv.root.file_id]
1069
inv.root.file_id = file_id
1070
inv._byid[inv.root.file_id] = inv.root
1073
if entry.parent_id == orig_root_id:
1074
entry.parent_id = inv.root.file_id
1075
self._write_inventory(inv)
1078
"""See Branch.unlock.
1080
WorkingTree locking just uses the Branch locking facilities.
1081
This is current because all working trees have an embedded branch
1082
within them. IF in the future, we were to make branch data shareable
1083
between multiple working trees, i.e. via shared storage, then we
1084
would probably want to lock both the local tree, and the branch.
1086
# FIXME: We want to write out the hashcache only when the last lock on
1087
# this working copy is released. Peeking at the lock count is a bit
1088
# of a nasty hack; probably it's better to have a transaction object,
1089
# which can do some finalization when it's either successfully or
1090
# unsuccessfully completed. (Denys's original patch did that.)
1091
if self._hashcache.needs_write and self._control_files._lock_count==1:
1092
self._hashcache.write()
1093
return self.branch.unlock()
1096
def _write_inventory(self, inv):
1097
"""Write inventory as the current inventory."""
1099
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1101
self._control_files.put('inventory', sio)
1102
self._set_inventory(inv)
1103
mutter('wrote working inventory')
1106
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1107
def get_conflicted_stem(path):
1108
for suffix in CONFLICT_SUFFIXES:
1109
if path.endswith(suffix):
1110
return path[:-len(suffix)]
1112
def is_control_file(filename):
1113
## FIXME: better check
1114
filename = normpath(filename)
1115
while filename != '':
1116
head, tail = os.path.split(filename)
1117
## mutter('check %r for control file' % ((head, tail),))
1118
if tail == bzrlib.BZRDIR:
1120
if filename == head:
1126
class WorkingTreeFormat(object):
1127
"""An encapsulation of the initialization and open routines for a format.
1129
Formats provide three things:
1130
* An initialization routine,
1134
Formats are placed in an dict by their format string for reference
1135
during workingtree opening. Its not required that these be instances, they
1136
can be classes themselves with class methods - it simply depends on
1137
whether state is needed for a given format or not.
1139
Once a format is deprecated, just deprecate the initialize and open
1140
methods on the format class. Do not deprecate the object, as the
1141
object will be created every time regardless.
1144
_default_format = None
1145
"""The default format used for new trees."""
1148
"""The known formats."""
1151
def find_format(klass, a_bzrdir):
1152
"""Return the format for the working tree object in a_bzrdir."""
1154
transport = a_bzrdir.get_workingtree_transport(None)
1155
format_string = transport.get("format").read()
1156
return klass._formats[format_string]
1158
raise errors.NotBranchError(path=transport.base)
1160
raise errors.UnknownFormatError(format_string)
1163
def get_default_format(klass):
1164
"""Return the current default format."""
1165
return klass._default_format
1167
def get_format_string(self):
1168
"""Return the ASCII format string that identifies this format."""
1169
raise NotImplementedError(self.get_format_string)
1171
def is_supported(self):
1172
"""Is this format supported?
1174
Supported formats can be initialized and opened.
1175
Unsupported formats may not support initialization or committing or
1176
some other features depending on the reason for not being supported.
1181
def register_format(klass, format):
1182
klass._formats[format.get_format_string()] = format
1185
def set_default_format(klass, format):
1186
klass._default_format = format
1189
def unregister_format(klass, format):
1190
assert klass._formats[format.get_format_string()] is format
1191
del klass._formats[format.get_format_string()]
1195
class WorkingTreeFormat2(WorkingTreeFormat):
1196
"""The second working tree format.
1198
This format modified the hash cache from the format 1 hash cache.
1201
def initialize(self, a_bzrdir):
1202
"""See WorkingTreeFormat.initialize()."""
1203
if not isinstance(a_bzrdir.transport, LocalTransport):
1204
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1205
b = a_bzrdir.open_branch()
1206
r = a_bzrdir.open_repository()
1207
revision = b.last_revision()
1208
basis_tree = r.revision_tree(revision)
1209
inv = basis_tree.inventory
1210
wt = WorkingTree(a_bzrdir.transport.clone('..').base, b, inv, _internal=True)
1211
wt._write_inventory(inv)
1212
wt.set_root_id(inv.root.file_id)
1213
wt.set_last_revision(revision)
1214
wt.set_pending_merges([])
1216
wt.bzrdir = a_bzrdir
1221
super(WorkingTreeFormat2, self).__init__()
1222
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1224
def open(self, a_bzrdir, _found=False):
1225
"""Return the WorkingTree object for a_bzrdir
1227
_found is a private parameter, do not use it. It is used to indicate
1228
if format probing has already been done.
1231
# we are being called directly and must probe.
1232
raise NotImplementedError
1233
if not isinstance(a_bzrdir.transport, LocalTransport):
1234
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1235
result = WorkingTree(a_bzrdir.transport.clone('..').base, _internal=True)
1236
result.bzrdir = a_bzrdir
1237
result._format = self
1241
class WorkingTreeFormat3(WorkingTreeFormat):
1242
"""The second working tree format updated to record a format marker.
1244
This format modified the hash cache from the format 1 hash cache.
1247
def get_format_string(self):
1248
"""See WorkingTreeFormat.get_format_string()."""
1249
return "Bazaar-NG Working Tree format 3"
1251
def initialize(self, a_bzrdir):
1252
"""See WorkingTreeFormat.initialize()."""
1253
if not isinstance(a_bzrdir.transport, LocalTransport):
1254
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1255
transport = a_bzrdir.get_workingtree_transport(self)
1256
control_files = LockableFiles(transport, 'lock')
1257
control_files.put_utf8('format', self.get_format_string())
1258
b = a_bzrdir.open_branch()
1259
r = a_bzrdir.open_repository()
1260
revision = b.last_revision()
1261
basis_tree = r.revision_tree(revision)
1262
inv = basis_tree.inventory
1263
wt = WorkingTree(a_bzrdir.transport.clone('..').base, b, inv, _internal=True)
1264
wt._write_inventory(inv)
1265
wt.set_root_id(inv.root.file_id)
1266
wt.set_last_revision(revision)
1267
wt.set_pending_merges([])
1269
wt.bzrdir = a_bzrdir
1274
super(WorkingTreeFormat3, self).__init__()
1275
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1277
def open(self, a_bzrdir, _found=False):
1278
"""Return the WorkingTree object for a_bzrdir
1280
_found is a private parameter, do not use it. It is used to indicate
1281
if format probing has already been done.
1284
# we are being called directly and must probe.
1285
raise NotImplementedError
1286
if not isinstance(a_bzrdir.transport, LocalTransport):
1287
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1288
result = WorkingTree(a_bzrdir.transport.clone('..').base, _internal=True)
1289
result.bzrdir = a_bzrdir
1290
result._format = self
1294
# formats which have no format string are not discoverable
1295
# and not independently creatable, so are not registered.
1296
__default_format = WorkingTreeFormat3()
1297
WorkingTreeFormat.register_format(__default_format)
1298
WorkingTreeFormat.set_default_format(__default_format)
1299
_legacy_formats = [WorkingTreeFormat2(),
1303
class WorkingTreeTestProviderAdapter(object):
1304
"""A tool to generate a suite testing multiple workingtree formats at once.
1306
This is done by copying the test once for each transport and injecting
1307
the transport_server, transport_readonly_server, and workingtree_format
1308
classes into each copy. Each copy is also given a new id() to make it
1312
def __init__(self, transport_server, transport_readonly_server, formats):
1313
self._transport_server = transport_server
1314
self._transport_readonly_server = transport_readonly_server
1315
self._formats = formats
1317
def adapt(self, test):
1318
from bzrlib.tests import TestSuite
1319
result = TestSuite()
1320
for workingtree_format, bzrdir_format in self._formats:
1321
new_test = deepcopy(test)
1322
new_test.transport_server = self._transport_server
1323
new_test.transport_readonly_server = self._transport_readonly_server
1324
new_test.bzrdir_format = bzrdir_format
1325
new_test.workingtree_format = workingtree_format
1326
def make_new_test_id():
1327
new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1328
return lambda: new_id
1329
new_test.id = make_new_test_id()
1330
result.addTest(new_test)