##// END OF EJS Templates
pull-requests: make my account and repo pr table more consistent.
marcink -
r4512:91239784 stable
parent child
Show More
@@ -1,1811 +1,1812
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, Reference
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 short=True),
115 'name_raw': pr.pull_request_id,
116 'name_raw': pr.pull_request_id,
116 'status': _render('pullrequest_status',
117 'status': _render('pullrequest_status',
117 pr.calculated_review_status()),
118 pr.calculated_review_status()),
118 'title': _render('pullrequest_title', pr.title, pr.description),
119 'title': _render('pullrequest_title', pr.title, pr.description),
119 'description': h.escape(pr.description),
120 'description': h.escape(pr.description),
120 'updated_on': _render('pullrequest_updated_on',
121 'updated_on': _render('pullrequest_updated_on',
121 h.datetime_to_time(pr.updated_on)),
122 h.datetime_to_time(pr.updated_on)),
122 'updated_on_raw': h.datetime_to_time(pr.updated_on),
123 'updated_on_raw': h.datetime_to_time(pr.updated_on),
123 'created_on': _render('pullrequest_updated_on',
124 'created_on': _render('pullrequest_updated_on',
124 h.datetime_to_time(pr.created_on)),
125 h.datetime_to_time(pr.created_on)),
125 'created_on_raw': h.datetime_to_time(pr.created_on),
126 'created_on_raw': h.datetime_to_time(pr.created_on),
126 'state': pr.pull_request_state,
127 'state': pr.pull_request_state,
127 'author': _render('pullrequest_author',
128 'author': _render('pullrequest_author',
128 pr.author.full_contact, ),
129 pr.author.full_contact, ),
129 'author_raw': pr.author.full_name,
130 'author_raw': pr.author.full_name,
130 'comments': _render('pullrequest_comments', comments_count),
131 'comments': _render('pullrequest_comments', comments_count),
131 'comments_raw': comments_count,
132 'comments_raw': comments_count,
132 'closed': pr.is_closed(),
133 'closed': pr.is_closed(),
133 })
134 })
134
135
135 data = ({
136 data = ({
136 'draw': draw,
137 'draw': draw,
137 'data': data,
138 'data': data,
138 'recordsTotal': pull_requests_total_count,
139 'recordsTotal': pull_requests_total_count,
139 'recordsFiltered': pull_requests_total_count,
140 'recordsFiltered': pull_requests_total_count,
140 })
141 })
141 return data
142 return data
142
143
143 @LoginRequired()
144 @LoginRequired()
144 @HasRepoPermissionAnyDecorator(
145 @HasRepoPermissionAnyDecorator(
145 'repository.read', 'repository.write', 'repository.admin')
146 'repository.read', 'repository.write', 'repository.admin')
146 @view_config(
147 @view_config(
147 route_name='pullrequest_show_all', request_method='GET',
148 route_name='pullrequest_show_all', request_method='GET',
148 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
149 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
149 def pull_request_list(self):
150 def pull_request_list(self):
150 c = self.load_default_context()
151 c = self.load_default_context()
151
152
152 req_get = self.request.GET
153 req_get = self.request.GET
153 c.source = str2bool(req_get.get('source'))
154 c.source = str2bool(req_get.get('source'))
154 c.closed = str2bool(req_get.get('closed'))
155 c.closed = str2bool(req_get.get('closed'))
155 c.my = str2bool(req_get.get('my'))
156 c.my = str2bool(req_get.get('my'))
156 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
157 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
157 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
158 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
158
159
159 c.active = 'open'
160 c.active = 'open'
160 if c.my:
161 if c.my:
161 c.active = 'my'
162 c.active = 'my'
162 if c.closed:
163 if c.closed:
163 c.active = 'closed'
164 c.active = 'closed'
164 if c.awaiting_review and not c.source:
165 if c.awaiting_review and not c.source:
165 c.active = 'awaiting'
166 c.active = 'awaiting'
166 if c.source and not c.awaiting_review:
167 if c.source and not c.awaiting_review:
167 c.active = 'source'
168 c.active = 'source'
168 if c.awaiting_my_review:
169 if c.awaiting_my_review:
169 c.active = 'awaiting_my'
170 c.active = 'awaiting_my'
170
171
171 return self._get_template_context(c)
172 return self._get_template_context(c)
172
173
173 @LoginRequired()
174 @LoginRequired()
174 @HasRepoPermissionAnyDecorator(
175 @HasRepoPermissionAnyDecorator(
175 'repository.read', 'repository.write', 'repository.admin')
176 'repository.read', 'repository.write', 'repository.admin')
176 @view_config(
177 @view_config(
177 route_name='pullrequest_show_all_data', request_method='GET',
178 route_name='pullrequest_show_all_data', request_method='GET',
178 renderer='json_ext', xhr=True)
179 renderer='json_ext', xhr=True)
179 def pull_request_list_data(self):
180 def pull_request_list_data(self):
180 self.load_default_context()
181 self.load_default_context()
181
182
182 # additional filters
183 # additional filters
183 req_get = self.request.GET
184 req_get = self.request.GET
184 source = str2bool(req_get.get('source'))
185 source = str2bool(req_get.get('source'))
185 closed = str2bool(req_get.get('closed'))
186 closed = str2bool(req_get.get('closed'))
186 my = str2bool(req_get.get('my'))
187 my = str2bool(req_get.get('my'))
187 awaiting_review = str2bool(req_get.get('awaiting_review'))
188 awaiting_review = str2bool(req_get.get('awaiting_review'))
188 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
189 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
189
190
190 filter_type = 'awaiting_review' if awaiting_review \
191 filter_type = 'awaiting_review' if awaiting_review \
191 else 'awaiting_my_review' if awaiting_my_review \
192 else 'awaiting_my_review' if awaiting_my_review \
192 else None
193 else None
193
194
194 opened_by = None
195 opened_by = None
195 if my:
196 if my:
196 opened_by = [self._rhodecode_user.user_id]
197 opened_by = [self._rhodecode_user.user_id]
197
198
198 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
199 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
199 if closed:
200 if closed:
200 statuses = [PullRequest.STATUS_CLOSED]
201 statuses = [PullRequest.STATUS_CLOSED]
201
202
202 data = self._get_pull_requests_list(
203 data = self._get_pull_requests_list(
203 repo_name=self.db_repo_name, source=source,
204 repo_name=self.db_repo_name, source=source,
204 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
205 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
205
206
206 return data
207 return data
207
208
208 def _is_diff_cache_enabled(self, target_repo):
209 def _is_diff_cache_enabled(self, target_repo):
209 caching_enabled = self._get_general_setting(
210 caching_enabled = self._get_general_setting(
210 target_repo, 'rhodecode_diff_cache')
211 target_repo, 'rhodecode_diff_cache')
211 log.debug('Diff caching enabled: %s', caching_enabled)
212 log.debug('Diff caching enabled: %s', caching_enabled)
212 return caching_enabled
213 return caching_enabled
213
214
214 def _get_diffset(self, source_repo_name, source_repo,
215 def _get_diffset(self, source_repo_name, source_repo,
215 ancestor_commit,
216 ancestor_commit,
216 source_ref_id, target_ref_id,
217 source_ref_id, target_ref_id,
217 target_commit, source_commit, diff_limit, file_limit,
218 target_commit, source_commit, diff_limit, file_limit,
218 fulldiff, hide_whitespace_changes, diff_context, use_ancestor=True):
219 fulldiff, hide_whitespace_changes, diff_context, use_ancestor=True):
219
220
220 if use_ancestor:
221 if use_ancestor:
221 # we might want to not use it for versions
222 # we might want to not use it for versions
222 target_ref_id = ancestor_commit.raw_id
223 target_ref_id = ancestor_commit.raw_id
223
224
224 vcs_diff = PullRequestModel().get_diff(
225 vcs_diff = PullRequestModel().get_diff(
225 source_repo, source_ref_id, target_ref_id,
226 source_repo, source_ref_id, target_ref_id,
226 hide_whitespace_changes, diff_context)
227 hide_whitespace_changes, diff_context)
227
228
228 diff_processor = diffs.DiffProcessor(
229 diff_processor = diffs.DiffProcessor(
229 vcs_diff, format='newdiff', diff_limit=diff_limit,
230 vcs_diff, format='newdiff', diff_limit=diff_limit,
230 file_limit=file_limit, show_full_diff=fulldiff)
231 file_limit=file_limit, show_full_diff=fulldiff)
231
232
232 _parsed = diff_processor.prepare()
233 _parsed = diff_processor.prepare()
233
234
234 diffset = codeblocks.DiffSet(
235 diffset = codeblocks.DiffSet(
235 repo_name=self.db_repo_name,
236 repo_name=self.db_repo_name,
236 source_repo_name=source_repo_name,
237 source_repo_name=source_repo_name,
237 source_node_getter=codeblocks.diffset_node_getter(target_commit),
238 source_node_getter=codeblocks.diffset_node_getter(target_commit),
238 target_node_getter=codeblocks.diffset_node_getter(source_commit),
239 target_node_getter=codeblocks.diffset_node_getter(source_commit),
239 )
240 )
240 diffset = self.path_filter.render_patchset_filtered(
241 diffset = self.path_filter.render_patchset_filtered(
241 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
242 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
242
243
243 return diffset
244 return diffset
244
245
245 def _get_range_diffset(self, source_scm, source_repo,
246 def _get_range_diffset(self, source_scm, source_repo,
246 commit1, commit2, diff_limit, file_limit,
247 commit1, commit2, diff_limit, file_limit,
247 fulldiff, hide_whitespace_changes, diff_context):
248 fulldiff, hide_whitespace_changes, diff_context):
248 vcs_diff = source_scm.get_diff(
249 vcs_diff = source_scm.get_diff(
249 commit1, commit2,
250 commit1, commit2,
250 ignore_whitespace=hide_whitespace_changes,
251 ignore_whitespace=hide_whitespace_changes,
251 context=diff_context)
252 context=diff_context)
252
253
253 diff_processor = diffs.DiffProcessor(
254 diff_processor = diffs.DiffProcessor(
254 vcs_diff, format='newdiff', diff_limit=diff_limit,
255 vcs_diff, format='newdiff', diff_limit=diff_limit,
255 file_limit=file_limit, show_full_diff=fulldiff)
256 file_limit=file_limit, show_full_diff=fulldiff)
256
257
257 _parsed = diff_processor.prepare()
258 _parsed = diff_processor.prepare()
258
259
259 diffset = codeblocks.DiffSet(
260 diffset = codeblocks.DiffSet(
260 repo_name=source_repo.repo_name,
261 repo_name=source_repo.repo_name,
261 source_node_getter=codeblocks.diffset_node_getter(commit1),
262 source_node_getter=codeblocks.diffset_node_getter(commit1),
262 target_node_getter=codeblocks.diffset_node_getter(commit2))
263 target_node_getter=codeblocks.diffset_node_getter(commit2))
263
264
264 diffset = self.path_filter.render_patchset_filtered(
265 diffset = self.path_filter.render_patchset_filtered(
265 diffset, _parsed, commit1.raw_id, commit2.raw_id)
266 diffset, _parsed, commit1.raw_id, commit2.raw_id)
266
267
267 return diffset
268 return diffset
268
269
269 def register_comments_vars(self, c, pull_request, versions):
270 def register_comments_vars(self, c, pull_request, versions):
270 comments_model = CommentsModel()
271 comments_model = CommentsModel()
271
272
272 # GENERAL COMMENTS with versions #
273 # GENERAL COMMENTS with versions #
273 q = comments_model._all_general_comments_of_pull_request(pull_request)
274 q = comments_model._all_general_comments_of_pull_request(pull_request)
274 q = q.order_by(ChangesetComment.comment_id.asc())
275 q = q.order_by(ChangesetComment.comment_id.asc())
275 general_comments = q
276 general_comments = q
276
277
277 # pick comments we want to render at current version
278 # pick comments we want to render at current version
278 c.comment_versions = comments_model.aggregate_comments(
279 c.comment_versions = comments_model.aggregate_comments(
279 general_comments, versions, c.at_version_num)
280 general_comments, versions, c.at_version_num)
280
281
281 # INLINE COMMENTS with versions #
282 # INLINE COMMENTS with versions #
282 q = comments_model._all_inline_comments_of_pull_request(pull_request)
283 q = comments_model._all_inline_comments_of_pull_request(pull_request)
283 q = q.order_by(ChangesetComment.comment_id.asc())
284 q = q.order_by(ChangesetComment.comment_id.asc())
284 inline_comments = q
285 inline_comments = q
285
286
286 c.inline_versions = comments_model.aggregate_comments(
287 c.inline_versions = comments_model.aggregate_comments(
287 inline_comments, versions, c.at_version_num, inline=True)
288 inline_comments, versions, c.at_version_num, inline=True)
288
289
289 # Comments inline+general
290 # Comments inline+general
290 if c.at_version:
291 if c.at_version:
291 c.inline_comments_flat = c.inline_versions[c.at_version_num]['display']
292 c.inline_comments_flat = c.inline_versions[c.at_version_num]['display']
292 c.comments = c.comment_versions[c.at_version_num]['display']
293 c.comments = c.comment_versions[c.at_version_num]['display']
293 else:
294 else:
294 c.inline_comments_flat = c.inline_versions[c.at_version_num]['until']
295 c.inline_comments_flat = c.inline_versions[c.at_version_num]['until']
295 c.comments = c.comment_versions[c.at_version_num]['until']
296 c.comments = c.comment_versions[c.at_version_num]['until']
296
297
297 return general_comments, inline_comments
298 return general_comments, inline_comments
298
299
299 @LoginRequired()
300 @LoginRequired()
300 @HasRepoPermissionAnyDecorator(
301 @HasRepoPermissionAnyDecorator(
301 'repository.read', 'repository.write', 'repository.admin')
302 'repository.read', 'repository.write', 'repository.admin')
302 @view_config(
303 @view_config(
303 route_name='pullrequest_show', request_method='GET',
304 route_name='pullrequest_show', request_method='GET',
304 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
305 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
305 def pull_request_show(self):
306 def pull_request_show(self):
306 _ = self.request.translate
307 _ = self.request.translate
307 c = self.load_default_context()
308 c = self.load_default_context()
308
309
309 pull_request = PullRequest.get_or_404(
310 pull_request = PullRequest.get_or_404(
310 self.request.matchdict['pull_request_id'])
311 self.request.matchdict['pull_request_id'])
311 pull_request_id = pull_request.pull_request_id
312 pull_request_id = pull_request.pull_request_id
312
313
313 c.state_progressing = pull_request.is_state_changing()
314 c.state_progressing = pull_request.is_state_changing()
314 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
315 c.pr_broadcast_channel = channelstream.pr_channel(pull_request)
315
316
316 _new_state = {
317 _new_state = {
317 'created': PullRequest.STATE_CREATED,
318 'created': PullRequest.STATE_CREATED,
318 }.get(self.request.GET.get('force_state'))
319 }.get(self.request.GET.get('force_state'))
319
320
320 if c.is_super_admin and _new_state:
321 if c.is_super_admin and _new_state:
321 with pull_request.set_state(PullRequest.STATE_UPDATING, final_state=_new_state):
322 with pull_request.set_state(PullRequest.STATE_UPDATING, final_state=_new_state):
322 h.flash(
323 h.flash(
323 _('Pull Request state was force changed to `{}`').format(_new_state),
324 _('Pull Request state was force changed to `{}`').format(_new_state),
324 category='success')
325 category='success')
325 Session().commit()
326 Session().commit()
326
327
327 raise HTTPFound(h.route_path(
328 raise HTTPFound(h.route_path(
328 'pullrequest_show', repo_name=self.db_repo_name,
329 'pullrequest_show', repo_name=self.db_repo_name,
329 pull_request_id=pull_request_id))
330 pull_request_id=pull_request_id))
330
331
331 version = self.request.GET.get('version')
332 version = self.request.GET.get('version')
332 from_version = self.request.GET.get('from_version') or version
333 from_version = self.request.GET.get('from_version') or version
333 merge_checks = self.request.GET.get('merge_checks')
334 merge_checks = self.request.GET.get('merge_checks')
334 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
335 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
335 force_refresh = str2bool(self.request.GET.get('force_refresh'))
336 force_refresh = str2bool(self.request.GET.get('force_refresh'))
336 c.range_diff_on = self.request.GET.get('range-diff') == "1"
337 c.range_diff_on = self.request.GET.get('range-diff') == "1"
337
338
338 # fetch global flags of ignore ws or context lines
339 # fetch global flags of ignore ws or context lines
339 diff_context = diffs.get_diff_context(self.request)
340 diff_context = diffs.get_diff_context(self.request)
340 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
341 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
341
342
342 (pull_request_latest,
343 (pull_request_latest,
343 pull_request_at_ver,
344 pull_request_at_ver,
344 pull_request_display_obj,
345 pull_request_display_obj,
345 at_version) = PullRequestModel().get_pr_version(
346 at_version) = PullRequestModel().get_pr_version(
346 pull_request_id, version=version)
347 pull_request_id, version=version)
347
348
348 pr_closed = pull_request_latest.is_closed()
349 pr_closed = pull_request_latest.is_closed()
349
350
350 if pr_closed and (version or from_version):
351 if pr_closed and (version or from_version):
351 # not allow to browse versions for closed PR
352 # not allow to browse versions for closed PR
352 raise HTTPFound(h.route_path(
353 raise HTTPFound(h.route_path(
353 'pullrequest_show', repo_name=self.db_repo_name,
354 'pullrequest_show', repo_name=self.db_repo_name,
354 pull_request_id=pull_request_id))
355 pull_request_id=pull_request_id))
355
356
356 versions = pull_request_display_obj.versions()
357 versions = pull_request_display_obj.versions()
357 # used to store per-commit range diffs
358 # used to store per-commit range diffs
358 c.changes = collections.OrderedDict()
359 c.changes = collections.OrderedDict()
359
360
360 c.at_version = at_version
361 c.at_version = at_version
361 c.at_version_num = (at_version
362 c.at_version_num = (at_version
362 if at_version and at_version != PullRequest.LATEST_VER
363 if at_version and at_version != PullRequest.LATEST_VER
363 else None)
364 else None)
364
365
365 c.at_version_index = ChangesetComment.get_index_from_version(
366 c.at_version_index = ChangesetComment.get_index_from_version(
366 c.at_version_num, versions)
367 c.at_version_num, versions)
367
368
368 (prev_pull_request_latest,
369 (prev_pull_request_latest,
369 prev_pull_request_at_ver,
370 prev_pull_request_at_ver,
370 prev_pull_request_display_obj,
371 prev_pull_request_display_obj,
371 prev_at_version) = PullRequestModel().get_pr_version(
372 prev_at_version) = PullRequestModel().get_pr_version(
372 pull_request_id, version=from_version)
373 pull_request_id, version=from_version)
373
374
374 c.from_version = prev_at_version
375 c.from_version = prev_at_version
375 c.from_version_num = (prev_at_version
376 c.from_version_num = (prev_at_version
376 if prev_at_version and prev_at_version != PullRequest.LATEST_VER
377 if prev_at_version and prev_at_version != PullRequest.LATEST_VER
377 else None)
378 else None)
378 c.from_version_index = ChangesetComment.get_index_from_version(
379 c.from_version_index = ChangesetComment.get_index_from_version(
379 c.from_version_num, versions)
380 c.from_version_num, versions)
380
381
381 # define if we're in COMPARE mode or VIEW at version mode
382 # define if we're in COMPARE mode or VIEW at version mode
382 compare = at_version != prev_at_version
383 compare = at_version != prev_at_version
383
384
384 # pull_requests repo_name we opened it against
385 # pull_requests repo_name we opened it against
385 # ie. target_repo must match
386 # ie. target_repo must match
386 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
387 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',
388 log.warning('Mismatch between the current repo: %s, and target %s',
388 self.db_repo_name, pull_request_at_ver.target_repo.repo_name)
389 self.db_repo_name, pull_request_at_ver.target_repo.repo_name)
389 raise HTTPNotFound()
390 raise HTTPNotFound()
390
391
391 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver)
392 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver)
392
393
393 c.pull_request = pull_request_display_obj
394 c.pull_request = pull_request_display_obj
394 c.renderer = pull_request_at_ver.description_renderer or c.renderer
395 c.renderer = pull_request_at_ver.description_renderer or c.renderer
395 c.pull_request_latest = pull_request_latest
396 c.pull_request_latest = pull_request_latest
396
397
397 # inject latest version
398 # inject latest version
398 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
399 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
399 c.versions = versions + [latest_ver]
400 c.versions = versions + [latest_ver]
400
401
401 if compare or (at_version and not at_version == PullRequest.LATEST_VER):
402 if compare or (at_version and not at_version == PullRequest.LATEST_VER):
402 c.allowed_to_change_status = False
403 c.allowed_to_change_status = False
403 c.allowed_to_update = False
404 c.allowed_to_update = False
404 c.allowed_to_merge = False
405 c.allowed_to_merge = False
405 c.allowed_to_delete = False
406 c.allowed_to_delete = False
406 c.allowed_to_comment = False
407 c.allowed_to_comment = False
407 c.allowed_to_close = False
408 c.allowed_to_close = False
408 else:
409 else:
409 can_change_status = PullRequestModel().check_user_change_status(
410 can_change_status = PullRequestModel().check_user_change_status(
410 pull_request_at_ver, self._rhodecode_user)
411 pull_request_at_ver, self._rhodecode_user)
411 c.allowed_to_change_status = can_change_status and not pr_closed
412 c.allowed_to_change_status = can_change_status and not pr_closed
412
413
413 c.allowed_to_update = PullRequestModel().check_user_update(
414 c.allowed_to_update = PullRequestModel().check_user_update(
414 pull_request_latest, self._rhodecode_user) and not pr_closed
415 pull_request_latest, self._rhodecode_user) and not pr_closed
415 c.allowed_to_merge = PullRequestModel().check_user_merge(
416 c.allowed_to_merge = PullRequestModel().check_user_merge(
416 pull_request_latest, self._rhodecode_user) and not pr_closed
417 pull_request_latest, self._rhodecode_user) and not pr_closed
417 c.allowed_to_delete = PullRequestModel().check_user_delete(
418 c.allowed_to_delete = PullRequestModel().check_user_delete(
418 pull_request_latest, self._rhodecode_user) and not pr_closed
419 pull_request_latest, self._rhodecode_user) and not pr_closed
419 c.allowed_to_comment = not pr_closed
420 c.allowed_to_comment = not pr_closed
420 c.allowed_to_close = c.allowed_to_merge and not pr_closed
421 c.allowed_to_close = c.allowed_to_merge and not pr_closed
421
422
422 c.forbid_adding_reviewers = False
423 c.forbid_adding_reviewers = False
423 c.forbid_author_to_review = False
424 c.forbid_author_to_review = False
424 c.forbid_commit_author_to_review = False
425 c.forbid_commit_author_to_review = False
425
426
426 if pull_request_latest.reviewer_data and \
427 if pull_request_latest.reviewer_data and \
427 'rules' in pull_request_latest.reviewer_data:
428 'rules' in pull_request_latest.reviewer_data:
428 rules = pull_request_latest.reviewer_data['rules'] or {}
429 rules = pull_request_latest.reviewer_data['rules'] or {}
429 try:
430 try:
430 c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers')
431 c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers')
431 c.forbid_author_to_review = rules.get('forbid_author_to_review')
432 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')
433 c.forbid_commit_author_to_review = rules.get('forbid_commit_author_to_review')
433 except Exception:
434 except Exception:
434 pass
435 pass
435
436
436 # check merge capabilities
437 # check merge capabilities
437 _merge_check = MergeCheck.validate(
438 _merge_check = MergeCheck.validate(
438 pull_request_latest, auth_user=self._rhodecode_user,
439 pull_request_latest, auth_user=self._rhodecode_user,
439 translator=self.request.translate,
440 translator=self.request.translate,
440 force_shadow_repo_refresh=force_refresh)
441 force_shadow_repo_refresh=force_refresh)
441
442
442 c.pr_merge_errors = _merge_check.error_details
443 c.pr_merge_errors = _merge_check.error_details
443 c.pr_merge_possible = not _merge_check.failed
444 c.pr_merge_possible = not _merge_check.failed
444 c.pr_merge_message = _merge_check.merge_msg
445 c.pr_merge_message = _merge_check.merge_msg
445 c.pr_merge_source_commit = _merge_check.source_commit
446 c.pr_merge_source_commit = _merge_check.source_commit
446 c.pr_merge_target_commit = _merge_check.target_commit
447 c.pr_merge_target_commit = _merge_check.target_commit
447
448
448 c.pr_merge_info = MergeCheck.get_merge_conditions(
449 c.pr_merge_info = MergeCheck.get_merge_conditions(
449 pull_request_latest, translator=self.request.translate)
450 pull_request_latest, translator=self.request.translate)
450
451
451 c.pull_request_review_status = _merge_check.review_status
452 c.pull_request_review_status = _merge_check.review_status
452 if merge_checks:
453 if merge_checks:
453 self.request.override_renderer = \
454 self.request.override_renderer = \
454 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
455 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
455 return self._get_template_context(c)
456 return self._get_template_context(c)
456
457
457 c.allowed_reviewers = [obj.user_id for obj in pull_request.reviewers if obj.user]
458 c.allowed_reviewers = [obj.user_id for obj in pull_request.reviewers if obj.user]
458 c.reviewers_count = pull_request.reviewers_count
459 c.reviewers_count = pull_request.reviewers_count
459 c.observers_count = pull_request.observers_count
460 c.observers_count = pull_request.observers_count
460
461
461 # reviewers and statuses
462 # reviewers and statuses
462 c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data)
463 c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data)
463 c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
464 c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
464 c.pull_request_set_observers_data_json = collections.OrderedDict({'observers': []})
465 c.pull_request_set_observers_data_json = collections.OrderedDict({'observers': []})
465
466
466 for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses():
467 for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses():
467 member_reviewer = h.reviewer_as_json(
468 member_reviewer = h.reviewer_as_json(
468 member, reasons=reasons, mandatory=mandatory,
469 member, reasons=reasons, mandatory=mandatory,
469 role=review_obj.role,
470 role=review_obj.role,
470 user_group=review_obj.rule_user_group_data()
471 user_group=review_obj.rule_user_group_data()
471 )
472 )
472
473
473 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
474 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
474 member_reviewer['review_status'] = current_review_status
475 member_reviewer['review_status'] = current_review_status
475 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
476 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
476 member_reviewer['allowed_to_update'] = c.allowed_to_update
477 member_reviewer['allowed_to_update'] = c.allowed_to_update
477 c.pull_request_set_reviewers_data_json['reviewers'].append(member_reviewer)
478 c.pull_request_set_reviewers_data_json['reviewers'].append(member_reviewer)
478
479
479 c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json)
480 c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json)
480
481
481 for observer_obj, member in pull_request_at_ver.observers():
482 for observer_obj, member in pull_request_at_ver.observers():
482 member_observer = h.reviewer_as_json(
483 member_observer = h.reviewer_as_json(
483 member, reasons=[], mandatory=False,
484 member, reasons=[], mandatory=False,
484 role=observer_obj.role,
485 role=observer_obj.role,
485 user_group=observer_obj.rule_user_group_data()
486 user_group=observer_obj.rule_user_group_data()
486 )
487 )
487 member_observer['allowed_to_update'] = c.allowed_to_update
488 member_observer['allowed_to_update'] = c.allowed_to_update
488 c.pull_request_set_observers_data_json['observers'].append(member_observer)
489 c.pull_request_set_observers_data_json['observers'].append(member_observer)
489
490
490 c.pull_request_set_observers_data_json = json.dumps(c.pull_request_set_observers_data_json)
491 c.pull_request_set_observers_data_json = json.dumps(c.pull_request_set_observers_data_json)
491
492
492 general_comments, inline_comments = \
493 general_comments, inline_comments = \
493 self.register_comments_vars(c, pull_request_latest, versions)
494 self.register_comments_vars(c, pull_request_latest, versions)
494
495
495 # TODOs
496 # TODOs
496 c.unresolved_comments = CommentsModel() \
497 c.unresolved_comments = CommentsModel() \
497 .get_pull_request_unresolved_todos(pull_request_latest)
498 .get_pull_request_unresolved_todos(pull_request_latest)
498 c.resolved_comments = CommentsModel() \
499 c.resolved_comments = CommentsModel() \
499 .get_pull_request_resolved_todos(pull_request_latest)
500 .get_pull_request_resolved_todos(pull_request_latest)
500
501
501 # if we use version, then do not show later comments
502 # if we use version, then do not show later comments
502 # than current version
503 # than current version
503 display_inline_comments = collections.defaultdict(
504 display_inline_comments = collections.defaultdict(
504 lambda: collections.defaultdict(list))
505 lambda: collections.defaultdict(list))
505 for co in inline_comments:
506 for co in inline_comments:
506 if c.at_version_num:
507 if c.at_version_num:
507 # pick comments that are at least UPTO given version, so we
508 # pick comments that are at least UPTO given version, so we
508 # don't render comments for higher version
509 # don't render comments for higher version
509 should_render = co.pull_request_version_id and \
510 should_render = co.pull_request_version_id and \
510 co.pull_request_version_id <= c.at_version_num
511 co.pull_request_version_id <= c.at_version_num
511 else:
512 else:
512 # showing all, for 'latest'
513 # showing all, for 'latest'
513 should_render = True
514 should_render = True
514
515
515 if should_render:
516 if should_render:
516 display_inline_comments[co.f_path][co.line_no].append(co)
517 display_inline_comments[co.f_path][co.line_no].append(co)
517
518
518 # load diff data into template context, if we use compare mode then
519 # load diff data into template context, if we use compare mode then
519 # diff is calculated based on changes between versions of PR
520 # diff is calculated based on changes between versions of PR
520
521
521 source_repo = pull_request_at_ver.source_repo
522 source_repo = pull_request_at_ver.source_repo
522 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
523 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
523
524
524 target_repo = pull_request_at_ver.target_repo
525 target_repo = pull_request_at_ver.target_repo
525 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
526 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
526
527
527 if compare:
528 if compare:
528 # in compare switch the diff base to latest commit from prev version
529 # in compare switch the diff base to latest commit from prev version
529 target_ref_id = prev_pull_request_display_obj.revisions[0]
530 target_ref_id = prev_pull_request_display_obj.revisions[0]
530
531
531 # despite opening commits for bookmarks/branches/tags, we always
532 # despite opening commits for bookmarks/branches/tags, we always
532 # convert this to rev to prevent changes after bookmark or branch change
533 # convert this to rev to prevent changes after bookmark or branch change
533 c.source_ref_type = 'rev'
534 c.source_ref_type = 'rev'
534 c.source_ref = source_ref_id
535 c.source_ref = source_ref_id
535
536
536 c.target_ref_type = 'rev'
537 c.target_ref_type = 'rev'
537 c.target_ref = target_ref_id
538 c.target_ref = target_ref_id
538
539
539 c.source_repo = source_repo
540 c.source_repo = source_repo
540 c.target_repo = target_repo
541 c.target_repo = target_repo
541
542
542 c.commit_ranges = []
543 c.commit_ranges = []
543 source_commit = EmptyCommit()
544 source_commit = EmptyCommit()
544 target_commit = EmptyCommit()
545 target_commit = EmptyCommit()
545 c.missing_requirements = False
546 c.missing_requirements = False
546
547
547 source_scm = source_repo.scm_instance()
548 source_scm = source_repo.scm_instance()
548 target_scm = target_repo.scm_instance()
549 target_scm = target_repo.scm_instance()
549
550
550 shadow_scm = None
551 shadow_scm = None
551 try:
552 try:
552 shadow_scm = pull_request_latest.get_shadow_repo()
553 shadow_scm = pull_request_latest.get_shadow_repo()
553 except Exception:
554 except Exception:
554 log.debug('Failed to get shadow repo', exc_info=True)
555 log.debug('Failed to get shadow repo', exc_info=True)
555 # try first the existing source_repo, and then shadow
556 # try first the existing source_repo, and then shadow
556 # repo if we can obtain one
557 # repo if we can obtain one
557 commits_source_repo = source_scm
558 commits_source_repo = source_scm
558 if shadow_scm:
559 if shadow_scm:
559 commits_source_repo = shadow_scm
560 commits_source_repo = shadow_scm
560
561
561 c.commits_source_repo = commits_source_repo
562 c.commits_source_repo = commits_source_repo
562 c.ancestor = None # set it to None, to hide it from PR view
563 c.ancestor = None # set it to None, to hide it from PR view
563
564
564 # empty version means latest, so we keep this to prevent
565 # empty version means latest, so we keep this to prevent
565 # double caching
566 # double caching
566 version_normalized = version or PullRequest.LATEST_VER
567 version_normalized = version or PullRequest.LATEST_VER
567 from_version_normalized = from_version or PullRequest.LATEST_VER
568 from_version_normalized = from_version or PullRequest.LATEST_VER
568
569
569 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
570 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
570 cache_file_path = diff_cache_exist(
571 cache_file_path = diff_cache_exist(
571 cache_path, 'pull_request', pull_request_id, version_normalized,
572 cache_path, 'pull_request', pull_request_id, version_normalized,
572 from_version_normalized, source_ref_id, target_ref_id,
573 from_version_normalized, source_ref_id, target_ref_id,
573 hide_whitespace_changes, diff_context, c.fulldiff)
574 hide_whitespace_changes, diff_context, c.fulldiff)
574
575
575 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
576 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
576 force_recache = self.get_recache_flag()