/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: Marius Kruger
  • Date: 2010-07-10 21:28:56 UTC
  • mto: (5384.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5385.
  • Revision ID: marius.kruger@enerweb.co.za-20100710212856-uq4ji3go0u5se7hx
* Update documentation
* add NEWS

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
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
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
from __future__ import absolute_import
18
 
 
19
17
# TODO: Up-front, stat all files in order and remove those which are deleted or
20
18
# out-of-date.  Don't actually re-read them until they're needed.  That ought
21
19
# to bring all the inodes into core so that future stats to them are fast, and
28
26
# TODO: Perhaps use a Python pickle instead of a text file; might be faster.
29
27
 
30
28
 
31
 
CACHE_HEADER = b"### bzr hashcache v5\n"
 
29
 
 
30
CACHE_HEADER = "### bzr hashcache v5\n"
32
31
 
33
32
import os
34
33
import stat
35
34
import time
36
35
 
37
 
from . import (
 
36
from bzrlib import (
38
37
    atomicfile,
39
38
    errors,
40
39
    filters as _mod_filters,
41
40
    osutils,
42
41
    trace,
43
42
    )
44
 
from .sixish import (
45
 
    text_type,
46
 
    viewitems,
47
 
    )
48
43
 
49
44
 
50
45
FP_MTIME_COLUMN = 1
52
47
FP_MODE_COLUMN = 5
53
48
 
54
49
 
 
50
 
55
51
class HashCache(object):
56
52
    """Cache for looking up file SHA-1.
57
53
 
89
85
    needs_write = False
90
86
 
91
87
    def __init__(self, root, cache_file_name, mode=None,
92
 
                 content_filter_stack_provider=None):
 
88
            content_filter_stack_provider=None):
93
89
        """Create a hash cache in base dir, and set the file mode to mode.
94
90
 
95
91
        :param content_filter_stack_provider: a function that takes a
97
93
            parameters and returns a stack of ContentFilters.
98
94
            If None, no content filtering is performed.
99
95
        """
100
 
        if not isinstance(root, text_type):
101
 
            raise ValueError("Base dir for hashcache must be text")
102
 
        self.root = root
 
96
        self.root = osutils.safe_unicode(root)
 
97
        self.root_utf8 = self.root.encode('utf8') # where is the filesystem encoding ?
103
98
        self.hit_count = 0
104
99
        self.miss_count = 0
105
100
        self.stat_count = 0
108
103
        self.update_count = 0
109
104
        self._cache = {}
110
105
        self._mode = mode
111
 
        self._cache_file_name = cache_file_name
 
106
        self._cache_file_name = osutils.safe_unicode(cache_file_name)
112
107
        self._filter_provider = content_filter_stack_provider
113
108
 
114
109
    def cache_file_name(self):
128
123
        Obsolete entries are those where the file has been modified or deleted
129
124
        since the entry was inserted.
130
125
        """
131
 
        # Stat in inode order as optimisation for at least linux.
132
 
        def inode_order(path_and_cache):
133
 
            return path_and_cache[1][1][3]
134
 
        for path, cache_val in sorted(viewitems(self._cache), key=inode_order):
 
126
        # FIXME optimisation opportunity, on linux [and check other oses]:
 
127
        # rather than iteritems order, stat in inode order.
 
128
        prep = [(ce[1][3], path, ce) for (path, ce) in self._cache.iteritems()]
 
129
        prep.sort()
 
130
 
 
131
        for inum, path, cache_entry in prep:
135
132
            abspath = osutils.pathjoin(self.root, path)
136
133
            fp = self._fingerprint(abspath)
137
134
            self.stat_count += 1
138
135
 
139
 
            if not fp or cache_val[1] != fp:
 
136
            cache_fp = cache_entry[1]
 
137
 
 
138
            if (not fp) or (cache_fp != fp):
140
139
                # not here or not a regular file anymore
141
140
                self.removed_count += 1
142
141
                self.needs_write = True
145
144
    def get_sha1(self, path, stat_value=None):
146
145
        """Return the sha1 of a file.
147
146
        """
148
 
        abspath = osutils.pathjoin(self.root, path)
 
147
        if path.__class__ is str:
 
148
            abspath = osutils.pathjoin(self.root_utf8, path)
 
149
        else:
 
150
            abspath = osutils.pathjoin(self.root, path)
149
151
        self.stat_count += 1
150
152
        file_fp = self._fingerprint(abspath, stat_value)
151
153
 
163
165
            cache_sha1, cache_fp = None, None
164
166
 
165
167
        if cache_fp == file_fp:
 
168
            ## mutter("hashcache hit for %s %r -> %s", path, file_fp, cache_sha1)
 
169
            ## mutter("now = %s", time.time())
166
170
            self.hit_count += 1
167
171
            return cache_sha1
168
172
 
173
177
            if self._filter_provider is None:
174
178
                filters = []
175
179
            else:
176
 
                filters = self._filter_provider(path=path)
 
180
                filters = self._filter_provider(path=path, file_id=None)
177
181
            digest = self._really_sha1_file(abspath, filters)
178
182
        elif stat.S_ISLNK(mode):
179
 
            target = osutils.readlink(abspath)
 
183
            target = osutils.readlink(osutils.safe_unicode(abspath))
180
184
            digest = osutils.sha_string(target.encode('UTF-8'))
181
185
        else:
182
186
            raise errors.BzrError("file %r: unknown file stat mode: %o"
205
209
                self.needs_write = True
206
210
                del self._cache[path]
207
211
        else:
208
 
            # mutter('%r added to cache: now=%f, mtime=%d, ctime=%d',
 
212
            ## mutter('%r added to cache: now=%f, mtime=%d, ctime=%d',
209
213
            ##        path, time.time(), file_fp[FP_MTIME_COLUMN],
210
 
            # file_fp[FP_CTIME_COLUMN])
 
214
            ##        file_fp[FP_CTIME_COLUMN])
211
215
            self.update_count += 1
212
216
            self.needs_write = True
213
217
            self._cache[path] = (digest, file_fp)
219
223
 
220
224
    def write(self):
221
225
        """Write contents of cache to file."""
222
 
        with atomicfile.AtomicFile(self.cache_file_name(), 'wb',
223
 
                                   new_mode=self._mode) as outf:
 
226
        outf = atomicfile.AtomicFile(self.cache_file_name(), 'wb',
 
227
                                     new_mode=self._mode)
 
228
        try:
224
229
            outf.write(CACHE_HEADER)
225
230
 
226
 
            for path, c in viewitems(self._cache):
227
 
                line_info = [path.encode('utf-8'), b'// ', c[0], b' ']
228
 
                line_info.append(b'%d %d %d %d %d %d' % c[1])
229
 
                line_info.append(b'\n')
230
 
                outf.write(b''.join(line_info))
 
231
            for path, c  in self._cache.iteritems():
 
232
                line_info = [path.encode('utf-8'), '// ', c[0], ' ']
 
233
                line_info.append(' '.join([str(fld) for fld in c[1]]))
 
234
                line_info.append('\n')
 
235
                outf.write(''.join(line_info))
 
236
            outf.commit()
231
237
            self.needs_write = False
232
 
            # mutter("write hash cache: %s hits=%d misses=%d stat=%d recent=%d updates=%d",
233
 
            #        self.cache_file_name(), self.hit_count, self.miss_count,
234
 
            # self.stat_count,
235
 
            # self.danger_count, self.update_count)
 
238
            ## mutter("write hash cache: %s hits=%d misses=%d stat=%d recent=%d updates=%d",
 
239
            ##        self.cache_file_name(), self.hit_count, self.miss_count,
 
240
            ##        self.stat_count,
 
241
            ##        self.danger_count, self.update_count)
 
242
        finally:
 
243
            outf.close()
236
244
 
237
245
    def read(self):
238
246
        """Reinstate cache from file.
245
253
 
246
254
        fn = self.cache_file_name()
247
255
        try:
248
 
            inf = open(fn, 'rb', buffering=65000)
249
 
        except IOError as e:
250
 
            trace.mutter("failed to open %s: %s", fn, str(e))
 
256
            inf = file(fn, 'rb', buffering=65000)
 
257
        except IOError, e:
 
258
            trace.mutter("failed to open %s: %s", fn, e)
251
259
            # better write it now so it is valid
252
260
            self.needs_write = True
253
261
            return
254
262
 
255
 
        with inf:
256
 
            hdr = inf.readline()
257
 
            if hdr != CACHE_HEADER:
258
 
                trace.mutter('cache header marker not found at top of %s;'
259
 
                             ' discarding cache', fn)
260
 
                self.needs_write = True
261
 
                return
262
 
 
263
 
            for l in inf:
264
 
                pos = l.index(b'// ')
265
 
                path = l[:pos].decode('utf-8')
266
 
                if path in self._cache:
267
 
                    trace.warning('duplicated path %r in cache' % path)
268
 
                    continue
269
 
 
270
 
                pos += 3
271
 
                fields = l[pos:].split(b' ')
272
 
                if len(fields) != 7:
273
 
                    trace.warning("bad line in hashcache: %r" % l)
274
 
                    continue
275
 
 
276
 
                sha1 = fields[0]
277
 
                if len(sha1) != 40:
278
 
                    trace.warning("bad sha1 in hashcache: %r" % sha1)
279
 
                    continue
280
 
 
281
 
                fp = tuple(map(int, fields[1:]))
282
 
 
283
 
                self._cache[path] = (sha1, fp)
 
263
        hdr = inf.readline()
 
264
        if hdr != CACHE_HEADER:
 
265
            trace.mutter('cache header marker not found at top of %s;'
 
266
                         ' discarding cache', fn)
 
267
            self.needs_write = True
 
268
            return
 
269
 
 
270
        for l in inf:
 
271
            pos = l.index('// ')
 
272
            path = l[:pos].decode('utf-8')
 
273
            if path in self._cache:
 
274
                trace.warning('duplicated path %r in cache' % path)
 
275
                continue
 
276
 
 
277
            pos += 3
 
278
            fields = l[pos:].split(' ')
 
279
            if len(fields) != 7:
 
280
                trace.warning("bad line in hashcache: %r" % l)
 
281
                continue
 
282
 
 
283
            sha1 = fields[0]
 
284
            if len(sha1) != 40:
 
285
                trace.warning("bad sha1 in hashcache: %r" % sha1)
 
286
                continue
 
287
 
 
288
            fp = tuple(map(long, fields[1:]))
 
289
 
 
290
            self._cache[path] = (sha1, fp)
 
291
 
 
292
        # GZ 2009-09-20: Should really use a try/finally block to ensure close
 
293
        inf.close()
284
294
 
285
295
        self.needs_write = False
286
296
 
303
313
            return None
304
314
        # we discard any high precision because it's not reliable; perhaps we
305
315
        # could do better on some systems?
306
 
        return (stat_value.st_size, int(stat_value.st_mtime),
307
 
                int(stat_value.st_ctime), stat_value.st_ino,
 
316
        return (stat_value.st_size, long(stat_value.st_mtime),
 
317
                long(stat_value.st_ctime), stat_value.st_ino,
308
318
                stat_value.st_dev, stat_value.st_mode)