##// END OF EJS Templates
pull-requests: security, check for permissions on exposure of repo-refs
ergo -
r2251:4ded942f stable
parent child Browse files
Show More
@@ -1,1236 +1,1243 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 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)
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest)
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 import events
32 from rhodecode import events
33 from rhodecode.apps._base import RepoAppView, DataGridAppView
33 from rhodecode.apps._base import RepoAppView, DataGridAppView
34
34
35 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
35 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
36 from rhodecode.lib.base import vcs_operation_context
36 from rhodecode.lib.base import vcs_operation_context
37 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.auth import (
38 from rhodecode.lib.auth import (
39 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
39 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
40 NotAnonymous, CSRFRequired)
40 NotAnonymous, CSRFRequired)
41 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
41 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
42 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
42 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
43 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
43 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
44 RepositoryRequirementError, NodeDoesNotExistError, EmptyRepositoryError)
44 RepositoryRequirementError, NodeDoesNotExistError, EmptyRepositoryError)
45 from rhodecode.model.changeset_status import ChangesetStatusModel
45 from rhodecode.model.changeset_status import ChangesetStatusModel
46 from rhodecode.model.comment import CommentsModel
46 from rhodecode.model.comment import CommentsModel
47 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
47 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
48 ChangesetComment, ChangesetStatus, Repository)
48 ChangesetComment, ChangesetStatus, Repository)
49 from rhodecode.model.forms import PullRequestForm
49 from rhodecode.model.forms import PullRequestForm
50 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
51 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
51 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
52 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 class RepoPullRequestsView(RepoAppView, DataGridAppView):
57 class RepoPullRequestsView(RepoAppView, DataGridAppView):
58
58
59 def load_default_context(self):
59 def load_default_context(self):
60 c = self._get_local_tmpl_context(include_app_defaults=True)
60 c = self._get_local_tmpl_context(include_app_defaults=True)
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 translator=self.request.translate)
367 translator=self.request.translate)
368 c.pr_merge_errors = _merge_check.error_details
368 c.pr_merge_errors = _merge_check.error_details
369 c.pr_merge_possible = not _merge_check.failed
369 c.pr_merge_possible = not _merge_check.failed
370 c.pr_merge_message = _merge_check.merge_msg
370 c.pr_merge_message = _merge_check.merge_msg
371
371
372 c.pr_merge_info = MergeCheck.get_merge_conditions(
372 c.pr_merge_info = MergeCheck.get_merge_conditions(
373 pull_request_latest, translator=self.request.translate)
373 pull_request_latest, translator=self.request.translate)
374
374
375 c.pull_request_review_status = _merge_check.review_status
375 c.pull_request_review_status = _merge_check.review_status
376 if merge_checks:
376 if merge_checks:
377 self.request.override_renderer = \
377 self.request.override_renderer = \
378 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
378 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
379 return self._get_template_context(c)
379 return self._get_template_context(c)
380
380
381 comments_model = CommentsModel()
381 comments_model = CommentsModel()
382
382
383 # reviewers and statuses
383 # reviewers and statuses
384 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
384 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
385 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
385 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
386
386
387 # GENERAL COMMENTS with versions #
387 # GENERAL COMMENTS with versions #
388 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
388 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
389 q = q.order_by(ChangesetComment.comment_id.asc())
389 q = q.order_by(ChangesetComment.comment_id.asc())
390 general_comments = q
390 general_comments = q
391
391
392 # pick comments we want to render at current version
392 # pick comments we want to render at current version
393 c.comment_versions = comments_model.aggregate_comments(
393 c.comment_versions = comments_model.aggregate_comments(
394 general_comments, versions, c.at_version_num)
394 general_comments, versions, c.at_version_num)
395 c.comments = c.comment_versions[c.at_version_num]['until']
395 c.comments = c.comment_versions[c.at_version_num]['until']
396
396
397 # INLINE COMMENTS with versions #
397 # INLINE COMMENTS with versions #
398 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
398 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
399 q = q.order_by(ChangesetComment.comment_id.asc())
399 q = q.order_by(ChangesetComment.comment_id.asc())
400 inline_comments = q
400 inline_comments = q
401
401
402 c.inline_versions = comments_model.aggregate_comments(
402 c.inline_versions = comments_model.aggregate_comments(
403 inline_comments, versions, c.at_version_num, inline=True)
403 inline_comments, versions, c.at_version_num, inline=True)
404
404
405 # inject latest version
405 # inject latest version
406 latest_ver = PullRequest.get_pr_display_object(
406 latest_ver = PullRequest.get_pr_display_object(
407 pull_request_latest, pull_request_latest)
407 pull_request_latest, pull_request_latest)
408
408
409 c.versions = versions + [latest_ver]
409 c.versions = versions + [latest_ver]
410
410
411 # if we use version, then do not show later comments
411 # if we use version, then do not show later comments
412 # than current version
412 # than current version
413 display_inline_comments = collections.defaultdict(
413 display_inline_comments = collections.defaultdict(
414 lambda: collections.defaultdict(list))
414 lambda: collections.defaultdict(list))
415 for co in inline_comments:
415 for co in inline_comments:
416 if c.at_version_num:
416 if c.at_version_num:
417 # pick comments that are at least UPTO given version, so we
417 # pick comments that are at least UPTO given version, so we
418 # don't render comments for higher version
418 # don't render comments for higher version
419 should_render = co.pull_request_version_id and \
419 should_render = co.pull_request_version_id and \
420 co.pull_request_version_id <= c.at_version_num
420 co.pull_request_version_id <= c.at_version_num
421 else:
421 else:
422 # showing all, for 'latest'
422 # showing all, for 'latest'
423 should_render = True
423 should_render = True
424
424
425 if should_render:
425 if should_render:
426 display_inline_comments[co.f_path][co.line_no].append(co)
426 display_inline_comments[co.f_path][co.line_no].append(co)
427
427
428 # load diff data into template context, if we use compare mode then
428 # load diff data into template context, if we use compare mode then
429 # diff is calculated based on changes between versions of PR
429 # diff is calculated based on changes between versions of PR
430
430
431 source_repo = pull_request_at_ver.source_repo
431 source_repo = pull_request_at_ver.source_repo
432 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
432 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
433
433
434 target_repo = pull_request_at_ver.target_repo
434 target_repo = pull_request_at_ver.target_repo
435 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
435 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
436
436
437 if compare:
437 if compare:
438 # in compare switch the diff base to latest commit from prev version
438 # in compare switch the diff base to latest commit from prev version
439 target_ref_id = prev_pull_request_display_obj.revisions[0]
439 target_ref_id = prev_pull_request_display_obj.revisions[0]
440
440
441 # despite opening commits for bookmarks/branches/tags, we always
441 # despite opening commits for bookmarks/branches/tags, we always
442 # convert this to rev to prevent changes after bookmark or branch change
442 # convert this to rev to prevent changes after bookmark or branch change
443 c.source_ref_type = 'rev'
443 c.source_ref_type = 'rev'
444 c.source_ref = source_ref_id
444 c.source_ref = source_ref_id
445
445
446 c.target_ref_type = 'rev'
446 c.target_ref_type = 'rev'
447 c.target_ref = target_ref_id
447 c.target_ref = target_ref_id
448
448
449 c.source_repo = source_repo
449 c.source_repo = source_repo
450 c.target_repo = target_repo
450 c.target_repo = target_repo
451
451
452 c.commit_ranges = []
452 c.commit_ranges = []
453 source_commit = EmptyCommit()
453 source_commit = EmptyCommit()
454 target_commit = EmptyCommit()
454 target_commit = EmptyCommit()
455 c.missing_requirements = False
455 c.missing_requirements = False
456
456
457 source_scm = source_repo.scm_instance()
457 source_scm = source_repo.scm_instance()
458 target_scm = target_repo.scm_instance()
458 target_scm = target_repo.scm_instance()
459
459
460 # try first shadow repo, fallback to regular repo
460 # try first shadow repo, fallback to regular repo
461 try:
461 try:
462 commits_source_repo = pull_request_latest.get_shadow_repo()
462 commits_source_repo = pull_request_latest.get_shadow_repo()
463 except Exception:
463 except Exception:
464 log.debug('Failed to get shadow repo', exc_info=True)
464 log.debug('Failed to get shadow repo', exc_info=True)
465 commits_source_repo = source_scm
465 commits_source_repo = source_scm
466
466
467 c.commits_source_repo = commits_source_repo
467 c.commits_source_repo = commits_source_repo
468 commit_cache = {}
468 commit_cache = {}
469 try:
469 try:
470 pre_load = ["author", "branch", "date", "message"]
470 pre_load = ["author", "branch", "date", "message"]
471 show_revs = pull_request_at_ver.revisions
471 show_revs = pull_request_at_ver.revisions
472 for rev in show_revs:
472 for rev in show_revs:
473 comm = commits_source_repo.get_commit(
473 comm = commits_source_repo.get_commit(
474 commit_id=rev, pre_load=pre_load)
474 commit_id=rev, pre_load=pre_load)
475 c.commit_ranges.append(comm)
475 c.commit_ranges.append(comm)
476 commit_cache[comm.raw_id] = comm
476 commit_cache[comm.raw_id] = comm
477
477
478 # Order here matters, we first need to get target, and then
478 # Order here matters, we first need to get target, and then
479 # the source
479 # the source
480 target_commit = commits_source_repo.get_commit(
480 target_commit = commits_source_repo.get_commit(
481 commit_id=safe_str(target_ref_id))
481 commit_id=safe_str(target_ref_id))
482
482
483 source_commit = commits_source_repo.get_commit(
483 source_commit = commits_source_repo.get_commit(
484 commit_id=safe_str(source_ref_id))
484 commit_id=safe_str(source_ref_id))
485
485
486 except CommitDoesNotExistError:
486 except CommitDoesNotExistError:
487 log.warning(
487 log.warning(
488 'Failed to get commit from `{}` repo'.format(
488 'Failed to get commit from `{}` repo'.format(
489 commits_source_repo), exc_info=True)
489 commits_source_repo), exc_info=True)
490 except RepositoryRequirementError:
490 except RepositoryRequirementError:
491 log.warning(
491 log.warning(
492 'Failed to get all required data from repo', exc_info=True)
492 'Failed to get all required data from repo', exc_info=True)
493 c.missing_requirements = True
493 c.missing_requirements = True
494
494
495 c.ancestor = None # set it to None, to hide it from PR view
495 c.ancestor = None # set it to None, to hide it from PR view
496
496
497 try:
497 try:
498 ancestor_id = source_scm.get_common_ancestor(
498 ancestor_id = source_scm.get_common_ancestor(
499 source_commit.raw_id, target_commit.raw_id, target_scm)
499 source_commit.raw_id, target_commit.raw_id, target_scm)
500 c.ancestor_commit = source_scm.get_commit(ancestor_id)
500 c.ancestor_commit = source_scm.get_commit(ancestor_id)
501 except Exception:
501 except Exception:
502 c.ancestor_commit = None
502 c.ancestor_commit = None
503
503
504 c.statuses = source_repo.statuses(
504 c.statuses = source_repo.statuses(
505 [x.raw_id for x in c.commit_ranges])
505 [x.raw_id for x in c.commit_ranges])
506
506
507 # auto collapse if we have more than limit
507 # auto collapse if we have more than limit
508 collapse_limit = diffs.DiffProcessor._collapse_commits_over
508 collapse_limit = diffs.DiffProcessor._collapse_commits_over
509 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
509 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
510 c.compare_mode = compare
510 c.compare_mode = compare
511
511
512 # diff_limit is the old behavior, will cut off the whole diff
512 # diff_limit is the old behavior, will cut off the whole diff
513 # if the limit is applied otherwise will just hide the
513 # if the limit is applied otherwise will just hide the
514 # big files from the front-end
514 # big files from the front-end
515 diff_limit = c.visual.cut_off_limit_diff
515 diff_limit = c.visual.cut_off_limit_diff
516 file_limit = c.visual.cut_off_limit_file
516 file_limit = c.visual.cut_off_limit_file
517
517
518 c.missing_commits = False
518 c.missing_commits = False
519 if (c.missing_requirements
519 if (c.missing_requirements
520 or isinstance(source_commit, EmptyCommit)
520 or isinstance(source_commit, EmptyCommit)
521 or source_commit == target_commit):
521 or source_commit == target_commit):
522
522
523 c.missing_commits = True
523 c.missing_commits = True
524 else:
524 else:
525
525
526 c.diffset = self._get_diffset(
526 c.diffset = self._get_diffset(
527 c.source_repo.repo_name, commits_source_repo,
527 c.source_repo.repo_name, commits_source_repo,
528 source_ref_id, target_ref_id,
528 source_ref_id, target_ref_id,
529 target_commit, source_commit,
529 target_commit, source_commit,
530 diff_limit, c.fulldiff, file_limit, display_inline_comments)
530 diff_limit, c.fulldiff, file_limit, display_inline_comments)
531
531
532 c.limited_diff = c.diffset.limited_diff
532 c.limited_diff = c.diffset.limited_diff
533
533
534 # calculate removed files that are bound to comments
534 # calculate removed files that are bound to comments
535 comment_deleted_files = [
535 comment_deleted_files = [
536 fname for fname in display_inline_comments
536 fname for fname in display_inline_comments
537 if fname not in c.diffset.file_stats]
537 if fname not in c.diffset.file_stats]
538
538
539 c.deleted_files_comments = collections.defaultdict(dict)
539 c.deleted_files_comments = collections.defaultdict(dict)
540 for fname, per_line_comments in display_inline_comments.items():
540 for fname, per_line_comments in display_inline_comments.items():
541 if fname in comment_deleted_files:
541 if fname in comment_deleted_files:
542 c.deleted_files_comments[fname]['stats'] = 0
542 c.deleted_files_comments[fname]['stats'] = 0
543 c.deleted_files_comments[fname]['comments'] = list()
543 c.deleted_files_comments[fname]['comments'] = list()
544 for lno, comments in per_line_comments.items():
544 for lno, comments in per_line_comments.items():
545 c.deleted_files_comments[fname]['comments'].extend(
545 c.deleted_files_comments[fname]['comments'].extend(
546 comments)
546 comments)
547
547
548 # this is a hack to properly display links, when creating PR, the
548 # this is a hack to properly display links, when creating PR, the
549 # compare view and others uses different notation, and
549 # compare view and others uses different notation, and
550 # compare_commits.mako renders links based on the target_repo.
550 # compare_commits.mako renders links based on the target_repo.
551 # We need to swap that here to generate it properly on the html side
551 # We need to swap that here to generate it properly on the html side
552 c.target_repo = c.source_repo
552 c.target_repo = c.source_repo
553
553
554 c.commit_statuses = ChangesetStatus.STATUSES
554 c.commit_statuses = ChangesetStatus.STATUSES
555
555
556 c.show_version_changes = not pr_closed
556 c.show_version_changes = not pr_closed
557 if c.show_version_changes:
557 if c.show_version_changes:
558 cur_obj = pull_request_at_ver
558 cur_obj = pull_request_at_ver
559 prev_obj = prev_pull_request_at_ver
559 prev_obj = prev_pull_request_at_ver
560
560
561 old_commit_ids = prev_obj.revisions
561 old_commit_ids = prev_obj.revisions
562 new_commit_ids = cur_obj.revisions
562 new_commit_ids = cur_obj.revisions
563 commit_changes = PullRequestModel()._calculate_commit_id_changes(
563 commit_changes = PullRequestModel()._calculate_commit_id_changes(
564 old_commit_ids, new_commit_ids)
564 old_commit_ids, new_commit_ids)
565 c.commit_changes_summary = commit_changes
565 c.commit_changes_summary = commit_changes
566
566
567 # calculate the diff for commits between versions
567 # calculate the diff for commits between versions
568 c.commit_changes = []
568 c.commit_changes = []
569 mark = lambda cs, fw: list(
569 mark = lambda cs, fw: list(
570 h.itertools.izip_longest([], cs, fillvalue=fw))
570 h.itertools.izip_longest([], cs, fillvalue=fw))
571 for c_type, raw_id in mark(commit_changes.added, 'a') \
571 for c_type, raw_id in mark(commit_changes.added, 'a') \
572 + mark(commit_changes.removed, 'r') \
572 + mark(commit_changes.removed, 'r') \
573 + mark(commit_changes.common, 'c'):
573 + mark(commit_changes.common, 'c'):
574
574
575 if raw_id in commit_cache:
575 if raw_id in commit_cache:
576 commit = commit_cache[raw_id]
576 commit = commit_cache[raw_id]
577 else:
577 else:
578 try:
578 try:
579 commit = commits_source_repo.get_commit(raw_id)
579 commit = commits_source_repo.get_commit(raw_id)
580 except CommitDoesNotExistError:
580 except CommitDoesNotExistError:
581 # in case we fail extracting still use "dummy" commit
581 # in case we fail extracting still use "dummy" commit
582 # for display in commit diff
582 # for display in commit diff
583 commit = h.AttributeDict(
583 commit = h.AttributeDict(
584 {'raw_id': raw_id,
584 {'raw_id': raw_id,
585 'message': 'EMPTY or MISSING COMMIT'})
585 'message': 'EMPTY or MISSING COMMIT'})
586 c.commit_changes.append([c_type, commit])
586 c.commit_changes.append([c_type, commit])
587
587
588 # current user review statuses for each version
588 # current user review statuses for each version
589 c.review_versions = {}
589 c.review_versions = {}
590 if self._rhodecode_user.user_id in allowed_reviewers:
590 if self._rhodecode_user.user_id in allowed_reviewers:
591 for co in general_comments:
591 for co in general_comments:
592 if co.author.user_id == self._rhodecode_user.user_id:
592 if co.author.user_id == self._rhodecode_user.user_id:
593 # each comment has a status change
593 # each comment has a status change
594 status = co.status_change
594 status = co.status_change
595 if status:
595 if status:
596 _ver_pr = status[0].comment.pull_request_version_id
596 _ver_pr = status[0].comment.pull_request_version_id
597 c.review_versions[_ver_pr] = status[0]
597 c.review_versions[_ver_pr] = status[0]
598
598
599 return self._get_template_context(c)
599 return self._get_template_context(c)
600
600
601 def assure_not_empty_repo(self):
601 def assure_not_empty_repo(self):
602 _ = self.request.translate
602 _ = self.request.translate
603
603
604 try:
604 try:
605 self.db_repo.scm_instance().get_commit()
605 self.db_repo.scm_instance().get_commit()
606 except EmptyRepositoryError:
606 except EmptyRepositoryError:
607 h.flash(h.literal(_('There are no commits yet')),
607 h.flash(h.literal(_('There are no commits yet')),
608 category='warning')
608 category='warning')
609 raise HTTPFound(
609 raise HTTPFound(
610 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
610 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
611
611
612 @LoginRequired()
612 @LoginRequired()
613 @NotAnonymous()
613 @NotAnonymous()
614 @HasRepoPermissionAnyDecorator(
614 @HasRepoPermissionAnyDecorator(
615 'repository.read', 'repository.write', 'repository.admin')
615 'repository.read', 'repository.write', 'repository.admin')
616 @view_config(
616 @view_config(
617 route_name='pullrequest_new', request_method='GET',
617 route_name='pullrequest_new', request_method='GET',
618 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
618 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
619 def pull_request_new(self):
619 def pull_request_new(self):
620 _ = self.request.translate
620 _ = self.request.translate
621 c = self.load_default_context()
621 c = self.load_default_context()
622
622
623 self.assure_not_empty_repo()
623 self.assure_not_empty_repo()
624 source_repo = self.db_repo
624 source_repo = self.db_repo
625
625
626 commit_id = self.request.GET.get('commit')
626 commit_id = self.request.GET.get('commit')
627 branch_ref = self.request.GET.get('branch')
627 branch_ref = self.request.GET.get('branch')
628 bookmark_ref = self.request.GET.get('bookmark')
628 bookmark_ref = self.request.GET.get('bookmark')
629
629
630 try:
630 try:
631 source_repo_data = PullRequestModel().generate_repo_data(
631 source_repo_data = PullRequestModel().generate_repo_data(
632 source_repo, commit_id=commit_id,
632 source_repo, commit_id=commit_id,
633 branch=branch_ref, bookmark=bookmark_ref, translator=self.request.translate)
633 branch=branch_ref, bookmark=bookmark_ref, translator=self.request.translate)
634 except CommitDoesNotExistError as e:
634 except CommitDoesNotExistError as e:
635 log.exception(e)
635 log.exception(e)
636 h.flash(_('Commit does not exist'), 'error')
636 h.flash(_('Commit does not exist'), 'error')
637 raise HTTPFound(
637 raise HTTPFound(
638 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
638 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
639
639
640 default_target_repo = source_repo
640 default_target_repo = source_repo
641
641
642 if source_repo.parent:
642 if source_repo.parent:
643 parent_vcs_obj = source_repo.parent.scm_instance()
643 parent_vcs_obj = source_repo.parent.scm_instance()
644 if parent_vcs_obj and not parent_vcs_obj.is_empty():
644 if parent_vcs_obj and not parent_vcs_obj.is_empty():
645 # change default if we have a parent repo
645 # change default if we have a parent repo
646 default_target_repo = source_repo.parent
646 default_target_repo = source_repo.parent
647
647
648 target_repo_data = PullRequestModel().generate_repo_data(
648 target_repo_data = PullRequestModel().generate_repo_data(
649 default_target_repo, translator=self.request.translate)
649 default_target_repo, translator=self.request.translate)
650
650
651 selected_source_ref = source_repo_data['refs']['selected_ref']
651 selected_source_ref = source_repo_data['refs']['selected_ref']
652
652
653 title_source_ref = selected_source_ref.split(':', 2)[1]
653 title_source_ref = selected_source_ref.split(':', 2)[1]
654 c.default_title = PullRequestModel().generate_pullrequest_title(
654 c.default_title = PullRequestModel().generate_pullrequest_title(
655 source=source_repo.repo_name,
655 source=source_repo.repo_name,
656 source_ref=title_source_ref,
656 source_ref=title_source_ref,
657 target=default_target_repo.repo_name
657 target=default_target_repo.repo_name
658 )
658 )
659
659
660 c.default_repo_data = {
660 c.default_repo_data = {
661 'source_repo_name': source_repo.repo_name,
661 'source_repo_name': source_repo.repo_name,
662 'source_refs_json': json.dumps(source_repo_data),
662 'source_refs_json': json.dumps(source_repo_data),
663 'target_repo_name': default_target_repo.repo_name,
663 'target_repo_name': default_target_repo.repo_name,
664 'target_refs_json': json.dumps(target_repo_data),
664 'target_refs_json': json.dumps(target_repo_data),
665 }
665 }
666 c.default_source_ref = selected_source_ref
666 c.default_source_ref = selected_source_ref
667
667
668 return self._get_template_context(c)
668 return self._get_template_context(c)
669
669
670 @LoginRequired()
670 @LoginRequired()
671 @NotAnonymous()
671 @NotAnonymous()
672 @HasRepoPermissionAnyDecorator(
672 @HasRepoPermissionAnyDecorator(
673 'repository.read', 'repository.write', 'repository.admin')
673 'repository.read', 'repository.write', 'repository.admin')
674 @view_config(
674 @view_config(
675 route_name='pullrequest_repo_refs', request_method='GET',
675 route_name='pullrequest_repo_refs', request_method='GET',
676 renderer='json_ext', xhr=True)
676 renderer='json_ext', xhr=True)
677 def pull_request_repo_refs(self):
677 def pull_request_repo_refs(self):
678 target_repo_name = self.request.matchdict['target_repo_name']
678 target_repo_name = self.request.matchdict['target_repo_name']
679 repo = Repository.get_by_repo_name(target_repo_name)
679 repo = Repository.get_by_repo_name(target_repo_name)
680 if not repo:
680 if not repo:
681 raise HTTPNotFound()
681 raise HTTPNotFound()
682
683 target_perm = HasRepoPermissionAny(
684 'repository.read', 'repository.write', 'repository.admin')(
685 target_repo_name)
686 if not target_perm:
687 raise HTTPNotFound()
688
682 return PullRequestModel().generate_repo_data(
689 return PullRequestModel().generate_repo_data(
683 repo, translator=self.request.translate)
690 repo, translator=self.request.translate)
684
691
685 @LoginRequired()
692 @LoginRequired()
686 @NotAnonymous()
693 @NotAnonymous()
687 @HasRepoPermissionAnyDecorator(
694 @HasRepoPermissionAnyDecorator(
688 'repository.read', 'repository.write', 'repository.admin')
695 'repository.read', 'repository.write', 'repository.admin')
689 @view_config(
696 @view_config(
690 route_name='pullrequest_repo_destinations', request_method='GET',
697 route_name='pullrequest_repo_destinations', request_method='GET',
691 renderer='json_ext', xhr=True)
698 renderer='json_ext', xhr=True)
692 def pull_request_repo_destinations(self):
699 def pull_request_repo_destinations(self):
693 _ = self.request.translate
700 _ = self.request.translate
694 filter_query = self.request.GET.get('query')
701 filter_query = self.request.GET.get('query')
695
702
696 query = Repository.query() \
703 query = Repository.query() \
697 .order_by(func.length(Repository.repo_name)) \
704 .order_by(func.length(Repository.repo_name)) \
698 .filter(
705 .filter(
699 or_(Repository.repo_name == self.db_repo.repo_name,
706 or_(Repository.repo_name == self.db_repo.repo_name,
700 Repository.fork_id == self.db_repo.repo_id))
707 Repository.fork_id == self.db_repo.repo_id))
701
708
702 if filter_query:
709 if filter_query:
703 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
710 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
704 query = query.filter(
711 query = query.filter(
705 Repository.repo_name.ilike(ilike_expression))
712 Repository.repo_name.ilike(ilike_expression))
706
713
707 add_parent = False
714 add_parent = False
708 if self.db_repo.parent:
715 if self.db_repo.parent:
709 if filter_query in self.db_repo.parent.repo_name:
716 if filter_query in self.db_repo.parent.repo_name:
710 parent_vcs_obj = self.db_repo.parent.scm_instance()
717 parent_vcs_obj = self.db_repo.parent.scm_instance()
711 if parent_vcs_obj and not parent_vcs_obj.is_empty():
718 if parent_vcs_obj and not parent_vcs_obj.is_empty():
712 add_parent = True
719 add_parent = True
713
720
714 limit = 20 - 1 if add_parent else 20
721 limit = 20 - 1 if add_parent else 20
715 all_repos = query.limit(limit).all()
722 all_repos = query.limit(limit).all()
716 if add_parent:
723 if add_parent:
717 all_repos += [self.db_repo.parent]
724 all_repos += [self.db_repo.parent]
718
725
719 repos = []
726 repos = []
720 for obj in ScmModel().get_repos(all_repos):
727 for obj in ScmModel().get_repos(all_repos):
721 repos.append({
728 repos.append({
722 'id': obj['name'],
729 'id': obj['name'],
723 'text': obj['name'],
730 'text': obj['name'],
724 'type': 'repo',
731 'type': 'repo',
725 'obj': obj['dbrepo']
732 'obj': obj['dbrepo']
726 })
733 })
727
734
728 data = {
735 data = {
729 'more': False,
736 'more': False,
730 'results': [{
737 'results': [{
731 'text': _('Repositories'),
738 'text': _('Repositories'),
732 'children': repos
739 'children': repos
733 }] if repos else []
740 }] if repos else []
734 }
741 }
735 return data
742 return data
736
743
737 @LoginRequired()
744 @LoginRequired()
738 @NotAnonymous()
745 @NotAnonymous()
739 @HasRepoPermissionAnyDecorator(
746 @HasRepoPermissionAnyDecorator(
740 'repository.read', 'repository.write', 'repository.admin')
747 'repository.read', 'repository.write', 'repository.admin')
741 @CSRFRequired()
748 @CSRFRequired()
742 @view_config(
749 @view_config(
743 route_name='pullrequest_create', request_method='POST',
750 route_name='pullrequest_create', request_method='POST',
744 renderer=None)
751 renderer=None)
745 def pull_request_create(self):
752 def pull_request_create(self):
746 _ = self.request.translate
753 _ = self.request.translate
747 self.assure_not_empty_repo()
754 self.assure_not_empty_repo()
748
755
749 controls = peppercorn.parse(self.request.POST.items())
756 controls = peppercorn.parse(self.request.POST.items())
750
757
751 try:
758 try:
752 _form = PullRequestForm(self.db_repo.repo_id)().to_python(controls)
759 _form = PullRequestForm(self.db_repo.repo_id)().to_python(controls)
753 except formencode.Invalid as errors:
760 except formencode.Invalid as errors:
754 if errors.error_dict.get('revisions'):
761 if errors.error_dict.get('revisions'):
755 msg = 'Revisions: %s' % errors.error_dict['revisions']
762 msg = 'Revisions: %s' % errors.error_dict['revisions']
756 elif errors.error_dict.get('pullrequest_title'):
763 elif errors.error_dict.get('pullrequest_title'):
757 msg = _('Pull request requires a title with min. 3 chars')
764 msg = _('Pull request requires a title with min. 3 chars')
758 else:
765 else:
759 msg = _('Error creating pull request: {}').format(errors)
766 msg = _('Error creating pull request: {}').format(errors)
760 log.exception(msg)
767 log.exception(msg)
761 h.flash(msg, 'error')
768 h.flash(msg, 'error')
762
769
763 # would rather just go back to form ...
770 # would rather just go back to form ...
764 raise HTTPFound(
771 raise HTTPFound(
765 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
772 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
766
773
767 source_repo = _form['source_repo']
774 source_repo = _form['source_repo']
768 source_ref = _form['source_ref']
775 source_ref = _form['source_ref']
769 target_repo = _form['target_repo']
776 target_repo = _form['target_repo']
770 target_ref = _form['target_ref']
777 target_ref = _form['target_ref']
771 commit_ids = _form['revisions'][::-1]
778 commit_ids = _form['revisions'][::-1]
772
779
773 # find the ancestor for this pr
780 # find the ancestor for this pr
774 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
781 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
775 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
782 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
776
783
777 # re-check permissions again here
784 # re-check permissions again here
778 # source_repo we must have read permissions
785 # source_repo we must have read permissions
779
786
780 source_perm = HasRepoPermissionAny(
787 source_perm = HasRepoPermissionAny(
781 'repository.read',
788 'repository.read',
782 'repository.write', 'repository.admin')(source_db_repo.repo_name)
789 'repository.write', 'repository.admin')(source_db_repo.repo_name)
783 if not source_perm:
790 if not source_perm:
784 msg = _('Not Enough permissions to source repo `{}`.'.format(
791 msg = _('Not Enough permissions to source repo `{}`.'.format(
785 source_db_repo.repo_name))
792 source_db_repo.repo_name))
786 h.flash(msg, category='error')
793 h.flash(msg, category='error')
787 # copy the args back to redirect
794 # copy the args back to redirect
788 org_query = self.request.GET.mixed()
795 org_query = self.request.GET.mixed()
789 raise HTTPFound(
796 raise HTTPFound(
790 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
797 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
791 _query=org_query))
798 _query=org_query))
792
799
793 # target repo we must have read permissions, and also later on
800 # target repo we must have read permissions, and also later on
794 # we want to check branch permissions here
801 # we want to check branch permissions here
795 target_perm = HasRepoPermissionAny(
802 target_perm = HasRepoPermissionAny(
796 'repository.read',
803 'repository.read',
797 'repository.write', 'repository.admin')(target_db_repo.repo_name)
804 'repository.write', 'repository.admin')(target_db_repo.repo_name)
798 if not target_perm:
805 if not target_perm:
799 msg = _('Not Enough permissions to target repo `{}`.'.format(
806 msg = _('Not Enough permissions to target repo `{}`.'.format(
800 target_db_repo.repo_name))
807 target_db_repo.repo_name))
801 h.flash(msg, category='error')
808 h.flash(msg, category='error')
802 # copy the args back to redirect
809 # copy the args back to redirect
803 org_query = self.request.GET.mixed()
810 org_query = self.request.GET.mixed()
804 raise HTTPFound(
811 raise HTTPFound(
805 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
812 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
806 _query=org_query))
813 _query=org_query))
807
814
808 source_scm = source_db_repo.scm_instance()
815 source_scm = source_db_repo.scm_instance()
809 target_scm = target_db_repo.scm_instance()
816 target_scm = target_db_repo.scm_instance()
810
817
811 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
818 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
812 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
819 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
813
820
814 ancestor = source_scm.get_common_ancestor(
821 ancestor = source_scm.get_common_ancestor(
815 source_commit.raw_id, target_commit.raw_id, target_scm)
822 source_commit.raw_id, target_commit.raw_id, target_scm)
816
823
817 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
824 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
818 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
825 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
819
826
820 pullrequest_title = _form['pullrequest_title']
827 pullrequest_title = _form['pullrequest_title']
821 title_source_ref = source_ref.split(':', 2)[1]
828 title_source_ref = source_ref.split(':', 2)[1]
822 if not pullrequest_title:
829 if not pullrequest_title:
823 pullrequest_title = PullRequestModel().generate_pullrequest_title(
830 pullrequest_title = PullRequestModel().generate_pullrequest_title(
824 source=source_repo,
831 source=source_repo,
825 source_ref=title_source_ref,
832 source_ref=title_source_ref,
826 target=target_repo
833 target=target_repo
827 )
834 )
828
835
829 description = _form['pullrequest_desc']
836 description = _form['pullrequest_desc']
830
837
831 get_default_reviewers_data, validate_default_reviewers = \
838 get_default_reviewers_data, validate_default_reviewers = \
832 PullRequestModel().get_reviewer_functions()
839 PullRequestModel().get_reviewer_functions()
833
840
834 # recalculate reviewers logic, to make sure we can validate this
841 # recalculate reviewers logic, to make sure we can validate this
835 reviewer_rules = get_default_reviewers_data(
842 reviewer_rules = get_default_reviewers_data(
836 self._rhodecode_db_user, source_db_repo,
843 self._rhodecode_db_user, source_db_repo,
837 source_commit, target_db_repo, target_commit)
844 source_commit, target_db_repo, target_commit)
838
845
839 given_reviewers = _form['review_members']
846 given_reviewers = _form['review_members']
840 reviewers = validate_default_reviewers(given_reviewers, reviewer_rules)
847 reviewers = validate_default_reviewers(given_reviewers, reviewer_rules)
841
848
842 try:
849 try:
843 pull_request = PullRequestModel().create(
850 pull_request = PullRequestModel().create(
844 self._rhodecode_user.user_id, source_repo, source_ref,
851 self._rhodecode_user.user_id, source_repo, source_ref,
845 target_repo, target_ref, commit_ids, reviewers,
852 target_repo, target_ref, commit_ids, reviewers,
846 pullrequest_title, description, reviewer_rules
853 pullrequest_title, description, reviewer_rules
847 )
854 )
848 Session().commit()
855 Session().commit()
849
856
850 h.flash(_('Successfully opened new pull request'),
857 h.flash(_('Successfully opened new pull request'),
851 category='success')
858 category='success')
852 except Exception:
859 except Exception:
853 msg = _('Error occurred during creation of this pull request.')
860 msg = _('Error occurred during creation of this pull request.')
854 log.exception(msg)
861 log.exception(msg)
855 h.flash(msg, category='error')
862 h.flash(msg, category='error')
856
863
857 # copy the args back to redirect
864 # copy the args back to redirect
858 org_query = self.request.GET.mixed()
865 org_query = self.request.GET.mixed()
859 raise HTTPFound(
866 raise HTTPFound(
860 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
867 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
861 _query=org_query))
868 _query=org_query))
862
869
863 raise HTTPFound(
870 raise HTTPFound(
864 h.route_path('pullrequest_show', repo_name=target_repo,
871 h.route_path('pullrequest_show', repo_name=target_repo,
865 pull_request_id=pull_request.pull_request_id))
872 pull_request_id=pull_request.pull_request_id))
866
873
867 @LoginRequired()
874 @LoginRequired()
868 @NotAnonymous()
875 @NotAnonymous()
869 @HasRepoPermissionAnyDecorator(
876 @HasRepoPermissionAnyDecorator(
870 'repository.read', 'repository.write', 'repository.admin')
877 'repository.read', 'repository.write', 'repository.admin')
871 @CSRFRequired()
878 @CSRFRequired()
872 @view_config(
879 @view_config(
873 route_name='pullrequest_update', request_method='POST',
880 route_name='pullrequest_update', request_method='POST',
874 renderer='json_ext')
881 renderer='json_ext')
875 def pull_request_update(self):
882 def pull_request_update(self):
876 pull_request = PullRequest.get_or_404(
883 pull_request = PullRequest.get_or_404(
877 self.request.matchdict['pull_request_id'])
884 self.request.matchdict['pull_request_id'])
878
885
879 # only owner or admin can update it
886 # only owner or admin can update it
880 allowed_to_update = PullRequestModel().check_user_update(
887 allowed_to_update = PullRequestModel().check_user_update(
881 pull_request, self._rhodecode_user)
888 pull_request, self._rhodecode_user)
882 if allowed_to_update:
889 if allowed_to_update:
883 controls = peppercorn.parse(self.request.POST.items())
890 controls = peppercorn.parse(self.request.POST.items())
884
891
885 if 'review_members' in controls:
892 if 'review_members' in controls:
886 self._update_reviewers(
893 self._update_reviewers(
887 pull_request, controls['review_members'],
894 pull_request, controls['review_members'],
888 pull_request.reviewer_data)
895 pull_request.reviewer_data)
889 elif str2bool(self.request.POST.get('update_commits', 'false')):
896 elif str2bool(self.request.POST.get('update_commits', 'false')):
890 self._update_commits(pull_request)
897 self._update_commits(pull_request)
891 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
898 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
892 self._edit_pull_request(pull_request)
899 self._edit_pull_request(pull_request)
893 else:
900 else:
894 raise HTTPBadRequest()
901 raise HTTPBadRequest()
895 return True
902 return True
896 raise HTTPForbidden()
903 raise HTTPForbidden()
897
904
898 def _edit_pull_request(self, pull_request):
905 def _edit_pull_request(self, pull_request):
899 _ = self.request.translate
906 _ = self.request.translate
900 try:
907 try:
901 PullRequestModel().edit(
908 PullRequestModel().edit(
902 pull_request, self.request.POST.get('title'),
909 pull_request, self.request.POST.get('title'),
903 self.request.POST.get('description'), self._rhodecode_user)
910 self.request.POST.get('description'), self._rhodecode_user)
904 except ValueError:
911 except ValueError:
905 msg = _(u'Cannot update closed pull requests.')
912 msg = _(u'Cannot update closed pull requests.')
906 h.flash(msg, category='error')
913 h.flash(msg, category='error')
907 return
914 return
908 else:
915 else:
909 Session().commit()
916 Session().commit()
910
917
911 msg = _(u'Pull request title & description updated.')
918 msg = _(u'Pull request title & description updated.')
912 h.flash(msg, category='success')
919 h.flash(msg, category='success')
913 return
920 return
914
921
915 def _update_commits(self, pull_request):
922 def _update_commits(self, pull_request):
916 _ = self.request.translate
923 _ = self.request.translate
917 resp = PullRequestModel().update_commits(pull_request)
924 resp = PullRequestModel().update_commits(pull_request)
918
925
919 if resp.executed:
926 if resp.executed:
920
927
921 if resp.target_changed and resp.source_changed:
928 if resp.target_changed and resp.source_changed:
922 changed = 'target and source repositories'
929 changed = 'target and source repositories'
923 elif resp.target_changed and not resp.source_changed:
930 elif resp.target_changed and not resp.source_changed:
924 changed = 'target repository'
931 changed = 'target repository'
925 elif not resp.target_changed and resp.source_changed:
932 elif not resp.target_changed and resp.source_changed:
926 changed = 'source repository'
933 changed = 'source repository'
927 else:
934 else:
928 changed = 'nothing'
935 changed = 'nothing'
929
936
930 msg = _(
937 msg = _(
931 u'Pull request updated to "{source_commit_id}" with '
938 u'Pull request updated to "{source_commit_id}" with '
932 u'{count_added} added, {count_removed} removed commits. '
939 u'{count_added} added, {count_removed} removed commits. '
933 u'Source of changes: {change_source}')
940 u'Source of changes: {change_source}')
934 msg = msg.format(
941 msg = msg.format(
935 source_commit_id=pull_request.source_ref_parts.commit_id,
942 source_commit_id=pull_request.source_ref_parts.commit_id,
936 count_added=len(resp.changes.added),
943 count_added=len(resp.changes.added),
937 count_removed=len(resp.changes.removed),
944 count_removed=len(resp.changes.removed),
938 change_source=changed)
945 change_source=changed)
939 h.flash(msg, category='success')
946 h.flash(msg, category='success')
940
947
941 channel = '/repo${}$/pr/{}'.format(
948 channel = '/repo${}$/pr/{}'.format(
942 pull_request.target_repo.repo_name,
949 pull_request.target_repo.repo_name,
943 pull_request.pull_request_id)
950 pull_request.pull_request_id)
944 message = msg + (
951 message = msg + (
945 ' - <a onclick="window.location.reload()">'
952 ' - <a onclick="window.location.reload()">'
946 '<strong>{}</strong></a>'.format(_('Reload page')))
953 '<strong>{}</strong></a>'.format(_('Reload page')))
947 channelstream.post_message(
954 channelstream.post_message(
948 channel, message, self._rhodecode_user.username,
955 channel, message, self._rhodecode_user.username,
949 registry=self.request.registry)
956 registry=self.request.registry)
950 else:
957 else:
951 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
958 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
952 warning_reasons = [
959 warning_reasons = [
953 UpdateFailureReason.NO_CHANGE,
960 UpdateFailureReason.NO_CHANGE,
954 UpdateFailureReason.WRONG_REF_TYPE,
961 UpdateFailureReason.WRONG_REF_TYPE,
955 ]
962 ]
956 category = 'warning' if resp.reason in warning_reasons else 'error'
963 category = 'warning' if resp.reason in warning_reasons else 'error'
957 h.flash(msg, category=category)
964 h.flash(msg, category=category)
958
965
959 @LoginRequired()
966 @LoginRequired()
960 @NotAnonymous()
967 @NotAnonymous()
961 @HasRepoPermissionAnyDecorator(
968 @HasRepoPermissionAnyDecorator(
962 'repository.read', 'repository.write', 'repository.admin')
969 'repository.read', 'repository.write', 'repository.admin')
963 @CSRFRequired()
970 @CSRFRequired()
964 @view_config(
971 @view_config(
965 route_name='pullrequest_merge', request_method='POST',
972 route_name='pullrequest_merge', request_method='POST',
966 renderer='json_ext')
973 renderer='json_ext')
967 def pull_request_merge(self):
974 def pull_request_merge(self):
968 """
975 """
969 Merge will perform a server-side merge of the specified
976 Merge will perform a server-side merge of the specified
970 pull request, if the pull request is approved and mergeable.
977 pull request, if the pull request is approved and mergeable.
971 After successful merging, the pull request is automatically
978 After successful merging, the pull request is automatically
972 closed, with a relevant comment.
979 closed, with a relevant comment.
973 """
980 """
974 pull_request = PullRequest.get_or_404(
981 pull_request = PullRequest.get_or_404(
975 self.request.matchdict['pull_request_id'])
982 self.request.matchdict['pull_request_id'])
976
983
977 check = MergeCheck.validate(pull_request, self._rhodecode_db_user,
984 check = MergeCheck.validate(pull_request, self._rhodecode_db_user,
978 translator=self.request.translate)
985 translator=self.request.translate)
979 merge_possible = not check.failed
986 merge_possible = not check.failed
980
987
981 for err_type, error_msg in check.errors:
988 for err_type, error_msg in check.errors:
982 h.flash(error_msg, category=err_type)
989 h.flash(error_msg, category=err_type)
983
990
984 if merge_possible:
991 if merge_possible:
985 log.debug("Pre-conditions checked, trying to merge.")
992 log.debug("Pre-conditions checked, trying to merge.")
986 extras = vcs_operation_context(
993 extras = vcs_operation_context(
987 self.request.environ, repo_name=pull_request.target_repo.repo_name,
994 self.request.environ, repo_name=pull_request.target_repo.repo_name,
988 username=self._rhodecode_db_user.username, action='push',
995 username=self._rhodecode_db_user.username, action='push',
989 scm=pull_request.target_repo.repo_type)
996 scm=pull_request.target_repo.repo_type)
990 self._merge_pull_request(
997 self._merge_pull_request(
991 pull_request, self._rhodecode_db_user, extras)
998 pull_request, self._rhodecode_db_user, extras)
992 else:
999 else:
993 log.debug("Pre-conditions failed, NOT merging.")
1000 log.debug("Pre-conditions failed, NOT merging.")
994
1001
995 raise HTTPFound(
1002 raise HTTPFound(
996 h.route_path('pullrequest_show',
1003 h.route_path('pullrequest_show',
997 repo_name=pull_request.target_repo.repo_name,
1004 repo_name=pull_request.target_repo.repo_name,
998 pull_request_id=pull_request.pull_request_id))
1005 pull_request_id=pull_request.pull_request_id))
999
1006
1000 def _merge_pull_request(self, pull_request, user, extras):
1007 def _merge_pull_request(self, pull_request, user, extras):
1001 _ = self.request.translate
1008 _ = self.request.translate
1002 merge_resp = PullRequestModel().merge(pull_request, user, extras=extras)
1009 merge_resp = PullRequestModel().merge(pull_request, user, extras=extras)
1003
1010
1004 if merge_resp.executed:
1011 if merge_resp.executed:
1005 log.debug("The merge was successful, closing the pull request.")
1012 log.debug("The merge was successful, closing the pull request.")
1006 PullRequestModel().close_pull_request(
1013 PullRequestModel().close_pull_request(
1007 pull_request.pull_request_id, user)
1014 pull_request.pull_request_id, user)
1008 Session().commit()
1015 Session().commit()
1009 msg = _('Pull request was successfully merged and closed.')
1016 msg = _('Pull request was successfully merged and closed.')
1010 h.flash(msg, category='success')
1017 h.flash(msg, category='success')
1011 else:
1018 else:
1012 log.debug(
1019 log.debug(
1013 "The merge was not successful. Merge response: %s",
1020 "The merge was not successful. Merge response: %s",
1014 merge_resp)
1021 merge_resp)
1015 msg = PullRequestModel().merge_status_message(
1022 msg = PullRequestModel().merge_status_message(
1016 merge_resp.failure_reason)
1023 merge_resp.failure_reason)
1017 h.flash(msg, category='error')
1024 h.flash(msg, category='error')
1018
1025
1019 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1026 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1020 _ = self.request.translate
1027 _ = self.request.translate
1021 get_default_reviewers_data, validate_default_reviewers = \
1028 get_default_reviewers_data, validate_default_reviewers = \
1022 PullRequestModel().get_reviewer_functions()
1029 PullRequestModel().get_reviewer_functions()
1023
1030
1024 try:
1031 try:
1025 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1032 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1026 except ValueError as e:
1033 except ValueError as e:
1027 log.error('Reviewers Validation: {}'.format(e))
1034 log.error('Reviewers Validation: {}'.format(e))
1028 h.flash(e, category='error')
1035 h.flash(e, category='error')
1029 return
1036 return
1030
1037
1031 PullRequestModel().update_reviewers(
1038 PullRequestModel().update_reviewers(
1032 pull_request, reviewers, self._rhodecode_user)
1039 pull_request, reviewers, self._rhodecode_user)
1033 h.flash(_('Pull request reviewers updated.'), category='success')
1040 h.flash(_('Pull request reviewers updated.'), category='success')
1034 Session().commit()
1041 Session().commit()
1035
1042
1036 @LoginRequired()
1043 @LoginRequired()
1037 @NotAnonymous()
1044 @NotAnonymous()
1038 @HasRepoPermissionAnyDecorator(
1045 @HasRepoPermissionAnyDecorator(
1039 'repository.read', 'repository.write', 'repository.admin')
1046 'repository.read', 'repository.write', 'repository.admin')
1040 @CSRFRequired()
1047 @CSRFRequired()
1041 @view_config(
1048 @view_config(
1042 route_name='pullrequest_delete', request_method='POST',
1049 route_name='pullrequest_delete', request_method='POST',
1043 renderer='json_ext')
1050 renderer='json_ext')
1044 def pull_request_delete(self):
1051 def pull_request_delete(self):
1045 _ = self.request.translate
1052 _ = self.request.translate
1046
1053
1047 pull_request = PullRequest.get_or_404(
1054 pull_request = PullRequest.get_or_404(
1048 self.request.matchdict['pull_request_id'])
1055 self.request.matchdict['pull_request_id'])
1049
1056
1050 pr_closed = pull_request.is_closed()
1057 pr_closed = pull_request.is_closed()
1051 allowed_to_delete = PullRequestModel().check_user_delete(
1058 allowed_to_delete = PullRequestModel().check_user_delete(
1052 pull_request, self._rhodecode_user) and not pr_closed
1059 pull_request, self._rhodecode_user) and not pr_closed
1053
1060
1054 # only owner can delete it !
1061 # only owner can delete it !
1055 if allowed_to_delete:
1062 if allowed_to_delete:
1056 PullRequestModel().delete(pull_request, self._rhodecode_user)
1063 PullRequestModel().delete(pull_request, self._rhodecode_user)
1057 Session().commit()
1064 Session().commit()
1058 h.flash(_('Successfully deleted pull request'),
1065 h.flash(_('Successfully deleted pull request'),
1059 category='success')
1066 category='success')
1060 raise HTTPFound(h.route_path('pullrequest_show_all',
1067 raise HTTPFound(h.route_path('pullrequest_show_all',
1061 repo_name=self.db_repo_name))
1068 repo_name=self.db_repo_name))
1062
1069
1063 log.warning('user %s tried to delete pull request without access',
1070 log.warning('user %s tried to delete pull request without access',
1064 self._rhodecode_user)
1071 self._rhodecode_user)
1065 raise HTTPNotFound()
1072 raise HTTPNotFound()
1066
1073
1067 @LoginRequired()
1074 @LoginRequired()
1068 @NotAnonymous()
1075 @NotAnonymous()
1069 @HasRepoPermissionAnyDecorator(
1076 @HasRepoPermissionAnyDecorator(
1070 'repository.read', 'repository.write', 'repository.admin')
1077 'repository.read', 'repository.write', 'repository.admin')
1071 @CSRFRequired()
1078 @CSRFRequired()
1072 @view_config(
1079 @view_config(
1073 route_name='pullrequest_comment_create', request_method='POST',
1080 route_name='pullrequest_comment_create', request_method='POST',
1074 renderer='json_ext')
1081 renderer='json_ext')
1075 def pull_request_comment_create(self):
1082 def pull_request_comment_create(self):
1076 _ = self.request.translate
1083 _ = self.request.translate
1077
1084
1078 pull_request = PullRequest.get_or_404(
1085 pull_request = PullRequest.get_or_404(
1079 self.request.matchdict['pull_request_id'])
1086 self.request.matchdict['pull_request_id'])
1080 pull_request_id = pull_request.pull_request_id
1087 pull_request_id = pull_request.pull_request_id
1081
1088
1082 if pull_request.is_closed():
1089 if pull_request.is_closed():
1083 log.debug('comment: forbidden because pull request is closed')
1090 log.debug('comment: forbidden because pull request is closed')
1084 raise HTTPForbidden()
1091 raise HTTPForbidden()
1085
1092
1086 allowed_to_comment = PullRequestModel().check_user_comment(
1093 allowed_to_comment = PullRequestModel().check_user_comment(
1087 pull_request, self._rhodecode_user)
1094 pull_request, self._rhodecode_user)
1088 if not allowed_to_comment:
1095 if not allowed_to_comment:
1089 log.debug(
1096 log.debug(
1090 'comment: forbidden because pull request is from forbidden repo')
1097 'comment: forbidden because pull request is from forbidden repo')
1091 raise HTTPForbidden()
1098 raise HTTPForbidden()
1092
1099
1093 c = self.load_default_context()
1100 c = self.load_default_context()
1094
1101
1095 status = self.request.POST.get('changeset_status', None)
1102 status = self.request.POST.get('changeset_status', None)
1096 text = self.request.POST.get('text')
1103 text = self.request.POST.get('text')
1097 comment_type = self.request.POST.get('comment_type')
1104 comment_type = self.request.POST.get('comment_type')
1098 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1105 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1099 close_pull_request = self.request.POST.get('close_pull_request')
1106 close_pull_request = self.request.POST.get('close_pull_request')
1100
1107
1101 # the logic here should work like following, if we submit close
1108 # the logic here should work like following, if we submit close
1102 # pr comment, use `close_pull_request_with_comment` function
1109 # pr comment, use `close_pull_request_with_comment` function
1103 # else handle regular comment logic
1110 # else handle regular comment logic
1104
1111
1105 if close_pull_request:
1112 if close_pull_request:
1106 # only owner or admin or person with write permissions
1113 # only owner or admin or person with write permissions
1107 allowed_to_close = PullRequestModel().check_user_update(
1114 allowed_to_close = PullRequestModel().check_user_update(
1108 pull_request, self._rhodecode_user)
1115 pull_request, self._rhodecode_user)
1109 if not allowed_to_close:
1116 if not allowed_to_close:
1110 log.debug('comment: forbidden because not allowed to close '
1117 log.debug('comment: forbidden because not allowed to close '
1111 'pull request %s', pull_request_id)
1118 'pull request %s', pull_request_id)
1112 raise HTTPForbidden()
1119 raise HTTPForbidden()
1113 comment, status = PullRequestModel().close_pull_request_with_comment(
1120 comment, status = PullRequestModel().close_pull_request_with_comment(
1114 pull_request, self._rhodecode_user, self.db_repo, message=text)
1121 pull_request, self._rhodecode_user, self.db_repo, message=text)
1115 Session().flush()
1122 Session().flush()
1116 events.trigger(
1123 events.trigger(
1117 events.PullRequestCommentEvent(pull_request, comment))
1124 events.PullRequestCommentEvent(pull_request, comment))
1118
1125
1119 else:
1126 else:
1120 # regular comment case, could be inline, or one with status.
1127 # regular comment case, could be inline, or one with status.
1121 # for that one we check also permissions
1128 # for that one we check also permissions
1122
1129
1123 allowed_to_change_status = PullRequestModel().check_user_change_status(
1130 allowed_to_change_status = PullRequestModel().check_user_change_status(
1124 pull_request, self._rhodecode_user)
1131 pull_request, self._rhodecode_user)
1125
1132
1126 if status and allowed_to_change_status:
1133 if status and allowed_to_change_status:
1127 message = (_('Status change %(transition_icon)s %(status)s')
1134 message = (_('Status change %(transition_icon)s %(status)s')
1128 % {'transition_icon': '>',
1135 % {'transition_icon': '>',
1129 'status': ChangesetStatus.get_status_lbl(status)})
1136 'status': ChangesetStatus.get_status_lbl(status)})
1130 text = text or message
1137 text = text or message
1131
1138
1132 comment = CommentsModel().create(
1139 comment = CommentsModel().create(
1133 text=text,
1140 text=text,
1134 repo=self.db_repo.repo_id,
1141 repo=self.db_repo.repo_id,
1135 user=self._rhodecode_user.user_id,
1142 user=self._rhodecode_user.user_id,
1136 pull_request=pull_request,
1143 pull_request=pull_request,
1137 f_path=self.request.POST.get('f_path'),
1144 f_path=self.request.POST.get('f_path'),
1138 line_no=self.request.POST.get('line'),
1145 line_no=self.request.POST.get('line'),
1139 status_change=(ChangesetStatus.get_status_lbl(status)
1146 status_change=(ChangesetStatus.get_status_lbl(status)
1140 if status and allowed_to_change_status else None),
1147 if status and allowed_to_change_status else None),
1141 status_change_type=(status
1148 status_change_type=(status
1142 if status and allowed_to_change_status else None),
1149 if status and allowed_to_change_status else None),
1143 comment_type=comment_type,
1150 comment_type=comment_type,
1144 resolves_comment_id=resolves_comment_id
1151 resolves_comment_id=resolves_comment_id
1145 )
1152 )
1146
1153
1147 if allowed_to_change_status:
1154 if allowed_to_change_status:
1148 # calculate old status before we change it
1155 # calculate old status before we change it
1149 old_calculated_status = pull_request.calculated_review_status()
1156 old_calculated_status = pull_request.calculated_review_status()
1150
1157
1151 # get status if set !
1158 # get status if set !
1152 if status:
1159 if status:
1153 ChangesetStatusModel().set_status(
1160 ChangesetStatusModel().set_status(
1154 self.db_repo.repo_id,
1161 self.db_repo.repo_id,
1155 status,
1162 status,
1156 self._rhodecode_user.user_id,
1163 self._rhodecode_user.user_id,
1157 comment,
1164 comment,
1158 pull_request=pull_request
1165 pull_request=pull_request
1159 )
1166 )
1160
1167
1161 Session().flush()
1168 Session().flush()
1162 events.trigger(
1169 events.trigger(
1163 events.PullRequestCommentEvent(pull_request, comment))
1170 events.PullRequestCommentEvent(pull_request, comment))
1164
1171
1165 # we now calculate the status of pull request, and based on that
1172 # we now calculate the status of pull request, and based on that
1166 # calculation we set the commits status
1173 # calculation we set the commits status
1167 calculated_status = pull_request.calculated_review_status()
1174 calculated_status = pull_request.calculated_review_status()
1168 if old_calculated_status != calculated_status:
1175 if old_calculated_status != calculated_status:
1169 PullRequestModel()._trigger_pull_request_hook(
1176 PullRequestModel()._trigger_pull_request_hook(
1170 pull_request, self._rhodecode_user, 'review_status_change')
1177 pull_request, self._rhodecode_user, 'review_status_change')
1171
1178
1172 Session().commit()
1179 Session().commit()
1173
1180
1174 data = {
1181 data = {
1175 'target_id': h.safeid(h.safe_unicode(
1182 'target_id': h.safeid(h.safe_unicode(
1176 self.request.POST.get('f_path'))),
1183 self.request.POST.get('f_path'))),
1177 }
1184 }
1178 if comment:
1185 if comment:
1179 c.co = comment
1186 c.co = comment
1180 rendered_comment = render(
1187 rendered_comment = render(
1181 'rhodecode:templates/changeset/changeset_comment_block.mako',
1188 'rhodecode:templates/changeset/changeset_comment_block.mako',
1182 self._get_template_context(c), self.request)
1189 self._get_template_context(c), self.request)
1183
1190
1184 data.update(comment.get_dict())
1191 data.update(comment.get_dict())
1185 data.update({'rendered_text': rendered_comment})
1192 data.update({'rendered_text': rendered_comment})
1186
1193
1187 return data
1194 return data
1188
1195
1189 @LoginRequired()
1196 @LoginRequired()
1190 @NotAnonymous()
1197 @NotAnonymous()
1191 @HasRepoPermissionAnyDecorator(
1198 @HasRepoPermissionAnyDecorator(
1192 'repository.read', 'repository.write', 'repository.admin')
1199 'repository.read', 'repository.write', 'repository.admin')
1193 @CSRFRequired()
1200 @CSRFRequired()
1194 @view_config(
1201 @view_config(
1195 route_name='pullrequest_comment_delete', request_method='POST',
1202 route_name='pullrequest_comment_delete', request_method='POST',
1196 renderer='json_ext')
1203 renderer='json_ext')
1197 def pull_request_comment_delete(self):
1204 def pull_request_comment_delete(self):
1198 pull_request = PullRequest.get_or_404(
1205 pull_request = PullRequest.get_or_404(
1199 self.request.matchdict['pull_request_id'])
1206 self.request.matchdict['pull_request_id'])
1200
1207
1201 comment = ChangesetComment.get_or_404(
1208 comment = ChangesetComment.get_or_404(
1202 self.request.matchdict['comment_id'])
1209 self.request.matchdict['comment_id'])
1203 comment_id = comment.comment_id
1210 comment_id = comment.comment_id
1204
1211
1205 if pull_request.is_closed():
1212 if pull_request.is_closed():
1206 log.debug('comment: forbidden because pull request is closed')
1213 log.debug('comment: forbidden because pull request is closed')
1207 raise HTTPForbidden()
1214 raise HTTPForbidden()
1208
1215
1209 if not comment:
1216 if not comment:
1210 log.debug('Comment with id:%s not found, skipping', comment_id)
1217 log.debug('Comment with id:%s not found, skipping', comment_id)
1211 # comment already deleted in another call probably
1218 # comment already deleted in another call probably
1212 return True
1219 return True
1213
1220
1214 if comment.pull_request.is_closed():
1221 if comment.pull_request.is_closed():
1215 # don't allow deleting comments on closed pull request
1222 # don't allow deleting comments on closed pull request
1216 raise HTTPForbidden()
1223 raise HTTPForbidden()
1217
1224
1218 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1225 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1219 super_admin = h.HasPermissionAny('hg.admin')()
1226 super_admin = h.HasPermissionAny('hg.admin')()
1220 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1227 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1221 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1228 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1222 comment_repo_admin = is_repo_admin and is_repo_comment
1229 comment_repo_admin = is_repo_admin and is_repo_comment
1223
1230
1224 if super_admin or comment_owner or comment_repo_admin:
1231 if super_admin or comment_owner or comment_repo_admin:
1225 old_calculated_status = comment.pull_request.calculated_review_status()
1232 old_calculated_status = comment.pull_request.calculated_review_status()
1226 CommentsModel().delete(comment=comment, user=self._rhodecode_user)
1233 CommentsModel().delete(comment=comment, user=self._rhodecode_user)
1227 Session().commit()
1234 Session().commit()
1228 calculated_status = comment.pull_request.calculated_review_status()
1235 calculated_status = comment.pull_request.calculated_review_status()
1229 if old_calculated_status != calculated_status:
1236 if old_calculated_status != calculated_status:
1230 PullRequestModel()._trigger_pull_request_hook(
1237 PullRequestModel()._trigger_pull_request_hook(
1231 comment.pull_request, self._rhodecode_user, 'review_status_change')
1238 comment.pull_request, self._rhodecode_user, 'review_status_change')
1232 return True
1239 return True
1233 else:
1240 else:
1234 log.warning('No permissions for user %s to delete comment_id: %s',
1241 log.warning('No permissions for user %s to delete comment_id: %s',
1235 self._rhodecode_db_user, comment_id)
1242 self._rhodecode_db_user, comment_id)
1236 raise HTTPNotFound()
1243 raise HTTPNotFound()
General Comments 0
You need to be logged in to leave comments. Login now