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
# TODO: Don't allow WorkingTrees to be constructed for remote branches.
19
# FIXME: I don't know if writing out the cache from the destructor is really a
20
# good idea, because destructors are considered poor taste in Python, and
21
# it's not predictable when it will be written out.
27
from bzrlib.branch import Branch, needs_read_lock, needs_write_lock, quotefn
29
from bzrlib.osutils import appendpath, file_kind, isdir, splitpath, relpath
30
from bzrlib.errors import BzrCheckError, DivergedBranches
31
from bzrlib.trace import mutter
33
class TreeEntry(object):
34
"""An entry that implements the minium interface used by commands.
36
This needs further inspection, it may be better to have
37
InventoryEntries without ids - though that seems wrong. For now,
38
this is a parallel hierarchy to InventoryEntry, and needs to become
39
one of several things: decorates to that hierarchy, children of, or
41
Another note is that these objects are currently only used when there is
42
no InventoryEntry available - i.e. for unversioned objects.
43
Perhaps they should be UnversionedEntry et al. ? - RBC 20051003
46
def __eq__(self, other):
47
# yes, this us ugly, TODO: best practice __eq__ style.
48
return (isinstance(other, TreeEntry)
49
and other.__class__ == self.__class__)
51
def kind_character(self):
55
class TreeDirectory(TreeEntry):
56
"""See TreeEntry. This is a directory in a working tree."""
58
def __eq__(self, other):
59
return (isinstance(other, TreeDirectory)
60
and other.__class__ == self.__class__)
62
def kind_character(self):
66
class TreeFile(TreeEntry):
67
"""See TreeEntry. This is a regular file in a working tree."""
69
def __eq__(self, other):
70
return (isinstance(other, TreeFile)
71
and other.__class__ == self.__class__)
73
def kind_character(self):
77
class TreeLink(TreeEntry):
78
"""See TreeEntry. This is a symlink in a working tree."""
80
def __eq__(self, other):
81
return (isinstance(other, TreeLink)
82
and other.__class__ == self.__class__)
84
def kind_character(self):
88
class WorkingTree(bzrlib.tree.Tree):
91
The inventory is held in the `Branch` working-inventory, and the
92
files are in a directory on disk.
94
It is possible for a `WorkingTree` to have a filename which is
95
not listed in the Inventory and vice versa.
98
def __init__(self, basedir, branch=None):
99
"""Construct a WorkingTree for basedir.
101
If the branch is not supplied, it is opened automatically.
102
If the branch is supplied, it must be the branch for this basedir.
103
(branch.base is not cross checked, because for remote branches that
104
would be meaningless).
106
from bzrlib.hashcache import HashCache
107
from bzrlib.trace import note, mutter
110
branch = Branch.open(basedir)
111
self._inventory = branch.inventory
112
self.path2id = self._inventory.path2id
114
self.basedir = basedir
116
# update the whole cache up front and write to disk if anything changed;
117
# in the future we might want to do this more selectively
118
hc = self._hashcache = HashCache(basedir)
128
if self._hashcache.needs_write:
129
self._hashcache.write()
133
"""Iterate through file_ids for this tree.
135
file_ids are in a WorkingTree if they are in the working inventory
136
and the working file exists.
138
inv = self._inventory
139
for path, ie in inv.iter_entries():
140
if bzrlib.osutils.lexists(self.abspath(path)):
145
return "<%s of %s>" % (self.__class__.__name__,
146
getattr(self, 'basedir', None))
150
def abspath(self, filename):
151
return os.path.join(self.basedir, filename)
153
def relpath(self, abspath):
154
"""Return the local path portion from a given absolute path."""
155
return relpath(self.basedir, abspath)
157
def has_filename(self, filename):
158
return bzrlib.osutils.lexists(self.abspath(filename))
160
def get_file(self, file_id):
161
return self.get_file_byname(self.id2path(file_id))
163
def get_file_byname(self, filename):
164
return file(self.abspath(filename), 'rb')
166
def _get_store_filename(self, file_id):
167
## XXX: badly named; this isn't in the store at all
168
return self.abspath(self.id2path(file_id))
171
def id2abspath(self, file_id):
172
return self.abspath(self.id2path(file_id))
175
def has_id(self, file_id):
176
# files that have been deleted are excluded
177
inv = self._inventory
178
if not inv.has_id(file_id):
180
path = inv.id2path(file_id)
181
return bzrlib.osutils.lexists(self.abspath(path))
183
def has_or_had_id(self, file_id):
184
if file_id == self.inventory.root.file_id:
186
return self.inventory.has_id(file_id)
188
__contains__ = has_id
191
def get_file_size(self, file_id):
192
return os.path.getsize(self.id2abspath(file_id))
194
def get_file_sha1(self, file_id):
195
path = self._inventory.id2path(file_id)
196
return self._hashcache.get_sha1(path)
199
def is_executable(self, file_id):
201
return self._inventory[file_id].executable
203
path = self._inventory.id2path(file_id)
204
mode = os.lstat(self.abspath(path)).st_mode
205
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
207
def get_symlink_target(self, file_id):
208
return os.readlink(self.id2abspath(file_id))
210
def file_class(self, filename):
211
if self.path2id(filename):
213
elif self.is_ignored(filename):
219
def list_files(self):
220
"""Recursively list all files as (path, class, kind, id).
222
Lists, but does not descend into unversioned directories.
224
This does not include files that have been deleted in this
227
Skips the control directory.
229
inv = self._inventory
231
def descend(from_dir_relpath, from_dir_id, dp):
235
## TODO: If we find a subdirectory with its own .bzr
236
## directory, then that is a separate tree and we
237
## should exclude it.
238
if bzrlib.BZRDIR == f:
242
fp = appendpath(from_dir_relpath, f)
245
fap = appendpath(dp, f)
247
f_ie = inv.get_child(from_dir_id, f)
250
elif self.is_ignored(fp):
259
raise BzrCheckError("file %r entered as kind %r id %r, "
261
% (fap, f_ie.kind, f_ie.file_id, fk))
263
# make a last minute entry
267
if fk == 'directory':
268
entry = TreeDirectory()
271
elif fk == 'symlink':
276
yield fp, c, fk, (f_ie and f_ie.file_id), entry
278
if fk != 'directory':
282
# don't descend unversioned directories
285
for ff in descend(fp, f_ie.file_id, fap):
288
for f in descend('', inv.root.file_id, self.basedir):
294
for subp in self.extras():
295
if not self.is_ignored(subp):
298
def iter_conflicts(self):
300
for path in (s[0] for s in self.list_files()):
301
stem = get_conflicted_stem(path)
304
if stem not in conflicted:
309
def pull(self, source, remember=False, clobber=False):
310
from bzrlib.merge import merge
313
old_revno = self.branch.revno()
314
old_revision_history = self.branch.revision_history()
316
self.branch.update_revisions(source)
317
except DivergedBranches:
320
self.branch.set_revision_history(source.revision_history())
321
new_revision_history = self.branch.revision_history()
322
if new_revision_history != old_revision_history:
323
merge((self.basedir, -1), (self.basedir, old_revno), check_clean=False)
324
if self.branch.get_parent() is None or remember:
325
self.branch.set_parent(source.base)
330
"""Yield all unknown files in this WorkingTree.
332
If there are any unknown directories then only the directory is
333
returned, not all its children. But if there are unknown files
334
under a versioned subdirectory, they are returned.
336
Currently returned depth-first, sorted by name within directories.
338
## TODO: Work from given directory downwards
339
for path, dir_entry in self.inventory.directories():
340
mutter("search for unknowns in %r" % path)
341
dirabs = self.abspath(path)
342
if not isdir(dirabs):
343
# e.g. directory deleted
347
for subf in os.listdir(dirabs):
349
and (subf not in dir_entry.children)):
354
subp = appendpath(path, subf)
358
def ignored_files(self):
359
"""Yield list of PATH, IGNORE_PATTERN"""
360
for subp in self.extras():
361
pat = self.is_ignored(subp)
366
def get_ignore_list(self):
367
"""Return list of ignore patterns.
369
Cached in the Tree object after the first call.
371
if hasattr(self, '_ignorelist'):
372
return self._ignorelist
374
l = bzrlib.DEFAULT_IGNORE[:]
375
if self.has_filename(bzrlib.IGNORE_FILENAME):
376
f = self.get_file_byname(bzrlib.IGNORE_FILENAME)
377
l.extend([line.rstrip("\n\r") for line in f.readlines()])
382
def is_ignored(self, filename):
383
r"""Check whether the filename matches an ignore pattern.
385
Patterns containing '/' or '\' need to match the whole path;
386
others match against only the last component.
388
If the file is ignored, returns the pattern which caused it to
389
be ignored, otherwise None. So this can simply be used as a
390
boolean if desired."""
392
# TODO: Use '**' to match directories, and other extended
393
# globbing stuff from cvs/rsync.
395
# XXX: fnmatch is actually not quite what we want: it's only
396
# approximately the same as real Unix fnmatch, and doesn't
397
# treat dotfiles correctly and allows * to match /.
398
# Eventually it should be replaced with something more
401
for pat in self.get_ignore_list():
402
if '/' in pat or '\\' in pat:
404
# as a special case, you can put ./ at the start of a
405
# pattern; this is good to match in the top-level
408
if (pat[:2] == './') or (pat[:2] == '.\\'):
412
if fnmatch.fnmatchcase(filename, newpat):
415
if fnmatch.fnmatchcase(splitpath(filename)[-1], pat):
420
def kind(self, file_id):
421
return file_kind(self.id2abspath(file_id))
424
"""See Branch.lock_read, and WorkingTree.unlock."""
425
return self.branch.lock_read()
427
def lock_write(self):
428
"""See Branch.lock_write, and WorkingTree.unlock."""
429
return self.branch.lock_write()
432
def remove(self, files, verbose=False):
433
"""Remove nominated files from the working inventory..
435
This does not remove their text. This does not run on XXX on what? RBC
437
TODO: Refuse to remove modified files unless --force is given?
439
TODO: Do something useful with directories.
441
TODO: Should this remove the text or not? Tough call; not
442
removing may be useful and the user can just use use rm, and
443
is the opposite of add. Removing it is consistent with most
444
other tools. Maybe an option.
446
## TODO: Normalize names
447
## TODO: Remove nested loops; better scalability
448
if isinstance(files, basestring):
453
# do this before any modifications
457
raise BzrError("cannot remove unversioned file %s" % quotefn(f))
458
mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
460
# having remove it, it must be either ignored or unknown
461
if self.is_ignored(f):
465
show_status(new_status, inv[fid].kind, quotefn(f))
468
self.branch._write_inventory(inv)
471
"""See Branch.unlock.
473
WorkingTree locking just uses the Branch locking facilities.
474
This is current because all working trees have an embedded branch
475
within them. IF in the future, we were to make branch data shareable
476
between multiple working trees, i.e. via shared storage, then we
477
would probably want to lock both the local tree, and the branch.
479
return self.branch.unlock()
482
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
483
def get_conflicted_stem(path):
484
for suffix in CONFLICT_SUFFIXES:
485
if path.endswith(suffix):
486
return path[:-len(suffix)]