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
This is true IF and ONLY IF the filename is part of the meta data
283
that bzr controls in this tree. I.E. a random .bzr directory placed
284
on disk will not be a control file for this tree.
287
self.bzrdir.transport.relpath(self.abspath(filename))
289
except errors.PathNotChild:
293
def open(path=None, _unsupported=False):
294
"""Open an existing working tree at path.
298
path = os.path.getcwdu()
299
control = bzrdir.BzrDir.open(path, _unsupported)
300
return control.open_workingtree(_unsupported)
303
def open_containing(path=None):
304
"""Open an existing working tree which has its root about path.
306
This probes for a working tree at path and searches upwards from there.
308
Basically we keep looking up until we find the control directory or
309
run into /. If there isn't one, raises NotBranchError.
310
TODO: give this a new exception.
311
If there is one, it is returned, along with the unused portion of path.
315
control, relpath = bzrdir.BzrDir.open_containing(path)
316
return control.open_workingtree(), relpath
319
def open_downlevel(path=None):
320
"""Open an unsupported working tree.
322
Only intended for advanced situations like upgrading part of a bzrdir.
324
return WorkingTree.open(path, _unsupported=True)
327
"""Iterate through file_ids for this tree.
329
file_ids are in a WorkingTree if they are in the working inventory
330
and the working file exists.
332
inv = self._inventory
333
for path, ie in inv.iter_entries():
334
if bzrlib.osutils.lexists(self.abspath(path)):
338
return "<%s of %s>" % (self.__class__.__name__,
339
getattr(self, 'basedir', None))
341
def abspath(self, filename):
342
return pathjoin(self.basedir, filename)
344
def basis_tree(self):
345
"""Return RevisionTree for the current last revision."""
346
revision_id = self.last_revision()
347
if revision_id is not None:
349
xml = self.read_basis_inventory(revision_id)
350
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
351
return bzrlib.tree.RevisionTree(self.branch.repository, inv,
355
return self.branch.repository.revision_tree(revision_id)
358
@deprecated_method(zero_eight)
359
def create(branch, directory):
360
"""Create a workingtree for branch at directory.
362
If existing_directory already exists it must have a .bzr directory.
363
If it does not exist, it will be created.
365
This returns a new WorkingTree object for the new checkout.
367
TODO FIXME RBC 20060124 when we have checkout formats in place this
368
should accept an optional revisionid to checkout [and reject this if
369
checking out into the same dir as a pre-checkout-aware branch format.]
371
XXX: When BzrDir is present, these should be created through that
374
warn('delete WorkingTree.create', stacklevel=3)
375
transport = get_transport(directory)
376
if branch.bzrdir.root_transport.base == transport.base:
378
return branch.bzrdir.create_workingtree()
379
# different directory,
380
# create a branch reference
381
# and now a working tree.
382
raise NotImplementedError
385
@deprecated_method(zero_eight)
386
def create_standalone(directory):
387
"""Create a checkout and a branch and a repo at directory.
389
Directory must exist and be empty.
391
please use BzrDir.create_standalone_workingtree
393
return bzrdir.BzrDir.create_standalone_workingtree(directory)
395
def relpath(self, abs):
396
"""Return the local path portion from a given absolute path."""
397
return relpath(self.basedir, abs)
399
def has_filename(self, filename):
400
return bzrlib.osutils.lexists(self.abspath(filename))
402
def get_file(self, file_id):
403
return self.get_file_byname(self.id2path(file_id))
405
def get_file_byname(self, filename):
406
return file(self.abspath(filename), 'rb')
408
def get_root_id(self):
409
"""Return the id of this trees root"""
410
inv = self.read_working_inventory()
411
return inv.root.file_id
413
def _get_store_filename(self, file_id):
414
## XXX: badly named; this is not in the store at all
415
return self.abspath(self.id2path(file_id))
418
def clone(self, to_bzrdir, revision_id=None, basis=None):
419
"""Duplicate this working tree into to_bzr, including all state.
421
Specifically modified files are kept as modified, but
422
ignored and unknown files are discarded.
424
If you want to make a new line of development, see bzrdir.sprout()
427
If not None, the cloned tree will have its last revision set to
428
revision, and and difference between the source trees last revision
429
and this one merged in.
432
If not None, a closer copy of a tree which may have some files in
433
common, and which file content should be preferentially copied from.
435
# assumes the target bzr dir format is compatible.
436
result = self._format.initialize(to_bzrdir)
437
self.copy_content_into(result, revision_id)
441
def copy_content_into(self, tree, revision_id=None):
442
"""Copy the current content and user files of this tree into tree."""
443
if revision_id is None:
444
transform_tree(tree, self)
446
# TODO now merge from tree.last_revision to revision
447
transform_tree(tree, self)
448
tree.set_last_revision(revision_id)
451
def commit(self, *args, **kwargs):
452
from bzrlib.commit import Commit
453
# args for wt.commit start at message from the Commit.commit method,
454
# but with branch a kwarg now, passing in args as is results in the
455
#message being used for the branch
456
args = (DEPRECATED_PARAMETER, ) + args
457
Commit().commit(working_tree=self, *args, **kwargs)
458
self._set_inventory(self.read_working_inventory())
460
def id2abspath(self, file_id):
461
return self.abspath(self.id2path(file_id))
463
def has_id(self, file_id):
464
# files that have been deleted are excluded
465
inv = self._inventory
466
if not inv.has_id(file_id):
468
path = inv.id2path(file_id)
469
return bzrlib.osutils.lexists(self.abspath(path))
471
def has_or_had_id(self, file_id):
472
if file_id == self.inventory.root.file_id:
474
return self.inventory.has_id(file_id)
476
__contains__ = has_id
478
def get_file_size(self, file_id):
479
return os.path.getsize(self.id2abspath(file_id))
482
def get_file_sha1(self, file_id):
483
path = self._inventory.id2path(file_id)
484
return self._hashcache.get_sha1(path)
486
def is_executable(self, file_id):
488
return self._inventory[file_id].executable
490
path = self._inventory.id2path(file_id)
491
mode = os.lstat(self.abspath(path)).st_mode
492
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
495
def add(self, files, ids=None):
496
"""Make files versioned.
498
Note that the command line normally calls smart_add instead,
499
which can automatically recurse.
501
This adds the files to the inventory, so that they will be
502
recorded by the next commit.
505
List of paths to add, relative to the base of the tree.
508
If set, use these instead of automatically generated ids.
509
Must be the same length as the list of files, but may
510
contain None for ids that are to be autogenerated.
512
TODO: Perhaps have an option to add the ids even if the files do
515
TODO: Perhaps callback with the ids and paths as they're added.
517
# TODO: Re-adding a file that is removed in the working copy
518
# should probably put it back with the previous ID.
519
if isinstance(files, basestring):
520
assert(ids is None or isinstance(ids, basestring))
526
ids = [None] * len(files)
528
assert(len(ids) == len(files))
530
inv = self.read_working_inventory()
531
for f,file_id in zip(files, ids):
532
if self.is_control_filename(f):
533
raise BzrError("cannot add control file %s" % quotefn(f))
538
raise BzrError("cannot add top-level %r" % f)
540
fullpath = normpath(self.abspath(f))
543
kind = file_kind(fullpath)
545
if e.errno == errno.ENOENT:
546
raise NoSuchFile(fullpath)
547
# maybe something better?
548
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
550
if not InventoryEntry.versionable_kind(kind):
551
raise BzrError('cannot add: not a versionable file ('
552
'i.e. regular file, symlink or directory): %s' % quotefn(f))
555
file_id = gen_file_id(f)
556
inv.add_path(f, kind=kind, file_id=file_id)
558
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
559
self._write_inventory(inv)
562
def add_pending_merge(self, *revision_ids):
563
# TODO: Perhaps should check at this point that the
564
# history of the revision is actually present?
565
p = self.pending_merges()
567
for rev_id in revision_ids:
573
self.set_pending_merges(p)
576
def pending_merges(self):
577
"""Return a list of pending merges.
579
These are revisions that have been merged into the working
580
directory but not yet committed.
583
merges_file = self._control_files.get_utf8('pending-merges')
585
if e.errno != errno.ENOENT:
589
for l in merges_file.readlines():
590
p.append(l.rstrip('\n'))
594
def set_pending_merges(self, rev_list):
595
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
597
def get_symlink_target(self, file_id):
598
return os.readlink(self.id2abspath(file_id))
600
def file_class(self, filename):
601
if self.path2id(filename):
603
elif self.is_ignored(filename):
608
def list_files(self):
609
"""Recursively list all files as (path, class, kind, id).
611
Lists, but does not descend into unversioned directories.
613
This does not include files that have been deleted in this
616
Skips the control directory.
618
inv = self._inventory
620
def descend(from_dir_relpath, from_dir_id, dp):
624
## TODO: If we find a subdirectory with its own .bzr
625
## directory, then that is a separate tree and we
626
## should exclude it.
628
# the bzrdir for this tree
629
if self.bzrdir.transport.base.endswith(f + '/'):
633
fp = appendpath(from_dir_relpath, f)
636
fap = appendpath(dp, f)
638
f_ie = inv.get_child(from_dir_id, f)
641
elif self.is_ignored(fp):
650
raise BzrCheckError("file %r entered as kind %r id %r, "
652
% (fap, f_ie.kind, f_ie.file_id, fk))
654
# make a last minute entry
658
if fk == 'directory':
659
entry = TreeDirectory()
662
elif fk == 'symlink':
667
yield fp, c, fk, (f_ie and f_ie.file_id), entry
669
if fk != 'directory':
673
# don't descend unversioned directories
676
for ff in descend(fp, f_ie.file_id, fap):
679
for f in descend(u'', inv.root.file_id, self.basedir):
683
def move(self, from_paths, to_name):
686
to_name must exist in the inventory.
688
If to_name exists and is a directory, the files are moved into
689
it, keeping their old names.
691
Note that to_name is only the last component of the new name;
692
this doesn't change the directory.
694
This returns a list of (from_path, to_path) pairs for each
698
## TODO: Option to move IDs only
699
assert not isinstance(from_paths, basestring)
701
to_abs = self.abspath(to_name)
702
if not isdir(to_abs):
703
raise BzrError("destination %r is not a directory" % to_abs)
704
if not self.has_filename(to_name):
705
raise BzrError("destination %r not in working directory" % to_abs)
706
to_dir_id = inv.path2id(to_name)
707
if to_dir_id == None and to_name != '':
708
raise BzrError("destination %r is not a versioned directory" % to_name)
709
to_dir_ie = inv[to_dir_id]
710
if to_dir_ie.kind not in ('directory', 'root_directory'):
711
raise BzrError("destination %r is not a directory" % to_abs)
713
to_idpath = inv.get_idpath(to_dir_id)
716
if not self.has_filename(f):
717
raise BzrError("%r does not exist in working tree" % f)
718
f_id = inv.path2id(f)
720
raise BzrError("%r is not versioned" % f)
721
name_tail = splitpath(f)[-1]
722
dest_path = appendpath(to_name, name_tail)
723
if self.has_filename(dest_path):
724
raise BzrError("destination %r already exists" % dest_path)
725
if f_id in to_idpath:
726
raise BzrError("can't move %r to a subdirectory of itself" % f)
728
# OK, so there's a race here, it's possible that someone will
729
# create a file in this interval and then the rename might be
730
# left half-done. But we should have caught most problems.
731
orig_inv = deepcopy(self.inventory)
734
name_tail = splitpath(f)[-1]
735
dest_path = appendpath(to_name, name_tail)
736
result.append((f, dest_path))
737
inv.rename(inv.path2id(f), to_dir_id, name_tail)
739
rename(self.abspath(f), self.abspath(dest_path))
741
raise BzrError("failed to rename %r to %r: %s" %
742
(f, dest_path, e[1]),
743
["rename rolled back"])
745
# restore the inventory on error
746
self._set_inventory(orig_inv)
748
self._write_inventory(inv)
752
def rename_one(self, from_rel, to_rel):
755
This can change the directory or the filename or both.
758
if not self.has_filename(from_rel):
759
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
760
if self.has_filename(to_rel):
761
raise BzrError("can't rename: new working file %r already exists" % to_rel)
763
file_id = inv.path2id(from_rel)
765
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
768
from_parent = entry.parent_id
769
from_name = entry.name
771
if inv.path2id(to_rel):
772
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
774
to_dir, to_tail = os.path.split(to_rel)
775
to_dir_id = inv.path2id(to_dir)
776
if to_dir_id == None and to_dir != '':
777
raise BzrError("can't determine destination directory id for %r" % to_dir)
779
mutter("rename_one:")
780
mutter(" file_id {%s}" % file_id)
781
mutter(" from_rel %r" % from_rel)
782
mutter(" to_rel %r" % to_rel)
783
mutter(" to_dir %r" % to_dir)
784
mutter(" to_dir_id {%s}" % to_dir_id)
786
inv.rename(file_id, to_dir_id, to_tail)
788
from_abs = self.abspath(from_rel)
789
to_abs = self.abspath(to_rel)
791
rename(from_abs, to_abs)
793
inv.rename(file_id, from_parent, from_name)
794
raise BzrError("failed to rename %r to %r: %s"
795
% (from_abs, to_abs, e[1]),
796
["rename rolled back"])
797
self._write_inventory(inv)
801
"""Return all unknown files.
803
These are files in the working directory that are not versioned or
804
control files or ignored.
806
>>> from bzrlib.bzrdir import ScratchDir
807
>>> d = ScratchDir(files=['foo', 'foo~'])
808
>>> b = d.open_branch()
809
>>> tree = d.open_workingtree()
810
>>> map(str, tree.unknowns())
813
>>> list(b.unknowns())
815
>>> tree.remove('foo')
816
>>> list(b.unknowns())
819
for subp in self.extras():
820
if not self.is_ignored(subp):
823
def iter_conflicts(self):
825
for path in (s[0] for s in self.list_files()):
826
stem = get_conflicted_stem(path)
829
if stem not in conflicted:
834
def pull(self, source, overwrite=False, stop_revision=None):
837
old_revision_history = self.branch.revision_history()
838
count = self.branch.pull(source, overwrite, stop_revision)
839
new_revision_history = self.branch.revision_history()
840
if new_revision_history != old_revision_history:
841
if len(old_revision_history):
842
other_revision = old_revision_history[-1]
844
other_revision = None
845
repository = self.branch.repository
846
merge_inner(self.branch,
848
repository.revision_tree(other_revision),
850
self.set_last_revision(self.branch.last_revision())
856
"""Yield all unknown files in this WorkingTree.
858
If there are any unknown directories then only the directory is
859
returned, not all its children. But if there are unknown files
860
under a versioned subdirectory, they are returned.
862
Currently returned depth-first, sorted by name within directories.
864
## TODO: Work from given directory downwards
865
for path, dir_entry in self.inventory.directories():
866
mutter("search for unknowns in %r", path)
867
dirabs = self.abspath(path)
868
if not isdir(dirabs):
869
# e.g. directory deleted
873
for subf in os.listdir(dirabs):
875
and (subf not in dir_entry.children)):
880
subp = appendpath(path, subf)
884
def ignored_files(self):
885
"""Yield list of PATH, IGNORE_PATTERN"""
886
for subp in self.extras():
887
pat = self.is_ignored(subp)
892
def get_ignore_list(self):
893
"""Return list of ignore patterns.
895
Cached in the Tree object after the first call.
897
if hasattr(self, '_ignorelist'):
898
return self._ignorelist
900
l = bzrlib.DEFAULT_IGNORE[:]
901
if self.has_filename(bzrlib.IGNORE_FILENAME):
902
f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
903
l.extend([line.rstrip("\n\r") for line in f.readlines()])
908
def is_ignored(self, filename):
909
r"""Check whether the filename matches an ignore pattern.
911
Patterns containing '/' or '\' need to match the whole path;
912
others match against only the last component.
914
If the file is ignored, returns the pattern which caused it to
915
be ignored, otherwise None. So this can simply be used as a
916
boolean if desired."""
918
# TODO: Use '**' to match directories, and other extended
919
# globbing stuff from cvs/rsync.
921
# XXX: fnmatch is actually not quite what we want: it's only
922
# approximately the same as real Unix fnmatch, and doesn't
923
# treat dotfiles correctly and allows * to match /.
924
# Eventually it should be replaced with something more
927
for pat in self.get_ignore_list():
928
if '/' in pat or '\\' in pat:
930
# as a special case, you can put ./ at the start of a
931
# pattern; this is good to match in the top-level
934
if (pat[:2] == './') or (pat[:2] == '.\\'):
938
if fnmatch.fnmatchcase(filename, newpat):
941
if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
946
def kind(self, file_id):
947
return file_kind(self.id2abspath(file_id))
950
def last_revision(self):
951
"""Return the last revision id of this working tree.
953
In early branch formats this was == the branch last_revision,
954
but that cannot be relied upon - for working tree operations,
955
always use tree.last_revision().
957
return self.branch.last_revision()
960
"""See Branch.lock_read, and WorkingTree.unlock."""
961
self.branch.lock_read()
963
return self._control_files.lock_read()
968
def lock_write(self):
969
"""See Branch.lock_write, and WorkingTree.unlock."""
970
self.branch.lock_write()
972
return self._control_files.lock_write()
977
def _basis_inventory_name(self, revision_id):
978
return 'basis-inventory.%s' % revision_id
981
def set_last_revision(self, new_revision, old_revision=None):
982
"""Change the last revision in the working tree."""
983
self._remove_old_basis(old_revision)
984
if self._change_last_revision(new_revision):
985
self._cache_basis_inventory(new_revision)
987
def _change_last_revision(self, new_revision):
988
"""Template method part of set_last_revision to perform the change."""
989
if new_revision is None:
990
self.branch.set_revision_history([])
992
# current format is locked in with the branch
993
revision_history = self.branch.revision_history()
995
position = revision_history.index(new_revision)
997
raise errors.NoSuchRevision(self.branch, new_revision)
998
self.branch.set_revision_history(revision_history[:position + 1])
1001
def _cache_basis_inventory(self, new_revision):
1002
"""Cache new_revision as the basis inventory."""
1004
xml = self.branch.repository.get_inventory_xml(new_revision)
1005
path = self._basis_inventory_name(new_revision)
1006
self._control_files.put_utf8(path, xml)
1007
except WeaveRevisionNotPresent:
1010
def _remove_old_basis(self, old_revision):
1011
"""Remove the old basis inventory 'old_revision'."""
1012
if old_revision is not None:
1014
path = self._basis_inventory_name(old_revision)
1015
path = self._control_files._escape(path)
1016
self._control_files._transport.delete(path)
1020
def read_basis_inventory(self, revision_id):
1021
"""Read the cached basis inventory."""
1022
path = self._basis_inventory_name(revision_id)
1023
return self._control_files.get_utf8(path).read()
1026
def read_working_inventory(self):
1027
"""Read the working inventory."""
1028
# ElementTree does its own conversion from UTF-8, so open in
1030
result = bzrlib.xml5.serializer_v5.read_inventory(
1031
self._control_files.get('inventory'))
1032
self._set_inventory(result)
1036
def remove(self, files, verbose=False):
1037
"""Remove nominated files from the working inventory..
1039
This does not remove their text. This does not run on XXX on what? RBC
1041
TODO: Refuse to remove modified files unless --force is given?
1043
TODO: Do something useful with directories.
1045
TODO: Should this remove the text or not? Tough call; not
1046
removing may be useful and the user can just use use rm, and
1047
is the opposite of add. Removing it is consistent with most
1048
other tools. Maybe an option.
1050
## TODO: Normalize names
1051
## TODO: Remove nested loops; better scalability
1052
if isinstance(files, basestring):
1055
inv = self.inventory
1057
# do this before any modifications
1059
fid = inv.path2id(f)
1061
# TODO: Perhaps make this just a warning, and continue?
1062
# This tends to happen when
1063
raise NotVersionedError(path=f)
1064
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1066
# having remove it, it must be either ignored or unknown
1067
if self.is_ignored(f):
1071
show_status(new_status, inv[fid].kind, quotefn(f))
1074
self._write_inventory(inv)
1077
def revert(self, filenames, old_tree=None, backups=True):
1078
from bzrlib.merge import merge_inner
1079
if old_tree is None:
1080
old_tree = self.basis_tree()
1081
merge_inner(self.branch, old_tree,
1082
self, ignore_zero=True,
1083
backup_files=backups,
1084
interesting_files=filenames,
1086
if not len(filenames):
1087
self.set_pending_merges([])
1090
def set_inventory(self, new_inventory_list):
1091
from bzrlib.inventory import (Inventory,
1096
inv = Inventory(self.get_root_id())
1097
for path, file_id, parent, kind in new_inventory_list:
1098
name = os.path.basename(path)
1101
# fixme, there should be a factory function inv,add_??
1102
if kind == 'directory':
1103
inv.add(InventoryDirectory(file_id, name, parent))
1104
elif kind == 'file':
1105
inv.add(InventoryFile(file_id, name, parent))
1106
elif kind == 'symlink':
1107
inv.add(InventoryLink(file_id, name, parent))
1109
raise BzrError("unknown kind %r" % kind)
1110
self._write_inventory(inv)
1113
def set_root_id(self, file_id):
1114
"""Set the root id for this tree."""
1115
inv = self.read_working_inventory()
1116
orig_root_id = inv.root.file_id
1117
del inv._byid[inv.root.file_id]
1118
inv.root.file_id = file_id
1119
inv._byid[inv.root.file_id] = inv.root
1122
if entry.parent_id == orig_root_id:
1123
entry.parent_id = inv.root.file_id
1124
self._write_inventory(inv)
1127
"""See Branch.unlock.
1129
WorkingTree locking just uses the Branch locking facilities.
1130
This is current because all working trees have an embedded branch
1131
within them. IF in the future, we were to make branch data shareable
1132
between multiple working trees, i.e. via shared storage, then we
1133
would probably want to lock both the local tree, and the branch.
1135
# FIXME: We want to write out the hashcache only when the last lock on
1136
# this working copy is released. Peeking at the lock count is a bit
1137
# of a nasty hack; probably it's better to have a transaction object,
1138
# which can do some finalization when it's either successfully or
1139
# unsuccessfully completed. (Denys's original patch did that.)
1140
# RBC 20060206 hookinhg into transaction will couple lock and transaction
1141
# wrongly. Hookinh into unllock on the control files object is fine though.
1143
# TODO: split this per format so there is no ugly if block
1144
if self._hashcache.needs_write and (
1145
# dedicated lock files
1146
self._control_files._lock_count==1 or
1148
(self._control_files is self.branch.control_files and
1149
self._control_files._lock_count==3)):
1150
self._hashcache.write()
1151
# reverse order of locking.
1152
result = self._control_files.unlock()
1154
self.branch.unlock()
1160
self.branch.lock_read()
1162
if self.last_revision() == self.branch.last_revision():
1164
basis = self.basis_tree()
1165
to_tree = self.branch.basis_tree()
1166
result = merge_inner(self.branch,
1170
self.set_last_revision(self.branch.last_revision())
1173
self.branch.unlock()
1176
def _write_inventory(self, inv):
1177
"""Write inventory as the current inventory."""
1179
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1181
self._control_files.put('inventory', sio)
1182
self._set_inventory(inv)
1183
mutter('wrote working inventory')
1186
class WorkingTree3(WorkingTree):
1187
"""This is the Format 3 working tree.
1189
This differs from the base WorkingTree by:
1190
- having its own file lock
1191
- having its own last-revision property.
1195
def last_revision(self):
1196
"""See WorkingTree.last_revision."""
1198
return self._control_files.get_utf8('last-revision').read()
1202
def _change_last_revision(self, revision_id):
1203
"""See WorkingTree._change_last_revision."""
1204
if revision_id is None or revision_id == NULL_REVISION:
1206
self._control_files._transport.delete('last-revision')
1207
except errors.NoSuchFile:
1212
self.branch.revision_history().index(revision_id)
1214
raise errors.NoSuchRevision(self.branch, revision_id)
1215
self._control_files.put_utf8('last-revision', revision_id)
1219
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1220
def get_conflicted_stem(path):
1221
for suffix in CONFLICT_SUFFIXES:
1222
if path.endswith(suffix):
1223
return path[:-len(suffix)]
1225
@deprecated_function(zero_eight)
1226
def is_control_file(filename):
1227
"""See WorkingTree.is_control_filename(filename)."""
1228
## FIXME: better check
1229
filename = normpath(filename)
1230
while filename != '':
1231
head, tail = os.path.split(filename)
1232
## mutter('check %r for control file' % ((head, tail),))
1235
if filename == head:
1241
class WorkingTreeFormat(object):
1242
"""An encapsulation of the initialization and open routines for a format.
1244
Formats provide three things:
1245
* An initialization routine,
1249
Formats are placed in an dict by their format string for reference
1250
during workingtree opening. Its not required that these be instances, they
1251
can be classes themselves with class methods - it simply depends on
1252
whether state is needed for a given format or not.
1254
Once a format is deprecated, just deprecate the initialize and open
1255
methods on the format class. Do not deprecate the object, as the
1256
object will be created every time regardless.
1259
_default_format = None
1260
"""The default format used for new trees."""
1263
"""The known formats."""
1266
def find_format(klass, a_bzrdir):
1267
"""Return the format for the working tree object in a_bzrdir."""
1269
transport = a_bzrdir.get_workingtree_transport(None)
1270
format_string = transport.get("format").read()
1271
return klass._formats[format_string]
1273
raise errors.NoWorkingTree(base=transport.base)
1275
raise errors.UnknownFormatError(format_string)
1278
def get_default_format(klass):
1279
"""Return the current default format."""
1280
return klass._default_format
1282
def get_format_string(self):
1283
"""Return the ASCII format string that identifies this format."""
1284
raise NotImplementedError(self.get_format_string)
1286
def is_supported(self):
1287
"""Is this format supported?
1289
Supported formats can be initialized and opened.
1290
Unsupported formats may not support initialization or committing or
1291
some other features depending on the reason for not being supported.
1296
def register_format(klass, format):
1297
klass._formats[format.get_format_string()] = format
1300
def set_default_format(klass, format):
1301
klass._default_format = format
1304
def unregister_format(klass, format):
1305
assert klass._formats[format.get_format_string()] is format
1306
del klass._formats[format.get_format_string()]
1310
class WorkingTreeFormat2(WorkingTreeFormat):
1311
"""The second working tree format.
1313
This format modified the hash cache from the format 1 hash cache.
1316
def initialize(self, a_bzrdir, revision_id=None):
1317
"""See WorkingTreeFormat.initialize()."""
1318
if not isinstance(a_bzrdir.transport, LocalTransport):
1319
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1320
branch = a_bzrdir.open_branch()
1321
if revision_id is not None:
1324
revision_history = branch.revision_history()
1326
position = revision_history.index(revision_id)
1328
raise errors.NoSuchRevision(branch, revision_id)
1329
branch.set_revision_history(revision_history[:position + 1])
1332
revision = branch.last_revision()
1333
basis_tree = branch.repository.revision_tree(revision)
1334
inv = basis_tree.inventory
1335
wt = WorkingTree(a_bzrdir.root_transport.base,
1341
wt._write_inventory(inv)
1342
wt.set_root_id(inv.root.file_id)
1343
wt.set_last_revision(revision)
1344
wt.set_pending_merges([])
1349
super(WorkingTreeFormat2, self).__init__()
1350
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1352
def open(self, a_bzrdir, _found=False):
1353
"""Return the WorkingTree object for a_bzrdir
1355
_found is a private parameter, do not use it. It is used to indicate
1356
if format probing has already been done.
1359
# we are being called directly and must probe.
1360
raise NotImplementedError
1361
if not isinstance(a_bzrdir.transport, LocalTransport):
1362
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1363
return WorkingTree(a_bzrdir.root_transport.base,
1369
class WorkingTreeFormat3(WorkingTreeFormat):
1370
"""The second working tree format updated to record a format marker.
1372
This format modified the hash cache from the format 1 hash cache.
1375
def get_format_string(self):
1376
"""See WorkingTreeFormat.get_format_string()."""
1377
return "Bazaar-NG Working Tree format 3"
1379
def initialize(self, a_bzrdir, revision_id=None):
1380
"""See WorkingTreeFormat.initialize().
1382
revision_id allows creating a working tree at a differnet
1383
revision than the branch is at.
1385
if not isinstance(a_bzrdir.transport, LocalTransport):
1386
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1387
transport = a_bzrdir.get_workingtree_transport(self)
1388
control_files = LockableFiles(transport, 'lock')
1389
control_files.put_utf8('format', self.get_format_string())
1390
branch = a_bzrdir.open_branch()
1391
if revision_id is None:
1392
revision_id = branch.last_revision()
1393
new_basis_tree = branch.repository.revision_tree(revision_id)
1394
inv = new_basis_tree.inventory
1395
wt = WorkingTree3(a_bzrdir.root_transport.base,
1401
wt._write_inventory(inv)
1402
wt.set_root_id(inv.root.file_id)
1403
wt.set_last_revision(revision_id)
1404
wt.set_pending_merges([])
1409
super(WorkingTreeFormat3, self).__init__()
1410
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1412
def open(self, a_bzrdir, _found=False):
1413
"""Return the WorkingTree object for a_bzrdir
1415
_found is a private parameter, do not use it. It is used to indicate
1416
if format probing has already been done.
1419
# we are being called directly and must probe.
1420
raise NotImplementedError
1421
if not isinstance(a_bzrdir.transport, LocalTransport):
1422
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1423
return WorkingTree3(a_bzrdir.root_transport.base,
1429
return self.get_format_string()
1432
# formats which have no format string are not discoverable
1433
# and not independently creatable, so are not registered.
1434
__default_format = WorkingTreeFormat3()
1435
WorkingTreeFormat.register_format(__default_format)
1436
WorkingTreeFormat.set_default_format(__default_format)
1437
_legacy_formats = [WorkingTreeFormat2(),
1441
class WorkingTreeTestProviderAdapter(object):
1442
"""A tool to generate a suite testing multiple workingtree formats at once.
1444
This is done by copying the test once for each transport and injecting
1445
the transport_server, transport_readonly_server, and workingtree_format
1446
classes into each copy. Each copy is also given a new id() to make it
1450
def __init__(self, transport_server, transport_readonly_server, formats):
1451
self._transport_server = transport_server
1452
self._transport_readonly_server = transport_readonly_server
1453
self._formats = formats
1455
def adapt(self, test):
1456
from bzrlib.tests import TestSuite
1457
result = TestSuite()
1458
for workingtree_format, bzrdir_format in self._formats:
1459
new_test = deepcopy(test)
1460
new_test.transport_server = self._transport_server
1461
new_test.transport_readonly_server = self._transport_readonly_server
1462
new_test.bzrdir_format = bzrdir_format
1463
new_test.workingtree_format = workingtree_format
1464
def make_new_test_id():
1465
new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1466
return lambda: new_id
1467
new_test.id = make_new_test_id()
1468
result.addTest(new_test)