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).
32
MERGE_MODIFIED_HEADER_1 = "BZR merge-modified list format 1"
34
# FIXME: I don't know if writing out the cache from the destructor is really a
35
# good idea, because destructors are considered poor taste in Python, and it's
36
# not predictable when it will be written out.
38
# TODO: Give the workingtree sole responsibility for the working inventory;
39
# remove the variable and references to it from the branch. This may require
40
# updating the commit code so as to update the inventory within the working
41
# copy, and making sure there's only one WorkingTree for any directory on disk.
42
# At the momenthey may alias the inventory and have old copies of it in memory.
44
from copy import deepcopy
45
from cStringIO import StringIO
52
from bzrlib.atomicfile import AtomicFile
53
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, Inventory
66
from bzrlib.lockable_files import LockableFiles, TransportLock
67
from bzrlib.merge import merge_inner, transform_tree
68
from bzrlib.osutils import (
86
from bzrlib.progress import DummyProgress
87
from bzrlib.revision import NULL_REVISION
88
from bzrlib.rio import RioReader, RioWriter, Stanza
89
from bzrlib.symbol_versioning import *
90
from bzrlib.textui import show_status
92
from bzrlib.trace import mutter
93
from bzrlib.transform import build_tree
94
from bzrlib.transport import get_transport
95
from bzrlib.transport.local import LocalTransport
100
def gen_file_id(name):
101
"""Return new file id.
103
This should probably generate proper UUIDs, but for the moment we
104
cope with just randomness because running uuidgen every time is
107
from binascii import hexlify
108
from time import time
111
idx = name.rfind('/')
113
name = name[idx+1 : ]
114
idx = name.rfind('\\')
116
name = name[idx+1 : ]
118
# make it not a hidden file
119
name = name.lstrip('.')
121
# remove any wierd characters; we don't escape them but rather
123
name = re.sub(r'[^\w.]', '', name)
125
s = hexlify(rand_bytes(8))
126
return '-'.join((name, compact_date(time()), s))
130
"""Return a new tree-root file id."""
131
return gen_file_id('TREE_ROOT')
134
class TreeEntry(object):
135
"""An entry that implements the minium interface used by commands.
137
This needs further inspection, it may be better to have
138
InventoryEntries without ids - though that seems wrong. For now,
139
this is a parallel hierarchy to InventoryEntry, and needs to become
140
one of several things: decorates to that hierarchy, children of, or
142
Another note is that these objects are currently only used when there is
143
no InventoryEntry available - i.e. for unversioned objects.
144
Perhaps they should be UnversionedEntry et al. ? - RBC 20051003
147
def __eq__(self, other):
148
# yes, this us ugly, TODO: best practice __eq__ style.
149
return (isinstance(other, TreeEntry)
150
and other.__class__ == self.__class__)
152
def kind_character(self):
156
class TreeDirectory(TreeEntry):
157
"""See TreeEntry. This is a directory in a working tree."""
159
def __eq__(self, other):
160
return (isinstance(other, TreeDirectory)
161
and other.__class__ == self.__class__)
163
def kind_character(self):
167
class TreeFile(TreeEntry):
168
"""See TreeEntry. This is a regular file in a working tree."""
170
def __eq__(self, other):
171
return (isinstance(other, TreeFile)
172
and other.__class__ == self.__class__)
174
def kind_character(self):
178
class TreeLink(TreeEntry):
179
"""See TreeEntry. This is a symlink in a working tree."""
181
def __eq__(self, other):
182
return (isinstance(other, TreeLink)
183
and other.__class__ == self.__class__)
185
def kind_character(self):
189
class WorkingTree(bzrlib.tree.Tree):
190
"""Working copy tree.
192
The inventory is held in the `Branch` working-inventory, and the
193
files are in a directory on disk.
195
It is possible for a `WorkingTree` to have a filename which is
196
not listed in the Inventory and vice versa.
199
def __init__(self, basedir='.',
200
branch=DEPRECATED_PARAMETER,
206
"""Construct a WorkingTree for basedir.
208
If the branch is not supplied, it is opened automatically.
209
If the branch is supplied, it must be the branch for this basedir.
210
(branch.base is not cross checked, because for remote branches that
211
would be meaningless).
213
self._format = _format
214
self.bzrdir = _bzrdir
216
# not created via open etc.
217
warn("WorkingTree() is deprecated as of bzr version 0.8. "
218
"Please use bzrdir.open_workingtree or WorkingTree.open().",
221
wt = WorkingTree.open(basedir)
222
self.branch = wt.branch
223
self.basedir = wt.basedir
224
self._control_files = wt._control_files
225
self._hashcache = wt._hashcache
226
self._set_inventory(wt._inventory)
227
self._format = wt._format
228
self.bzrdir = wt.bzrdir
229
from bzrlib.hashcache import HashCache
230
from bzrlib.trace import note, mutter
231
assert isinstance(basedir, basestring), \
232
"base directory %r is not a string" % basedir
233
basedir = safe_unicode(basedir)
234
mutter("opening working tree %r", basedir)
235
if deprecated_passed(branch):
237
warn("WorkingTree(..., branch=XXX) is deprecated as of bzr 0.8."
238
" Please use bzrdir.open_workingtree() or WorkingTree.open().",
244
self.branch = self.bzrdir.open_branch()
245
assert isinstance(self.branch, Branch), \
246
"branch %r is not a Branch" % self.branch
247
self.basedir = realpath(basedir)
248
# if branch is at our basedir and is a format 6 or less
249
if isinstance(self._format, WorkingTreeFormat2):
250
# share control object
251
self._control_files = self.branch.control_files
252
elif _control_files is not None:
253
assert False, "not done yet"
254
# self._control_files = _control_files
256
# only ready for format 3
257
assert isinstance(self._format, WorkingTreeFormat3)
258
self._control_files = LockableFiles(
259
self.bzrdir.get_workingtree_transport(None),
260
'lock', TransportLock)
262
# update the whole cache up front and write to disk if anything changed;
263
# in the future we might want to do this more selectively
264
# two possible ways offer themselves : in self._unlock, write the cache
265
# if needed, or, when the cache sees a change, append it to the hash
266
# cache file, and have the parser take the most recent entry for a
268
cache_filename = self.bzrdir.get_workingtree_transport(None).abspath('stat-cache')
269
hc = self._hashcache = HashCache(basedir, cache_filename, self._control_files._file_mode)
271
# is this scan needed ? it makes things kinda slow.
278
if _inventory is None:
279
self._set_inventory(self.read_working_inventory())
281
self._set_inventory(_inventory)
283
def _set_inventory(self, inv):
284
self._inventory = inv
285
self.path2id = self._inventory.path2id
287
def is_control_filename(self, filename):
288
"""True if filename is the name of a control file in this tree.
290
This is true IF and ONLY IF the filename is part of the meta data
291
that bzr controls in this tree. I.E. a random .bzr directory placed
292
on disk will not be a control file for this tree.
295
self.bzrdir.transport.relpath(self.abspath(filename))
297
except errors.PathNotChild:
301
def open(path=None, _unsupported=False):
302
"""Open an existing working tree at path.
306
path = os.path.getcwdu()
307
control = bzrdir.BzrDir.open(path, _unsupported)
308
return control.open_workingtree(_unsupported)
311
def open_containing(path=None):
312
"""Open an existing working tree which has its root about path.
314
This probes for a working tree at path and searches upwards from there.
316
Basically we keep looking up until we find the control directory or
317
run into /. If there isn't one, raises NotBranchError.
318
TODO: give this a new exception.
319
If there is one, it is returned, along with the unused portion of path.
323
control, relpath = bzrdir.BzrDir.open_containing(path)
324
return control.open_workingtree(), relpath
327
def open_downlevel(path=None):
328
"""Open an unsupported working tree.
330
Only intended for advanced situations like upgrading part of a bzrdir.
332
return WorkingTree.open(path, _unsupported=True)
335
"""Iterate through file_ids for this tree.
337
file_ids are in a WorkingTree if they are in the working inventory
338
and the working file exists.
340
inv = self._inventory
341
for path, ie in inv.iter_entries():
342
if bzrlib.osutils.lexists(self.abspath(path)):
346
return "<%s of %s>" % (self.__class__.__name__,
347
getattr(self, 'basedir', None))
349
def abspath(self, filename):
350
return pathjoin(self.basedir, filename)
352
def basis_tree(self):
353
"""Return RevisionTree for the current last revision."""
354
revision_id = self.last_revision()
355
if revision_id is not None:
357
xml = self.read_basis_inventory(revision_id)
358
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
359
return bzrlib.tree.RevisionTree(self.branch.repository, inv,
363
return self.branch.repository.revision_tree(revision_id)
366
@deprecated_method(zero_eight)
367
def create(branch, directory):
368
"""Create a workingtree for branch at directory.
370
If existing_directory already exists it must have a .bzr directory.
371
If it does not exist, it will be created.
373
This returns a new WorkingTree object for the new checkout.
375
TODO FIXME RBC 20060124 when we have checkout formats in place this
376
should accept an optional revisionid to checkout [and reject this if
377
checking out into the same dir as a pre-checkout-aware branch format.]
379
XXX: When BzrDir is present, these should be created through that
382
warn('delete WorkingTree.create', stacklevel=3)
383
transport = get_transport(directory)
384
if branch.bzrdir.root_transport.base == transport.base:
386
return branch.bzrdir.create_workingtree()
387
# different directory,
388
# create a branch reference
389
# and now a working tree.
390
raise NotImplementedError
393
@deprecated_method(zero_eight)
394
def create_standalone(directory):
395
"""Create a checkout and a branch and a repo at directory.
397
Directory must exist and be empty.
399
please use BzrDir.create_standalone_workingtree
401
return bzrdir.BzrDir.create_standalone_workingtree(directory)
403
def relpath(self, abs):
404
"""Return the local path portion from a given absolute path."""
405
return relpath(self.basedir, abs)
407
def has_filename(self, filename):
408
return bzrlib.osutils.lexists(self.abspath(filename))
410
def get_file(self, file_id):
411
return self.get_file_byname(self.id2path(file_id))
413
def get_file_byname(self, filename):
414
return file(self.abspath(filename), 'rb')
416
def get_root_id(self):
417
"""Return the id of this trees root"""
418
inv = self.read_working_inventory()
419
return inv.root.file_id
421
def _get_store_filename(self, file_id):
422
## XXX: badly named; this is not in the store at all
423
return self.abspath(self.id2path(file_id))
426
def clone(self, to_bzrdir, revision_id=None, basis=None):
427
"""Duplicate this working tree into to_bzr, including all state.
429
Specifically modified files are kept as modified, but
430
ignored and unknown files are discarded.
432
If you want to make a new line of development, see bzrdir.sprout()
435
If not None, the cloned tree will have its last revision set to
436
revision, and and difference between the source trees last revision
437
and this one merged in.
440
If not None, a closer copy of a tree which may have some files in
441
common, and which file content should be preferentially copied from.
443
# assumes the target bzr dir format is compatible.
444
result = self._format.initialize(to_bzrdir)
445
self.copy_content_into(result, revision_id)
449
def copy_content_into(self, tree, revision_id=None):
450
"""Copy the current content and user files of this tree into tree."""
451
if revision_id is None:
452
transform_tree(tree, self)
454
# TODO now merge from tree.last_revision to revision
455
transform_tree(tree, self)
456
tree.set_last_revision(revision_id)
459
def commit(self, message=None, revprops=None, *args, **kwargs):
460
# avoid circular imports
461
from bzrlib.commit import Commit
464
if not 'branch-nick' in revprops:
465
revprops['branch-nick'] = self.branch.nick
466
# args for wt.commit start at message from the Commit.commit method,
467
# but with branch a kwarg now, passing in args as is results in the
468
#message being used for the branch
469
args = (DEPRECATED_PARAMETER, message, ) + args
470
Commit().commit(working_tree=self, revprops=revprops, *args, **kwargs)
471
self._set_inventory(self.read_working_inventory())
473
def id2abspath(self, file_id):
474
return self.abspath(self.id2path(file_id))
476
def has_id(self, file_id):
477
# files that have been deleted are excluded
478
inv = self._inventory
479
if not inv.has_id(file_id):
481
path = inv.id2path(file_id)
482
return bzrlib.osutils.lexists(self.abspath(path))
484
def has_or_had_id(self, file_id):
485
if file_id == self.inventory.root.file_id:
487
return self.inventory.has_id(file_id)
489
__contains__ = has_id
491
def get_file_size(self, file_id):
492
return os.path.getsize(self.id2abspath(file_id))
495
def get_file_sha1(self, file_id):
496
path = self._inventory.id2path(file_id)
497
return self._hashcache.get_sha1(path)
499
def is_executable(self, file_id):
500
if not supports_executable():
501
return self._inventory[file_id].executable
503
path = self._inventory.id2path(file_id)
504
mode = os.lstat(self.abspath(path)).st_mode
505
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
508
def add(self, files, ids=None):
509
"""Make files versioned.
511
Note that the command line normally calls smart_add instead,
512
which can automatically recurse.
514
This adds the files to the inventory, so that they will be
515
recorded by the next commit.
518
List of paths to add, relative to the base of the tree.
521
If set, use these instead of automatically generated ids.
522
Must be the same length as the list of files, but may
523
contain None for ids that are to be autogenerated.
525
TODO: Perhaps have an option to add the ids even if the files do
528
TODO: Perhaps callback with the ids and paths as they're added.
530
# TODO: Re-adding a file that is removed in the working copy
531
# should probably put it back with the previous ID.
532
if isinstance(files, basestring):
533
assert(ids is None or isinstance(ids, basestring))
539
ids = [None] * len(files)
541
assert(len(ids) == len(files))
543
inv = self.read_working_inventory()
544
for f,file_id in zip(files, ids):
545
if self.is_control_filename(f):
546
raise BzrError("cannot add control file %s" % quotefn(f))
551
raise BzrError("cannot add top-level %r" % f)
553
fullpath = normpath(self.abspath(f))
556
kind = file_kind(fullpath)
558
if e.errno == errno.ENOENT:
559
raise NoSuchFile(fullpath)
560
# maybe something better?
561
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
563
if not InventoryEntry.versionable_kind(kind):
564
raise BzrError('cannot add: not a versionable file ('
565
'i.e. regular file, symlink or directory): %s' % quotefn(f))
568
file_id = gen_file_id(f)
569
inv.add_path(f, kind=kind, file_id=file_id)
571
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
572
self._write_inventory(inv)
575
def add_pending_merge(self, *revision_ids):
576
# TODO: Perhaps should check at this point that the
577
# history of the revision is actually present?
578
p = self.pending_merges()
580
for rev_id in revision_ids:
586
self.set_pending_merges(p)
589
def pending_merges(self):
590
"""Return a list of pending merges.
592
These are revisions that have been merged into the working
593
directory but not yet committed.
596
merges_file = self._control_files.get_utf8('pending-merges')
598
if e.errno != errno.ENOENT:
602
for l in merges_file.readlines():
603
p.append(l.rstrip('\n'))
607
def set_pending_merges(self, rev_list):
608
self._control_files.put_utf8('pending-merges', '\n'.join(rev_list))
611
def set_merge_modified(self, modified_hashes):
613
my_file.write(MERGE_MODIFIED_HEADER_1 + '\n')
614
writer = RioWriter(my_file)
615
for file_id, hash in modified_hashes.iteritems():
616
s = Stanza(file_id=file_id, hash=hash)
617
writer.write_stanza(s)
619
self._control_files.put('merge-hashes', my_file)
622
def merge_modified(self):
624
hashfile = self._control_files.get('merge-hashes')
629
if hashfile.next() != MERGE_MODIFIED_HEADER_1 + '\n':
630
raise MergeModifiedFormatError()
631
except StopIteration:
632
raise MergeModifiedFormatError()
633
for s in RioReader(hashfile):
634
file_id = s.get("file_id")
636
if hash == self.get_file_sha1(file_id):
637
merge_hashes[file_id] = hash
640
def get_symlink_target(self, file_id):
641
return os.readlink(self.id2abspath(file_id))
643
def file_class(self, filename):
644
if self.path2id(filename):
646
elif self.is_ignored(filename):
651
def list_files(self):
652
"""Recursively list all files as (path, class, kind, id).
654
Lists, but does not descend into unversioned directories.
656
This does not include files that have been deleted in this
659
Skips the control directory.
661
inv = self._inventory
663
def descend(from_dir_relpath, from_dir_id, dp):
667
## TODO: If we find a subdirectory with its own .bzr
668
## directory, then that is a separate tree and we
669
## should exclude it.
671
# the bzrdir for this tree
672
if self.bzrdir.transport.base.endswith(f + '/'):
676
fp = appendpath(from_dir_relpath, f)
679
fap = appendpath(dp, f)
681
f_ie = inv.get_child(from_dir_id, f)
684
elif self.is_ignored(fp):
693
raise BzrCheckError("file %r entered as kind %r id %r, "
695
% (fap, f_ie.kind, f_ie.file_id, fk))
697
# make a last minute entry
701
if fk == 'directory':
702
entry = TreeDirectory()
705
elif fk == 'symlink':
710
yield fp, c, fk, (f_ie and f_ie.file_id), entry
712
if fk != 'directory':
716
# don't descend unversioned directories
719
for ff in descend(fp, f_ie.file_id, fap):
722
for f in descend(u'', inv.root.file_id, self.basedir):
726
def move(self, from_paths, to_name):
729
to_name must exist in the inventory.
731
If to_name exists and is a directory, the files are moved into
732
it, keeping their old names.
734
Note that to_name is only the last component of the new name;
735
this doesn't change the directory.
737
This returns a list of (from_path, to_path) pairs for each
741
## TODO: Option to move IDs only
742
assert not isinstance(from_paths, basestring)
744
to_abs = self.abspath(to_name)
745
if not isdir(to_abs):
746
raise BzrError("destination %r is not a directory" % to_abs)
747
if not self.has_filename(to_name):
748
raise BzrError("destination %r not in working directory" % to_abs)
749
to_dir_id = inv.path2id(to_name)
750
if to_dir_id == None and to_name != '':
751
raise BzrError("destination %r is not a versioned directory" % to_name)
752
to_dir_ie = inv[to_dir_id]
753
if to_dir_ie.kind not in ('directory', 'root_directory'):
754
raise BzrError("destination %r is not a directory" % to_abs)
756
to_idpath = inv.get_idpath(to_dir_id)
759
if not self.has_filename(f):
760
raise BzrError("%r does not exist in working tree" % f)
761
f_id = inv.path2id(f)
763
raise BzrError("%r is not versioned" % f)
764
name_tail = splitpath(f)[-1]
765
dest_path = appendpath(to_name, name_tail)
766
if self.has_filename(dest_path):
767
raise BzrError("destination %r already exists" % dest_path)
768
if f_id in to_idpath:
769
raise BzrError("can't move %r to a subdirectory of itself" % f)
771
# OK, so there's a race here, it's possible that someone will
772
# create a file in this interval and then the rename might be
773
# left half-done. But we should have caught most problems.
774
orig_inv = deepcopy(self.inventory)
777
name_tail = splitpath(f)[-1]
778
dest_path = appendpath(to_name, name_tail)
779
result.append((f, dest_path))
780
inv.rename(inv.path2id(f), to_dir_id, name_tail)
782
rename(self.abspath(f), self.abspath(dest_path))
784
raise BzrError("failed to rename %r to %r: %s" %
785
(f, dest_path, e[1]),
786
["rename rolled back"])
788
# restore the inventory on error
789
self._set_inventory(orig_inv)
791
self._write_inventory(inv)
795
def rename_one(self, from_rel, to_rel):
798
This can change the directory or the filename or both.
801
if not self.has_filename(from_rel):
802
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
803
if self.has_filename(to_rel):
804
raise BzrError("can't rename: new working file %r already exists" % to_rel)
806
file_id = inv.path2id(from_rel)
808
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
811
from_parent = entry.parent_id
812
from_name = entry.name
814
if inv.path2id(to_rel):
815
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
817
to_dir, to_tail = os.path.split(to_rel)
818
to_dir_id = inv.path2id(to_dir)
819
if to_dir_id == None and to_dir != '':
820
raise BzrError("can't determine destination directory id for %r" % to_dir)
822
mutter("rename_one:")
823
mutter(" file_id {%s}" % file_id)
824
mutter(" from_rel %r" % from_rel)
825
mutter(" to_rel %r" % to_rel)
826
mutter(" to_dir %r" % to_dir)
827
mutter(" to_dir_id {%s}" % to_dir_id)
829
inv.rename(file_id, to_dir_id, to_tail)
831
from_abs = self.abspath(from_rel)
832
to_abs = self.abspath(to_rel)
834
rename(from_abs, to_abs)
836
inv.rename(file_id, from_parent, from_name)
837
raise BzrError("failed to rename %r to %r: %s"
838
% (from_abs, to_abs, e[1]),
839
["rename rolled back"])
840
self._write_inventory(inv)
844
"""Return all unknown files.
846
These are files in the working directory that are not versioned or
847
control files or ignored.
849
>>> from bzrlib.bzrdir import ScratchDir
850
>>> d = ScratchDir(files=['foo', 'foo~'])
851
>>> b = d.open_branch()
852
>>> tree = d.open_workingtree()
853
>>> map(str, tree.unknowns())
856
>>> list(b.unknowns())
858
>>> tree.remove('foo')
859
>>> list(b.unknowns())
862
for subp in self.extras():
863
if not self.is_ignored(subp):
866
def iter_conflicts(self):
868
for path in (s[0] for s in self.list_files()):
869
stem = get_conflicted_stem(path)
872
if stem not in conflicted:
877
def pull(self, source, overwrite=False, stop_revision=None):
880
old_revision_history = self.branch.revision_history()
881
basis_tree = self.basis_tree()
882
count = self.branch.pull(source, overwrite, stop_revision)
883
new_revision_history = self.branch.revision_history()
884
if new_revision_history != old_revision_history:
885
if len(old_revision_history):
886
other_revision = old_revision_history[-1]
888
other_revision = None
889
repository = self.branch.repository
890
pb = bzrlib.ui.ui_factory.nested_progress_bar()
892
merge_inner(self.branch,
893
self.branch.basis_tree(),
899
self.set_last_revision(self.branch.last_revision())
905
"""Yield all unknown files in this WorkingTree.
907
If there are any unknown directories then only the directory is
908
returned, not all its children. But if there are unknown files
909
under a versioned subdirectory, they are returned.
911
Currently returned depth-first, sorted by name within directories.
913
## TODO: Work from given directory downwards
914
for path, dir_entry in self.inventory.directories():
915
mutter("search for unknowns in %r", path)
916
dirabs = self.abspath(path)
917
if not isdir(dirabs):
918
# e.g. directory deleted
922
for subf in os.listdir(dirabs):
924
and (subf not in dir_entry.children)):
929
subp = appendpath(path, subf)
933
def ignored_files(self):
934
"""Yield list of PATH, IGNORE_PATTERN"""
935
for subp in self.extras():
936
pat = self.is_ignored(subp)
941
def get_ignore_list(self):
942
"""Return list of ignore patterns.
944
Cached in the Tree object after the first call.
946
if hasattr(self, '_ignorelist'):
947
return self._ignorelist
949
l = bzrlib.DEFAULT_IGNORE[:]
950
if self.has_filename(bzrlib.IGNORE_FILENAME):
951
f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
952
l.extend([line.rstrip("\n\r") for line in f.readlines()])
957
def is_ignored(self, filename):
958
r"""Check whether the filename matches an ignore pattern.
960
Patterns containing '/' or '\' need to match the whole path;
961
others match against only the last component.
963
If the file is ignored, returns the pattern which caused it to
964
be ignored, otherwise None. So this can simply be used as a
965
boolean if desired."""
967
# TODO: Use '**' to match directories, and other extended
968
# globbing stuff from cvs/rsync.
970
# XXX: fnmatch is actually not quite what we want: it's only
971
# approximately the same as real Unix fnmatch, and doesn't
972
# treat dotfiles correctly and allows * to match /.
973
# Eventually it should be replaced with something more
976
for pat in self.get_ignore_list():
977
if '/' in pat or '\\' in pat:
979
# as a special case, you can put ./ at the start of a
980
# pattern; this is good to match in the top-level
983
if (pat[:2] == './') or (pat[:2] == '.\\'):
987
if fnmatch.fnmatchcase(filename, newpat):
990
if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
995
def kind(self, file_id):
996
return file_kind(self.id2abspath(file_id))
999
def last_revision(self):
1000
"""Return the last revision id of this working tree.
1002
In early branch formats this was == the branch last_revision,
1003
but that cannot be relied upon - for working tree operations,
1004
always use tree.last_revision().
1006
return self.branch.last_revision()
1008
def lock_read(self):
1009
"""See Branch.lock_read, and WorkingTree.unlock."""
1010
self.branch.lock_read()
1012
return self._control_files.lock_read()
1014
self.branch.unlock()
1017
def lock_write(self):
1018
"""See Branch.lock_write, and WorkingTree.unlock."""
1019
self.branch.lock_write()
1021
return self._control_files.lock_write()
1023
self.branch.unlock()
1026
def _basis_inventory_name(self, revision_id):
1027
return 'basis-inventory.%s' % revision_id
1030
def set_last_revision(self, new_revision, old_revision=None):
1031
"""Change the last revision in the working tree."""
1032
self._remove_old_basis(old_revision)
1033
if self._change_last_revision(new_revision):
1034
self._cache_basis_inventory(new_revision)
1036
def _change_last_revision(self, new_revision):
1037
"""Template method part of set_last_revision to perform the change."""
1038
if new_revision is None:
1039
self.branch.set_revision_history([])
1041
# current format is locked in with the branch
1042
revision_history = self.branch.revision_history()
1044
position = revision_history.index(new_revision)
1046
raise errors.NoSuchRevision(self.branch, new_revision)
1047
self.branch.set_revision_history(revision_history[:position + 1])
1050
def _cache_basis_inventory(self, new_revision):
1051
"""Cache new_revision as the basis inventory."""
1053
xml = self.branch.repository.get_inventory_xml(new_revision)
1054
path = self._basis_inventory_name(new_revision)
1055
self._control_files.put_utf8(path, xml)
1056
except WeaveRevisionNotPresent:
1059
def _remove_old_basis(self, old_revision):
1060
"""Remove the old basis inventory 'old_revision'."""
1061
if old_revision is not None:
1063
path = self._basis_inventory_name(old_revision)
1064
path = self._control_files._escape(path)
1065
self._control_files._transport.delete(path)
1069
def read_basis_inventory(self, revision_id):
1070
"""Read the cached basis inventory."""
1071
path = self._basis_inventory_name(revision_id)
1072
return self._control_files.get_utf8(path).read()
1075
def read_working_inventory(self):
1076
"""Read the working inventory."""
1077
# ElementTree does its own conversion from UTF-8, so open in
1079
result = bzrlib.xml5.serializer_v5.read_inventory(
1080
self._control_files.get('inventory'))
1081
self._set_inventory(result)
1085
def remove(self, files, verbose=False):
1086
"""Remove nominated files from the working inventory..
1088
This does not remove their text. This does not run on XXX on what? RBC
1090
TODO: Refuse to remove modified files unless --force is given?
1092
TODO: Do something useful with directories.
1094
TODO: Should this remove the text or not? Tough call; not
1095
removing may be useful and the user can just use use rm, and
1096
is the opposite of add. Removing it is consistent with most
1097
other tools. Maybe an option.
1099
## TODO: Normalize names
1100
## TODO: Remove nested loops; better scalability
1101
if isinstance(files, basestring):
1104
inv = self.inventory
1106
# do this before any modifications
1108
fid = inv.path2id(f)
1110
# TODO: Perhaps make this just a warning, and continue?
1111
# This tends to happen when
1112
raise NotVersionedError(path=f)
1113
mutter("remove inventory entry %s {%s}", quotefn(f), fid)
1115
# having remove it, it must be either ignored or unknown
1116
if self.is_ignored(f):
1120
show_status(new_status, inv[fid].kind, quotefn(f))
1123
self._write_inventory(inv)
1126
def revert(self, filenames, old_tree=None, backups=True,
1127
pb=DummyProgress()):
1128
from transform import revert
1129
if old_tree is None:
1130
old_tree = self.basis_tree()
1131
revert(self, old_tree, filenames, backups, pb)
1132
if not len(filenames):
1133
self.set_pending_merges([])
1136
def set_inventory(self, new_inventory_list):
1137
from bzrlib.inventory import (Inventory,
1142
inv = Inventory(self.get_root_id())
1143
for path, file_id, parent, kind in new_inventory_list:
1144
name = os.path.basename(path)
1147
# fixme, there should be a factory function inv,add_??
1148
if kind == 'directory':
1149
inv.add(InventoryDirectory(file_id, name, parent))
1150
elif kind == 'file':
1151
inv.add(InventoryFile(file_id, name, parent))
1152
elif kind == 'symlink':
1153
inv.add(InventoryLink(file_id, name, parent))
1155
raise BzrError("unknown kind %r" % kind)
1156
self._write_inventory(inv)
1159
def set_root_id(self, file_id):
1160
"""Set the root id for this tree."""
1161
inv = self.read_working_inventory()
1162
orig_root_id = inv.root.file_id
1163
del inv._byid[inv.root.file_id]
1164
inv.root.file_id = file_id
1165
inv._byid[inv.root.file_id] = inv.root
1168
if entry.parent_id == orig_root_id:
1169
entry.parent_id = inv.root.file_id
1170
self._write_inventory(inv)
1173
"""See Branch.unlock.
1175
WorkingTree locking just uses the Branch locking facilities.
1176
This is current because all working trees have an embedded branch
1177
within them. IF in the future, we were to make branch data shareable
1178
between multiple working trees, i.e. via shared storage, then we
1179
would probably want to lock both the local tree, and the branch.
1181
# FIXME: We want to write out the hashcache only when the last lock on
1182
# this working copy is released. Peeking at the lock count is a bit
1183
# of a nasty hack; probably it's better to have a transaction object,
1184
# which can do some finalization when it's either successfully or
1185
# unsuccessfully completed. (Denys's original patch did that.)
1186
# RBC 20060206 hookinhg into transaction will couple lock and transaction
1187
# wrongly. Hookinh into unllock on the control files object is fine though.
1189
# TODO: split this per format so there is no ugly if block
1190
if self._hashcache.needs_write and (
1191
# dedicated lock files
1192
self._control_files._lock_count==1 or
1194
(self._control_files is self.branch.control_files and
1195
self._control_files._lock_count==3)):
1196
self._hashcache.write()
1197
# reverse order of locking.
1198
result = self._control_files.unlock()
1200
self.branch.unlock()
1206
"""Update a working tree along its branch.
1208
This will update the branch if its bound too, which means we have multiple trees involved:
1209
The new basis tree of the master.
1210
The old basis tree of the branch.
1211
The old basis tree of the working tree.
1212
The current working tree state.
1213
pathologically all three may be different, and non ancestors of each other.
1214
Conceptually we want to:
1215
Preserve the wt.basis->wt.state changes
1216
Transform the wt.basis to the new master basis.
1217
Apply a merge of the old branch basis to get any 'local' changes from it into the tree.
1218
Restore the wt.basis->wt.state changes.
1220
There isn't a single operation at the moment to do that, so we:
1221
Merge current state -> basis tree of the master w.r.t. the old tree basis.
1222
Do a 'normal' merge of the old branch basis if it is relevant.
1224
old_tip = self.branch.update()
1225
if old_tip is not None:
1226
self.add_pending_merge(old_tip)
1227
self.branch.lock_read()
1230
if self.last_revision() != self.branch.last_revision():
1231
# merge tree state up to new branch tip.
1232
basis = self.basis_tree()
1233
to_tree = self.branch.basis_tree()
1234
result += merge_inner(self.branch,
1238
self.set_last_revision(self.branch.last_revision())
1239
if old_tip and old_tip != self.last_revision():
1240
# our last revision was not the prior branch last reivison
1241
# and we have converted that last revision to a pending merge.
1242
# base is somewhere between the branch tip now
1243
# and the now pending merge
1244
from bzrlib.revision import common_ancestor
1246
base_rev_id = common_ancestor(self.branch.last_revision(),
1248
self.branch.repository)
1249
except errors.NoCommonAncestor:
1251
base_tree = self.branch.repository.revision_tree(base_rev_id)
1252
other_tree = self.branch.repository.revision_tree(old_tip)
1253
result += merge_inner(self.branch,
1259
self.branch.unlock()
1262
def _write_inventory(self, inv):
1263
"""Write inventory as the current inventory."""
1265
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
1267
self._control_files.put('inventory', sio)
1268
self._set_inventory(inv)
1269
mutter('wrote working inventory')
1272
class WorkingTree3(WorkingTree):
1273
"""This is the Format 3 working tree.
1275
This differs from the base WorkingTree by:
1276
- having its own file lock
1277
- having its own last-revision property.
1281
def last_revision(self):
1282
"""See WorkingTree.last_revision."""
1284
return self._control_files.get_utf8('last-revision').read()
1288
def _change_last_revision(self, revision_id):
1289
"""See WorkingTree._change_last_revision."""
1290
if revision_id is None or revision_id == NULL_REVISION:
1292
self._control_files._transport.delete('last-revision')
1293
except errors.NoSuchFile:
1298
self.branch.revision_history().index(revision_id)
1300
raise errors.NoSuchRevision(self.branch, revision_id)
1301
self._control_files.put_utf8('last-revision', revision_id)
1305
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
1306
def get_conflicted_stem(path):
1307
for suffix in CONFLICT_SUFFIXES:
1308
if path.endswith(suffix):
1309
return path[:-len(suffix)]
1311
@deprecated_function(zero_eight)
1312
def is_control_file(filename):
1313
"""See WorkingTree.is_control_filename(filename)."""
1314
## FIXME: better check
1315
filename = normpath(filename)
1316
while filename != '':
1317
head, tail = os.path.split(filename)
1318
## mutter('check %r for control file' % ((head, tail),))
1321
if filename == head:
1327
class WorkingTreeFormat(object):
1328
"""An encapsulation of the initialization and open routines for a format.
1330
Formats provide three things:
1331
* An initialization routine,
1335
Formats are placed in an dict by their format string for reference
1336
during workingtree opening. Its not required that these be instances, they
1337
can be classes themselves with class methods - it simply depends on
1338
whether state is needed for a given format or not.
1340
Once a format is deprecated, just deprecate the initialize and open
1341
methods on the format class. Do not deprecate the object, as the
1342
object will be created every time regardless.
1345
_default_format = None
1346
"""The default format used for new trees."""
1349
"""The known formats."""
1352
def find_format(klass, a_bzrdir):
1353
"""Return the format for the working tree object in a_bzrdir."""
1355
transport = a_bzrdir.get_workingtree_transport(None)
1356
format_string = transport.get("format").read()
1357
return klass._formats[format_string]
1359
raise errors.NoWorkingTree(base=transport.base)
1361
raise errors.UnknownFormatError(format_string)
1364
def get_default_format(klass):
1365
"""Return the current default format."""
1366
return klass._default_format
1368
def get_format_string(self):
1369
"""Return the ASCII format string that identifies this format."""
1370
raise NotImplementedError(self.get_format_string)
1372
def is_supported(self):
1373
"""Is this format supported?
1375
Supported formats can be initialized and opened.
1376
Unsupported formats may not support initialization or committing or
1377
some other features depending on the reason for not being supported.
1382
def register_format(klass, format):
1383
klass._formats[format.get_format_string()] = format
1386
def set_default_format(klass, format):
1387
klass._default_format = format
1390
def unregister_format(klass, format):
1391
assert klass._formats[format.get_format_string()] is format
1392
del klass._formats[format.get_format_string()]
1396
class WorkingTreeFormat2(WorkingTreeFormat):
1397
"""The second working tree format.
1399
This format modified the hash cache from the format 1 hash cache.
1402
def initialize(self, a_bzrdir, revision_id=None):
1403
"""See WorkingTreeFormat.initialize()."""
1404
if not isinstance(a_bzrdir.transport, LocalTransport):
1405
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1406
branch = a_bzrdir.open_branch()
1407
if revision_id is not None:
1410
revision_history = branch.revision_history()
1412
position = revision_history.index(revision_id)
1414
raise errors.NoSuchRevision(branch, revision_id)
1415
branch.set_revision_history(revision_history[:position + 1])
1418
revision = branch.last_revision()
1420
wt = WorkingTree(a_bzrdir.root_transport.base,
1426
wt._write_inventory(inv)
1427
wt.set_root_id(inv.root.file_id)
1428
wt.set_last_revision(revision)
1429
wt.set_pending_merges([])
1430
build_tree(wt.basis_tree(), wt)
1434
super(WorkingTreeFormat2, self).__init__()
1435
self._matchingbzrdir = bzrdir.BzrDirFormat6()
1437
def open(self, a_bzrdir, _found=False):
1438
"""Return the WorkingTree object for a_bzrdir
1440
_found is a private parameter, do not use it. It is used to indicate
1441
if format probing has already been done.
1444
# we are being called directly and must probe.
1445
raise NotImplementedError
1446
if not isinstance(a_bzrdir.transport, LocalTransport):
1447
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1448
return WorkingTree(a_bzrdir.root_transport.base,
1454
class WorkingTreeFormat3(WorkingTreeFormat):
1455
"""The second working tree format updated to record a format marker.
1457
This format modified the hash cache from the format 1 hash cache.
1460
def get_format_string(self):
1461
"""See WorkingTreeFormat.get_format_string()."""
1462
return "Bazaar-NG Working Tree format 3"
1464
def initialize(self, a_bzrdir, revision_id=None):
1465
"""See WorkingTreeFormat.initialize().
1467
revision_id allows creating a working tree at a differnet
1468
revision than the branch is at.
1470
if not isinstance(a_bzrdir.transport, LocalTransport):
1471
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1472
transport = a_bzrdir.get_workingtree_transport(self)
1473
control_files = LockableFiles(transport, 'lock', TransportLock)
1474
control_files.put_utf8('format', self.get_format_string())
1475
branch = a_bzrdir.open_branch()
1476
if revision_id is None:
1477
revision_id = branch.last_revision()
1479
wt = WorkingTree3(a_bzrdir.root_transport.base,
1485
wt._write_inventory(inv)
1486
wt.set_root_id(inv.root.file_id)
1487
wt.set_last_revision(revision_id)
1488
wt.set_pending_merges([])
1489
build_tree(wt.basis_tree(), wt)
1493
super(WorkingTreeFormat3, self).__init__()
1494
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
1496
def open(self, a_bzrdir, _found=False):
1497
"""Return the WorkingTree object for a_bzrdir
1499
_found is a private parameter, do not use it. It is used to indicate
1500
if format probing has already been done.
1503
# we are being called directly and must probe.
1504
raise NotImplementedError
1505
if not isinstance(a_bzrdir.transport, LocalTransport):
1506
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1507
return WorkingTree3(a_bzrdir.root_transport.base,
1513
return self.get_format_string()
1516
# formats which have no format string are not discoverable
1517
# and not independently creatable, so are not registered.
1518
__default_format = WorkingTreeFormat3()
1519
WorkingTreeFormat.register_format(__default_format)
1520
WorkingTreeFormat.set_default_format(__default_format)
1521
_legacy_formats = [WorkingTreeFormat2(),
1525
class WorkingTreeTestProviderAdapter(object):
1526
"""A tool to generate a suite testing multiple workingtree formats at once.
1528
This is done by copying the test once for each transport and injecting
1529
the transport_server, transport_readonly_server, and workingtree_format
1530
classes into each copy. Each copy is also given a new id() to make it
1534
def __init__(self, transport_server, transport_readonly_server, formats):
1535
self._transport_server = transport_server
1536
self._transport_readonly_server = transport_readonly_server
1537
self._formats = formats
1539
def adapt(self, test):
1540
from bzrlib.tests import TestSuite
1541
result = TestSuite()
1542
for workingtree_format, bzrdir_format in self._formats:
1543
new_test = deepcopy(test)
1544
new_test.transport_server = self._transport_server
1545
new_test.transport_readonly_server = self._transport_readonly_server
1546
new_test.bzrdir_format = bzrdir_format
1547
new_test.workingtree_format = workingtree_format
1548
def make_new_test_id():
1549
new_id = "%s(%s)" % (new_test.id(), workingtree_format.__class__.__name__)
1550
return lambda: new_id
1551
new_test.id = make_new_test_id()
1552
result.addTest(new_test)