##// END OF EJS Templates
refs: refactored references code to remove any pylons elements....
marcink -
r1898:981cb51c default
parent child Browse files
Show More
@@ -1,357 +1,363 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import time
22 22 import logging
23 23
24 24 from pyramid.httpexceptions import HTTPFound
25 25
26 26 from rhodecode.lib import helpers as h
27 27 from rhodecode.lib.utils2 import StrictAttributeDict, safe_int, datetime_to_time
28 28 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
29 from rhodecode.lib.ext_json import json
30 29 from rhodecode.model import repo
31 30 from rhodecode.model import repo_group
32 31 from rhodecode.model.db import User
33 32 from rhodecode.model.scm import ScmModel
34 33
35 34 log = logging.getLogger(__name__)
36 35
37 36
38 37 ADMIN_PREFIX = '/_admin'
39 38 STATIC_FILE_PREFIX = '/_static'
40 39
41 40
42 41 def add_route_with_slash(config,name, pattern, **kw):
43 42 config.add_route(name, pattern, **kw)
44 43 if not pattern.endswith('/'):
45 44 config.add_route(name + '_slash', pattern + '/', **kw)
46 45
47 46
48 47 def get_format_ref_id(repo):
49 48 """Returns a `repo` specific reference formatter function"""
50 49 if h.is_svn(repo):
51 50 return _format_ref_id_svn
52 51 else:
53 52 return _format_ref_id
54 53
55 54
56 55 def _format_ref_id(name, raw_id):
57 56 """Default formatting of a given reference `name`"""
58 57 return name
59 58
60 59
61 60 def _format_ref_id_svn(name, raw_id):
62 61 """Special way of formatting a reference for Subversion including path"""
63 62 return '%s@%s' % (name, raw_id)
64 63
65 64
66 65 class TemplateArgs(StrictAttributeDict):
67 66 pass
68 67
69 68
70 69 class BaseAppView(object):
71 70
72 71 def __init__(self, context, request):
73 72 self.request = request
74 73 self.context = context
75 74 self.session = request.session
76 75 self._rhodecode_user = request.user # auth user
77 76 self._rhodecode_db_user = self._rhodecode_user.get_instance()
78 77 self._maybe_needs_password_change(
79 78 request.matched_route.name, self._rhodecode_db_user)
80 79
81 80 def _maybe_needs_password_change(self, view_name, user_obj):
82 81 log.debug('Checking if user %s needs password change on view %s',
83 82 user_obj, view_name)
84 83 skip_user_views = [
85 84 'logout', 'login',
86 85 'my_account_password', 'my_account_password_update'
87 86 ]
88 87
89 88 if not user_obj:
90 89 return
91 90
92 91 if user_obj.username == User.DEFAULT_USER:
93 92 return
94 93
95 94 now = time.time()
96 95 should_change = user_obj.user_data.get('force_password_change')
97 96 change_after = safe_int(should_change) or 0
98 97 if should_change and now > change_after:
99 98 log.debug('User %s requires password change', user_obj)
100 99 h.flash('You are required to change your password', 'warning',
101 100 ignore_duplicate=True)
102 101
103 102 if view_name not in skip_user_views:
104 103 raise HTTPFound(
105 104 self.request.route_path('my_account_password'))
106 105
107 106 def _get_local_tmpl_context(self, include_app_defaults=False):
108 107 c = TemplateArgs()
109 108 c.auth_user = self.request.user
110 109 if include_app_defaults:
111 110 # NOTE(marcink): after full pyramid migration include_app_defaults
112 111 # should be turned on by default
113 112 from rhodecode.lib.base import attach_context_attributes
114 113 attach_context_attributes(c, self.request, self.request.user.user_id)
115 114 return c
116 115
117 116 def _register_global_c(self, tmpl_args):
118 117 """
119 118 Registers attributes to pylons global `c`
120 119 """
121 120 # TODO(marcink): remove once pyramid migration is finished
122 121 from pylons import tmpl_context as c
123 122 for k, v in tmpl_args.items():
124 123 setattr(c, k, v)
125 124
126 125 def _get_template_context(self, tmpl_args):
127 126 self._register_global_c(tmpl_args)
128 127
129 128 local_tmpl_args = {
130 129 'defaults': {},
131 130 'errors': {},
132 131 }
133 132 local_tmpl_args.update(tmpl_args)
134 133 return local_tmpl_args
135 134
136 135 def load_default_context(self):
137 136 """
138 137 example:
139 138
140 139 def load_default_context(self):
141 140 c = self._get_local_tmpl_context()
142 141 c.custom_var = 'foobar'
143 142 self._register_global_c(c)
144 143 return c
145 144 """
146 145 raise NotImplementedError('Needs implementation in view class')
147 146
148 147
149 148 class RepoAppView(BaseAppView):
150 149
151 150 def __init__(self, context, request):
152 151 super(RepoAppView, self).__init__(context, request)
153 152 self.db_repo = request.db_repo
154 153 self.db_repo_name = self.db_repo.repo_name
155 154 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
156 155
157 156 def _handle_missing_requirements(self, error):
158 157 log.error(
159 158 'Requirements are missing for repository %s: %s',
160 159 self.db_repo_name, error.message)
161 160
162 161 def _get_local_tmpl_context(self, include_app_defaults=False):
163 162 c = super(RepoAppView, self)._get_local_tmpl_context(
164 163 include_app_defaults=include_app_defaults)
165 164
166 165 # register common vars for this type of view
167 166 c.rhodecode_db_repo = self.db_repo
168 167 c.repo_name = self.db_repo_name
169 168 c.repository_pull_requests = self.db_repo_pull_requests
170 169
171 170 c.repository_requirements_missing = False
172 171 try:
173 172 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
174 173 except RepositoryRequirementError as e:
175 174 c.repository_requirements_missing = True
176 175 self._handle_missing_requirements(e)
177 176
178 177 return c
179 178
180 179
181 180 class DataGridAppView(object):
182 181 """
183 182 Common class to have re-usable grid rendering components
184 183 """
185 184
186 185 def _extract_ordering(self, request, column_map=None):
187 186 column_map = column_map or {}
188 187 column_index = safe_int(request.GET.get('order[0][column]'))
189 188 order_dir = request.GET.get(
190 189 'order[0][dir]', 'desc')
191 190 order_by = request.GET.get(
192 191 'columns[%s][data][sort]' % column_index, 'name_raw')
193 192
194 193 # translate datatable to DB columns
195 194 order_by = column_map.get(order_by) or order_by
196 195
197 196 search_q = request.GET.get('search[value]')
198 197 return search_q, order_by, order_dir
199 198
200 199 def _extract_chunk(self, request):
201 200 start = safe_int(request.GET.get('start'), 0)
202 201 length = safe_int(request.GET.get('length'), 25)
203 202 draw = safe_int(request.GET.get('draw'))
204 203 return draw, start, length
205 204
206 205
207 206 class BaseReferencesView(RepoAppView):
208 207 """
209 208 Base for reference view for branches, tags and bookmarks.
210 209 """
211 210 def load_default_context(self):
212 211 c = self._get_local_tmpl_context()
213 212
214 213 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
215 214 c.repo_info = self.db_repo
216 215
217 216 self._register_global_c(c)
218 217 return c
219 218
220 219 def load_refs_context(self, ref_items, partials_template):
221 _data = []
222 220 _render = self.request.get_partial_renderer(partials_template)
223 221 pre_load = ["author", "date", "message"]
224 222
225 223 is_svn = h.is_svn(self.rhodecode_vcs_repo)
224 is_hg = h.is_hg(self.rhodecode_vcs_repo)
225
226 226 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
227 227
228 closed_refs = {}
229 if is_hg:
230 closed_refs = self.rhodecode_vcs_repo.branches_closed
231
232 data = []
228 233 for ref_name, commit_id in ref_items:
229 234 commit = self.rhodecode_vcs_repo.get_commit(
230 235 commit_id=commit_id, pre_load=pre_load)
236 closed = ref_name in closed_refs
231 237
232 238 # TODO: johbo: Unify generation of reference links
233 239 use_commit_id = '/' in ref_name or is_svn
234 240 files_url = h.url(
235 241 'files_home',
236 repo_name=c.repo_name,
242 repo_name=self.db_repo_name,
237 243 f_path=ref_name if is_svn else '',
238 244 revision=commit_id if use_commit_id else ref_name,
239 245 at=ref_name)
240 246
241 _data.append({
242 "name": _render('name', ref_name, files_url),
247 data.append({
248 "name": _render('name', ref_name, files_url, closed),
243 249 "name_raw": ref_name,
244 250 "date": _render('date', commit.date),
245 251 "date_raw": datetime_to_time(commit.date),
246 252 "author": _render('author', commit.author),
247 253 "commit": _render(
248 254 'commit', commit.message, commit.raw_id, commit.idx),
249 255 "commit_raw": commit.idx,
250 256 "compare": _render(
251 257 'compare', format_ref_id(ref_name, commit.raw_id)),
252 258 })
253 c.has_references = bool(_data)
254 c.data = json.dumps(_data)
259
260 return data
255 261
256 262
257 263 class RepoRoutePredicate(object):
258 264 def __init__(self, val, config):
259 265 self.val = val
260 266
261 267 def text(self):
262 268 return 'repo_route = %s' % self.val
263 269
264 270 phash = text
265 271
266 272 def __call__(self, info, request):
267 273
268 274 if hasattr(request, 'vcs_call'):
269 275 # skip vcs calls
270 276 return
271 277
272 278 repo_name = info['match']['repo_name']
273 279 repo_model = repo.RepoModel()
274 280 by_name_match = repo_model.get_by_repo_name(repo_name, cache=True)
275 281
276 282 if by_name_match:
277 283 # register this as request object we can re-use later
278 284 request.db_repo = by_name_match
279 285 return True
280 286
281 287 by_id_match = repo_model.get_repo_by_id(repo_name)
282 288 if by_id_match:
283 289 request.db_repo = by_id_match
284 290 return True
285 291
286 292 return False
287 293
288 294
289 295 class RepoTypeRoutePredicate(object):
290 296 def __init__(self, val, config):
291 297 self.val = val or ['hg', 'git', 'svn']
292 298
293 299 def text(self):
294 300 return 'repo_accepted_type = %s' % self.val
295 301
296 302 phash = text
297 303
298 304 def __call__(self, info, request):
299 305 if hasattr(request, 'vcs_call'):
300 306 # skip vcs calls
301 307 return
302 308
303 309 rhodecode_db_repo = request.db_repo
304 310
305 311 log.debug(
306 312 '%s checking repo type for %s in %s',
307 313 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
308 314
309 315 if rhodecode_db_repo.repo_type in self.val:
310 316 return True
311 317 else:
312 318 log.warning('Current view is not supported for repo type:%s',
313 319 rhodecode_db_repo.repo_type)
314 320 #
315 321 # h.flash(h.literal(
316 322 # _('Action not supported for %s.' % rhodecode_repo.alias)),
317 323 # category='warning')
318 324 # return redirect(
319 325 # route_path('repo_summary', repo_name=cls.rhodecode_db_repo.repo_name))
320 326
321 327 return False
322 328
323 329
324 330 class RepoGroupRoutePredicate(object):
325 331 def __init__(self, val, config):
326 332 self.val = val
327 333
328 334 def text(self):
329 335 return 'repo_group_route = %s' % self.val
330 336
331 337 phash = text
332 338
333 339 def __call__(self, info, request):
334 340 if hasattr(request, 'vcs_call'):
335 341 # skip vcs calls
336 342 return
337 343
338 344 repo_group_name = info['match']['repo_group_name']
339 345 repo_group_model = repo_group.RepoGroupModel()
340 346 by_name_match = repo_group_model.get_by_group_name(
341 347 repo_group_name, cache=True)
342 348
343 349 if by_name_match:
344 350 # register this as request object we can re-use later
345 351 request.db_repo_group = by_name_match
346 352 return True
347 353
348 354 return False
349 355
350 356
351 357 def includeme(config):
352 358 config.add_route_predicate(
353 359 'repo_route', RepoRoutePredicate)
354 360 config.add_route_predicate(
355 361 'repo_accepted_types', RepoTypeRoutePredicate)
356 362 config.add_route_predicate(
357 363 'repo_group_route', RepoGroupRoutePredicate)
@@ -1,50 +1,54 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 import logging
21 21
22 22 from pyramid.httpexceptions import HTTPNotFound
23 23 from pyramid.view import view_config
24 24
25 25 from rhodecode.apps._base import BaseReferencesView
26 from rhodecode.lib.ext_json import json
27 from rhodecode.lib import helpers as h
26 28 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator)
27 from rhodecode.lib import helpers as h
29
28 30
29 31 log = logging.getLogger(__name__)
30 32
31 33
32 34 class RepoBookmarksView(BaseReferencesView):
33 35
34 36 @LoginRequired()
35 37 @HasRepoPermissionAnyDecorator(
36 38 'repository.read', 'repository.write', 'repository.admin')
37 39 @view_config(
38 40 route_name='bookmarks_home', request_method='GET',
39 41 renderer='rhodecode:templates/bookmarks/bookmarks.mako')
40 42 def bookmarks(self):
41 43 c = self.load_default_context()
42 44
43 45 if not h.is_hg(self.db_repo):
44 46 raise HTTPNotFound()
45 47
46 48 ref_items = self.rhodecode_vcs_repo.bookmarks.items()
47 self.load_refs_context(
49 data = self.load_refs_context(
48 50 ref_items=ref_items, partials_template='bookmarks/bookmarks_data.mako')
49 51
52 c.has_references = bool(data)
53 c.data = json.dumps(data)
50 54 return self._get_template_context(c)
@@ -1,51 +1,49 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 from pyramid.view import view_config
23 23
24 24 from rhodecode.apps._base import BaseReferencesView
25 from rhodecode.lib.ext_json import json
25 26 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator)
26 27
27 28
28 29 log = logging.getLogger(__name__)
29 30
30 31
31 32 class RepoBranchesView(BaseReferencesView):
32 33
33 34 @LoginRequired()
34 35 @HasRepoPermissionAnyDecorator(
35 36 'repository.read', 'repository.write', 'repository.admin')
36 37 @view_config(
37 38 route_name='branches_home', request_method='GET',
38 39 renderer='rhodecode:templates/branches/branches.mako')
39 40 def branches(self):
40 41 c = self.load_default_context()
41 c.closed_branches = self.rhodecode_vcs_repo.branches_closed
42 # NOTE(marcink):
43 # we need this trick because of PartialRenderer still uses the
44 # global 'c', we might not need this after full pylons migration
45 self._register_global_c(c)
46 42
47 43 ref_items = self.rhodecode_vcs_repo.branches_all.items()
48 self.load_refs_context(
44 data = self.load_refs_context(
49 45 ref_items=ref_items, partials_template='branches/branches_data.mako')
50 46
47 c.has_references = bool(data)
48 c.data = json.dumps(data)
51 49 return self._get_template_context(c)
@@ -1,45 +1,48 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-2017 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 from pyramid.view import view_config
23 23
24 24 from rhodecode.apps._base import BaseReferencesView
25 from rhodecode.lib.ext_json import json
25 26 from rhodecode.lib.auth import (LoginRequired, HasRepoPermissionAnyDecorator)
26 27
27 28 log = logging.getLogger(__name__)
28 29
29 30
30 31 class RepoTagsView(BaseReferencesView):
31 32
32 33 @LoginRequired()
33 34 @HasRepoPermissionAnyDecorator(
34 35 'repository.read', 'repository.write', 'repository.admin')
35 36 @view_config(
36 37 route_name='tags_home', request_method='GET',
37 38 renderer='rhodecode:templates/tags/tags.mako')
38 39 def tags(self):
39 40 c = self.load_default_context()
40 41
41 42 ref_items = self.rhodecode_vcs_repo.tags.items()
42 self.load_refs_context(
43 data = self.load_refs_context(
43 44 ref_items=ref_items, partials_template='tags/tags_data.mako')
44 45
46 c.has_references = bool(data)
47 c.data = json.dumps(data)
45 48 return self._get_template_context(c)
@@ -1,33 +1,33 b''
1 1 ## DATA TABLE RE USABLE ELEMENTS FOR BOOKMARKS
2 2 ## usage:
3 3 ## <%namespace name="bookmarks" file="/bookmarks/bookmarks_data.mako"/>
4 4 ## bookmarks.<func_name>(arg,arg2)
5 5
6 6 <%def name="compare(commit_id)">
7 7 <input class="compare-radio-button" type="radio" name="compare_source" value="${commit_id}"/>
8 8 <input class="compare-radio-button" type="radio" name="compare_target" value="${commit_id}"/>
9 9 </%def>
10 10
11 11
12 <%def name="name(name, files_url)">
12 <%def name="name(name, files_url, closed)">
13 13 <span class="tag booktag" title="${h.tooltip(_('Bookmark %s') % (name,))}">
14 14 <a href="${files_url}">
15 15 <i class="icon-bookmark"></i>
16 16 ${name}
17 17 </a>
18 18 </span>
19 19 </%def>
20 20
21 21 <%def name="date(date)">
22 22 ${h.age_component(date)}
23 23 </%def>
24 24
25 25 <%def name="author(author)">
26 26 <span class="tooltip" title="${h.tooltip(author)}">${h.link_to_user(author)}</span>
27 27 </%def>
28 28
29 29 <%def name="commit(message, commit_id, commit_idx)">
30 30 <div>
31 31 <pre><a title="${h.tooltip(message)}" href="${h.url('files_home',repo_name=c.repo_name,revision=commit_id)}">r${commit_idx}:${h.short_id(commit_id)}</a></pre>
32 32 </div>
33 33 </%def>
@@ -1,33 +1,33 b''
1 1 ## DATA TABLE RE USABLE ELEMENTS FOR BRANCHES
2 2 ## usage:
3 3 ## <%namespace name="branch" file="/branches/branches_data.mako"/>
4 4 ## branch.<func_name>(arg,arg2)
5 5
6 6 <%def name="compare(commit_id)">
7 7 <input class="compare-radio-button" type="radio" name="compare_source" value="${commit_id}"/>
8 8 <input class="compare-radio-button" type="radio" name="compare_target" value="${commit_id}"/>
9 9 </%def>
10 10
11 <%def name="name(name, files_url)">
11 <%def name="name(name, files_url, closed)">
12 12 <span class="tag branchtag" title="${h.tooltip(_('Branch %s') % (name,))}">
13 13 <a href="${files_url}"><i class="icon-code-fork"></i>${name}
14 %if name in c.closed_branches:
14 %if closed:
15 15 [closed]
16 16 %endif
17 17 </a>
18 18 </span>
19 19 </%def>
20 20
21 21 <%def name="date(date)">
22 22 ${h.age_component(date)}
23 23 </%def>
24 24
25 25 <%def name="author(author)">
26 26 <span class="tooltip" title="${h.tooltip(author)}">${h.link_to_user(author)}</span>
27 27 </%def>
28 28
29 29 <%def name="commit(message, commit_id, commit_idx)">
30 30 <div>
31 31 <pre><a title="${h.tooltip(message)}" href="${h.url('files_home',repo_name=c.repo_name,revision=commit_id)}">r${commit_idx}:${h.short_id(commit_id)}</a></pre>
32 32 </div>
33 33 </%def>
@@ -1,29 +1,29 b''
1 1 ## DATA TABLE RE USABLE ELEMENTS FOR TAGS
2 2 ## usage:
3 3 ## <%namespace name="tags" file="/tags/tags_data.mako"/>
4 4 ## tags.<func_name>(arg,arg2)
5 5
6 6 <%def name="compare(commit_id)">
7 7 <input class="compare-radio-button" type="radio" name="compare_source" value="${commit_id}"/>
8 8 <input class="compare-radio-button" type="radio" name="compare_target" value="${commit_id}"/>
9 9 </%def>
10 10
11 <%def name="name(name, files_url)">
11 <%def name="name(name, files_url, closed)">
12 12 <span class="tagtag tag" title="${h.tooltip(_('Tag %s') % (name,))}">
13 13 <a href="${files_url}"><i class="icon-tag"></i>${name}</a>
14 14 </span>
15 15 </%def>
16 16
17 17 <%def name="date(date)">
18 18 ${h.age_component(date)}
19 19 </%def>
20 20
21 21 <%def name="author(author)">
22 22 <span class="tooltip" title="${h.tooltip(author)}">${h.link_to_user(author)}</span>
23 23 </%def>
24 24
25 25 <%def name="commit(message, commit_id, commit_idx)">
26 26 <div>
27 27 <pre><a title="${h.tooltip(message)}" href="${h.url('files_home',repo_name=c.repo_name,revision=commit_id)}">r${commit_idx}:${h.short_id(commit_id)}</a></pre>
28 28 </div>
29 29 </%def>
General Comments 0
You need to be logged in to leave comments. Login now