@@ -345,6 +345,16 def includeme(config): | |||||
345 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete', |
|
345 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comment/{comment_id}/delete', | |
346 | repo_route=True, repo_accepted_types=['hg', 'git']) |
|
346 | repo_route=True, repo_accepted_types=['hg', 'git']) | |
347 |
|
347 | |||
|
348 | config.add_route( | |||
|
349 | name='pullrequest_comments', | |||
|
350 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/comments', | |||
|
351 | repo_route=True) | |||
|
352 | ||||
|
353 | config.add_route( | |||
|
354 | name='pullrequest_todos', | |||
|
355 | pattern='/{repo_name:.*?[^/]}/pull-request/{pull_request_id:\d+}/todos', | |||
|
356 | repo_route=True) | |||
|
357 | ||||
348 | # Artifacts, (EE feature) |
|
358 | # Artifacts, (EE feature) | |
349 | config.add_route( |
|
359 | config.add_route( | |
350 | name='repo_artifacts_list', |
|
360 | name='repo_artifacts_list', |
@@ -87,6 +87,7 class RepoCommitsView(RepoAppView): | |||||
87 | diff_limit = c.visual.cut_off_limit_diff |
|
87 | diff_limit = c.visual.cut_off_limit_diff | |
88 | file_limit = c.visual.cut_off_limit_file |
|
88 | file_limit = c.visual.cut_off_limit_file | |
89 |
|
89 | |||
|
90 | ||||
90 | # get ranges of commit ids if preset |
|
91 | # get ranges of commit ids if preset | |
91 | commit_range = commit_id_range.split('...')[:2] |
|
92 | commit_range = commit_id_range.split('...')[:2] | |
92 |
|
93 | |||
@@ -226,6 +227,7 class RepoCommitsView(RepoAppView): | |||||
226 |
|
227 | |||
227 | # sort comments by how they were generated |
|
228 | # sort comments by how they were generated | |
228 | c.comments = sorted(c.comments, key=lambda x: x.comment_id) |
|
229 | c.comments = sorted(c.comments, key=lambda x: x.comment_id) | |
|
230 | c.at_version_num = None | |||
229 |
|
231 | |||
230 | if len(c.commit_ranges) == 1: |
|
232 | if len(c.commit_ranges) == 1: | |
231 | c.commit = c.commit_ranges[0] |
|
233 | c.commit = c.commit_ranges[0] |
@@ -265,6 +265,36 class RepoPullRequestsView(RepoAppView, | |||||
265 |
|
265 | |||
266 | return diffset |
|
266 | return diffset | |
267 |
|
267 | |||
|
268 | def register_comments_vars(self, c, pull_request, versions): | |||
|
269 | comments_model = CommentsModel() | |||
|
270 | ||||
|
271 | # GENERAL COMMENTS with versions # | |||
|
272 | q = comments_model._all_general_comments_of_pull_request(pull_request) | |||
|
273 | q = q.order_by(ChangesetComment.comment_id.asc()) | |||
|
274 | general_comments = q | |||
|
275 | ||||
|
276 | # pick comments we want to render at current version | |||
|
277 | c.comment_versions = comments_model.aggregate_comments( | |||
|
278 | general_comments, versions, c.at_version_num) | |||
|
279 | ||||
|
280 | # INLINE COMMENTS with versions # | |||
|
281 | q = comments_model._all_inline_comments_of_pull_request(pull_request) | |||
|
282 | q = q.order_by(ChangesetComment.comment_id.asc()) | |||
|
283 | inline_comments = q | |||
|
284 | ||||
|
285 | c.inline_versions = comments_model.aggregate_comments( | |||
|
286 | inline_comments, versions, c.at_version_num, inline=True) | |||
|
287 | ||||
|
288 | # Comments inline+general | |||
|
289 | if c.at_version: | |||
|
290 | c.inline_comments_flat = c.inline_versions[c.at_version_num]['display'] | |||
|
291 | c.comments = c.comment_versions[c.at_version_num]['display'] | |||
|
292 | else: | |||
|
293 | c.inline_comments_flat = c.inline_versions[c.at_version_num]['until'] | |||
|
294 | c.comments = c.comment_versions[c.at_version_num]['until'] | |||
|
295 | ||||
|
296 | return general_comments, inline_comments | |||
|
297 | ||||
268 | @LoginRequired() |
|
298 | @LoginRequired() | |
269 | @HasRepoPermissionAnyDecorator( |
|
299 | @HasRepoPermissionAnyDecorator( | |
270 | 'repository.read', 'repository.write', 'repository.admin') |
|
300 | 'repository.read', 'repository.write', 'repository.admin') | |
@@ -280,6 +310,8 class RepoPullRequestsView(RepoAppView, | |||||
280 | pull_request_id = pull_request.pull_request_id |
|
310 | pull_request_id = pull_request.pull_request_id | |
281 |
|
311 | |||
282 | c.state_progressing = pull_request.is_state_changing() |
|
312 | c.state_progressing = pull_request.is_state_changing() | |
|
313 | c.pr_broadcast_channel = '/repo${}$/pr/{}'.format( | |||
|
314 | pull_request.target_repo.repo_name, pull_request.pull_request_id) | |||
283 |
|
315 | |||
284 | _new_state = { |
|
316 | _new_state = { | |
285 | 'created': PullRequest.STATE_CREATED, |
|
317 | 'created': PullRequest.STATE_CREATED, | |
@@ -300,22 +332,23 class RepoPullRequestsView(RepoAppView, | |||||
300 | from_version = self.request.GET.get('from_version') or version |
|
332 | from_version = self.request.GET.get('from_version') or version | |
301 | merge_checks = self.request.GET.get('merge_checks') |
|
333 | merge_checks = self.request.GET.get('merge_checks') | |
302 | c.fulldiff = str2bool(self.request.GET.get('fulldiff')) |
|
334 | c.fulldiff = str2bool(self.request.GET.get('fulldiff')) | |
|
335 | force_refresh = str2bool(self.request.GET.get('force_refresh')) | |||
|
336 | c.range_diff_on = self.request.GET.get('range-diff') == "1" | |||
303 |
|
337 | |||
304 | # fetch global flags of ignore ws or context lines |
|
338 | # fetch global flags of ignore ws or context lines | |
305 | diff_context = diffs.get_diff_context(self.request) |
|
339 | diff_context = diffs.get_diff_context(self.request) | |
306 | hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request) |
|
340 | hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request) | |
307 |
|
341 | |||
308 | force_refresh = str2bool(self.request.GET.get('force_refresh')) |
|
|||
309 |
|
||||
310 | (pull_request_latest, |
|
342 | (pull_request_latest, | |
311 | pull_request_at_ver, |
|
343 | pull_request_at_ver, | |
312 | pull_request_display_obj, |
|
344 | pull_request_display_obj, | |
313 | at_version) = PullRequestModel().get_pr_version( |
|
345 | at_version) = PullRequestModel().get_pr_version( | |
314 | pull_request_id, version=version) |
|
346 | pull_request_id, version=version) | |
|
347 | ||||
315 | pr_closed = pull_request_latest.is_closed() |
|
348 | pr_closed = pull_request_latest.is_closed() | |
316 |
|
349 | |||
317 | if pr_closed and (version or from_version): |
|
350 | if pr_closed and (version or from_version): | |
318 | # not allow to browse versions |
|
351 | # not allow to browse versions for closed PR | |
319 | raise HTTPFound(h.route_path( |
|
352 | raise HTTPFound(h.route_path( | |
320 | 'pullrequest_show', repo_name=self.db_repo_name, |
|
353 | 'pullrequest_show', repo_name=self.db_repo_name, | |
321 | pull_request_id=pull_request_id)) |
|
354 | pull_request_id=pull_request_id)) | |
@@ -323,13 +356,13 class RepoPullRequestsView(RepoAppView, | |||||
323 | versions = pull_request_display_obj.versions() |
|
356 | versions = pull_request_display_obj.versions() | |
324 | # used to store per-commit range diffs |
|
357 | # used to store per-commit range diffs | |
325 | c.changes = collections.OrderedDict() |
|
358 | c.changes = collections.OrderedDict() | |
326 | c.range_diff_on = self.request.GET.get('range-diff') == "1" |
|
|||
327 |
|
359 | |||
328 | c.at_version = at_version |
|
360 | c.at_version = at_version | |
329 | c.at_version_num = (at_version |
|
361 | c.at_version_num = (at_version | |
330 |
if at_version and at_version != |
|
362 | if at_version and at_version != PullRequest.LATEST_VER | |
331 | else None) |
|
363 | else None) | |
332 | c.at_version_pos = ChangesetComment.get_index_from_version( |
|
364 | ||
|
365 | c.at_version_index = ChangesetComment.get_index_from_version( | |||
333 | c.at_version_num, versions) |
|
366 | c.at_version_num, versions) | |
334 |
|
367 | |||
335 | (prev_pull_request_latest, |
|
368 | (prev_pull_request_latest, | |
@@ -340,9 +373,9 class RepoPullRequestsView(RepoAppView, | |||||
340 |
|
373 | |||
341 | c.from_version = prev_at_version |
|
374 | c.from_version = prev_at_version | |
342 | c.from_version_num = (prev_at_version |
|
375 | c.from_version_num = (prev_at_version | |
343 |
if prev_at_version and prev_at_version != |
|
376 | if prev_at_version and prev_at_version != PullRequest.LATEST_VER | |
344 | else None) |
|
377 | else None) | |
345 |
c.from_version_ |
|
378 | c.from_version_index = ChangesetComment.get_index_from_version( | |
346 | c.from_version_num, versions) |
|
379 | c.from_version_num, versions) | |
347 |
|
380 | |||
348 | # define if we're in COMPARE mode or VIEW at version mode |
|
381 | # define if we're in COMPARE mode or VIEW at version mode | |
@@ -355,14 +388,17 class RepoPullRequestsView(RepoAppView, | |||||
355 | self.db_repo_name, pull_request_at_ver.target_repo.repo_name) |
|
388 | self.db_repo_name, pull_request_at_ver.target_repo.repo_name) | |
356 | raise HTTPNotFound() |
|
389 | raise HTTPNotFound() | |
357 |
|
390 | |||
358 | c.shadow_clone_url = PullRequestModel().get_shadow_clone_url( |
|
391 | c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver) | |
359 | pull_request_at_ver) |
|
|||
360 |
|
392 | |||
361 | c.pull_request = pull_request_display_obj |
|
393 | c.pull_request = pull_request_display_obj | |
362 | c.renderer = pull_request_at_ver.description_renderer or c.renderer |
|
394 | c.renderer = pull_request_at_ver.description_renderer or c.renderer | |
363 | c.pull_request_latest = pull_request_latest |
|
395 | c.pull_request_latest = pull_request_latest | |
364 |
|
396 | |||
365 | if compare or (at_version and not at_version == 'latest'): |
|
397 | # inject latest version | |
|
398 | latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest) | |||
|
399 | c.versions = versions + [latest_ver] | |||
|
400 | ||||
|
401 | if compare or (at_version and not at_version == PullRequest.LATEST_VER): | |||
366 | c.allowed_to_change_status = False |
|
402 | c.allowed_to_change_status = False | |
367 | c.allowed_to_update = False |
|
403 | c.allowed_to_update = False | |
368 | c.allowed_to_merge = False |
|
404 | c.allowed_to_merge = False | |
@@ -391,12 +427,9 class RepoPullRequestsView(RepoAppView, | |||||
391 | 'rules' in pull_request_latest.reviewer_data: |
|
427 | 'rules' in pull_request_latest.reviewer_data: | |
392 | rules = pull_request_latest.reviewer_data['rules'] or {} |
|
428 | rules = pull_request_latest.reviewer_data['rules'] or {} | |
393 | try: |
|
429 | try: | |
394 | c.forbid_adding_reviewers = rules.get( |
|
430 | c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers') | |
395 | 'forbid_adding_reviewers') |
|
431 | c.forbid_author_to_review = rules.get('forbid_author_to_review') | |
396 | c.forbid_author_to_review = rules.get( |
|
432 | c.forbid_commit_author_to_review = rules.get('forbid_commit_author_to_review') | |
397 | 'forbid_author_to_review') |
|
|||
398 | c.forbid_commit_author_to_review = rules.get( |
|
|||
399 | 'forbid_commit_author_to_review') |
|
|||
400 | except Exception: |
|
433 | except Exception: | |
401 | pass |
|
434 | pass | |
402 |
|
435 | |||
@@ -421,41 +454,37 class RepoPullRequestsView(RepoAppView, | |||||
421 | 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako' |
|
454 | 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako' | |
422 | return self._get_template_context(c) |
|
455 | return self._get_template_context(c) | |
423 |
|
456 | |||
424 | comments_model = CommentsModel() |
|
457 | c.allowed_reviewers = [obj.user_id for obj in pull_request.reviewers if obj.user] | |
425 |
|
458 | |||
426 | # reviewers and statuses |
|
459 | # reviewers and statuses | |
427 |
c.pull_request_reviewers = |
|
460 | c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data) | |
428 | allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers] |
|
461 | c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []}) | |
429 |
|
462 | |||
430 | # GENERAL COMMENTS with versions # |
|
463 | for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses(): | |
431 | q = comments_model._all_general_comments_of_pull_request(pull_request_latest) |
|
464 | member_reviewer = h.reviewer_as_json( | |
432 | q = q.order_by(ChangesetComment.comment_id.asc()) |
|
465 | member, reasons=reasons, mandatory=mandatory, | |
433 | general_comments = q |
|
466 | user_group=review_obj.rule_user_group_data() | |
|
467 | ) | |||
434 |
|
468 | |||
435 | # pick comments we want to render at current version |
|
469 | current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED | |
436 | c.comment_versions = comments_model.aggregate_comments( |
|
470 | member_reviewer['review_status'] = current_review_status | |
437 | general_comments, versions, c.at_version_num) |
|
471 | member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status) | |
438 | c.comments = c.comment_versions[c.at_version_num]['until'] |
|
472 | member_reviewer['allowed_to_update'] = c.allowed_to_update | |
|
473 | c.pull_request_set_reviewers_data_json['reviewers'].append(member_reviewer) | |||
439 |
|
474 | |||
440 | # INLINE COMMENTS with versions # |
|
475 | c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json) | |
441 | q = comments_model._all_inline_comments_of_pull_request(pull_request_latest) |
|
476 | ||
442 | q = q.order_by(ChangesetComment.comment_id.asc()) |
|
477 | ||
443 | inline_comments = q |
|
|||
444 |
|
478 | |||
445 | c.inline_versions = comments_model.aggregate_comments( |
|
479 | ||
446 | inline_comments, versions, c.at_version_num, inline=True) |
|
480 | general_comments, inline_comments = \ | |
|
481 | self.register_comments_vars(c, pull_request_latest, versions) | |||
447 |
|
482 | |||
448 | # TODOs |
|
483 | # TODOs | |
449 | c.unresolved_comments = CommentsModel() \ |
|
484 | c.unresolved_comments = CommentsModel() \ | |
450 | .get_pull_request_unresolved_todos(pull_request) |
|
485 | .get_pull_request_unresolved_todos(pull_request_latest) | |
451 | c.resolved_comments = CommentsModel() \ |
|
486 | c.resolved_comments = CommentsModel() \ | |
452 | .get_pull_request_resolved_todos(pull_request) |
|
487 | .get_pull_request_resolved_todos(pull_request_latest) | |
453 |
|
||||
454 | # inject latest version |
|
|||
455 | latest_ver = PullRequest.get_pr_display_object( |
|
|||
456 | pull_request_latest, pull_request_latest) |
|
|||
457 |
|
||||
458 | c.versions = versions + [latest_ver] |
|
|||
459 |
|
488 | |||
460 | # if we use version, then do not show later comments |
|
489 | # if we use version, then do not show later comments | |
461 | # than current version |
|
490 | # than current version | |
@@ -522,8 +551,8 class RepoPullRequestsView(RepoAppView, | |||||
522 |
|
551 | |||
523 | # empty version means latest, so we keep this to prevent |
|
552 | # empty version means latest, so we keep this to prevent | |
524 | # double caching |
|
553 | # double caching | |
525 |
version_normalized = version or |
|
554 | version_normalized = version or PullRequest.LATEST_VER | |
526 |
from_version_normalized = from_version or |
|
555 | from_version_normalized = from_version or PullRequest.LATEST_VER | |
527 |
|
556 | |||
528 | cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo) |
|
557 | cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo) | |
529 | cache_file_path = diff_cache_exist( |
|
558 | cache_file_path = diff_cache_exist( | |
@@ -615,7 +644,7 class RepoPullRequestsView(RepoAppView, | |||||
615 | diff_limit, file_limit, c.fulldiff, |
|
644 | diff_limit, file_limit, c.fulldiff, | |
616 | hide_whitespace_changes, diff_context, |
|
645 | hide_whitespace_changes, diff_context, | |
617 | use_ancestor=use_ancestor |
|
646 | use_ancestor=use_ancestor | |
618 | ) |
|
647 | ) | |
619 |
|
648 | |||
620 | # save cached diff |
|
649 | # save cached diff | |
621 | if caching_enabled: |
|
650 | if caching_enabled: | |
@@ -719,7 +748,7 class RepoPullRequestsView(RepoAppView, | |||||
719 |
|
748 | |||
720 | # current user review statuses for each version |
|
749 | # current user review statuses for each version | |
721 | c.review_versions = {} |
|
750 | c.review_versions = {} | |
722 | if self._rhodecode_user.user_id in allowed_reviewers: |
|
751 | if self._rhodecode_user.user_id in c.allowed_reviewers: | |
723 | for co in general_comments: |
|
752 | for co in general_comments: | |
724 | if co.author.user_id == self._rhodecode_user.user_id: |
|
753 | if co.author.user_id == self._rhodecode_user.user_id: | |
725 | status = co.status_change |
|
754 | status = co.status_change | |
@@ -939,6 +968,83 class RepoPullRequestsView(RepoAppView, | |||||
939 | @NotAnonymous() |
|
968 | @NotAnonymous() | |
940 | @HasRepoPermissionAnyDecorator( |
|
969 | @HasRepoPermissionAnyDecorator( | |
941 | 'repository.read', 'repository.write', 'repository.admin') |
|
970 | 'repository.read', 'repository.write', 'repository.admin') | |
|
971 | @view_config( | |||
|
972 | route_name='pullrequest_comments', request_method='POST', | |||
|
973 | renderer='string', xhr=True) | |||
|
974 | def pullrequest_comments(self): | |||
|
975 | self.load_default_context() | |||
|
976 | ||||
|
977 | pull_request = PullRequest.get_or_404( | |||
|
978 | self.request.matchdict['pull_request_id']) | |||
|
979 | pull_request_id = pull_request.pull_request_id | |||
|
980 | version = self.request.GET.get('version') | |||
|
981 | ||||
|
982 | _render = self.request.get_partial_renderer( | |||
|
983 | 'rhodecode:templates/pullrequests/pullrequest_show.mako') | |||
|
984 | c = _render.get_call_context() | |||
|
985 | ||||
|
986 | (pull_request_latest, | |||
|
987 | pull_request_at_ver, | |||
|
988 | pull_request_display_obj, | |||
|
989 | at_version) = PullRequestModel().get_pr_version( | |||
|
990 | pull_request_id, version=version) | |||
|
991 | versions = pull_request_display_obj.versions() | |||
|
992 | latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest) | |||
|
993 | c.versions = versions + [latest_ver] | |||
|
994 | ||||
|
995 | c.at_version = at_version | |||
|
996 | c.at_version_num = (at_version | |||
|
997 | if at_version and at_version != PullRequest.LATEST_VER | |||
|
998 | else None) | |||
|
999 | ||||
|
1000 | self.register_comments_vars(c, pull_request_latest, versions) | |||
|
1001 | all_comments = c.inline_comments_flat + c.comments | |||
|
1002 | return _render('comments_table', all_comments, len(all_comments)) | |||
|
1003 | ||||
|
1004 | @LoginRequired() | |||
|
1005 | @NotAnonymous() | |||
|
1006 | @HasRepoPermissionAnyDecorator( | |||
|
1007 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
1008 | @view_config( | |||
|
1009 | route_name='pullrequest_todos', request_method='POST', | |||
|
1010 | renderer='string', xhr=True) | |||
|
1011 | def pullrequest_todos(self): | |||
|
1012 | self.load_default_context() | |||
|
1013 | ||||
|
1014 | pull_request = PullRequest.get_or_404( | |||
|
1015 | self.request.matchdict['pull_request_id']) | |||
|
1016 | pull_request_id = pull_request.pull_request_id | |||
|
1017 | version = self.request.GET.get('version') | |||
|
1018 | ||||
|
1019 | _render = self.request.get_partial_renderer( | |||
|
1020 | 'rhodecode:templates/pullrequests/pullrequest_show.mako') | |||
|
1021 | c = _render.get_call_context() | |||
|
1022 | (pull_request_latest, | |||
|
1023 | pull_request_at_ver, | |||
|
1024 | pull_request_display_obj, | |||
|
1025 | at_version) = PullRequestModel().get_pr_version( | |||
|
1026 | pull_request_id, version=version) | |||
|
1027 | versions = pull_request_display_obj.versions() | |||
|
1028 | latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest) | |||
|
1029 | c.versions = versions + [latest_ver] | |||
|
1030 | ||||
|
1031 | c.at_version = at_version | |||
|
1032 | c.at_version_num = (at_version | |||
|
1033 | if at_version and at_version != PullRequest.LATEST_VER | |||
|
1034 | else None) | |||
|
1035 | ||||
|
1036 | c.unresolved_comments = CommentsModel() \ | |||
|
1037 | .get_pull_request_unresolved_todos(pull_request) | |||
|
1038 | c.resolved_comments = CommentsModel() \ | |||
|
1039 | .get_pull_request_resolved_todos(pull_request) | |||
|
1040 | ||||
|
1041 | all_comments = c.unresolved_comments + c.resolved_comments | |||
|
1042 | return _render('comments_table', all_comments, len(c.unresolved_comments), todo_comments=True) | |||
|
1043 | ||||
|
1044 | @LoginRequired() | |||
|
1045 | @NotAnonymous() | |||
|
1046 | @HasRepoPermissionAnyDecorator( | |||
|
1047 | 'repository.read', 'repository.write', 'repository.admin') | |||
942 | @CSRFRequired() |
|
1048 | @CSRFRequired() | |
943 | @view_config( |
|
1049 | @view_config( | |
944 | route_name='pullrequest_create', request_method='POST', |
|
1050 | route_name='pullrequest_create', request_method='POST', | |
@@ -1100,7 +1206,7 class RepoPullRequestsView(RepoAppView, | |||||
1100 | self.request.matchdict['pull_request_id']) |
|
1206 | self.request.matchdict['pull_request_id']) | |
1101 | _ = self.request.translate |
|
1207 | _ = self.request.translate | |
1102 |
|
1208 | |||
1103 | self.load_default_context() |
|
1209 | c = self.load_default_context() | |
1104 | redirect_url = None |
|
1210 | redirect_url = None | |
1105 |
|
1211 | |||
1106 | if pull_request.is_closed(): |
|
1212 | if pull_request.is_closed(): | |
@@ -1111,6 +1217,8 class RepoPullRequestsView(RepoAppView, | |||||
1111 | 'redirect_url': redirect_url} |
|
1217 | 'redirect_url': redirect_url} | |
1112 |
|
1218 | |||
1113 | is_state_changing = pull_request.is_state_changing() |
|
1219 | is_state_changing = pull_request.is_state_changing() | |
|
1220 | c.pr_broadcast_channel = '/repo${}$/pr/{}'.format( | |||
|
1221 | pull_request.target_repo.repo_name, pull_request.pull_request_id) | |||
1114 |
|
1222 | |||
1115 | # only owner or admin can update it |
|
1223 | # only owner or admin can update it | |
1116 | allowed_to_update = PullRequestModel().check_user_update( |
|
1224 | allowed_to_update = PullRequestModel().check_user_update( | |
@@ -1134,7 +1242,7 class RepoPullRequestsView(RepoAppView, | |||||
1134 | return {'response': True, |
|
1242 | return {'response': True, | |
1135 | 'redirect_url': redirect_url} |
|
1243 | 'redirect_url': redirect_url} | |
1136 |
|
1244 | |||
1137 | self._update_commits(pull_request) |
|
1245 | self._update_commits(c, pull_request) | |
1138 | if force_refresh: |
|
1246 | if force_refresh: | |
1139 | redirect_url = h.route_path( |
|
1247 | redirect_url = h.route_path( | |
1140 | 'pullrequest_show', repo_name=self.db_repo_name, |
|
1248 | 'pullrequest_show', repo_name=self.db_repo_name, | |
@@ -1170,7 +1278,7 class RepoPullRequestsView(RepoAppView, | |||||
1170 | h.flash(msg, category='success') |
|
1278 | h.flash(msg, category='success') | |
1171 | return |
|
1279 | return | |
1172 |
|
1280 | |||
1173 | def _update_commits(self, pull_request): |
|
1281 | def _update_commits(self, c, pull_request): | |
1174 | _ = self.request.translate |
|
1282 | _ = self.request.translate | |
1175 |
|
1283 | |||
1176 | with pull_request.set_state(PullRequest.STATE_UPDATING): |
|
1284 | with pull_request.set_state(PullRequest.STATE_UPDATING): | |
@@ -1198,13 +1306,18 class RepoPullRequestsView(RepoAppView, | |||||
1198 | change_source=changed) |
|
1306 | change_source=changed) | |
1199 | h.flash(msg, category='success') |
|
1307 | h.flash(msg, category='success') | |
1200 |
|
1308 | |||
1201 | channel = '/repo${}$/pr/{}'.format( |
|
|||
1202 | pull_request.target_repo.repo_name, pull_request.pull_request_id) |
|
|||
1203 | message = msg + ( |
|
1309 | message = msg + ( | |
1204 | ' - <a onclick="window.location.reload()">' |
|
1310 | ' - <a onclick="window.location.reload()">' | |
1205 | '<strong>{}</strong></a>'.format(_('Reload page'))) |
|
1311 | '<strong>{}</strong></a>'.format(_('Reload page'))) | |
|
1312 | ||||
|
1313 | message_obj = { | |||
|
1314 | 'message': message, | |||
|
1315 | 'level': 'success', | |||
|
1316 | 'topic': '/notifications' | |||
|
1317 | } | |||
|
1318 | ||||
1206 | channelstream.post_message( |
|
1319 | channelstream.post_message( | |
1207 | channel, message, self._rhodecode_user.username, |
|
1320 | c.pr_broadcast_channel, message_obj, self._rhodecode_user.username, | |
1208 | registry=self.request.registry) |
|
1321 | registry=self.request.registry) | |
1209 | else: |
|
1322 | else: | |
1210 | msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason] |
|
1323 | msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason] | |
@@ -1474,6 +1587,7 class RepoPullRequestsView(RepoAppView, | |||||
1474 | } |
|
1587 | } | |
1475 | if comment: |
|
1588 | if comment: | |
1476 | c.co = comment |
|
1589 | c.co = comment | |
|
1590 | c.at_version_num = None | |||
1477 | rendered_comment = render( |
|
1591 | rendered_comment = render( | |
1478 | 'rhodecode:templates/changeset/changeset_comment_block.mako', |
|
1592 | 'rhodecode:templates/changeset/changeset_comment_block.mako', | |
1479 | self._get_template_context(c), self.request) |
|
1593 | self._get_template_context(c), self.request) |
@@ -810,8 +810,7 import tzlocal | |||||
810 | local_timezone = tzlocal.get_localzone() |
|
810 | local_timezone = tzlocal.get_localzone() | |
811 |
|
811 | |||
812 |
|
812 | |||
813 |
def |
|
813 | def get_timezone(datetime_iso, time_is_local=False): | |
814 | title = value or format_date(datetime_iso) |
|
|||
815 | tzinfo = '+00:00' |
|
814 | tzinfo = '+00:00' | |
816 |
|
815 | |||
817 | # detect if we have a timezone info, otherwise, add it |
|
816 | # detect if we have a timezone info, otherwise, add it | |
@@ -822,6 +821,12 def age_component(datetime_iso, value=No | |||||
822 | timezone = force_timezone or local_timezone |
|
821 | timezone = force_timezone or local_timezone | |
823 | offset = timezone.localize(datetime_iso).strftime('%z') |
|
822 | offset = timezone.localize(datetime_iso).strftime('%z') | |
824 | tzinfo = '{}:{}'.format(offset[:-2], offset[-2:]) |
|
823 | tzinfo = '{}:{}'.format(offset[:-2], offset[-2:]) | |
|
824 | return tzinfo | |||
|
825 | ||||
|
826 | ||||
|
827 | def age_component(datetime_iso, value=None, time_is_local=False, tooltip=True): | |||
|
828 | title = value or format_date(datetime_iso) | |||
|
829 | tzinfo = get_timezone(datetime_iso, time_is_local=time_is_local) | |||
825 |
|
830 | |||
826 | return literal( |
|
831 | return literal( | |
827 | '<time class="timeago {cls}" title="{tt_title}" datetime="{dt}{tzinfo}">{title}</time>'.format( |
|
832 | '<time class="timeago {cls}" title="{tt_title}" datetime="{dt}{tzinfo}">{title}</time>'.format( | |
@@ -1650,17 +1655,18 def get_active_pattern_entries(repo_name | |||||
1650 |
|
1655 | |||
1651 | pr_pattern_re = re.compile(r'(?:(?:^!)|(?: !))(\d+)') |
|
1656 | pr_pattern_re = re.compile(r'(?:(?:^!)|(?: !))(\d+)') | |
1652 |
|
1657 | |||
|
1658 | allowed_link_formats = [ | |||
|
1659 | 'html', 'rst', 'markdown', 'html+hovercard', 'rst+hovercard', 'markdown+hovercard'] | |||
|
1660 | ||||
1653 |
|
1661 | |||
1654 | def process_patterns(text_string, repo_name, link_format='html', active_entries=None): |
|
1662 | def process_patterns(text_string, repo_name, link_format='html', active_entries=None): | |
1655 |
|
1663 | |||
1656 | allowed_formats = ['html', 'rst', 'markdown', |
|
1664 | if link_format not in allowed_link_formats: | |
1657 | 'html+hovercard', 'rst+hovercard', 'markdown+hovercard'] |
|
|||
1658 | if link_format not in allowed_formats: |
|
|||
1659 | raise ValueError('Link format can be only one of:{} got {}'.format( |
|
1665 | raise ValueError('Link format can be only one of:{} got {}'.format( | |
1660 | allowed_formats, link_format)) |
|
1666 | allowed_link_formats, link_format)) | |
1661 |
|
1667 | |||
1662 | if active_entries is None: |
|
1668 | if active_entries is None: | |
1663 | log.debug('Fetch active patterns for repo: %s', repo_name) |
|
1669 | log.debug('Fetch active issue tracker patterns for repo: %s', repo_name) | |
1664 | active_entries = get_active_pattern_entries(repo_name) |
|
1670 | active_entries = get_active_pattern_entries(repo_name) | |
1665 |
|
1671 | |||
1666 | issues_data = [] |
|
1672 | issues_data = [] | |
@@ -1718,7 +1724,8 def process_patterns(text_string, repo_n | |||||
1718 | return new_text, issues_data |
|
1724 | return new_text, issues_data | |
1719 |
|
1725 | |||
1720 |
|
1726 | |||
1721 |
def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None |
|
1727 | def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None, | |
|
1728 | issues_container=None): | |||
1722 | """ |
|
1729 | """ | |
1723 | Parses given text message and makes proper links. |
|
1730 | Parses given text message and makes proper links. | |
1724 | issues are linked to given issue-server, and rest is a commit link |
|
1731 | issues are linked to given issue-server, and rest is a commit link | |
@@ -1741,6 +1748,9 def urlify_commit_message(commit_text, r | |||||
1741 | new_text, issues = process_patterns(new_text, repository or '', |
|
1748 | new_text, issues = process_patterns(new_text, repository or '', | |
1742 | active_entries=active_pattern_entries) |
|
1749 | active_entries=active_pattern_entries) | |
1743 |
|
1750 | |||
|
1751 | if issues_container is not None: | |||
|
1752 | issues_container.extend(issues) | |||
|
1753 | ||||
1744 | return literal(new_text) |
|
1754 | return literal(new_text) | |
1745 |
|
1755 | |||
1746 |
|
1756 | |||
@@ -1781,7 +1791,7 def renderer_from_filename(filename, exc | |||||
1781 |
|
1791 | |||
1782 |
|
1792 | |||
1783 | def render(source, renderer='rst', mentions=False, relative_urls=None, |
|
1793 | def render(source, renderer='rst', mentions=False, relative_urls=None, | |
1784 | repo_name=None, active_pattern_entries=None): |
|
1794 | repo_name=None, active_pattern_entries=None, issues_container=None): | |
1785 |
|
1795 | |||
1786 | def maybe_convert_relative_links(html_source): |
|
1796 | def maybe_convert_relative_links(html_source): | |
1787 | if relative_urls: |
|
1797 | if relative_urls: | |
@@ -1798,6 +1808,8 def render(source, renderer='rst', menti | |||||
1798 | source, issues = process_patterns( |
|
1808 | source, issues = process_patterns( | |
1799 | source, repo_name, link_format='rst', |
|
1809 | source, repo_name, link_format='rst', | |
1800 | active_entries=active_pattern_entries) |
|
1810 | active_entries=active_pattern_entries) | |
|
1811 | if issues_container is not None: | |||
|
1812 | issues_container.extend(issues) | |||
1801 |
|
1813 | |||
1802 | return literal( |
|
1814 | return literal( | |
1803 | '<div class="rst-block">%s</div>' % |
|
1815 | '<div class="rst-block">%s</div>' % | |
@@ -1810,6 +1822,8 def render(source, renderer='rst', menti | |||||
1810 | source, issues = process_patterns( |
|
1822 | source, issues = process_patterns( | |
1811 | source, repo_name, link_format='markdown', |
|
1823 | source, repo_name, link_format='markdown', | |
1812 | active_entries=active_pattern_entries) |
|
1824 | active_entries=active_pattern_entries) | |
|
1825 | if issues_container is not None: | |||
|
1826 | issues_container.extend(issues) | |||
1813 |
|
1827 | |||
1814 | return literal( |
|
1828 | return literal( | |
1815 | '<div class="markdown-block">%s</div>' % |
|
1829 | '<div class="markdown-block">%s</div>' % |
@@ -91,8 +91,7 class CommentsModel(BaseModel): | |||||
91 | # group by versions, and count until, and display objects |
|
91 | # group by versions, and count until, and display objects | |
92 |
|
92 | |||
93 | comment_groups = collections.defaultdict(list) |
|
93 | comment_groups = collections.defaultdict(list) | |
94 | [comment_groups[ |
|
94 | [comment_groups[_co.pull_request_version_id].append(_co) for _co in comments] | |
95 | _co.pull_request_version_id].append(_co) for _co in comments] |
|
|||
96 |
|
95 | |||
97 | def yield_comments(pos): |
|
96 | def yield_comments(pos): | |
98 | for co in comment_groups[pos]: |
|
97 | for co in comment_groups[pos]: | |
@@ -456,38 +455,54 class CommentsModel(BaseModel): | |||||
456 | else: |
|
455 | else: | |
457 | action = 'repo.commit.comment.create' |
|
456 | action = 'repo.commit.comment.create' | |
458 |
|
457 | |||
|
458 | comment_id = comment.comment_id | |||
459 | comment_data = comment.get_api_data() |
|
459 | comment_data = comment.get_api_data() | |
|
460 | ||||
460 | self._log_audit_action( |
|
461 | self._log_audit_action( | |
461 | action, {'data': comment_data}, auth_user, comment) |
|
462 | action, {'data': comment_data}, auth_user, comment) | |
462 |
|
463 | |||
463 | msg_url = '' |
|
|||
464 | channel = None |
|
464 | channel = None | |
465 | if commit_obj: |
|
465 | if commit_obj: | |
466 | msg_url = commit_comment_url |
|
|||
467 | repo_name = repo.repo_name |
|
466 | repo_name = repo.repo_name | |
468 | channel = u'/repo${}$/commit/{}'.format( |
|
467 | channel = u'/repo${}$/commit/{}'.format( | |
469 | repo_name, |
|
468 | repo_name, | |
470 | commit_obj.raw_id |
|
469 | commit_obj.raw_id | |
471 | ) |
|
470 | ) | |
472 | elif pull_request_obj: |
|
471 | elif pull_request_obj: | |
473 | msg_url = pr_comment_url |
|
|||
474 | repo_name = pr_target_repo.repo_name |
|
472 | repo_name = pr_target_repo.repo_name | |
475 | channel = u'/repo${}$/pr/{}'.format( |
|
473 | channel = u'/repo${}$/pr/{}'.format( | |
476 | repo_name, |
|
474 | repo_name, | |
477 | pull_request_id |
|
475 | pull_request_obj.pull_request_id | |
478 | ) |
|
476 | ) | |
479 |
|
477 | |||
480 | message = '<strong>{}</strong> {} - ' \ |
|
478 | if channel: | |
481 | '<a onclick="window.location=\'{}\';' \ |
|
479 | username = user.username | |
482 | 'window.location.reload()">' \ |
|
480 | message = '<strong>{}</strong> {} #{}, {}' | |
483 | '<strong>{}</strong></a>' |
|
481 | message = message.format( | |
484 | message = message.format( |
|
482 | username, | |
485 |
|
|
483 | _('posted a new comment'), | |
486 | _('Show it now')) |
|
484 | comment_id, | |
|
485 | _('Refresh the page to see new comments.')) | |||
487 |
|
486 | |||
488 | channelstream.post_message( |
|
487 | message_obj = { | |
489 | channel, message, user.username, |
|
488 | 'message': message, | |
490 | registry=get_current_registry()) |
|
489 | 'level': 'success', | |
|
490 | 'topic': '/notifications' | |||
|
491 | } | |||
|
492 | ||||
|
493 | channelstream.post_message( | |||
|
494 | channel, message_obj, user.username, | |||
|
495 | registry=get_current_registry()) | |||
|
496 | ||||
|
497 | message_obj = { | |||
|
498 | 'message': None, | |||
|
499 | 'user': username, | |||
|
500 | 'comment_id': comment_id, | |||
|
501 | 'topic': '/comment' | |||
|
502 | } | |||
|
503 | channelstream.post_message( | |||
|
504 | channel, message_obj, user.username, | |||
|
505 | registry=get_current_registry()) | |||
491 |
|
506 | |||
492 | return comment |
|
507 | return comment | |
493 |
|
508 | |||
@@ -642,15 +657,15 class CommentsModel(BaseModel): | |||||
642 | return self._group_comments_by_path_and_line_number(q) |
|
657 | return self._group_comments_by_path_and_line_number(q) | |
643 |
|
658 | |||
644 | def get_inline_comments_as_list(self, inline_comments, skip_outdated=True, |
|
659 | def get_inline_comments_as_list(self, inline_comments, skip_outdated=True, | |
645 | version=None): |
|
660 | version=None): | |
646 |
inline_c |
|
661 | inline_comms = [] | |
647 | for fname, per_line_comments in inline_comments.iteritems(): |
|
662 | for fname, per_line_comments in inline_comments.iteritems(): | |
648 | for lno, comments in per_line_comments.iteritems(): |
|
663 | for lno, comments in per_line_comments.iteritems(): | |
649 | for comm in comments: |
|
664 | for comm in comments: | |
650 | if not comm.outdated_at_version(version) and skip_outdated: |
|
665 | if not comm.outdated_at_version(version) and skip_outdated: | |
651 |
inline_c |
|
666 | inline_comms.append(comm) | |
652 |
|
667 | |||
653 |
return inline_c |
|
668 | return inline_comms | |
654 |
|
669 | |||
655 | def get_outdated_comments(self, repo_id, pull_request): |
|
670 | def get_outdated_comments(self, repo_id, pull_request): | |
656 | # TODO: johbo: Remove `repo_id`, it is not needed to find the comments |
|
671 | # TODO: johbo: Remove `repo_id`, it is not needed to find the comments |
@@ -3821,16 +3821,35 class ChangesetComment(Base, BaseModel): | |||||
3821 | """ |
|
3821 | """ | |
3822 | Checks if comment is outdated for given pull request version |
|
3822 | Checks if comment is outdated for given pull request version | |
3823 | """ |
|
3823 | """ | |
3824 | return self.outdated and self.pull_request_version_id != version |
|
3824 | def version_check(): | |
|
3825 | return self.pull_request_version_id and self.pull_request_version_id != version | |||
|
3826 | ||||
|
3827 | if self.is_inline: | |||
|
3828 | return self.outdated and version_check() | |||
|
3829 | else: | |||
|
3830 | # general comments don't have .outdated set, also latest don't have a version | |||
|
3831 | return version_check() | |||
|
3832 | ||||
|
3833 | def outdated_at_version_js(self, version): | |||
|
3834 | """ | |||
|
3835 | Checks if comment is outdated for given pull request version | |||
|
3836 | """ | |||
|
3837 | return json.dumps(self.outdated_at_version(version)) | |||
3825 |
|
3838 | |||
3826 | def older_than_version(self, version): |
|
3839 | def older_than_version(self, version): | |
3827 | """ |
|
3840 | """ | |
3828 | Checks if comment is made from previous version than given |
|
3841 | Checks if comment is made from previous version than given | |
3829 | """ |
|
3842 | """ | |
3830 | if version is None: |
|
3843 | if version is None: | |
3831 |
return self.pull_request_version |
|
3844 | return self.pull_request_version != version | |
3832 |
|
3845 | |||
3833 |
return self.pull_request_version |
|
3846 | return self.pull_request_version < version | |
|
3847 | ||||
|
3848 | def older_than_version_js(self, version): | |||
|
3849 | """ | |||
|
3850 | Checks if comment is made from previous version than given | |||
|
3851 | """ | |||
|
3852 | return json.dumps(self.older_than_version(version)) | |||
3834 |
|
3853 | |||
3835 | @property |
|
3854 | @property | |
3836 | def commit_id(self): |
|
3855 | def commit_id(self): | |
@@ -4327,6 +4346,7 class PullRequest(Base, _PullRequestBase | |||||
4327 | __table_args__ = ( |
|
4346 | __table_args__ = ( | |
4328 | base_table_args, |
|
4347 | base_table_args, | |
4329 | ) |
|
4348 | ) | |
|
4349 | LATEST_VER = 'latest' | |||
4330 |
|
4350 | |||
4331 | pull_request_id = Column( |
|
4351 | pull_request_id = Column( | |
4332 | 'pull_request_id', Integer(), nullable=False, primary_key=True) |
|
4352 | 'pull_request_id', Integer(), nullable=False, primary_key=True) | |
@@ -4385,6 +4405,10 class PullRequest(Base, _PullRequestBase | |||||
4385 | def pull_request_version_id(self): |
|
4405 | def pull_request_version_id(self): | |
4386 | return getattr(pull_request_obj, 'pull_request_version_id', None) |
|
4406 | return getattr(pull_request_obj, 'pull_request_version_id', None) | |
4387 |
|
4407 | |||
|
4408 | @property | |||
|
4409 | def pull_request_last_version(self): | |||
|
4410 | return pull_request_obj.pull_request_last_version | |||
|
4411 | ||||
4388 | attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False)) |
|
4412 | attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False)) | |
4389 |
|
4413 | |||
4390 | attrs.author = StrictAttributeDict( |
|
4414 | attrs.author = StrictAttributeDict( | |
@@ -4449,6 +4473,10 class PullRequest(Base, _PullRequestBase | |||||
4449 | """ |
|
4473 | """ | |
4450 | return self.versions.count() + 1 |
|
4474 | return self.versions.count() + 1 | |
4451 |
|
4475 | |||
|
4476 | @property | |||
|
4477 | def pull_request_last_version(self): | |||
|
4478 | return self.versions_count | |||
|
4479 | ||||
4452 |
|
4480 | |||
4453 | class PullRequestVersion(Base, _PullRequestBase): |
|
4481 | class PullRequestVersion(Base, _PullRequestBase): | |
4454 | __tablename__ = 'pull_request_versions' |
|
4482 | __tablename__ = 'pull_request_versions' |
@@ -240,14 +240,14 div.markdown-block ol { | |||||
240 | div.markdown-block ul.checkbox li, |
|
240 | div.markdown-block ul.checkbox li, | |
241 | div.markdown-block ol.checkbox li { |
|
241 | div.markdown-block ol.checkbox li { | |
242 | list-style: none !important; |
|
242 | list-style: none !important; | |
243 |
margin: |
|
243 | margin: 0px !important; | |
244 | padding: 0 !important; |
|
244 | padding: 0 !important; | |
245 | } |
|
245 | } | |
246 |
|
246 | |||
247 | div.markdown-block ul li, |
|
247 | div.markdown-block ul li, | |
248 | div.markdown-block ol li { |
|
248 | div.markdown-block ol li { | |
249 | list-style: disc !important; |
|
249 | list-style: disc !important; | |
250 |
margin: |
|
250 | margin: 0px !important; | |
251 | padding: 0 !important; |
|
251 | padding: 0 !important; | |
252 | } |
|
252 | } | |
253 |
|
253 |
@@ -1510,18 +1510,14 table.integrations { | |||||
1510 | min-height: 55px; |
|
1510 | min-height: 55px; | |
1511 | } |
|
1511 | } | |
1512 |
|
1512 | |||
1513 | .reviewers_member { |
|
|||
1514 | width: 100%; |
|
|||
1515 | overflow: auto; |
|
|||
1516 | } |
|
|||
1517 | .reviewer_reason { |
|
1513 | .reviewer_reason { | |
1518 | padding-left: 20px; |
|
1514 | padding-left: 20px; | |
1519 | line-height: 1.5em; |
|
1515 | line-height: 1.5em; | |
1520 | } |
|
1516 | } | |
1521 | .reviewer_status { |
|
1517 | .reviewer_status { | |
1522 | display: inline-block; |
|
1518 | display: inline-block; | |
1523 |
width: 2 |
|
1519 | width: 20px; | |
1524 |
min-width: 2 |
|
1520 | min-width: 20px; | |
1525 | height: 1.2em; |
|
1521 | height: 1.2em; | |
1526 | line-height: 1em; |
|
1522 | line-height: 1em; | |
1527 |