/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/hashcache.py

  • Committer: Martin Pool
  • Date: 2007-01-17 01:47:02 UTC
  • mfrom: (2235 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2236.
  • Revision ID: mbp@sourcefrog.net-20070117014702-yjki61vdjsv3vztw
merge dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 by Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
 
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
 
 
12
#
13
13
# You should have received a copy of the GNU General Public License
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
42
42
FP_CTIME_COLUMN = 2
43
43
FP_MODE_COLUMN = 5
44
44
 
45
 
def _fingerprint(abspath):
46
 
    try:
47
 
        fs = os.lstat(abspath)
48
 
    except OSError:
49
 
        # might be missing, etc
50
 
        return None
51
 
 
52
 
    if stat.S_ISDIR(fs.st_mode):
53
 
        return None
54
 
 
55
 
    # we discard any high precision because it's not reliable; perhaps we
56
 
    # could do better on some systems?
57
 
    return (fs.st_size, long(fs.st_mtime),
58
 
            long(fs.st_ctime), fs.st_ino, fs.st_dev, fs.st_mode)
59
45
 
60
46
 
61
47
class HashCache(object):
131
117
        
132
118
        for inum, path, cache_entry in prep:
133
119
            abspath = pathjoin(self.root, path)
134
 
            fp = _fingerprint(abspath)
 
120
            fp = self._fingerprint(abspath)
135
121
            self.stat_count += 1
136
122
            
137
123
            cache_fp = cache_entry[1]
142
128
                self.needs_write = True
143
129
                del self._cache[path]
144
130
 
145
 
 
146
 
    def get_sha1(self, path):
 
131
    def get_sha1(self, path, stat_value=None):
147
132
        """Return the sha1 of a file.
148
133
        """
149
134
        abspath = pathjoin(self.root, path)
150
135
        self.stat_count += 1
151
 
        file_fp = _fingerprint(abspath)
 
136
        file_fp = self._fingerprint(abspath, stat_value)
152
137
        
153
138
        if not file_fp:
154
139
            # not a regular file or not existing
164
149
            cache_sha1, cache_fp = None, None
165
150
 
166
151
        if cache_fp == file_fp:
 
152
            ## mutter("hashcache hit for %s %r -> %s", path, file_fp, cache_sha1)
 
153
            ## mutter("now = %s", time.time())
167
154
            self.hit_count += 1
168
155
            return cache_sha1
169
156
        
170
157
        self.miss_count += 1
171
158
 
172
 
 
173
159
        mode = file_fp[FP_MODE_COLUMN]
174
160
        if stat.S_ISREG(mode):
175
 
            digest = sha_file(file(abspath, 'rb', buffering=65000))
 
161
            digest = self._really_sha1_file(abspath)
176
162
        elif stat.S_ISLNK(mode):
177
163
            digest = sha.new(os.readlink(abspath)).hexdigest()
178
164
        else:
179
165
            raise BzrError("file %r: unknown file stat mode: %o"%(abspath,mode))
180
166
 
181
 
        now = int(time.time())
182
 
        if file_fp[FP_MTIME_COLUMN] >= now or file_fp[FP_CTIME_COLUMN] >= now:
 
167
        # window of 3 seconds to allow for 2s resolution on windows,
 
168
        # unsynchronized file servers, etc.
 
169
        cutoff = self._cutoff_time()
 
170
        if file_fp[FP_MTIME_COLUMN] >= cutoff \
 
171
                or file_fp[FP_CTIME_COLUMN] >= cutoff:
183
172
            # changed too recently; can't be cached.  we can
184
173
            # return the result and it could possibly be cached
185
174
            # next time.
191
180
            # need to let sufficient time elapse before we may cache this entry
192
181
            # again.  If we didn't do this, then, for example, a very quick 1
193
182
            # byte replacement in the file might go undetected.
194
 
            self.danger_count += 1 
 
183
            ## mutter('%r modified too recently; not caching', path)
 
184
            self.danger_count += 1
195
185
            if cache_fp:
196
186
                self.removed_count += 1
197
187
                self.needs_write = True
198
188
                del self._cache[path]
199
189
        else:
 
190
            ## mutter('%r added to cache: now=%f, mtime=%d, ctime=%d',
 
191
            ##        path, time.time(), file_fp[FP_MTIME_COLUMN],
 
192
            ##        file_fp[FP_CTIME_COLUMN])
200
193
            self.update_count += 1
201
194
            self.needs_write = True
202
195
            self._cache[path] = (digest, file_fp)
203
196
        return digest
 
197
 
 
198
    def _really_sha1_file(self, abspath):
 
199
        """Calculate the SHA1 of a file by reading the full text"""
 
200
        return sha_file(file(abspath, 'rb', buffering=65000))
204
201
        
205
202
    def write(self):
206
203
        """Write contents of cache to file."""
207
204
        outf = AtomicFile(self.cache_file_name(), 'wb', new_mode=self._mode)
208
205
        try:
209
 
            print >>outf, CACHE_HEADER,
 
206
            outf.write(CACHE_HEADER)
210
207
 
211
208
            for path, c  in self._cache.iteritems():
212
209
                assert '//' not in path, path
213
 
                outf.write(path.encode('utf-8'))
214
 
                outf.write('// ')
215
 
                print >>outf, c[0],     # hex sha1
216
 
                for fld in c[1]:
217
 
                    print >>outf, "%d" % fld,
218
 
                print >>outf
 
210
                line_info = [path.encode('utf-8'), '// ', c[0], ' ']
 
211
                line_info.append(' '.join([str(fld) for fld in c[1]]))
 
212
                line_info.append('\n')
 
213
                outf.write(''.join(line_info))
219
214
            outf.commit()
220
215
            self.needs_write = False
221
 
            mutter("write hash cache: %s hits=%d misses=%d stat=%d recent=%d updates=%d",
222
 
                   self.cache_file_name(), self.hit_count, self.miss_count,
223
 
                   self.stat_count,
224
 
                   self.danger_count, self.update_count)
 
216
            ## mutter("write hash cache: %s hits=%d misses=%d stat=%d recent=%d updates=%d",
 
217
            ##        self.cache_file_name(), self.hit_count, self.miss_count,
 
218
            ##        self.stat_count,
 
219
            ##        self.danger_count, self.update_count)
225
220
        finally:
226
 
            if not outf.closed:
227
 
                outf.abort()
 
221
            outf.close()
228
222
 
229
223
    def read(self):
230
224
        """Reinstate cache from file.
244
238
            self.needs_write = True
245
239
            return
246
240
 
247
 
 
248
241
        hdr = inf.readline()
249
242
        if hdr != CACHE_HEADER:
250
243
            mutter('cache header marker not found at top of %s;'
275
268
            self._cache[path] = (sha1, fp)
276
269
 
277
270
        self.needs_write = False
 
271
 
 
272
    def _cutoff_time(self):
 
273
        """Return cutoff time.
 
274
 
 
275
        Files modified more recently than this time are at risk of being
 
276
        undetectably modified and so can't be cached.
 
277
        """
 
278
        return int(time.time()) - 3
278
279
           
279
 
 
280
 
 
281
 
        
 
280
    def _fingerprint(self, abspath, stat_value=None):
 
281
        if stat_value is None:
 
282
            try:
 
283
                stat_value = os.lstat(abspath)
 
284
            except OSError:
 
285
                # might be missing, etc
 
286
                return None
 
287
        if stat.S_ISDIR(stat_value.st_mode):
 
288
            return None
 
289
        # we discard any high precision because it's not reliable; perhaps we
 
290
        # could do better on some systems?
 
291
        return (stat_value.st_size, long(stat_value.st_mtime),
 
292
                long(stat_value.st_ctime), stat_value.st_ino, 
 
293
                stat_value.st_dev, stat_value.st_mode)