Klaus Demo ~jonashaag/klaus / fbfa171
Revert "Refactor to prepare for caching overhaul. Refs #248" Not working on caching at the moment... This reverts commit e21cf6a3eb47ec0d4cb268896007092cd648a43f. Jonas Haag 19 days ago
3 changed file(s) with 101 addition(s) and 65 deletion(s). Raw diff Collapse all Expand all
1616 NOT_SET = '__not_set__'
1717
1818
19 def cached_call(key, validator, producer, _cache={}):
20 data, old_validator = _cache.get(key, (None, NOT_SET))
21 if old_validator != validator:
22 data = producer()
23 _cache[key] = (data, validator)
24 return data
25
26
1927 class FancyRepo(dulwich.repo.Repo):
2028 """A wrapper around Dulwich's Repo that adds some helper methods."""
2129 @property
2230 def name(self):
2331 return repo_human_name(self.path)
2432
25 def getref(self, k, default=NOT_SET):
26 try:
27 return self[k]
28 except KeyError:
29 if default is not NOT_SET:
30 return default
31 else:
32 raise
33
34 def get_refs_as_dict(self, base=None):
35 return self.refs.as_dict(base)
36
37 def get_resolved_refs_as_dict(self, base=None, resolve_default=NOT_SET):
38 res = {}
39 for k, v in self.get_refs_as_dict(base).items():
40 v = self.getref(v, default=None)
41 if v is None and resolve_default is NOT_SET:
42 # Skip unresolvable refs when no default is given.
43 pass
44 else:
45 res[k] = v or resolve_default
46 return res
47
4833 # TODO: factor out stuff into dulwich
4934 def get_last_updated_at(self):
5035 """Get datetime of last commit to this repository."""
51 commit_times = [getattr(obj, 'commit_time', float('-inf'))
52 for obj in self.get_resolved_refs_as_dict().values()]
53 if commit_times:
54 return max(commit_times)
55 else:
56 return None
36 # Cache result to speed up repo_list.html template.
37 # If self.get_refs() has changed, we should invalidate the cache.
38 all_refs = self.get_refs()
39 return cached_call(
40 key=(id(self), 'get_last_updated_at'),
41 validator=all_refs,
42 producer=lambda: self._get_last_updated_at(all_refs)
43 )
44
45 def _get_last_updated_at(self, all_refs):
46 resolveable_refs = []
47 for ref_hash in all_refs:
48 try:
49 resolveable_refs.append(self[ref_hash])
50 except KeyError:
51 # Whoops. The ref points at a non-existant object
52 pass
53 resolveable_refs.sort(
54 key=lambda obj:getattr(obj, 'commit_time', float('-inf')),
55 reverse=True
56 )
57 for ref in resolveable_refs:
58 # Find the latest ref that has a commit_time; tags do not
59 # have a commit time
60 if hasattr(ref, "commit_time"):
61 return ref.commit_time
62 return None
5763
5864 @property
5965 def cloneurl(self):
7177 """Like Dulwich's `get_description`, but returns None if the file
7278 contains Git's default text "Unnamed repository[...]".
7379 """
80 # Cache result to speed up repo_list.html template.
81 # If description file mtime has changed, we should invalidate the cache.
82 description_file = os.path.join(self._controldir, 'description')
83 try:
84 description_mtime = os.stat(os.path.join(self._controldir, 'description')).st_mtime
85 except OSError:
86 description_mtime = None
87
88 return cached_call(
89 key=(id(self), 'get_description'),
90 validator=description_mtime,
91 producer=self._get_description
92 )
93
94 def _get_description(self):
7495 description = super(FancyRepo, self).get_description()
7596 if description:
7697 description = force_unicode(description)
82103 for prefix in ['refs/heads/', 'refs/tags/', '']:
83104 key = prefix + rev
84105 try:
85 obj = self.getref(encode_for_git(key))
106 obj = self[encode_for_git(key)]
86107 if isinstance(obj, dulwich.objects.Tag):
87 obj = self.getref(obj.object[1])
108 obj = self[obj.object[1]]
88109 return obj
89110 except KeyError:
90111 pass
107128 """Return a list of ref names that begin with `prefix`, ordered by the
108129 time they have been committed to last.
109130 """
110 def get_commit_time(obj):
111 if obj is None:
112 # Put refs that point to non-existent objects last.
131 def get_commit_time(refname):
132 try:
133 obj = self[refs[refname]]
134 except KeyError:
135 # Default to 0, i.e. sorting refs that point at non-existant
136 # objects last.
113137 return 0
114 elif isinstance(obj, dulwich.objects.Tag):
138 if isinstance(obj, dulwich.objects.Tag):
115139 return obj.tag_time
116 else:
117 return obj.commit_time
118
119 refs = self.get_resolved_refs_as_dict(
120 encode_for_git(prefix),
121 resolve_default=None
122 )
140 return obj.commit_time
141
142 refs = self.refs.as_dict(encode_for_git(prefix))
123143 if exclude:
124144 refs.pop(prefix + exclude, None)
125 sorted_refs = sorted(
126 refs.items(),
127 key=lambda item: get_commit_time(item[1]),
128 reverse=True
129 )
130 return [decode_from_git(name) for name, _ in sorted_refs]
145 sorted_names = sorted(refs.keys(), key=get_commit_time, reverse=True)
146 return [decode_from_git(ref) for ref in sorted_names]
131147
132148 def get_branch_names(self, exclude=None):
133149 """Return a list of branch names of this repo, ordered by the time they
141157
142158 def get_tag_and_branch_shas(self):
143159 """Return a list of SHAs of all tags and branches."""
144 tag_shas = self.get_refs_as_dict(b'refs/tags/').values()
145 branch_shas = self.get_refs_as_dict(b'refs/heads/').values()
160 tag_shas = self.refs.as_dict(b'refs/tags/').values()
161 branch_shas = self.refs.as_dict(b'refs/heads/').values()
146162 return set(tag_shas) | set(branch_shas)
147163
148164 def history(self, commit, path=None, max_commits=None, skip=0):
170186
171187 output = subprocess.check_output(cmd, cwd=os.path.abspath(self.path))
172188 sha1_sums = output.strip().split(b'\n')
173 return [self.getref(sha1) for sha1 in sha1_sums]
189 return [self[sha1] for sha1 in sha1_sums]
174190
175191 def blame(self, commit, path):
176192 """Return a 'git blame' list for the file at `path`: For each line in
180196 cmd = ['git', 'blame', '-ls', '--root', decode_from_git(commit.id), '--', path]
181197 output = subprocess.check_output(cmd, cwd=os.path.abspath(self.path))
182198 sha1_sums = [line[:40] for line in output.strip().split(b'\n')]
183 lines = []
184 for sha1 in sha1_sums:
185 obj = self.getref(sha1, None)
186 if obj is not None:
187 obj = decode_from_git(obj.id)
188 lines.append(obj)
189 return lines
199 return [None if self[sha1] is None else decode_from_git(self[sha1].id) for sha1 in sha1_sums]
190200
191201 def get_blob_or_tree(self, commit, path):
192202 """Return the Git tree or blob object for `path` at `commit`."""
193203 try:
194 (mode, oid) = tree_lookup_path(self.getref, commit.tree,
204 (mode, oid) = tree_lookup_path(self.__getitem__, commit.tree,
195205 encode_for_git(path))
196206 except NotTreeError:
197207 # Some part of the path was a file where a folder was expected.
198208 # Example: path="/path/to/foo.txt" but "to" is a file in "/path".
199209 raise KeyError
200 return self.getref(oid)
210 return self[oid]
201211
202212 def listdir(self, commit, path):
203213 """Return a list of submodules, directories and files in given
232242 from klaus.utils import guess_is_binary
233243
234244 if commit.parents:
235 parent_tree = self.getref(commit.parents[0]).tree
245 parent_tree = self[commit.parents[0]].tree
236246 else:
237247 parent_tree = None
238248
279289
280290 def raw_commit_diff(self, commit):
281291 if commit.parents:
282 parent_tree = self.getref(commit.parents[0]).tree
292 parent_tree = self[commit.parents[0]].tree
283293 else:
284294 parent_tree = None
285295 bytesio = io.BytesIO()
286296 dulwich.patch.write_tree_diff(bytesio, self.object_store, parent_tree, commit.tree)
287297 return bytesio.getvalue()
288298
299 def freeze(self):
300 return FrozenFancyRepo(self)
301
302
303 class FrozenFancyRepo(object):
304 """A special version of FancyRepo that assumes the underlying Git
305 repository does not change. Used for performance optimizations.
306 """
307 def __init__(self, repo):
308 self.__repo = repo
309 self.__last_updated_at = NOT_SET
310
311 def __setattr__(self, name, value):
312 if not name.startswith('_FrozenFancyRepo__'):
313 raise TypeError("Can't set %s attribute on FrozenFancyRepo" % name)
314 super(FrozenFancyRepo, self).__setattr__(name, value)
315
316 def __getattr__(self, name):
317 return getattr(self.__repo, name)
318
319 def fast_get_last_updated_at(self):
320 if self.__last_updated_at is NOT_SET:
321 self.__last_updated_at = self.__repo.get_last_updated_at()
322 return self.__last_updated_at
323
289324
290325 class InvalidRepo:
291326 """Represent an invalid repository and store pertinent data."""
1515 </h2>
1616 <ul class=repolist>
1717 {% for repo in repos %}
18 {% set last_updated_at = repo.get_last_updated_at() %}
18 {% set last_updated_at = repo.fast_get_last_updated_at() %}
1919 {% set description = repo.get_description() %}
2020 <li>
2121 <a
3636 sort_key = lambda repo: repo.name
3737 else:
3838 order_by = 'last_updated'
39 sort_key = lambda repo: (-(repo.get_last_updated_at() or -1), repo.name)
40 repos = sorted(current_app.valid_repos.values(), key=sort_key)
39 sort_key = lambda repo: (-(repo.fast_get_last_updated_at() or -1), repo.name)
40 repos = sorted([repo.freeze() for repo in current_app.valid_repos.values()],
41 key=sort_key)
4142 invalid_repos = sorted(current_app.invalid_repos.values(), key=lambda repo: repo.name)
4243 return render_template('repo_list.html', repos=repos, invalid_repos=invalid_repos,
4344 order_by=order_by, base_href=None)