##// END OF EJS Templates
pull-requests: limit the ammount of data saved in default reviewers data for better memory usage...
marcink -
r4509:b5299f6d stable
parent child
Show More
@@ -1,95 +1,111
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
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 REVIEWER_API_VERSION = 'V4'
28
28
29
29
30 def reviewer_as_json(user, reasons=None, role=None, mandatory=False, rules=None, user_group=None):
30 def reviewer_as_json(user, reasons=None, role=None, mandatory=False, rules=None, user_group=None):
31 """
31 """
32 Returns json struct of a reviewer for frontend
32 Returns json struct of a reviewer for frontend
33
33
34 :param user: the reviewer
34 :param user: the reviewer
35 :param reasons: list of strings of why they are reviewers
35 :param reasons: list of strings of why they are reviewers
36 :param mandatory: bool, to set user as mandatory
36 :param mandatory: bool, to set user as mandatory
37 """
37 """
38 role = role or PullRequestReviewers.ROLE_REVIEWER
38 role = role or PullRequestReviewers.ROLE_REVIEWER
39 if role not in PullRequestReviewers.ROLES:
39 if role not in PullRequestReviewers.ROLES:
40 raise ValueError('role is not one of %s', PullRequestReviewers.ROLES)
40 raise ValueError('role is not one of %s', PullRequestReviewers.ROLES)
41
41
42 return {
42 return {
43 'user_id': user.user_id,
43 'user_id': user.user_id,
44 'reasons': reasons or [],
44 'reasons': reasons or [],
45 'rules': rules or [],
45 'rules': rules or [],
46 'role': role,
46 'role': role,
47 'mandatory': mandatory,
47 'mandatory': mandatory,
48 'user_group': user_group,
48 'user_group': user_group,
49 'username': user.username,
49 'username': user.username,
50 'first_name': user.first_name,
50 'first_name': user.first_name,
51 'last_name': user.last_name,
51 'last_name': user.last_name,
52 'user_link': h.link_to_user(user),
52 'user_link': h.link_to_user(user),
53 'gravatar_link': h.gravatar_url(user.email, 14),
53 'gravatar_link': h.gravatar_url(user.email, 14),
54 }
54 }
55
55
56
56
57 def get_default_reviewers_data(current_user, source_repo, source_commit, target_repo, target_commit):
57 def to_reviewers(e):
58 if isinstance(e, (tuple, list)):
59 return map(reviewer_as_json, e)
60 else:
61 return reviewer_as_json(e)
62
63
64 def get_default_reviewers_data(current_user, source_repo, source_ref, target_repo, target_ref,
65 include_diff_info=True):
58 """
66 """
59 Return json for default reviewers of a repository
67 Return json for default reviewers of a repository
60 """
68 """
61
69
62 diff_info = get_diff_info(
70 diff_info = {}
63 source_repo, source_commit.raw_id, target_repo, target_commit.raw_id)
71 if include_diff_info:
72 diff_info = get_diff_info(
73 source_repo, source_ref.commit_id, target_repo, target_ref.commit_id)
64
74
65 reasons = ['Default reviewer', 'Repository owner']
75 reasons = ['Default reviewer', 'Repository owner']
66 json_reviewers = [reviewer_as_json(
76 json_reviewers = [reviewer_as_json(
67 user=target_repo.user, reasons=reasons, mandatory=False, rules=None, role=None)]
77 user=target_repo.user, reasons=reasons, mandatory=False, rules=None, role=None)]
68
78
79 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 source_ref.commit_id, target_repo.repo_id, target_ref.type, target_ref.name,
82 target_ref.commit_id)
83
69 return {
84 return {
70 'api_ver': REVIEWER_API_VERSION, # define version for later possible schema upgrade
85 'api_ver': REVIEWER_API_VERSION, # define version for later possible schema upgrade
86 'compute_key': compute_key,
71 'diff_info': diff_info,
87 'diff_info': diff_info,
72 'reviewers': json_reviewers,
88 'reviewers': json_reviewers,
73 'rules': {},
89 'rules': {},
74 'rules_data': {},
90 'rules_data': {},
75 }
91 }
76
92
77
93
78 def validate_default_reviewers(review_members, reviewer_rules):
94 def validate_default_reviewers(review_members, reviewer_rules):
79 """
95 """
80 Function to validate submitted reviewers against the saved rules
96 Function to validate submitted reviewers against the saved rules
81 """
97 """
82 reviewers = []
98 reviewers = []
83 reviewer_by_id = {}
99 reviewer_by_id = {}
84 for r in review_members:
100 for r in review_members:
85 reviewer_user_id = safe_int(r['user_id'])
101 reviewer_user_id = safe_int(r['user_id'])
86 entry = (reviewer_user_id, r['reasons'], r['mandatory'], r['role'], r['rules'])
102 entry = (reviewer_user_id, r['reasons'], r['mandatory'], r['role'], r['rules'])
87
103
88 reviewer_by_id[reviewer_user_id] = entry
104 reviewer_by_id[reviewer_user_id] = entry
89 reviewers.append(entry)
105 reviewers.append(entry)
90
106
91 return reviewers
107 return reviewers
92
108
93
109
94 def validate_observers(observer_members):
110 def validate_observers(observer_members):
95 return {}
111 return {}
@@ -1,1806 +1,1811
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 EmptyCommit, UpdateFailureReason
43 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason, Reference
44 from rhodecode.lib.vcs.exceptions import (
44 from rhodecode.lib.vcs.exceptions import (
45 CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError)
45 CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError)
46 from rhodecode.model.changeset_status import ChangesetStatusModel
46 from rhodecode.model.changeset_status import ChangesetStatusModel
47 from rhodecode.model.comment import CommentsModel
47 from rhodecode.model.comment import CommentsModel
48 from rhodecode.model.db import (
48 from rhodecode.model.db import (
49 func, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository,
49 func, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository,
50 PullRequestReviewers)
50 PullRequestReviewers)
51 from rhodecode.model.forms import PullRequestForm
51 from rhodecode.model.forms import PullRequestForm
52 from rhodecode.model.meta import Session
52 from rhodecode.model.meta import Session
53 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
53 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
54 from rhodecode.model.scm import ScmModel
54 from rhodecode.model.scm import ScmModel
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 class RepoPullRequestsView(RepoAppView, DataGridAppView):
59 class RepoPullRequestsView(RepoAppView, DataGridAppView):
60
60
61 def load_default_context(self):
61 def load_default_context(self):
62 c = self._get_local_tmpl_context(include_app_defaults=True)
62 c = self._get_local_tmpl_context(include_app_defaults=True)
63 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
63 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
64 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
64 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
65 # backward compat., we use for OLD PRs a plain renderer
65 # backward compat., we use for OLD PRs a plain renderer
66 c.renderer = 'plain'
66 c.renderer = 'plain'
67 return c
67 return c
68
68
69 def _get_pull_requests_list(
69 def _get_pull_requests_list(
70 self, repo_name, source, filter_type, opened_by, statuses):
70 self, repo_name, source, filter_type, opened_by, statuses):
71
71
72 draw, start, limit = self._extract_chunk(self.request)
72 draw, start, limit = self._extract_chunk(self.request)
73 search_q, order_by, order_dir = self._extract_ordering(self.request)
73 search_q, order_by, order_dir = self._extract_ordering(self.request)
74 _render = self.request.get_partial_renderer(
74 _render = self.request.get_partial_renderer(
75 'rhodecode:templates/data_table/_dt_elements.mako')
75 'rhodecode:templates/data_table/_dt_elements.mako')
76
76
77 # pagination
77 # pagination
78
78
79 if filter_type == 'awaiting_review':
79 if filter_type == 'awaiting_review':
80 pull_requests = PullRequestModel().get_awaiting_review(
80 pull_requests = PullRequestModel().get_awaiting_review(
81 repo_name, search_q=search_q, source=source, opened_by=opened_by,
81 repo_name, search_q=search_q, source=source, opened_by=opened_by,
82 statuses=statuses, offset=start, length=limit,
82 statuses=statuses, offset=start, length=limit,
83 order_by=order_by, order_dir=order_dir)
83 order_by=order_by, order_dir=order_dir)
84 pull_requests_total_count = PullRequestModel().count_awaiting_review(
84 pull_requests_total_count = PullRequestModel().count_awaiting_review(
85 repo_name, search_q=search_q, source=source, statuses=statuses,
85 repo_name, search_q=search_q, source=source, statuses=statuses,
86 opened_by=opened_by)
86 opened_by=opened_by)
87 elif filter_type == 'awaiting_my_review':
87 elif filter_type == 'awaiting_my_review':
88 pull_requests = PullRequestModel().get_awaiting_my_review(
88 pull_requests = PullRequestModel().get_awaiting_my_review(
89 repo_name, search_q=search_q, source=source, opened_by=opened_by,
89 repo_name, search_q=search_q, source=source, opened_by=opened_by,
90 user_id=self._rhodecode_user.user_id, statuses=statuses,
90 user_id=self._rhodecode_user.user_id, statuses=statuses,
91 offset=start, length=limit, order_by=order_by,
91 offset=start, length=limit, order_by=order_by,
92 order_dir=order_dir)
92 order_dir=order_dir)
93 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
93 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
94 repo_name, search_q=search_q, source=source, user_id=self._rhodecode_user.user_id,
94 repo_name, search_q=search_q, source=source, user_id=self._rhodecode_user.user_id,
95 statuses=statuses, opened_by=opened_by)
95 statuses=statuses, opened_by=opened_by)
96 else:
96 else:
97 pull_requests = PullRequestModel().get_all(
97 pull_requests = PullRequestModel().get_all(
98 repo_name, search_q=search_q, source=source, opened_by=opened_by,
98 repo_name, search_q=search_q, source=source, opened_by=opened_by,
99 statuses=statuses, offset=start, length=limit,
99 statuses=statuses, offset=start, length=limit,
100 order_by=order_by, order_dir=order_dir)
100 order_by=order_by, order_dir=order_dir)
101 pull_requests_total_count = PullRequestModel().count_all(
101 pull_requests_total_count = PullRequestModel().count_all(
102 repo_name, search_q=search_q, source=source, statuses=statuses,
102 repo_name, search_q=search_q, source=source, statuses=statuses,
103 opened_by=opened_by)
103 opened_by=opened_by)
104
104
105 data = []
105 data = []
106 comments_model = CommentsModel()
106 comments_model = CommentsModel()
107 for pr in pull_requests:
107 for pr in pull_requests:
108 comments_count = comments_model.get_all_comments(
108 comments_count = comments_model.get_all_comments(
109 self.db_repo.repo_id, pull_request=pr, count_only=True)
109 self.db_repo.repo_id, pull_request=pr, count_only=True)
110
110
111 data.append({
111 data.append({
112 'name': _render('pullrequest_name',
112 'name': _render('pullrequest_name',
113 pr.pull_request_id, pr.pull_request_state,
113 pr.pull_request_id, pr.pull_request_state,
114 pr.work_in_progress, pr.target_repo.repo_name),
114 pr.work_in_progress, pr.target_repo.repo_name),
115 'name_raw': pr.pull_request_id,
115 'name_raw': pr.pull_request_id,
116 'status': _render('pullrequest_status',
116 'status': _render('pullrequest_status',
117 pr.calculated_review_status()),
117 pr.calculated_review_status()),
118 'title': _render('pullrequest_title', pr.title, pr.description),
118 'title': _render('pullrequest_title', pr.title, pr.description),
119 'description': h.escape(pr.description),
119 'description': h.escape(pr.description),
120 'updated_on': _render('pullrequest_updated_on',
120 'updated_on': _render('pullrequest_updated_on',
121 h.datetime_to_time(pr.updated_on)),
121 h.datetime_to_time(pr.updated_on)),
122 'updated_on_raw': h.datetime_to_time(pr.updated_on),
122 'updated_on_raw': h.datetime_to_time(pr.updated_on),
123 'created_on': _render('pullrequest_updated_on',
123 'created_on': _render('pullrequest_updated_on',
124 h.datetime_to_time(pr.created_on)),
124 h.datetime_to_time(pr.created_on)),
125 'created_on_raw': h.datetime_to_time(pr.created_on),
125 'created_on_raw': h.datetime_to_time(pr.created_on),
126 'state': pr.pull_request_state,
126 'state': pr.pull_request_state,
127 'author': _render('pullrequest_author',
127 'author': _render('pullrequest_author',
128 pr.author.full_contact, ),
128 pr.author.full_contact, ),
129 'author_raw': pr.author.full_name,
129 'author_raw': pr.author.full_name,
130 'comments': _render('pullrequest_comments', comments_count),
130 'comments': _render('pullrequest_comments', comments_count),
131 'comments_raw': comments_count,
131 'comments_raw': comments_count,
132 'closed': pr.is_closed(),
132 'closed': pr.is_closed(),
133 })
133 })
134
134
135 data = ({
135 data = ({
136 'draw': draw,
136 'draw': draw,
137 'data': data,
137 'data': data,
138 'recordsTotal': pull_requests_total_count,
138 'recordsTotal': pull_requests_total_count,
139 'recordsFiltered': pull_requests_total_count,
139 'recordsFiltered': pull_requests_total_count,
140 })
140 })
141 return data
141 return data
142
142
143 @LoginRequired()
143 @LoginRequired()
144 @HasRepoPermissionAnyDecorator(
144 @HasRepoPermissionAnyDecorator(
145 'repository.read', 'repository.write', 'repository.admin')
145 'repository.read', 'repository.write', 'repository.admin')
146 @view_config(
146 @view_config(
147 route_name='pullrequest_show_all', request_method='GET',
147 route_name='pullrequest_show_all', request_method='GET',
148 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
148 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
149 def pull_request_list(self):
149 def pull_request_list(self):
150 c = self.load_default_context()
150 c = self.load_default_context()
151
151
152 req_get = self.request.GET
152 req_get = self.request.GET
153 c.source = str2bool(req_get.get('source'))
153 c.source = str2bool(req_get.get('source'))
154 c.closed = str2bool(req_get.get('closed'))
154 c.closed = str2bool(req_get.get('closed'))
155 c.my = str2bool(req_get.get('my'))
155 c.my = str2bool(req_get.get('my'))
156 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
156 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
157 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
157 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
158
158
159 c.active = 'open'
159 c.active = 'open'
160 if c.my:
160 if c.my:
161 c.active = 'my'
161 c.active = 'my'
162 if c.closed:
162 if c.closed:
163 c.active = 'closed'
163 c.active = 'closed'
164 if c.awaiting_review and not c.source:
164 if c.awaiting_review and not c.source:
165 c.active = 'awaiting'
165 c.active = 'awaiting'
166 if c.source and not c.awaiting_review:
166 if c.source and not c.awaiting_review:
167 c.active = 'source'
167 c.active = 'source'
168 if c.awaiting_my_review:
168 if c.awaiting_my_review:
169 c.active = 'awaiting_my'
169 c.active = 'awaiting_my'
170
170
171 return self._get_template_context(c)
171 return self._get_template_context(c)
172
172
173 @LoginRequired()
173 @LoginRequired()
174 @HasRepoPermissionAnyDecorator(
174 @HasRepoPermissionAnyDecorator(
175 'repository.read', 'repository.write', 'repository.admin')
175 'repository.read', 'repository.write', 'repository.admin')
176 @view_config(
176 @view_config(
177 route_name='pullrequest_show_all_data', request_method='GET',
177 route_name='pullrequest_show_all_data', request_method='GET',
178 renderer='json_ext', xhr=True)
178 renderer='json_ext', xhr=True)
179 def pull_request_list_data(self):
179 def pull_request_list_data(self):
180 self.load_default_context()
180 self.load_default_context()
181
181
182 # additional filters
182 # additional filters
183 req_get = self.request.GET
183 req_get = self.request.GET
184 source = str2bool(req_get.get('source'))
184 source = str2bool(req_get.get('source'))
185 closed = str2bool(req_get.get('closed'))
185 closed = str2bool(req_get.get('closed'))
186 my = str2bool(req_get.get('my'))
186 my = str2bool(req_get.get('my'))
187 awaiting_review = str2bool(req_get.get('awaiting_review'))
187 awaiting_review = str2bool(req_get.get('awaiting_review'))
188 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
188 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
189
189
190 filter_type = 'awaiting_review' if awaiting_review \
190 filter_type = 'awaiting_review' if awaiting_review \
191 else 'awaiting_my_review' if awaiting_my_review \
191 else 'awaiting_my_review' if awaiting_my_review \
192 else None
192 else None
193
193
194 opened_by = None
194 opened_by = None
195 if my:
195 if my:
196 opened_by = [self._rhodecode_user.user_id]
196 opened_by = [self._rhodecode_user.user_id]
197
197
198 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
198 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
199 if closed:
199 if closed:
200 statuses = [PullRequest.STATUS_CLOSED]
200 statuses = [PullRequest.STATUS_CLOSED]
201
201
202 data = self._get_pull_requests_list(
202 data = self._get_pull_requests_list(
203 repo_name=self.db_repo_name, source=source,
203 repo_name=self.db_repo_name, source=source,
204 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
204 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
205
205
206 return data
206 return data
207
207
208 def _is_diff_cache_enabled(self, target_repo):
208 def _is_diff_cache_enabled(self, target_repo):
209 caching_enabled = self._get_general_setting(
209 caching_enabled = self._get_general_setting(
210 target_repo, 'rhodecode_diff_cache')
210 target_repo, 'rhodecode_diff_cache')
211 log.debug('Diff caching enabled: %s', caching_enabled)
211 log.debug('Diff caching enabled: %s', caching_enabled)
212 return caching_enabled
212 return caching_enabled
213
213
214 def _get_diffset(self, source_repo_name, source_repo,
214 def _get_diffset(self, source_repo_name, source_repo,
215 ancestor_commit,
215 ancestor_commit,
216 source_ref_id, target_ref_id,
216 source_ref_id, target_ref_id,
217 target_commit, source_commit, diff_limit, file_limit,
217 target_commit, source_commit, diff_limit, file_limit,
218 fulldiff, hide_whitespace_changes, diff_context, use_ancestor=True):
218 fulldiff, hide_whitespace_changes, diff_context, use_ancestor=True):
219
219
220 if use_ancestor:
220 if use_ancestor:
221 # we might want to not use it for versions
221 # we might want to not use it for versions
222 target_ref_id = ancestor_commit.raw_id
222 target_ref_id = ancestor_commit.raw_id
223
223
224 vcs_diff = PullRequestModel().get_diff(
224 vcs_diff = PullRequestModel().get_diff(
225 source_repo, source_ref_id, target_ref_id,
225 source_repo, source_ref_id, target_ref_id,
226 hide_whitespace_changes, diff_context)
226 hide_whitespace_changes, diff_context)
227
227
228 diff_processor = diffs.DiffProcessor(
228 diff_processor = diffs.DiffProcessor(
229 vcs_diff, format='newdiff', diff_limit=diff_limit,
229 vcs_diff, format='newdiff', diff_limit=diff_limit,
230 file_limit=file_limit, show_full_diff=fulldiff)
230 file_limit=file_limit, show_full_diff=fulldiff)
231
231
232 _parsed = diff_processor.prepare()
232 _parsed = diff_processor.prepare()
233
233
234 diffset = codeblocks.DiffSet(
234 diffset = codeblocks.DiffSet(
235 repo_name=self.db_repo_name,
235 repo_name=self.db_repo_name,
236 source_repo_name=source_repo_name,
236 source_repo_name=source_repo_name,
237 source_node_getter=codeblocks.diffset_node_getter(target_commit),
237 source_node_getter=codeblocks.diffset_node_getter(target_commit),
238 target_node_getter=codeblocks.diffset_node_getter(source_commit),
238 target_node_getter=codeblocks.diffset_node_getter(source_commit),
239 )
239 )
240 diffset = self.path_filter.render_patchset_filtered(
240 diffset = self.path_filter.render_patchset_filtered(
241 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
241 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
242
242
243 return diffset
243 return diffset
244
244
245 def _get_range_diffset(self, source_scm, source_repo,
245 def _get_range_diffset(self, source_scm, source_repo,
246 commit1, commit2, diff_limit, file_limit,
246 commit1, commit2, diff_limit, file_limit,
247 fulldiff, hide_whitespace_changes, diff_context):
247 fulldiff, hide_whitespace_changes, diff_context):
248 vcs_diff = source_scm.get_diff(
248 vcs_diff = source_scm.get_diff(
249 commit1, commit2,
249 commit1, commit2,
250 ignore_whitespace=hide_whitespace_changes,
250 ignore_whitespace=hide_whitespace_changes,
251 context=diff_context)
251 context=diff_context)
252
252
253 diff_processor = diffs.DiffProcessor(
253 diff_processor = diffs.DiffProcessor(
254 vcs_diff, format='newdiff', diff_limit=diff_limit,
254 vcs_diff, format='newdiff', diff_limit=diff_limit,
255 file_limit=file_limit, show_full_diff=fulldiff)
255 file_limit=file_limit, show_full_diff=fulldiff)
256
256
257 _parsed = diff_processor.prepare()
257 _parsed = diff_processor.prepare()
258
258
259 diffset = codeblocks.DiffSet(
259 diffset = codeblocks.DiffSet(
260 repo_name=source_repo.repo_name,
260 repo_name=source_repo.repo_name,
261 source_node_getter=codeblocks.diffset_node_getter(commit1),
261 source_node_getter=codeblocks.diffset_node_getter(commit1),
262 target_node_getter=codeblocks.diffset_node_getter(commit2))
262 target_node_getter=codeblocks.diffset_node_getter(commit2))
263
263
264 diffset = self.path_filter.render_patchset_filtered(
264 diffset = self.path_filter.render_patchset_filtered(
265 diffset, _parsed, commit1.raw_id, commit2.raw_id)
265 diffset, _parsed, commit1.raw_id, commit2.raw_id)
266
266
267 return diffset
267 return diffset
268
268
269 def register_comments_vars(self, c, pull_request, versions):
269 def register_comments_vars(self, c, pull_request, versions):
270 comments_model = CommentsModel()
270 comments_model = CommentsModel()
271
271
272 # GENERAL COMMENTS with versions #
272 # GENERAL COMMENTS with versions #
273 q = comments_model._all_general_comments_of_pull_request(pull_request)
273 q = comments_model._all_general_comments_of_pull_request(pull_request)
274 q = q.order_by(ChangesetComment.comment_id.asc())
274 q = q.order_by(ChangesetComment.comment_id.asc())
275 general_comments = q
275 general_comments = q
276
276
277 # pick comments we want to render at current version
277 # pick comments we want to render at current version
278 c.comment_versions = comments_model.aggregate_comments(
278 c.comment_versions = comments_model.aggregate_comments(
279 general_comments, versions, c.at_version_num)
279 general_comments, versions, c.at_version_num)
280
280
281 # INLINE COMMENTS with versions #
281 # INLINE COMMENTS with versions #
282 q = comments_model._all_inline_comments_of_pull_request(pull_request)
282 q = comments_model._all_inline_comments_of_pull_request(pull_request)
283 q = q.order_by(ChangesetComment.comment_id.asc())
283 q = q.order_by(ChangesetComment.comment_id.asc())
284 inline_comments = q
284 inline_comments = q
285
285
286 c.inline_versions = comments_model.aggregate_comments(
286 c.inline_versions = comments_model.aggregate_comments(
287 inline_comments, versions, c.at_version_num, inline=True)
287 inline_comments, versions, c.at_version_num, inline=True)
288
288
289 # Comments inline+general
289 # Comments inline+general
290 if c.at_version:
290 if c.at_version:
291 c.inline_comments_flat = c.inline_versions[c.at_version_num]['display']
291 c.inline_comments_flat = c.inline_versions[c.at_version_num]['display']
292 c.comments = c.comment_versions[c.at_version_num]['display']
292 c.comments = c.comment_versions[c.at_version_num]['display']
293 else:
293 else:
294 c.inline_comments_flat = c.inline_versions[c.at_version_num]['until']
294 c.inline_comments_flat = c.inline_versions[c.at_version_num]['until']
295 c.comments = c.comment_versions[c.at_version_num]['until']
295 c.comments = c.comment_versions[c.at_version_num]['until']
296
296
297 return general_comments, inline_comments
297 return general_comments, inline_comments
298
298
299 @LoginRequired()
299 @LoginRequired()
300 @HasRepoPermissionAnyDecorator(
300 @HasRepoPermissionAnyDecorator(
301 'repository.read', 'repository.write', 'repository.admin')
301 'repository.read', 'repository.write', 'repository.admin')
302 @view_config(
302 @view_config(
303 route_name='pullrequest_show', request_method='GET',
303 route_name='pullrequest_show', request_method='GET',
304 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
304 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
305 def pull_request_show(self):
305 def pull_request_show(self):
306 _ = self.request.translate
306 _ = self.request.translate
307 c = self.load_default_context()
307 c = self.load_default_context()
308
308
309 pull_request = PullRequest.get_or_404(
309 pull_request = PullRequest.get_or_404(
310 self.request.matchdict['pull_request_id'])
310 self.request.matchdict['pull_request_id'])
311 pull_request_id = pull_request.pull_request_id
311 pull_request_id = pull_request.pull_request_id
312
312
313 c.state_progressing = pull_request.is_state_changing()
313 c.state_progressing = pull_request.is_state_changing()
314 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
314 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
315
315
316 _new_state = {
316 _new_state = {
317 'created': PullRequest.STATE_CREATED,
317 'created': PullRequest.STATE_CREATED,
318 }.get(self.request.GET.get('force_state'))
318 }.get(self.request.GET.get('force_state'))
319
319
320 if c.is_super_admin and _new_state:
320 if c.is_super_admin and _new_state:
321 with pull_request.set_state(PullRequest.STATE_UPDATING, final_state=_new_state):
321 with pull_request.set_state(PullRequest.STATE_UPDATING, final_state=_new_state):
322 h.flash(
322 h.flash(
323 _('Pull Request state was force changed to `{}`').format(_new_state),
323 _('Pull Request state was force changed to `{}`').format(_new_state),
324 category='success')
324 category='success')
325 Session().commit()
325 Session().commit()
326
326
327 raise HTTPFound(h.route_path(
327 raise HTTPFound(h.route_path(
328 'pullrequest_show', repo_name=self.db_repo_name,
328 'pullrequest_show', repo_name=self.db_repo_name,
329 pull_request_id=pull_request_id))
329 pull_request_id=pull_request_id))
330
330
331 version = self.request.GET.get('version')
331 version = self.request.GET.get('version')
332 from_version = self.request.GET.get('from_version') or version
332 from_version = self.request.GET.get('from_version') or version
333 merge_checks = self.request.GET.get('merge_checks')
333 merge_checks = self.request.GET.get('merge_checks')
334 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
334 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
335 force_refresh = str2bool(self.request.GET.get('force_refresh'))
335 force_refresh = str2bool(self.request.GET.get('force_refresh'))
336 c.range_diff_on = self.request.GET.get('range-diff') == "1"
336 c.range_diff_on = self.request.GET.get('range-diff') == "1"
337
337
338 # fetch global flags of ignore ws or context lines
338 # fetch global flags of ignore ws or context lines
339 diff_context = diffs.get_diff_context(self.request)
339 diff_context = diffs.get_diff_context(self.request)
340 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
340 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
341
341
342 (pull_request_latest,
342 (pull_request_latest,
343 pull_request_at_ver,
343 pull_request_at_ver,
344 pull_request_display_obj,
344 pull_request_display_obj,
345 at_version) = PullRequestModel().get_pr_version(
345 at_version) = PullRequestModel().get_pr_version(
346 pull_request_id, version=version)
346 pull_request_id, version=version)
347
347
348 pr_closed = pull_request_latest.is_closed()
348 pr_closed = pull_request_latest.is_closed()
349
349
350 if pr_closed and (version or from_version):
350 if pr_closed and (version or from_version):
351 # not allow to browse versions for closed PR
351 # not allow to browse versions for closed PR
352 raise HTTPFound(h.route_path(
352 raise HTTPFound(h.route_path(
353 'pullrequest_show', repo_name=self.db_repo_name,
353 'pullrequest_show', repo_name=self.db_repo_name,
354 pull_request_id=pull_request_id))
354 pull_request_id=pull_request_id))
355
355
356 versions = pull_request_display_obj.versions()
356 versions = pull_request_display_obj.versions()
357 # used to store per-commit range diffs
357 # used to store per-commit range diffs
358 c.changes = collections.OrderedDict()
358 c.changes = collections.OrderedDict()
359
359
360 c.at_version = at_version
360 c.at_version = at_version
361 c.at_version_num = (at_version
361 c.at_version_num = (at_version
362 if at_version and at_version != PullRequest.LATEST_VER
362 if at_version and at_version != PullRequest.LATEST_VER
363 else None)
363 else None)
364
364
365 c.at_version_index = ChangesetComment.get_index_from_version(
365 c.at_version_index = ChangesetComment.get_index_from_version(
366 c.at_version_num, versions)
366 c.at_version_num, versions)
367
367
368 (prev_pull_request_latest,
368 (prev_pull_request_latest,
369 prev_pull_request_at_ver,
369 prev_pull_request_at_ver,
370 prev_pull_request_display_obj,
370 prev_pull_request_display_obj,
371 prev_at_version) = PullRequestModel().get_pr_version(
371 prev_at_version) = PullRequestModel().get_pr_version(
372 pull_request_id, version=from_version)
372 pull_request_id, version=from_version)
373
373
374 c.from_version = prev_at_version
374 c.from_version = prev_at_version
375 c.from_version_num = (prev_at_version
375 c.from_version_num = (prev_at_version
376 if prev_at_version and prev_at_version != PullRequest.LATEST_VER
376 if prev_at_version and prev_at_version != PullRequest.LATEST_VER
377 else None)
377 else None)
378 c.from_version_index = ChangesetComment.get_index_from_version(
378 c.from_version_index = ChangesetComment.get_index_from_version(
379 c.from_version_num, versions)
379 c.from_version_num, versions)
380
380
381 # define if we're in COMPARE mode or VIEW at version mode
381 # define if we're in COMPARE mode or VIEW at version mode
382 compare = at_version != prev_at_version
382 compare = at_version != prev_at_version
383
383
384 # pull_requests repo_name we opened it against
384 # pull_requests repo_name we opened it against
385 # ie. target_repo must match
385 # ie. target_repo must match
386 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
386 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
387 log.warning('Mismatch between the current repo: %s, and target %s',
387 log.warning('Mismatch between the current repo: %s, and target %s',
388 self.db_repo_name, pull_request_at_ver.target_repo.repo_name)
388 self.db_repo_name, pull_request_at_ver.target_repo.repo_name)
389 raise HTTPNotFound()
389 raise HTTPNotFound()
390
390
391 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver)
391 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver)
392
392
393 c.pull_request = pull_request_display_obj
393 c.pull_request = pull_request_display_obj
394 c.renderer = pull_request_at_ver.description_renderer or c.renderer
394 c.renderer = pull_request_at_ver.description_renderer or c.renderer
395 c.pull_request_latest = pull_request_latest
395 c.pull_request_latest = pull_request_latest
396
396
397 # inject latest version
397 # inject latest version
398 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
398 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
399 c.versions = versions + [latest_ver]
399 c.versions = versions + [latest_ver]
400
400
401 if compare or (at_version and not at_version == PullRequest.LATEST_VER):
401 if compare or (at_version and not at_version == PullRequest.LATEST_VER):
402 c.allowed_to_change_status = False
402 c.allowed_to_change_status = False
403 c.allowed_to_update = False
403 c.allowed_to_update = False
404 c.allowed_to_merge = False
404 c.allowed_to_merge = False
405 c.allowed_to_delete = False
405 c.allowed_to_delete = False
406 c.allowed_to_comment = False
406 c.allowed_to_comment = False
407 c.allowed_to_close = False
407 c.allowed_to_close = False
408 else:
408 else:
409 can_change_status = PullRequestModel().check_user_change_status(
409 can_change_status = PullRequestModel().check_user_change_status(
410 pull_request_at_ver, self._rhodecode_user)
410 pull_request_at_ver, self._rhodecode_user)
411 c.allowed_to_change_status = can_change_status and not pr_closed
411 c.allowed_to_change_status = can_change_status and not pr_closed
412
412
413 c.allowed_to_update = PullRequestModel().check_user_update(
413 c.allowed_to_update = PullRequestModel().check_user_update(
414 pull_request_latest, self._rhodecode_user) and not pr_closed
414 pull_request_latest, self._rhodecode_user) and not pr_closed
415 c.allowed_to_merge = PullRequestModel().check_user_merge(
415 c.allowed_to_merge = PullRequestModel().check_user_merge(
416 pull_request_latest, self._rhodecode_user) and not pr_closed
416 pull_request_latest, self._rhodecode_user) and not pr_closed
417 c.allowed_to_delete = PullRequestModel().check_user_delete(
417 c.allowed_to_delete = PullRequestModel().check_user_delete(
418 pull_request_latest, self._rhodecode_user) and not pr_closed
418 pull_request_latest, self._rhodecode_user) and not pr_closed
419 c.allowed_to_comment = not pr_closed
419 c.allowed_to_comment = not pr_closed
420 c.allowed_to_close = c.allowed_to_merge and not pr_closed
420 c.allowed_to_close = c.allowed_to_merge and not pr_closed
421
421
422 c.forbid_adding_reviewers = False
422 c.forbid_adding_reviewers = False
423 c.forbid_author_to_review = False
423 c.forbid_author_to_review = False
424 c.forbid_commit_author_to_review = False
424 c.forbid_commit_author_to_review = False
425
425
426 if pull_request_latest.reviewer_data and \
426 if pull_request_latest.reviewer_data and \
427 'rules' in pull_request_latest.reviewer_data:
427 'rules' in pull_request_latest.reviewer_data:
428 rules = pull_request_latest.reviewer_data['rules'] or {}
428 rules = pull_request_latest.reviewer_data['rules'] or {}
429 try:
429 try:
430 c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers')
430 c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers')
431 c.forbid_author_to_review = rules.get('forbid_author_to_review')
431 c.forbid_author_to_review = rules.get('forbid_author_to_review')
432 c.forbid_commit_author_to_review = rules.get('forbid_commit_author_to_review')
432 c.forbid_commit_author_to_review = rules.get('forbid_commit_author_to_review')
433 except Exception:
433 except Exception:
434 pass
434 pass
435
435
436 # check merge capabilities
436 # check merge capabilities
437 _merge_check = MergeCheck.validate(
437 _merge_check = MergeCheck.validate(
438 pull_request_latest, auth_user=self._rhodecode_user,
438 pull_request_latest, auth_user=self._rhodecode_user,
439 translator=self.request.translate,
439 translator=self.request.translate,
440 force_shadow_repo_refresh=force_refresh)
440 force_shadow_repo_refresh=force_refresh)
441
441
442 c.pr_merge_errors = _merge_check.error_details
442 c.pr_merge_errors = _merge_check.error_details
443 c.pr_merge_possible = not _merge_check.failed
443 c.pr_merge_possible = not _merge_check.failed
444 c.pr_merge_message = _merge_check.merge_msg
444 c.pr_merge_message = _merge_check.merge_msg
445 c.pr_merge_source_commit = _merge_check.source_commit
445 c.pr_merge_source_commit = _merge_check.source_commit
446 c.pr_merge_target_commit = _merge_check.target_commit
446 c.pr_merge_target_commit = _merge_check.target_commit
447
447
448 c.pr_merge_info = MergeCheck.get_merge_conditions(
448 c.pr_merge_info = MergeCheck.get_merge_conditions(
449 pull_request_latest, translator=self.request.translate)
449 pull_request_latest, translator=self.request.translate)
450
450
451 c.pull_request_review_status = _merge_check.review_status
451 c.pull_request_review_status = _merge_check.review_status
452 if merge_checks:
452 if merge_checks:
453 self.request.override_renderer = \
453 self.request.override_renderer = \
454 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
454 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
455 return self._get_template_context(c)
455 return self._get_template_context(c)
456
456
457 c.allowed_reviewers = [obj.user_id for obj in pull_request.reviewers if obj.user]
457 c.allowed_reviewers = [obj.user_id for obj in pull_request.reviewers if obj.user]
458 c.reviewers_count = pull_request.reviewers_count
458 c.reviewers_count = pull_request.reviewers_count
459 c.observers_count = pull_request.observers_count
459 c.observers_count = pull_request.observers_count
460
460
461 # reviewers and statuses
461 # reviewers and statuses
462 c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data)
462 c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data)
463 c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
463 c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
464 c.pull_request_set_observers_data_json = collections.OrderedDict({'observers': []})
464 c.pull_request_set_observers_data_json = collections.OrderedDict({'observers': []})
465
465
466 for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses():
466 for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses():
467 member_reviewer = h.reviewer_as_json(
467 member_reviewer = h.reviewer_as_json(
468 member, reasons=reasons, mandatory=mandatory,
468 member, reasons=reasons, mandatory=mandatory,
469 role=review_obj.role,
469 role=review_obj.role,
470 user_group=review_obj.rule_user_group_data()
470