~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/hashcache.py

merge from aaron - fixes bare excepts, adds ancestor namespace

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
16
16
 
17
 
 
18
 
 
19
 
# TODO: Perhaps have a way to stat all the files in inode order, and
20
 
# then remember that they're all fresh for the lifetime of the object?
21
 
 
22
 
# TODO: Keep track of whether there are in-memory updates that need to
23
 
# be flushed.
 
17
# TODO: Up-front, stat all files in order and remove those which are deleted or 
 
18
# out-of-date.  Don't actually re-read them until they're needed.  That ought 
 
19
# to bring all the inodes into core so that future stats to them are fast, and 
 
20
# it preserves the nice property that any caller will always get up-to-date
 
21
# data except in unavoidable cases.
24
22
 
25
23
# TODO: Perhaps return more details on the file to avoid statting it
26
24
# again: nonexistent, file type, size, etc
27
25
 
28
26
 
29
27
 
30
 
 
31
28
CACHE_HEADER = "### bzr hashcache v5\n"
32
29
 
 
30
import os, stat, time
 
31
 
 
32
from bzrlib.osutils import sha_file
 
33
from bzrlib.trace import mutter, warning
 
34
 
 
35
 
33
36
 
34
37
def _fingerprint(abspath):
35
 
    import os, stat
36
 
 
37
38
    try:
38
39
        fs = os.lstat(abspath)
39
40
    except OSError:
91
92
        self.miss_count = 0
92
93
        self.stat_count = 0
93
94
        self.danger_count = 0
 
95
        self.removed_count = 0
 
96
        self.update_count = 0
94
97
        self._cache = {}
95
98
 
96
99
 
97
 
 
98
100
    def cache_file_name(self):
99
 
        import os.path
100
 
        return os.path.join(self.basedir, '.bzr', 'stat-cache')
 
101
        return os.sep.join([self.basedir, '.bzr', 'stat-cache'])
101
102
 
102
103
 
103
104
 
111
112
            self._cache = {}
112
113
 
113
114
 
 
115
    def scan(self):
 
116
        """Scan all files and remove entries where the cache entry is obsolete.
 
117
        
 
118
        Obsolete entries are those where the file has been modified or deleted
 
119
        since the entry was inserted.        
 
120
        """
 
121
        prep = [(ce[1][3], path, ce) for (path, ce) in self._cache.iteritems()]
 
122
        prep.sort()
 
123
        
 
124
        for inum, path, cache_entry in prep:
 
125
            abspath = os.sep.join([self.basedir, path])
 
126
            fp = _fingerprint(abspath)
 
127
            self.stat_count += 1
 
128
            
 
129
            cache_fp = cache_entry[1]
 
130
    
 
131
            if (not fp) or (cache_fp != fp):
 
132
                # not here or not a regular file anymore
 
133
                self.removed_count += 1
 
134
                self.needs_write = True
 
135
                del self._cache[path]
 
136
 
 
137
 
 
138
 
114
139
    def get_sha1(self, path):
115
 
        """Return the hex SHA-1 of the contents of the file at path.
116
 
 
117
 
        XXX: If the file does not exist or is not a plain file???
 
140
        """Return the sha1 of a file.
118
141
        """
119
 
 
120
 
        import os, time
121
 
        from bzrlib.osutils import sha_file
122
 
        from bzrlib.trace import mutter
 
142
        abspath = os.sep.join([self.basedir, path])
 
143
        self.stat_count += 1
 
144
        file_fp = _fingerprint(abspath)
123
145
        
124
 
        abspath = os.path.join(self.basedir, path)
125
 
        fp = _fingerprint(abspath)
126
 
        c = self._cache.get(path)
127
 
        if c:
128
 
            cache_sha1, cache_fp = c
 
146
        if not file_fp:
 
147
            # not a regular file or not existing
 
148
            if path in self._cache:
 
149
                self.removed_count += 1
 
150
                self.needs_write = True
 
151
                del self._cache[path]
 
152
            return None        
 
153
 
 
154
        if path in self._cache:
 
155
            cache_sha1, cache_fp = self._cache[path]
129
156
        else:
130
157
            cache_sha1, cache_fp = None, None
131
158
 
132
 
        self.stat_count += 1
133
 
 
134
 
        if not fp:
135
 
            # not a regular file
136
 
            return None
137
 
        elif cache_fp and (cache_fp == fp):
 
159
        if cache_fp == file_fp:
138
160
            self.hit_count += 1
139
161
            return cache_sha1
 
162
        
 
163
        self.miss_count += 1
 
164
        digest = sha_file(file(abspath, 'rb', buffering=65000))
 
165
 
 
166
        now = int(time.time())
 
167
        if file_fp[1] >= now or file_fp[2] >= now:
 
168
            # changed too recently; can't be cached.  we can
 
169
            # return the result and it could possibly be cached
 
170
            # next time.
 
171
            self.danger_count += 1 
 
172
            if cache_fp:
 
173
                self.removed_count += 1
 
174
                self.needs_write = True
 
175
                del self._cache[path]
140
176
        else:
141
 
            self.miss_count += 1
142
 
            digest = sha_file(file(abspath, 'rb'))
143
 
 
144
 
            now = int(time.time())
145
 
            if fp[1] >= now or fp[2] >= now:
146
 
                # changed too recently; can't be cached.  we can
147
 
                # return the result and it could possibly be cached
148
 
                # next time.
149
 
                self.danger_count += 1 
150
 
                if cache_fp:
151
 
                    mutter("remove outdated entry for %s" % path)
152
 
                    self.needs_write = True
153
 
                    del self._cache[path]
154
 
            elif (fp != cache_fp) or (digest != cache_sha1):
155
 
                mutter("update entry for %s" % path)
156
 
                mutter("  %r" % (fp,))
157
 
                mutter("  %r" % (cache_fp,))
158
 
                self.needs_write = True
159
 
                self._cache[path] = (digest, fp)
160
 
 
161
 
            return digest
 
177
            self.update_count += 1
 
178
            self.needs_write = True
 
179
            self._cache[path] = (digest, file_fp)
 
180
        
 
181
        return digest
 
182
        
162
183
 
163
184
 
164
185
 
194
215
 
195
216
        If the cache file has the wrong version marker, this just clears 
196
217
        the cache."""
197
 
        from bzrlib.trace import mutter, warning
198
 
 
199
218
        self._cache = {}
200
219
 
201
220
        fn = self.cache_file_name()
202
221
        try:
203
 
            inf = file(fn, 'rb')
 
222
            inf = file(fn, 'rb', buffering=65000)
204
223
        except IOError, e:
205
224
            mutter("failed to open %s: %s" % (fn, e))
 
225
            # better write it now so it is valid
 
226
            self.needs_write = True
206
227
            return
207
228
 
208
229
 
210
231
        if hdr != CACHE_HEADER:
211
232
            mutter('cache header marker not found at top of %s; discarding cache'
212
233
                   % fn)
 
234
            self.needs_write = True
213
235
            return
214
236
 
215
237
        for l in inf: