##// END OF EJS Templates
pull-requests: redirect to repo PR after delete instead of my account.
marcink -
r2051:e0dd362a default
parent child Browse files
Show More
@@ -1,1186 +1,1187 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2017 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 peppercorn
25 import peppercorn
26 from pyramid.httpexceptions import (
26 from pyramid.httpexceptions import (
27 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest)
27 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest)
28 from pyramid.view import view_config
28 from pyramid.view import view_config
29 from pyramid.renderers import render
29 from pyramid.renderers import render
30
30
31 from rhodecode import events
31 from rhodecode import events
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.ext_json import json
36 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.auth import (
37 from rhodecode.lib.auth import (
38 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
38 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
39 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
39 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
40 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
40 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
41 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
41 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
42 RepositoryRequirementError, NodeDoesNotExistError, EmptyRepositoryError)
42 RepositoryRequirementError, NodeDoesNotExistError, EmptyRepositoryError)
43 from rhodecode.model.changeset_status import ChangesetStatusModel
43 from rhodecode.model.changeset_status import ChangesetStatusModel
44 from rhodecode.model.comment import CommentsModel
44 from rhodecode.model.comment import CommentsModel
45 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
45 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
46 ChangesetComment, ChangesetStatus, Repository)
46 ChangesetComment, ChangesetStatus, Repository)
47 from rhodecode.model.forms import PullRequestForm
47 from rhodecode.model.forms import PullRequestForm
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
49 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
50 from rhodecode.model.scm import ScmModel
50 from rhodecode.model.scm import ScmModel
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 class RepoPullRequestsView(RepoAppView, DataGridAppView):
55 class RepoPullRequestsView(RepoAppView, DataGridAppView):
56
56
57 def load_default_context(self):
57 def load_default_context(self):
58 c = self._get_local_tmpl_context(include_app_defaults=True)
58 c = self._get_local_tmpl_context(include_app_defaults=True)
59 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
59 # TODO(marcink): remove repo_info and use c.rhodecode_db_repo instead
60 c.repo_info = self.db_repo
60 c.repo_info = self.db_repo
61 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
61 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
62 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
62 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
63 self._register_global_c(c)
63 self._register_global_c(c)
64 return c
64 return c
65
65
66 def _get_pull_requests_list(
66 def _get_pull_requests_list(
67 self, repo_name, source, filter_type, opened_by, statuses):
67 self, repo_name, source, filter_type, opened_by, statuses):
68
68
69 draw, start, limit = self._extract_chunk(self.request)
69 draw, start, limit = self._extract_chunk(self.request)
70 search_q, order_by, order_dir = self._extract_ordering(self.request)
70 search_q, order_by, order_dir = self._extract_ordering(self.request)
71 _render = self.request.get_partial_renderer(
71 _render = self.request.get_partial_renderer(
72 'data_table/_dt_elements.mako')
72 'data_table/_dt_elements.mako')
73
73
74 # pagination
74 # pagination
75
75
76 if filter_type == 'awaiting_review':
76 if filter_type == 'awaiting_review':
77 pull_requests = PullRequestModel().get_awaiting_review(
77 pull_requests = PullRequestModel().get_awaiting_review(
78 repo_name, source=source, opened_by=opened_by,
78 repo_name, source=source, opened_by=opened_by,
79 statuses=statuses, offset=start, length=limit,
79 statuses=statuses, offset=start, length=limit,
80 order_by=order_by, order_dir=order_dir)
80 order_by=order_by, order_dir=order_dir)
81 pull_requests_total_count = PullRequestModel().count_awaiting_review(
81 pull_requests_total_count = PullRequestModel().count_awaiting_review(
82 repo_name, source=source, statuses=statuses,
82 repo_name, source=source, statuses=statuses,
83 opened_by=opened_by)
83 opened_by=opened_by)
84 elif filter_type == 'awaiting_my_review':
84 elif filter_type == 'awaiting_my_review':
85 pull_requests = PullRequestModel().get_awaiting_my_review(
85 pull_requests = PullRequestModel().get_awaiting_my_review(
86 repo_name, source=source, opened_by=opened_by,
86 repo_name, source=source, opened_by=opened_by,
87 user_id=self._rhodecode_user.user_id, statuses=statuses,
87 user_id=self._rhodecode_user.user_id, statuses=statuses,
88 offset=start, length=limit, order_by=order_by,
88 offset=start, length=limit, order_by=order_by,
89 order_dir=order_dir)
89 order_dir=order_dir)
90 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
90 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
91 repo_name, source=source, user_id=self._rhodecode_user.user_id,
91 repo_name, source=source, user_id=self._rhodecode_user.user_id,
92 statuses=statuses, opened_by=opened_by)
92 statuses=statuses, opened_by=opened_by)
93 else:
93 else:
94 pull_requests = PullRequestModel().get_all(
94 pull_requests = PullRequestModel().get_all(
95 repo_name, source=source, opened_by=opened_by,
95 repo_name, source=source, opened_by=opened_by,
96 statuses=statuses, offset=start, length=limit,
96 statuses=statuses, offset=start, length=limit,
97 order_by=order_by, order_dir=order_dir)
97 order_by=order_by, order_dir=order_dir)
98 pull_requests_total_count = PullRequestModel().count_all(
98 pull_requests_total_count = PullRequestModel().count_all(
99 repo_name, source=source, statuses=statuses,
99 repo_name, source=source, statuses=statuses,
100 opened_by=opened_by)
100 opened_by=opened_by)
101
101
102 data = []
102 data = []
103 comments_model = CommentsModel()
103 comments_model = CommentsModel()
104 for pr in pull_requests:
104 for pr in pull_requests:
105 comments = comments_model.get_all_comments(
105 comments = comments_model.get_all_comments(
106 self.db_repo.repo_id, pull_request=pr)
106 self.db_repo.repo_id, pull_request=pr)
107
107
108 data.append({
108 data.append({
109 'name': _render('pullrequest_name',
109 'name': _render('pullrequest_name',
110 pr.pull_request_id, pr.target_repo.repo_name),
110 pr.pull_request_id, pr.target_repo.repo_name),
111 'name_raw': pr.pull_request_id,
111 'name_raw': pr.pull_request_id,
112 'status': _render('pullrequest_status',
112 'status': _render('pullrequest_status',
113 pr.calculated_review_status()),
113 pr.calculated_review_status()),
114 'title': _render(
114 'title': _render(
115 'pullrequest_title', pr.title, pr.description),
115 'pullrequest_title', pr.title, pr.description),
116 'description': h.escape(pr.description),
116 'description': h.escape(pr.description),
117 'updated_on': _render('pullrequest_updated_on',
117 'updated_on': _render('pullrequest_updated_on',
118 h.datetime_to_time(pr.updated_on)),
118 h.datetime_to_time(pr.updated_on)),
119 'updated_on_raw': h.datetime_to_time(pr.updated_on),
119 'updated_on_raw': h.datetime_to_time(pr.updated_on),
120 'created_on': _render('pullrequest_updated_on',
120 'created_on': _render('pullrequest_updated_on',
121 h.datetime_to_time(pr.created_on)),
121 h.datetime_to_time(pr.created_on)),
122 'created_on_raw': h.datetime_to_time(pr.created_on),
122 'created_on_raw': h.datetime_to_time(pr.created_on),
123 'author': _render('pullrequest_author',
123 'author': _render('pullrequest_author',
124 pr.author.full_contact, ),
124 pr.author.full_contact, ),
125 'author_raw': pr.author.full_name,
125 'author_raw': pr.author.full_name,
126 'comments': _render('pullrequest_comments', len(comments)),
126 'comments': _render('pullrequest_comments', len(comments)),
127 'comments_raw': len(comments),
127 'comments_raw': len(comments),
128 'closed': pr.is_closed(),
128 'closed': pr.is_closed(),
129 })
129 })
130
130
131 data = ({
131 data = ({
132 'draw': draw,
132 'draw': draw,
133 'data': data,
133 'data': data,
134 'recordsTotal': pull_requests_total_count,
134 'recordsTotal': pull_requests_total_count,
135 'recordsFiltered': pull_requests_total_count,
135 'recordsFiltered': pull_requests_total_count,
136 })
136 })
137 return data
137 return data
138
138
139 @LoginRequired()
139 @LoginRequired()
140 @HasRepoPermissionAnyDecorator(
140 @HasRepoPermissionAnyDecorator(
141 'repository.read', 'repository.write', 'repository.admin')
141 'repository.read', 'repository.write', 'repository.admin')
142 @view_config(
142 @view_config(
143 route_name='pullrequest_show_all', request_method='GET',
143 route_name='pullrequest_show_all', request_method='GET',
144 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
144 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
145 def pull_request_list(self):
145 def pull_request_list(self):
146 c = self.load_default_context()
146 c = self.load_default_context()
147
147
148 req_get = self.request.GET
148 req_get = self.request.GET
149 c.source = str2bool(req_get.get('source'))
149 c.source = str2bool(req_get.get('source'))
150 c.closed = str2bool(req_get.get('closed'))
150 c.closed = str2bool(req_get.get('closed'))
151 c.my = str2bool(req_get.get('my'))
151 c.my = str2bool(req_get.get('my'))
152 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
152 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
153 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
153 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
154
154
155 c.active = 'open'
155 c.active = 'open'
156 if c.my:
156 if c.my:
157 c.active = 'my'
157 c.active = 'my'
158 if c.closed:
158 if c.closed:
159 c.active = 'closed'
159 c.active = 'closed'
160 if c.awaiting_review and not c.source:
160 if c.awaiting_review and not c.source:
161 c.active = 'awaiting'
161 c.active = 'awaiting'
162 if c.source and not c.awaiting_review:
162 if c.source and not c.awaiting_review:
163 c.active = 'source'
163 c.active = 'source'
164 if c.awaiting_my_review:
164 if c.awaiting_my_review:
165 c.active = 'awaiting_my'
165 c.active = 'awaiting_my'
166
166
167 return self._get_template_context(c)
167 return self._get_template_context(c)
168
168
169 @LoginRequired()
169 @LoginRequired()
170 @HasRepoPermissionAnyDecorator(
170 @HasRepoPermissionAnyDecorator(
171 'repository.read', 'repository.write', 'repository.admin')
171 'repository.read', 'repository.write', 'repository.admin')
172 @view_config(
172 @view_config(
173 route_name='pullrequest_show_all_data', request_method='GET',
173 route_name='pullrequest_show_all_data', request_method='GET',
174 renderer='json_ext', xhr=True)
174 renderer='json_ext', xhr=True)
175 def pull_request_list_data(self):
175 def pull_request_list_data(self):
176
176
177 # additional filters
177 # additional filters
178 req_get = self.request.GET
178 req_get = self.request.GET
179 source = str2bool(req_get.get('source'))
179 source = str2bool(req_get.get('source'))
180 closed = str2bool(req_get.get('closed'))
180 closed = str2bool(req_get.get('closed'))
181 my = str2bool(req_get.get('my'))
181 my = str2bool(req_get.get('my'))
182 awaiting_review = str2bool(req_get.get('awaiting_review'))
182 awaiting_review = str2bool(req_get.get('awaiting_review'))
183 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
183 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
184
184
185 filter_type = 'awaiting_review' if awaiting_review \
185 filter_type = 'awaiting_review' if awaiting_review \
186 else 'awaiting_my_review' if awaiting_my_review \
186 else 'awaiting_my_review' if awaiting_my_review \
187 else None
187 else None
188
188
189 opened_by = None
189 opened_by = None
190 if my:
190 if my:
191 opened_by = [self._rhodecode_user.user_id]
191 opened_by = [self._rhodecode_user.user_id]
192
192
193 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
193 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
194 if closed:
194 if closed:
195 statuses = [PullRequest.STATUS_CLOSED]
195 statuses = [PullRequest.STATUS_CLOSED]
196
196
197 data = self._get_pull_requests_list(
197 data = self._get_pull_requests_list(
198 repo_name=self.db_repo_name, source=source,
198 repo_name=self.db_repo_name, source=source,
199 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
199 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
200
200
201 return data
201 return data
202
202
203 def _get_pr_version(self, pull_request_id, version=None):
203 def _get_pr_version(self, pull_request_id, version=None):
204 at_version = None
204 at_version = None
205
205
206 if version and version == 'latest':
206 if version and version == 'latest':
207 pull_request_ver = PullRequest.get(pull_request_id)
207 pull_request_ver = PullRequest.get(pull_request_id)
208 pull_request_obj = pull_request_ver
208 pull_request_obj = pull_request_ver
209 _org_pull_request_obj = pull_request_obj
209 _org_pull_request_obj = pull_request_obj
210 at_version = 'latest'
210 at_version = 'latest'
211 elif version:
211 elif version:
212 pull_request_ver = PullRequestVersion.get_or_404(version)
212 pull_request_ver = PullRequestVersion.get_or_404(version)
213 pull_request_obj = pull_request_ver
213 pull_request_obj = pull_request_ver
214 _org_pull_request_obj = pull_request_ver.pull_request
214 _org_pull_request_obj = pull_request_ver.pull_request
215 at_version = pull_request_ver.pull_request_version_id
215 at_version = pull_request_ver.pull_request_version_id
216 else:
216 else:
217 _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(
217 _org_pull_request_obj = pull_request_obj = PullRequest.get_or_404(
218 pull_request_id)
218 pull_request_id)
219
219
220 pull_request_display_obj = PullRequest.get_pr_display_object(
220 pull_request_display_obj = PullRequest.get_pr_display_object(
221 pull_request_obj, _org_pull_request_obj)
221 pull_request_obj, _org_pull_request_obj)
222
222
223 return _org_pull_request_obj, pull_request_obj, \
223 return _org_pull_request_obj, pull_request_obj, \
224 pull_request_display_obj, at_version
224 pull_request_display_obj, at_version
225
225
226 def _get_diffset(self, source_repo_name, source_repo,
226 def _get_diffset(self, source_repo_name, source_repo,
227 source_ref_id, target_ref_id,
227 source_ref_id, target_ref_id,
228 target_commit, source_commit, diff_limit, fulldiff,
228 target_commit, source_commit, diff_limit, fulldiff,
229 file_limit, display_inline_comments):
229 file_limit, display_inline_comments):
230
230
231 vcs_diff = PullRequestModel().get_diff(
231 vcs_diff = PullRequestModel().get_diff(
232 source_repo, source_ref_id, target_ref_id)
232 source_repo, source_ref_id, target_ref_id)
233
233
234 diff_processor = diffs.DiffProcessor(
234 diff_processor = diffs.DiffProcessor(
235 vcs_diff, format='newdiff', diff_limit=diff_limit,
235 vcs_diff, format='newdiff', diff_limit=diff_limit,
236 file_limit=file_limit, show_full_diff=fulldiff)
236 file_limit=file_limit, show_full_diff=fulldiff)
237
237
238 _parsed = diff_processor.prepare()
238 _parsed = diff_processor.prepare()
239
239
240 def _node_getter(commit):
240 def _node_getter(commit):
241 def get_node(fname):
241 def get_node(fname):
242 try:
242 try:
243 return commit.get_node(fname)
243 return commit.get_node(fname)
244 except NodeDoesNotExistError:
244 except NodeDoesNotExistError:
245 return None
245 return None
246
246
247 return get_node
247 return get_node
248
248
249 diffset = codeblocks.DiffSet(
249 diffset = codeblocks.DiffSet(
250 repo_name=self.db_repo_name,
250 repo_name=self.db_repo_name,
251 source_repo_name=source_repo_name,
251 source_repo_name=source_repo_name,
252 source_node_getter=_node_getter(target_commit),
252 source_node_getter=_node_getter(target_commit),
253 target_node_getter=_node_getter(source_commit),
253 target_node_getter=_node_getter(source_commit),
254 comments=display_inline_comments
254 comments=display_inline_comments
255 )
255 )
256 diffset = diffset.render_patchset(
256 diffset = diffset.render_patchset(
257 _parsed, target_commit.raw_id, source_commit.raw_id)
257 _parsed, target_commit.raw_id, source_commit.raw_id)
258
258
259 return diffset
259 return diffset
260
260
261 @LoginRequired()
261 @LoginRequired()
262 @HasRepoPermissionAnyDecorator(
262 @HasRepoPermissionAnyDecorator(
263 'repository.read', 'repository.write', 'repository.admin')
263 'repository.read', 'repository.write', 'repository.admin')
264 @view_config(
264 @view_config(
265 route_name='pullrequest_show', request_method='GET',
265 route_name='pullrequest_show', request_method='GET',
266 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
266 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
267 def pull_request_show(self):
267 def pull_request_show(self):
268 pull_request_id = self.request.matchdict['pull_request_id']
268 pull_request_id = self.request.matchdict['pull_request_id']
269
269
270 c = self.load_default_context()
270 c = self.load_default_context()
271
271
272 version = self.request.GET.get('version')
272 version = self.request.GET.get('version')
273 from_version = self.request.GET.get('from_version') or version
273 from_version = self.request.GET.get('from_version') or version
274 merge_checks = self.request.GET.get('merge_checks')
274 merge_checks = self.request.GET.get('merge_checks')
275 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
275 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
276
276
277 (pull_request_latest,
277 (pull_request_latest,
278 pull_request_at_ver,
278 pull_request_at_ver,
279 pull_request_display_obj,
279 pull_request_display_obj,
280 at_version) = self._get_pr_version(
280 at_version) = self._get_pr_version(
281 pull_request_id, version=version)
281 pull_request_id, version=version)
282 pr_closed = pull_request_latest.is_closed()
282 pr_closed = pull_request_latest.is_closed()
283
283
284 if pr_closed and (version or from_version):
284 if pr_closed and (version or from_version):
285 # not allow to browse versions
285 # not allow to browse versions
286 raise HTTPFound(h.route_path(
286 raise HTTPFound(h.route_path(
287 'pullrequest_show', repo_name=self.db_repo_name,
287 'pullrequest_show', repo_name=self.db_repo_name,
288 pull_request_id=pull_request_id))
288 pull_request_id=pull_request_id))
289
289
290 versions = pull_request_display_obj.versions()
290 versions = pull_request_display_obj.versions()
291
291
292 c.at_version = at_version
292 c.at_version = at_version
293 c.at_version_num = (at_version
293 c.at_version_num = (at_version
294 if at_version and at_version != 'latest'
294 if at_version and at_version != 'latest'
295 else None)
295 else None)
296 c.at_version_pos = ChangesetComment.get_index_from_version(
296 c.at_version_pos = ChangesetComment.get_index_from_version(
297 c.at_version_num, versions)
297 c.at_version_num, versions)
298
298
299 (prev_pull_request_latest,
299 (prev_pull_request_latest,
300 prev_pull_request_at_ver,
300 prev_pull_request_at_ver,
301 prev_pull_request_display_obj,
301 prev_pull_request_display_obj,
302 prev_at_version) = self._get_pr_version(
302 prev_at_version) = self._get_pr_version(
303 pull_request_id, version=from_version)
303 pull_request_id, version=from_version)
304
304
305 c.from_version = prev_at_version
305 c.from_version = prev_at_version
306 c.from_version_num = (prev_at_version
306 c.from_version_num = (prev_at_version
307 if prev_at_version and prev_at_version != 'latest'
307 if prev_at_version and prev_at_version != 'latest'
308 else None)
308 else None)
309 c.from_version_pos = ChangesetComment.get_index_from_version(
309 c.from_version_pos = ChangesetComment.get_index_from_version(
310 c.from_version_num, versions)
310 c.from_version_num, versions)
311
311
312 # define if we're in COMPARE mode or VIEW at version mode
312 # define if we're in COMPARE mode or VIEW at version mode
313 compare = at_version != prev_at_version
313 compare = at_version != prev_at_version
314
314
315 # pull_requests repo_name we opened it against
315 # pull_requests repo_name we opened it against
316 # ie. target_repo must match
316 # ie. target_repo must match
317 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
317 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
318 raise HTTPNotFound()
318 raise HTTPNotFound()
319
319
320 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
320 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
321 pull_request_at_ver)
321 pull_request_at_ver)
322
322
323 c.pull_request = pull_request_display_obj
323 c.pull_request = pull_request_display_obj
324 c.pull_request_latest = pull_request_latest
324 c.pull_request_latest = pull_request_latest
325
325
326 if compare or (at_version and not at_version == 'latest'):
326 if compare or (at_version and not at_version == 'latest'):
327 c.allowed_to_change_status = False
327 c.allowed_to_change_status = False
328 c.allowed_to_update = False
328 c.allowed_to_update = False
329 c.allowed_to_merge = False
329 c.allowed_to_merge = False
330 c.allowed_to_delete = False
330 c.allowed_to_delete = False
331 c.allowed_to_comment = False
331 c.allowed_to_comment = False
332 c.allowed_to_close = False
332 c.allowed_to_close = False
333 else:
333 else:
334 can_change_status = PullRequestModel().check_user_change_status(
334 can_change_status = PullRequestModel().check_user_change_status(
335 pull_request_at_ver, self._rhodecode_user)
335 pull_request_at_ver, self._rhodecode_user)
336 c.allowed_to_change_status = can_change_status and not pr_closed
336 c.allowed_to_change_status = can_change_status and not pr_closed
337
337
338 c.allowed_to_update = PullRequestModel().check_user_update(
338 c.allowed_to_update = PullRequestModel().check_user_update(
339 pull_request_latest, self._rhodecode_user) and not pr_closed
339 pull_request_latest, self._rhodecode_user) and not pr_closed
340 c.allowed_to_merge = PullRequestModel().check_user_merge(
340 c.allowed_to_merge = PullRequestModel().check_user_merge(
341 pull_request_latest, self._rhodecode_user) and not pr_closed
341 pull_request_latest, self._rhodecode_user) and not pr_closed
342 c.allowed_to_delete = PullRequestModel().check_user_delete(
342 c.allowed_to_delete = PullRequestModel().check_user_delete(
343 pull_request_latest, self._rhodecode_user) and not pr_closed
343 pull_request_latest, self._rhodecode_user) and not pr_closed
344 c.allowed_to_comment = not pr_closed
344 c.allowed_to_comment = not pr_closed
345 c.allowed_to_close = c.allowed_to_merge and not pr_closed
345 c.allowed_to_close = c.allowed_to_merge and not pr_closed
346
346
347 c.forbid_adding_reviewers = False
347 c.forbid_adding_reviewers = False
348 c.forbid_author_to_review = False
348 c.forbid_author_to_review = False
349 c.forbid_commit_author_to_review = False
349 c.forbid_commit_author_to_review = False
350
350
351 if pull_request_latest.reviewer_data and \
351 if pull_request_latest.reviewer_data and \
352 'rules' in pull_request_latest.reviewer_data:
352 'rules' in pull_request_latest.reviewer_data:
353 rules = pull_request_latest.reviewer_data['rules'] or {}
353 rules = pull_request_latest.reviewer_data['rules'] or {}
354 try:
354 try:
355 c.forbid_adding_reviewers = rules.get(
355 c.forbid_adding_reviewers = rules.get(
356 'forbid_adding_reviewers')
356 'forbid_adding_reviewers')
357 c.forbid_author_to_review = rules.get(
357 c.forbid_author_to_review = rules.get(
358 'forbid_author_to_review')
358 'forbid_author_to_review')
359 c.forbid_commit_author_to_review = rules.get(
359 c.forbid_commit_author_to_review = rules.get(
360 'forbid_commit_author_to_review')
360 'forbid_commit_author_to_review')
361 except Exception:
361 except Exception:
362 pass
362 pass
363
363
364 # check merge capabilities
364 # check merge capabilities
365 _merge_check = MergeCheck.validate(
365 _merge_check = MergeCheck.validate(
366 pull_request_latest, user=self._rhodecode_user)
366 pull_request_latest, user=self._rhodecode_user)
367 c.pr_merge_errors = _merge_check.error_details
367 c.pr_merge_errors = _merge_check.error_details
368 c.pr_merge_possible = not _merge_check.failed
368 c.pr_merge_possible = not _merge_check.failed
369 c.pr_merge_message = _merge_check.merge_msg
369 c.pr_merge_message = _merge_check.merge_msg
370
370
371 c.pull_request_review_status = _merge_check.review_status
371 c.pull_request_review_status = _merge_check.review_status
372 if merge_checks:
372 if merge_checks:
373 self.request.override_renderer = \
373 self.request.override_renderer = \
374 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
374 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
375 return self._get_template_context(c)
375 return self._get_template_context(c)
376
376
377 comments_model = CommentsModel()
377 comments_model = CommentsModel()
378
378
379 # reviewers and statuses
379 # reviewers and statuses
380 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
380 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
381 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
381 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
382
382
383 # GENERAL COMMENTS with versions #
383 # GENERAL COMMENTS with versions #
384 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
384 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
385 q = q.order_by(ChangesetComment.comment_id.asc())
385 q = q.order_by(ChangesetComment.comment_id.asc())
386 general_comments = q
386 general_comments = q
387
387
388 # pick comments we want to render at current version
388 # pick comments we want to render at current version
389 c.comment_versions = comments_model.aggregate_comments(
389 c.comment_versions = comments_model.aggregate_comments(
390 general_comments, versions, c.at_version_num)
390 general_comments, versions, c.at_version_num)
391 c.comments = c.comment_versions[c.at_version_num]['until']
391 c.comments = c.comment_versions[c.at_version_num]['until']
392
392
393 # INLINE COMMENTS with versions #
393 # INLINE COMMENTS with versions #
394 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
394 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
395 q = q.order_by(ChangesetComment.comment_id.asc())
395 q = q.order_by(ChangesetComment.comment_id.asc())
396 inline_comments = q
396 inline_comments = q
397
397
398 c.inline_versions = comments_model.aggregate_comments(
398 c.inline_versions = comments_model.aggregate_comments(
399 inline_comments, versions, c.at_version_num, inline=True)
399 inline_comments, versions, c.at_version_num, inline=True)
400
400
401 # inject latest version
401 # inject latest version
402 latest_ver = PullRequest.get_pr_display_object(
402 latest_ver = PullRequest.get_pr_display_object(
403 pull_request_latest, pull_request_latest)
403 pull_request_latest, pull_request_latest)
404
404
405 c.versions = versions + [latest_ver]
405 c.versions = versions + [latest_ver]
406
406
407 # if we use version, then do not show later comments
407 # if we use version, then do not show later comments
408 # than current version
408 # than current version
409 display_inline_comments = collections.defaultdict(
409 display_inline_comments = collections.defaultdict(
410 lambda: collections.defaultdict(list))
410 lambda: collections.defaultdict(list))
411 for co in inline_comments:
411 for co in inline_comments:
412 if c.at_version_num:
412 if c.at_version_num:
413 # pick comments that are at least UPTO given version, so we
413 # pick comments that are at least UPTO given version, so we
414 # don't render comments for higher version
414 # don't render comments for higher version
415 should_render = co.pull_request_version_id and \
415 should_render = co.pull_request_version_id and \
416 co.pull_request_version_id <= c.at_version_num
416 co.pull_request_version_id <= c.at_version_num
417 else:
417 else:
418 # showing all, for 'latest'
418 # showing all, for 'latest'
419 should_render = True
419 should_render = True
420
420
421 if should_render:
421 if should_render:
422 display_inline_comments[co.f_path][co.line_no].append(co)
422 display_inline_comments[co.f_path][co.line_no].append(co)
423
423
424 # load diff data into template context, if we use compare mode then
424 # load diff data into template context, if we use compare mode then
425 # diff is calculated based on changes between versions of PR
425 # diff is calculated based on changes between versions of PR
426
426
427 source_repo = pull_request_at_ver.source_repo
427 source_repo = pull_request_at_ver.source_repo
428 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
428 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
429
429
430 target_repo = pull_request_at_ver.target_repo
430 target_repo = pull_request_at_ver.target_repo
431 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
431 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
432
432
433 if compare:
433 if compare:
434 # in compare switch the diff base to latest commit from prev version
434 # in compare switch the diff base to latest commit from prev version
435 target_ref_id = prev_pull_request_display_obj.revisions[0]
435 target_ref_id = prev_pull_request_display_obj.revisions[0]
436
436
437 # despite opening commits for bookmarks/branches/tags, we always
437 # despite opening commits for bookmarks/branches/tags, we always
438 # convert this to rev to prevent changes after bookmark or branch change
438 # convert this to rev to prevent changes after bookmark or branch change
439 c.source_ref_type = 'rev'
439 c.source_ref_type = 'rev'
440 c.source_ref = source_ref_id
440 c.source_ref = source_ref_id
441
441
442 c.target_ref_type = 'rev'
442 c.target_ref_type = 'rev'
443 c.target_ref = target_ref_id
443 c.target_ref = target_ref_id
444
444
445 c.source_repo = source_repo
445 c.source_repo = source_repo
446 c.target_repo = target_repo
446 c.target_repo = target_repo
447
447
448 c.commit_ranges = []
448 c.commit_ranges = []
449 source_commit = EmptyCommit()
449 source_commit = EmptyCommit()
450 target_commit = EmptyCommit()
450 target_commit = EmptyCommit()
451 c.missing_requirements = False
451 c.missing_requirements = False
452
452
453 source_scm = source_repo.scm_instance()
453 source_scm = source_repo.scm_instance()
454 target_scm = target_repo.scm_instance()
454 target_scm = target_repo.scm_instance()
455
455
456 # try first shadow repo, fallback to regular repo
456 # try first shadow repo, fallback to regular repo
457 try:
457 try:
458 commits_source_repo = pull_request_latest.get_shadow_repo()
458 commits_source_repo = pull_request_latest.get_shadow_repo()
459 except Exception:
459 except Exception:
460 log.debug('Failed to get shadow repo', exc_info=True)
460 log.debug('Failed to get shadow repo', exc_info=True)
461 commits_source_repo = source_scm
461 commits_source_repo = source_scm
462
462
463 c.commits_source_repo = commits_source_repo
463 c.commits_source_repo = commits_source_repo
464 commit_cache = {}
464 commit_cache = {}
465 try:
465 try:
466 pre_load = ["author", "branch", "date", "message"]
466 pre_load = ["author", "branch", "date", "message"]
467 show_revs = pull_request_at_ver.revisions
467 show_revs = pull_request_at_ver.revisions
468 for rev in show_revs:
468 for rev in show_revs:
469 comm = commits_source_repo.get_commit(
469 comm = commits_source_repo.get_commit(
470 commit_id=rev, pre_load=pre_load)
470 commit_id=rev, pre_load=pre_load)
471 c.commit_ranges.append(comm)
471 c.commit_ranges.append(comm)
472 commit_cache[comm.raw_id] = comm
472 commit_cache[comm.raw_id] = comm
473
473
474 # Order here matters, we first need to get target, and then
474 # Order here matters, we first need to get target, and then
475 # the source
475 # the source
476 target_commit = commits_source_repo.get_commit(
476 target_commit = commits_source_repo.get_commit(
477 commit_id=safe_str(target_ref_id))
477 commit_id=safe_str(target_ref_id))
478
478
479 source_commit = commits_source_repo.get_commit(
479 source_commit = commits_source_repo.get_commit(
480 commit_id=safe_str(source_ref_id))
480 commit_id=safe_str(source_ref_id))
481
481
482 except CommitDoesNotExistError:
482 except CommitDoesNotExistError:
483 log.warning(
483 log.warning(
484 'Failed to get commit from `{}` repo'.format(
484 'Failed to get commit from `{}` repo'.format(
485 commits_source_repo), exc_info=True)
485 commits_source_repo), exc_info=True)
486 except RepositoryRequirementError:
486 except RepositoryRequirementError:
487 log.warning(
487 log.warning(
488 'Failed to get all required data from repo', exc_info=True)
488 'Failed to get all required data from repo', exc_info=True)
489 c.missing_requirements = True
489 c.missing_requirements = True
490
490
491 c.ancestor = None # set it to None, to hide it from PR view
491 c.ancestor = None # set it to None, to hide it from PR view
492
492
493 try:
493 try:
494 ancestor_id = source_scm.get_common_ancestor(
494 ancestor_id = source_scm.get_common_ancestor(
495 source_commit.raw_id, target_commit.raw_id, target_scm)
495 source_commit.raw_id, target_commit.raw_id, target_scm)
496 c.ancestor_commit = source_scm.get_commit(ancestor_id)
496 c.ancestor_commit = source_scm.get_commit(ancestor_id)
497 except Exception:
497 except Exception:
498 c.ancestor_commit = None
498 c.ancestor_commit = None
499
499
500 c.statuses = source_repo.statuses(
500 c.statuses = source_repo.statuses(
501 [x.raw_id for x in c.commit_ranges])
501 [x.raw_id for x in c.commit_ranges])
502
502
503 # auto collapse if we have more than limit
503 # auto collapse if we have more than limit
504 collapse_limit = diffs.DiffProcessor._collapse_commits_over
504 collapse_limit = diffs.DiffProcessor._collapse_commits_over
505 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
505 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
506 c.compare_mode = compare
506 c.compare_mode = compare
507
507
508 # diff_limit is the old behavior, will cut off the whole diff
508 # diff_limit is the old behavior, will cut off the whole diff
509 # if the limit is applied otherwise will just hide the
509 # if the limit is applied otherwise will just hide the
510 # big files from the front-end
510 # big files from the front-end
511 diff_limit = c.visual.cut_off_limit_diff
511 diff_limit = c.visual.cut_off_limit_diff
512 file_limit = c.visual.cut_off_limit_file
512 file_limit = c.visual.cut_off_limit_file
513
513
514 c.missing_commits = False
514 c.missing_commits = False
515 if (c.missing_requirements
515 if (c.missing_requirements
516 or isinstance(source_commit, EmptyCommit)
516 or isinstance(source_commit, EmptyCommit)
517 or source_commit == target_commit):
517 or source_commit == target_commit):
518
518
519 c.missing_commits = True
519 c.missing_commits = True
520 else:
520 else:
521
521
522 c.diffset = self._get_diffset(
522 c.diffset = self._get_diffset(
523 c.source_repo.repo_name, commits_source_repo,
523 c.source_repo.repo_name, commits_source_repo,
524 source_ref_id, target_ref_id,
524 source_ref_id, target_ref_id,
525 target_commit, source_commit,
525 target_commit, source_commit,
526 diff_limit, c.fulldiff, file_limit, display_inline_comments)
526 diff_limit, c.fulldiff, file_limit, display_inline_comments)
527
527
528 c.limited_diff = c.diffset.limited_diff
528 c.limited_diff = c.diffset.limited_diff
529
529
530 # calculate removed files that are bound to comments
530 # calculate removed files that are bound to comments
531 comment_deleted_files = [
531 comment_deleted_files = [
532 fname for fname in display_inline_comments
532 fname for fname in display_inline_comments
533 if fname not in c.diffset.file_stats]
533 if fname not in c.diffset.file_stats]
534
534
535 c.deleted_files_comments = collections.defaultdict(dict)
535 c.deleted_files_comments = collections.defaultdict(dict)
536 for fname, per_line_comments in display_inline_comments.items():
536 for fname, per_line_comments in display_inline_comments.items():
537 if fname in comment_deleted_files:
537 if fname in comment_deleted_files:
538 c.deleted_files_comments[fname]['stats'] = 0
538 c.deleted_files_comments[fname]['stats'] = 0
539 c.deleted_files_comments[fname]['comments'] = list()
539 c.deleted_files_comments[fname]['comments'] = list()
540 for lno, comments in per_line_comments.items():
540 for lno, comments in per_line_comments.items():
541 c.deleted_files_comments[fname]['comments'].extend(
541 c.deleted_files_comments[fname]['comments'].extend(
542 comments)
542 comments)
543
543
544 # this is a hack to properly display links, when creating PR, the
544 # this is a hack to properly display links, when creating PR, the
545 # compare view and others uses different notation, and
545 # compare view and others uses different notation, and
546 # compare_commits.mako renders links based on the target_repo.
546 # compare_commits.mako renders links based on the target_repo.
547 # We need to swap that here to generate it properly on the html side
547 # We need to swap that here to generate it properly on the html side
548 c.target_repo = c.source_repo
548 c.target_repo = c.source_repo
549
549
550 c.commit_statuses = ChangesetStatus.STATUSES
550 c.commit_statuses = ChangesetStatus.STATUSES
551
551
552 c.show_version_changes = not pr_closed
552 c.show_version_changes = not pr_closed
553 if c.show_version_changes:
553 if c.show_version_changes:
554 cur_obj = pull_request_at_ver
554 cur_obj = pull_request_at_ver
555 prev_obj = prev_pull_request_at_ver
555 prev_obj = prev_pull_request_at_ver
556
556
557 old_commit_ids = prev_obj.revisions
557 old_commit_ids = prev_obj.revisions
558 new_commit_ids = cur_obj.revisions
558 new_commit_ids = cur_obj.revisions
559 commit_changes = PullRequestModel()._calculate_commit_id_changes(
559 commit_changes = PullRequestModel()._calculate_commit_id_changes(
560 old_commit_ids, new_commit_ids)
560 old_commit_ids, new_commit_ids)
561 c.commit_changes_summary = commit_changes
561 c.commit_changes_summary = commit_changes
562
562
563 # calculate the diff for commits between versions
563 # calculate the diff for commits between versions
564 c.commit_changes = []
564 c.commit_changes = []
565 mark = lambda cs, fw: list(
565 mark = lambda cs, fw: list(
566 h.itertools.izip_longest([], cs, fillvalue=fw))
566 h.itertools.izip_longest([], cs, fillvalue=fw))
567 for c_type, raw_id in mark(commit_changes.added, 'a') \
567 for c_type, raw_id in mark(commit_changes.added, 'a') \
568 + mark(commit_changes.removed, 'r') \
568 + mark(commit_changes.removed, 'r') \
569 + mark(commit_changes.common, 'c'):
569 + mark(commit_changes.common, 'c'):
570
570
571 if raw_id in commit_cache:
571 if raw_id in commit_cache:
572 commit = commit_cache[raw_id]
572 commit = commit_cache[raw_id]
573 else:
573 else:
574 try:
574 try:
575 commit = commits_source_repo.get_commit(raw_id)
575 commit = commits_source_repo.get_commit(raw_id)
576 except CommitDoesNotExistError:
576 except CommitDoesNotExistError:
577 # in case we fail extracting still use "dummy" commit
577 # in case we fail extracting still use "dummy" commit
578 # for display in commit diff
578 # for display in commit diff
579 commit = h.AttributeDict(
579 commit = h.AttributeDict(
580 {'raw_id': raw_id,
580 {'raw_id': raw_id,
581 'message': 'EMPTY or MISSING COMMIT'})
581 'message': 'EMPTY or MISSING COMMIT'})
582 c.commit_changes.append([c_type, commit])
582 c.commit_changes.append([c_type, commit])
583
583
584 # current user review statuses for each version
584 # current user review statuses for each version
585 c.review_versions = {}
585 c.review_versions = {}
586 if self._rhodecode_user.user_id in allowed_reviewers:
586 if self._rhodecode_user.user_id in allowed_reviewers:
587 for co in general_comments:
587 for co in general_comments:
588 if co.author.user_id == self._rhodecode_user.user_id:
588 if co.author.user_id == self._rhodecode_user.user_id:
589 # each comment has a status change
589 # each comment has a status change
590 status = co.status_change
590 status = co.status_change
591 if status:
591 if status:
592 _ver_pr = status[0].comment.pull_request_version_id
592 _ver_pr = status[0].comment.pull_request_version_id
593 c.review_versions[_ver_pr] = status[0]
593 c.review_versions[_ver_pr] = status[0]
594
594
595 return self._get_template_context(c)
595 return self._get_template_context(c)
596
596
597 def assure_not_empty_repo(self):
597 def assure_not_empty_repo(self):
598 _ = self.request.translate
598 _ = self.request.translate
599
599
600 try:
600 try:
601 self.db_repo.scm_instance().get_commit()
601 self.db_repo.scm_instance().get_commit()
602 except EmptyRepositoryError:
602 except EmptyRepositoryError:
603 h.flash(h.literal(_('There are no commits yet')),
603 h.flash(h.literal(_('There are no commits yet')),
604 category='warning')
604 category='warning')
605 raise HTTPFound(
605 raise HTTPFound(
606 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
606 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
607
607
608 @LoginRequired()
608 @LoginRequired()
609 @NotAnonymous()
609 @NotAnonymous()
610 @HasRepoPermissionAnyDecorator(
610 @HasRepoPermissionAnyDecorator(
611 'repository.read', 'repository.write', 'repository.admin')
611 'repository.read', 'repository.write', 'repository.admin')
612 @view_config(
612 @view_config(
613 route_name='pullrequest_new', request_method='GET',
613 route_name='pullrequest_new', request_method='GET',
614 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
614 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
615 def pull_request_new(self):
615 def pull_request_new(self):
616 _ = self.request.translate
616 _ = self.request.translate
617 c = self.load_default_context()
617 c = self.load_default_context()
618
618
619 self.assure_not_empty_repo()
619 self.assure_not_empty_repo()
620 source_repo = self.db_repo
620 source_repo = self.db_repo
621
621
622 commit_id = self.request.GET.get('commit')
622 commit_id = self.request.GET.get('commit')
623 branch_ref = self.request.GET.get('branch')
623 branch_ref = self.request.GET.get('branch')
624 bookmark_ref = self.request.GET.get('bookmark')
624 bookmark_ref = self.request.GET.get('bookmark')
625
625
626 try:
626 try:
627 source_repo_data = PullRequestModel().generate_repo_data(
627 source_repo_data = PullRequestModel().generate_repo_data(
628 source_repo, commit_id=commit_id,
628 source_repo, commit_id=commit_id,
629 branch=branch_ref, bookmark=bookmark_ref)
629 branch=branch_ref, bookmark=bookmark_ref)
630 except CommitDoesNotExistError as e:
630 except CommitDoesNotExistError as e:
631 log.exception(e)
631 log.exception(e)
632 h.flash(_('Commit does not exist'), 'error')
632 h.flash(_('Commit does not exist'), 'error')
633 raise HTTPFound(
633 raise HTTPFound(
634 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
634 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
635
635
636 default_target_repo = source_repo
636 default_target_repo = source_repo
637
637
638 if source_repo.parent:
638 if source_repo.parent:
639 parent_vcs_obj = source_repo.parent.scm_instance()
639 parent_vcs_obj = source_repo.parent.scm_instance()
640 if parent_vcs_obj and not parent_vcs_obj.is_empty():
640 if parent_vcs_obj and not parent_vcs_obj.is_empty():
641 # change default if we have a parent repo
641 # change default if we have a parent repo
642 default_target_repo = source_repo.parent
642 default_target_repo = source_repo.parent
643
643
644 target_repo_data = PullRequestModel().generate_repo_data(
644 target_repo_data = PullRequestModel().generate_repo_data(
645 default_target_repo)
645 default_target_repo)
646
646
647 selected_source_ref = source_repo_data['refs']['selected_ref']
647 selected_source_ref = source_repo_data['refs']['selected_ref']
648
648
649 title_source_ref = selected_source_ref.split(':', 2)[1]
649 title_source_ref = selected_source_ref.split(':', 2)[1]
650 c.default_title = PullRequestModel().generate_pullrequest_title(
650 c.default_title = PullRequestModel().generate_pullrequest_title(
651 source=source_repo.repo_name,
651 source=source_repo.repo_name,
652 source_ref=title_source_ref,
652 source_ref=title_source_ref,
653 target=default_target_repo.repo_name
653 target=default_target_repo.repo_name
654 )
654 )
655
655
656 c.default_repo_data = {
656 c.default_repo_data = {
657 'source_repo_name': source_repo.repo_name,
657 'source_repo_name': source_repo.repo_name,
658 'source_refs_json': json.dumps(source_repo_data),
658 'source_refs_json': json.dumps(source_repo_data),
659 'target_repo_name': default_target_repo.repo_name,
659 'target_repo_name': default_target_repo.repo_name,
660 'target_refs_json': json.dumps(target_repo_data),
660 'target_refs_json': json.dumps(target_repo_data),
661 }
661 }
662 c.default_source_ref = selected_source_ref
662 c.default_source_ref = selected_source_ref
663
663
664 return self._get_template_context(c)
664 return self._get_template_context(c)
665
665
666 @LoginRequired()
666 @LoginRequired()
667 @NotAnonymous()
667 @NotAnonymous()
668 @HasRepoPermissionAnyDecorator(
668 @HasRepoPermissionAnyDecorator(
669 'repository.read', 'repository.write', 'repository.admin')
669 'repository.read', 'repository.write', 'repository.admin')
670 @view_config(
670 @view_config(
671 route_name='pullrequest_repo_refs', request_method='GET',
671 route_name='pullrequest_repo_refs', request_method='GET',
672 renderer='json_ext', xhr=True)
672 renderer='json_ext', xhr=True)
673 def pull_request_repo_refs(self):
673 def pull_request_repo_refs(self):
674 target_repo_name = self.request.matchdict['target_repo_name']
674 target_repo_name = self.request.matchdict['target_repo_name']
675 repo = Repository.get_by_repo_name(target_repo_name)
675 repo = Repository.get_by_repo_name(target_repo_name)
676 if not repo:
676 if not repo:
677 raise HTTPNotFound()
677 raise HTTPNotFound()
678 return PullRequestModel().generate_repo_data(repo)
678 return PullRequestModel().generate_repo_data(repo)
679
679
680 @LoginRequired()
680 @LoginRequired()
681 @NotAnonymous()
681 @NotAnonymous()
682 @HasRepoPermissionAnyDecorator(
682 @HasRepoPermissionAnyDecorator(
683 'repository.read', 'repository.write', 'repository.admin')
683 'repository.read', 'repository.write', 'repository.admin')
684 @view_config(
684 @view_config(
685 route_name='pullrequest_repo_destinations', request_method='GET',
685 route_name='pullrequest_repo_destinations', request_method='GET',
686 renderer='json_ext', xhr=True)
686 renderer='json_ext', xhr=True)
687 def pull_request_repo_destinations(self):
687 def pull_request_repo_destinations(self):
688 _ = self.request.translate
688 _ = self.request.translate
689 filter_query = self.request.GET.get('query')
689 filter_query = self.request.GET.get('query')
690
690
691 query = Repository.query() \
691 query = Repository.query() \
692 .order_by(func.length(Repository.repo_name)) \
692 .order_by(func.length(Repository.repo_name)) \
693 .filter(
693 .filter(
694 or_(Repository.repo_name == self.db_repo.repo_name,
694 or_(Repository.repo_name == self.db_repo.repo_name,
695 Repository.fork_id == self.db_repo.repo_id))
695 Repository.fork_id == self.db_repo.repo_id))
696
696
697 if filter_query:
697 if filter_query:
698 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
698 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
699 query = query.filter(
699 query = query.filter(
700 Repository.repo_name.ilike(ilike_expression))
700 Repository.repo_name.ilike(ilike_expression))
701
701
702 add_parent = False
702 add_parent = False
703 if self.db_repo.parent:
703 if self.db_repo.parent:
704 if filter_query in self.db_repo.parent.repo_name:
704 if filter_query in self.db_repo.parent.repo_name:
705 parent_vcs_obj = self.db_repo.parent.scm_instance()
705 parent_vcs_obj = self.db_repo.parent.scm_instance()
706 if parent_vcs_obj and not parent_vcs_obj.is_empty():
706 if parent_vcs_obj and not parent_vcs_obj.is_empty():
707 add_parent = True
707 add_parent = True
708
708
709 limit = 20 - 1 if add_parent else 20
709 limit = 20 - 1 if add_parent else 20
710 all_repos = query.limit(limit).all()
710 all_repos = query.limit(limit).all()
711 if add_parent:
711 if add_parent:
712 all_repos += [self.db_repo.parent]
712 all_repos += [self.db_repo.parent]
713
713
714 repos = []
714 repos = []
715 for obj in ScmModel().get_repos(all_repos):
715 for obj in ScmModel().get_repos(all_repos):
716 repos.append({
716 repos.append({
717 'id': obj['name'],
717 'id': obj['name'],
718 'text': obj['name'],
718 'text': obj['name'],
719 'type': 'repo',
719 'type': 'repo',
720 'obj': obj['dbrepo']
720 'obj': obj['dbrepo']
721 })
721 })
722
722
723 data = {
723 data = {
724 'more': False,
724 'more': False,
725 'results': [{
725 'results': [{
726 'text': _('Repositories'),
726 'text': _('Repositories'),
727 'children': repos
727 'children': repos
728 }] if repos else []
728 }] if repos else []
729 }
729 }
730 return data
730 return data
731
731
732 @LoginRequired()
732 @LoginRequired()
733 @NotAnonymous()
733 @NotAnonymous()
734 @HasRepoPermissionAnyDecorator(
734 @HasRepoPermissionAnyDecorator(
735 'repository.read', 'repository.write', 'repository.admin')
735 'repository.read', 'repository.write', 'repository.admin')
736 @CSRFRequired()
736 @CSRFRequired()
737 @view_config(
737 @view_config(
738 route_name='pullrequest_create', request_method='POST',
738 route_name='pullrequest_create', request_method='POST',
739 renderer=None)
739 renderer=None)
740 def pull_request_create(self):
740 def pull_request_create(self):
741 _ = self.request.translate
741 _ = self.request.translate
742 self.assure_not_empty_repo()
742 self.assure_not_empty_repo()
743
743
744 controls = peppercorn.parse(self.request.POST.items())
744 controls = peppercorn.parse(self.request.POST.items())
745
745
746 try:
746 try:
747 _form = PullRequestForm(self.db_repo.repo_id)().to_python(controls)
747 _form = PullRequestForm(self.db_repo.repo_id)().to_python(controls)
748 except formencode.Invalid as errors:
748 except formencode.Invalid as errors:
749 if errors.error_dict.get('revisions'):
749 if errors.error_dict.get('revisions'):
750 msg = 'Revisions: %s' % errors.error_dict['revisions']
750 msg = 'Revisions: %s' % errors.error_dict['revisions']
751 elif errors.error_dict.get('pullrequest_title'):
751 elif errors.error_dict.get('pullrequest_title'):
752 msg = _('Pull request requires a title with min. 3 chars')
752 msg = _('Pull request requires a title with min. 3 chars')
753 else:
753 else:
754 msg = _('Error creating pull request: {}').format(errors)
754 msg = _('Error creating pull request: {}').format(errors)
755 log.exception(msg)
755 log.exception(msg)
756 h.flash(msg, 'error')
756 h.flash(msg, 'error')
757
757
758 # would rather just go back to form ...
758 # would rather just go back to form ...
759 raise HTTPFound(
759 raise HTTPFound(
760 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
760 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
761
761
762 source_repo = _form['source_repo']
762 source_repo = _form['source_repo']
763 source_ref = _form['source_ref']
763 source_ref = _form['source_ref']
764 target_repo = _form['target_repo']
764 target_repo = _form['target_repo']
765 target_ref = _form['target_ref']
765 target_ref = _form['target_ref']
766 commit_ids = _form['revisions'][::-1]
766 commit_ids = _form['revisions'][::-1]
767
767
768 # find the ancestor for this pr
768 # find the ancestor for this pr
769 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
769 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
770 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
770 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
771
771
772 source_scm = source_db_repo.scm_instance()
772 source_scm = source_db_repo.scm_instance()
773 target_scm = target_db_repo.scm_instance()
773 target_scm = target_db_repo.scm_instance()
774
774
775 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
775 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
776 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
776 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
777
777
778 ancestor = source_scm.get_common_ancestor(
778 ancestor = source_scm.get_common_ancestor(
779 source_commit.raw_id, target_commit.raw_id, target_scm)
779 source_commit.raw_id, target_commit.raw_id, target_scm)
780
780
781 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
781 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
782 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
782 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
783
783
784 pullrequest_title = _form['pullrequest_title']
784 pullrequest_title = _form['pullrequest_title']
785 title_source_ref = source_ref.split(':', 2)[1]
785 title_source_ref = source_ref.split(':', 2)[1]
786 if not pullrequest_title:
786 if not pullrequest_title:
787 pullrequest_title = PullRequestModel().generate_pullrequest_title(
787 pullrequest_title = PullRequestModel().generate_pullrequest_title(
788 source=source_repo,
788 source=source_repo,
789 source_ref=title_source_ref,
789 source_ref=title_source_ref,
790 target=target_repo
790 target=target_repo
791 )
791 )
792
792
793 description = _form['pullrequest_desc']
793 description = _form['pullrequest_desc']
794
794
795 get_default_reviewers_data, validate_default_reviewers = \
795 get_default_reviewers_data, validate_default_reviewers = \
796 PullRequestModel().get_reviewer_functions()
796 PullRequestModel().get_reviewer_functions()
797
797
798 # recalculate reviewers logic, to make sure we can validate this
798 # recalculate reviewers logic, to make sure we can validate this
799 reviewer_rules = get_default_reviewers_data(
799 reviewer_rules = get_default_reviewers_data(
800 self._rhodecode_db_user, source_db_repo,
800 self._rhodecode_db_user, source_db_repo,
801 source_commit, target_db_repo, target_commit)
801 source_commit, target_db_repo, target_commit)
802
802
803 given_reviewers = _form['review_members']
803 given_reviewers = _form['review_members']
804 reviewers = validate_default_reviewers(given_reviewers, reviewer_rules)
804 reviewers = validate_default_reviewers(given_reviewers, reviewer_rules)
805
805
806 try:
806 try:
807 pull_request = PullRequestModel().create(
807 pull_request = PullRequestModel().create(
808 self._rhodecode_user.user_id, source_repo, source_ref, target_repo,
808 self._rhodecode_user.user_id, source_repo, source_ref, target_repo,
809 target_ref, commit_ids, reviewers, pullrequest_title,
809 target_ref, commit_ids, reviewers, pullrequest_title,
810 description, reviewer_rules
810 description, reviewer_rules
811 )
811 )
812 Session().commit()
812 Session().commit()
813 h.flash(_('Successfully opened new pull request'),
813 h.flash(_('Successfully opened new pull request'),
814 category='success')
814 category='success')
815 except Exception as e:
815 except Exception as e:
816 msg = _('Error occurred during creation of this pull request.')
816 msg = _('Error occurred during creation of this pull request.')
817 log.exception(msg)
817 log.exception(msg)
818 h.flash(msg, category='error')
818 h.flash(msg, category='error')
819 raise HTTPFound(
819 raise HTTPFound(
820 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
820 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
821
821
822 raise HTTPFound(
822 raise HTTPFound(
823 h.route_path('pullrequest_show', repo_name=target_repo,
823 h.route_path('pullrequest_show', repo_name=target_repo,
824 pull_request_id=pull_request.pull_request_id))
824 pull_request_id=pull_request.pull_request_id))
825
825
826 @LoginRequired()
826 @LoginRequired()
827 @NotAnonymous()
827 @NotAnonymous()
828 @HasRepoPermissionAnyDecorator(
828 @HasRepoPermissionAnyDecorator(
829 'repository.read', 'repository.write', 'repository.admin')
829 'repository.read', 'repository.write', 'repository.admin')
830 @CSRFRequired()
830 @CSRFRequired()
831 @view_config(
831 @view_config(
832 route_name='pullrequest_update', request_method='POST',
832 route_name='pullrequest_update', request_method='POST',
833 renderer='json_ext')
833 renderer='json_ext')
834 def pull_request_update(self):
834 def pull_request_update(self):
835 pull_request = PullRequest.get_or_404(
835 pull_request = PullRequest.get_or_404(
836 self.request.matchdict['pull_request_id'])
836 self.request.matchdict['pull_request_id'])
837
837
838 # only owner or admin can update it
838 # only owner or admin can update it
839 allowed_to_update = PullRequestModel().check_user_update(
839 allowed_to_update = PullRequestModel().check_user_update(
840 pull_request, self._rhodecode_user)
840 pull_request, self._rhodecode_user)
841 if allowed_to_update:
841 if allowed_to_update:
842 controls = peppercorn.parse(self.request.POST.items())
842 controls = peppercorn.parse(self.request.POST.items())
843
843
844 if 'review_members' in controls:
844 if 'review_members' in controls:
845 self._update_reviewers(
845 self._update_reviewers(
846 pull_request, controls['review_members'],
846 pull_request, controls['review_members'],
847 pull_request.reviewer_data)
847 pull_request.reviewer_data)
848 elif str2bool(self.request.POST.get('update_commits', 'false')):
848 elif str2bool(self.request.POST.get('update_commits', 'false')):
849 self._update_commits(pull_request)
849 self._update_commits(pull_request)
850 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
850 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
851 self._edit_pull_request(pull_request)
851 self._edit_pull_request(pull_request)
852 else:
852 else:
853 raise HTTPBadRequest()
853 raise HTTPBadRequest()
854 return True
854 return True
855 raise HTTPForbidden()
855 raise HTTPForbidden()
856
856
857 def _edit_pull_request(self, pull_request):
857 def _edit_pull_request(self, pull_request):
858 _ = self.request.translate
858 _ = self.request.translate
859 try:
859 try:
860 PullRequestModel().edit(
860 PullRequestModel().edit(
861 pull_request, self.request.POST.get('title'),
861 pull_request, self.request.POST.get('title'),
862 self.request.POST.get('description'), self._rhodecode_user)
862 self.request.POST.get('description'), self._rhodecode_user)
863 except ValueError:
863 except ValueError:
864 msg = _(u'Cannot update closed pull requests.')
864 msg = _(u'Cannot update closed pull requests.')
865 h.flash(msg, category='error')
865 h.flash(msg, category='error')
866 return
866 return
867 else:
867 else:
868 Session().commit()
868 Session().commit()
869
869
870 msg = _(u'Pull request title & description updated.')
870 msg = _(u'Pull request title & description updated.')
871 h.flash(msg, category='success')
871 h.flash(msg, category='success')
872 return
872 return
873
873
874 def _update_commits(self, pull_request):
874 def _update_commits(self, pull_request):
875 _ = self.request.translate
875 _ = self.request.translate
876 resp = PullRequestModel().update_commits(pull_request)
876 resp = PullRequestModel().update_commits(pull_request)
877
877
878 if resp.executed:
878 if resp.executed:
879
879
880 if resp.target_changed and resp.source_changed:
880 if resp.target_changed and resp.source_changed:
881 changed = 'target and source repositories'
881 changed = 'target and source repositories'
882 elif resp.target_changed and not resp.source_changed:
882 elif resp.target_changed and not resp.source_changed:
883 changed = 'target repository'
883 changed = 'target repository'
884 elif not resp.target_changed and resp.source_changed:
884 elif not resp.target_changed and resp.source_changed:
885 changed = 'source repository'
885 changed = 'source repository'
886 else:
886 else:
887 changed = 'nothing'
887 changed = 'nothing'
888
888
889 msg = _(
889 msg = _(
890 u'Pull request updated to "{source_commit_id}" with '
890 u'Pull request updated to "{source_commit_id}" with '
891 u'{count_added} added, {count_removed} removed commits. '
891 u'{count_added} added, {count_removed} removed commits. '
892 u'Source of changes: {change_source}')
892 u'Source of changes: {change_source}')
893 msg = msg.format(
893 msg = msg.format(
894 source_commit_id=pull_request.source_ref_parts.commit_id,
894 source_commit_id=pull_request.source_ref_parts.commit_id,
895 count_added=len(resp.changes.added),
895 count_added=len(resp.changes.added),
896 count_removed=len(resp.changes.removed),
896 count_removed=len(resp.changes.removed),
897 change_source=changed)
897 change_source=changed)
898 h.flash(msg, category='success')
898 h.flash(msg, category='success')
899
899
900 channel = '/repo${}$/pr/{}'.format(
900 channel = '/repo${}$/pr/{}'.format(
901 pull_request.target_repo.repo_name,
901 pull_request.target_repo.repo_name,
902 pull_request.pull_request_id)
902 pull_request.pull_request_id)
903 message = msg + (
903 message = msg + (
904 ' - <a onclick="window.location.reload()">'
904 ' - <a onclick="window.location.reload()">'
905 '<strong>{}</strong></a>'.format(_('Reload page')))
905 '<strong>{}</strong></a>'.format(_('Reload page')))
906 channelstream.post_message(
906 channelstream.post_message(
907 channel, message, self._rhodecode_user.username,
907 channel, message, self._rhodecode_user.username,
908 registry=self.request.registry)
908 registry=self.request.registry)
909 else:
909 else:
910 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
910 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
911 warning_reasons = [
911 warning_reasons = [
912 UpdateFailureReason.NO_CHANGE,
912 UpdateFailureReason.NO_CHANGE,
913 UpdateFailureReason.WRONG_REF_TYPE,
913 UpdateFailureReason.WRONG_REF_TYPE,
914 ]
914 ]
915 category = 'warning' if resp.reason in warning_reasons else 'error'
915 category = 'warning' if resp.reason in warning_reasons else 'error'
916 h.flash(msg, category=category)
916 h.flash(msg, category=category)
917
917
918 @LoginRequired()
918 @LoginRequired()
919 @NotAnonymous()
919 @NotAnonymous()
920 @HasRepoPermissionAnyDecorator(
920 @HasRepoPermissionAnyDecorator(
921 'repository.read', 'repository.write', 'repository.admin')
921 'repository.read', 'repository.write', 'repository.admin')
922 @CSRFRequired()
922 @CSRFRequired()
923 @view_config(
923 @view_config(
924 route_name='pullrequest_merge', request_method='POST',
924 route_name='pullrequest_merge', request_method='POST',
925 renderer='json_ext')
925 renderer='json_ext')
926 def pull_request_merge(self):
926 def pull_request_merge(self):
927 """
927 """
928 Merge will perform a server-side merge of the specified
928 Merge will perform a server-side merge of the specified
929 pull request, if the pull request is approved and mergeable.
929 pull request, if the pull request is approved and mergeable.
930 After successful merging, the pull request is automatically
930 After successful merging, the pull request is automatically
931 closed, with a relevant comment.
931 closed, with a relevant comment.
932 """
932 """
933 pull_request = PullRequest.get_or_404(
933 pull_request = PullRequest.get_or_404(
934 self.request.matchdict['pull_request_id'])
934 self.request.matchdict['pull_request_id'])
935
935
936 check = MergeCheck.validate(pull_request, self._rhodecode_db_user)
936 check = MergeCheck.validate(pull_request, self._rhodecode_db_user)
937 merge_possible = not check.failed
937 merge_possible = not check.failed
938
938
939 for err_type, error_msg in check.errors:
939 for err_type, error_msg in check.errors:
940 h.flash(error_msg, category=err_type)
940 h.flash(error_msg, category=err_type)
941
941
942 if merge_possible:
942 if merge_possible:
943 log.debug("Pre-conditions checked, trying to merge.")
943 log.debug("Pre-conditions checked, trying to merge.")
944 extras = vcs_operation_context(
944 extras = vcs_operation_context(
945 self.request.environ, repo_name=pull_request.target_repo.repo_name,
945 self.request.environ, repo_name=pull_request.target_repo.repo_name,
946 username=self._rhodecode_db_user.username, action='push',
946 username=self._rhodecode_db_user.username, action='push',
947 scm=pull_request.target_repo.repo_type)
947 scm=pull_request.target_repo.repo_type)
948 self._merge_pull_request(
948 self._merge_pull_request(
949 pull_request, self._rhodecode_db_user, extras)
949 pull_request, self._rhodecode_db_user, extras)
950 else:
950 else:
951 log.debug("Pre-conditions failed, NOT merging.")
951 log.debug("Pre-conditions failed, NOT merging.")
952
952
953 raise HTTPFound(
953 raise HTTPFound(
954 h.route_path('pullrequest_show',
954 h.route_path('pullrequest_show',
955 repo_name=pull_request.target_repo.repo_name,
955 repo_name=pull_request.target_repo.repo_name,
956 pull_request_id=pull_request.pull_request_id))
956 pull_request_id=pull_request.pull_request_id))
957
957
958 def _merge_pull_request(self, pull_request, user, extras):
958 def _merge_pull_request(self, pull_request, user, extras):
959 _ = self.request.translate
959 _ = self.request.translate
960 merge_resp = PullRequestModel().merge(pull_request, user, extras=extras)
960 merge_resp = PullRequestModel().merge(pull_request, user, extras=extras)
961
961
962 if merge_resp.executed:
962 if merge_resp.executed:
963 log.debug("The merge was successful, closing the pull request.")
963 log.debug("The merge was successful, closing the pull request.")
964 PullRequestModel().close_pull_request(
964 PullRequestModel().close_pull_request(
965 pull_request.pull_request_id, user)
965 pull_request.pull_request_id, user)
966 Session().commit()
966 Session().commit()
967 msg = _('Pull request was successfully merged and closed.')
967 msg = _('Pull request was successfully merged and closed.')
968 h.flash(msg, category='success')
968 h.flash(msg, category='success')
969 else:
969 else:
970 log.debug(
970 log.debug(
971 "The merge was not successful. Merge response: %s",
971 "The merge was not successful. Merge response: %s",
972 merge_resp)
972 merge_resp)
973 msg = PullRequestModel().merge_status_message(
973 msg = PullRequestModel().merge_status_message(
974 merge_resp.failure_reason)
974 merge_resp.failure_reason)
975 h.flash(msg, category='error')
975 h.flash(msg, category='error')
976
976
977 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
977 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
978 _ = self.request.translate
978 _ = self.request.translate
979 get_default_reviewers_data, validate_default_reviewers = \
979 get_default_reviewers_data, validate_default_reviewers = \
980 PullRequestModel().get_reviewer_functions()
980 PullRequestModel().get_reviewer_functions()
981
981
982 try:
982 try:
983 reviewers = validate_default_reviewers(review_members, reviewer_rules)
983 reviewers = validate_default_reviewers(review_members, reviewer_rules)
984 except ValueError as e:
984 except ValueError as e:
985 log.error('Reviewers Validation: {}'.format(e))
985 log.error('Reviewers Validation: {}'.format(e))
986 h.flash(e, category='error')
986 h.flash(e, category='error')
987 return
987 return
988
988
989 PullRequestModel().update_reviewers(
989 PullRequestModel().update_reviewers(
990 pull_request, reviewers, self._rhodecode_user)
990 pull_request, reviewers, self._rhodecode_user)
991 h.flash(_('Pull request reviewers updated.'), category='success')
991 h.flash(_('Pull request reviewers updated.'), category='success')
992 Session().commit()
992 Session().commit()
993
993
994 @LoginRequired()
994 @LoginRequired()
995 @NotAnonymous()
995 @NotAnonymous()
996 @HasRepoPermissionAnyDecorator(
996 @HasRepoPermissionAnyDecorator(
997 'repository.read', 'repository.write', 'repository.admin')
997 'repository.read', 'repository.write', 'repository.admin')
998 @CSRFRequired()
998 @CSRFRequired()
999 @view_config(
999 @view_config(
1000 route_name='pullrequest_delete', request_method='POST',
1000 route_name='pullrequest_delete', request_method='POST',
1001 renderer='json_ext')
1001 renderer='json_ext')
1002 def pull_request_delete(self):
1002 def pull_request_delete(self):
1003 _ = self.request.translate
1003 _ = self.request.translate
1004
1004
1005 pull_request = PullRequest.get_or_404(
1005 pull_request = PullRequest.get_or_404(
1006 self.request.matchdict['pull_request_id'])
1006 self.request.matchdict['pull_request_id'])
1007
1007
1008 pr_closed = pull_request.is_closed()
1008 pr_closed = pull_request.is_closed()
1009 allowed_to_delete = PullRequestModel().check_user_delete(
1009 allowed_to_delete = PullRequestModel().check_user_delete(
1010 pull_request, self._rhodecode_user) and not pr_closed
1010 pull_request, self._rhodecode_user) and not pr_closed
1011
1011
1012 # only owner can delete it !
1012 # only owner can delete it !
1013 if allowed_to_delete:
1013 if allowed_to_delete:
1014 PullRequestModel().delete(pull_request, self._rhodecode_user)
1014 PullRequestModel().delete(pull_request, self._rhodecode_user)
1015 Session().commit()
1015 Session().commit()
1016 h.flash(_('Successfully deleted pull request'),
1016 h.flash(_('Successfully deleted pull request'),
1017 category='success')
1017 category='success')
1018 raise HTTPFound(h.route_path('my_account_pullrequests'))
1018 raise HTTPFound(h.route_path('pullrequest_show_all',
1019 repo_name=self.db_repo_name))
1019
1020
1020 log.warning('user %s tried to delete pull request without access',
1021 log.warning('user %s tried to delete pull request without access',
1021 self._rhodecode_user)
1022 self._rhodecode_user)
1022 raise HTTPNotFound()
1023 raise HTTPNotFound()
1023
1024
1024 @LoginRequired()
1025 @LoginRequired()
1025 @NotAnonymous()
1026 @NotAnonymous()
1026 @HasRepoPermissionAnyDecorator(
1027 @HasRepoPermissionAnyDecorator(
1027 'repository.read', 'repository.write', 'repository.admin')
1028 'repository.read', 'repository.write', 'repository.admin')
1028 @CSRFRequired()
1029 @CSRFRequired()
1029 @view_config(
1030 @view_config(
1030 route_name='pullrequest_comment_create', request_method='POST',
1031 route_name='pullrequest_comment_create', request_method='POST',
1031 renderer='json_ext')
1032 renderer='json_ext')
1032 def pull_request_comment_create(self):
1033 def pull_request_comment_create(self):
1033 _ = self.request.translate
1034 _ = self.request.translate
1034
1035
1035 pull_request = PullRequest.get_or_404(
1036 pull_request = PullRequest.get_or_404(
1036 self.request.matchdict['pull_request_id'])
1037 self.request.matchdict['pull_request_id'])
1037 pull_request_id = pull_request.pull_request_id
1038 pull_request_id = pull_request.pull_request_id
1038
1039
1039 if pull_request.is_closed():
1040 if pull_request.is_closed():
1040 log.debug('comment: forbidden because pull request is closed')
1041 log.debug('comment: forbidden because pull request is closed')
1041 raise HTTPForbidden()
1042 raise HTTPForbidden()
1042
1043
1043 c = self.load_default_context()
1044 c = self.load_default_context()
1044
1045
1045 status = self.request.POST.get('changeset_status', None)
1046 status = self.request.POST.get('changeset_status', None)
1046 text = self.request.POST.get('text')
1047 text = self.request.POST.get('text')
1047 comment_type = self.request.POST.get('comment_type')
1048 comment_type = self.request.POST.get('comment_type')
1048 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1049 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1049 close_pull_request = self.request.POST.get('close_pull_request')
1050 close_pull_request = self.request.POST.get('close_pull_request')
1050
1051
1051 # the logic here should work like following, if we submit close
1052 # the logic here should work like following, if we submit close
1052 # pr comment, use `close_pull_request_with_comment` function
1053 # pr comment, use `close_pull_request_with_comment` function
1053 # else handle regular comment logic
1054 # else handle regular comment logic
1054
1055
1055 if close_pull_request:
1056 if close_pull_request:
1056 # only owner or admin or person with write permissions
1057 # only owner or admin or person with write permissions
1057 allowed_to_close = PullRequestModel().check_user_update(
1058 allowed_to_close = PullRequestModel().check_user_update(
1058 pull_request, self._rhodecode_user)
1059 pull_request, self._rhodecode_user)
1059 if not allowed_to_close:
1060 if not allowed_to_close:
1060 log.debug('comment: forbidden because not allowed to close '
1061 log.debug('comment: forbidden because not allowed to close '
1061 'pull request %s', pull_request_id)
1062 'pull request %s', pull_request_id)
1062 raise HTTPForbidden()
1063 raise HTTPForbidden()
1063 comment, status = PullRequestModel().close_pull_request_with_comment(
1064 comment, status = PullRequestModel().close_pull_request_with_comment(
1064 pull_request, self._rhodecode_user, self.db_repo, message=text)
1065 pull_request, self._rhodecode_user, self.db_repo, message=text)
1065 Session().flush()
1066 Session().flush()
1066 events.trigger(
1067 events.trigger(
1067 events.PullRequestCommentEvent(pull_request, comment))
1068 events.PullRequestCommentEvent(pull_request, comment))
1068
1069
1069 else:
1070 else:
1070 # regular comment case, could be inline, or one with status.
1071 # regular comment case, could be inline, or one with status.
1071 # for that one we check also permissions
1072 # for that one we check also permissions
1072
1073
1073 allowed_to_change_status = PullRequestModel().check_user_change_status(
1074 allowed_to_change_status = PullRequestModel().check_user_change_status(
1074 pull_request, self._rhodecode_user)
1075 pull_request, self._rhodecode_user)
1075
1076
1076 if status and allowed_to_change_status:
1077 if status and allowed_to_change_status:
1077 message = (_('Status change %(transition_icon)s %(status)s')
1078 message = (_('Status change %(transition_icon)s %(status)s')
1078 % {'transition_icon': '>',
1079 % {'transition_icon': '>',
1079 'status': ChangesetStatus.get_status_lbl(status)})
1080 'status': ChangesetStatus.get_status_lbl(status)})
1080 text = text or message
1081 text = text or message
1081
1082
1082 comment = CommentsModel().create(
1083 comment = CommentsModel().create(
1083 text=text,
1084 text=text,
1084 repo=self.db_repo.repo_id,
1085 repo=self.db_repo.repo_id,
1085 user=self._rhodecode_user.user_id,
1086 user=self._rhodecode_user.user_id,
1086 pull_request=pull_request,
1087 pull_request=pull_request,
1087 f_path=self.request.POST.get('f_path'),
1088 f_path=self.request.POST.get('f_path'),
1088 line_no=self.request.POST.get('line'),
1089 line_no=self.request.POST.get('line'),
1089 status_change=(ChangesetStatus.get_status_lbl(status)
1090 status_change=(ChangesetStatus.get_status_lbl(status)
1090 if status and allowed_to_change_status else None),
1091 if status and allowed_to_change_status else None),
1091 status_change_type=(status
1092 status_change_type=(status
1092 if status and allowed_to_change_status else None),
1093 if status and allowed_to_change_status else None),
1093 comment_type=comment_type,
1094 comment_type=comment_type,
1094 resolves_comment_id=resolves_comment_id
1095 resolves_comment_id=resolves_comment_id
1095 )
1096 )
1096
1097
1097 if allowed_to_change_status:
1098 if allowed_to_change_status:
1098 # calculate old status before we change it
1099 # calculate old status before we change it
1099 old_calculated_status = pull_request.calculated_review_status()
1100 old_calculated_status = pull_request.calculated_review_status()
1100
1101
1101 # get status if set !
1102 # get status if set !
1102 if status:
1103 if status:
1103 ChangesetStatusModel().set_status(
1104 ChangesetStatusModel().set_status(
1104 self.db_repo.repo_id,
1105 self.db_repo.repo_id,
1105 status,
1106 status,
1106 self._rhodecode_user.user_id,
1107 self._rhodecode_user.user_id,
1107 comment,
1108 comment,
1108 pull_request=pull_request
1109 pull_request=pull_request
1109 )
1110 )
1110
1111
1111 Session().flush()
1112 Session().flush()
1112 events.trigger(
1113 events.trigger(
1113 events.PullRequestCommentEvent(pull_request, comment))
1114 events.PullRequestCommentEvent(pull_request, comment))
1114
1115
1115 # we now calculate the status of pull request, and based on that
1116 # we now calculate the status of pull request, and based on that
1116 # calculation we set the commits status
1117 # calculation we set the commits status
1117 calculated_status = pull_request.calculated_review_status()
1118 calculated_status = pull_request.calculated_review_status()
1118 if old_calculated_status != calculated_status:
1119 if old_calculated_status != calculated_status:
1119 PullRequestModel()._trigger_pull_request_hook(
1120 PullRequestModel()._trigger_pull_request_hook(
1120 pull_request, self._rhodecode_user, 'review_status_change')
1121 pull_request, self._rhodecode_user, 'review_status_change')
1121
1122
1122 Session().commit()
1123 Session().commit()
1123
1124
1124 data = {
1125 data = {
1125 'target_id': h.safeid(h.safe_unicode(
1126 'target_id': h.safeid(h.safe_unicode(
1126 self.request.POST.get('f_path'))),
1127 self.request.POST.get('f_path'))),
1127 }
1128 }
1128 if comment:
1129 if comment:
1129 c.co = comment
1130 c.co = comment
1130 rendered_comment = render(
1131 rendered_comment = render(
1131 'rhodecode:templates/changeset/changeset_comment_block.mako',
1132 'rhodecode:templates/changeset/changeset_comment_block.mako',
1132 self._get_template_context(c), self.request)
1133 self._get_template_context(c), self.request)
1133
1134
1134 data.update(comment.get_dict())
1135 data.update(comment.get_dict())
1135 data.update({'rendered_text': rendered_comment})
1136 data.update({'rendered_text': rendered_comment})
1136
1137
1137 return data
1138 return data
1138
1139
1139 @LoginRequired()
1140 @LoginRequired()
1140 @NotAnonymous()
1141 @NotAnonymous()
1141 @HasRepoPermissionAnyDecorator(
1142 @HasRepoPermissionAnyDecorator(
1142 'repository.read', 'repository.write', 'repository.admin')
1143 'repository.read', 'repository.write', 'repository.admin')
1143 @CSRFRequired()
1144 @CSRFRequired()
1144 @view_config(
1145 @view_config(
1145 route_name='pullrequest_comment_delete', request_method='POST',
1146 route_name='pullrequest_comment_delete', request_method='POST',
1146 renderer='json_ext')
1147 renderer='json_ext')
1147 def pull_request_comment_delete(self):
1148 def pull_request_comment_delete(self):
1148 pull_request = PullRequest.get_or_404(
1149 pull_request = PullRequest.get_or_404(
1149 self.request.matchdict['pull_request_id'])
1150 self.request.matchdict['pull_request_id'])
1150
1151
1151 comment = ChangesetComment.get_or_404(
1152 comment = ChangesetComment.get_or_404(
1152 self.request.matchdict['comment_id'])
1153 self.request.matchdict['comment_id'])
1153 comment_id = comment.comment_id
1154 comment_id = comment.comment_id
1154
1155
1155 if pull_request.is_closed():
1156 if pull_request.is_closed():
1156 log.debug('comment: forbidden because pull request is closed')
1157 log.debug('comment: forbidden because pull request is closed')
1157 raise HTTPForbidden()
1158 raise HTTPForbidden()
1158
1159
1159 if not comment:
1160 if not comment:
1160 log.debug('Comment with id:%s not found, skipping', comment_id)
1161 log.debug('Comment with id:%s not found, skipping', comment_id)
1161 # comment already deleted in another call probably
1162 # comment already deleted in another call probably
1162 return True
1163 return True
1163
1164
1164 if comment.pull_request.is_closed():
1165 if comment.pull_request.is_closed():
1165 # don't allow deleting comments on closed pull request
1166 # don't allow deleting comments on closed pull request
1166 raise HTTPForbidden()
1167 raise HTTPForbidden()
1167
1168
1168 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1169 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1169 super_admin = h.HasPermissionAny('hg.admin')()
1170 super_admin = h.HasPermissionAny('hg.admin')()
1170 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1171 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1171 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1172 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1172 comment_repo_admin = is_repo_admin and is_repo_comment
1173 comment_repo_admin = is_repo_admin and is_repo_comment
1173
1174
1174 if super_admin or comment_owner or comment_repo_admin:
1175 if super_admin or comment_owner or comment_repo_admin:
1175 old_calculated_status = comment.pull_request.calculated_review_status()
1176 old_calculated_status = comment.pull_request.calculated_review_status()
1176 CommentsModel().delete(comment=comment, user=self._rhodecode_user)
1177 CommentsModel().delete(comment=comment, user=self._rhodecode_user)
1177 Session().commit()
1178 Session().commit()
1178 calculated_status = comment.pull_request.calculated_review_status()
1179 calculated_status = comment.pull_request.calculated_review_status()
1179 if old_calculated_status != calculated_status:
1180 if old_calculated_status != calculated_status:
1180 PullRequestModel()._trigger_pull_request_hook(
1181 PullRequestModel()._trigger_pull_request_hook(
1181 comment.pull_request, self._rhodecode_user, 'review_status_change')
1182 comment.pull_request, self._rhodecode_user, 'review_status_change')
1182 return True
1183 return True
1183 else:
1184 else:
1184 log.warning('No permissions for user %s to delete comment_id: %s',
1185 log.warning('No permissions for user %s to delete comment_id: %s',
1185 self._rhodecode_db_user, comment_id)
1186 self._rhodecode_db_user, comment_id)
1186 raise HTTPNotFound()
1187 raise HTTPNotFound()
General Comments 0
You need to be logged in to leave comments. Login now