##// END OF EJS Templates
reviewers: changes for new author/commit author logic....
milka -
r4559:36905c9f default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,78 b''
1 # -*- coding: utf-8 -*-
2
3 import logging
4 from sqlalchemy import *
5
6 from alembic.migration import MigrationContext
7 from alembic.operations import Operations
8
9 from rhodecode.lib.dbmigrate.versions import _reset_base
10 from rhodecode.model import meta, init_model_encryption
11
12
13 log = logging.getLogger(__name__)
14
15
16 def upgrade(migrate_engine):
17 """
18 Upgrade operations go here.
19 Don't create your own engine; bind migrate_engine to your metadata
20 """
21 _reset_base(migrate_engine)
22 from rhodecode.lib.dbmigrate.schema import db_4_20_0_0 as db
23
24 init_model_encryption(db)
25
26 context = MigrationContext.configure(migrate_engine.connect())
27 op = Operations(context)
28
29 table = db.RepoReviewRule.__table__
30 with op.batch_alter_table(table.name) as batch_op:
31
32 new_column = Column('pr_author', UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
33 batch_op.add_column(new_column)
34
35 new_column = Column('commit_author', UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
36 batch_op.add_column(new_column)
37
38 _migrate_review_flags_to_new_cols(op, meta.Session)
39
40
41 def downgrade(migrate_engine):
42 meta = MetaData()
43 meta.bind = migrate_engine
44
45
46 def fixups(models, _SESSION):
47 pass
48
49
50 def _migrate_review_flags_to_new_cols(op, session):
51
52 # set defaults for pr_author
53 query = text(
54 'UPDATE repo_review_rules SET pr_author = :val'
55 ).bindparams(val='no_rule')
56 op.execute(query)
57
58 # set defaults for commit_author
59 query = text(
60 'UPDATE repo_review_rules SET commit_author = :val'
61 ).bindparams(val='no_rule')
62 op.execute(query)
63
64 session().commit()
65
66 # now change the flags to forbid based on
67 # forbid_author_to_review, forbid_commit_author_to_review
68 query = text(
69 'UPDATE repo_review_rules SET pr_author = :val WHERE forbid_author_to_review = TRUE'
70 ).bindparams(val='forbid_pr_author')
71 op.execute(query)
72
73 query = text(
74 'UPDATE repo_review_rules SET commit_author = :val WHERE forbid_commit_author_to_review = TRUE'
75 ).bindparams(val='forbid_commit_author')
76 op.execute(query)
77
78 session().commit()
@@ -1,60 +1,60 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 from collections import OrderedDict
22 from collections import OrderedDict
23
23
24 import sys
24 import sys
25 import platform
25 import platform
26
26
27 VERSION = tuple(open(os.path.join(
27 VERSION = tuple(open(os.path.join(
28 os.path.dirname(__file__), 'VERSION')).read().split('.'))
28 os.path.dirname(__file__), 'VERSION')).read().split('.'))
29
29
30 BACKENDS = OrderedDict()
30 BACKENDS = OrderedDict()
31
31
32 BACKENDS['hg'] = 'Mercurial repository'
32 BACKENDS['hg'] = 'Mercurial repository'
33 BACKENDS['git'] = 'Git repository'
33 BACKENDS['git'] = 'Git repository'
34 BACKENDS['svn'] = 'Subversion repository'
34 BACKENDS['svn'] = 'Subversion repository'
35
35
36
36
37 CELERY_ENABLED = False
37 CELERY_ENABLED = False
38 CELERY_EAGER = False
38 CELERY_EAGER = False
39
39
40 # link to config for pyramid
40 # link to config for pyramid
41 CONFIG = {}
41 CONFIG = {}
42
42
43 # Populated with the settings dictionary from application init in
43 # Populated with the settings dictionary from application init in
44 # rhodecode.conf.environment.load_pyramid_environment
44 # rhodecode.conf.environment.load_pyramid_environment
45 PYRAMID_SETTINGS = {}
45 PYRAMID_SETTINGS = {}
46
46
47 # Linked module for extensions
47 # Linked module for extensions
48 EXTENSIONS = {}
48 EXTENSIONS = {}
49
49
50 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
50 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
51 __dbversion__ = 111 # defines current db version for migrations
51 __dbversion__ = 112 # defines current db version for migrations
52 __platform__ = platform.system()
52 __platform__ = platform.system()
53 __license__ = 'AGPLv3, and Commercial License'
53 __license__ = 'AGPLv3, and Commercial License'
54 __author__ = 'RhodeCode GmbH'
54 __author__ = 'RhodeCode GmbH'
55 __url__ = 'https://code.rhodecode.com'
55 __url__ = 'https://code.rhodecode.com'
56
56
57 is_windows = __platform__ in ['Windows']
57 is_windows = __platform__ in ['Windows']
58 is_unix = not is_windows
58 is_unix = not is_windows
59 is_test = False
59 is_test = False
60 disable_error_handler = False
60 disable_error_handler = False
@@ -1,111 +1,113 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 from rhodecode.lib import helpers as h, rc_cache
21 from rhodecode.lib import helpers as h, rc_cache
22 from rhodecode.lib.utils2 import safe_int
22 from rhodecode.lib.utils2 import safe_int
23 from rhodecode.model.pull_request import get_diff_info
23 from rhodecode.model.pull_request import get_diff_info
24 from rhodecode.model.db import PullRequestReviewers
24 from rhodecode.model.db import PullRequestReviewers
25 # V3 - Reviewers, with default rules data
25 # V3 - Reviewers, with default rules data
26 # v4 - Added observers metadata
26 # v4 - Added observers metadata
27 REVIEWER_API_VERSION = 'V4'
27 # v5 - pr_author/commit_author include/exclude logic
28 REVIEWER_API_VERSION = 'V5'
28
29
29
30
30 def reviewer_as_json(user, reasons=None, role=None, mandatory=False, rules=None, user_group=None):
31 def reviewer_as_json(user, reasons=None, role=None, mandatory=False, rules=None, user_group=None):
31 """
32 """
32 Returns json struct of a reviewer for frontend
33 Returns json struct of a reviewer for frontend
33
34
34 :param user: the reviewer
35 :param user: the reviewer
35 :param reasons: list of strings of why they are reviewers
36 :param reasons: list of strings of why they are reviewers
36 :param mandatory: bool, to set user as mandatory
37 :param mandatory: bool, to set user as mandatory
37 """
38 """
38 role = role or PullRequestReviewers.ROLE_REVIEWER
39 role = role or PullRequestReviewers.ROLE_REVIEWER
39 if role not in PullRequestReviewers.ROLES:
40 if role not in PullRequestReviewers.ROLES:
40 raise ValueError('role is not one of %s', PullRequestReviewers.ROLES)
41 raise ValueError('role is not one of %s', PullRequestReviewers.ROLES)
41
42
42 return {
43 return {
43 'user_id': user.user_id,
44 'user_id': user.user_id,
44 'reasons': reasons or [],
45 'reasons': reasons or [],
45 'rules': rules or [],
46 'rules': rules or [],
46 'role': role,
47 'role': role,
47 'mandatory': mandatory,
48 'mandatory': mandatory,
48 'user_group': user_group,
49 'user_group': user_group,
49 'username': user.username,
50 'username': user.username,
50 'first_name': user.first_name,
51 'first_name': user.first_name,
51 'last_name': user.last_name,
52 'last_name': user.last_name,
52 'user_link': h.link_to_user(user),
53 'user_link': h.link_to_user(user),
53 'gravatar_link': h.gravatar_url(user.email, 14),
54 'gravatar_link': h.gravatar_url(user.email, 14),
54 }
55 }
55
56
56
57
57 def to_reviewers(e):
58 def to_reviewers(e):
58 if isinstance(e, (tuple, list)):
59 if isinstance(e, (tuple, list)):
59 return map(reviewer_as_json, e)
60 return map(reviewer_as_json, e)
60 else:
61 else:
61 return reviewer_as_json(e)
62 return reviewer_as_json(e)
62
63
63
64
64 def get_default_reviewers_data(current_user, source_repo, source_ref, target_repo, target_ref,
65 def get_default_reviewers_data(current_user, source_repo, source_ref, target_repo, target_ref,
65 include_diff_info=True):
66 include_diff_info=True):
66 """
67 """
67 Return json for default reviewers of a repository
68 Return json for default reviewers of a repository
68 """
69 """
69
70
70 diff_info = {}
71 diff_info = {}
71 if include_diff_info:
72 if include_diff_info:
72 diff_info = get_diff_info(
73 diff_info = get_diff_info(
73 source_repo, source_ref.commit_id, target_repo, target_ref.commit_id)
74 source_repo, source_ref.commit_id, target_repo, target_ref.commit_id)
74
75
75 reasons = ['Default reviewer', 'Repository owner']
76 reasons = ['Default reviewer', 'Repository owner']
76 json_reviewers = [reviewer_as_json(
77 json_reviewers = [reviewer_as_json(
77 user=target_repo.user, reasons=reasons, mandatory=False, rules=None, role=None)]
78 user=target_repo.user, reasons=reasons, mandatory=False, rules=None, role=None)]
78
79
79 compute_key = rc_cache.utils.compute_key_from_params(
80 compute_key = rc_cache.utils.compute_key_from_params(
80 current_user.user_id, source_repo.repo_id, source_ref.type, source_ref.name,
81 current_user.user_id, source_repo.repo_id, source_ref.type, source_ref.name,
81 source_ref.commit_id, target_repo.repo_id, target_ref.type, target_ref.name,
82 source_ref.commit_id, target_repo.repo_id, target_ref.type, target_ref.name,
82 target_ref.commit_id)
83 target_ref.commit_id)
83
84
84 return {
85 return {
85 'api_ver': REVIEWER_API_VERSION, # define version for later possible schema upgrade
86 'api_ver': REVIEWER_API_VERSION, # define version for later possible schema upgrade
86 'compute_key': compute_key,
87 'compute_key': compute_key,
87 'diff_info': diff_info,
88 'diff_info': diff_info,
88 'reviewers': json_reviewers,
89 'reviewers': json_reviewers,
89 'rules': {},
90 'rules': {},
90 'rules_data': {},
91 'rules_data': {},
92 'rules_humanized': [],
91 }
93 }
92
94
93
95
94 def validate_default_reviewers(review_members, reviewer_rules):
96 def validate_default_reviewers(review_members, reviewer_rules):
95 """
97 """
96 Function to validate submitted reviewers against the saved rules
98 Function to validate submitted reviewers against the saved rules
97 """
99 """
98 reviewers = []
100 reviewers = []
99 reviewer_by_id = {}
101 reviewer_by_id = {}
100 for r in review_members:
102 for r in review_members:
101 reviewer_user_id = safe_int(r['user_id'])
103 reviewer_user_id = safe_int(r['user_id'])
102 entry = (reviewer_user_id, r['reasons'], r['mandatory'], r['role'], r['rules'])
104 entry = (reviewer_user_id, r['reasons'], r['mandatory'], r['role'], r['rules'])
103
105
104 reviewer_by_id[reviewer_user_id] = entry
106 reviewer_by_id[reviewer_user_id] = entry
105 reviewers.append(entry)
107 reviewers.append(entry)
106
108
107 return reviewers
109 return reviewers
108
110
109
111
110 def validate_observers(observer_members, reviewer_rules):
112 def validate_observers(observer_members, reviewer_rules):
111 return {}
113 return {}
@@ -1,1855 +1,1851 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import collections
22 import collections
23
23
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26 import peppercorn
26 import peppercorn
27 from pyramid.httpexceptions import (
27 from pyramid.httpexceptions import (
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest, HTTPConflict)
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest, HTTPConflict)
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31
31
32 from rhodecode.apps._base import RepoAppView, DataGridAppView
32 from rhodecode.apps._base import RepoAppView, DataGridAppView
33
33
34 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
34 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
35 from rhodecode.lib.base import vcs_operation_context
35 from rhodecode.lib.base import vcs_operation_context
36 from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist
36 from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist
37 from rhodecode.lib.exceptions import CommentVersionMismatch
37 from rhodecode.lib.exceptions import CommentVersionMismatch
38 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
40 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
41 NotAnonymous, CSRFRequired)
41 NotAnonymous, CSRFRequired)
42 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int, aslist
42 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int, aslist
43 from rhodecode.lib.vcs.backends.base import (
43 from rhodecode.lib.vcs.backends.base import (
44 EmptyCommit, UpdateFailureReason, unicode_to_reference)
44 EmptyCommit, UpdateFailureReason, unicode_to_reference)
45 from rhodecode.lib.vcs.exceptions import (
45 from rhodecode.lib.vcs.exceptions import (
46 CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError)
46 CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError)
47 from rhodecode.model.changeset_status import ChangesetStatusModel
47 from rhodecode.model.changeset_status import ChangesetStatusModel
48 from rhodecode.model.comment import CommentsModel
48 from rhodecode.model.comment import CommentsModel
49 from rhodecode.model.db import (
49 from rhodecode.model.db import (
50 func, false, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository,
50 func, false, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository,
51 PullRequestReviewers)
51 PullRequestReviewers)
52 from rhodecode.model.forms import PullRequestForm
52 from rhodecode.model.forms import PullRequestForm
53 from rhodecode.model.meta import Session
53 from rhodecode.model.meta import Session
54 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
54 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
55 from rhodecode.model.scm import ScmModel
55 from rhodecode.model.scm import ScmModel
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 class RepoPullRequestsView(RepoAppView, DataGridAppView):
60 class RepoPullRequestsView(RepoAppView, DataGridAppView):
61
61
62 def load_default_context(self):
62 def load_default_context(self):
63 c = self._get_local_tmpl_context(include_app_defaults=True)
63 c = self._get_local_tmpl_context(include_app_defaults=True)
64 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
64 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
65 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
65 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
66 # backward compat., we use for OLD PRs a plain renderer
66 # backward compat., we use for OLD PRs a plain renderer
67 c.renderer = 'plain'
67 c.renderer = 'plain'
68 return c
68 return c
69
69
70 def _get_pull_requests_list(
70 def _get_pull_requests_list(
71 self, repo_name, source, filter_type, opened_by, statuses):
71 self, repo_name, source, filter_type, opened_by, statuses):
72
72
73 draw, start, limit = self._extract_chunk(self.request)
73 draw, start, limit = self._extract_chunk(self.request)
74 search_q, order_by, order_dir = self._extract_ordering(self.request)
74 search_q, order_by, order_dir = self._extract_ordering(self.request)
75 _render = self.request.get_partial_renderer(
75 _render = self.request.get_partial_renderer(
76 'rhodecode:templates/data_table/_dt_elements.mako')
76 'rhodecode:templates/data_table/_dt_elements.mako')
77
77
78 # pagination
78 # pagination
79
79
80 if filter_type == 'awaiting_review':
80 if filter_type == 'awaiting_review':
81 pull_requests = PullRequestModel().get_awaiting_review(
81 pull_requests = PullRequestModel().get_awaiting_review(
82 repo_name, search_q=search_q, source=source, opened_by=opened_by,
82 repo_name, search_q=search_q, source=source, opened_by=opened_by,
83 statuses=statuses, offset=start, length=limit,
83 statuses=statuses, offset=start, length=limit,
84 order_by=order_by, order_dir=order_dir)
84 order_by=order_by, order_dir=order_dir)
85 pull_requests_total_count = PullRequestModel().count_awaiting_review(
85 pull_requests_total_count = PullRequestModel().count_awaiting_review(
86 repo_name, search_q=search_q, source=source, statuses=statuses,
86 repo_name, search_q=search_q, source=source, statuses=statuses,
87 opened_by=opened_by)
87 opened_by=opened_by)
88 elif filter_type == 'awaiting_my_review':
88 elif filter_type == 'awaiting_my_review':
89 pull_requests = PullRequestModel().get_awaiting_my_review(
89 pull_requests = PullRequestModel().get_awaiting_my_review(
90 repo_name, search_q=search_q, source=source, opened_by=opened_by,
90 repo_name, search_q=search_q, source=source, opened_by=opened_by,
91 user_id=self._rhodecode_user.user_id, statuses=statuses,
91 user_id=self._rhodecode_user.user_id, statuses=statuses,
92 offset=start, length=limit, order_by=order_by,
92 offset=start, length=limit, order_by=order_by,
93 order_dir=order_dir)
93 order_dir=order_dir)
94 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
94 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
95 repo_name, search_q=search_q, source=source, user_id=self._rhodecode_user.user_id,
95 repo_name, search_q=search_q, source=source, user_id=self._rhodecode_user.user_id,
96 statuses=statuses, opened_by=opened_by)
96 statuses=statuses, opened_by=opened_by)
97 else:
97 else:
98 pull_requests = PullRequestModel().get_all(
98 pull_requests = PullRequestModel().get_all(
99 repo_name, search_q=search_q, source=source, opened_by=opened_by,
99 repo_name, search_q=search_q, source=source, opened_by=opened_by,
100 statuses=statuses, offset=start, length=limit,
100 statuses=statuses, offset=start, length=limit,
101 order_by=order_by, order_dir=order_dir)
101 order_by=order_by, order_dir=order_dir)
102 pull_requests_total_count = PullRequestModel().count_all(
102 pull_requests_total_count = PullRequestModel().count_all(
103 repo_name, search_q=search_q, source=source, statuses=statuses,
103 repo_name, search_q=search_q, source=source, statuses=statuses,
104 opened_by=opened_by)
104 opened_by=opened_by)
105
105
106 data = []
106 data = []
107 comments_model = CommentsModel()
107 comments_model = CommentsModel()
108 for pr in pull_requests:
108 for pr in pull_requests:
109 comments_count = comments_model.get_all_comments(
109 comments_count = comments_model.get_all_comments(
110 self.db_repo.repo_id, pull_request=pr,
110 self.db_repo.repo_id, pull_request=pr,
111 include_drafts=False, count_only=True)
111 include_drafts=False, count_only=True)
112
112
113 data.append({
113 data.append({
114 'name': _render('pullrequest_name',
114 'name': _render('pullrequest_name',
115 pr.pull_request_id, pr.pull_request_state,
115 pr.pull_request_id, pr.pull_request_state,
116 pr.work_in_progress, pr.target_repo.repo_name,
116 pr.work_in_progress, pr.target_repo.repo_name,
117 short=True),
117 short=True),
118 'name_raw': pr.pull_request_id,
118 'name_raw': pr.pull_request_id,
119 'status': _render('pullrequest_status',
119 'status': _render('pullrequest_status',
120 pr.calculated_review_status()),
120 pr.calculated_review_status()),
121 'title': _render('pullrequest_title', pr.title, pr.description),
121 'title': _render('pullrequest_title', pr.title, pr.description),
122 'description': h.escape(pr.description),
122 'description': h.escape(pr.description),
123 'updated_on': _render('pullrequest_updated_on',
123 'updated_on': _render('pullrequest_updated_on',
124 h.datetime_to_time(pr.updated_on),
124 h.datetime_to_time(pr.updated_on),
125 pr.versions_count),
125 pr.versions_count),
126 'updated_on_raw': h.datetime_to_time(pr.updated_on),
126 'updated_on_raw': h.datetime_to_time(pr.updated_on),
127 'created_on': _render('pullrequest_updated_on',
127 'created_on': _render('pullrequest_updated_on',
128 h.datetime_to_time(pr.created_on)),
128 h.datetime_to_time(pr.created_on)),
129 'created_on_raw': h.datetime_to_time(pr.created_on),
129 'created_on_raw': h.datetime_to_time(pr.created_on),
130 'state': pr.pull_request_state,
130 'state': pr.pull_request_state,
131 'author': _render('pullrequest_author',
131 'author': _render('pullrequest_author',
132 pr.author.full_contact, ),
132 pr.author.full_contact, ),
133 'author_raw': pr.author.full_name,
133 'author_raw': pr.author.full_name,
134 'comments': _render('pullrequest_comments', comments_count),
134 'comments': _render('pullrequest_comments', comments_count),
135 'comments_raw': comments_count,
135 'comments_raw': comments_count,
136 'closed': pr.is_closed(),
136 'closed': pr.is_closed(),
137 })
137 })
138
138
139 data = ({
139 data = ({
140 'draw': draw,
140 'draw': draw,
141 'data': data,
141 'data': data,
142 'recordsTotal': pull_requests_total_count,
142 'recordsTotal': pull_requests_total_count,
143 'recordsFiltered': pull_requests_total_count,
143 'recordsFiltered': pull_requests_total_count,
144 })
144 })
145 return data
145 return data
146
146
147 @LoginRequired()
147 @LoginRequired()
148 @HasRepoPermissionAnyDecorator(
148 @HasRepoPermissionAnyDecorator(
149 'repository.read', 'repository.write', 'repository.admin')
149 'repository.read', 'repository.write', 'repository.admin')
150 @view_config(
150 @view_config(
151 route_name='pullrequest_show_all', request_method='GET',
151 route_name='pullrequest_show_all', request_method='GET',
152 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
152 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
153 def pull_request_list(self):
153 def pull_request_list(self):
154 c = self.load_default_context()
154 c = self.load_default_context()
155
155
156 req_get = self.request.GET
156 req_get = self.request.GET
157 c.source = str2bool(req_get.get('source'))
157 c.source = str2bool(req_get.get('source'))
158 c.closed = str2bool(req_get.get('closed'))
158 c.closed = str2bool(req_get.get('closed'))
159 c.my = str2bool(req_get.get('my'))
159 c.my = str2bool(req_get.get('my'))
160 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
160 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
161 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
161 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
162
162
163 c.active = 'open'
163 c.active = 'open'
164 if c.my:
164 if c.my:
165 c.active = 'my'
165 c.active = 'my'
166 if c.closed:
166 if c.closed:
167 c.active = 'closed'
167 c.active = 'closed'
168 if c.awaiting_review and not c.source:
168 if c.awaiting_review and not c.source:
169 c.active = 'awaiting'
169 c.active = 'awaiting'
170 if c.source and not c.awaiting_review:
170 if c.source and not c.awaiting_review:
171 c.active = 'source'
171 c.active = 'source'
172 if c.awaiting_my_review:
172 if c.awaiting_my_review:
173 c.active = 'awaiting_my'
173 c.active = 'awaiting_my'
174
174
175 return self._get_template_context(c)
175 return self._get_template_context(c)
176
176
177 @LoginRequired()
177 @LoginRequired()
178 @HasRepoPermissionAnyDecorator(
178 @HasRepoPermissionAnyDecorator(
179 'repository.read', 'repository.write', 'repository.admin')
179 'repository.read', 'repository.write', 'repository.admin')
180 @view_config(
180 @view_config(
181 route_name='pullrequest_show_all_data', request_method='GET',
181 route_name='pullrequest_show_all_data', request_method='GET',
182 renderer='json_ext', xhr=True)
182 renderer='json_ext', xhr=True)
183 def pull_request_list_data(self):
183 def pull_request_list_data(self):
184 self.load_default_context()
184 self.load_default_context()
185
185
186 # additional filters
186 # additional filters
187 req_get = self.request.GET
187 req_get = self.request.GET
188 source = str2bool(req_get.get('source'))
188 source = str2bool(req_get.get('source'))
189 closed = str2bool(req_get.get('closed'))
189 closed = str2bool(req_get.get('closed'))
190 my = str2bool(req_get.get('my'))
190 my = str2bool(req_get.get('my'))
191 awaiting_review = str2bool(req_get.get('awaiting_review'))
191 awaiting_review = str2bool(req_get.get('awaiting_review'))
192 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
192 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
193
193
194 filter_type = 'awaiting_review' if awaiting_review \
194 filter_type = 'awaiting_review' if awaiting_review \
195 else 'awaiting_my_review' if awaiting_my_review \
195 else 'awaiting_my_review' if awaiting_my_review \
196 else None
196 else None
197
197
198 opened_by = None
198 opened_by = None
199 if my:
199 if my:
200 opened_by = [self._rhodecode_user.user_id]
200 opened_by = [self._rhodecode_user.user_id]
201
201
202 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
202 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
203 if closed:
203 if closed:
204 statuses = [PullRequest.STATUS_CLOSED]
204 statuses = [PullRequest.STATUS_CLOSED]
205
205
206 data = self._get_pull_requests_list(
206 data = self._get_pull_requests_list(
207 repo_name=self.db_repo_name, source=source,
207 repo_name=self.db_repo_name, source=source,
208 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
208 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
209
209
210 return data
210 return data
211
211
212 def _is_diff_cache_enabled(self, target_repo):
212 def _is_diff_cache_enabled(self, target_repo):
213 caching_enabled = self._get_general_setting(
213 caching_enabled = self._get_general_setting(
214 target_repo, 'rhodecode_diff_cache')
214 target_repo, 'rhodecode_diff_cache')
215 log.debug('Diff caching enabled: %s', caching_enabled)
215 log.debug('Diff caching enabled: %s', caching_enabled)
216 return caching_enabled
216 return caching_enabled
217
217
218 def _get_diffset(self, source_repo_name, source_repo,
218 def _get_diffset(self, source_repo_name, source_repo,
219 ancestor_commit,
219 ancestor_commit,
220 source_ref_id, target_ref_id,
220 source_ref_id, target_ref_id,
221 target_commit, source_commit, diff_limit, file_limit,
221 target_commit, source_commit, diff_limit, file_limit,
222 fulldiff, hide_whitespace_changes, diff_context, use_ancestor=True):
222 fulldiff, hide_whitespace_changes, diff_context, use_ancestor=True):
223
223
224 if use_ancestor:
224 if use_ancestor:
225 # we might want to not use it for versions
225 # we might want to not use it for versions
226 target_ref_id = ancestor_commit.raw_id
226 target_ref_id = ancestor_commit.raw_id
227
227
228 vcs_diff = PullRequestModel().get_diff(
228 vcs_diff = PullRequestModel().get_diff(
229 source_repo, source_ref_id, target_ref_id,
229 source_repo, source_ref_id, target_ref_id,
230 hide_whitespace_changes, diff_context)
230 hide_whitespace_changes, diff_context)
231
231
232 diff_processor = diffs.DiffProcessor(
232 diff_processor = diffs.DiffProcessor(
233 vcs_diff, format='newdiff', diff_limit=diff_limit,
233 vcs_diff, format='newdiff', diff_limit=diff_limit,
234 file_limit=file_limit, show_full_diff=fulldiff)
234 file_limit=file_limit, show_full_diff=fulldiff)
235
235
236 _parsed = diff_processor.prepare()
236 _parsed = diff_processor.prepare()
237
237
238 diffset = codeblocks.DiffSet(
238 diffset = codeblocks.DiffSet(
239 repo_name=self.db_repo_name,
239 repo_name=self.db_repo_name,
240 source_repo_name=source_repo_name,
240 source_repo_name=source_repo_name,
241 source_node_getter=codeblocks.diffset_node_getter(target_commit),
241 source_node_getter=codeblocks.diffset_node_getter(target_commit),
242 target_node_getter=codeblocks.diffset_node_getter(source_commit),
242 target_node_getter=codeblocks.diffset_node_getter(source_commit),
243 )
243 )
244 diffset = self.path_filter.render_patchset_filtered(
244 diffset = self.path_filter.render_patchset_filtered(
245 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
245 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
246
246
247 return diffset
247 return diffset
248
248
249 def _get_range_diffset(self, source_scm, source_repo,
249 def _get_range_diffset(self, source_scm, source_repo,
250 commit1, commit2, diff_limit, file_limit,
250 commit1, commit2, diff_limit, file_limit,
251 fulldiff, hide_whitespace_changes, diff_context):
251 fulldiff, hide_whitespace_changes, diff_context):
252 vcs_diff = source_scm.get_diff(
252 vcs_diff = source_scm.get_diff(
253 commit1, commit2,
253 commit1, commit2,
254 ignore_whitespace=hide_whitespace_changes,
254 ignore_whitespace=hide_whitespace_changes,
255 context=diff_context)
255 context=diff_context)
256
256
257 diff_processor = diffs.DiffProcessor(
257 diff_processor = diffs.DiffProcessor(
258 vcs_diff, format='newdiff', diff_limit=diff_limit,
258 vcs_diff, format='newdiff', diff_limit=diff_limit,
259 file_limit=file_limit, show_full_diff=fulldiff)
259 file_limit=file_limit, show_full_diff=fulldiff)
260
260
261 _parsed = diff_processor.prepare()
261 _parsed = diff_processor.prepare()
262
262
263 diffset = codeblocks.DiffSet(
263 diffset = codeblocks.DiffSet(
264 repo_name=source_repo.repo_name,
264 repo_name=source_repo.repo_name,
265 source_node_getter=codeblocks.diffset_node_getter(commit1),
265 source_node_getter=codeblocks.diffset_node_getter(commit1),
266 target_node_getter=codeblocks.diffset_node_getter(commit2))
266 target_node_getter=codeblocks.diffset_node_getter(commit2))
267
267
268 diffset = self.path_filter.render_patchset_filtered(
268 diffset = self.path_filter.render_patchset_filtered(
269 diffset, _parsed, commit1.raw_id, commit2.raw_id)
269 diffset, _parsed, commit1.raw_id, commit2.raw_id)
270
270
271 return diffset
271 return diffset
272
272
273 def register_comments_vars(self, c, pull_request, versions, include_drafts=True):
273 def register_comments_vars(self, c, pull_request, versions, include_drafts=True):
274 comments_model = CommentsModel()
274 comments_model = CommentsModel()
275
275
276 # GENERAL COMMENTS with versions #
276 # GENERAL COMMENTS with versions #
277 q = comments_model._all_general_comments_of_pull_request(pull_request)
277 q = comments_model._all_general_comments_of_pull_request(pull_request)
278 q = q.order_by(ChangesetComment.comment_id.asc())
278 q = q.order_by(ChangesetComment.comment_id.asc())
279 if not include_drafts:
279 if not include_drafts:
280 q = q.filter(ChangesetComment.draft == false())
280 q = q.filter(ChangesetComment.draft == false())
281 general_comments = q
281 general_comments = q
282
282
283 # pick comments we want to render at current version
283 # pick comments we want to render at current version
284 c.comment_versions = comments_model.aggregate_comments(
284 c.comment_versions = comments_model.aggregate_comments(
285 general_comments, versions, c.at_version_num)
285 general_comments, versions, c.at_version_num)
286
286
287 # INLINE COMMENTS with versions #
287 # INLINE COMMENTS with versions #
288 q = comments_model._all_inline_comments_of_pull_request(pull_request)
288 q = comments_model._all_inline_comments_of_pull_request(pull_request)
289 q = q.order_by(ChangesetComment.comment_id.asc())
289 q = q.order_by(ChangesetComment.comment_id.asc())
290 if not include_drafts:
290 if not include_drafts:
291 q = q.filter(ChangesetComment.draft == false())
291 q = q.filter(ChangesetComment.draft == false())
292 inline_comments = q
292 inline_comments = q
293
293
294 c.inline_versions = comments_model.aggregate_comments(
294 c.inline_versions = comments_model.aggregate_comments(
295 inline_comments, versions, c.at_version_num, inline=True)
295 inline_comments, versions, c.at_version_num, inline=True)
296
296
297 # Comments inline+general
297 # Comments inline+general
298 if c.at_version:
298 if c.at_version:
299 c.inline_comments_flat = c.inline_versions[c.at_version_num]['display']
299 c.inline_comments_flat = c.inline_versions[c.at_version_num]['display']
300 c.comments = c.comment_versions[c.at_version_num]['display']
300 c.comments = c.comment_versions[c.at_version_num]['display']
301 else:
301 else:
302 c.inline_comments_flat = c.inline_versions[c.at_version_num]['until']
302 c.inline_comments_flat = c.inline_versions[c.at_version_num]['until']
303 c.comments = c.comment_versions[c.at_version_num]['until']
303 c.comments = c.comment_versions[c.at_version_num]['until']
304
304
305 return general_comments, inline_comments
305 return general_comments, inline_comments
306
306
307 @LoginRequired()
307 @LoginRequired()
308 @HasRepoPermissionAnyDecorator(
308 @HasRepoPermissionAnyDecorator(
309 'repository.read', 'repository.write', 'repository.admin')
309 'repository.read', 'repository.write', 'repository.admin')
310 @view_config(
310 @view_config(
311 route_name='pullrequest_show', request_method='GET',
311 route_name='pullrequest_show', request_method='GET',
312 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
312 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
313 def pull_request_show(self):
313 def pull_request_show(self):
314 _ = self.request.translate
314 _ = self.request.translate
315 c = self.load_default_context()
315 c = self.load_default_context()
316
316
317 pull_request = PullRequest.get_or_404(
317 pull_request = PullRequest.get_or_404(
318 self.request.matchdict['pull_request_id'])
318 self.request.matchdict['pull_request_id'])
319 pull_request_id = pull_request.pull_request_id
319 pull_request_id = pull_request.pull_request_id
320
320
321 c.state_progressing = pull_request.is_state_changing()
321 c.state_progressing = pull_request.is_state_changing()
322 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
322 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
323
323
324 _new_state = {
324 _new_state = {
325 'created': PullRequest.STATE_CREATED,
325 'created': PullRequest.STATE_CREATED,
326 }.get(self.request.GET.get('force_state'))
326 }.get(self.request.GET.get('force_state'))
327
327
328 if c.is_super_admin and _new_state:
328 if c.is_super_admin and _new_state:
329 with pull_request.set_state(PullRequest.STATE_UPDATING, final_state=_new_state):
329 with pull_request.set_state(PullRequest.STATE_UPDATING, final_state=_new_state):
330 h.flash(
330 h.flash(
331 _('Pull Request state was force changed to `{}`').format(_new_state),
331 _('Pull Request state was force changed to `{}`').format(_new_state),
332 category='success')
332 category='success')
333 Session().commit()
333 Session().commit()
334
334
335 raise HTTPFound(h.route_path(
335 raise HTTPFound(h.route_path(
336 'pullrequest_show', repo_name=self.db_repo_name,
336 'pullrequest_show', repo_name=self.db_repo_name,
337 pull_request_id=pull_request_id))
337 pull_request_id=pull_request_id))
338
338
339 version = self.request.GET.get('version')
339 version = self.request.GET.get('version')
340 from_version = self.request.GET.get('from_version') or version
340 from_version = self.request.GET.get('from_version') or version
341 merge_checks = self.request.GET.get('merge_checks')
341 merge_checks = self.request.GET.get('merge_checks')
342 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
342 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
343 force_refresh = str2bool(self.request.GET.get('force_refresh'))
343 force_refresh = str2bool(self.request.GET.get('force_refresh'))
344 c.range_diff_on = self.request.GET.get('range-diff') == "1"
344 c.range_diff_on = self.request.GET.get('range-diff') == "1"
345
345
346 # fetch global flags of ignore ws or context lines
346 # fetch global flags of ignore ws or context lines
347 diff_context = diffs.get_diff_context(self.request)
347 diff_context = diffs.get_diff_context(self.request)
348 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
348 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
349
349
350 (pull_request_latest,
350 (pull_request_latest,
351 pull_request_at_ver,
351 pull_request_at_ver,
352 pull_request_display_obj,
352 pull_request_display_obj,
353 at_version) = PullRequestModel().get_pr_version(
353 at_version) = PullRequestModel().get_pr_version(
354 pull_request_id, version=version)
354 pull_request_id, version=version)
355
355
356 pr_closed = pull_request_latest.is_closed()
356 pr_closed = pull_request_latest.is_closed()
357
357
358 if pr_closed and (version or from_version):
358 if pr_closed and (version or from_version):
359 # not allow to browse versions for closed PR
359 # not allow to browse versions for closed PR
360 raise HTTPFound(h.route_path(
360 raise HTTPFound(h.route_path(
361 'pullrequest_show', repo_name=self.db_repo_name,
361 'pullrequest_show', repo_name=self.db_repo_name,
362 pull_request_id=pull_request_id))
362 pull_request_id=pull_request_id))
363
363
364 versions = pull_request_display_obj.versions()
364 versions = pull_request_display_obj.versions()
365 # used to store per-commit range diffs
365 # used to store per-commit range diffs
366 c.changes = collections.OrderedDict()
366 c.changes = collections.OrderedDict()
367
367
368 c.at_version = at_version
368 c.at_version = at_version
369 c.at_version_num = (at_version
369 c.at_version_num = (at_version
370 if at_version and at_version != PullRequest.LATEST_VER
370 if at_version and at_version != PullRequest.LATEST_VER
371 else None)
371 else None)
372
372
373 c.at_version_index = ChangesetComment.get_index_from_version(
373 c.at_version_index = ChangesetComment.get_index_from_version(
374 c.at_version_num, versions)
374 c.at_version_num, versions)
375
375
376 (prev_pull_request_latest,
376 (prev_pull_request_latest,
377 prev_pull_request_at_ver,
377 prev_pull_request_at_ver,
378 prev_pull_request_display_obj,
378 prev_pull_request_display_obj,
379 prev_at_version) = PullRequestModel().get_pr_version(
379 prev_at_version) = PullRequestModel().get_pr_version(
380 pull_request_id, version=from_version)
380 pull_request_id, version=from_version)
381
381
382 c.from_version = prev_at_version
382 c.from_version = prev_at_version
383 c.from_version_num = (prev_at_version
383 c.from_version_num = (prev_at_version
384 if prev_at_version and prev_at_version != PullRequest.LATEST_VER
384 if prev_at_version and prev_at_version != PullRequest.LATEST_VER
385 else None)
385 else None)
386 c.from_version_index = ChangesetComment.get_index_from_version(
386 c.from_version_index = ChangesetComment.get_index_from_version(
387 c.from_version_num, versions)
387 c.from_version_num, versions)
388
388
389 # define if we're in COMPARE mode or VIEW at version mode
389 # define if we're in COMPARE mode or VIEW at version mode
390 compare = at_version != prev_at_version
390 compare = at_version != prev_at_version
391
391
392 # pull_requests repo_name we opened it against
392 # pull_requests repo_name we opened it against
393 # ie. target_repo must match
393 # ie. target_repo must match
394 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
394 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
395 log.warning('Mismatch between the current repo: %s, and target %s',
395 log.warning('Mismatch between the current repo: %s, and target %s',
396 self.db_repo_name, pull_request_at_ver.target_repo.repo_name)
396 self.db_repo_name, pull_request_at_ver.target_repo.repo_name)
397 raise HTTPNotFound()
397 raise HTTPNotFound()
398
398
399 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver)
399 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver)
400
400
401 c.pull_request = pull_request_display_obj
401 c.pull_request = pull_request_display_obj
402 c.renderer = pull_request_at_ver.description_renderer or c.renderer
402 c.renderer = pull_request_at_ver.description_renderer or c.renderer
403 c.pull_request_latest = pull_request_latest
403 c.pull_request_latest = pull_request_latest
404
404
405 # inject latest version
405 # inject latest version
406 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
406 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
407 c.versions = versions + [latest_ver]
407 c.versions = versions + [latest_ver]
408
408
409 if compare or (at_version and not at_version == PullRequest.LATEST_VER):
409 if compare or (at_version and not at_version == PullRequest.LATEST_VER):
410 c.allowed_to_change_status = False
410 c.allowed_to_change_status = False
411 c.allowed_to_update = False
411 c.allowed_to_update = False
412 c.allowed_to_merge = False
412 c.allowed_to_merge = False
413 c.allowed_to_delete = False
413 c.allowed_to_delete = False
414 c.allowed_to_comment = False
414 c.allowed_to_comment = False
415 c.allowed_to_close = False
415 c.allowed_to_close = False
416 else:
416 else:
417 can_change_status = PullRequestModel().check_user_change_status(
417 can_change_status = PullRequestModel().check_user_change_status(
418 pull_request_at_ver, self._rhodecode_user)
418 pull_request_at_ver, self._rhodecode_user)
419 c.allowed_to_change_status = can_change_status and not pr_closed
419 c.allowed_to_change_status = can_change_status and not pr_closed
420
420
421 c.allowed_to_update = PullRequestModel().check_user_update(
421 c.allowed_to_update = PullRequestModel().check_user_update(
422 pull_request_latest, self._rhodecode_user) and not pr_closed
422 pull_request_latest, self._rhodecode_user) and not pr_closed
423 c.allowed_to_merge = PullRequestModel().check_user_merge(
423 c.allowed_to_merge = PullRequestModel().check_user_merge(
424 pull_request_latest, self._rhodecode_user) and not pr_closed
424 pull_request_latest, self._rhodecode_user) and not pr_closed
425 c.allowed_to_delete = PullRequestModel().check_user_delete(
425 c.allowed_to_delete = PullRequestModel().check_user_delete(
426 pull_request_latest, self._rhodecode_user) and not pr_closed
426 pull_request_latest, self._rhodecode_user) and not pr_closed
427 c.allowed_to_comment = not pr_closed
427 c.allowed_to_comment = not pr_closed
428 c.allowed_to_close = c.allowed_to_merge and not pr_closed
428 c.allowed_to_close = c.allowed_to_merge and not pr_closed
429
429
430 c.forbid_adding_reviewers = False
430 c.forbid_adding_reviewers = False
431 c.forbid_author_to_review = False
432 c.forbid_commit_author_to_review = False
433
431
434 if pull_request_latest.reviewer_data and \
432 if pull_request_latest.reviewer_data and \
435 'rules' in pull_request_latest.reviewer_data:
433 'rules' in pull_request_latest.reviewer_data:
436 rules = pull_request_latest.reviewer_data['rules'] or {}
434 rules = pull_request_latest.reviewer_data['rules'] or {}
437 try:
435 try:
438 c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers')
436 c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers')
439 c.forbid_author_to_review = rules.get('forbid_author_to_review')
440 c.forbid_commit_author_to_review = rules.get('forbid_commit_author_to_review')
441 except Exception:
437 except Exception:
442 pass
438 pass
443
439
444 # check merge capabilities
440 # check merge capabilities
445 _merge_check = MergeCheck.validate(
441 _merge_check = MergeCheck.validate(
446 pull_request_latest, auth_user=self._rhodecode_user,
442 pull_request_latest, auth_user=self._rhodecode_user,
447 translator=self.request.translate,
443 translator=self.request.translate,
448 force_shadow_repo_refresh=force_refresh)
444 force_shadow_repo_refresh=force_refresh)
449
445
450 c.pr_merge_errors = _merge_check.error_details
446 c.pr_merge_errors = _merge_check.error_details
451 c.pr_merge_possible = not _merge_check.failed
447 c.pr_merge_possible = not _merge_check.failed
452 c.pr_merge_message = _merge_check.merge_msg
448 c.pr_merge_message = _merge_check.merge_msg
453 c.pr_merge_source_commit = _merge_check.source_commit
449 c.pr_merge_source_commit = _merge_check.source_commit
454 c.pr_merge_target_commit = _merge_check.target_commit
450 c.pr_merge_target_commit = _merge_check.target_commit
455
451
456 c.pr_merge_info = MergeCheck.get_merge_conditions(
452 c.pr_merge_info = MergeCheck.get_merge_conditions(
457 pull_request_latest, translator=self.request.translate)
453 pull_request_latest, translator=self.request.translate)
458
454
459 c.pull_request_review_status = _merge_check.review_status
455 c.pull_request_review_status = _merge_check.review_status
460 if merge_checks:
456 if merge_checks:
461 self.request.override_renderer = \
457 self.request.override_renderer = \
462 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
458 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
463 return self._get_template_context(c)
459 return self._get_template_context(c)
464
460
465 c.reviewers_count = pull_request.reviewers_count
461 c.reviewers_count = pull_request.reviewers_count
466 c.observers_count = pull_request.observers_count
462 c.observers_count = pull_request.observers_count
467
463
468 # reviewers and statuses
464 # reviewers and statuses
469 c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data)
465 c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data)
470 c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
466 c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
471 c.pull_request_set_observers_data_json = collections.OrderedDict({'observers': []})
467 c.pull_request_set_observers_data_json = collections.OrderedDict({'observers': []})
472
468
473 for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses():
469 for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses():
474 member_reviewer = h.reviewer_as_json(
470 member_reviewer = h.reviewer_as_json(
475 member, reasons=reasons, mandatory=mandatory,
471 member, reasons=reasons, mandatory=mandatory,
476 role=review_obj.role,
472 role=review_obj.role,
477 user_group=review_obj.rule_user_group_data()
473 user_group=review_obj.rule_user_group_data()
478 )
474 )
479
475
480 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
476 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
481 member_reviewer['review_status'] = current_review_status
477 member_reviewer['review_status'] = current_review_status
482 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
478 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
483 member_reviewer['allowed_to_update'] = c.allowed_to_update
479 member_reviewer['allowed_to_update'] = c.allowed_to_update
484 c.pull_request_set_reviewers_data_json['reviewers'].append(member_reviewer)
480 c.pull_request_set_reviewers_data_json['reviewers'].append(member_reviewer)
485
481
486 c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json)
482 c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json)
487
483
488 for observer_obj, member in pull_request_at_ver.observers():
484 for observer_obj, member in pull_request_at_ver.observers():
489 member_observer = h.reviewer_as_json(
485 member_observer = h.reviewer_as_json(
490 member, reasons=[], mandatory=False,
486 member, reasons=[], mandatory=False,
491 role=observer_obj.role,
487 role=observer_obj.role,
492 user_group=observer_obj.rule_user_group_data()
488 user_group=observer_obj.rule_user_group_data()
493 )
489 )
494 member_observer['allowed_to_update'] = c.allowed_to_update
490 member_observer['allowed_to_update'] = c.allowed_to_update
495 c.pull_request_set_observers_data_json['observers'].append(member_observer)
491 c.pull_request_set_observers_data_json['observers'].append(member_observer)
496
492
497 c.pull_request_set_observers_data_json = json.dumps(c.pull_request_set_observers_data_json)
493 c.pull_request_set_observers_data_json = json.dumps(c.pull_request_set_observers_data_json)
498
494
499 general_comments, inline_comments = \
495 general_comments, inline_comments = \
500 self.register_comments_vars(c, pull_request_latest, versions)
496 self.register_comments_vars(c, pull_request_latest, versions)
501
497
502 # TODOs
498 # TODOs
503 c.unresolved_comments = CommentsModel() \
499 c.unresolved_comments = CommentsModel() \
504 .get_pull_request_unresolved_todos(pull_request_latest)
500 .get_pull_request_unresolved_todos(pull_request_latest)
505 c.resolved_comments = CommentsModel() \
501 c.resolved_comments = CommentsModel() \
506 .get_pull_request_resolved_todos(pull_request_latest)
502 .get_pull_request_resolved_todos(pull_request_latest)
507
503
508 # if we use version, then do not show later comments
504 # if we use version, then do not show later comments
509 # than current version
505 # than current version
510 display_inline_comments = collections.defaultdict(
506 display_inline_comments = collections.defaultdict(
511 lambda: collections.defaultdict(list))
507 lambda: collections.defaultdict(list))
512 for co in inline_comments:
508 for co in inline_comments:
513 if c.at_version_num:
509 if c.at_version_num:
514 # pick comments that are at least UPTO given version, so we
510 # pick comments that are at least UPTO given version, so we
515 # don't render comments for higher version
511 # don't render comments for higher version
516 should_render = co.pull_request_version_id and \
512 should_render = co.pull_request_version_id and \
517 co.pull_request_version_id <= c.at_version_num
513 co.pull_request_version_id <= c.at_version_num
518 else:
514 else:
519 # showing all, for 'latest'
515 # showing all, for 'latest'
520 should_render = True
516 should_render = True
521
517
522 if should_render:
518 if should_render:
523 display_inline_comments[co.f_path][co.line_no].append(co)
519 display_inline_comments[co.f_path][co.line_no].append(co)
524
520
525 # load diff data into template context, if we use compare mode then
521 # load diff data into template context, if we use compare mode then
526 # diff is calculated based on changes between versions of PR
522 # diff is calculated based on changes between versions of PR
527
523
528 source_repo = pull_request_at_ver.source_repo
524 source_repo = pull_request_at_ver.source_repo
529 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
525 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
530
526
531 target_repo = pull_request_at_ver.target_repo
527 target_repo = pull_request_at_ver.target_repo
532 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
528 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
533
529
534 if compare:
530 if compare:
535 # in compare switch the diff base to latest commit from prev version
531 # in compare switch the diff base to latest commit from prev version
536 target_ref_id = prev_pull_request_display_obj.revisions[0]
532 target_ref_id = prev_pull_request_display_obj.revisions[0]
537
533
538 # despite opening commits for bookmarks/branches/tags, we always
534 # despite opening commits for bookmarks/branches/tags, we always
539 # convert this to rev to prevent changes after bookmark or branch change
535 # convert this to rev to prevent changes after bookmark or branch change
540 c.source_ref_type = 'rev'
536 c.source_ref_type = 'rev'
541 c.source_ref = source_ref_id
537 c.source_ref = source_ref_id
542
538
543 c.target_ref_type = 'rev'
539 c.target_ref_type = 'rev'
544 c.target_ref = target_ref_id
540 c.target_ref = target_ref_id
545
541
546 c.source_repo = source_repo
542 c.source_repo = source_repo
547 c.target_repo = target_repo
543 c.target_repo = target_repo
548
544
549 c.commit_ranges = []
545 c.commit_ranges = []
550 source_commit = EmptyCommit()
546 source_commit = EmptyCommit()
551 target_commit = EmptyCommit()
547 target_commit = EmptyCommit()
552 c.missing_requirements = False
548 c.missing_requirements = False
553
549
554 source_scm = source_repo.scm_instance()
550 source_scm = source_repo.scm_instance()
555 target_scm = target_repo.scm_instance()
551 target_scm = target_repo.scm_instance()
556
552
557 shadow_scm = None
553 shadow_scm = None
558 try:
554 try:
559 shadow_scm = pull_request_latest.get_shadow_repo()
555 shadow_scm = pull_request_latest.get_shadow_repo()
560 except Exception:
556 except Exception:
561 log.debug('Failed to get shadow repo', exc_info=True)
557 log.debug('Failed to get shadow repo', exc_info=True)
562 # try first the existing source_repo, and then shadow
558 # try first the existing source_repo, and then shadow
563 # repo if we can obtain one
559 # repo if we can obtain one
564 commits_source_repo = source_scm
560 commits_source_repo = source_scm
565 if shadow_scm:
561 if shadow_scm:
566 commits_source_repo = shadow_scm
562 commits_source_repo = shadow_scm
567
563
568 c.commits_source_repo = commits_source_repo
564 c.commits_source_repo = commits_source_repo
569 c.ancestor = None # set it to None, to hide it from PR view
565 c.ancestor = None # set it to None, to hide it from PR view
570
566
571 # empty version means latest, so we keep this to prevent
567 # empty version means latest, so we keep this to prevent
572 # double caching
568 # double caching
573 version_normalized = version or PullRequest.LATEST_VER
569 version_normalized = version or PullRequest.LATEST_VER
574 from_version_normalized = from_version or PullRequest.LATEST_VER
570 from_version_normalized = from_version or PullRequest.LATEST_VER
575
571
576 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
572 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
577 cache_file_path = diff_cache_exist(
573 cache_file_path = diff_cache_exist(
578 cache_path, 'pull_request', pull_request_id, version_normalized,
574 cache_path, 'pull_request', pull_request_id, version_normalized,
579 from_version_normalized, source_ref_id, target_ref_id,
575 from_version_normalized, source_ref_id, target_ref_id,
580 hide_whitespace_changes, diff_context, c.fulldiff)
576 hide_whitespace_changes, diff_context, c.fulldiff)
581
577
582 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
578 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
583 force_recache = self.get_recache_flag()
579 force_recache = self.get_recache_flag()
584
580
585 cached_diff = None
581 cached_diff = None
586 if caching_enabled:
582 if caching_enabled:
587 cached_diff = load_cached_diff(cache_file_path)
583 cached_diff = load_cached_diff(cache_file_path)
588
584
589 has_proper_commit_cache = (
585 has_proper_commit_cache = (
590 cached_diff and cached_diff.get('commits')
586 cached_diff and cached_diff.get('commits')
591 and len(cached_diff.get('commits', [])) == 5
587 and len(cached_diff.get('commits', [])) == 5
592 and cached_diff.get('commits')[0]
588 and cached_diff.get('commits')[0]
593 and cached_diff.get('commits')[3])
589 and cached_diff.get('commits')[3])
594
590
595 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
591 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
596 diff_commit_cache = \
592 diff_commit_cache = \
597 (ancestor_commit, commit_cache, missing_requirements,
593 (ancestor_commit, commit_cache, missing_requirements,
598 source_commit, target_commit) = cached_diff['commits']
594 source_commit, target_commit) = cached_diff['commits']
599 else:
595 else:
600 # NOTE(marcink): we reach potentially unreachable errors when a PR has
596 # NOTE(marcink): we reach potentially unreachable errors when a PR has
601 # merge errors resulting in potentially hidden commits in the shadow repo.
597 # merge errors resulting in potentially hidden commits in the shadow repo.
602 maybe_unreachable = _merge_check.MERGE_CHECK in _merge_check.error_details \
598 maybe_unreachable = _merge_check.MERGE_CHECK in _merge_check.error_details \
603 and _merge_check.merge_response
599 and _merge_check.merge_response
604 maybe_unreachable = maybe_unreachable \
600 maybe_unreachable = maybe_unreachable \
605 and _merge_check.merge_response.metadata.get('unresolved_files')
601 and _merge_check.merge_response.metadata.get('unresolved_files')
606 log.debug("Using unreachable commits due to MERGE_CHECK in merge simulation")
602 log.debug("Using unreachable commits due to MERGE_CHECK in merge simulation")
607 diff_commit_cache = \
603 diff_commit_cache = \
608 (ancestor_commit, commit_cache, missing_requirements,
604 (ancestor_commit, commit_cache, missing_requirements,
609 source_commit, target_commit) = self.get_commits(
605 source_commit, target_commit) = self.get_commits(
610 commits_source_repo,
606 commits_source_repo,
611 pull_request_at_ver,
607 pull_request_at_ver,
612 source_commit,
608 source_commit,
613 source_ref_id,
609 source_ref_id,
614 source_scm,
610 source_scm,
615 target_commit,
611 target_commit,
616 target_ref_id,
612 target_ref_id,
617 target_scm,
613 target_scm,
618 maybe_unreachable=maybe_unreachable)
614 maybe_unreachable=maybe_unreachable)
619
615
620 # register our commit range
616 # register our commit range
621 for comm in commit_cache.values():
617 for comm in commit_cache.values():
622 c.commit_ranges.append(comm)
618 c.commit_ranges.append(comm)
623
619
624 c.missing_requirements = missing_requirements
620 c.missing_requirements = missing_requirements
625 c.ancestor_commit = ancestor_commit
621 c.ancestor_commit = ancestor_commit
626 c.statuses = source_repo.statuses(
622 c.statuses = source_repo.statuses(
627 [x.raw_id for x in c.commit_ranges])
623 [x.raw_id for x in c.commit_ranges])
628
624
629 # auto collapse if we have more than limit
625 # auto collapse if we have more than limit
630 collapse_limit = diffs.DiffProcessor._collapse_commits_over
626 collapse_limit = diffs.DiffProcessor._collapse_commits_over
631 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
627 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
632 c.compare_mode = compare
628 c.compare_mode = compare
633
629
634 # diff_limit is the old behavior, will cut off the whole diff
630 # diff_limit is the old behavior, will cut off the whole diff
635 # if the limit is applied otherwise will just hide the
631 # if the limit is applied otherwise will just hide the
636 # big files from the front-end
632 # big files from the front-end
637 diff_limit = c.visual.cut_off_limit_diff
633 diff_limit = c.visual.cut_off_limit_diff
638 file_limit = c.visual.cut_off_limit_file
634 file_limit = c.visual.cut_off_limit_file
639
635
640 c.missing_commits = False
636 c.missing_commits = False
641 if (c.missing_requirements
637 if (c.missing_requirements
642 or isinstance(source_commit, EmptyCommit)
638 or isinstance(source_commit, EmptyCommit)
643 or source_commit == target_commit):
639 or source_commit == target_commit):
644
640
645 c.missing_commits = True
641 c.missing_commits = True
646 else:
642 else:
647 c.inline_comments = display_inline_comments
643 c.inline_comments = display_inline_comments
648
644
649 use_ancestor = True
645 use_ancestor = True
650 if from_version_normalized != version_normalized:
646 if from_version_normalized != version_normalized:
651 use_ancestor = False
647 use_ancestor = False
652
648
653 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
649 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
654 if not force_recache and has_proper_diff_cache:
650 if not force_recache and has_proper_diff_cache:
655 c.diffset = cached_diff['diff']
651 c.diffset = cached_diff['diff']
656 else:
652 else:
657 try:
653 try:
658 c.diffset = self._get_diffset(
654 c.diffset = self._get_diffset(
659 c.source_repo.repo_name, commits_source_repo,
655 c.source_repo.repo_name, commits_source_repo,
660 c.ancestor_commit,
656 c.ancestor_commit,
661 source_ref_id, target_ref_id,
657 source_ref_id, target_ref_id,
662 target_commit, source_commit,
658 target_commit, source_commit,
663 diff_limit, file_limit, c.fulldiff,
659 diff_limit, file_limit, c.fulldiff,
664 hide_whitespace_changes, diff_context,
660 hide_whitespace_changes, diff_context,
665 use_ancestor=use_ancestor
661 use_ancestor=use_ancestor
666 )
662 )
667
663
668 # save cached diff
664 # save cached diff
669 if caching_enabled:
665 if caching_enabled:
670 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
666 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
671 except CommitDoesNotExistError:
667 except CommitDoesNotExistError:
672 log.exception('Failed to generate diffset')
668 log.exception('Failed to generate diffset')
673 c.missing_commits = True
669 c.missing_commits = True
674
670
675 if not c.missing_commits:
671 if not c.missing_commits:
676
672
677 c.limited_diff = c.diffset.limited_diff
673 c.limited_diff = c.diffset.limited_diff
678
674
679 # calculate removed files that are bound to comments
675 # calculate removed files that are bound to comments
680 comment_deleted_files = [
676 comment_deleted_files = [
681 fname for fname in display_inline_comments
677 fname for fname in display_inline_comments
682 if fname not in c.diffset.file_stats]
678 if fname not in c.diffset.file_stats]
683
679
684 c.deleted_files_comments = collections.defaultdict(dict)
680 c.deleted_files_comments = collections.defaultdict(dict)
685 for fname, per_line_comments in display_inline_comments.items():
681 for fname, per_line_comments in display_inline_comments.items():
686 if fname in comment_deleted_files:
682 if fname in comment_deleted_files:
687 c.deleted_files_comments[fname]['stats'] = 0
683 c.deleted_files_comments[fname]['stats'] = 0
688 c.deleted_files_comments[fname]['comments'] = list()
684 c.deleted_files_comments[fname]['comments'] = list()
689 for lno, comments in per_line_comments.items():
685 for lno, comments in per_line_comments.items():
690 c.deleted_files_comments[fname]['comments'].extend(comments)
686 c.deleted_files_comments[fname]['comments'].extend(comments)
691
687
692 # maybe calculate the range diff
688 # maybe calculate the range diff
693 if c.range_diff_on:
689 if c.range_diff_on:
694 # TODO(marcink): set whitespace/context
690 # TODO(marcink): set whitespace/context
695 context_lcl = 3
691 context_lcl = 3
696 ign_whitespace_lcl = False
692 ign_whitespace_lcl = False
697
693
698 for commit in c.commit_ranges:
694 for commit in c.commit_ranges:
699 commit2 = commit
695 commit2 = commit
700 commit1 = commit.first_parent
696 commit1 = commit.first_parent
701
697
702 range_diff_cache_file_path = diff_cache_exist(
698 range_diff_cache_file_path = diff_cache_exist(
703 cache_path, 'diff', commit.raw_id,
699 cache_path, 'diff', commit.raw_id,
704 ign_whitespace_lcl, context_lcl, c.fulldiff)
700 ign_whitespace_lcl, context_lcl, c.fulldiff)
705
701
706 cached_diff = None
702 cached_diff = None
707 if caching_enabled:
703 if caching_enabled:
708 cached_diff = load_cached_diff(range_diff_cache_file_path)
704 cached_diff = load_cached_diff(range_diff_cache_file_path)
709
705
710 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
706 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
711 if not force_recache and has_proper_diff_cache:
707 if not force_recache and has_proper_diff_cache:
712 diffset = cached_diff['diff']
708 diffset = cached_diff['diff']
713 else:
709 else:
714 diffset = self._get_range_diffset(
710 diffset = self._get_range_diffset(
715 commits_source_repo, source_repo,
711 commits_source_repo, source_repo,
716 commit1, commit2, diff_limit, file_limit,
712 commit1, commit2, diff_limit, file_limit,
717 c.fulldiff, ign_whitespace_lcl, context_lcl
713 c.fulldiff, ign_whitespace_lcl, context_lcl
718 )
714 )
719
715
720 # save cached diff
716 # save cached diff
721 if caching_enabled:
717 if caching_enabled:
722 cache_diff(range_diff_cache_file_path, diffset, None)
718 cache_diff(range_diff_cache_file_path, diffset, None)
723
719
724 c.changes[commit.raw_id] = diffset
720 c.changes[commit.raw_id] = diffset
725
721
726 # this is a hack to properly display links, when creating PR, the
722 # this is a hack to properly display links, when creating PR, the
727 # compare view and others uses different notation, and
723 # compare view and others uses different notation, and
728 # compare_commits.mako renders links based on the target_repo.
724 # compare_commits.mako renders links based on the target_repo.
729 # We need to swap that here to generate it properly on the html side
725 # We need to swap that here to generate it properly on the html side
730 c.target_repo = c.source_repo
726 c.target_repo = c.source_repo
731
727
732 c.commit_statuses = ChangesetStatus.STATUSES
728 c.commit_statuses = ChangesetStatus.STATUSES
733
729
734 c.show_version_changes = not pr_closed
730 c.show_version_changes = not pr_closed
735 if c.show_version_changes:
731 if c.show_version_changes:
736 cur_obj = pull_request_at_ver
732 cur_obj = pull_request_at_ver
737 prev_obj = prev_pull_request_at_ver
733 prev_obj = prev_pull_request_at_ver
738
734
739 old_commit_ids = prev_obj.revisions
735 old_commit_ids = prev_obj.revisions
740 new_commit_ids = cur_obj.revisions
736 new_commit_ids = cur_obj.revisions
741 commit_changes = PullRequestModel()._calculate_commit_id_changes(
737 commit_changes = PullRequestModel()._calculate_commit_id_changes(
742 old_commit_ids, new_commit_ids)
738 old_commit_ids, new_commit_ids)
743 c.commit_changes_summary = commit_changes
739 c.commit_changes_summary = commit_changes
744
740
745 # calculate the diff for commits between versions
741 # calculate the diff for commits between versions
746 c.commit_changes = []
742 c.commit_changes = []
747
743
748 def mark(cs, fw):
744 def mark(cs, fw):
749 return list(h.itertools.izip_longest([], cs, fillvalue=fw))
745 return list(h.itertools.izip_longest([], cs, fillvalue=fw))
750
746
751 for c_type, raw_id in mark(commit_changes.added, 'a') \
747 for c_type, raw_id in mark(commit_changes.added, 'a') \
752 + mark(commit_changes.removed, 'r') \
748 + mark(commit_changes.removed, 'r') \
753 + mark(commit_changes.common, 'c'):
749 + mark(commit_changes.common, 'c'):
754
750
755 if raw_id in commit_cache:
751 if raw_id in commit_cache:
756 commit = commit_cache[raw_id]
752 commit = commit_cache[raw_id]
757 else:
753 else:
758 try:
754 try:
759 commit = commits_source_repo.get_commit(raw_id)
755 commit = commits_source_repo.get_commit(raw_id)
760 except CommitDoesNotExistError:
756 except CommitDoesNotExistError:
761 # in case we fail extracting still use "dummy" commit
757 # in case we fail extracting still use "dummy" commit
762 # for display in commit diff
758 # for display in commit diff
763 commit = h.AttributeDict(
759 commit = h.AttributeDict(
764 {'raw_id': raw_id,
760 {'raw_id': raw_id,
765 'message': 'EMPTY or MISSING COMMIT'})
761 'message': 'EMPTY or MISSING COMMIT'})
766 c.commit_changes.append([c_type, commit])
762 c.commit_changes.append([c_type, commit])
767
763
768 # current user review statuses for each version
764 # current user review statuses for each version
769 c.review_versions = {}
765 c.review_versions = {}
770 is_reviewer = PullRequestModel().is_user_reviewer(
766 is_reviewer = PullRequestModel().is_user_reviewer(
771 pull_request, self._rhodecode_user)
767 pull_request, self._rhodecode_user)
772 if is_reviewer:
768 if is_reviewer:
773 for co in general_comments:
769 for co in general_comments:
774 if co.author.user_id == self._rhodecode_user.user_id:
770 if co.author.user_id == self._rhodecode_user.user_id:
775 status = co.status_change
771 status = co.status_change
776 if status:
772 if status:
777 _ver_pr = status[0].comment.pull_request_version_id
773 _ver_pr = status[0].comment.pull_request_version_id
778 c.review_versions[_ver_pr] = status[0]
774 c.review_versions[_ver_pr] = status[0]
779
775
780 return self._get_template_context(c)
776 return self._get_template_context(c)
781
777
782 def get_commits(
778 def get_commits(
783 self, commits_source_repo, pull_request_at_ver, source_commit,
779 self, commits_source_repo, pull_request_at_ver, source_commit,
784 source_ref_id, source_scm, target_commit, target_ref_id, target_scm,
780 source_ref_id, source_scm, target_commit, target_ref_id, target_scm,
785 maybe_unreachable=False):
781 maybe_unreachable=False):
786
782
787 commit_cache = collections.OrderedDict()
783 commit_cache = collections.OrderedDict()
788 missing_requirements = False
784 missing_requirements = False
789
785
790 try:
786 try:
791 pre_load = ["author", "date", "message", "branch", "parents"]
787 pre_load = ["author", "date", "message", "branch", "parents"]
792
788
793 pull_request_commits = pull_request_at_ver.revisions
789 pull_request_commits = pull_request_at_ver.revisions
794 log.debug('Loading %s commits from %s',
790 log.debug('Loading %s commits from %s',
795 len(pull_request_commits), commits_source_repo)
791 len(pull_request_commits), commits_source_repo)
796
792
797 for rev in pull_request_commits:
793 for rev in pull_request_commits:
798 comm = commits_source_repo.get_commit(commit_id=rev, pre_load=pre_load,
794 comm = commits_source_repo.get_commit(commit_id=rev, pre_load=pre_load,
799 maybe_unreachable=maybe_unreachable)
795 maybe_unreachable=maybe_unreachable)
800 commit_cache[comm.raw_id] = comm
796 commit_cache[comm.raw_id] = comm
801
797
802 # Order here matters, we first need to get target, and then
798 # Order here matters, we first need to get target, and then
803 # the source
799 # the source
804 target_commit = commits_source_repo.get_commit(
800 target_commit = commits_source_repo.get_commit(
805 commit_id=safe_str(target_ref_id))
801 commit_id=safe_str(target_ref_id))
806
802
807 source_commit = commits_source_repo.get_commit(
803 source_commit = commits_source_repo.get_commit(
808 commit_id=safe_str(source_ref_id), maybe_unreachable=True)
804 commit_id=safe_str(source_ref_id), maybe_unreachable=True)
809 except CommitDoesNotExistError:
805 except CommitDoesNotExistError:
810 log.warning('Failed to get commit from `{}` repo'.format(
806 log.warning('Failed to get commit from `{}` repo'.format(
811 commits_source_repo), exc_info=True)
807 commits_source_repo), exc_info=True)
812 except RepositoryRequirementError:
808 except RepositoryRequirementError:
813 log.warning('Failed to get all required data from repo', exc_info=True)
809 log.warning('Failed to get all required data from repo', exc_info=True)
814 missing_requirements = True
810 missing_requirements = True
815
811
816 pr_ancestor_id = pull_request_at_ver.common_ancestor_id
812 pr_ancestor_id = pull_request_at_ver.common_ancestor_id
817
813
818 try:
814 try:
819 ancestor_commit = source_scm.get_commit(pr_ancestor_id)
815 ancestor_commit = source_scm.get_commit(pr_ancestor_id)
820 except Exception:
816 except Exception:
821 ancestor_commit = None
817 ancestor_commit = None
822
818
823 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
819 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
824
820
825 def assure_not_empty_repo(self):
821 def assure_not_empty_repo(self):
826 _ = self.request.translate
822 _ = self.request.translate
827
823
828 try:
824 try:
829 self.db_repo.scm_instance().get_commit()
825 self.db_repo.scm_instance().get_commit()
830 except EmptyRepositoryError:
826 except EmptyRepositoryError:
831 h.flash(h.literal(_('There are no commits yet')),
827 h.flash(h.literal(_('There are no commits yet')),
832 category='warning')
828 category='warning')
833 raise HTTPFound(
829 raise HTTPFound(
834 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
830 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
835
831
836 @LoginRequired()
832 @LoginRequired()
837 @NotAnonymous()
833 @NotAnonymous()
838 @HasRepoPermissionAnyDecorator(
834 @HasRepoPermissionAnyDecorator(
839 'repository.read', 'repository.write', 'repository.admin')
835 'repository.read', 'repository.write', 'repository.admin')
840 @view_config(
836 @view_config(
841 route_name='pullrequest_new', request_method='GET',
837 route_name='pullrequest_new', request_method='GET',
842 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
838 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
843 def pull_request_new(self):
839 def pull_request_new(self):
844 _ = self.request.translate
840 _ = self.request.translate
845 c = self.load_default_context()
841 c = self.load_default_context()
846
842
847 self.assure_not_empty_repo()
843 self.assure_not_empty_repo()
848 source_repo = self.db_repo
844 source_repo = self.db_repo
849
845
850 commit_id = self.request.GET.get('commit')
846 commit_id = self.request.GET.get('commit')
851 branch_ref = self.request.GET.get('branch')
847 branch_ref = self.request.GET.get('branch')
852 bookmark_ref = self.request.GET.get('bookmark')
848 bookmark_ref = self.request.GET.get('bookmark')
853
849
854 try:
850 try:
855 source_repo_data = PullRequestModel().generate_repo_data(
851 source_repo_data = PullRequestModel().generate_repo_data(
856 source_repo, commit_id=commit_id,
852 source_repo, commit_id=commit_id,
857 branch=branch_ref, bookmark=bookmark_ref,
853 branch=branch_ref, bookmark=bookmark_ref,
858 translator=self.request.translate)
854 translator=self.request.translate)
859 except CommitDoesNotExistError as e:
855 except CommitDoesNotExistError as e:
860 log.exception(e)
856 log.exception(e)
861 h.flash(_('Commit does not exist'), 'error')
857 h.flash(_('Commit does not exist'), 'error')
862 raise HTTPFound(
858 raise HTTPFound(
863 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
859 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
864
860
865 default_target_repo = source_repo
861 default_target_repo = source_repo
866
862
867 if source_repo.parent and c.has_origin_repo_read_perm:
863 if source_repo.parent and c.has_origin_repo_read_perm:
868 parent_vcs_obj = source_repo.parent.scm_instance()
864 parent_vcs_obj = source_repo.parent.scm_instance()
869 if parent_vcs_obj and not parent_vcs_obj.is_empty():
865 if parent_vcs_obj and not parent_vcs_obj.is_empty():
870 # change default if we have a parent repo
866 # change default if we have a parent repo
871 default_target_repo = source_repo.parent
867 default_target_repo = source_repo.parent
872
868
873 target_repo_data = PullRequestModel().generate_repo_data(
869 target_repo_data = PullRequestModel().generate_repo_data(
874 default_target_repo, translator=self.request.translate)
870 default_target_repo, translator=self.request.translate)
875
871
876 selected_source_ref = source_repo_data['refs']['selected_ref']
872 selected_source_ref = source_repo_data['refs']['selected_ref']
877 title_source_ref = ''
873 title_source_ref = ''
878 if selected_source_ref:
874 if selected_source_ref:
879 title_source_ref = selected_source_ref.split(':', 2)[1]
875 title_source_ref = selected_source_ref.split(':', 2)[1]
880 c.default_title = PullRequestModel().generate_pullrequest_title(
876 c.default_title = PullRequestModel().generate_pullrequest_title(
881 source=source_repo.repo_name,
877 source=source_repo.repo_name,
882 source_ref=title_source_ref,
878 source_ref=title_source_ref,
883 target=default_target_repo.repo_name
879 target=default_target_repo.repo_name
884 )
880 )
885
881
886 c.default_repo_data = {
882 c.default_repo_data = {
887 'source_repo_name': source_repo.repo_name,
883 'source_repo_name': source_repo.repo_name,
888 'source_refs_json': json.dumps(source_repo_data),
884 'source_refs_json': json.dumps(source_repo_data),
889 'target_repo_name': default_target_repo.repo_name,
885 'target_repo_name': default_target_repo.repo_name,
890 'target_refs_json': json.dumps(target_repo_data),
886 'target_refs_json': json.dumps(target_repo_data),
891 }
887 }
892 c.default_source_ref = selected_source_ref
888 c.default_source_ref = selected_source_ref
893
889
894 return self._get_template_context(c)
890 return self._get_template_context(c)
895
891
896 @LoginRequired()
892 @LoginRequired()
897 @NotAnonymous()
893 @NotAnonymous()
898 @HasRepoPermissionAnyDecorator(
894 @HasRepoPermissionAnyDecorator(
899 'repository.read', 'repository.write', 'repository.admin')
895 'repository.read', 'repository.write', 'repository.admin')
900 @view_config(
896 @view_config(
901 route_name='pullrequest_repo_refs', request_method='GET',
897 route_name='pullrequest_repo_refs', request_method='GET',
902 renderer='json_ext', xhr=True)
898 renderer='json_ext', xhr=True)
903 def pull_request_repo_refs(self):
899 def pull_request_repo_refs(self):
904 self.load_default_context()
900 self.load_default_context()
905 target_repo_name = self.request.matchdict['target_repo_name']
901 target_repo_name = self.request.matchdict['target_repo_name']
906 repo = Repository.get_by_repo_name(target_repo_name)
902 repo = Repository.get_by_repo_name(target_repo_name)
907 if not repo:
903 if not repo:
908 raise HTTPNotFound()
904 raise HTTPNotFound()
909
905
910 target_perm = HasRepoPermissionAny(
906 target_perm = HasRepoPermissionAny(
911 'repository.read', 'repository.write', 'repository.admin')(
907 'repository.read', 'repository.write', 'repository.admin')(
912 target_repo_name)
908 target_repo_name)
913 if not target_perm:
909 if not target_perm:
914 raise HTTPNotFound()
910 raise HTTPNotFound()
915
911
916 return PullRequestModel().generate_repo_data(
912 return PullRequestModel().generate_repo_data(
917 repo, translator=self.request.translate)
913 repo, translator=self.request.translate)
918
914
919 @LoginRequired()
915 @LoginRequired()
920 @NotAnonymous()
916 @NotAnonymous()
921 @HasRepoPermissionAnyDecorator(
917 @HasRepoPermissionAnyDecorator(
922 'repository.read', 'repository.write', 'repository.admin')
918 'repository.read', 'repository.write', 'repository.admin')
923 @view_config(
919 @view_config(
924 route_name='pullrequest_repo_targets', request_method='GET',
920 route_name='pullrequest_repo_targets', request_method='GET',
925 renderer='json_ext', xhr=True)
921 renderer='json_ext', xhr=True)
926 def pullrequest_repo_targets(self):
922 def pullrequest_repo_targets(self):
927 _ = self.request.translate
923 _ = self.request.translate
928 filter_query = self.request.GET.get('query')
924 filter_query = self.request.GET.get('query')
929
925
930 # get the parents
926 # get the parents
931 parent_target_repos = []
927 parent_target_repos = []
932 if self.db_repo.parent:
928 if self.db_repo.parent:
933 parents_query = Repository.query() \
929 parents_query = Repository.query() \
934 .order_by(func.length(Repository.repo_name)) \
930 .order_by(func.length(Repository.repo_name)) \
935 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
931 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
936
932
937 if filter_query:
933 if filter_query:
938 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
934 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
939 parents_query = parents_query.filter(
935 parents_query = parents_query.filter(
940 Repository.repo_name.ilike(ilike_expression))
936 Repository.repo_name.ilike(ilike_expression))
941 parents = parents_query.limit(20).all()
937 parents = parents_query.limit(20).all()
942
938
943 for parent in parents:
939 for parent in parents:
944 parent_vcs_obj = parent.scm_instance()
940 parent_vcs_obj = parent.scm_instance()
945 if parent_vcs_obj and not parent_vcs_obj.is_empty():
941 if parent_vcs_obj and not parent_vcs_obj.is_empty():
946 parent_target_repos.append(parent)
942 parent_target_repos.append(parent)
947
943
948 # get other forks, and repo itself
944 # get other forks, and repo itself
949 query = Repository.query() \
945 query = Repository.query() \
950 .order_by(func.length(Repository.repo_name)) \
946 .order_by(func.length(Repository.repo_name)) \
951 .filter(
947 .filter(
952 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
948 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
953 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
949 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
954 ) \
950 ) \
955 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
951 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
956
952
957 if filter_query:
953 if filter_query:
958 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
954 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
959 query = query.filter(Repository.repo_name.ilike(ilike_expression))
955 query = query.filter(Repository.repo_name.ilike(ilike_expression))
960
956
961 limit = max(20 - len(parent_target_repos), 5) # not less then 5
957 limit = max(20 - len(parent_target_repos), 5) # not less then 5
962 target_repos = query.limit(limit).all()
958 target_repos = query.limit(limit).all()
963
959
964 all_target_repos = target_repos + parent_target_repos
960 all_target_repos = target_repos + parent_target_repos
965
961
966 repos = []
962 repos = []
967 # This checks permissions to the repositories
963 # This checks permissions to the repositories
968 for obj in ScmModel().get_repos(all_target_repos):
964 for obj in ScmModel().get_repos(all_target_repos):
969 repos.append({
965 repos.append({
970 'id': obj['name'],
966 'id': obj['name'],
971 'text': obj['name'],
967 'text': obj['name'],
972 'type': 'repo',
968 'type': 'repo',
973 'repo_id': obj['dbrepo']['repo_id'],
969 'repo_id': obj['dbrepo']['repo_id'],
974 'repo_type': obj['dbrepo']['repo_type'],
970 'repo_type': obj['dbrepo']['repo_type'],
975 'private': obj['dbrepo']['private'],
971 'private': obj['dbrepo']['private'],
976
972
977 })
973 })
978
974
979 data = {
975 data = {
980 'more': False,
976 'more': False,
981 'results': [{
977 'results': [{
982 'text': _('Repositories'),
978 'text': _('Repositories'),
983 'children': repos
979 'children': repos
984 }] if repos else []
980 }] if repos else []
985 }
981 }
986 return data
982 return data
987
983
988 @classmethod
984 @classmethod
989 def get_comment_ids(cls, post_data):
985 def get_comment_ids(cls, post_data):
990 return filter(lambda e: e > 0, map(safe_int, aslist(post_data.get('comments'), ',')))
986 return filter(lambda e: e > 0, map(safe_int, aslist(post_data.get('comments'), ',')))
991
987
992 @LoginRequired()
988 @LoginRequired()
993 @NotAnonymous()
989 @NotAnonymous()
994 @HasRepoPermissionAnyDecorator(
990 @HasRepoPermissionAnyDecorator(
995 'repository.read', 'repository.write', 'repository.admin')
991 'repository.read', 'repository.write', 'repository.admin')
996 @view_config(
992 @view_config(
997 route_name='pullrequest_comments', request_method='POST',
993 route_name='pullrequest_comments', request_method='POST',
998 renderer='string_html', xhr=True)
994 renderer='string_html', xhr=True)
999 def pullrequest_comments(self):
995 def pullrequest_comments(self):
1000 self.load_default_context()
996 self.load_default_context()
1001
997
1002 pull_request = PullRequest.get_or_404(
998 pull_request = PullRequest.get_or_404(
1003 self.request.matchdict['pull_request_id'])
999 self.request.matchdict['pull_request_id'])
1004 pull_request_id = pull_request.pull_request_id
1000 pull_request_id = pull_request.pull_request_id
1005 version = self.request.GET.get('version')
1001 version = self.request.GET.get('version')
1006
1002
1007 _render = self.request.get_partial_renderer(
1003 _render = self.request.get_partial_renderer(
1008 'rhodecode:templates/base/sidebar.mako')
1004 'rhodecode:templates/base/sidebar.mako')
1009 c = _render.get_call_context()
1005 c = _render.get_call_context()
1010
1006
1011 (pull_request_latest,
1007 (pull_request_latest,
1012 pull_request_at_ver,
1008 pull_request_at_ver,
1013 pull_request_display_obj,
1009 pull_request_display_obj,
1014 at_version) = PullRequestModel().get_pr_version(
1010 at_version) = PullRequestModel().get_pr_version(
1015 pull_request_id, version=version)
1011 pull_request_id, version=version)
1016 versions = pull_request_display_obj.versions()
1012 versions = pull_request_display_obj.versions()
1017 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1013 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1018 c.versions = versions + [latest_ver]
1014 c.versions = versions + [latest_ver]
1019
1015
1020 c.at_version = at_version
1016 c.at_version = at_version
1021 c.at_version_num = (at_version
1017 c.at_version_num = (at_version
1022 if at_version and at_version != PullRequest.LATEST_VER
1018 if at_version and at_version != PullRequest.LATEST_VER
1023 else None)
1019 else None)
1024
1020
1025 self.register_comments_vars(c, pull_request_latest, versions, include_drafts=False)
1021 self.register_comments_vars(c, pull_request_latest, versions, include_drafts=False)
1026 all_comments = c.inline_comments_flat + c.comments
1022 all_comments = c.inline_comments_flat + c.comments
1027
1023
1028 existing_ids = self.get_comment_ids(self.request.POST)
1024 existing_ids = self.get_comment_ids(self.request.POST)
1029 return _render('comments_table', all_comments, len(all_comments),
1025 return _render('comments_table', all_comments, len(all_comments),
1030 existing_ids=existing_ids)
1026 existing_ids=existing_ids)
1031
1027
1032 @LoginRequired()
1028 @LoginRequired()
1033 @NotAnonymous()
1029 @NotAnonymous()
1034 @HasRepoPermissionAnyDecorator(
1030 @HasRepoPermissionAnyDecorator(
1035 'repository.read', 'repository.write', 'repository.admin')
1031 'repository.read', 'repository.write', 'repository.admin')
1036 @view_config(
1032 @view_config(
1037 route_name='pullrequest_todos', request_method='POST',
1033 route_name='pullrequest_todos', request_method='POST',
1038 renderer='string_html', xhr=True)
1034 renderer='string_html', xhr=True)
1039 def pullrequest_todos(self):
1035 def pullrequest_todos(self):
1040 self.load_default_context()
1036 self.load_default_context()
1041
1037
1042 pull_request = PullRequest.get_or_404(
1038 pull_request = PullRequest.get_or_404(
1043 self.request.matchdict['pull_request_id'])
1039 self.request.matchdict['pull_request_id'])
1044 pull_request_id = pull_request.pull_request_id
1040 pull_request_id = pull_request.pull_request_id
1045 version = self.request.GET.get('version')
1041 version = self.request.GET.get('version')
1046
1042
1047 _render = self.request.get_partial_renderer(
1043 _render = self.request.get_partial_renderer(
1048 'rhodecode:templates/base/sidebar.mako')
1044 'rhodecode:templates/base/sidebar.mako')
1049 c = _render.get_call_context()
1045 c = _render.get_call_context()
1050 (pull_request_latest,
1046 (pull_request_latest,
1051 pull_request_at_ver,
1047 pull_request_at_ver,
1052 pull_request_display_obj,
1048 pull_request_display_obj,
1053 at_version) = PullRequestModel().get_pr_version(
1049 at_version) = PullRequestModel().get_pr_version(
1054 pull_request_id, version=version)
1050 pull_request_id, version=version)
1055 versions = pull_request_display_obj.versions()
1051 versions = pull_request_display_obj.versions()
1056 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1052 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1057 c.versions = versions + [latest_ver]
1053 c.versions = versions + [latest_ver]
1058
1054
1059 c.at_version = at_version
1055 c.at_version = at_version
1060 c.at_version_num = (at_version
1056 c.at_version_num = (at_version
1061 if at_version and at_version != PullRequest.LATEST_VER
1057 if at_version and at_version != PullRequest.LATEST_VER
1062 else None)
1058 else None)
1063
1059
1064 c.unresolved_comments = CommentsModel() \
1060 c.unresolved_comments = CommentsModel() \
1065 .get_pull_request_unresolved_todos(pull_request, include_drafts=False)
1061 .get_pull_request_unresolved_todos(pull_request, include_drafts=False)
1066 c.resolved_comments = CommentsModel() \
1062 c.resolved_comments = CommentsModel() \
1067 .get_pull_request_resolved_todos(pull_request, include_drafts=False)
1063 .get_pull_request_resolved_todos(pull_request, include_drafts=False)
1068
1064
1069 all_comments = c.unresolved_comments + c.resolved_comments
1065 all_comments = c.unresolved_comments + c.resolved_comments
1070 existing_ids = self.get_comment_ids(self.request.POST)
1066 existing_ids = self.get_comment_ids(self.request.POST)
1071 return _render('comments_table', all_comments, len(c.unresolved_comments),
1067 return _render('comments_table', all_comments, len(c.unresolved_comments),
1072 todo_comments=True, existing_ids=existing_ids)
1068 todo_comments=True, existing_ids=existing_ids)
1073
1069
1074 @LoginRequired()
1070 @LoginRequired()
1075 @NotAnonymous()
1071 @NotAnonymous()
1076 @HasRepoPermissionAnyDecorator(
1072 @HasRepoPermissionAnyDecorator(
1077 'repository.read', 'repository.write', 'repository.admin')
1073 'repository.read', 'repository.write', 'repository.admin')
1078 @CSRFRequired()
1074 @CSRFRequired()
1079 @view_config(
1075 @view_config(
1080 route_name='pullrequest_create', request_method='POST',
1076 route_name='pullrequest_create', request_method='POST',
1081 renderer=None)
1077 renderer=None)
1082 def pull_request_create(self):
1078 def pull_request_create(self):
1083 _ = self.request.translate
1079 _ = self.request.translate
1084 self.assure_not_empty_repo()
1080 self.assure_not_empty_repo()
1085 self.load_default_context()
1081 self.load_default_context()
1086
1082
1087 controls = peppercorn.parse(self.request.POST.items())
1083 controls = peppercorn.parse(self.request.POST.items())
1088
1084
1089 try:
1085 try:
1090 form = PullRequestForm(
1086 form = PullRequestForm(
1091 self.request.translate, self.db_repo.repo_id)()
1087 self.request.translate, self.db_repo.repo_id)()
1092 _form = form.to_python(controls)
1088 _form = form.to_python(controls)
1093 except formencode.Invalid as errors:
1089 except formencode.Invalid as errors:
1094 if errors.error_dict.get('revisions'):
1090 if errors.error_dict.get('revisions'):
1095 msg = 'Revisions: %s' % errors.error_dict['revisions']
1091 msg = 'Revisions: %s' % errors.error_dict['revisions']
1096 elif errors.error_dict.get('pullrequest_title'):
1092 elif errors.error_dict.get('pullrequest_title'):
1097 msg = errors.error_dict.get('pullrequest_title')
1093 msg = errors.error_dict.get('pullrequest_title')
1098 else:
1094 else:
1099 msg = _('Error creating pull request: {}').format(errors)
1095 msg = _('Error creating pull request: {}').format(errors)
1100 log.exception(msg)
1096 log.exception(msg)
1101 h.flash(msg, 'error')
1097 h.flash(msg, 'error')
1102
1098
1103 # would rather just go back to form ...
1099 # would rather just go back to form ...
1104 raise HTTPFound(
1100 raise HTTPFound(
1105 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1101 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1106
1102
1107 source_repo = _form['source_repo']
1103 source_repo = _form['source_repo']
1108 source_ref = _form['source_ref']
1104 source_ref = _form['source_ref']
1109 target_repo = _form['target_repo']
1105 target_repo = _form['target_repo']
1110 target_ref = _form['target_ref']
1106 target_ref = _form['target_ref']
1111 commit_ids = _form['revisions'][::-1]
1107 commit_ids = _form['revisions'][::-1]
1112 common_ancestor_id = _form['common_ancestor']
1108 common_ancestor_id = _form['common_ancestor']
1113
1109
1114 # find the ancestor for this pr
1110 # find the ancestor for this pr
1115 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
1111 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
1116 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
1112 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
1117
1113
1118 if not (source_db_repo or target_db_repo):
1114 if not (source_db_repo or target_db_repo):
1119 h.flash(_('source_repo or target repo not found'), category='error')
1115 h.flash(_('source_repo or target repo not found'), category='error')
1120 raise HTTPFound(
1116 raise HTTPFound(
1121 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1117 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1122
1118
1123 # re-check permissions again here
1119 # re-check permissions again here
1124 # source_repo we must have read permissions
1120 # source_repo we must have read permissions
1125
1121
1126 source_perm = HasRepoPermissionAny(
1122 source_perm = HasRepoPermissionAny(
1127 'repository.read', 'repository.write', 'repository.admin')(
1123 'repository.read', 'repository.write', 'repository.admin')(
1128 source_db_repo.repo_name)
1124 source_db_repo.repo_name)
1129 if not source_perm:
1125 if not source_perm:
1130 msg = _('Not Enough permissions to source repo `{}`.'.format(
1126 msg = _('Not Enough permissions to source repo `{}`.'.format(
1131 source_db_repo.repo_name))
1127 source_db_repo.repo_name))
1132 h.flash(msg, category='error')
1128 h.flash(msg, category='error')
1133 # copy the args back to redirect
1129 # copy the args back to redirect
1134 org_query = self.request.GET.mixed()
1130 org_query = self.request.GET.mixed()
1135 raise HTTPFound(
1131 raise HTTPFound(
1136 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1132 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1137 _query=org_query))
1133 _query=org_query))
1138
1134
1139 # target repo we must have read permissions, and also later on
1135 # target repo we must have read permissions, and also later on
1140 # we want to check branch permissions here
1136 # we want to check branch permissions here
1141 target_perm = HasRepoPermissionAny(
1137 target_perm = HasRepoPermissionAny(
1142 'repository.read', 'repository.write', 'repository.admin')(
1138 'repository.read', 'repository.write', 'repository.admin')(
1143 target_db_repo.repo_name)
1139 target_db_repo.repo_name)
1144 if not target_perm:
1140 if not target_perm:
1145 msg = _('Not Enough permissions to target repo `{}`.'.format(
1141 msg = _('Not Enough permissions to target repo `{}`.'.format(
1146 target_db_repo.repo_name))
1142 target_db_repo.repo_name))
1147 h.flash(msg, category='error')
1143 h.flash(msg, category='error')
1148 # copy the args back to redirect
1144 # copy the args back to redirect
1149 org_query = self.request.GET.mixed()
1145 org_query = self.request.GET.mixed()
1150 raise HTTPFound(
1146 raise HTTPFound(
1151 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1147 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1152 _query=org_query))
1148 _query=org_query))
1153
1149
1154 source_scm = source_db_repo.scm_instance()
1150 source_scm = source_db_repo.scm_instance()
1155 target_scm = target_db_repo.scm_instance()
1151 target_scm = target_db_repo.scm_instance()
1156
1152
1157 source_ref_obj = unicode_to_reference(source_ref)
1153 source_ref_obj = unicode_to_reference(source_ref)
1158 target_ref_obj = unicode_to_reference(target_ref)
1154 target_ref_obj = unicode_to_reference(target_ref)
1159
1155
1160 source_commit = source_scm.get_commit(source_ref_obj.commit_id)
1156 source_commit = source_scm.get_commit(source_ref_obj.commit_id)
1161 target_commit = target_scm.get_commit(target_ref_obj.commit_id)
1157 target_commit = target_scm.get_commit(target_ref_obj.commit_id)
1162
1158
1163 ancestor = source_scm.get_common_ancestor(
1159 ancestor = source_scm.get_common_ancestor(
1164 source_commit.raw_id, target_commit.raw_id, target_scm)
1160 source_commit.raw_id, target_commit.raw_id, target_scm)
1165
1161
1166 # recalculate target ref based on ancestor
1162 # recalculate target ref based on ancestor
1167 target_ref = ':'.join((target_ref_obj.type, target_ref_obj.name, ancestor))
1163 target_ref = ':'.join((target_ref_obj.type, target_ref_obj.name, ancestor))
1168
1164
1169 get_default_reviewers_data, validate_default_reviewers, validate_observers = \
1165 get_default_reviewers_data, validate_default_reviewers, validate_observers = \
1170 PullRequestModel().get_reviewer_functions()
1166 PullRequestModel().get_reviewer_functions()
1171
1167
1172 # recalculate reviewers logic, to make sure we can validate this
1168 # recalculate reviewers logic, to make sure we can validate this
1173 reviewer_rules = get_default_reviewers_data(
1169 reviewer_rules = get_default_reviewers_data(
1174 self._rhodecode_db_user,
1170 self._rhodecode_db_user,
1175 source_db_repo,
1171 source_db_repo,
1176 source_ref_obj,
1172 source_ref_obj,
1177 target_db_repo,
1173 target_db_repo,
1178 target_ref_obj,
1174 target_ref_obj,
1179 include_diff_info=False)
1175 include_diff_info=False)
1180
1176
1181 reviewers = validate_default_reviewers(_form['review_members'], reviewer_rules)
1177 reviewers = validate_default_reviewers(_form['review_members'], reviewer_rules)
1182 observers = validate_observers(_form['observer_members'], reviewer_rules)
1178 observers = validate_observers(_form['observer_members'], reviewer_rules)
1183
1179
1184 pullrequest_title = _form['pullrequest_title']
1180 pullrequest_title = _form['pullrequest_title']
1185 title_source_ref = source_ref_obj.name
1181 title_source_ref = source_ref_obj.name
1186 if not pullrequest_title:
1182 if not pullrequest_title:
1187 pullrequest_title = PullRequestModel().generate_pullrequest_title(
1183 pullrequest_title = PullRequestModel().generate_pullrequest_title(
1188 source=source_repo,
1184 source=source_repo,
1189 source_ref=title_source_ref,
1185 source_ref=title_source_ref,
1190 target=target_repo
1186 target=target_repo
1191 )
1187 )
1192
1188
1193 description = _form['pullrequest_desc']
1189 description = _form['pullrequest_desc']
1194 description_renderer = _form['description_renderer']
1190 description_renderer = _form['description_renderer']
1195
1191
1196 try:
1192 try:
1197 pull_request = PullRequestModel().create(
1193 pull_request = PullRequestModel().create(
1198 created_by=self._rhodecode_user.user_id,
1194 created_by=self._rhodecode_user.user_id,
1199 source_repo=source_repo,
1195 source_repo=source_repo,
1200 source_ref=source_ref,
1196 source_ref=source_ref,
1201 target_repo=target_repo,
1197 target_repo=target_repo,
1202 target_ref=target_ref,
1198 target_ref=target_ref,
1203 revisions=commit_ids,
1199 revisions=commit_ids,
1204 common_ancestor_id=common_ancestor_id,
1200 common_ancestor_id=common_ancestor_id,
1205 reviewers=reviewers,
1201 reviewers=reviewers,
1206 observers=observers,
1202 observers=observers,
1207 title=pullrequest_title,
1203 title=pullrequest_title,
1208 description=description,
1204 description=description,
1209 description_renderer=description_renderer,
1205 description_renderer=description_renderer,
1210 reviewer_data=reviewer_rules,
1206 reviewer_data=reviewer_rules,
1211 auth_user=self._rhodecode_user
1207 auth_user=self._rhodecode_user
1212 )
1208 )
1213 Session().commit()
1209 Session().commit()
1214
1210
1215 h.flash(_('Successfully opened new pull request'),
1211 h.flash(_('Successfully opened new pull request'),
1216 category='success')
1212 category='success')
1217 except Exception:
1213 except Exception:
1218 msg = _('Error occurred during creation of this pull request.')
1214 msg = _('Error occurred during creation of this pull request.')
1219 log.exception(msg)
1215 log.exception(msg)
1220 h.flash(msg, category='error')
1216 h.flash(msg, category='error')
1221
1217
1222 # copy the args back to redirect
1218 # copy the args back to redirect
1223 org_query = self.request.GET.mixed()
1219 org_query = self.request.GET.mixed()
1224 raise HTTPFound(
1220 raise HTTPFound(
1225 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1221 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1226 _query=org_query))
1222 _query=org_query))
1227
1223
1228 raise HTTPFound(
1224 raise HTTPFound(
1229 h.route_path('pullrequest_show', repo_name=target_repo,
1225 h.route_path('pullrequest_show', repo_name=target_repo,
1230 pull_request_id=pull_request.pull_request_id))
1226 pull_request_id=pull_request.pull_request_id))
1231
1227
1232 @LoginRequired()
1228 @LoginRequired()
1233 @NotAnonymous()
1229 @NotAnonymous()
1234 @HasRepoPermissionAnyDecorator(
1230 @HasRepoPermissionAnyDecorator(
1235 'repository.read', 'repository.write', 'repository.admin')
1231 'repository.read', 'repository.write', 'repository.admin')
1236 @CSRFRequired()
1232 @CSRFRequired()
1237 @view_config(
1233 @view_config(
1238 route_name='pullrequest_update', request_method='POST',
1234 route_name='pullrequest_update', request_method='POST',
1239 renderer='json_ext')
1235 renderer='json_ext')
1240 def pull_request_update(self):
1236 def pull_request_update(self):
1241 pull_request = PullRequest.get_or_404(
1237 pull_request = PullRequest.get_or_404(
1242 self.request.matchdict['pull_request_id'])
1238 self.request.matchdict['pull_request_id'])
1243 _ = self.request.translate
1239 _ = self.request.translate
1244
1240
1245 c = self.load_default_context()
1241 c = self.load_default_context()
1246 redirect_url = None
1242 redirect_url = None
1247
1243
1248 if pull_request.is_closed():
1244 if pull_request.is_closed():
1249 log.debug('update: forbidden because pull request is closed')
1245 log.debug('update: forbidden because pull request is closed')
1250 msg = _(u'Cannot update closed pull requests.')
1246 msg = _(u'Cannot update closed pull requests.')
1251 h.flash(msg, category='error')
1247 h.flash(msg, category='error')
1252 return {'response': True,
1248 return {'response': True,
1253 'redirect_url': redirect_url}
1249 'redirect_url': redirect_url}
1254
1250
1255 is_state_changing = pull_request.is_state_changing()
1251 is_state_changing = pull_request.is_state_changing()
1256 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
1252 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
1257
1253
1258 # only owner or admin can update it
1254 # only owner or admin can update it
1259 allowed_to_update = PullRequestModel().check_user_update(
1255 allowed_to_update = PullRequestModel().check_user_update(
1260 pull_request, self._rhodecode_user)
1256 pull_request, self._rhodecode_user)
1261
1257
1262 if allowed_to_update:
1258 if allowed_to_update:
1263 controls = peppercorn.parse(self.request.POST.items())
1259 controls = peppercorn.parse(self.request.POST.items())
1264 force_refresh = str2bool(self.request.POST.get('force_refresh'))
1260 force_refresh = str2bool(self.request.POST.get('force_refresh'))
1265
1261
1266 if 'review_members' in controls:
1262 if 'review_members' in controls:
1267 self._update_reviewers(
1263 self._update_reviewers(
1268 c,
1264 c,
1269 pull_request, controls['review_members'],
1265 pull_request, controls['review_members'],
1270 pull_request.reviewer_data,
1266 pull_request.reviewer_data,
1271 PullRequestReviewers.ROLE_REVIEWER)
1267 PullRequestReviewers.ROLE_REVIEWER)
1272 elif 'observer_members' in controls:
1268 elif 'observer_members' in controls:
1273 self._update_reviewers(
1269 self._update_reviewers(
1274 c,
1270 c,
1275 pull_request, controls['observer_members'],
1271 pull_request, controls['observer_members'],
1276 pull_request.reviewer_data,
1272 pull_request.reviewer_data,
1277 PullRequestReviewers.ROLE_OBSERVER)
1273 PullRequestReviewers.ROLE_OBSERVER)
1278 elif str2bool(self.request.POST.get('update_commits', 'false')):
1274 elif str2bool(self.request.POST.get('update_commits', 'false')):
1279 if is_state_changing:
1275 if is_state_changing:
1280 log.debug('commits update: forbidden because pull request is in state %s',
1276 log.debug('commits update: forbidden because pull request is in state %s',
1281 pull_request.pull_request_state)
1277 pull_request.pull_request_state)
1282 msg = _(u'Cannot update pull requests commits in state other than `{}`. '
1278 msg = _(u'Cannot update pull requests commits in state other than `{}`. '
1283 u'Current state is: `{}`').format(
1279 u'Current state is: `{}`').format(
1284 PullRequest.STATE_CREATED, pull_request.pull_request_state)
1280 PullRequest.STATE_CREATED, pull_request.pull_request_state)
1285 h.flash(msg, category='error')
1281 h.flash(msg, category='error')
1286 return {'response': True,
1282 return {'response': True,
1287 'redirect_url': redirect_url}
1283 'redirect_url': redirect_url}
1288
1284
1289 self._update_commits(c, pull_request)
1285 self._update_commits(c, pull_request)
1290 if force_refresh:
1286 if force_refresh:
1291 redirect_url = h.route_path(
1287 redirect_url = h.route_path(
1292 'pullrequest_show', repo_name=self.db_repo_name,
1288 'pullrequest_show', repo_name=self.db_repo_name,
1293 pull_request_id=pull_request.pull_request_id,
1289 pull_request_id=pull_request.pull_request_id,
1294 _query={"force_refresh": 1})
1290 _query={"force_refresh": 1})
1295 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1291 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1296 self._edit_pull_request(pull_request)
1292 self._edit_pull_request(pull_request)
1297 else:
1293 else:
1298 log.error('Unhandled update data.')
1294 log.error('Unhandled update data.')
1299 raise HTTPBadRequest()
1295 raise HTTPBadRequest()
1300
1296
1301 return {'response': True,
1297 return {'response': True,
1302 'redirect_url': redirect_url}
1298 'redirect_url': redirect_url}
1303 raise HTTPForbidden()
1299 raise HTTPForbidden()
1304
1300
1305 def _edit_pull_request(self, pull_request):
1301 def _edit_pull_request(self, pull_request):
1306 """
1302 """
1307 Edit title and description
1303 Edit title and description
1308 """
1304 """
1309 _ = self.request.translate
1305 _ = self.request.translate
1310
1306
1311 try:
1307 try:
1312 PullRequestModel().edit(
1308 PullRequestModel().edit(
1313 pull_request,
1309 pull_request,
1314 self.request.POST.get('title'),
1310 self.request.POST.get('title'),
1315 self.request.POST.get('description'),
1311 self.request.POST.get('description'),
1316 self.request.POST.get('description_renderer'),
1312 self.request.POST.get('description_renderer'),
1317 self._rhodecode_user)
1313 self._rhodecode_user)
1318 except ValueError:
1314 except ValueError:
1319 msg = _(u'Cannot update closed pull requests.')
1315 msg = _(u'Cannot update closed pull requests.')
1320 h.flash(msg, category='error')
1316 h.flash(msg, category='error')
1321 return
1317 return
1322 else:
1318 else:
1323 Session().commit()
1319 Session().commit()
1324
1320
1325 msg = _(u'Pull request title & description updated.')
1321 msg = _(u'Pull request title & description updated.')
1326 h.flash(msg, category='success')
1322 h.flash(msg, category='success')
1327 return
1323 return
1328
1324
1329 def _update_commits(self, c, pull_request):
1325 def _update_commits(self, c, pull_request):
1330 _ = self.request.translate
1326 _ = self.request.translate
1331
1327
1332 with pull_request.set_state(PullRequest.STATE_UPDATING):
1328 with pull_request.set_state(PullRequest.STATE_UPDATING):
1333 resp = PullRequestModel().update_commits(
1329 resp = PullRequestModel().update_commits(
1334 pull_request, self._rhodecode_db_user)
1330 pull_request, self._rhodecode_db_user)
1335
1331
1336 if resp.executed:
1332 if resp.executed:
1337
1333
1338 if resp.target_changed and resp.source_changed:
1334 if resp.target_changed and resp.source_changed:
1339 changed = 'target and source repositories'
1335 changed = 'target and source repositories'
1340 elif resp.target_changed and not resp.source_changed:
1336 elif resp.target_changed and not resp.source_changed:
1341 changed = 'target repository'
1337 changed = 'target repository'
1342 elif not resp.target_changed and resp.source_changed:
1338 elif not resp.target_changed and resp.source_changed:
1343 changed = 'source repository'
1339 changed = 'source repository'
1344 else:
1340 else:
1345 changed = 'nothing'
1341 changed = 'nothing'
1346
1342
1347 msg = _(u'Pull request updated to "{source_commit_id}" with '
1343 msg = _(u'Pull request updated to "{source_commit_id}" with '
1348 u'{count_added} added, {count_removed} removed commits. '
1344 u'{count_added} added, {count_removed} removed commits. '
1349 u'Source of changes: {change_source}.')
1345 u'Source of changes: {change_source}.')
1350 msg = msg.format(
1346 msg = msg.format(
1351 source_commit_id=pull_request.source_ref_parts.commit_id,
1347 source_commit_id=pull_request.source_ref_parts.commit_id,
1352 count_added=len(resp.changes.added),
1348 count_added=len(resp.changes.added),
1353 count_removed=len(resp.changes.removed),
1349 count_removed=len(resp.changes.removed),
1354 change_source=changed)
1350 change_source=changed)
1355 h.flash(msg, category='success')
1351 h.flash(msg, category='success')
1356 channelstream.pr_update_channelstream_push(
1352 channelstream.pr_update_channelstream_push(
1357 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1353 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1358 else:
1354 else:
1359 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1355 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1360 warning_reasons = [
1356 warning_reasons = [
1361 UpdateFailureReason.NO_CHANGE,
1357 UpdateFailureReason.NO_CHANGE,
1362 UpdateFailureReason.WRONG_REF_TYPE,
1358 UpdateFailureReason.WRONG_REF_TYPE,
1363 ]
1359 ]
1364 category = 'warning' if resp.reason in warning_reasons else 'error'
1360 category = 'warning' if resp.reason in warning_reasons else 'error'
1365 h.flash(msg, category=category)
1361 h.flash(msg, category=category)
1366
1362
1367 def _update_reviewers(self, c, pull_request, review_members, reviewer_rules, role):
1363 def _update_reviewers(self, c, pull_request, review_members, reviewer_rules, role):
1368 _ = self.request.translate
1364 _ = self.request.translate
1369
1365
1370 get_default_reviewers_data, validate_default_reviewers, validate_observers = \
1366 get_default_reviewers_data, validate_default_reviewers, validate_observers = \
1371 PullRequestModel().get_reviewer_functions()
1367 PullRequestModel().get_reviewer_functions()
1372
1368
1373 if role == PullRequestReviewers.ROLE_REVIEWER:
1369 if role == PullRequestReviewers.ROLE_REVIEWER:
1374 try:
1370 try:
1375 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1371 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1376 except ValueError as e:
1372 except ValueError as e:
1377 log.error('Reviewers Validation: {}'.format(e))
1373 log.error('Reviewers Validation: {}'.format(e))
1378 h.flash(e, category='error')
1374 h.flash(e, category='error')
1379 return
1375 return
1380
1376
1381 old_calculated_status = pull_request.calculated_review_status()
1377 old_calculated_status = pull_request.calculated_review_status()
1382 PullRequestModel().update_reviewers(
1378 PullRequestModel().update_reviewers(
1383 pull_request, reviewers, self._rhodecode_db_user)
1379 pull_request, reviewers, self._rhodecode_db_user)
1384
1380
1385 Session().commit()
1381 Session().commit()
1386
1382
1387 msg = _('Pull request reviewers updated.')
1383 msg = _('Pull request reviewers updated.')
1388 h.flash(msg, category='success')
1384 h.flash(msg, category='success')
1389 channelstream.pr_update_channelstream_push(
1385 channelstream.pr_update_channelstream_push(
1390 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1386 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1391
1387
1392 # trigger status changed if change in reviewers changes the status
1388 # trigger status changed if change in reviewers changes the status
1393 calculated_status = pull_request.calculated_review_status()
1389 calculated_status = pull_request.calculated_review_status()
1394 if old_calculated_status != calculated_status:
1390 if old_calculated_status != calculated_status:
1395 PullRequestModel().trigger_pull_request_hook(
1391 PullRequestModel().trigger_pull_request_hook(
1396 pull_request, self._rhodecode_user, 'review_status_change',
1392 pull_request, self._rhodecode_user, 'review_status_change',
1397 data={'status': calculated_status})
1393 data={'status': calculated_status})
1398
1394
1399 elif role == PullRequestReviewers.ROLE_OBSERVER:
1395 elif role == PullRequestReviewers.ROLE_OBSERVER:
1400 try:
1396 try:
1401 observers = validate_observers(review_members, reviewer_rules)
1397 observers = validate_observers(review_members, reviewer_rules)
1402 except ValueError as e:
1398 except ValueError as e:
1403 log.error('Observers Validation: {}'.format(e))
1399 log.error('Observers Validation: {}'.format(e))
1404 h.flash(e, category='error')
1400 h.flash(e, category='error')
1405 return
1401 return
1406
1402
1407 PullRequestModel().update_observers(
1403 PullRequestModel().update_observers(
1408 pull_request, observers, self._rhodecode_db_user)
1404 pull_request, observers, self._rhodecode_db_user)
1409
1405
1410 Session().commit()
1406 Session().commit()
1411 msg = _('Pull request observers updated.')
1407 msg = _('Pull request observers updated.')
1412 h.flash(msg, category='success')
1408 h.flash(msg, category='success')
1413 channelstream.pr_update_channelstream_push(
1409 channelstream.pr_update_channelstream_push(
1414 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1410 self.request, c.pr_broadcast_channel, self._rhodecode_user, msg)
1415
1411
1416 @LoginRequired()
1412 @LoginRequired()
1417 @NotAnonymous()
1413 @NotAnonymous()
1418 @HasRepoPermissionAnyDecorator(
1414 @HasRepoPermissionAnyDecorator(
1419 'repository.read', 'repository.write', 'repository.admin')
1415 'repository.read', 'repository.write', 'repository.admin')
1420 @CSRFRequired()
1416 @CSRFRequired()
1421 @view_config(
1417 @view_config(
1422 route_name='pullrequest_merge', request_method='POST',
1418 route_name='pullrequest_merge', request_method='POST',
1423 renderer='json_ext')
1419 renderer='json_ext')
1424 def pull_request_merge(self):
1420 def pull_request_merge(self):
1425 """
1421 """
1426 Merge will perform a server-side merge of the specified
1422 Merge will perform a server-side merge of the specified
1427 pull request, if the pull request is approved and mergeable.
1423 pull request, if the pull request is approved and mergeable.
1428 After successful merging, the pull request is automatically
1424 After successful merging, the pull request is automatically
1429 closed, with a relevant comment.
1425 closed, with a relevant comment.
1430 """
1426 """
1431 pull_request = PullRequest.get_or_404(
1427 pull_request = PullRequest.get_or_404(
1432 self.request.matchdict['pull_request_id'])
1428 self.request.matchdict['pull_request_id'])
1433 _ = self.request.translate
1429 _ = self.request.translate
1434
1430
1435 if pull_request.is_state_changing():
1431 if pull_request.is_state_changing():
1436 log.debug('show: forbidden because pull request is in state %s',
1432 log.debug('show: forbidden because pull request is in state %s',
1437 pull_request.pull_request_state)
1433 pull_request.pull_request_state)
1438 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1434 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1439 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1435 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1440 pull_request.pull_request_state)
1436 pull_request.pull_request_state)
1441 h.flash(msg, category='error')
1437 h.flash(msg, category='error')
1442 raise HTTPFound(
1438 raise HTTPFound(
1443 h.route_path('pullrequest_show',
1439 h.route_path('pullrequest_show',
1444 repo_name=pull_request.target_repo.repo_name,
1440 repo_name=pull_request.target_repo.repo_name,
1445 pull_request_id=pull_request.pull_request_id))
1441 pull_request_id=pull_request.pull_request_id))
1446
1442
1447 self.load_default_context()
1443 self.load_default_context()
1448
1444
1449 with pull_request.set_state(PullRequest.STATE_UPDATING):
1445 with pull_request.set_state(PullRequest.STATE_UPDATING):
1450 check = MergeCheck.validate(
1446 check = MergeCheck.validate(
1451 pull_request, auth_user=self._rhodecode_user,
1447 pull_request, auth_user=self._rhodecode_user,
1452 translator=self.request.translate)
1448 translator=self.request.translate)
1453 merge_possible = not check.failed
1449 merge_possible = not check.failed
1454
1450
1455 for err_type, error_msg in check.errors:
1451 for err_type, error_msg in check.errors:
1456 h.flash(error_msg, category=err_type)
1452 h.flash(error_msg, category=err_type)
1457
1453
1458 if merge_possible:
1454 if merge_possible:
1459 log.debug("Pre-conditions checked, trying to merge.")
1455 log.debug("Pre-conditions checked, trying to merge.")
1460 extras = vcs_operation_context(
1456 extras = vcs_operation_context(
1461 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1457 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1462 username=self._rhodecode_db_user.username, action='push',
1458 username=self._rhodecode_db_user.username, action='push',
1463 scm=pull_request.target_repo.repo_type)
1459 scm=pull_request.target_repo.repo_type)
1464 with pull_request.set_state(PullRequest.STATE_UPDATING):
1460 with pull_request.set_state(PullRequest.STATE_UPDATING):
1465 self._merge_pull_request(
1461 self._merge_pull_request(
1466 pull_request, self._rhodecode_db_user, extras)
1462 pull_request, self._rhodecode_db_user, extras)
1467 else:
1463 else:
1468 log.debug("Pre-conditions failed, NOT merging.")
1464 log.debug("Pre-conditions failed, NOT merging.")
1469
1465
1470 raise HTTPFound(
1466 raise HTTPFound(
1471 h.route_path('pullrequest_show',
1467 h.route_path('pullrequest_show',
1472 repo_name=pull_request.target_repo.repo_name,
1468 repo_name=pull_request.target_repo.repo_name,
1473 pull_request_id=pull_request.pull_request_id))
1469 pull_request_id=pull_request.pull_request_id))
1474
1470
1475 def _merge_pull_request(self, pull_request, user, extras):
1471 def _merge_pull_request(self, pull_request, user, extras):
1476 _ = self.request.translate
1472 _ = self.request.translate
1477 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1473 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1478
1474
1479 if merge_resp.executed:
1475 if merge_resp.executed:
1480 log.debug("The merge was successful, closing the pull request.")
1476 log.debug("The merge was successful, closing the pull request.")
1481 PullRequestModel().close_pull_request(
1477 PullRequestModel().close_pull_request(
1482 pull_request.pull_request_id, user)
1478 pull_request.pull_request_id, user)
1483 Session().commit()
1479 Session().commit()
1484 msg = _('Pull request was successfully merged and closed.')
1480 msg = _('Pull request was successfully merged and closed.')
1485 h.flash(msg, category='success')
1481 h.flash(msg, category='success')
1486 else:
1482 else:
1487 log.debug(
1483 log.debug(
1488 "The merge was not successful. Merge response: %s", merge_resp)
1484 "The merge was not successful. Merge response: %s", merge_resp)
1489 msg = merge_resp.merge_status_message
1485 msg = merge_resp.merge_status_message
1490 h.flash(msg, category='error')
1486 h.flash(msg, category='error')
1491
1487
1492 @LoginRequired()
1488 @LoginRequired()
1493 @NotAnonymous()
1489 @NotAnonymous()
1494 @HasRepoPermissionAnyDecorator(
1490 @HasRepoPermissionAnyDecorator(
1495 'repository.read', 'repository.write', 'repository.admin')
1491 'repository.read', 'repository.write', 'repository.admin')
1496 @CSRFRequired()
1492 @CSRFRequired()
1497 @view_config(
1493 @view_config(
1498 route_name='pullrequest_delete', request_method='POST',
1494 route_name='pullrequest_delete', request_method='POST',
1499 renderer='json_ext')
1495 renderer='json_ext')
1500 def pull_request_delete(self):
1496 def pull_request_delete(self):
1501 _ = self.request.translate
1497 _ = self.request.translate
1502
1498
1503 pull_request = PullRequest.get_or_404(
1499 pull_request = PullRequest.get_or_404(
1504 self.request.matchdict['pull_request_id'])
1500 self.request.matchdict['pull_request_id'])
1505 self.load_default_context()
1501 self.load_default_context()
1506
1502
1507 pr_closed = pull_request.is_closed()
1503 pr_closed = pull_request.is_closed()
1508 allowed_to_delete = PullRequestModel().check_user_delete(
1504 allowed_to_delete = PullRequestModel().check_user_delete(
1509 pull_request, self._rhodecode_user) and not pr_closed
1505 pull_request, self._rhodecode_user) and not pr_closed
1510
1506
1511 # only owner can delete it !
1507 # only owner can delete it !
1512 if allowed_to_delete:
1508 if allowed_to_delete:
1513 PullRequestModel().delete(pull_request, self._rhodecode_user)
1509 PullRequestModel().delete(pull_request, self._rhodecode_user)
1514 Session().commit()
1510 Session().commit()
1515 h.flash(_('Successfully deleted pull request'),
1511 h.flash(_('Successfully deleted pull request'),
1516 category='success')
1512 category='success')
1517 raise HTTPFound(h.route_path('pullrequest_show_all',
1513 raise HTTPFound(h.route_path('pullrequest_show_all',
1518 repo_name=self.db_repo_name))
1514 repo_name=self.db_repo_name))
1519
1515
1520 log.warning('user %s tried to delete pull request without access',
1516 log.warning('user %s tried to delete pull request without access',
1521 self._rhodecode_user)
1517 self._rhodecode_user)
1522 raise HTTPNotFound()
1518 raise HTTPNotFound()
1523
1519
1524 def _pull_request_comments_create(self, pull_request, comments):
1520 def _pull_request_comments_create(self, pull_request, comments):
1525 _ = self.request.translate
1521 _ = self.request.translate
1526 data = {}
1522 data = {}
1527 if not comments:
1523 if not comments:
1528 return
1524 return
1529 pull_request_id = pull_request.pull_request_id
1525 pull_request_id = pull_request.pull_request_id
1530
1526
1531 all_drafts = len([x for x in comments if str2bool(x['is_draft'])]) == len(comments)
1527 all_drafts = len([x for x in comments if str2bool(x['is_draft'])]) == len(comments)
1532
1528
1533 for entry in comments:
1529 for entry in comments:
1534 c = self.load_default_context()
1530 c = self.load_default_context()
1535 comment_type = entry['comment_type']
1531 comment_type = entry['comment_type']
1536 text = entry['text']
1532 text = entry['text']
1537 status = entry['status']
1533 status = entry['status']
1538 is_draft = str2bool(entry['is_draft'])
1534 is_draft = str2bool(entry['is_draft'])
1539 resolves_comment_id = entry['resolves_comment_id']
1535 resolves_comment_id = entry['resolves_comment_id']
1540 close_pull_request = entry['close_pull_request']
1536 close_pull_request = entry['close_pull_request']
1541 f_path = entry['f_path']
1537 f_path = entry['f_path']
1542 line_no = entry['line']
1538 line_no = entry['line']
1543 target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path)))
1539 target_elem_id = 'file-{}'.format(h.safeid(h.safe_unicode(f_path)))
1544
1540
1545 # the logic here should work like following, if we submit close
1541 # the logic here should work like following, if we submit close
1546 # pr comment, use `close_pull_request_with_comment` function
1542 # pr comment, use `close_pull_request_with_comment` function
1547 # else handle regular comment logic
1543 # else handle regular comment logic
1548
1544
1549 if close_pull_request:
1545 if close_pull_request:
1550 # only owner or admin or person with write permissions
1546 # only owner or admin or person with write permissions
1551 allowed_to_close = PullRequestModel().check_user_update(
1547 allowed_to_close = PullRequestModel().check_user_update(
1552 pull_request, self._rhodecode_user)
1548 pull_request, self._rhodecode_user)
1553 if not allowed_to_close:
1549 if not allowed_to_close:
1554 log.debug('comment: forbidden because not allowed to close '
1550 log.debug('comment: forbidden because not allowed to close '
1555 'pull request %s', pull_request_id)
1551 'pull request %s', pull_request_id)
1556 raise HTTPForbidden()
1552 raise HTTPForbidden()
1557
1553
1558 # This also triggers `review_status_change`
1554 # This also triggers `review_status_change`
1559 comment, status = PullRequestModel().close_pull_request_with_comment(
1555 comment, status = PullRequestModel().close_pull_request_with_comment(
1560 pull_request, self._rhodecode_user, self.db_repo, message=text,
1556 pull_request, self._rhodecode_user, self.db_repo, message=text,
1561 auth_user=self._rhodecode_user)
1557 auth_user=self._rhodecode_user)
1562 Session().flush()
1558 Session().flush()
1563 is_inline = comment.is_inline
1559 is_inline = comment.is_inline
1564
1560
1565 PullRequestModel().trigger_pull_request_hook(
1561 PullRequestModel().trigger_pull_request_hook(
1566 pull_request, self._rhodecode_user, 'comment',
1562 pull_request, self._rhodecode_user, 'comment',
1567 data={'comment': comment})
1563 data={'comment': comment})
1568
1564
1569 else:
1565 else:
1570 # regular comment case, could be inline, or one with status.
1566 # regular comment case, could be inline, or one with status.
1571 # for that one we check also permissions
1567 # for that one we check also permissions
1572 # Additionally ENSURE if somehow draft is sent we're then unable to change status
1568 # Additionally ENSURE if somehow draft is sent we're then unable to change status
1573 allowed_to_change_status = PullRequestModel().check_user_change_status(
1569 allowed_to_change_status = PullRequestModel().check_user_change_status(
1574 pull_request, self._rhodecode_user) and not is_draft
1570 pull_request, self._rhodecode_user) and not is_draft
1575
1571
1576 if status and allowed_to_change_status:
1572 if status and allowed_to_change_status:
1577 message = (_('Status change %(transition_icon)s %(status)s')
1573 message = (_('Status change %(transition_icon)s %(status)s')
1578 % {'transition_icon': '>',
1574 % {'transition_icon': '>',
1579 'status': ChangesetStatus.get_status_lbl(status)})
1575 'status': ChangesetStatus.get_status_lbl(status)})
1580 text = text or message
1576 text = text or message
1581
1577
1582 comment = CommentsModel().create(
1578 comment = CommentsModel().create(
1583 text=text,
1579 text=text,
1584 repo=self.db_repo.repo_id,
1580 repo=self.db_repo.repo_id,
1585 user=self._rhodecode_user.user_id,
1581 user=self._rhodecode_user.user_id,
1586 pull_request=pull_request,
1582 pull_request=pull_request,
1587 f_path=f_path,
1583 f_path=f_path,
1588 line_no=line_no,
1584 line_no=line_no,
1589 status_change=(ChangesetStatus.get_status_lbl(status)
1585 status_change=(ChangesetStatus.get_status_lbl(status)
1590 if status and allowed_to_change_status else None),
1586 if status and allowed_to_change_status else None),
1591 status_change_type=(status
1587 status_change_type=(status
1592 if status and allowed_to_change_status else None),
1588 if status and allowed_to_change_status else None),
1593 comment_type=comment_type,
1589 comment_type=comment_type,
1594 is_draft=is_draft,
1590 is_draft=is_draft,
1595 resolves_comment_id=resolves_comment_id,
1591 resolves_comment_id=resolves_comment_id,
1596 auth_user=self._rhodecode_user,
1592 auth_user=self._rhodecode_user,
1597 send_email=not is_draft, # skip notification for draft comments
1593 send_email=not is_draft, # skip notification for draft comments
1598 )
1594 )
1599 is_inline = comment.is_inline
1595 is_inline = comment.is_inline
1600
1596
1601 if allowed_to_change_status:
1597 if allowed_to_change_status:
1602 # calculate old status before we change it
1598 # calculate old status before we change it
1603 old_calculated_status = pull_request.calculated_review_status()
1599 old_calculated_status = pull_request.calculated_review_status()
1604
1600
1605 # get status if set !
1601 # get status if set !
1606 if status:
1602 if status:
1607 ChangesetStatusModel().set_status(
1603 ChangesetStatusModel().set_status(
1608 self.db_repo.repo_id,
1604 self.db_repo.repo_id,
1609 status,
1605 status,
1610 self._rhodecode_user.user_id,
1606 self._rhodecode_user.user_id,
1611 comment,
1607 comment,
1612 pull_request=pull_request
1608 pull_request=pull_request
1613 )
1609 )
1614
1610
1615 Session().flush()
1611 Session().flush()
1616 # this is somehow required to get access to some relationship
1612 # this is somehow required to get access to some relationship
1617 # loaded on comment
1613 # loaded on comment
1618 Session().refresh(comment)
1614 Session().refresh(comment)
1619
1615
1620 # skip notifications for drafts
1616 # skip notifications for drafts
1621 if not is_draft:
1617 if not is_draft:
1622 PullRequestModel().trigger_pull_request_hook(
1618 PullRequestModel().trigger_pull_request_hook(
1623 pull_request, self._rhodecode_user, 'comment',
1619 pull_request, self._rhodecode_user, 'comment',
1624 data={'comment': comment})
1620 data={'comment': comment})
1625
1621
1626 # we now calculate the status of pull request, and based on that
1622 # we now calculate the status of pull request, and based on that
1627 # calculation we set the commits status
1623 # calculation we set the commits status
1628 calculated_status = pull_request.calculated_review_status()
1624 calculated_status = pull_request.calculated_review_status()
1629 if old_calculated_status != calculated_status:
1625 if old_calculated_status != calculated_status:
1630 PullRequestModel().trigger_pull_request_hook(
1626 PullRequestModel().trigger_pull_request_hook(
1631 pull_request, self._rhodecode_user, 'review_status_change',
1627 pull_request, self._rhodecode_user, 'review_status_change',
1632 data={'status': calculated_status})
1628 data={'status': calculated_status})
1633
1629
1634 comment_id = comment.comment_id
1630 comment_id = comment.comment_id
1635 data[comment_id] = {
1631 data[comment_id] = {
1636 'target_id': target_elem_id
1632 'target_id': target_elem_id
1637 }
1633 }
1638 Session().flush()
1634 Session().flush()
1639
1635
1640 c.co = comment
1636 c.co = comment
1641 c.at_version_num = None
1637 c.at_version_num = None
1642 c.is_new = True
1638 c.is_new = True
1643 rendered_comment = render(
1639 rendered_comment = render(
1644 'rhodecode:templates/changeset/changeset_comment_block.mako',
1640 'rhodecode:templates/changeset/changeset_comment_block.mako',
1645 self._get_template_context(c), self.request)
1641 self._get_template_context(c), self.request)
1646
1642
1647 data[comment_id].update(comment.get_dict())
1643 data[comment_id].update(comment.get_dict())
1648 data[comment_id].update({'rendered_text': rendered_comment})
1644 data[comment_id].update({'rendered_text': rendered_comment})
1649
1645
1650 Session().commit()
1646 Session().commit()
1651
1647
1652 # skip channelstream for draft comments
1648 # skip channelstream for draft comments
1653 if not all_drafts:
1649 if not all_drafts:
1654 comment_broadcast_channel = channelstream.comment_channel(
1650 comment_broadcast_channel = channelstream.comment_channel(
1655 self.db_repo_name, pull_request_obj=pull_request)
1651 self.db_repo_name, pull_request_obj=pull_request)
1656
1652
1657 comment_data = data
1653 comment_data = data
1658 posted_comment_type = 'inline' if is_inline else 'general'
1654 posted_comment_type = 'inline' if is_inline else 'general'
1659 if len(data) == 1:
1655 if len(data) == 1:
1660 msg = _('posted {} new {} comment').format(len(data), posted_comment_type)
1656 msg = _('posted {} new {} comment').format(len(data), posted_comment_type)
1661 else:
1657 else:
1662 msg = _('posted {} new {} comments').format(len(data), posted_comment_type)
1658 msg = _('posted {} new {} comments').format(len(data), posted_comment_type)
1663
1659
1664 channelstream.comment_channelstream_push(
1660 channelstream.comment_channelstream_push(
1665 self.request, comment_broadcast_channel, self._rhodecode_user, msg,
1661 self.request, comment_broadcast_channel, self._rhodecode_user, msg,
1666 comment_data=comment_data)
1662 comment_data=comment_data)
1667
1663
1668 return data
1664 return data
1669
1665
1670 @LoginRequired()
1666 @LoginRequired()
1671 @NotAnonymous()
1667 @NotAnonymous()
1672 @HasRepoPermissionAnyDecorator(
1668 @HasRepoPermissionAnyDecorator(
1673 'repository.read', 'repository.write', 'repository.admin')
1669 'repository.read', 'repository.write', 'repository.admin')
1674 @CSRFRequired()
1670 @CSRFRequired()
1675 @view_config(
1671 @view_config(
1676 route_name='pullrequest_comment_create', request_method='POST',
1672 route_name='pullrequest_comment_create', request_method='POST',
1677 renderer='json_ext')
1673 renderer='json_ext')
1678 def pull_request_comment_create(self):
1674 def pull_request_comment_create(self):
1679 _ = self.request.translate
1675 _ = self.request.translate
1680
1676
1681 pull_request = PullRequest.get_or_404(self.request.matchdict['pull_request_id'])
1677 pull_request = PullRequest.get_or_404(self.request.matchdict['pull_request_id'])
1682
1678
1683 if pull_request.is_closed():
1679 if pull_request.is_closed():
1684 log.debug('comment: forbidden because pull request is closed')
1680 log.debug('comment: forbidden because pull request is closed')
1685 raise HTTPForbidden()
1681 raise HTTPForbidden()
1686
1682
1687 allowed_to_comment = PullRequestModel().check_user_comment(
1683 allowed_to_comment = PullRequestModel().check_user_comment(
1688 pull_request, self._rhodecode_user)
1684 pull_request, self._rhodecode_user)
1689 if not allowed_to_comment:
1685 if not allowed_to_comment:
1690 log.debug('comment: forbidden because pull request is from forbidden repo')
1686 log.debug('comment: forbidden because pull request is from forbidden repo')
1691 raise HTTPForbidden()
1687 raise HTTPForbidden()
1692
1688
1693 comment_data = {
1689 comment_data = {
1694 'comment_type': self.request.POST.get('comment_type'),
1690 'comment_type': self.request.POST.get('comment_type'),
1695 'text': self.request.POST.get('text'),
1691 'text': self.request.POST.get('text'),
1696 'status': self.request.POST.get('changeset_status', None),
1692 'status': self.request.POST.get('changeset_status', None),
1697 'is_draft': self.request.POST.get('draft'),
1693 'is_draft': self.request.POST.get('draft'),
1698 'resolves_comment_id': self.request.POST.get('resolves_comment_id', None),
1694 'resolves_comment_id': self.request.POST.get('resolves_comment_id', None),
1699 'close_pull_request': self.request.POST.get('close_pull_request'),
1695 'close_pull_request': self.request.POST.get('close_pull_request'),
1700 'f_path': self.request.POST.get('f_path'),
1696 'f_path': self.request.POST.get('f_path'),
1701 'line': self.request.POST.get('line'),
1697 'line': self.request.POST.get('line'),
1702 }
1698 }
1703 data = self._pull_request_comments_create(pull_request, [comment_data])
1699 data = self._pull_request_comments_create(pull_request, [comment_data])
1704
1700
1705 return data
1701 return data
1706
1702
1707 @LoginRequired()
1703 @LoginRequired()
1708 @NotAnonymous()
1704 @NotAnonymous()
1709 @HasRepoPermissionAnyDecorator(
1705 @HasRepoPermissionAnyDecorator(
1710 'repository.read', 'repository.write', 'repository.admin')
1706 'repository.read', 'repository.write', 'repository.admin')
1711 @CSRFRequired()
1707 @CSRFRequired()
1712 @view_config(
1708 @view_config(
1713 route_name='pullrequest_comment_delete', request_method='POST',
1709 route_name='pullrequest_comment_delete', request_method='POST',
1714 renderer='json_ext')
1710 renderer='json_ext')
1715 def pull_request_comment_delete(self):
1711 def pull_request_comment_delete(self):
1716 pull_request = PullRequest.get_or_404(
1712 pull_request = PullRequest.get_or_404(
1717 self.request.matchdict['pull_request_id'])
1713 self.request.matchdict['pull_request_id'])
1718
1714
1719 comment = ChangesetComment.get_or_404(
1715 comment = ChangesetComment.get_or_404(
1720 self.request.matchdict['comment_id'])
1716 self.request.matchdict['comment_id'])
1721 comment_id = comment.comment_id
1717 comment_id = comment.comment_id
1722
1718
1723 if comment.immutable:
1719 if comment.immutable:
1724 # don't allow deleting comments that are immutable
1720 # don't allow deleting comments that are immutable
1725 raise HTTPForbidden()
1721 raise HTTPForbidden()
1726
1722
1727 if pull_request.is_closed():
1723 if pull_request.is_closed():
1728 log.debug('comment: forbidden because pull request is closed')
1724 log.debug('comment: forbidden because pull request is closed')
1729 raise HTTPForbidden()
1725 raise HTTPForbidden()
1730
1726
1731 if not comment:
1727 if not comment:
1732 log.debug('Comment with id:%s not found, skipping', comment_id)
1728 log.debug('Comment with id:%s not found, skipping', comment_id)
1733 # comment already deleted in another call probably
1729 # comment already deleted in another call probably
1734 return True
1730 return True
1735
1731
1736 if comment.pull_request.is_closed():
1732 if comment.pull_request.is_closed():
1737 # don't allow deleting comments on closed pull request
1733 # don't allow deleting comments on closed pull request
1738 raise HTTPForbidden()
1734 raise HTTPForbidden()
1739
1735
1740 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1736 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1741 super_admin = h.HasPermissionAny('hg.admin')()
1737 super_admin = h.HasPermissionAny('hg.admin')()
1742 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1738 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1743 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1739 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1744 comment_repo_admin = is_repo_admin and is_repo_comment
1740 comment_repo_admin = is_repo_admin and is_repo_comment
1745
1741
1746 if super_admin or comment_owner or comment_repo_admin:
1742 if super_admin or comment_owner or comment_repo_admin:
1747 old_calculated_status = comment.pull_request.calculated_review_status()
1743 old_calculated_status = comment.pull_request.calculated_review_status()
1748 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1744 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1749 Session().commit()
1745 Session().commit()
1750 calculated_status = comment.pull_request.calculated_review_status()
1746 calculated_status = comment.pull_request.calculated_review_status()
1751 if old_calculated_status != calculated_status:
1747 if old_calculated_status != calculated_status:
1752 PullRequestModel().trigger_pull_request_hook(
1748 PullRequestModel().trigger_pull_request_hook(
1753 comment.pull_request, self._rhodecode_user, 'review_status_change',
1749 comment.pull_request, self._rhodecode_user, 'review_status_change',
1754 data={'status': calculated_status})
1750 data={'status': calculated_status})
1755 return True
1751 return True
1756 else:
1752 else:
1757 log.warning('No permissions for user %s to delete comment_id: %s',
1753 log.warning('No permissions for user %s to delete comment_id: %s',
1758 self._rhodecode_db_user, comment_id)
1754 self._rhodecode_db_user, comment_id)
1759 raise HTTPNotFound()
1755 raise HTTPNotFound()
1760
1756
1761 @LoginRequired()
1757 @LoginRequired()
1762 @NotAnonymous()
1758 @NotAnonymous()
1763 @HasRepoPermissionAnyDecorator(
1759 @HasRepoPermissionAnyDecorator(
1764 'repository.read', 'repository.write', 'repository.admin')
1760 'repository.read', 'repository.write', 'repository.admin')
1765 @CSRFRequired()
1761 @CSRFRequired()
1766 @view_config(
1762 @view_config(
1767 route_name='pullrequest_comment_edit', request_method='POST',
1763 route_name='pullrequest_comment_edit', request_method='POST',
1768 renderer='json_ext')
1764 renderer='json_ext')
1769 def pull_request_comment_edit(self):
1765 def pull_request_comment_edit(self):
1770 self.load_default_context()
1766 self.load_default_context()
1771
1767
1772 pull_request = PullRequest.get_or_404(
1768 pull_request = PullRequest.get_or_404(
1773 self.request.matchdict['pull_request_id']
1769 self.request.matchdict['pull_request_id']
1774 )
1770 )
1775 comment = ChangesetComment.get_or_404(
1771 comment = ChangesetComment.get_or_404(
1776 self.request.matchdict['comment_id']
1772 self.request.matchdict['comment_id']
1777 )
1773 )
1778 comment_id = comment.comment_id
1774 comment_id = comment.comment_id
1779
1775
1780 if comment.immutable:
1776 if comment.immutable:
1781 # don't allow deleting comments that are immutable
1777 # don't allow deleting comments that are immutable
1782 raise HTTPForbidden()
1778 raise HTTPForbidden()
1783
1779
1784 if pull_request.is_closed():
1780 if pull_request.is_closed():
1785 log.debug('comment: forbidden because pull request is closed')
1781 log.debug('comment: forbidden because pull request is closed')
1786 raise HTTPForbidden()
1782 raise HTTPForbidden()
1787
1783
1788 if comment.pull_request.is_closed():
1784 if comment.pull_request.is_closed():
1789 # don't allow deleting comments on closed pull request
1785 # don't allow deleting comments on closed pull request
1790 raise HTTPForbidden()
1786 raise HTTPForbidden()
1791
1787
1792 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1788 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1793 super_admin = h.HasPermissionAny('hg.admin')()
1789 super_admin = h.HasPermissionAny('hg.admin')()
1794 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1790 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1795 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1791 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1796 comment_repo_admin = is_repo_admin and is_repo_comment
1792 comment_repo_admin = is_repo_admin and is_repo_comment
1797
1793
1798 if super_admin or comment_owner or comment_repo_admin:
1794 if super_admin or comment_owner or comment_repo_admin:
1799 text = self.request.POST.get('text')
1795 text = self.request.POST.get('text')
1800 version = self.request.POST.get('version')
1796 version = self.request.POST.get('version')
1801 if text == comment.text:
1797 if text == comment.text:
1802 log.warning(
1798 log.warning(
1803 'Comment(PR): '
1799 'Comment(PR): '
1804 'Trying to create new version '
1800 'Trying to create new version '
1805 'with the same comment body {}'.format(
1801 'with the same comment body {}'.format(
1806 comment_id,
1802 comment_id,
1807 )
1803 )
1808 )
1804 )
1809 raise HTTPNotFound()
1805 raise HTTPNotFound()
1810
1806
1811 if version.isdigit():
1807 if version.isdigit():
1812 version = int(version)
1808 version = int(version)
1813 else:
1809 else:
1814 log.warning(
1810 log.warning(
1815 'Comment(PR): Wrong version type {} {} '
1811 'Comment(PR): Wrong version type {} {} '
1816 'for comment {}'.format(
1812 'for comment {}'.format(
1817 version,
1813 version,
1818 type(version),
1814 type(version),
1819 comment_id,
1815 comment_id,
1820 )
1816 )
1821 )
1817 )
1822 raise HTTPNotFound()
1818 raise HTTPNotFound()
1823
1819
1824 try:
1820 try:
1825 comment_history = CommentsModel().edit(
1821 comment_history = CommentsModel().edit(
1826 comment_id=comment_id,
1822 comment_id=comment_id,
1827 text=text,
1823 text=text,
1828 auth_user=self._rhodecode_user,
1824 auth_user=self._rhodecode_user,
1829 version=version,
1825 version=version,
1830 )
1826 )
1831 except CommentVersionMismatch:
1827 except CommentVersionMismatch:
1832 raise HTTPConflict()
1828 raise HTTPConflict()
1833
1829
1834 if not comment_history:
1830 if not comment_history:
1835 raise HTTPNotFound()
1831 raise HTTPNotFound()
1836
1832
1837 Session().commit()
1833 Session().commit()
1838 if not comment.draft:
1834 if not comment.draft:
1839 PullRequestModel().trigger_pull_request_hook(
1835 PullRequestModel().trigger_pull_request_hook(
1840 pull_request, self._rhodecode_user, 'comment_edit',
1836 pull_request, self._rhodecode_user, 'comment_edit',
1841 data={'comment': comment})
1837 data={'comment': comment})
1842
1838
1843 return {
1839 return {
1844 'comment_history_id': comment_history.comment_history_id,
1840 'comment_history_id': comment_history.comment_history_id,
1845 'comment_id': comment.comment_id,
1841 'comment_id': comment.comment_id,
1846 'comment_version': comment_history.version,
1842 'comment_version': comment_history.version,
1847 'comment_author_username': comment_history.author.username,
1843 'comment_author_username': comment_history.author.username,
1848 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
1844 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
1849 'comment_created_on': h.age_component(comment_history.created_on,
1845 'comment_created_on': h.age_component(comment_history.created_on,
1850 time_is_local=True),
1846 time_is_local=True),
1851 }
1847 }
1852 else:
1848 else:
1853 log.warning('No permissions for user %s to edit comment_id: %s',
1849 log.warning('No permissions for user %s to edit comment_id: %s',
1854 self._rhodecode_db_user, comment_id)
1850 self._rhodecode_db_user, comment_id)
1855 raise HTTPNotFound()
1851 raise HTTPNotFound()
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,1195 +1,1146 b''
1 // # Copyright (C) 2010-2020 RhodeCode GmbH
1 // # Copyright (C) 2010-2020 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19
19
20 var prButtonLockChecks = {
20 var prButtonLockChecks = {
21 'compare': false,
21 'compare': false,
22 'reviewers': false
22 'reviewers': false
23 };
23 };
24
24
25 /**
25 /**
26 * lock button until all checks and loads are made. E.g reviewer calculation
26 * lock button until all checks and loads are made. E.g reviewer calculation
27 * should prevent from submitting a PR
27 * should prevent from submitting a PR
28 * @param lockEnabled
28 * @param lockEnabled
29 * @param msg
29 * @param msg
30 * @param scope
30 * @param scope
31 */
31 */
32 var prButtonLock = function(lockEnabled, msg, scope) {
32 var prButtonLock = function(lockEnabled, msg, scope) {
33 scope = scope || 'all';
33 scope = scope || 'all';
34 if (scope == 'all'){
34 if (scope == 'all'){
35 prButtonLockChecks['compare'] = !lockEnabled;
35 prButtonLockChecks['compare'] = !lockEnabled;
36 prButtonLockChecks['reviewers'] = !lockEnabled;
36 prButtonLockChecks['reviewers'] = !lockEnabled;
37 } else if (scope == 'compare') {
37 } else if (scope == 'compare') {
38 prButtonLockChecks['compare'] = !lockEnabled;
38 prButtonLockChecks['compare'] = !lockEnabled;
39 } else if (scope == 'reviewers'){
39 } else if (scope == 'reviewers'){
40 prButtonLockChecks['reviewers'] = !lockEnabled;
40 prButtonLockChecks['reviewers'] = !lockEnabled;
41 }
41 }
42 var checksMeet = prButtonLockChecks.compare && prButtonLockChecks.reviewers;
42 var checksMeet = prButtonLockChecks.compare && prButtonLockChecks.reviewers;
43 if (lockEnabled) {
43 if (lockEnabled) {
44 $('#pr_submit').attr('disabled', 'disabled');
44 $('#pr_submit').attr('disabled', 'disabled');
45 }
45 }
46 else if (checksMeet) {
46 else if (checksMeet) {
47 $('#pr_submit').removeAttr('disabled');
47 $('#pr_submit').removeAttr('disabled');
48 }
48 }
49
49
50 if (msg) {
50 if (msg) {
51 $('#pr_open_message').html(msg);
51 $('#pr_open_message').html(msg);
52 }
52 }
53 };
53 };
54
54
55
55
56 /**
56 /**
57 Generate Title and Description for a PullRequest.
57 Generate Title and Description for a PullRequest.
58 In case of 1 commits, the title and description is that one commit
58 In case of 1 commits, the title and description is that one commit
59 in case of multiple commits, we iterate on them with max N number of commits,
59 in case of multiple commits, we iterate on them with max N number of commits,
60 and build description in a form
60 and build description in a form
61 - commitN
61 - commitN
62 - commitN+1
62 - commitN+1
63 ...
63 ...
64
64
65 Title is then constructed from branch names, or other references,
65 Title is then constructed from branch names, or other references,
66 replacing '-' and '_' into spaces
66 replacing '-' and '_' into spaces
67
67
68 * @param sourceRef
68 * @param sourceRef
69 * @param elements
69 * @param elements
70 * @param limit
70 * @param limit
71 * @returns {*[]}
71 * @returns {*[]}
72 */
72 */
73 var getTitleAndDescription = function(sourceRefType, sourceRef, elements, limit) {
73 var getTitleAndDescription = function(sourceRefType, sourceRef, elements, limit) {
74 var title = '';
74 var title = '';
75 var desc = '';
75 var desc = '';
76
76
77 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
77 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
78 var rawMessage = value['message'];
78 var rawMessage = value['message'];
79 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
79 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
80 });
80 });
81 // only 1 commit, use commit message as title
81 // only 1 commit, use commit message as title
82 if (elements.length === 1) {
82 if (elements.length === 1) {
83 var rawMessage = elements[0]['message'];
83 var rawMessage = elements[0]['message'];
84 title = rawMessage.split('\n')[0];
84 title = rawMessage.split('\n')[0];
85 }
85 }
86 else {
86 else {
87 // use reference name
87 // use reference name
88 var normalizedRef = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter()
88 var normalizedRef = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter()
89 var refType = sourceRefType;
89 var refType = sourceRefType;
90 title = 'Changes from {0}: {1}'.format(refType, normalizedRef);
90 title = 'Changes from {0}: {1}'.format(refType, normalizedRef);
91 }
91 }
92
92
93 return [title, desc]
93 return [title, desc]
94 };
94 };
95
95
96
96
97 window.ReviewersController = function () {
97 window.ReviewersController = function () {
98 var self = this;
98 var self = this;
99 this.$loadingIndicator = $('.calculate-reviewers');
99 this.$loadingIndicator = $('.calculate-reviewers');
100 this.$reviewRulesContainer = $('#review_rules');
100 this.$reviewRulesContainer = $('#review_rules');
101 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
101 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
102 this.$userRule = $('.pr-user-rule-container');
102 this.$userRule = $('.pr-user-rule-container');
103 this.$reviewMembers = $('#review_members');
103 this.$reviewMembers = $('#review_members');
104 this.$observerMembers = $('#observer_members');
104 this.$observerMembers = $('#observer_members');
105
105
106 this.currentRequest = null;
106 this.currentRequest = null;
107 this.diffData = null;
107 this.diffData = null;
108 this.enabledRules = [];
108 this.enabledRules = [];
109 // sync with db.py entries
109 // sync with db.py entries
110 this.ROLE_REVIEWER = 'reviewer';
110 this.ROLE_REVIEWER = 'reviewer';
111 this.ROLE_OBSERVER = 'observer'
111 this.ROLE_OBSERVER = 'observer'
112
112
113 //dummy handler, we might register our own later
113 //dummy handler, we might register our own later
114 this.diffDataHandler = function (data) {};
114 this.diffDataHandler = function (data) {};
115
115
116 this.defaultForbidUsers = function () {
116 this.defaultForbidUsers = function () {
117 return [
117 return [
118 {
118 {
119 'username': 'default',
119 'username': 'default',
120 'user_id': templateContext.default_user.user_id
120 'user_id': templateContext.default_user.user_id
121 }
121 }
122 ];
122 ];
123 };
123 };
124
124
125 // init default forbidden users
125 // init default forbidden users
126 this.forbidUsers = this.defaultForbidUsers();
126 this.forbidUsers = this.defaultForbidUsers();
127
127
128 this.hideReviewRules = function () {
128 this.hideReviewRules = function () {
129 self.$reviewRulesContainer.hide();
129 self.$reviewRulesContainer.hide();
130 $(self.$userRule.selector).hide();
130 $(self.$userRule.selector).hide();
131 };
131 };
132
132
133 this.showReviewRules = function () {
133 this.showReviewRules = function () {
134 self.$reviewRulesContainer.show();
134 self.$reviewRulesContainer.show();
135 $(self.$userRule.selector).show();
135 $(self.$userRule.selector).show();
136 };
136 };
137
137
138 this.addRule = function (ruleText) {
138 this.addRule = function (ruleText) {
139 self.showReviewRules();
139 self.showReviewRules();
140 self.enabledRules.push(ruleText);
140 self.enabledRules.push(ruleText);
141 return '<div>- {0}</div>'.format(ruleText)
141 return '<div>- {0}</div>'.format(ruleText)
142 };
142 };
143
143
144 this.increaseCounter = function(role) {
144 this.increaseCounter = function(role) {
145 if (role === self.ROLE_REVIEWER) {
145 if (role === self.ROLE_REVIEWER) {
146 var $elem = $('#reviewers-cnt')
146 var $elem = $('#reviewers-cnt')
147 var cnt = parseInt($elem.data('count') || 0)
147 var cnt = parseInt($elem.data('count') || 0)
148 cnt +=1
148 cnt +=1
149 $elem.html(cnt);
149 $elem.html(cnt);
150 $elem.data('count', cnt);
150 $elem.data('count', cnt);
151 }
151 }
152 else if (role === self.ROLE_OBSERVER) {
152 else if (role === self.ROLE_OBSERVER) {
153 var $elem = $('#observers-cnt');
153 var $elem = $('#observers-cnt');
154 var cnt = parseInt($elem.data('count') || 0)
154 var cnt = parseInt($elem.data('count') || 0)
155 cnt +=1
155 cnt +=1
156 $elem.html(cnt);
156 $elem.html(cnt);
157 $elem.data('count', cnt);
157 $elem.data('count', cnt);
158 }
158 }
159 }
159 }
160
160
161 this.resetCounter = function () {
161 this.resetCounter = function () {
162 var $elem = $('#reviewers-cnt');
162 var $elem = $('#reviewers-cnt');
163
163
164 $elem.data('count', 0);
164 $elem.data('count', 0);
165 $elem.html(0);
165 $elem.html(0);
166
166
167 var $elem = $('#observers-cnt');
167 var $elem = $('#observers-cnt');
168
168
169 $elem.data('count', 0);
169 $elem.data('count', 0);
170 $elem.html(0);
170 $elem.html(0);
171 }
171 }
172
172
173 this.loadReviewRules = function (data) {
173 this.loadReviewRules = function (data) {
174 self.diffData = data;
174 self.diffData = data;
175
175
176 // reset forbidden Users
176 // reset forbidden Users
177 this.forbidUsers = self.defaultForbidUsers();
177 this.forbidUsers = self.defaultForbidUsers();
178
178
179 // reset state of review rules
179 // reset state of review rules
180 self.$rulesList.html('');
180 self.$rulesList.html('');
181
181
182 if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) {
182 if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) {
183 // default rule, case for older repo that don't have any rules stored
183 // default rule, case for older repo that don't have any rules stored
184 self.$rulesList.append(
184 self.$rulesList.append(
185 self.addRule(
185 self.addRule(_gettext('All reviewers must vote.'))
186 _gettext('All reviewers must vote.'))
187 );
186 );
188 return self.forbidUsers
187 return self.forbidUsers
189 }
188 }
190
189
191 if (data.rules.voting !== undefined) {
190 if (data.rules.forbid_adding_reviewers) {
192 if (data.rules.voting < 0) {
191 $('#add_reviewer_input').remove();
193 self.$rulesList.append(
194 self.addRule(
195 _gettext('All individual reviewers must vote.'))
196 )
197 } else if (data.rules.voting === 1) {
198 self.$rulesList.append(
199 self.addRule(
200 _gettext('At least {0} reviewer must vote.').format(data.rules.voting))
201 )
202
203 } else {
204 self.$rulesList.append(
205 self.addRule(
206 _gettext('At least {0} reviewers must vote.').format(data.rules.voting))
207 )
208 }
209 }
192 }
210
193
211 if (data.rules.voting_groups !== undefined) {
194 if (data.rules_data !== undefined && data.rules_data.forbidden_users !== undefined) {
212 $.each(data.rules.voting_groups, function (index, rule_data) {
195 $.each(data.rules_data.forbidden_users, function(idx, val){
213 self.$rulesList.append(
196 self.forbidUsers.push(val)
214 self.addRule(rule_data.text)
197 })
215 )
216 });
217 }
218
219 if (data.rules.use_code_authors_for_review) {
220 self.$rulesList.append(
221 self.addRule(
222 _gettext('Reviewers picked from source code changes.'))
223 )
224 }
198 }
225
199
226 if (data.rules.forbid_adding_reviewers) {
200 if (data.rules_humanized !== undefined && data.rules_humanized.length > 0) {
227 $('#add_reviewer_input').remove();
201 $.each(data.rules_humanized, function(idx, val) {
202 self.$rulesList.append(
203 self.addRule(val)
204 )
205 })
206 } else {
207 // we don't have any rules set, so we inform users about it
228 self.$rulesList.append(
208 self.$rulesList.append(
229 self.addRule(
209 self.addRule(_gettext('No additional review rules set.'))
230 _gettext('Adding new reviewers is forbidden.'))
231 )
232 }
233
234 if (data.rules.forbid_author_to_review) {
235 self.forbidUsers.push(data.rules_data.pr_author);
236 self.$rulesList.append(
237 self.addRule(
238 _gettext('Author is not allowed to be a reviewer.'))
239 )
210 )
240 }
211 }
241
212
242 if (data.rules.forbid_commit_author_to_review) {
243
244 if (data.rules_data.forbidden_users) {
245 $.each(data.rules_data.forbidden_users, function (index, member_data) {
246 self.forbidUsers.push(member_data)
247 });
248 }
249
250 self.$rulesList.append(
251 self.addRule(
252 _gettext('Commit Authors are not allowed to be a reviewer.'))
253 )
254 }
255
256 // we don't have any rules set, so we inform users about it
257 if (self.enabledRules.length === 0) {
258 self.addRule(
259 _gettext('No review rules set.'))
260 }
261
262 return self.forbidUsers
213 return self.forbidUsers
263 };
214 };
264
215
265 this.emptyTables = function () {
216 this.emptyTables = function () {
266 self.emptyReviewersTable();
217 self.emptyReviewersTable();
267 self.emptyObserversTable();
218 self.emptyObserversTable();
268
219
269 // Also reset counters.
220 // Also reset counters.
270 self.resetCounter();
221 self.resetCounter();
271 }
222 }
272
223
273 this.emptyReviewersTable = function (withText) {
224 this.emptyReviewersTable = function (withText) {
274 self.$reviewMembers.empty();
225 self.$reviewMembers.empty();
275 if (withText !== undefined) {
226 if (withText !== undefined) {
276 self.$reviewMembers.html(withText)
227 self.$reviewMembers.html(withText)
277 }
228 }
278 };
229 };
279
230
280 this.emptyObserversTable = function (withText) {
231 this.emptyObserversTable = function (withText) {
281 self.$observerMembers.empty();
232 self.$observerMembers.empty();
282 if (withText !== undefined) {
233 if (withText !== undefined) {
283 self.$observerMembers.html(withText)
234 self.$observerMembers.html(withText)
284 }
235 }
285 }
236 }
286
237
287 this.loadDefaultReviewers = function (sourceRepo, sourceRef, targetRepo, targetRef) {
238 this.loadDefaultReviewers = function (sourceRepo, sourceRef, targetRepo, targetRef) {
288
239
289 if (self.currentRequest) {
240 if (self.currentRequest) {
290 // make sure we cleanup old running requests before triggering this again
241 // make sure we cleanup old running requests before triggering this again
291 self.currentRequest.abort();
242 self.currentRequest.abort();
292 }
243 }
293
244
294 self.$loadingIndicator.show();
245 self.$loadingIndicator.show();
295
246
296 // reset reviewer/observe members
247 // reset reviewer/observe members
297 self.emptyTables();
248 self.emptyTables();
298
249
299 prButtonLock(true, null, 'reviewers');
250 prButtonLock(true, null, 'reviewers');
300 $('#user').hide(); // hide user autocomplete before load
251 $('#user').hide(); // hide user autocomplete before load
301 $('#observer').hide(); //hide observer autocomplete before load
252 $('#observer').hide(); //hide observer autocomplete before load
302
253
303 // lock PR button, so we cannot send PR before it's calculated
254 // lock PR button, so we cannot send PR before it's calculated
304 prButtonLock(true, _gettext('Loading diff ...'), 'compare');
255 prButtonLock(true, _gettext('Loading diff ...'), 'compare');
305
256
306 if (sourceRef.length !== 3 || targetRef.length !== 3) {
257 if (sourceRef.length !== 3 || targetRef.length !== 3) {
307 // don't load defaults in case we're missing some refs...
258 // don't load defaults in case we're missing some refs...
308 self.$loadingIndicator.hide();
259 self.$loadingIndicator.hide();
309 return
260 return
310 }
261 }
311
262
312 var url = pyroutes.url('repo_default_reviewers_data',
263 var url = pyroutes.url('repo_default_reviewers_data',
313 {
264 {
314 'repo_name': templateContext.repo_name,
265 'repo_name': templateContext.repo_name,
315 'source_repo': sourceRepo,
266 'source_repo': sourceRepo,
316 'source_ref_type': sourceRef[0],
267 'source_ref_type': sourceRef[0],
317 'source_ref_name': sourceRef[1],
268 'source_ref_name': sourceRef[1],
318 'source_ref': sourceRef[2],
269 'source_ref': sourceRef[2],
319 'target_repo': targetRepo,
270 'target_repo': targetRepo,
320 'target_ref': targetRef[2],
271 'target_ref': targetRef[2],
321 'target_ref_type': sourceRef[0],
272 'target_ref_type': sourceRef[0],
322 'target_ref_name': sourceRef[1]
273 'target_ref_name': sourceRef[1]
323 });
274 });
324
275
325 self.currentRequest = $.ajax({
276 self.currentRequest = $.ajax({
326 url: url,
277 url: url,
327 headers: {'X-PARTIAL-XHR': true},
278 headers: {'X-PARTIAL-XHR': true},
328 type: 'GET',
279 type: 'GET',
329 success: function (data) {
280 success: function (data) {
330
281
331 self.currentRequest = null;
282 self.currentRequest = null;
332
283
333 // review rules
284 // review rules
334 self.loadReviewRules(data);
285 self.loadReviewRules(data);
335 var diffHandled = self.handleDiffData(data["diff_info"]);
286 var diffHandled = self.handleDiffData(data["diff_info"]);
336 if (diffHandled === false) {
287 if (diffHandled === false) {
337 return
288 return
338 }
289 }
339
290
340 for (var i = 0; i < data.reviewers.length; i++) {
291 for (var i = 0; i < data.reviewers.length; i++) {
341 var reviewer = data.reviewers[i];
292 var reviewer = data.reviewers[i];
342 // load reviewer rules from the repo data
293 // load reviewer rules from the repo data
343 self.addMember(reviewer, reviewer.reasons, reviewer.mandatory, reviewer.role);
294 self.addMember(reviewer, reviewer.reasons, reviewer.mandatory, reviewer.role);
344 }
295 }
345
296
346
297
347 self.$loadingIndicator.hide();
298 self.$loadingIndicator.hide();
348 prButtonLock(false, null, 'reviewers');
299 prButtonLock(false, null, 'reviewers');
349
300
350 $('#user').show(); // show user autocomplete before load
301 $('#user').show(); // show user autocomplete before load
351 $('#observer').show(); // show observer autocomplete before load
302 $('#observer').show(); // show observer autocomplete before load
352
303
353 var commitElements = data["diff_info"]['commits'];
304 var commitElements = data["diff_info"]['commits'];
354
305
355 if (commitElements.length === 0) {
306 if (commitElements.length === 0) {
356 var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format(
307 var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format(
357 _gettext('There are no commits to merge.'));
308 _gettext('There are no commits to merge.'));
358 prButtonLock(true, noCommitsMsg, 'all');
309 prButtonLock(true, noCommitsMsg, 'all');
359
310
360 } else {
311 } else {
361 // un-lock PR button, so we cannot send PR before it's calculated
312 // un-lock PR button, so we cannot send PR before it's calculated
362 prButtonLock(false, null, 'compare');
313 prButtonLock(false, null, 'compare');
363 }
314 }
364
315
365 },
316 },
366 error: function (jqXHR, textStatus, errorThrown) {
317 error: function (jqXHR, textStatus, errorThrown) {
367 var prefix = "Loading diff and reviewers/observers failed\n"
318 var prefix = "Loading diff and reviewers/observers failed\n"
368 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
319 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
369 ajaxErrorSwal(message);
320 ajaxErrorSwal(message);
370 }
321 }
371 });
322 });
372
323
373 };
324 };
374
325
375 // check those, refactor
326 // check those, refactor
376 this.removeMember = function (reviewer_id, mark_delete) {
327 this.removeMember = function (reviewer_id, mark_delete) {
377 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
328 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
378
329
379 if (typeof (mark_delete) === undefined) {
330 if (typeof (mark_delete) === undefined) {
380 mark_delete = false;
331 mark_delete = false;
381 }
332 }
382
333
383 if (mark_delete === true) {
334 if (mark_delete === true) {
384 if (reviewer) {
335 if (reviewer) {
385 // now delete the input
336 // now delete the input
386 $('#reviewer_{0} input'.format(reviewer_id)).remove();
337 $('#reviewer_{0} input'.format(reviewer_id)).remove();
387 $('#reviewer_{0}_rules input'.format(reviewer_id)).remove();
338 $('#reviewer_{0}_rules input'.format(reviewer_id)).remove();
388 // mark as to-delete
339 // mark as to-delete
389 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
340 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
390 obj.addClass('to-delete');
341 obj.addClass('to-delete');
391 obj.css({"text-decoration": "line-through", "opacity": 0.5});
342 obj.css({"text-decoration": "line-through", "opacity": 0.5});
392 }
343 }
393 } else {
344 } else {
394 $('#reviewer_{0}'.format(reviewer_id)).remove();
345 $('#reviewer_{0}'.format(reviewer_id)).remove();
395 }
346 }
396 };
347 };
397
348
398 this.addMember = function (reviewer_obj, reasons, mandatory, role) {
349 this.addMember = function (reviewer_obj, reasons, mandatory, role) {
399
350
400 var id = reviewer_obj.user_id;
351 var id = reviewer_obj.user_id;
401 var username = reviewer_obj.username;
352 var username = reviewer_obj.username;
402
353
403 reasons = reasons || [];
354 reasons = reasons || [];
404 mandatory = mandatory || false;
355 mandatory = mandatory || false;
405 role = role || self.ROLE_REVIEWER
356 role = role || self.ROLE_REVIEWER
406
357
407 // register current set IDS to check if we don't have this ID already in
358 // register current set IDS to check if we don't have this ID already in
408 // and prevent duplicates
359 // and prevent duplicates
409 var currentIds = [];
360 var currentIds = [];
410
361
411 $.each($('.reviewer_entry'), function (index, value) {
362 $.each($('.reviewer_entry'), function (index, value) {
412 currentIds.push($(value).data('reviewerUserId'))
363 currentIds.push($(value).data('reviewerUserId'))
413 })
364 })
414
365
415 var userAllowedReview = function (userId) {
366 var userAllowedReview = function (userId) {
416 var allowed = true;
367 var allowed = true;
417 $.each(self.forbidUsers, function (index, member_data) {
368 $.each(self.forbidUsers, function (index, member_data) {
418 if (parseInt(userId) === member_data['user_id']) {
369 if (parseInt(userId) === member_data['user_id']) {
419 allowed = false;
370 allowed = false;
420 return false // breaks the loop
371 return false // breaks the loop
421 }
372 }
422 });
373 });
423 return allowed
374 return allowed
424 };
375 };
425
376
426 var userAllowed = userAllowedReview(id);
377 var userAllowed = userAllowedReview(id);
427
378
428 if (!userAllowed) {
379 if (!userAllowed) {
429 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
380 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
430 } else {
381 } else {
431 // only add if it's not there
382 // only add if it's not there
432 var alreadyReviewer = currentIds.indexOf(id) != -1;
383 var alreadyReviewer = currentIds.indexOf(id) != -1;
433
384
434 if (alreadyReviewer) {
385 if (alreadyReviewer) {
435 alert(_gettext('User `{0}` already in reviewers/observers').format(username));
386 alert(_gettext('User `{0}` already in reviewers/observers').format(username));
436 } else {
387 } else {
437
388
438 var reviewerEntry = renderTemplate('reviewMemberEntry', {
389 var reviewerEntry = renderTemplate('reviewMemberEntry', {
439 'member': reviewer_obj,
390 'member': reviewer_obj,
440 'mandatory': mandatory,
391 'mandatory': mandatory,
441 'role': role,
392 'role': role,
442 'reasons': reasons,
393 'reasons': reasons,
443 'allowed_to_update': true,
394 'allowed_to_update': true,
444 'review_status': 'not_reviewed',
395 'review_status': 'not_reviewed',
445 'review_status_label': _gettext('Not Reviewed'),
396 'review_status_label': _gettext('Not Reviewed'),
446 'user_group': reviewer_obj.user_group,
397 'user_group': reviewer_obj.user_group,
447 'create': true,
398 'create': true,
448 'rule_show': true,
399 'rule_show': true,
449 })
400 })
450
401
451 if (role === self.ROLE_REVIEWER) {
402 if (role === self.ROLE_REVIEWER) {
452 $(self.$reviewMembers.selector).append(reviewerEntry);
403 $(self.$reviewMembers.selector).append(reviewerEntry);
453 self.increaseCounter(self.ROLE_REVIEWER);
404 self.increaseCounter(self.ROLE_REVIEWER);
454 $('#reviewer-empty-msg').remove()
405 $('#reviewer-empty-msg').remove()
455 }
406 }
456 else if (role === self.ROLE_OBSERVER) {
407 else if (role === self.ROLE_OBSERVER) {
457 $(self.$observerMembers.selector).append(reviewerEntry);
408 $(self.$observerMembers.selector).append(reviewerEntry);
458 self.increaseCounter(self.ROLE_OBSERVER);
409 self.increaseCounter(self.ROLE_OBSERVER);
459 $('#observer-empty-msg').remove();
410 $('#observer-empty-msg').remove();
460 }
411 }
461
412
462 tooltipActivate();
413 tooltipActivate();
463 }
414 }
464 }
415 }
465
416
466 };
417 };
467
418
468 this.updateReviewers = function (repo_name, pull_request_id, role) {
419 this.updateReviewers = function (repo_name, pull_request_id, role) {
469 if (role === 'reviewer') {
420 if (role === 'reviewer') {
470 var postData = $('#reviewers input').serialize();
421 var postData = $('#reviewers input').serialize();
471 _updatePullRequest(repo_name, pull_request_id, postData);
422 _updatePullRequest(repo_name, pull_request_id, postData);
472 } else if (role === 'observer') {
423 } else if (role === 'observer') {
473 var postData = $('#observers input').serialize();
424 var postData = $('#observers input').serialize();
474 _updatePullRequest(repo_name, pull_request_id, postData);
425 _updatePullRequest(repo_name, pull_request_id, postData);
475 }
426 }
476 };
427 };
477
428
478 this.handleDiffData = function (data) {
429 this.handleDiffData = function (data) {
479 return self.diffDataHandler(data)
430 return self.diffDataHandler(data)
480 }
431 }
481 };
432 };
482
433
483
434
484 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
435 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
485 var url = pyroutes.url(
436 var url = pyroutes.url(
486 'pullrequest_update',
437 'pullrequest_update',
487 {"repo_name": repo_name, "pull_request_id": pull_request_id});
438 {"repo_name": repo_name, "pull_request_id": pull_request_id});
488 if (typeof postData === 'string' ) {
439 if (typeof postData === 'string' ) {
489 postData += '&csrf_token=' + CSRF_TOKEN;
440 postData += '&csrf_token=' + CSRF_TOKEN;
490 } else {
441 } else {
491 postData.csrf_token = CSRF_TOKEN;
442 postData.csrf_token = CSRF_TOKEN;
492 }
443 }
493
444
494 var success = function(o) {
445 var success = function(o) {
495 var redirectUrl = o['redirect_url'];
446 var redirectUrl = o['redirect_url'];
496 if (redirectUrl !== undefined && redirectUrl !== null && redirectUrl !== '') {
447 if (redirectUrl !== undefined && redirectUrl !== null && redirectUrl !== '') {
497 window.location = redirectUrl;
448 window.location = redirectUrl;
498 } else {
449 } else {
499 window.location.reload();
450 window.location.reload();
500 }
451 }
501 };
452 };
502
453
503 ajaxPOST(url, postData, success);
454 ajaxPOST(url, postData, success);
504 };
455 };
505
456
506 /**
457 /**
507 * PULL REQUEST update commits
458 * PULL REQUEST update commits
508 */
459 */
509 var updateCommits = function(repo_name, pull_request_id, force) {
460 var updateCommits = function(repo_name, pull_request_id, force) {
510 var postData = {
461 var postData = {
511 'update_commits': true
462 'update_commits': true
512 };
463 };
513 if (force !== undefined && force === true) {
464 if (force !== undefined && force === true) {
514 postData['force_refresh'] = true
465 postData['force_refresh'] = true
515 }
466 }
516 _updatePullRequest(repo_name, pull_request_id, postData);
467 _updatePullRequest(repo_name, pull_request_id, postData);
517 };
468 };
518
469
519
470
520 /**
471 /**
521 * PULL REQUEST edit info
472 * PULL REQUEST edit info
522 */
473 */
523 var editPullRequest = function(repo_name, pull_request_id, title, description, renderer) {
474 var editPullRequest = function(repo_name, pull_request_id, title, description, renderer) {
524 var url = pyroutes.url(
475 var url = pyroutes.url(
525 'pullrequest_update',
476 'pullrequest_update',
526 {"repo_name": repo_name, "pull_request_id": pull_request_id});
477 {"repo_name": repo_name, "pull_request_id": pull_request_id});
527
478
528 var postData = {
479 var postData = {
529 'title': title,
480 'title': title,
530 'description': description,
481 'description': description,
531 'description_renderer': renderer,
482 'description_renderer': renderer,
532 'edit_pull_request': true,
483 'edit_pull_request': true,
533 'csrf_token': CSRF_TOKEN
484 'csrf_token': CSRF_TOKEN
534 };
485 };
535 var success = function(o) {
486 var success = function(o) {
536 window.location.reload();
487 window.location.reload();
537 };
488 };
538 ajaxPOST(url, postData, success);
489 ajaxPOST(url, postData, success);
539 };
490 };
540
491
541
492
542 /**
493 /**
543 * autocomplete handler for reviewers/observers
494 * autocomplete handler for reviewers/observers
544 */
495 */
545 var autoCompleteHandler = function (inputId, controller, role) {
496 var autoCompleteHandler = function (inputId, controller, role) {
546
497
547 return function (element, data) {
498 return function (element, data) {
548 var mandatory = false;
499 var mandatory = false;
549 var reasons = [_gettext('added manually by "{0}"').format(
500 var reasons = [_gettext('added manually by "{0}"').format(
550 templateContext.rhodecode_user.username)];
501 templateContext.rhodecode_user.username)];
551
502
552 // add whole user groups
503 // add whole user groups
553 if (data.value_type == 'user_group') {
504 if (data.value_type == 'user_group') {
554 reasons.push(_gettext('member of "{0}"').format(data.value_display));
505 reasons.push(_gettext('member of "{0}"').format(data.value_display));
555
506
556 $.each(data.members, function (index, member_data) {
507 $.each(data.members, function (index, member_data) {
557 var reviewer = member_data;
508 var reviewer = member_data;
558 reviewer['user_id'] = member_data['id'];
509 reviewer['user_id'] = member_data['id'];
559 reviewer['gravatar_link'] = member_data['icon_link'];
510 reviewer['gravatar_link'] = member_data['icon_link'];
560 reviewer['user_link'] = member_data['profile_link'];
511 reviewer['user_link'] = member_data['profile_link'];
561 reviewer['rules'] = [];
512 reviewer['rules'] = [];
562 controller.addMember(reviewer, reasons, mandatory, role);
513 controller.addMember(reviewer, reasons, mandatory, role);
563 })
514 })
564 }
515 }
565 // add single user
516 // add single user
566 else {
517 else {
567 var reviewer = data;
518 var reviewer = data;
568 reviewer['user_id'] = data['id'];
519 reviewer['user_id'] = data['id'];
569 reviewer['gravatar_link'] = data['icon_link'];
520 reviewer['gravatar_link'] = data['icon_link'];
570 reviewer['user_link'] = data['profile_link'];
521 reviewer['user_link'] = data['profile_link'];
571 reviewer['rules'] = [];
522 reviewer['rules'] = [];
572 controller.addMember(reviewer, reasons, mandatory, role);
523 controller.addMember(reviewer, reasons, mandatory, role);
573 }
524 }
574
525
575 $(inputId).val('');
526 $(inputId).val('');
576 }
527 }
577 }
528 }
578
529
579 /**
530 /**
580 * Reviewer autocomplete
531 * Reviewer autocomplete
581 */
532 */
582 var ReviewerAutoComplete = function (inputId, controller) {
533 var ReviewerAutoComplete = function (inputId, controller) {
583 var self = this;
534 var self = this;
584 self.controller = controller;
535 self.controller = controller;
585 self.inputId = inputId;
536 self.inputId = inputId;
586 var handler = autoCompleteHandler(inputId, controller, controller.ROLE_REVIEWER);
537 var handler = autoCompleteHandler(inputId, controller, controller.ROLE_REVIEWER);
587
538
588 $(inputId).autocomplete({
539 $(inputId).autocomplete({
589 serviceUrl: pyroutes.url('user_autocomplete_data'),
540 serviceUrl: pyroutes.url('user_autocomplete_data'),
590 minChars: 2,
541 minChars: 2,
591 maxHeight: 400,
542 maxHeight: 400,
592 deferRequestBy: 300, //miliseconds
543 deferRequestBy: 300, //miliseconds
593 showNoSuggestionNotice: true,
544 showNoSuggestionNotice: true,
594 tabDisabled: true,
545 tabDisabled: true,
595 autoSelectFirst: true,
546 autoSelectFirst: true,
596 params: {
547 params: {
597 user_id: templateContext.rhodecode_user.user_id,
548 user_id: templateContext.rhodecode_user.user_id,
598 user_groups: true,
549 user_groups: true,
599 user_groups_expand: true,
550 user_groups_expand: true,
600 skip_default_user: true
551 skip_default_user: true
601 },
552 },
602 formatResult: autocompleteFormatResult,
553 formatResult: autocompleteFormatResult,
603 lookupFilter: autocompleteFilterResult,
554 lookupFilter: autocompleteFilterResult,
604 onSelect: handler
555 onSelect: handler
605 });
556 });
606 };
557 };
607
558
608 /**
559 /**
609 * Observers autocomplete
560 * Observers autocomplete
610 */
561 */
611 var ObserverAutoComplete = function(inputId, controller) {
562 var ObserverAutoComplete = function(inputId, controller) {
612 var self = this;
563 var self = this;
613 self.controller = controller;
564 self.controller = controller;
614 self.inputId = inputId;
565 self.inputId = inputId;
615 var handler = autoCompleteHandler(inputId, controller, controller.ROLE_OBSERVER);
566 var handler = autoCompleteHandler(inputId, controller, controller.ROLE_OBSERVER);
616
567
617 $(inputId).autocomplete({
568 $(inputId).autocomplete({
618 serviceUrl: pyroutes.url('user_autocomplete_data'),
569 serviceUrl: pyroutes.url('user_autocomplete_data'),
619 minChars: 2,
570 minChars: 2,
620 maxHeight: 400,
571 maxHeight: 400,
621 deferRequestBy: 300, //miliseconds
572 deferRequestBy: 300, //miliseconds
622 showNoSuggestionNotice: true,
573 showNoSuggestionNotice: true,
623 tabDisabled: true,
574 tabDisabled: true,
624 autoSelectFirst: true,
575 autoSelectFirst: true,
625 params: {
576 params: {
626 user_id: templateContext.rhodecode_user.user_id,
577 user_id: templateContext.rhodecode_user.user_id,
627 user_groups: true,
578 user_groups: true,
628 user_groups_expand: true,
579 user_groups_expand: true,
629 skip_default_user: true
580 skip_default_user: true
630 },
581 },
631 formatResult: autocompleteFormatResult,
582 formatResult: autocompleteFormatResult,
632 lookupFilter: autocompleteFilterResult,
583 lookupFilter: autocompleteFilterResult,
633 onSelect: handler
584 onSelect: handler
634 });
585 });
635 }
586 }
636
587
637
588
638 window.VersionController = function () {
589 window.VersionController = function () {
639 var self = this;
590 var self = this;
640 this.$verSource = $('input[name=ver_source]');
591 this.$verSource = $('input[name=ver_source]');
641 this.$verTarget = $('input[name=ver_target]');
592 this.$verTarget = $('input[name=ver_target]');
642 this.$showVersionDiff = $('#show-version-diff');
593 this.$showVersionDiff = $('#show-version-diff');
643
594
644 this.adjustRadioSelectors = function (curNode) {
595 this.adjustRadioSelectors = function (curNode) {
645 var getVal = function (item) {
596 var getVal = function (item) {
646 if (item === 'latest') {
597 if (item === 'latest') {
647 return Number.MAX_SAFE_INTEGER
598 return Number.MAX_SAFE_INTEGER
648 }
599 }
649 else {
600 else {
650 return parseInt(item)
601 return parseInt(item)
651 }
602 }
652 };
603 };
653
604
654 var curVal = getVal($(curNode).val());
605 var curVal = getVal($(curNode).val());
655 var cleared = false;
606 var cleared = false;
656
607
657 $.each(self.$verSource, function (index, value) {
608 $.each(self.$verSource, function (index, value) {
658 var elVal = getVal($(value).val());
609 var elVal = getVal($(value).val());
659
610
660 if (elVal > curVal) {
611 if (elVal > curVal) {
661 if ($(value).is(':checked')) {
612 if ($(value).is(':checked')) {
662 cleared = true;
613 cleared = true;
663 }
614 }
664 $(value).attr('disabled', 'disabled');
615 $(value).attr('disabled', 'disabled');
665 $(value).removeAttr('checked');
616 $(value).removeAttr('checked');
666 $(value).css({'opacity': 0.1});
617 $(value).css({'opacity': 0.1});
667 }
618 }
668 else {
619 else {
669 $(value).css({'opacity': 1});
620 $(value).css({'opacity': 1});
670 $(value).removeAttr('disabled');
621 $(value).removeAttr('disabled');
671 }
622 }
672 });
623 });
673
624
674 if (cleared) {
625 if (cleared) {
675 // if we unchecked an active, set the next one to same loc.
626 // if we unchecked an active, set the next one to same loc.
676 $(this.$verSource).filter('[value={0}]'.format(
627 $(this.$verSource).filter('[value={0}]'.format(
677 curVal)).attr('checked', 'checked');
628 curVal)).attr('checked', 'checked');
678 }
629 }
679
630
680 self.setLockAction(false,
631 self.setLockAction(false,
681 $(curNode).data('verPos'),
632 $(curNode).data('verPos'),
682 $(this.$verSource).filter(':checked').data('verPos')
633 $(this.$verSource).filter(':checked').data('verPos')
683 );
634 );
684 };
635 };
685
636
686
637
687 this.attachVersionListener = function () {
638 this.attachVersionListener = function () {
688 self.$verTarget.change(function (e) {
639 self.$verTarget.change(function (e) {
689 self.adjustRadioSelectors(this)
640 self.adjustRadioSelectors(this)
690 });
641 });
691 self.$verSource.change(function (e) {
642 self.$verSource.change(function (e) {
692 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
643 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
693 });
644 });
694 };
645 };
695
646
696 this.init = function () {
647 this.init = function () {
697
648
698 var curNode = self.$verTarget.filter(':checked');
649 var curNode = self.$verTarget.filter(':checked');
699 self.adjustRadioSelectors(curNode);
650 self.adjustRadioSelectors(curNode);
700 self.setLockAction(true);
651 self.setLockAction(true);
701 self.attachVersionListener();
652 self.attachVersionListener();
702
653
703 };
654 };
704
655
705 this.setLockAction = function (state, selectedVersion, otherVersion) {
656 this.setLockAction = function (state, selectedVersion, otherVersion) {
706 var $showVersionDiff = this.$showVersionDiff;
657 var $showVersionDiff = this.$showVersionDiff;
707
658
708 if (state) {
659 if (state) {
709 $showVersionDiff.attr('disabled', 'disabled');
660 $showVersionDiff.attr('disabled', 'disabled');
710 $showVersionDiff.addClass('disabled');
661 $showVersionDiff.addClass('disabled');
711 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
662 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
712 }
663 }
713 else {
664 else {
714 $showVersionDiff.removeAttr('disabled');
665 $showVersionDiff.removeAttr('disabled');
715 $showVersionDiff.removeClass('disabled');
666 $showVersionDiff.removeClass('disabled');
716
667
717 if (selectedVersion == otherVersion) {
668 if (selectedVersion == otherVersion) {
718 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
669 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
719 } else {
670 } else {
720 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
671 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
721 }
672 }
722 }
673 }
723
674
724 };
675 };
725
676
726 this.showVersionDiff = function () {
677 this.showVersionDiff = function () {
727 var target = self.$verTarget.filter(':checked');
678 var target = self.$verTarget.filter(':checked');
728 var source = self.$verSource.filter(':checked');
679 var source = self.$verSource.filter(':checked');
729
680
730 if (target.val() && source.val()) {
681 if (target.val() && source.val()) {
731 var params = {
682 var params = {
732 'pull_request_id': templateContext.pull_request_data.pull_request_id,
683 'pull_request_id': templateContext.pull_request_data.pull_request_id,
733 'repo_name': templateContext.repo_name,
684 'repo_name': templateContext.repo_name,
734 'version': target.val(),
685 'version': target.val(),
735 'from_version': source.val()
686 'from_version': source.val()
736 };
687 };
737 window.location = pyroutes.url('pullrequest_show', params)
688 window.location = pyroutes.url('pullrequest_show', params)
738 }
689 }
739
690
740 return false;
691 return false;
741 };
692 };
742
693
743 this.toggleVersionView = function (elem) {
694 this.toggleVersionView = function (elem) {
744
695
745 if (this.$showVersionDiff.is(':visible')) {
696 if (this.$showVersionDiff.is(':visible')) {
746 $('.version-pr').hide();
697 $('.version-pr').hide();
747 this.$showVersionDiff.hide();
698 this.$showVersionDiff.hide();
748 $(elem).html($(elem).data('toggleOn'))
699 $(elem).html($(elem).data('toggleOn'))
749 } else {
700 } else {
750 $('.version-pr').show();
701 $('.version-pr').show();
751 this.$showVersionDiff.show();
702 this.$showVersionDiff.show();
752 $(elem).html($(elem).data('toggleOff'))
703 $(elem).html($(elem).data('toggleOff'))
753 }
704 }
754
705
755 return false
706 return false
756 };
707 };
757
708
758 };
709 };
759
710
760
711
761 window.UpdatePrController = function () {
712 window.UpdatePrController = function () {
762 var self = this;
713 var self = this;
763 this.$updateCommits = $('#update_commits');
714 this.$updateCommits = $('#update_commits');
764 this.$updateCommitsSwitcher = $('#update_commits_switcher');
715 this.$updateCommitsSwitcher = $('#update_commits_switcher');
765
716
766 this.lockUpdateButton = function (label) {
717 this.lockUpdateButton = function (label) {
767 self.$updateCommits.attr('disabled', 'disabled');
718 self.$updateCommits.attr('disabled', 'disabled');
768 self.$updateCommitsSwitcher.attr('disabled', 'disabled');
719 self.$updateCommitsSwitcher.attr('disabled', 'disabled');
769
720
770 self.$updateCommits.addClass('disabled');
721 self.$updateCommits.addClass('disabled');
771 self.$updateCommitsSwitcher.addClass('disabled');
722 self.$updateCommitsSwitcher.addClass('disabled');
772
723
773 self.$updateCommits.removeClass('btn-primary');
724 self.$updateCommits.removeClass('btn-primary');
774 self.$updateCommitsSwitcher.removeClass('btn-primary');
725 self.$updateCommitsSwitcher.removeClass('btn-primary');
775
726
776 self.$updateCommits.text(_gettext(label));
727 self.$updateCommits.text(_gettext(label));
777 };
728 };
778
729
779 this.isUpdateLocked = function () {
730 this.isUpdateLocked = function () {
780 return self.$updateCommits.attr('disabled') !== undefined;
731 return self.$updateCommits.attr('disabled') !== undefined;
781 };
732 };
782
733
783 this.updateCommits = function (curNode) {
734 this.updateCommits = function (curNode) {
784 if (self.isUpdateLocked()) {
735 if (self.isUpdateLocked()) {
785 return
736 return
786 }
737 }
787 self.lockUpdateButton(_gettext('Updating...'));
738 self.lockUpdateButton(_gettext('Updating...'));
788 updateCommits(
739 updateCommits(
789 templateContext.repo_name,
740 templateContext.repo_name,
790 templateContext.pull_request_data.pull_request_id);
741 templateContext.pull_request_data.pull_request_id);
791 };
742 };
792
743
793 this.forceUpdateCommits = function () {
744 this.forceUpdateCommits = function () {
794 if (self.isUpdateLocked()) {
745 if (self.isUpdateLocked()) {
795 return
746 return
796 }
747 }
797 self.lockUpdateButton(_gettext('Force updating...'));
748 self.lockUpdateButton(_gettext('Force updating...'));
798 var force = true;
749 var force = true;
799 updateCommits(
750 updateCommits(
800 templateContext.repo_name,
751 templateContext.repo_name,
801 templateContext.pull_request_data.pull_request_id, force);
752 templateContext.pull_request_data.pull_request_id, force);
802 };
753 };
803 };
754 };
804
755
805
756
806 /**
757 /**
807 * Reviewer display panel
758 * Reviewer display panel
808 */
759 */
809 window.ReviewersPanel = {
760 window.ReviewersPanel = {
810 editButton: null,
761 editButton: null,
811 closeButton: null,
762 closeButton: null,
812 addButton: null,
763 addButton: null,
813 removeButtons: null,
764 removeButtons: null,
814 reviewRules: null,
765 reviewRules: null,
815 setReviewers: null,
766 setReviewers: null,
816 controller: null,
767 controller: null,
817
768
818 setSelectors: function () {
769 setSelectors: function () {
819 var self = this;
770 var self = this;
820 self.editButton = $('#open_edit_reviewers');
771 self.editButton = $('#open_edit_reviewers');
821 self.closeButton =$('#close_edit_reviewers');
772 self.closeButton =$('#close_edit_reviewers');
822 self.addButton = $('#add_reviewer');
773 self.addButton = $('#add_reviewer');
823 self.removeButtons = $('.reviewer_member_remove,.reviewer_member_mandatory_remove');
774 self.removeButtons = $('.reviewer_member_remove,.reviewer_member_mandatory_remove');
824 },
775 },
825
776
826 init: function (controller, reviewRules, setReviewers) {
777 init: function (controller, reviewRules, setReviewers) {
827 var self = this;
778 var self = this;
828 self.setSelectors();
779 self.setSelectors();
829
780
830 self.controller = controller;
781 self.controller = controller;
831 self.reviewRules = reviewRules;
782 self.reviewRules = reviewRules;
832 self.setReviewers = setReviewers;
783 self.setReviewers = setReviewers;
833
784
834 self.editButton.on('click', function (e) {
785 self.editButton.on('click', function (e) {
835 self.edit();
786 self.edit();
836 });
787 });
837 self.closeButton.on('click', function (e) {
788 self.closeButton.on('click', function (e) {
838 self.close();
789 self.close();
839 self.renderReviewers();
790 self.renderReviewers();
840 });
791 });
841
792
842 self.renderReviewers();
793 self.renderReviewers();
843
794
844 },
795 },
845
796
846 renderReviewers: function () {
797 renderReviewers: function () {
847 var self = this;
798 var self = this;
848
799
849 if (self.setReviewers.reviewers === undefined) {
800 if (self.setReviewers.reviewers === undefined) {
850 return
801 return
851 }
802 }
852 if (self.setReviewers.reviewers.length === 0) {
803 if (self.setReviewers.reviewers.length === 0) {
853 self.controller.emptyReviewersTable('<tr id="reviewer-empty-msg"><td colspan="6">No reviewers</td></tr>');
804 self.controller.emptyReviewersTable('<tr id="reviewer-empty-msg"><td colspan="6">No reviewers</td></tr>');
854 return
805 return
855 }
806 }
856
807
857 self.controller.emptyReviewersTable();
808 self.controller.emptyReviewersTable();
858
809
859 $.each(self.setReviewers.reviewers, function (key, val) {
810 $.each(self.setReviewers.reviewers, function (key, val) {
860
811
861 var member = val;
812 var member = val;
862 if (member.role === self.controller.ROLE_REVIEWER) {
813 if (member.role === self.controller.ROLE_REVIEWER) {
863 var entry = renderTemplate('reviewMemberEntry', {
814 var entry = renderTemplate('reviewMemberEntry', {
864 'member': member,
815 'member': member,
865 'mandatory': member.mandatory,
816 'mandatory': member.mandatory,
866 'role': member.role,
817 'role': member.role,
867 'reasons': member.reasons,
818 'reasons': member.reasons,
868 'allowed_to_update': member.allowed_to_update,
819 'allowed_to_update': member.allowed_to_update,
869 'review_status': member.review_status,
820 'review_status': member.review_status,
870 'review_status_label': member.review_status_label,
821 'review_status_label': member.review_status_label,
871 'user_group': member.user_group,
822 'user_group': member.user_group,
872 'create': false
823 'create': false
873 });
824 });
874
825
875 $(self.controller.$reviewMembers.selector).append(entry)
826 $(self.controller.$reviewMembers.selector).append(entry)
876 }
827 }
877 });
828 });
878
829
879 tooltipActivate();
830 tooltipActivate();
880 },
831 },
881
832
882 edit: function (event) {
833 edit: function (event) {
883 var self = this;
834 var self = this;
884 self.editButton.hide();
835 self.editButton.hide();
885 self.closeButton.show();
836 self.closeButton.show();
886 self.addButton.show();
837 self.addButton.show();
887 $(self.removeButtons.selector).css('visibility', 'visible');
838 $(self.removeButtons.selector).css('visibility', 'visible');
888 // review rules
839 // review rules
889 self.controller.loadReviewRules(this.reviewRules);
840 self.controller.loadReviewRules(this.reviewRules);
890 },
841 },
891
842
892 close: function (event) {
843 close: function (event) {
893 var self = this;
844 var self = this;
894 this.editButton.show();
845 this.editButton.show();
895 this.closeButton.hide();
846 this.closeButton.hide();
896 this.addButton.hide();
847 this.addButton.hide();
897 $(this.removeButtons.selector).css('visibility', 'hidden');
848 $(this.removeButtons.selector).css('visibility', 'hidden');
898 // hide review rules
849 // hide review rules
899 self.controller.hideReviewRules();
850 self.controller.hideReviewRules();
900 }
851 }
901 };
852 };
902
853
903 /**
854 /**
904 * Reviewer display panel
855 * Reviewer display panel
905 */
856 */
906 window.ObserversPanel = {
857 window.ObserversPanel = {
907 editButton: null,
858 editButton: null,
908 closeButton: null,
859 closeButton: null,
909 addButton: null,
860 addButton: null,
910 removeButtons: null,
861 removeButtons: null,
911 reviewRules: null,
862 reviewRules: null,
912 setReviewers: null,
863 setReviewers: null,
913 controller: null,
864 controller: null,
914
865
915 setSelectors: function () {
866 setSelectors: function () {
916 var self = this;
867 var self = this;
917 self.editButton = $('#open_edit_observers');
868 self.editButton = $('#open_edit_observers');
918 self.closeButton =$('#close_edit_observers');
869 self.closeButton =$('#close_edit_observers');
919 self.addButton = $('#add_observer');
870 self.addButton = $('#add_observer');
920 self.removeButtons = $('.observer_member_remove,.observer_member_mandatory_remove');
871 self.removeButtons = $('.observer_member_remove,.observer_member_mandatory_remove');
921 },
872 },
922
873
923 init: function (controller, reviewRules, setReviewers) {
874 init: function (controller, reviewRules, setReviewers) {
924 var self = this;
875 var self = this;
925 self.setSelectors();
876 self.setSelectors();
926
877
927 self.controller = controller;
878 self.controller = controller;
928 self.reviewRules = reviewRules;
879 self.reviewRules = reviewRules;
929 self.setReviewers = setReviewers;
880 self.setReviewers = setReviewers;
930
881
931 self.editButton.on('click', function (e) {
882 self.editButton.on('click', function (e) {
932 self.edit();
883 self.edit();
933 });
884 });
934 self.closeButton.on('click', function (e) {
885 self.closeButton.on('click', function (e) {
935 self.close();
886 self.close();
936 self.renderObservers();
887 self.renderObservers();
937 });
888 });
938
889
939 self.renderObservers();
890 self.renderObservers();
940
891
941 },
892 },
942
893
943 renderObservers: function () {
894 renderObservers: function () {
944 var self = this;
895 var self = this;
945 if (self.setReviewers.observers === undefined) {
896 if (self.setReviewers.observers === undefined) {
946 return
897 return
947 }
898 }
948 if (self.setReviewers.observers.length === 0) {
899 if (self.setReviewers.observers.length === 0) {
949 self.controller.emptyObserversTable('<tr id="observer-empty-msg"><td colspan="6">No observers</td></tr>');
900 self.controller.emptyObserversTable('<tr id="observer-empty-msg"><td colspan="6">No observers</td></tr>');
950 return
901 return
951 }
902 }
952
903
953 self.controller.emptyObserversTable();
904 self.controller.emptyObserversTable();
954
905
955 $.each(self.setReviewers.observers, function (key, val) {
906 $.each(self.setReviewers.observers, function (key, val) {
956 var member = val;
907 var member = val;
957 if (member.role === self.controller.ROLE_OBSERVER) {
908 if (member.role === self.controller.ROLE_OBSERVER) {
958 var entry = renderTemplate('reviewMemberEntry', {
909 var entry = renderTemplate('reviewMemberEntry', {
959 'member': member,
910 'member': member,
960 'mandatory': member.mandatory,
911 'mandatory': member.mandatory,
961 'role': member.role,
912 'role': member.role,
962 'reasons': member.reasons,
913 'reasons': member.reasons,
963 'allowed_to_update': member.allowed_to_update,
914 'allowed_to_update': member.allowed_to_update,
964 'review_status': member.review_status,
915 'review_status': member.review_status,
965 'review_status_label': member.review_status_label,
916 'review_status_label': member.review_status_label,
966 'user_group': member.user_group,
917 'user_group': member.user_group,
967 'create': false
918 'create': false
968 });
919 });
969
920
970 $(self.controller.$observerMembers.selector).append(entry)
921 $(self.controller.$observerMembers.selector).append(entry)
971 }
922 }
972 });
923 });
973
924
974 tooltipActivate();
925 tooltipActivate();
975 },
926 },
976
927
977 edit: function (event) {
928 edit: function (event) {
978 this.editButton.hide();
929 this.editButton.hide();
979 this.closeButton.show();
930 this.closeButton.show();
980 this.addButton.show();
931 this.addButton.show();
981 $(this.removeButtons.selector).css('visibility', 'visible');
932 $(this.removeButtons.selector).css('visibility', 'visible');
982 },
933 },
983
934
984 close: function (event) {
935 close: function (event) {
985 this.editButton.show();
936 this.editButton.show();
986 this.closeButton.hide();
937 this.closeButton.hide();
987 this.addButton.hide();
938 this.addButton.hide();
988 $(this.removeButtons.selector).css('visibility', 'hidden');
939 $(this.removeButtons.selector).css('visibility', 'hidden');
989 }
940 }
990
941
991 };
942 };
992
943
993 window.PRDetails = {
944 window.PRDetails = {
994 editButton: null,
945 editButton: null,
995 closeButton: null,
946 closeButton: null,
996 deleteButton: null,
947 deleteButton: null,
997 viewFields: null,
948 viewFields: null,
998 editFields: null,
949 editFields: null,
999
950
1000 setSelectors: function () {
951 setSelectors: function () {
1001 var self = this;
952 var self = this;
1002 self.editButton = $('#open_edit_pullrequest')
953 self.editButton = $('#open_edit_pullrequest')
1003 self.closeButton = $('#close_edit_pullrequest')
954 self.closeButton = $('#close_edit_pullrequest')
1004 self.deleteButton = $('#delete_pullrequest')
955 self.deleteButton = $('#delete_pullrequest')
1005 self.viewFields = $('#pr-desc, #pr-title')
956 self.viewFields = $('#pr-desc, #pr-title')
1006 self.editFields = $('#pr-desc-edit, #pr-title-edit, .pr-save')
957 self.editFields = $('#pr-desc-edit, #pr-title-edit, .pr-save')
1007 },
958 },
1008
959
1009 init: function () {
960 init: function () {
1010 var self = this;
961 var self = this;
1011 self.setSelectors();
962 self.setSelectors();
1012 self.editButton.on('click', function (e) {
963 self.editButton.on('click', function (e) {
1013 self.edit();
964 self.edit();
1014 });
965 });
1015 self.closeButton.on('click', function (e) {
966 self.closeButton.on('click', function (e) {
1016 self.view();
967 self.view();
1017 });
968 });
1018 },
969 },
1019
970
1020 edit: function (event) {
971 edit: function (event) {
1021 var cmInstance = $('#pr-description-input').get(0).MarkupForm.cm;
972 var cmInstance = $('#pr-description-input').get(0).MarkupForm.cm;
1022 this.viewFields.hide();
973 this.viewFields.hide();
1023 this.editButton.hide();
974 this.editButton.hide();
1024 this.deleteButton.hide();
975 this.deleteButton.hide();
1025 this.closeButton.show();
976 this.closeButton.show();
1026 this.editFields.show();
977 this.editFields.show();
1027 cmInstance.refresh();
978 cmInstance.refresh();
1028 },
979 },
1029
980
1030 view: function (event) {
981 view: function (event) {
1031 this.editButton.show();
982 this.editButton.show();
1032 this.deleteButton.show();
983 this.deleteButton.show();
1033 this.editFields.hide();
984 this.editFields.hide();
1034 this.closeButton.hide();
985 this.closeButton.hide();
1035 this.viewFields.show();
986 this.viewFields.show();
1036 }
987 }
1037 };
988 };
1038
989
1039 /**
990 /**
1040 * OnLine presence using channelstream
991 * OnLine presence using channelstream
1041 */
992 */
1042 window.ReviewerPresenceController = function (channel) {
993 window.ReviewerPresenceController = function (channel) {
1043 var self = this;
994 var self = this;
1044 this.channel = channel;
995 this.channel = channel;
1045 this.users = {};
996 this.users = {};
1046
997
1047 this.storeUsers = function (users) {
998 this.storeUsers = function (users) {
1048 self.users = {}
999 self.users = {}
1049 $.each(users, function (index, value) {
1000 $.each(users, function (index, value) {
1050 var userId = value.state.id;
1001 var userId = value.state.id;
1051 self.users[userId] = value.state;
1002 self.users[userId] = value.state;
1052 })
1003 })
1053 }
1004 }
1054
1005
1055 this.render = function () {
1006 this.render = function () {
1056 $.each($('.reviewer_entry'), function (index, value) {
1007 $.each($('.reviewer_entry'), function (index, value) {
1057 var userData = $(value).data();
1008 var userData = $(value).data();
1058 if (self.users[userData.reviewerUserId] !== undefined) {
1009 if (self.users[userData.reviewerUserId] !== undefined) {
1059 $(value).find('.presence-state').show();
1010 $(value).find('.presence-state').show();
1060 } else {
1011 } else {
1061 $(value).find('.presence-state').hide();
1012 $(value).find('.presence-state').hide();
1062 }
1013 }
1063 })
1014 })
1064 };
1015 };
1065
1016
1066 this.handlePresence = function (data) {
1017 this.handlePresence = function (data) {
1067 if (data.type == 'presence' && data.channel === self.channel) {
1018 if (data.type == 'presence' && data.channel === self.channel) {
1068 this.storeUsers(data.users);
1019 this.storeUsers(data.users);
1069 this.render();
1020 this.render();
1070 }
1021 }
1071 };
1022 };
1072
1023
1073 this.handleChannelUpdate = function (data) {
1024 this.handleChannelUpdate = function (data) {
1074 if (data.channel === this.channel) {
1025 if (data.channel === this.channel) {
1075 this.storeUsers(data.state.users);
1026 this.storeUsers(data.state.users);
1076 this.render();
1027 this.render();
1077 }
1028 }
1078
1029
1079 };
1030 };
1080
1031
1081 /* subscribe to the current presence */
1032 /* subscribe to the current presence */
1082 $.Topic('/connection_controller/presence').subscribe(this.handlePresence.bind(this));
1033 $.Topic('/connection_controller/presence').subscribe(this.handlePresence.bind(this));
1083 /* subscribe to updates e.g connect/disconnect */
1034 /* subscribe to updates e.g connect/disconnect */
1084 $.Topic('/connection_controller/channel_update').subscribe(this.handleChannelUpdate.bind(this));
1035 $.Topic('/connection_controller/channel_update').subscribe(this.handleChannelUpdate.bind(this));
1085
1036
1086 };
1037 };
1087
1038
1088 window.refreshComments = function (version) {
1039 window.refreshComments = function (version) {
1089 version = version || templateContext.pull_request_data.pull_request_version || '';
1040 version = version || templateContext.pull_request_data.pull_request_version || '';
1090
1041
1091 // Pull request case
1042 // Pull request case
1092 if (templateContext.pull_request_data.pull_request_id !== null) {
1043 if (templateContext.pull_request_data.pull_request_id !== null) {
1093 var params = {
1044 var params = {
1094 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1045 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1095 'repo_name': templateContext.repo_name,
1046 'repo_name': templateContext.repo_name,
1096 'version': version,
1047 'version': version,
1097 };
1048 };
1098 var loadUrl = pyroutes.url('pullrequest_comments', params);
1049 var loadUrl = pyroutes.url('pullrequest_comments', params);
1099 } // commit case
1050 } // commit case
1100 else {
1051 else {
1101 return
1052 return
1102 }
1053 }
1103
1054
1104 var currentIDs = []
1055 var currentIDs = []
1105 $.each($('.comment'), function (idx, element) {
1056 $.each($('.comment'), function (idx, element) {
1106 currentIDs.push($(element).data('commentId'));
1057 currentIDs.push($(element).data('commentId'));
1107 });
1058 });
1108 var data = {"comments": currentIDs};
1059 var data = {"comments": currentIDs};
1109
1060
1110 var $targetElem = $('.comments-content-table');
1061 var $targetElem = $('.comments-content-table');
1111 $targetElem.css('opacity', 0.3);
1062 $targetElem.css('opacity', 0.3);
1112
1063
1113 var success = function (data) {
1064 var success = function (data) {
1114 var $counterElem = $('#comments-count');
1065 var $counterElem = $('#comments-count');
1115 var newCount = $(data).data('counter');
1066 var newCount = $(data).data('counter');
1116 if (newCount !== undefined) {
1067 if (newCount !== undefined) {
1117 var callback = function () {
1068 var callback = function () {
1118 $counterElem.animate({'opacity': 1.00}, 200)
1069 $counterElem.animate({'opacity': 1.00}, 200)
1119 $counterElem.html(newCount);
1070 $counterElem.html(newCount);
1120 };
1071 };
1121 $counterElem.animate({'opacity': 0.15}, 200, callback);
1072 $counterElem.animate({'opacity': 0.15}, 200, callback);
1122 }
1073 }
1123
1074
1124 $targetElem.css('opacity', 1);
1075 $targetElem.css('opacity', 1);
1125 $targetElem.html(data);
1076 $targetElem.html(data);
1126 tooltipActivate();
1077 tooltipActivate();
1127 }
1078 }
1128
1079
1129 ajaxPOST(loadUrl, data, success, null, {})
1080 ajaxPOST(loadUrl, data, success, null, {})
1130
1081
1131 }
1082 }
1132
1083
1133 window.refreshTODOs = function (version) {
1084 window.refreshTODOs = function (version) {
1134 version = version || templateContext.pull_request_data.pull_request_version || '';
1085 version = version || templateContext.pull_request_data.pull_request_version || '';
1135 // Pull request case
1086 // Pull request case
1136 if (templateContext.pull_request_data.pull_request_id !== null) {
1087 if (templateContext.pull_request_data.pull_request_id !== null) {
1137 var params = {
1088 var params = {
1138 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1089 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1139 'repo_name': templateContext.repo_name,
1090 'repo_name': templateContext.repo_name,
1140 'version': version,
1091 'version': version,
1141 };
1092 };
1142 var loadUrl = pyroutes.url('pullrequest_todos', params);
1093 var loadUrl = pyroutes.url('pullrequest_todos', params);
1143 } // commit case
1094 } // commit case
1144 else {
1095 else {
1145 return
1096 return
1146 }
1097 }
1147
1098
1148 var currentIDs = []
1099 var currentIDs = []
1149 $.each($('.comment'), function (idx, element) {
1100 $.each($('.comment'), function (idx, element) {
1150 currentIDs.push($(element).data('commentId'));
1101 currentIDs.push($(element).data('commentId'));
1151 });
1102 });
1152
1103
1153 var data = {"comments": currentIDs};
1104 var data = {"comments": currentIDs};
1154 var $targetElem = $('.todos-content-table');
1105 var $targetElem = $('.todos-content-table');
1155 $targetElem.css('opacity', 0.3);
1106 $targetElem.css('opacity', 0.3);
1156
1107
1157 var success = function (data) {
1108 var success = function (data) {
1158 var $counterElem = $('#todos-count')
1109 var $counterElem = $('#todos-count')
1159 var newCount = $(data).data('counter');
1110 var newCount = $(data).data('counter');
1160 if (newCount !== undefined) {
1111 if (newCount !== undefined) {
1161 var callback = function () {
1112 var callback = function () {
1162 $counterElem.animate({'opacity': 1.00}, 200)
1113 $counterElem.animate({'opacity': 1.00}, 200)
1163 $counterElem.html(newCount);
1114 $counterElem.html(newCount);
1164 };
1115 };
1165 $counterElem.animate({'opacity': 0.15}, 200, callback);
1116 $counterElem.animate({'opacity': 0.15}, 200, callback);
1166 }
1117 }
1167
1118
1168 $targetElem.css('opacity', 1);
1119 $targetElem.css('opacity', 1);
1169 $targetElem.html(data);
1120 $targetElem.html(data);
1170 tooltipActivate();
1121 tooltipActivate();
1171 }
1122 }
1172
1123
1173 ajaxPOST(loadUrl, data, success, null, {})
1124 ajaxPOST(loadUrl, data, success, null, {})
1174
1125
1175 }
1126 }
1176
1127
1177 window.refreshAllComments = function (version) {
1128 window.refreshAllComments = function (version) {
1178 version = version || templateContext.pull_request_data.pull_request_version || '';
1129 version = version || templateContext.pull_request_data.pull_request_version || '';
1179
1130
1180 refreshComments(version);
1131 refreshComments(version);
1181 refreshTODOs(version);
1132 refreshTODOs(version);
1182 };
1133 };
1183
1134
1184 window.refreshDraftComments = function () {
1135 window.refreshDraftComments = function () {
1185 alert('TODO: refresh Draft Comments needs implementation')
1136 alert('TODO: refresh Draft Comments needs implementation')
1186 };
1137 };
1187
1138
1188 window.sidebarComment = function (commentId) {
1139 window.sidebarComment = function (commentId) {
1189 var jsonData = $('#commentHovercard{0}'.format(commentId)).data('commentJsonB64');
1140 var jsonData = $('#commentHovercard{0}'.format(commentId)).data('commentJsonB64');
1190 if (!jsonData) {
1141 if (!jsonData) {
1191 return 'Failed to load comment {0}'.format(commentId)
1142 return 'Failed to load comment {0}'.format(commentId)
1192 }
1143 }
1193 var funcData = JSON.parse(atob(jsonData));
1144 var funcData = JSON.parse(atob(jsonData));
1194 return renderTemplate('sideBarCommentHovercard', funcData)
1145 return renderTemplate('sideBarCommentHovercard', funcData)
1195 };
1146 };
General Comments 0
You need to be logged in to leave comments. Login now