14
14
# along with this program; if not, write to the Free Software
15
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.
28
from bzrlib.osutils import appendpath, file_kind, isdir, splitpath
29
from bzrlib.errors import BzrCheckError
30
from bzrlib.trace import mutter
32
class TreeEntry(object):
33
"""An entry that implements the minium interface used by commands.
35
This needs further inspection, it may be better to have
36
InventoryEntries without ids - though that seems wrong. For now,
37
this is a parallel hierarchy to InventoryEntry, and needs to become
38
one of several things: decorates to that hierarchy, children of, or
40
Another note is that these objects are currently only used when there is
41
no InventoryEntry available - i.e. for unversioned objects.
42
Perhaps they should be UnversionedEntry et al. ? - RBC 20051003
45
def __eq__(self, other):
46
# yes, this us ugly, TODO: best practice __eq__ style.
47
return (isinstance(other, TreeEntry)
48
and other.__class__ == self.__class__)
50
def kind_character(self):
54
class TreeDirectory(TreeEntry):
55
"""See TreeEntry. This is a directory in a working tree."""
57
def __eq__(self, other):
58
return (isinstance(other, TreeDirectory)
59
and other.__class__ == self.__class__)
61
def kind_character(self):
65
class TreeFile(TreeEntry):
66
"""See TreeEntry. This is a regular file in a working tree."""
68
def __eq__(self, other):
69
return (isinstance(other, TreeFile)
70
and other.__class__ == self.__class__)
72
def kind_character(self):
76
class TreeLink(TreeEntry):
77
"""See TreeEntry. This is a symlink in a working tree."""
79
def __eq__(self, other):
80
return (isinstance(other, TreeLink)
81
and other.__class__ == self.__class__)
83
def kind_character(self):
21
from errors import BzrCheckError
22
from trace import mutter
87
25
class WorkingTree(bzrlib.tree.Tree):
93
31
It is possible for a `WorkingTree` to have a filename which is
94
32
not listed in the Inventory and vice versa.
96
36
def __init__(self, basedir, inv):
97
from bzrlib.hashcache import HashCache
98
from bzrlib.trace import note, mutter
100
37
self._inventory = inv
101
38
self.basedir = basedir
102
39
self.path2id = inv.path2id
104
# update the whole cache up front and write to disk if anything changed;
105
# in the future we might want to do this more selectively
106
hc = self._hashcache = HashCache(basedir)
116
if self._hashcache.needs_write:
117
self._hashcache.write()
120
41
def __iter__(self):
121
42
"""Iterate through file_ids for this tree.
123
44
file_ids are in a WorkingTree if they are in the working inventory
124
45
and the working file exists.
47
self._update_statcache()
126
48
inv = self._inventory
127
for path, ie in inv.iter_entries():
128
if bzrlib.osutils.lexists(self.abspath(path)):
49
for file_id in self._inventory:
50
# TODO: This is slightly redundant; we should be able to just
51
# check the statcache but it only includes regular files.
52
# only include files which still exist on disk
55
if ((file_id in self._statcache)
56
or (os.path.exists(self.abspath(inv.id2path(file_id))))):
132
61
def __repr__(self):
133
62
return "<%s of %s>" % (self.__class__.__name__,
134
getattr(self, 'basedir', None))
138
65
def abspath(self, filename):
139
66
return os.path.join(self.basedir, filename)
141
68
def has_filename(self, filename):
142
return bzrlib.osutils.lexists(self.abspath(filename))
69
return os.path.exists(self.abspath(filename))
144
71
def get_file(self, file_id):
145
72
return self.get_file_byname(self.id2path(file_id))
151
78
## XXX: badly named; this isn't in the store at all
152
79
return self.abspath(self.id2path(file_id))
155
def id2abspath(self, file_id):
156
return self.abspath(self.id2path(file_id))
159
82
def has_id(self, file_id):
160
83
# files that have been deleted are excluded
161
inv = self._inventory
162
if not inv.has_id(file_id):
84
if not self.inventory.has_id(file_id):
164
path = inv.id2path(file_id)
165
return bzrlib.osutils.lexists(self.abspath(path))
86
self._update_statcache()
87
if file_id in self._statcache:
89
return os.path.exists(self.abspath(self.id2path(file_id)))
168
92
__contains__ = has_id
95
def _update_statcache(self):
97
if not self._statcache:
98
self._statcache = statcache.update_cache(self.basedir, self.inventory)
171
100
def get_file_size(self, file_id):
172
return os.path.getsize(self.id2abspath(file_id))
102
return os.stat(self._get_store_filename(file_id))[stat.ST_SIZE]
174
105
def get_file_sha1(self, file_id):
175
path = self._inventory.id2path(file_id)
176
return self._hashcache.get_sha1(path)
179
def is_executable(self, file_id):
181
return self._inventory[file_id].executable
183
path = self._inventory.id2path(file_id)
184
mode = os.lstat(self.abspath(path)).st_mode
185
return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
187
def get_symlink_target(self, file_id):
188
return os.readlink(self.id2abspath(file_id))
107
self._update_statcache()
108
return self._statcache[file_id][statcache.SC_SHA1]
190
111
def file_class(self, filename):
191
112
if self.path2id(filename):
241
165
% (fap, f_ie.kind, f_ie.file_id, fk))
243
# make a last minute entry
247
if fk == 'directory':
248
entry = TreeDirectory()
251
elif fk == 'symlink':
256
yield fp, c, fk, (f_ie and f_ie.file_id), entry
167
yield fp, c, fk, (f_ie and f_ie.file_id)
258
169
if fk != 'directory':
295
197
Currently returned depth-first, sorted by name within directories.
297
199
## TODO: Work from given directory downwards
200
from osutils import isdir, appendpath
298
202
for path, dir_entry in self.inventory.directories():
299
203
mutter("search for unknowns in %r" % path)
300
204
dirabs = self.abspath(path)