##// END OF EJS Templates
pull-requests: prepare the migration of pull request to pyramid....
marcink -
r1813:07e2beb0 default
parent child Browse files
Show More
@@ -20,18 +20,23 b''
20 20
21 21 import logging
22 22
23 import collections
24 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
23 25 from pyramid.view import view_config
24 26
25 27 from rhodecode.apps._base import RepoAppView, DataGridAppView
26 from rhodecode.lib import helpers as h
27 from rhodecode.lib import audit_logger
28 from rhodecode.lib import helpers as h, diffs, codeblocks
28 29 from rhodecode.lib.auth import (
29 30 LoginRequired, HasRepoPermissionAnyDecorator)
30 31 from rhodecode.lib.utils import PartialRenderer
31 from rhodecode.lib.utils2 import str2bool
32 from rhodecode.lib.utils2 import str2bool, safe_int, safe_str
33 from rhodecode.lib.vcs.backends.base import EmptyCommit
34 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError, \
35 RepositoryRequirementError, NodeDoesNotExistError
32 36 from rhodecode.model.comment import CommentsModel
33 from rhodecode.model.db import PullRequest
34 from rhodecode.model.pull_request import PullRequestModel
37 from rhodecode.model.db import PullRequest, PullRequestVersion, \
38 ChangesetComment, ChangesetStatus
39 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
35 40
36 41 log = logging.getLogger(__name__)
37 42
@@ -39,11 +44,11 b' log = logging.getLogger(__name__)'
39 44 class RepoPullRequestsView(RepoAppView, DataGridAppView):
40 45
41 46 def load_default_context(self):
42 c = self._get_local_tmpl_context()
43
47 c = self._get_local_tmpl_context(include_app_defaults=True)
44 48 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
45 49 c.repo_info = self.db_repo
46
50 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
51 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
47 52 self._register_global_c(c)
48 53 return c
49 54
@@ -182,3 +187,398 b' class RepoPullRequestsView(RepoAppView, '
182 187 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
183 188
184 189 return data
190
191 def _get_pr_version(self, pull_request_id, version=None):
192 pull_request_id = safe_int(pull_request_id)
193 at_version = None
194
195 if version and version == 'latest':
196 pull_request_ver = PullRequest.get(pull_request_id)
197 pull_request_obj = pull_request_ver
198 _org_pull_request_obj = pull_request_obj
199 at_version = 'latest'
200 elif version:
201 pull_request_ver = PullRequestVersion.get_or_404(version)
202 pull_request_obj = pull_request_ver
203 _org_pull_request_obj = pull_request_ver.pull_request
204 at_version = pull_request_ver.pull_request_version_id
205 else:
206 _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(
207 pull_request_id)
208
209 pull_request_display_obj = PullRequest.get_pr_display_object(
210 pull_request_obj, _org_pull_request_obj)
211
212 return _org_pull_request_obj, pull_request_obj, \
213 pull_request_display_obj, at_version
214
215 def _get_diffset(self, source_repo_name, source_repo,
216 source_ref_id, target_ref_id,
217 target_commit, source_commit, diff_limit, fulldiff,
218 file_limit, display_inline_comments):
219
220 vcs_diff = PullRequestModel().get_diff(
221 source_repo, source_ref_id, target_ref_id)
222
223 diff_processor = diffs.DiffProcessor(
224 vcs_diff, format='newdiff', diff_limit=diff_limit,
225 file_limit=file_limit, show_full_diff=fulldiff)
226
227 _parsed = diff_processor.prepare()
228
229 def _node_getter(commit):
230 def get_node(fname):
231 try:
232 return commit.get_node(fname)
233 except NodeDoesNotExistError:
234 return None
235
236 return get_node
237
238 diffset = codeblocks.DiffSet(
239 repo_name=self.db_repo_name,
240 source_repo_name=source_repo_name,
241 source_node_getter=_node_getter(target_commit),
242 target_node_getter=_node_getter(source_commit),
243 comments=display_inline_comments
244 )
245 diffset = diffset.render_patchset(
246 _parsed, target_commit.raw_id, source_commit.raw_id)
247
248 return diffset
249
250 @LoginRequired()
251 @HasRepoPermissionAnyDecorator(
252 'repository.read', 'repository.write', 'repository.admin')
253 # @view_config(
254 # route_name='pullrequest_show', request_method='GET',
255 # renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
256 def pull_request_show(self):
257 pull_request_id = safe_int(
258 self.request.matchdict.get('pull_request_id'))
259 c = self.load_default_context()
260
261 version = self.request.GET.get('version')
262 from_version = self.request.GET.get('from_version') or version
263 merge_checks = self.request.GET.get('merge_checks')
264 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
265
266 (pull_request_latest,
267 pull_request_at_ver,
268 pull_request_display_obj,
269 at_version) = self._get_pr_version(
270 pull_request_id, version=version)
271 pr_closed = pull_request_latest.is_closed()
272
273 if pr_closed and (version or from_version):
274 # not allow to browse versions
275 raise HTTPFound(h.route_path(
276 'pullrequest_show', repo_name=self.db_repo_name,
277 pull_request_id=pull_request_id))
278
279 versions = pull_request_display_obj.versions()
280
281 c.at_version = at_version
282 c.at_version_num = (at_version
283 if at_version and at_version != 'latest'
284 else None)
285 c.at_version_pos = ChangesetComment.get_index_from_version(
286 c.at_version_num, versions)
287
288 (prev_pull_request_latest,
289 prev_pull_request_at_ver,
290 prev_pull_request_display_obj,
291 prev_at_version) = self._get_pr_version(
292 pull_request_id, version=from_version)
293
294 c.from_version = prev_at_version
295 c.from_version_num = (prev_at_version
296 if prev_at_version and prev_at_version != 'latest'
297 else None)
298 c.from_version_pos = ChangesetComment.get_index_from_version(
299 c.from_version_num, versions)
300
301 # define if we're in COMPARE mode or VIEW at version mode
302 compare = at_version != prev_at_version
303
304 # pull_requests repo_name we opened it against
305 # ie. target_repo must match
306 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
307 raise HTTPNotFound()
308
309 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
310 pull_request_at_ver)
311
312 c.pull_request = pull_request_display_obj
313 c.pull_request_latest = pull_request_latest
314
315 if compare or (at_version and not at_version == 'latest'):
316 c.allowed_to_change_status = False
317 c.allowed_to_update = False
318 c.allowed_to_merge = False
319 c.allowed_to_delete = False
320 c.allowed_to_comment = False
321 c.allowed_to_close = False
322 else:
323 can_change_status = PullRequestModel().check_user_change_status(
324 pull_request_at_ver, self._rhodecode_user)
325 c.allowed_to_change_status = can_change_status and not pr_closed
326
327 c.allowed_to_update = PullRequestModel().check_user_update(
328 pull_request_latest, self._rhodecode_user) and not pr_closed
329 c.allowed_to_merge = PullRequestModel().check_user_merge(
330 pull_request_latest, self._rhodecode_user) and not pr_closed
331 c.allowed_to_delete = PullRequestModel().check_user_delete(
332 pull_request_latest, self._rhodecode_user) and not pr_closed
333 c.allowed_to_comment = not pr_closed
334 c.allowed_to_close = c.allowed_to_merge and not pr_closed
335
336 c.forbid_adding_reviewers = False
337 c.forbid_author_to_review = False
338 c.forbid_commit_author_to_review = False
339
340 if pull_request_latest.reviewer_data and \
341 'rules' in pull_request_latest.reviewer_data:
342 rules = pull_request_latest.reviewer_data['rules'] or {}
343 try:
344 c.forbid_adding_reviewers = rules.get(
345 'forbid_adding_reviewers')
346 c.forbid_author_to_review = rules.get(
347 'forbid_author_to_review')
348 c.forbid_commit_author_to_review = rules.get(
349 'forbid_commit_author_to_review')
350 except Exception:
351 pass
352
353 # check merge capabilities
354 _merge_check = MergeCheck.validate(
355 pull_request_latest, user=self._rhodecode_user)
356 c.pr_merge_errors = _merge_check.error_details
357 c.pr_merge_possible = not _merge_check.failed
358 c.pr_merge_message = _merge_check.merge_msg
359
360 c.pull_request_review_status = _merge_check.review_status
361 if merge_checks:
362 self.request.override_renderer = \
363 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
364 return self._get_template_context(c)
365
366 comments_model = CommentsModel()
367
368 # reviewers and statuses
369 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
370 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
371
372 # GENERAL COMMENTS with versions #
373 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
374 q = q.order_by(ChangesetComment.comment_id.asc())
375 general_comments = q
376
377 # pick comments we want to render at current version
378 c.comment_versions = comments_model.aggregate_comments(
379 general_comments, versions, c.at_version_num)
380 c.comments = c.comment_versions[c.at_version_num]['until']
381
382 # INLINE COMMENTS with versions #
383 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
384 q = q.order_by(ChangesetComment.comment_id.asc())
385 inline_comments = q
386
387 c.inline_versions = comments_model.aggregate_comments(
388 inline_comments, versions, c.at_version_num, inline=True)
389
390 # inject latest version
391 latest_ver = PullRequest.get_pr_display_object(
392 pull_request_latest, pull_request_latest)
393
394 c.versions = versions + [latest_ver]
395
396 # if we use version, then do not show later comments
397 # than current version
398 display_inline_comments = collections.defaultdict(
399 lambda: collections.defaultdict(list))
400 for co in inline_comments:
401 if c.at_version_num:
402 # pick comments that are at least UPTO given version, so we
403 # don't render comments for higher version
404 should_render = co.pull_request_version_id and \
405 co.pull_request_version_id <= c.at_version_num
406 else:
407 # showing all, for 'latest'
408 should_render = True
409
410 if should_render:
411 display_inline_comments[co.f_path][co.line_no].append(co)
412
413 # load diff data into template context, if we use compare mode then
414 # diff is calculated based on changes between versions of PR
415
416 source_repo = pull_request_at_ver.source_repo
417 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
418
419 target_repo = pull_request_at_ver.target_repo
420 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
421
422 if compare:
423 # in compare switch the diff base to latest commit from prev version
424 target_ref_id = prev_pull_request_display_obj.revisions[0]
425
426 # despite opening commits for bookmarks/branches/tags, we always
427 # convert this to rev to prevent changes after bookmark or branch change
428 c.source_ref_type = 'rev'
429 c.source_ref = source_ref_id
430
431 c.target_ref_type = 'rev'
432 c.target_ref = target_ref_id
433
434 c.source_repo = source_repo
435 c.target_repo = target_repo
436
437 c.commit_ranges = []
438 source_commit = EmptyCommit()
439 target_commit = EmptyCommit()
440 c.missing_requirements = False
441
442 source_scm = source_repo.scm_instance()
443 target_scm = target_repo.scm_instance()
444
445 # try first shadow repo, fallback to regular repo
446 try:
447 commits_source_repo = pull_request_latest.get_shadow_repo()
448 except Exception:
449 log.debug('Failed to get shadow repo', exc_info=True)
450 commits_source_repo = source_scm
451
452 c.commits_source_repo = commits_source_repo
453 commit_cache = {}
454 try:
455 pre_load = ["author", "branch", "date", "message"]
456 show_revs = pull_request_at_ver.revisions
457 for rev in show_revs:
458 comm = commits_source_repo.get_commit(
459 commit_id=rev, pre_load=pre_load)
460 c.commit_ranges.append(comm)
461 commit_cache[comm.raw_id] = comm
462
463 # Order here matters, we first need to get target, and then
464 # the source
465 target_commit = commits_source_repo.get_commit(
466 commit_id=safe_str(target_ref_id))
467
468 source_commit = commits_source_repo.get_commit(
469 commit_id=safe_str(source_ref_id))
470
471 except CommitDoesNotExistError:
472 log.warning(
473 'Failed to get commit from `{}` repo'.format(
474 commits_source_repo), exc_info=True)
475 except RepositoryRequirementError:
476 log.warning(
477 'Failed to get all required data from repo', exc_info=True)
478 c.missing_requirements = True
479
480 c.ancestor = None # set it to None, to hide it from PR view
481
482 try:
483 ancestor_id = source_scm.get_common_ancestor(
484 source_commit.raw_id, target_commit.raw_id, target_scm)
485 c.ancestor_commit = source_scm.get_commit(ancestor_id)
486 except Exception:
487 c.ancestor_commit = None
488
489 c.statuses = source_repo.statuses(
490 [x.raw_id for x in c.commit_ranges])
491
492 # auto collapse if we have more than limit
493 collapse_limit = diffs.DiffProcessor._collapse_commits_over
494 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
495 c.compare_mode = compare
496
497 # diff_limit is the old behavior, will cut off the whole diff
498 # if the limit is applied otherwise will just hide the
499 # big files from the front-end
500 diff_limit = c.visual.cut_off_limit_diff
501 file_limit = c.visual.cut_off_limit_file
502
503 c.missing_commits = False
504 if (c.missing_requirements
505 or isinstance(source_commit, EmptyCommit)
506 or source_commit == target_commit):
507
508 c.missing_commits = True
509 else:
510
511 c.diffset = self._get_diffset(
512 c.source_repo.repo_name, commits_source_repo,
513 source_ref_id, target_ref_id,
514 target_commit, source_commit,
515 diff_limit, c.fulldiff, file_limit, display_inline_comments)
516
517 c.limited_diff = c.diffset.limited_diff
518
519 # calculate removed files that are bound to comments
520 comment_deleted_files = [
521 fname for fname in display_inline_comments
522 if fname not in c.diffset.file_stats]
523
524 c.deleted_files_comments = collections.defaultdict(dict)
525 for fname, per_line_comments in display_inline_comments.items():
526 if fname in comment_deleted_files:
527 c.deleted_files_comments[fname]['stats'] = 0
528 c.deleted_files_comments[fname]['comments'] = list()
529 for lno, comments in per_line_comments.items():
530 c.deleted_files_comments[fname]['comments'].extend(
531 comments)
532
533 # this is a hack to properly display links, when creating PR, the
534 # compare view and others uses different notation, and
535 # compare_commits.mako renders links based on the target_repo.
536 # We need to swap that here to generate it properly on the html side
537 c.target_repo = c.source_repo
538
539 c.commit_statuses = ChangesetStatus.STATUSES
540
541 c.show_version_changes = not pr_closed
542 if c.show_version_changes:
543 cur_obj = pull_request_at_ver
544 prev_obj = prev_pull_request_at_ver
545
546 old_commit_ids = prev_obj.revisions
547 new_commit_ids = cur_obj.revisions
548 commit_changes = PullRequestModel()._calculate_commit_id_changes(
549 old_commit_ids, new_commit_ids)
550 c.commit_changes_summary = commit_changes
551
552 # calculate the diff for commits between versions
553 c.commit_changes = []
554 mark = lambda cs, fw: list(
555 h.itertools.izip_longest([], cs, fillvalue=fw))
556 for c_type, raw_id in mark(commit_changes.added, 'a') \
557 + mark(commit_changes.removed, 'r') \
558 + mark(commit_changes.common, 'c'):
559
560 if raw_id in commit_cache:
561 commit = commit_cache[raw_id]
562 else:
563 try:
564 commit = commits_source_repo.get_commit(raw_id)
565 except CommitDoesNotExistError:
566 # in case we fail extracting still use "dummy" commit
567 # for display in commit diff
568 commit = h.AttributeDict(
569 {'raw_id': raw_id,
570 'message': 'EMPTY or MISSING COMMIT'})
571 c.commit_changes.append([c_type, commit])
572
573 # current user review statuses for each version
574 c.review_versions = {}
575 if self._rhodecode_user.user_id in allowed_reviewers:
576 for co in general_comments:
577 if co.author.user_id == self._rhodecode_user.user_id:
578 # each comment has a status change
579 status = co.status_change
580 if status:
581 _ver_pr = status[0].comment.pull_request_version_id
582 c.review_versions[_ver_pr] = status[0]
583
584 return self._get_template_context(c)
@@ -282,7 +282,8 b' class PullrequestsController(BaseRepoCon'
282 282 h.flash(msg, category='error')
283 283 return redirect(url('pullrequest_home', repo_name=repo_name))
284 284
285 return redirect(url('pullrequest_show', repo_name=target_repo,
285 raise HTTPFound(
286 h.route_path('pullrequest_show', repo_name=target_repo,
286 287 pull_request_id=pull_request.pull_request_id))
287 288
288 289 @LoginRequired()
@@ -420,8 +421,8 b' class PullrequestsController(BaseRepoCon'
420 421 scm=pull_request.target_repo.repo_type)
421 422 self._merge_pull_request(pull_request, user, extras)
422 423
423 return redirect(url(
424 'pullrequest_show',
424 raise HTTPFound(
425 h.route_path('pullrequest_show',
425 426 repo_name=pull_request.target_repo.repo_name,
426 427 pull_request_id=pull_request.pull_request_id))
427 428
@@ -964,7 +965,9 b' class PullrequestsController(BaseRepoCon'
964 965 Session().commit()
965 966
966 967 if not request.is_xhr:
967 return redirect(h.url('pullrequest_show', repo_name=repo_name,
968 raise HTTPFound(
969 h.route_path('pullrequest_show',
970 repo_name=repo_name,
968 971 pull_request_id=pull_request_id))
969 972
970 973 data = {
@@ -175,6 +175,7 b' class ActionParser(object):'
175 175 return group_name
176 176
177 177 def get_pull_request(self):
178 from rhodecode.lib import helpers as h
178 179 pull_request_id = self.action_params
179 180 if self.is_deleted():
180 181 repo_name = self.user_log.repository_name
@@ -182,7 +183,7 b' class ActionParser(object):'
182 183 repo_name = self.user_log.repository.repo_name
183 184 return link_to(
184 185 _('Pull request #%s') % pull_request_id,
185 url('pullrequest_show', repo_name=repo_name,
186 h.route_path('pullrequest_show', repo_name=repo_name,
186 187 pull_request_id=pull_request_id))
187 188
188 189 def get_archive_name(self):
@@ -330,6 +330,11 b' def attach_context_attributes(context, r'
330 330
331 331 context.rhodecode_instanceid = config.get('instance_id')
332 332
333 context.visual.cut_off_limit_diff = safe_int(
334 config.get('cut_off_limit_diff'))
335 context.visual.cut_off_limit_file = safe_int(
336 config.get('cut_off_limit_file'))
337
333 338 # AppEnlight
334 339 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
335 340 context.appenlight_api_public_key = config.get(
@@ -438,8 +438,7 b' class CommentsModel(BaseModel):'
438 438 pull_request_id=pull_request.pull_request_id,
439 439 _anchor='comment-%s' % comment.comment_id)
440 440 else:
441 return request.route_url(
442 'pullrequest_show',
441 return request.route_url('pullrequest_show',
443 442 repo_name=safe_str(pull_request.target_repo.repo_name),
444 443 pull_request_id=pull_request.pull_request_id,
445 444 _anchor='comment-%s' % comment.comment_id)
@@ -997,8 +997,7 b' class PullRequestModel(BaseModel):'
997 997 'pull_requests_global',
998 998 pull_request_id=pull_request.pull_request_id,)
999 999 else:
1000 return request.route_url(
1001 'pullrequest_show',
1000 return request.route_url('pullrequest_show',
1002 1001 repo_name=safe_str(pull_request.target_repo.repo_name),
1003 1002 pull_request_id=pull_request.pull_request_id,)
1004 1003
@@ -1027,11 +1026,9 b' class PullRequestModel(BaseModel):'
1027 1026 pr_source_repo = pull_request_obj.source_repo
1028 1027 pr_target_repo = pull_request_obj.target_repo
1029 1028
1030 pr_url = h.url(
1031 'pullrequest_show',
1029 pr_url = h.route_url('pullrequest_show',
1032 1030 repo_name=pr_target_repo.repo_name,
1033 pull_request_id=pull_request_obj.pull_request_id,
1034 qualified=True,)
1031 pull_request_id=pull_request_obj.pull_request_id,)
1035 1032
1036 1033 # set some variables for email notification
1037 1034 pr_target_repo_url = h.route_url(
@@ -23,7 +23,7 b''
23 23 %if c.statuses.get(commit.raw_id):
24 24 <div class="changeset-status-ico">
25 25 %if c.statuses.get(commit.raw_id)[2]:
26 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]), c.statuses.get(commit.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(commit.raw_id)[3],pull_request_id=c.statuses.get(commit.raw_id)[2])}">
26 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]), c.statuses.get(commit.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(commit.raw_id)[3],pull_request_id=c.statuses.get(commit.raw_id)[2])}">
27 27 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
28 28 </a>
29 29 %else:
@@ -61,7 +61,7 b''
61 61 % else:
62 62 <div class="status-change">
63 63 % if comment.pull_request:
64 <a href="${h.url('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
64 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
65 65 % if comment.status_change:
66 66 ${_('pull request #%s') % comment.pull_request.pull_request_id}:
67 67 % else:
@@ -122,7 +122,7 b''
122 122 </a>
123 123 % else:
124 124 <div title="${_('Comment from pull request version {0}').format(pr_index_ver)}">
125 <a href="${h.url('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}">
125 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}">
126 126 <code class="pr-version-num">
127 127 ${'v{}'.format(pr_index_ver)}
128 128 </code>
@@ -299,7 +299,7 b''
299 299 </%def>
300 300
301 301 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
302 <a href="${h.url('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
302 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
303 303 % if short:
304 304 #${pull_request_id}
305 305 % else:
@@ -18,7 +18,7 b''
18 18 %if c.statuses.get(cs.raw_id):
19 19 <div class="changeset-status-ico shortlog">
20 20 %if c.statuses.get(cs.raw_id)[2]:
21 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
21 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.route_path('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
22 22 <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div>
23 23 </a>
24 24 %else:
General Comments 0
You need to be logged in to leave comments. Login now