Klaus Demo klaus / 0861604
Improve caching Refs #238 Jonas Haag 4 months ago
3 changed file(s) with 81 addition(s) and 26 deletion(s). Raw diff Collapse all Expand all
1010
1111 from klaus.utils import force_unicode, parent_directory, encode_for_git, decode_from_git
1212 from klaus.diff import render_diff
13
14
15 NOT_SET = '__not_set__'
16
17
18 def cached_call(key, validator, producer, _cache={}):
19 data, old_validator = _cache.get(key, (None, NOT_SET))
20 if old_validator != validator:
21 data = producer()
22 _cache[key] = (data, validator)
23 return data
1324
1425
1526 class FancyRepo(dulwich.repo.Repo):
3041
3142 def get_last_updated_at(self):
3243 """Get datetime of last commit to this repository."""
33 def _get_last_updated_at():
34 refs = []
35 for ref_hash in self.get_refs().values():
36 try:
37 refs.append(self[ref_hash])
38 except KeyError:
39 # Whoops. The ref points at a non-existant object
40 pass
41 refs.sort(key=lambda obj:getattr(obj, 'commit_time', float('-inf')),
42 reverse=True)
43 for ref in refs:
44 # Find the latest ref that has a commit_time; tags do not
45 # have a commit time
46 if hasattr(ref, "commit_time"):
47 return ref.commit_time
48 return None
49
5044 # Cache result to speed up repo_list.html template.
51 # If self.refs.keys() as changed, we should invalidate the cache.
52 cache_key = self.refs.keys()
53 if cache_key != getattr(self, '_last_updated_at_cache_key', None):
54 self._last_updated_at_cache_retval = _get_last_updated_at()
55 self._last_updated_at_cache_key = cache_key
56 return self._last_updated_at_cache_retval
45 # If self.get_refs() has changed, we should invalidate the cache.
46 all_refs = self.get_refs()
47 return cached_call(
48 key=(id(self), 'get_last_updated_at'),
49 validator=all_refs,
50 producer=lambda: self._get_last_updated_at(all_refs)
51 )
52
53 def _get_last_updated_at(self, all_refs):
54 resolveable_refs = []
55 for ref_hash in all_refs:
56 try:
57 resolveable_refs.append(self[ref_hash])
58 except KeyError:
59 # Whoops. The ref points at a non-existant object
60 pass
61 resolveable_refs.sort(
62 key=lambda obj:getattr(obj, 'commit_time', float('-inf')),
63 reverse=True
64 )
65 for ref in resolveable_refs:
66 # Find the latest ref that has a commit_time; tags do not
67 # have a commit time
68 if hasattr(ref, "commit_time"):
69 return ref.commit_time
70 return None
5771
5872 @property
5973 def cloneurl(self):
7185 """Like Dulwich's `get_description`, but returns None if the file
7286 contains Git's default text "Unnamed repository[...]".
7387 """
88 # Cache result to speed up repo_list.html template.
89 # If description file mtime has changed, we should invalidate the cache.
90 description_file = os.path.join(self._controldir, 'description')
91 try:
92 description_mtime = os.stat(os.path.join(self._controldir, 'description')).st_mtime
93 except OSError:
94 description_mtime = None
95
96 return cached_call(
97 key=(id(self), 'get_description'),
98 validator=description_mtime,
99 producer=self._get_description
100 )
101
102 def _get_description(self):
74103 description = super(FancyRepo, self).get_description()
75104 if description:
76105 description = force_unicode(description)
274303 bytesio = io.BytesIO()
275304 dulwich.patch.write_tree_diff(bytesio, self.object_store, parent_tree, commit.tree)
276305 return bytesio.getvalue()
306
307 def freeze(self):
308 return FrozenFancyRepo(self)
309
310
311 class FrozenFancyRepo(object):
312 """A special version of FancyRepo that assumes the underlying Git
313 repository does not change. Used for performance optimizations.
314 """
315 def __init__(self, repo):
316 self.__repo = repo
317 self.__last_updated_at = NOT_SET
318
319 def __setattr__(self, name, value):
320 if not name.startswith('_FrozenFancyRepo__'):
321 raise TypeError("Can't set %s attribute on FrozenFancyRepo" % name)
322 super(FrozenFancyRepo, self).__setattr__(name, value)
323
324 def __getattr__(self, name):
325 return getattr(self.__repo, name)
326
327 def fast_get_last_updated_at(self):
328 if self.__last_updated_at is NOT_SET:
329 self.__last_updated_at = self.__repo.get_last_updated_at()
330 return self.__last_updated_at
1111 </h2>
1212 <ul class=repolist>
1313 {% for repo in repos %}
14 {% set last_updated_at = repo.get_last_updated_at() %}
14 {% set last_updated_at = repo.fast_get_last_updated_at() %}
1515 {% set description = repo.get_description() %}
1616 <li>
1717 <a
3434 if 'by-name' in request.args:
3535 sort_key = lambda repo: repo.name
3636 else:
37 sort_key = lambda repo: (-(repo.get_last_updated_at() or -1), repo.name)
38 repos = sorted(current_app.repos.values(), key=sort_key)
37 sort_key = lambda repo: (-(repo.fast_get_last_updated_at() or -1), repo.name)
38 repos = sorted([repo.freeze() for repo in current_app.repos.values()],
39 key=sort_key)
3940 return render_template('repo_list.html', repos=repos, base_href=None)
4041
4142