##// END OF EJS Templates
pull-requests: fixed source of changes to be using shadow repos if it exists....
marcink -
r4128:1e7d835f default
parent child Browse files
Show More
@@ -1,1477 +1,1479 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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.apps._base import RepoAppView, DataGridAppView
32 from rhodecode.apps._base import RepoAppView, DataGridAppView
33
33
34 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
34 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
35 from rhodecode.lib.base import vcs_operation_context
35 from rhodecode.lib.base import vcs_operation_context
36 from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist
36 from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist
37 from rhodecode.lib.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, EmptyRepositoryError)
44 RepositoryRequirementError, 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 # backward compat., we use for OLD PRs a plain renderer
63 # backward compat., we use for OLD PRs a plain renderer
64 c.renderer = 'plain'
64 c.renderer = 'plain'
65 return c
65 return c
66
66
67 def _get_pull_requests_list(
67 def _get_pull_requests_list(
68 self, repo_name, source, filter_type, opened_by, statuses):
68 self, repo_name, source, filter_type, opened_by, statuses):
69
69
70 draw, start, limit = self._extract_chunk(self.request)
70 draw, start, limit = self._extract_chunk(self.request)
71 search_q, order_by, order_dir = self._extract_ordering(self.request)
71 search_q, order_by, order_dir = self._extract_ordering(self.request)
72 _render = self.request.get_partial_renderer(
72 _render = self.request.get_partial_renderer(
73 'rhodecode:templates/data_table/_dt_elements.mako')
73 'rhodecode:templates/data_table/_dt_elements.mako')
74
74
75 # pagination
75 # pagination
76
76
77 if filter_type == 'awaiting_review':
77 if filter_type == 'awaiting_review':
78 pull_requests = PullRequestModel().get_awaiting_review(
78 pull_requests = PullRequestModel().get_awaiting_review(
79 repo_name, search_q=search_q, source=source, opened_by=opened_by,
79 repo_name, search_q=search_q, source=source, opened_by=opened_by,
80 statuses=statuses, offset=start, length=limit,
80 statuses=statuses, offset=start, length=limit,
81 order_by=order_by, order_dir=order_dir)
81 order_by=order_by, order_dir=order_dir)
82 pull_requests_total_count = PullRequestModel().count_awaiting_review(
82 pull_requests_total_count = PullRequestModel().count_awaiting_review(
83 repo_name, search_q=search_q, source=source, statuses=statuses,
83 repo_name, search_q=search_q, source=source, statuses=statuses,
84 opened_by=opened_by)
84 opened_by=opened_by)
85 elif filter_type == 'awaiting_my_review':
85 elif filter_type == 'awaiting_my_review':
86 pull_requests = PullRequestModel().get_awaiting_my_review(
86 pull_requests = PullRequestModel().get_awaiting_my_review(
87 repo_name, search_q=search_q, source=source, opened_by=opened_by,
87 repo_name, search_q=search_q, source=source, opened_by=opened_by,
88 user_id=self._rhodecode_user.user_id, statuses=statuses,
88 user_id=self._rhodecode_user.user_id, statuses=statuses,
89 offset=start, length=limit, order_by=order_by,
89 offset=start, length=limit, order_by=order_by,
90 order_dir=order_dir)
90 order_dir=order_dir)
91 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
91 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
92 repo_name, search_q=search_q, source=source, user_id=self._rhodecode_user.user_id,
92 repo_name, search_q=search_q, source=source, user_id=self._rhodecode_user.user_id,
93 statuses=statuses, opened_by=opened_by)
93 statuses=statuses, opened_by=opened_by)
94 else:
94 else:
95 pull_requests = PullRequestModel().get_all(
95 pull_requests = PullRequestModel().get_all(
96 repo_name, search_q=search_q, source=source, opened_by=opened_by,
96 repo_name, search_q=search_q, source=source, opened_by=opened_by,
97 statuses=statuses, offset=start, length=limit,
97 statuses=statuses, offset=start, length=limit,
98 order_by=order_by, order_dir=order_dir)
98 order_by=order_by, order_dir=order_dir)
99 pull_requests_total_count = PullRequestModel().count_all(
99 pull_requests_total_count = PullRequestModel().count_all(
100 repo_name, search_q=search_q, source=source, statuses=statuses,
100 repo_name, search_q=search_q, source=source, statuses=statuses,
101 opened_by=opened_by)
101 opened_by=opened_by)
102
102
103 data = []
103 data = []
104 comments_model = CommentsModel()
104 comments_model = CommentsModel()
105 for pr in pull_requests:
105 for pr in pull_requests:
106 comments = comments_model.get_all_comments(
106 comments = comments_model.get_all_comments(
107 self.db_repo.repo_id, pull_request=pr)
107 self.db_repo.repo_id, pull_request=pr)
108
108
109 data.append({
109 data.append({
110 'name': _render('pullrequest_name',
110 'name': _render('pullrequest_name',
111 pr.pull_request_id, pr.pull_request_state,
111 pr.pull_request_id, pr.pull_request_state,
112 pr.work_in_progress, pr.target_repo.repo_name),
112 pr.work_in_progress, pr.target_repo.repo_name),
113 'name_raw': pr.pull_request_id,
113 'name_raw': pr.pull_request_id,
114 'status': _render('pullrequest_status',
114 'status': _render('pullrequest_status',
115 pr.calculated_review_status()),
115 pr.calculated_review_status()),
116 'title': _render('pullrequest_title', pr.title, pr.description),
116 'title': _render('pullrequest_title', pr.title, pr.description),
117 'description': h.escape(pr.description),
117 'description': h.escape(pr.description),
118 'updated_on': _render('pullrequest_updated_on',
118 'updated_on': _render('pullrequest_updated_on',
119 h.datetime_to_time(pr.updated_on)),
119 h.datetime_to_time(pr.updated_on)),
120 'updated_on_raw': h.datetime_to_time(pr.updated_on),
120 'updated_on_raw': h.datetime_to_time(pr.updated_on),
121 'created_on': _render('pullrequest_updated_on',
121 'created_on': _render('pullrequest_updated_on',
122 h.datetime_to_time(pr.created_on)),
122 h.datetime_to_time(pr.created_on)),
123 'created_on_raw': h.datetime_to_time(pr.created_on),
123 'created_on_raw': h.datetime_to_time(pr.created_on),
124 'state': pr.pull_request_state,
124 'state': pr.pull_request_state,
125 'author': _render('pullrequest_author',
125 'author': _render('pullrequest_author',
126 pr.author.full_contact, ),
126 pr.author.full_contact, ),
127 'author_raw': pr.author.full_name,
127 'author_raw': pr.author.full_name,
128 'comments': _render('pullrequest_comments', len(comments)),
128 'comments': _render('pullrequest_comments', len(comments)),
129 'comments_raw': len(comments),
129 'comments_raw': len(comments),
130 'closed': pr.is_closed(),
130 'closed': pr.is_closed(),
131 })
131 })
132
132
133 data = ({
133 data = ({
134 'draw': draw,
134 'draw': draw,
135 'data': data,
135 'data': data,
136 'recordsTotal': pull_requests_total_count,
136 'recordsTotal': pull_requests_total_count,
137 'recordsFiltered': pull_requests_total_count,
137 'recordsFiltered': pull_requests_total_count,
138 })
138 })
139 return data
139 return data
140
140
141 @LoginRequired()
141 @LoginRequired()
142 @HasRepoPermissionAnyDecorator(
142 @HasRepoPermissionAnyDecorator(
143 'repository.read', 'repository.write', 'repository.admin')
143 'repository.read', 'repository.write', 'repository.admin')
144 @view_config(
144 @view_config(
145 route_name='pullrequest_show_all', request_method='GET',
145 route_name='pullrequest_show_all', request_method='GET',
146 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
146 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
147 def pull_request_list(self):
147 def pull_request_list(self):
148 c = self.load_default_context()
148 c = self.load_default_context()
149
149
150 req_get = self.request.GET
150 req_get = self.request.GET
151 c.source = str2bool(req_get.get('source'))
151 c.source = str2bool(req_get.get('source'))
152 c.closed = str2bool(req_get.get('closed'))
152 c.closed = str2bool(req_get.get('closed'))
153 c.my = str2bool(req_get.get('my'))
153 c.my = str2bool(req_get.get('my'))
154 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
154 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
155 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
155 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
156
156
157 c.active = 'open'
157 c.active = 'open'
158 if c.my:
158 if c.my:
159 c.active = 'my'
159 c.active = 'my'
160 if c.closed:
160 if c.closed:
161 c.active = 'closed'
161 c.active = 'closed'
162 if c.awaiting_review and not c.source:
162 if c.awaiting_review and not c.source:
163 c.active = 'awaiting'
163 c.active = 'awaiting'
164 if c.source and not c.awaiting_review:
164 if c.source and not c.awaiting_review:
165 c.active = 'source'
165 c.active = 'source'
166 if c.awaiting_my_review:
166 if c.awaiting_my_review:
167 c.active = 'awaiting_my'
167 c.active = 'awaiting_my'
168
168
169 return self._get_template_context(c)
169 return self._get_template_context(c)
170
170
171 @LoginRequired()
171 @LoginRequired()
172 @HasRepoPermissionAnyDecorator(
172 @HasRepoPermissionAnyDecorator(
173 'repository.read', 'repository.write', 'repository.admin')
173 'repository.read', 'repository.write', 'repository.admin')
174 @view_config(
174 @view_config(
175 route_name='pullrequest_show_all_data', request_method='GET',
175 route_name='pullrequest_show_all_data', request_method='GET',
176 renderer='json_ext', xhr=True)
176 renderer='json_ext', xhr=True)
177 def pull_request_list_data(self):
177 def pull_request_list_data(self):
178 self.load_default_context()
178 self.load_default_context()
179
179
180 # additional filters
180 # additional filters
181 req_get = self.request.GET
181 req_get = self.request.GET
182 source = str2bool(req_get.get('source'))
182 source = str2bool(req_get.get('source'))
183 closed = str2bool(req_get.get('closed'))
183 closed = str2bool(req_get.get('closed'))
184 my = str2bool(req_get.get('my'))
184 my = str2bool(req_get.get('my'))
185 awaiting_review = str2bool(req_get.get('awaiting_review'))
185 awaiting_review = str2bool(req_get.get('awaiting_review'))
186 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
186 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
187
187
188 filter_type = 'awaiting_review' if awaiting_review \
188 filter_type = 'awaiting_review' if awaiting_review \
189 else 'awaiting_my_review' if awaiting_my_review \
189 else 'awaiting_my_review' if awaiting_my_review \
190 else None
190 else None
191
191
192 opened_by = None
192 opened_by = None
193 if my:
193 if my:
194 opened_by = [self._rhodecode_user.user_id]
194 opened_by = [self._rhodecode_user.user_id]
195
195
196 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
196 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
197 if closed:
197 if closed:
198 statuses = [PullRequest.STATUS_CLOSED]
198 statuses = [PullRequest.STATUS_CLOSED]
199
199
200 data = self._get_pull_requests_list(
200 data = self._get_pull_requests_list(
201 repo_name=self.db_repo_name, source=source,
201 repo_name=self.db_repo_name, source=source,
202 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
202 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
203
203
204 return data
204 return data
205
205
206 def _is_diff_cache_enabled(self, target_repo):
206 def _is_diff_cache_enabled(self, target_repo):
207 caching_enabled = self._get_general_setting(
207 caching_enabled = self._get_general_setting(
208 target_repo, 'rhodecode_diff_cache')
208 target_repo, 'rhodecode_diff_cache')
209 log.debug('Diff caching enabled: %s', caching_enabled)
209 log.debug('Diff caching enabled: %s', caching_enabled)
210 return caching_enabled
210 return caching_enabled
211
211
212 def _get_diffset(self, source_repo_name, source_repo,
212 def _get_diffset(self, source_repo_name, source_repo,
213 source_ref_id, target_ref_id,
213 source_ref_id, target_ref_id,
214 target_commit, source_commit, diff_limit, file_limit,
214 target_commit, source_commit, diff_limit, file_limit,
215 fulldiff, hide_whitespace_changes, diff_context):
215 fulldiff, hide_whitespace_changes, diff_context):
216
216
217 vcs_diff = PullRequestModel().get_diff(
217 vcs_diff = PullRequestModel().get_diff(
218 source_repo, source_ref_id, target_ref_id,
218 source_repo, source_ref_id, target_ref_id,
219 hide_whitespace_changes, diff_context)
219 hide_whitespace_changes, diff_context)
220
220
221 diff_processor = diffs.DiffProcessor(
221 diff_processor = diffs.DiffProcessor(
222 vcs_diff, format='newdiff', diff_limit=diff_limit,
222 vcs_diff, format='newdiff', diff_limit=diff_limit,
223 file_limit=file_limit, show_full_diff=fulldiff)
223 file_limit=file_limit, show_full_diff=fulldiff)
224
224
225 _parsed = diff_processor.prepare()
225 _parsed = diff_processor.prepare()
226
226
227 diffset = codeblocks.DiffSet(
227 diffset = codeblocks.DiffSet(
228 repo_name=self.db_repo_name,
228 repo_name=self.db_repo_name,
229 source_repo_name=source_repo_name,
229 source_repo_name=source_repo_name,
230 source_node_getter=codeblocks.diffset_node_getter(target_commit),
230 source_node_getter=codeblocks.diffset_node_getter(target_commit),
231 target_node_getter=codeblocks.diffset_node_getter(source_commit),
231 target_node_getter=codeblocks.diffset_node_getter(source_commit),
232 )
232 )
233 diffset = self.path_filter.render_patchset_filtered(
233 diffset = self.path_filter.render_patchset_filtered(
234 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
234 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
235
235
236 return diffset
236 return diffset
237
237
238 def _get_range_diffset(self, source_scm, source_repo,
238 def _get_range_diffset(self, source_scm, source_repo,
239 commit1, commit2, diff_limit, file_limit,
239 commit1, commit2, diff_limit, file_limit,
240 fulldiff, hide_whitespace_changes, diff_context):
240 fulldiff, hide_whitespace_changes, diff_context):
241 vcs_diff = source_scm.get_diff(
241 vcs_diff = source_scm.get_diff(
242 commit1, commit2,
242 commit1, commit2,
243 ignore_whitespace=hide_whitespace_changes,
243 ignore_whitespace=hide_whitespace_changes,
244 context=diff_context)
244 context=diff_context)
245
245
246 diff_processor = diffs.DiffProcessor(
246 diff_processor = diffs.DiffProcessor(
247 vcs_diff, format='newdiff', diff_limit=diff_limit,
247 vcs_diff, format='newdiff', diff_limit=diff_limit,
248 file_limit=file_limit, show_full_diff=fulldiff)
248 file_limit=file_limit, show_full_diff=fulldiff)
249
249
250 _parsed = diff_processor.prepare()
250 _parsed = diff_processor.prepare()
251
251
252 diffset = codeblocks.DiffSet(
252 diffset = codeblocks.DiffSet(
253 repo_name=source_repo.repo_name,
253 repo_name=source_repo.repo_name,
254 source_node_getter=codeblocks.diffset_node_getter(commit1),
254 source_node_getter=codeblocks.diffset_node_getter(commit1),
255 target_node_getter=codeblocks.diffset_node_getter(commit2))
255 target_node_getter=codeblocks.diffset_node_getter(commit2))
256
256
257 diffset = self.path_filter.render_patchset_filtered(
257 diffset = self.path_filter.render_patchset_filtered(
258 diffset, _parsed, commit1.raw_id, commit2.raw_id)
258 diffset, _parsed, commit1.raw_id, commit2.raw_id)
259
259
260 return diffset
260 return diffset
261
261
262 @LoginRequired()
262 @LoginRequired()
263 @HasRepoPermissionAnyDecorator(
263 @HasRepoPermissionAnyDecorator(
264 'repository.read', 'repository.write', 'repository.admin')
264 'repository.read', 'repository.write', 'repository.admin')
265 @view_config(
265 @view_config(
266 route_name='pullrequest_show', request_method='GET',
266 route_name='pullrequest_show', request_method='GET',
267 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
267 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
268 def pull_request_show(self):
268 def pull_request_show(self):
269 _ = self.request.translate
269 _ = self.request.translate
270 c = self.load_default_context()
270 c = self.load_default_context()
271
271
272 pull_request = PullRequest.get_or_404(
272 pull_request = PullRequest.get_or_404(
273 self.request.matchdict['pull_request_id'])
273 self.request.matchdict['pull_request_id'])
274 pull_request_id = pull_request.pull_request_id
274 pull_request_id = pull_request.pull_request_id
275
275
276 c.state_progressing = pull_request.is_state_changing()
276 c.state_progressing = pull_request.is_state_changing()
277
277
278 version = self.request.GET.get('version')
278 version = self.request.GET.get('version')
279 from_version = self.request.GET.get('from_version') or version
279 from_version = self.request.GET.get('from_version') or version
280 merge_checks = self.request.GET.get('merge_checks')
280 merge_checks = self.request.GET.get('merge_checks')
281 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
281 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
282
282
283 # fetch global flags of ignore ws or context lines
283 # fetch global flags of ignore ws or context lines
284 diff_context = diffs.get_diff_context(self.request)
284 diff_context = diffs.get_diff_context(self.request)
285 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
285 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
286
286
287 force_refresh = str2bool(self.request.GET.get('force_refresh'))
287 force_refresh = str2bool(self.request.GET.get('force_refresh'))
288
288
289 (pull_request_latest,
289 (pull_request_latest,
290 pull_request_at_ver,
290 pull_request_at_ver,
291 pull_request_display_obj,
291 pull_request_display_obj,
292 at_version) = PullRequestModel().get_pr_version(
292 at_version) = PullRequestModel().get_pr_version(
293 pull_request_id, version=version)
293 pull_request_id, version=version)
294 pr_closed = pull_request_latest.is_closed()
294 pr_closed = pull_request_latest.is_closed()
295
295
296 if pr_closed and (version or from_version):
296 if pr_closed and (version or from_version):
297 # not allow to browse versions
297 # not allow to browse versions
298 raise HTTPFound(h.route_path(
298 raise HTTPFound(h.route_path(
299 'pullrequest_show', repo_name=self.db_repo_name,
299 'pullrequest_show', repo_name=self.db_repo_name,
300 pull_request_id=pull_request_id))
300 pull_request_id=pull_request_id))
301
301
302 versions = pull_request_display_obj.versions()
302 versions = pull_request_display_obj.versions()
303 # used to store per-commit range diffs
303 # used to store per-commit range diffs
304 c.changes = collections.OrderedDict()
304 c.changes = collections.OrderedDict()
305 c.range_diff_on = self.request.GET.get('range-diff') == "1"
305 c.range_diff_on = self.request.GET.get('range-diff') == "1"
306
306
307 c.at_version = at_version
307 c.at_version = at_version
308 c.at_version_num = (at_version
308 c.at_version_num = (at_version
309 if at_version and at_version != 'latest'
309 if at_version and at_version != 'latest'
310 else None)
310 else None)
311 c.at_version_pos = ChangesetComment.get_index_from_version(
311 c.at_version_pos = ChangesetComment.get_index_from_version(
312 c.at_version_num, versions)
312 c.at_version_num, versions)
313
313
314 (prev_pull_request_latest,
314 (prev_pull_request_latest,
315 prev_pull_request_at_ver,
315 prev_pull_request_at_ver,
316 prev_pull_request_display_obj,
316 prev_pull_request_display_obj,
317 prev_at_version) = PullRequestModel().get_pr_version(
317 prev_at_version) = PullRequestModel().get_pr_version(
318 pull_request_id, version=from_version)
318 pull_request_id, version=from_version)
319
319
320 c.from_version = prev_at_version
320 c.from_version = prev_at_version
321 c.from_version_num = (prev_at_version
321 c.from_version_num = (prev_at_version
322 if prev_at_version and prev_at_version != 'latest'
322 if prev_at_version and prev_at_version != 'latest'
323 else None)
323 else None)
324 c.from_version_pos = ChangesetComment.get_index_from_version(
324 c.from_version_pos = ChangesetComment.get_index_from_version(
325 c.from_version_num, versions)
325 c.from_version_num, versions)
326
326
327 # define if we're in COMPARE mode or VIEW at version mode
327 # define if we're in COMPARE mode or VIEW at version mode
328 compare = at_version != prev_at_version
328 compare = at_version != prev_at_version
329
329
330 # pull_requests repo_name we opened it against
330 # pull_requests repo_name we opened it against
331 # ie. target_repo must match
331 # ie. target_repo must match
332 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
332 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
333 raise HTTPNotFound()
333 raise HTTPNotFound()
334
334
335 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
335 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
336 pull_request_at_ver)
336 pull_request_at_ver)
337
337
338 c.pull_request = pull_request_display_obj
338 c.pull_request = pull_request_display_obj
339 c.renderer = pull_request_at_ver.description_renderer or c.renderer
339 c.renderer = pull_request_at_ver.description_renderer or c.renderer
340 c.pull_request_latest = pull_request_latest
340 c.pull_request_latest = pull_request_latest
341
341
342 if compare or (at_version and not at_version == 'latest'):
342 if compare or (at_version and not at_version == 'latest'):
343 c.allowed_to_change_status = False
343 c.allowed_to_change_status = False
344 c.allowed_to_update = False
344 c.allowed_to_update = False
345 c.allowed_to_merge = False
345 c.allowed_to_merge = False
346 c.allowed_to_delete = False
346 c.allowed_to_delete = False
347 c.allowed_to_comment = False
347 c.allowed_to_comment = False
348 c.allowed_to_close = False
348 c.allowed_to_close = False
349 else:
349 else:
350 can_change_status = PullRequestModel().check_user_change_status(
350 can_change_status = PullRequestModel().check_user_change_status(
351 pull_request_at_ver, self._rhodecode_user)
351 pull_request_at_ver, self._rhodecode_user)
352 c.allowed_to_change_status = can_change_status and not pr_closed
352 c.allowed_to_change_status = can_change_status and not pr_closed
353
353
354 c.allowed_to_update = PullRequestModel().check_user_update(
354 c.allowed_to_update = PullRequestModel().check_user_update(
355 pull_request_latest, self._rhodecode_user) and not pr_closed
355 pull_request_latest, self._rhodecode_user) and not pr_closed
356 c.allowed_to_merge = PullRequestModel().check_user_merge(
356 c.allowed_to_merge = PullRequestModel().check_user_merge(
357 pull_request_latest, self._rhodecode_user) and not pr_closed
357 pull_request_latest, self._rhodecode_user) and not pr_closed
358 c.allowed_to_delete = PullRequestModel().check_user_delete(
358 c.allowed_to_delete = PullRequestModel().check_user_delete(
359 pull_request_latest, self._rhodecode_user) and not pr_closed
359 pull_request_latest, self._rhodecode_user) and not pr_closed
360 c.allowed_to_comment = not pr_closed
360 c.allowed_to_comment = not pr_closed
361 c.allowed_to_close = c.allowed_to_merge and not pr_closed
361 c.allowed_to_close = c.allowed_to_merge and not pr_closed
362
362
363 c.forbid_adding_reviewers = False
363 c.forbid_adding_reviewers = False
364 c.forbid_author_to_review = False
364 c.forbid_author_to_review = False
365 c.forbid_commit_author_to_review = False
365 c.forbid_commit_author_to_review = False
366
366
367 if pull_request_latest.reviewer_data and \
367 if pull_request_latest.reviewer_data and \
368 'rules' in pull_request_latest.reviewer_data:
368 'rules' in pull_request_latest.reviewer_data:
369 rules = pull_request_latest.reviewer_data['rules'] or {}
369 rules = pull_request_latest.reviewer_data['rules'] or {}
370 try:
370 try:
371 c.forbid_adding_reviewers = rules.get(
371 c.forbid_adding_reviewers = rules.get(
372 'forbid_adding_reviewers')
372 'forbid_adding_reviewers')
373 c.forbid_author_to_review = rules.get(
373 c.forbid_author_to_review = rules.get(
374 'forbid_author_to_review')
374 'forbid_author_to_review')
375 c.forbid_commit_author_to_review = rules.get(
375 c.forbid_commit_author_to_review = rules.get(
376 'forbid_commit_author_to_review')
376 'forbid_commit_author_to_review')
377 except Exception:
377 except Exception:
378 pass
378 pass
379
379
380 # check merge capabilities
380 # check merge capabilities
381 _merge_check = MergeCheck.validate(
381 _merge_check = MergeCheck.validate(
382 pull_request_latest, auth_user=self._rhodecode_user,
382 pull_request_latest, auth_user=self._rhodecode_user,
383 translator=self.request.translate,
383 translator=self.request.translate,
384 force_shadow_repo_refresh=force_refresh)
384 force_shadow_repo_refresh=force_refresh)
385 c.pr_merge_errors = _merge_check.error_details
385 c.pr_merge_errors = _merge_check.error_details
386 c.pr_merge_possible = not _merge_check.failed
386 c.pr_merge_possible = not _merge_check.failed
387 c.pr_merge_message = _merge_check.merge_msg
387 c.pr_merge_message = _merge_check.merge_msg
388
388
389 c.pr_merge_info = MergeCheck.get_merge_conditions(
389 c.pr_merge_info = MergeCheck.get_merge_conditions(
390 pull_request_latest, translator=self.request.translate)
390 pull_request_latest, translator=self.request.translate)
391
391
392 c.pull_request_review_status = _merge_check.review_status
392 c.pull_request_review_status = _merge_check.review_status
393 if merge_checks:
393 if merge_checks:
394 self.request.override_renderer = \
394 self.request.override_renderer = \
395 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
395 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
396 return self._get_template_context(c)
396 return self._get_template_context(c)
397
397
398 comments_model = CommentsModel()
398 comments_model = CommentsModel()
399
399
400 # reviewers and statuses
400 # reviewers and statuses
401 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
401 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
402 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
402 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
403
403
404 # GENERAL COMMENTS with versions #
404 # GENERAL COMMENTS with versions #
405 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
405 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
406 q = q.order_by(ChangesetComment.comment_id.asc())
406 q = q.order_by(ChangesetComment.comment_id.asc())
407 general_comments = q
407 general_comments = q
408
408
409 # pick comments we want to render at current version
409 # pick comments we want to render at current version
410 c.comment_versions = comments_model.aggregate_comments(
410 c.comment_versions = comments_model.aggregate_comments(
411 general_comments, versions, c.at_version_num)
411 general_comments, versions, c.at_version_num)
412 c.comments = c.comment_versions[c.at_version_num]['until']
412 c.comments = c.comment_versions[c.at_version_num]['until']
413
413
414 # INLINE COMMENTS with versions #
414 # INLINE COMMENTS with versions #
415 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
415 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
416 q = q.order_by(ChangesetComment.comment_id.asc())
416 q = q.order_by(ChangesetComment.comment_id.asc())
417 inline_comments = q
417 inline_comments = q
418
418
419 c.inline_versions = comments_model.aggregate_comments(
419 c.inline_versions = comments_model.aggregate_comments(
420 inline_comments, versions, c.at_version_num, inline=True)
420 inline_comments, versions, c.at_version_num, inline=True)
421
421
422 # TODOs
422 # TODOs
423 c.unresolved_comments = CommentsModel() \
423 c.unresolved_comments = CommentsModel() \
424 .get_pull_request_unresolved_todos(pull_request)
424 .get_pull_request_unresolved_todos(pull_request)
425 c.resolved_comments = CommentsModel() \
425 c.resolved_comments = CommentsModel() \
426 .get_pull_request_resolved_todos(pull_request)
426 .get_pull_request_resolved_todos(pull_request)
427
427
428 # inject latest version
428 # inject latest version
429 latest_ver = PullRequest.get_pr_display_object(
429 latest_ver = PullRequest.get_pr_display_object(
430 pull_request_latest, pull_request_latest)
430 pull_request_latest, pull_request_latest)
431
431
432 c.versions = versions + [latest_ver]
432 c.versions = versions + [latest_ver]
433
433
434 # if we use version, then do not show later comments
434 # if we use version, then do not show later comments
435 # than current version
435 # than current version
436 display_inline_comments = collections.defaultdict(
436 display_inline_comments = collections.defaultdict(
437 lambda: collections.defaultdict(list))
437 lambda: collections.defaultdict(list))
438 for co in inline_comments:
438 for co in inline_comments:
439 if c.at_version_num:
439 if c.at_version_num:
440 # pick comments that are at least UPTO given version, so we
440 # pick comments that are at least UPTO given version, so we
441 # don't render comments for higher version
441 # don't render comments for higher version
442 should_render = co.pull_request_version_id and \
442 should_render = co.pull_request_version_id and \
443 co.pull_request_version_id <= c.at_version_num
443 co.pull_request_version_id <= c.at_version_num
444 else:
444 else:
445 # showing all, for 'latest'
445 # showing all, for 'latest'
446 should_render = True
446 should_render = True
447
447
448 if should_render:
448 if should_render:
449 display_inline_comments[co.f_path][co.line_no].append(co)
449 display_inline_comments[co.f_path][co.line_no].append(co)
450
450
451 # load diff data into template context, if we use compare mode then
451 # load diff data into template context, if we use compare mode then
452 # diff is calculated based on changes between versions of PR
452 # diff is calculated based on changes between versions of PR
453
453
454 source_repo = pull_request_at_ver.source_repo
454 source_repo = pull_request_at_ver.source_repo
455 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
455 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
456
456
457 target_repo = pull_request_at_ver.target_repo
457 target_repo = pull_request_at_ver.target_repo
458 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
458 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
459
459
460 if compare:
460 if compare:
461 # in compare switch the diff base to latest commit from prev version
461 # in compare switch the diff base to latest commit from prev version
462 target_ref_id = prev_pull_request_display_obj.revisions[0]
462 target_ref_id = prev_pull_request_display_obj.revisions[0]
463
463
464 # despite opening commits for bookmarks/branches/tags, we always
464 # despite opening commits for bookmarks/branches/tags, we always
465 # convert this to rev to prevent changes after bookmark or branch change
465 # convert this to rev to prevent changes after bookmark or branch change
466 c.source_ref_type = 'rev'
466 c.source_ref_type = 'rev'
467 c.source_ref = source_ref_id
467 c.source_ref = source_ref_id
468
468
469 c.target_ref_type = 'rev'
469 c.target_ref_type = 'rev'
470 c.target_ref = target_ref_id
470 c.target_ref = target_ref_id
471
471
472 c.source_repo = source_repo
472 c.source_repo = source_repo
473 c.target_repo = target_repo
473 c.target_repo = target_repo
474
474
475 c.commit_ranges = []
475 c.commit_ranges = []
476 source_commit = EmptyCommit()
476 source_commit = EmptyCommit()
477 target_commit = EmptyCommit()
477 target_commit = EmptyCommit()
478 c.missing_requirements = False
478 c.missing_requirements = False
479
479
480 source_scm = source_repo.scm_instance()
480 source_scm = source_repo.scm_instance()
481 target_scm = target_repo.scm_instance()
481 target_scm = target_repo.scm_instance()
482
482
483 shadow_scm = None
483 shadow_scm = None
484 try:
484 try:
485 shadow_scm = pull_request_latest.get_shadow_repo()
485 shadow_scm = pull_request_latest.get_shadow_repo()
486 except Exception:
486 except Exception:
487 log.debug('Failed to get shadow repo', exc_info=True)
487 log.debug('Failed to get shadow repo', exc_info=True)
488 # try first the existing source_repo, and then shadow
488 # try first the existing source_repo, and then shadow
489 # repo if we can obtain one
489 # repo if we can obtain one
490 commits_source_repo = source_scm or shadow_scm
490 commits_source_repo = source_scm
491 if shadow_scm:
492 commits_source_repo = shadow_scm
491
493
492 c.commits_source_repo = commits_source_repo
494 c.commits_source_repo = commits_source_repo
493 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
494
496
495 # empty version means latest, so we keep this to prevent
497 # empty version means latest, so we keep this to prevent
496 # double caching
498 # double caching
497 version_normalized = version or 'latest'
499 version_normalized = version or 'latest'
498 from_version_normalized = from_version or 'latest'
500 from_version_normalized = from_version or 'latest'
499
501
500 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
502 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
501 cache_file_path = diff_cache_exist(
503 cache_file_path = diff_cache_exist(
502 cache_path, 'pull_request', pull_request_id, version_normalized,
504 cache_path, 'pull_request', pull_request_id, version_normalized,
503 from_version_normalized, source_ref_id, target_ref_id,
505 from_version_normalized, source_ref_id, target_ref_id,
504 hide_whitespace_changes, diff_context, c.fulldiff)
506 hide_whitespace_changes, diff_context, c.fulldiff)
505
507
506 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
508 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
507 force_recache = self.get_recache_flag()
509 force_recache = self.get_recache_flag()
508
510
509 cached_diff = None
511 cached_diff = None
510 if caching_enabled:
512 if caching_enabled:
511 cached_diff = load_cached_diff(cache_file_path)
513 cached_diff = load_cached_diff(cache_file_path)
512
514
513 has_proper_commit_cache = (
515 has_proper_commit_cache = (
514 cached_diff and cached_diff.get('commits')
516 cached_diff and cached_diff.get('commits')
515 and len(cached_diff.get('commits', [])) == 5
517 and len(cached_diff.get('commits', [])) == 5
516 and cached_diff.get('commits')[0]
518 and cached_diff.get('commits')[0]
517 and cached_diff.get('commits')[3])
519 and cached_diff.get('commits')[3])
518
520
519 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
521 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
520 diff_commit_cache = \
522 diff_commit_cache = \
521 (ancestor_commit, commit_cache, missing_requirements,
523 (ancestor_commit, commit_cache, missing_requirements,
522 source_commit, target_commit) = cached_diff['commits']
524 source_commit, target_commit) = cached_diff['commits']
523 else:
525 else:
524 diff_commit_cache = \
526 diff_commit_cache = \
525 (ancestor_commit, commit_cache, missing_requirements,
527 (ancestor_commit, commit_cache, missing_requirements,
526 source_commit, target_commit) = self.get_commits(
528 source_commit, target_commit) = self.get_commits(
527 commits_source_repo,
529 commits_source_repo,
528 pull_request_at_ver,
530 pull_request_at_ver,
529 source_commit,
531 source_commit,
530 source_ref_id,
532 source_ref_id,
531 source_scm,
533 source_scm,
532 target_commit,
534 target_commit,
533 target_ref_id,
535 target_ref_id,
534 target_scm)
536 target_scm)
535
537
536 # register our commit range
538 # register our commit range
537 for comm in commit_cache.values():
539 for comm in commit_cache.values():
538 c.commit_ranges.append(comm)
540 c.commit_ranges.append(comm)
539
541
540 c.missing_requirements = missing_requirements
542 c.missing_requirements = missing_requirements
541 c.ancestor_commit = ancestor_commit
543 c.ancestor_commit = ancestor_commit
542 c.statuses = source_repo.statuses(
544 c.statuses = source_repo.statuses(
543 [x.raw_id for x in c.commit_ranges])
545 [x.raw_id for x in c.commit_ranges])
544
546
545 # auto collapse if we have more than limit
547 # auto collapse if we have more than limit
546 collapse_limit = diffs.DiffProcessor._collapse_commits_over
548 collapse_limit = diffs.DiffProcessor._collapse_commits_over
547 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
549 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
548 c.compare_mode = compare
550 c.compare_mode = compare
549
551
550 # diff_limit is the old behavior, will cut off the whole diff
552 # diff_limit is the old behavior, will cut off the whole diff
551 # if the limit is applied otherwise will just hide the
553 # if the limit is applied otherwise will just hide the
552 # big files from the front-end
554 # big files from the front-end
553 diff_limit = c.visual.cut_off_limit_diff
555 diff_limit = c.visual.cut_off_limit_diff
554 file_limit = c.visual.cut_off_limit_file
556 file_limit = c.visual.cut_off_limit_file
555
557
556 c.missing_commits = False
558 c.missing_commits = False
557 if (c.missing_requirements
559 if (c.missing_requirements
558 or isinstance(source_commit, EmptyCommit)
560 or isinstance(source_commit, EmptyCommit)
559 or source_commit == target_commit):
561 or source_commit == target_commit):
560
562
561 c.missing_commits = True
563 c.missing_commits = True
562 else:
564 else:
563 c.inline_comments = display_inline_comments
565 c.inline_comments = display_inline_comments
564
566
565 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
567 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
566 if not force_recache and has_proper_diff_cache:
568 if not force_recache and has_proper_diff_cache:
567 c.diffset = cached_diff['diff']
569 c.diffset = cached_diff['diff']
568 (ancestor_commit, commit_cache, missing_requirements,
570 (ancestor_commit, commit_cache, missing_requirements,
569 source_commit, target_commit) = cached_diff['commits']
571 source_commit, target_commit) = cached_diff['commits']
570 else:
572 else:
571 c.diffset = self._get_diffset(
573 c.diffset = self._get_diffset(
572 c.source_repo.repo_name, commits_source_repo,
574 c.source_repo.repo_name, commits_source_repo,
573 source_ref_id, target_ref_id,
575 source_ref_id, target_ref_id,
574 target_commit, source_commit,
576 target_commit, source_commit,
575 diff_limit, file_limit, c.fulldiff,
577 diff_limit, file_limit, c.fulldiff,
576 hide_whitespace_changes, diff_context)
578 hide_whitespace_changes, diff_context)
577
579
578 # save cached diff
580 # save cached diff
579 if caching_enabled:
581 if caching_enabled:
580 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
582 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
581
583
582 c.limited_diff = c.diffset.limited_diff
584 c.limited_diff = c.diffset.limited_diff
583
585
584 # calculate removed files that are bound to comments
586 # calculate removed files that are bound to comments
585 comment_deleted_files = [
587 comment_deleted_files = [
586 fname for fname in display_inline_comments
588 fname for fname in display_inline_comments
587 if fname not in c.diffset.file_stats]
589 if fname not in c.diffset.file_stats]
588
590
589 c.deleted_files_comments = collections.defaultdict(dict)
591 c.deleted_files_comments = collections.defaultdict(dict)
590 for fname, per_line_comments in display_inline_comments.items():
592 for fname, per_line_comments in display_inline_comments.items():
591 if fname in comment_deleted_files:
593 if fname in comment_deleted_files:
592 c.deleted_files_comments[fname]['stats'] = 0
594 c.deleted_files_comments[fname]['stats'] = 0
593 c.deleted_files_comments[fname]['comments'] = list()
595 c.deleted_files_comments[fname]['comments'] = list()
594 for lno, comments in per_line_comments.items():
596 for lno, comments in per_line_comments.items():
595 c.deleted_files_comments[fname]['comments'].extend(comments)
597 c.deleted_files_comments[fname]['comments'].extend(comments)
596
598
597 # maybe calculate the range diff
599 # maybe calculate the range diff
598 if c.range_diff_on:
600 if c.range_diff_on:
599 # TODO(marcink): set whitespace/context
601 # TODO(marcink): set whitespace/context
600 context_lcl = 3
602 context_lcl = 3
601 ign_whitespace_lcl = False
603 ign_whitespace_lcl = False
602
604
603 for commit in c.commit_ranges:
605 for commit in c.commit_ranges:
604 commit2 = commit
606 commit2 = commit
605 commit1 = commit.first_parent
607 commit1 = commit.first_parent
606
608
607 range_diff_cache_file_path = diff_cache_exist(
609 range_diff_cache_file_path = diff_cache_exist(
608 cache_path, 'diff', commit.raw_id,
610 cache_path, 'diff', commit.raw_id,
609 ign_whitespace_lcl, context_lcl, c.fulldiff)
611 ign_whitespace_lcl, context_lcl, c.fulldiff)
610
612
611 cached_diff = None
613 cached_diff = None
612 if caching_enabled:
614 if caching_enabled:
613 cached_diff = load_cached_diff(range_diff_cache_file_path)
615 cached_diff = load_cached_diff(range_diff_cache_file_path)
614
616
615 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
617 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
616 if not force_recache and has_proper_diff_cache:
618 if not force_recache and has_proper_diff_cache:
617 diffset = cached_diff['diff']
619 diffset = cached_diff['diff']
618 else:
620 else:
619 diffset = self._get_range_diffset(
621 diffset = self._get_range_diffset(
620 source_scm, source_repo,
622 commits_source_repo, source_repo,
621 commit1, commit2, diff_limit, file_limit,
623 commit1, commit2, diff_limit, file_limit,
622 c.fulldiff, ign_whitespace_lcl, context_lcl
624 c.fulldiff, ign_whitespace_lcl, context_lcl
623 )
625 )
624
626
625 # save cached diff
627 # save cached diff
626 if caching_enabled:
628 if caching_enabled:
627 cache_diff(range_diff_cache_file_path, diffset, None)
629 cache_diff(range_diff_cache_file_path, diffset, None)
628
630
629 c.changes[commit.raw_id] = diffset
631 c.changes[commit.raw_id] = diffset
630
632
631 # this is a hack to properly display links, when creating PR, the
633 # this is a hack to properly display links, when creating PR, the
632 # compare view and others uses different notation, and
634 # compare view and others uses different notation, and
633 # compare_commits.mako renders links based on the target_repo.
635 # compare_commits.mako renders links based on the target_repo.
634 # We need to swap that here to generate it properly on the html side
636 # We need to swap that here to generate it properly on the html side
635 c.target_repo = c.source_repo
637 c.target_repo = c.source_repo
636
638
637 c.commit_statuses = ChangesetStatus.STATUSES
639 c.commit_statuses = ChangesetStatus.STATUSES
638
640
639 c.show_version_changes = not pr_closed
641 c.show_version_changes = not pr_closed
640 if c.show_version_changes:
642 if c.show_version_changes:
641 cur_obj = pull_request_at_ver
643 cur_obj = pull_request_at_ver
642 prev_obj = prev_pull_request_at_ver
644 prev_obj = prev_pull_request_at_ver
643
645
644 old_commit_ids = prev_obj.revisions
646 old_commit_ids = prev_obj.revisions
645 new_commit_ids = cur_obj.revisions
647 new_commit_ids = cur_obj.revisions
646 commit_changes = PullRequestModel()._calculate_commit_id_changes(
648 commit_changes = PullRequestModel()._calculate_commit_id_changes(
647 old_commit_ids, new_commit_ids)
649 old_commit_ids, new_commit_ids)
648 c.commit_changes_summary = commit_changes
650 c.commit_changes_summary = commit_changes
649
651
650 # calculate the diff for commits between versions
652 # calculate the diff for commits between versions
651 c.commit_changes = []
653 c.commit_changes = []
652 mark = lambda cs, fw: list(
654 mark = lambda cs, fw: list(
653 h.itertools.izip_longest([], cs, fillvalue=fw))
655 h.itertools.izip_longest([], cs, fillvalue=fw))
654 for c_type, raw_id in mark(commit_changes.added, 'a') \
656 for c_type, raw_id in mark(commit_changes.added, 'a') \
655 + mark(commit_changes.removed, 'r') \
657 + mark(commit_changes.removed, 'r') \
656 + mark(commit_changes.common, 'c'):
658 + mark(commit_changes.common, 'c'):
657
659
658 if raw_id in commit_cache:
660 if raw_id in commit_cache:
659 commit = commit_cache[raw_id]
661 commit = commit_cache[raw_id]
660 else:
662 else:
661 try:
663 try:
662 commit = commits_source_repo.get_commit(raw_id)
664 commit = commits_source_repo.get_commit(raw_id)
663 except CommitDoesNotExistError:
665 except CommitDoesNotExistError:
664 # in case we fail extracting still use "dummy" commit
666 # in case we fail extracting still use "dummy" commit
665 # for display in commit diff
667 # for display in commit diff
666 commit = h.AttributeDict(
668 commit = h.AttributeDict(
667 {'raw_id': raw_id,
669 {'raw_id': raw_id,
668 'message': 'EMPTY or MISSING COMMIT'})
670 'message': 'EMPTY or MISSING COMMIT'})
669 c.commit_changes.append([c_type, commit])
671 c.commit_changes.append([c_type, commit])
670
672
671 # current user review statuses for each version
673 # current user review statuses for each version
672 c.review_versions = {}
674 c.review_versions = {}
673 if self._rhodecode_user.user_id in allowed_reviewers:
675 if self._rhodecode_user.user_id in allowed_reviewers:
674 for co in general_comments:
676 for co in general_comments:
675 if co.author.user_id == self._rhodecode_user.user_id:
677 if co.author.user_id == self._rhodecode_user.user_id:
676 status = co.status_change
678 status = co.status_change
677 if status:
679 if status:
678 _ver_pr = status[0].comment.pull_request_version_id
680 _ver_pr = status[0].comment.pull_request_version_id
679 c.review_versions[_ver_pr] = status[0]
681 c.review_versions[_ver_pr] = status[0]
680
682
681 return self._get_template_context(c)
683 return self._get_template_context(c)
682
684
683 def get_commits(
685 def get_commits(
684 self, commits_source_repo, pull_request_at_ver, source_commit,
686 self, commits_source_repo, pull_request_at_ver, source_commit,
685 source_ref_id, source_scm, target_commit, target_ref_id, target_scm):
687 source_ref_id, source_scm, target_commit, target_ref_id, target_scm):
686 commit_cache = collections.OrderedDict()
688 commit_cache = collections.OrderedDict()
687 missing_requirements = False
689 missing_requirements = False
688 try:
690 try:
689 pre_load = ["author", "date", "message", "branch", "parents"]
691 pre_load = ["author", "date", "message", "branch", "parents"]
690 show_revs = pull_request_at_ver.revisions
692 show_revs = pull_request_at_ver.revisions
691 for rev in show_revs:
693 for rev in show_revs:
692 comm = commits_source_repo.get_commit(
694 comm = commits_source_repo.get_commit(
693 commit_id=rev, pre_load=pre_load)
695 commit_id=rev, pre_load=pre_load)
694 commit_cache[comm.raw_id] = comm
696 commit_cache[comm.raw_id] = comm
695
697
696 # Order here matters, we first need to get target, and then
698 # Order here matters, we first need to get target, and then
697 # the source
699 # the source
698 target_commit = commits_source_repo.get_commit(
700 target_commit = commits_source_repo.get_commit(
699 commit_id=safe_str(target_ref_id))
701 commit_id=safe_str(target_ref_id))
700
702
701 source_commit = commits_source_repo.get_commit(
703 source_commit = commits_source_repo.get_commit(
702 commit_id=safe_str(source_ref_id))
704 commit_id=safe_str(source_ref_id))
703 except CommitDoesNotExistError:
705 except CommitDoesNotExistError:
704 log.warning(
706 log.warning(
705 'Failed to get commit from `{}` repo'.format(
707 'Failed to get commit from `{}` repo'.format(
706 commits_source_repo), exc_info=True)
708 commits_source_repo), exc_info=True)
707 except RepositoryRequirementError:
709 except RepositoryRequirementError:
708 log.warning(
710 log.warning(
709 'Failed to get all required data from repo', exc_info=True)
711 'Failed to get all required data from repo', exc_info=True)
710 missing_requirements = True
712 missing_requirements = True
711 ancestor_commit = None
713 ancestor_commit = None
712 try:
714 try:
713 ancestor_id = source_scm.get_common_ancestor(
715 ancestor_id = source_scm.get_common_ancestor(
714 source_commit.raw_id, target_commit.raw_id, target_scm)
716 source_commit.raw_id, target_commit.raw_id, target_scm)
715 ancestor_commit = source_scm.get_commit(ancestor_id)
717 ancestor_commit = source_scm.get_commit(ancestor_id)
716 except Exception:
718 except Exception:
717 ancestor_commit = None
719 ancestor_commit = None
718 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
720 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
719
721
720 def assure_not_empty_repo(self):
722 def assure_not_empty_repo(self):
721 _ = self.request.translate
723 _ = self.request.translate
722
724
723 try:
725 try:
724 self.db_repo.scm_instance().get_commit()
726 self.db_repo.scm_instance().get_commit()
725 except EmptyRepositoryError:
727 except EmptyRepositoryError:
726 h.flash(h.literal(_('There are no commits yet')),
728 h.flash(h.literal(_('There are no commits yet')),
727 category='warning')
729 category='warning')
728 raise HTTPFound(
730 raise HTTPFound(
729 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
731 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
730
732
731 @LoginRequired()
733 @LoginRequired()
732 @NotAnonymous()
734 @NotAnonymous()
733 @HasRepoPermissionAnyDecorator(
735 @HasRepoPermissionAnyDecorator(
734 'repository.read', 'repository.write', 'repository.admin')
736 'repository.read', 'repository.write', 'repository.admin')
735 @view_config(
737 @view_config(
736 route_name='pullrequest_new', request_method='GET',
738 route_name='pullrequest_new', request_method='GET',
737 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
739 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
738 def pull_request_new(self):
740 def pull_request_new(self):
739 _ = self.request.translate
741 _ = self.request.translate
740 c = self.load_default_context()
742 c = self.load_default_context()
741
743
742 self.assure_not_empty_repo()
744 self.assure_not_empty_repo()
743 source_repo = self.db_repo
745 source_repo = self.db_repo
744
746
745 commit_id = self.request.GET.get('commit')
747 commit_id = self.request.GET.get('commit')
746 branch_ref = self.request.GET.get('branch')
748 branch_ref = self.request.GET.get('branch')
747 bookmark_ref = self.request.GET.get('bookmark')
749 bookmark_ref = self.request.GET.get('bookmark')
748
750
749 try:
751 try:
750 source_repo_data = PullRequestModel().generate_repo_data(
752 source_repo_data = PullRequestModel().generate_repo_data(
751 source_repo, commit_id=commit_id,
753 source_repo, commit_id=commit_id,
752 branch=branch_ref, bookmark=bookmark_ref,
754 branch=branch_ref, bookmark=bookmark_ref,
753 translator=self.request.translate)
755 translator=self.request.translate)
754 except CommitDoesNotExistError as e:
756 except CommitDoesNotExistError as e:
755 log.exception(e)
757 log.exception(e)
756 h.flash(_('Commit does not exist'), 'error')
758 h.flash(_('Commit does not exist'), 'error')
757 raise HTTPFound(
759 raise HTTPFound(
758 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
760 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
759
761
760 default_target_repo = source_repo
762 default_target_repo = source_repo
761
763
762 if source_repo.parent and c.has_origin_repo_read_perm:
764 if source_repo.parent and c.has_origin_repo_read_perm:
763 parent_vcs_obj = source_repo.parent.scm_instance()
765 parent_vcs_obj = source_repo.parent.scm_instance()
764 if parent_vcs_obj and not parent_vcs_obj.is_empty():
766 if parent_vcs_obj and not parent_vcs_obj.is_empty():
765 # change default if we have a parent repo
767 # change default if we have a parent repo
766 default_target_repo = source_repo.parent
768 default_target_repo = source_repo.parent
767
769
768 target_repo_data = PullRequestModel().generate_repo_data(
770 target_repo_data = PullRequestModel().generate_repo_data(
769 default_target_repo, translator=self.request.translate)
771 default_target_repo, translator=self.request.translate)
770
772
771 selected_source_ref = source_repo_data['refs']['selected_ref']
773 selected_source_ref = source_repo_data['refs']['selected_ref']
772 title_source_ref = ''
774 title_source_ref = ''
773 if selected_source_ref:
775 if selected_source_ref:
774 title_source_ref = selected_source_ref.split(':', 2)[1]
776 title_source_ref = selected_source_ref.split(':', 2)[1]
775 c.default_title = PullRequestModel().generate_pullrequest_title(
777 c.default_title = PullRequestModel().generate_pullrequest_title(
776 source=source_repo.repo_name,
778 source=source_repo.repo_name,
777 source_ref=title_source_ref,
779 source_ref=title_source_ref,
778 target=default_target_repo.repo_name
780 target=default_target_repo.repo_name
779 )
781 )
780
782
781 c.default_repo_data = {
783 c.default_repo_data = {
782 'source_repo_name': source_repo.repo_name,
784 'source_repo_name': source_repo.repo_name,
783 'source_refs_json': json.dumps(source_repo_data),
785 'source_refs_json': json.dumps(source_repo_data),
784 'target_repo_name': default_target_repo.repo_name,
786 'target_repo_name': default_target_repo.repo_name,
785 'target_refs_json': json.dumps(target_repo_data),
787 'target_refs_json': json.dumps(target_repo_data),
786 }
788 }
787 c.default_source_ref = selected_source_ref
789 c.default_source_ref = selected_source_ref
788
790
789 return self._get_template_context(c)
791 return self._get_template_context(c)
790
792
791 @LoginRequired()
793 @LoginRequired()
792 @NotAnonymous()
794 @NotAnonymous()
793 @HasRepoPermissionAnyDecorator(
795 @HasRepoPermissionAnyDecorator(
794 'repository.read', 'repository.write', 'repository.admin')
796 'repository.read', 'repository.write', 'repository.admin')
795 @view_config(
797 @view_config(
796 route_name='pullrequest_repo_refs', request_method='GET',
798 route_name='pullrequest_repo_refs', request_method='GET',
797 renderer='json_ext', xhr=True)
799 renderer='json_ext', xhr=True)
798 def pull_request_repo_refs(self):
800 def pull_request_repo_refs(self):
799 self.load_default_context()
801 self.load_default_context()
800 target_repo_name = self.request.matchdict['target_repo_name']
802 target_repo_name = self.request.matchdict['target_repo_name']
801 repo = Repository.get_by_repo_name(target_repo_name)
803 repo = Repository.get_by_repo_name(target_repo_name)
802 if not repo:
804 if not repo:
803 raise HTTPNotFound()
805 raise HTTPNotFound()
804
806
805 target_perm = HasRepoPermissionAny(
807 target_perm = HasRepoPermissionAny(
806 'repository.read', 'repository.write', 'repository.admin')(
808 'repository.read', 'repository.write', 'repository.admin')(
807 target_repo_name)
809 target_repo_name)
808 if not target_perm:
810 if not target_perm:
809 raise HTTPNotFound()
811 raise HTTPNotFound()
810
812
811 return PullRequestModel().generate_repo_data(
813 return PullRequestModel().generate_repo_data(
812 repo, translator=self.request.translate)
814 repo, translator=self.request.translate)
813
815
814 @LoginRequired()
816 @LoginRequired()
815 @NotAnonymous()
817 @NotAnonymous()
816 @HasRepoPermissionAnyDecorator(
818 @HasRepoPermissionAnyDecorator(
817 'repository.read', 'repository.write', 'repository.admin')
819 'repository.read', 'repository.write', 'repository.admin')
818 @view_config(
820 @view_config(
819 route_name='pullrequest_repo_targets', request_method='GET',
821 route_name='pullrequest_repo_targets', request_method='GET',
820 renderer='json_ext', xhr=True)
822 renderer='json_ext', xhr=True)
821 def pullrequest_repo_targets(self):
823 def pullrequest_repo_targets(self):
822 _ = self.request.translate
824 _ = self.request.translate
823 filter_query = self.request.GET.get('query')
825 filter_query = self.request.GET.get('query')
824
826
825 # get the parents
827 # get the parents
826 parent_target_repos = []
828 parent_target_repos = []
827 if self.db_repo.parent:
829 if self.db_repo.parent:
828 parents_query = Repository.query() \
830 parents_query = Repository.query() \
829 .order_by(func.length(Repository.repo_name)) \
831 .order_by(func.length(Repository.repo_name)) \
830 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
832 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
831
833
832 if filter_query:
834 if filter_query:
833 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
835 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
834 parents_query = parents_query.filter(
836 parents_query = parents_query.filter(
835 Repository.repo_name.ilike(ilike_expression))
837 Repository.repo_name.ilike(ilike_expression))
836 parents = parents_query.limit(20).all()
838 parents = parents_query.limit(20).all()
837
839
838 for parent in parents:
840 for parent in parents:
839 parent_vcs_obj = parent.scm_instance()
841 parent_vcs_obj = parent.scm_instance()
840 if parent_vcs_obj and not parent_vcs_obj.is_empty():
842 if parent_vcs_obj and not parent_vcs_obj.is_empty():
841 parent_target_repos.append(parent)
843 parent_target_repos.append(parent)
842
844
843 # get other forks, and repo itself
845 # get other forks, and repo itself
844 query = Repository.query() \
846 query = Repository.query() \
845 .order_by(func.length(Repository.repo_name)) \
847 .order_by(func.length(Repository.repo_name)) \
846 .filter(
848 .filter(
847 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
849 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
848 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
850 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
849 ) \
851 ) \
850 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
852 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
851
853
852 if filter_query:
854 if filter_query:
853 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
855 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
854 query = query.filter(Repository.repo_name.ilike(ilike_expression))
856 query = query.filter(Repository.repo_name.ilike(ilike_expression))
855
857
856 limit = max(20 - len(parent_target_repos), 5) # not less then 5
858 limit = max(20 - len(parent_target_repos), 5) # not less then 5
857 target_repos = query.limit(limit).all()
859 target_repos = query.limit(limit).all()
858
860
859 all_target_repos = target_repos + parent_target_repos
861 all_target_repos = target_repos + parent_target_repos
860
862
861 repos = []
863 repos = []
862 # This checks permissions to the repositories
864 # This checks permissions to the repositories
863 for obj in ScmModel().get_repos(all_target_repos):
865 for obj in ScmModel().get_repos(all_target_repos):
864 repos.append({
866 repos.append({
865 'id': obj['name'],
867 'id': obj['name'],
866 'text': obj['name'],
868 'text': obj['name'],
867 'type': 'repo',
869 'type': 'repo',
868 'repo_id': obj['dbrepo']['repo_id'],
870 'repo_id': obj['dbrepo']['repo_id'],
869 'repo_type': obj['dbrepo']['repo_type'],
871 'repo_type': obj['dbrepo']['repo_type'],
870 'private': obj['dbrepo']['private'],
872 'private': obj['dbrepo']['private'],
871
873
872 })
874 })
873
875
874 data = {
876 data = {
875 'more': False,
877 'more': False,
876 'results': [{
878 'results': [{
877 'text': _('Repositories'),
879 'text': _('Repositories'),
878 'children': repos
880 'children': repos
879 }] if repos else []
881 }] if repos else []
880 }
882 }
881 return data
883 return data
882
884
883 @LoginRequired()
885 @LoginRequired()
884 @NotAnonymous()
886 @NotAnonymous()
885 @HasRepoPermissionAnyDecorator(
887 @HasRepoPermissionAnyDecorator(
886 'repository.read', 'repository.write', 'repository.admin')
888 'repository.read', 'repository.write', 'repository.admin')
887 @CSRFRequired()
889 @CSRFRequired()
888 @view_config(
890 @view_config(
889 route_name='pullrequest_create', request_method='POST',
891 route_name='pullrequest_create', request_method='POST',
890 renderer=None)
892 renderer=None)
891 def pull_request_create(self):
893 def pull_request_create(self):
892 _ = self.request.translate
894 _ = self.request.translate
893 self.assure_not_empty_repo()
895 self.assure_not_empty_repo()
894 self.load_default_context()
896 self.load_default_context()
895
897
896 controls = peppercorn.parse(self.request.POST.items())
898 controls = peppercorn.parse(self.request.POST.items())
897
899
898 try:
900 try:
899 form = PullRequestForm(
901 form = PullRequestForm(
900 self.request.translate, self.db_repo.repo_id)()
902 self.request.translate, self.db_repo.repo_id)()
901 _form = form.to_python(controls)
903 _form = form.to_python(controls)
902 except formencode.Invalid as errors:
904 except formencode.Invalid as errors:
903 if errors.error_dict.get('revisions'):
905 if errors.error_dict.get('revisions'):
904 msg = 'Revisions: %s' % errors.error_dict['revisions']
906 msg = 'Revisions: %s' % errors.error_dict['revisions']
905 elif errors.error_dict.get('pullrequest_title'):
907 elif errors.error_dict.get('pullrequest_title'):
906 msg = errors.error_dict.get('pullrequest_title')
908 msg = errors.error_dict.get('pullrequest_title')
907 else:
909 else:
908 msg = _('Error creating pull request: {}').format(errors)
910 msg = _('Error creating pull request: {}').format(errors)
909 log.exception(msg)
911 log.exception(msg)
910 h.flash(msg, 'error')
912 h.flash(msg, 'error')
911
913
912 # would rather just go back to form ...
914 # would rather just go back to form ...
913 raise HTTPFound(
915 raise HTTPFound(
914 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
916 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
915
917
916 source_repo = _form['source_repo']
918 source_repo = _form['source_repo']
917 source_ref = _form['source_ref']
919 source_ref = _form['source_ref']
918 target_repo = _form['target_repo']
920 target_repo = _form['target_repo']
919 target_ref = _form['target_ref']
921 target_ref = _form['target_ref']
920 commit_ids = _form['revisions'][::-1]
922 commit_ids = _form['revisions'][::-1]
921
923
922 # find the ancestor for this pr
924 # find the ancestor for this pr
923 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
925 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
924 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
926 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
925
927
926 if not (source_db_repo or target_db_repo):
928 if not (source_db_repo or target_db_repo):
927 h.flash(_('source_repo or target repo not found'), category='error')
929 h.flash(_('source_repo or target repo not found'), category='error')
928 raise HTTPFound(
930 raise HTTPFound(
929 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
931 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
930
932
931 # re-check permissions again here
933 # re-check permissions again here
932 # source_repo we must have read permissions
934 # source_repo we must have read permissions
933
935
934 source_perm = HasRepoPermissionAny(
936 source_perm = HasRepoPermissionAny(
935 'repository.read', 'repository.write', 'repository.admin')(
937 'repository.read', 'repository.write', 'repository.admin')(
936 source_db_repo.repo_name)
938 source_db_repo.repo_name)
937 if not source_perm:
939 if not source_perm:
938 msg = _('Not Enough permissions to source repo `{}`.'.format(
940 msg = _('Not Enough permissions to source repo `{}`.'.format(
939 source_db_repo.repo_name))
941 source_db_repo.repo_name))
940 h.flash(msg, category='error')
942 h.flash(msg, category='error')
941 # copy the args back to redirect
943 # copy the args back to redirect
942 org_query = self.request.GET.mixed()
944 org_query = self.request.GET.mixed()
943 raise HTTPFound(
945 raise HTTPFound(
944 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
946 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
945 _query=org_query))
947 _query=org_query))
946
948
947 # target repo we must have read permissions, and also later on
949 # target repo we must have read permissions, and also later on
948 # we want to check branch permissions here
950 # we want to check branch permissions here
949 target_perm = HasRepoPermissionAny(
951 target_perm = HasRepoPermissionAny(
950 'repository.read', 'repository.write', 'repository.admin')(
952 'repository.read', 'repository.write', 'repository.admin')(
951 target_db_repo.repo_name)
953 target_db_repo.repo_name)
952 if not target_perm:
954 if not target_perm:
953 msg = _('Not Enough permissions to target repo `{}`.'.format(
955 msg = _('Not Enough permissions to target repo `{}`.'.format(
954 target_db_repo.repo_name))
956 target_db_repo.repo_name))
955 h.flash(msg, category='error')
957 h.flash(msg, category='error')
956 # copy the args back to redirect
958 # copy the args back to redirect
957 org_query = self.request.GET.mixed()
959 org_query = self.request.GET.mixed()
958 raise HTTPFound(
960 raise HTTPFound(
959 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
961 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
960 _query=org_query))
962 _query=org_query))
961
963
962 source_scm = source_db_repo.scm_instance()
964 source_scm = source_db_repo.scm_instance()
963 target_scm = target_db_repo.scm_instance()
965 target_scm = target_db_repo.scm_instance()
964
966
965 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
967 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
966 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
968 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
967
969
968 ancestor = source_scm.get_common_ancestor(
970 ancestor = source_scm.get_common_ancestor(
969 source_commit.raw_id, target_commit.raw_id, target_scm)
971 source_commit.raw_id, target_commit.raw_id, target_scm)
970
972
971 # recalculate target ref based on ancestor
973 # recalculate target ref based on ancestor
972 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
974 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
973 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
975 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
974
976
975 get_default_reviewers_data, validate_default_reviewers = \
977 get_default_reviewers_data, validate_default_reviewers = \
976 PullRequestModel().get_reviewer_functions()
978 PullRequestModel().get_reviewer_functions()
977
979
978 # recalculate reviewers logic, to make sure we can validate this
980 # recalculate reviewers logic, to make sure we can validate this
979 reviewer_rules = get_default_reviewers_data(
981 reviewer_rules = get_default_reviewers_data(
980 self._rhodecode_db_user, source_db_repo,
982 self._rhodecode_db_user, source_db_repo,
981 source_commit, target_db_repo, target_commit)
983 source_commit, target_db_repo, target_commit)
982
984
983 given_reviewers = _form['review_members']
985 given_reviewers = _form['review_members']
984 reviewers = validate_default_reviewers(
986 reviewers = validate_default_reviewers(
985 given_reviewers, reviewer_rules)
987 given_reviewers, reviewer_rules)
986
988
987 pullrequest_title = _form['pullrequest_title']
989 pullrequest_title = _form['pullrequest_title']
988 title_source_ref = source_ref.split(':', 2)[1]
990 title_source_ref = source_ref.split(':', 2)[1]
989 if not pullrequest_title:
991 if not pullrequest_title:
990 pullrequest_title = PullRequestModel().generate_pullrequest_title(
992 pullrequest_title = PullRequestModel().generate_pullrequest_title(
991 source=source_repo,
993 source=source_repo,
992 source_ref=title_source_ref,
994 source_ref=title_source_ref,
993 target=target_repo
995 target=target_repo
994 )
996 )
995
997
996 description = _form['pullrequest_desc']
998 description = _form['pullrequest_desc']
997 description_renderer = _form['description_renderer']
999 description_renderer = _form['description_renderer']
998
1000
999 try:
1001 try:
1000 pull_request = PullRequestModel().create(
1002 pull_request = PullRequestModel().create(
1001 created_by=self._rhodecode_user.user_id,
1003 created_by=self._rhodecode_user.user_id,
1002 source_repo=source_repo,
1004 source_repo=source_repo,
1003 source_ref=source_ref,
1005 source_ref=source_ref,
1004 target_repo=target_repo,
1006 target_repo=target_repo,
1005 target_ref=target_ref,
1007 target_ref=target_ref,
1006 revisions=commit_ids,
1008 revisions=commit_ids,
1007 reviewers=reviewers,
1009 reviewers=reviewers,
1008 title=pullrequest_title,
1010 title=pullrequest_title,
1009 description=description,
1011 description=description,
1010 description_renderer=description_renderer,
1012 description_renderer=description_renderer,
1011 reviewer_data=reviewer_rules,
1013 reviewer_data=reviewer_rules,
1012 auth_user=self._rhodecode_user
1014 auth_user=self._rhodecode_user
1013 )
1015 )
1014 Session().commit()
1016 Session().commit()
1015
1017
1016 h.flash(_('Successfully opened new pull request'),
1018 h.flash(_('Successfully opened new pull request'),
1017 category='success')
1019 category='success')
1018 except Exception:
1020 except Exception:
1019 msg = _('Error occurred during creation of this pull request.')
1021 msg = _('Error occurred during creation of this pull request.')
1020 log.exception(msg)
1022 log.exception(msg)
1021 h.flash(msg, category='error')
1023 h.flash(msg, category='error')
1022
1024
1023 # copy the args back to redirect
1025 # copy the args back to redirect
1024 org_query = self.request.GET.mixed()
1026 org_query = self.request.GET.mixed()
1025 raise HTTPFound(
1027 raise HTTPFound(
1026 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1028 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1027 _query=org_query))
1029 _query=org_query))
1028
1030
1029 raise HTTPFound(
1031 raise HTTPFound(
1030 h.route_path('pullrequest_show', repo_name=target_repo,
1032 h.route_path('pullrequest_show', repo_name=target_repo,
1031 pull_request_id=pull_request.pull_request_id))
1033 pull_request_id=pull_request.pull_request_id))
1032
1034
1033 @LoginRequired()
1035 @LoginRequired()
1034 @NotAnonymous()
1036 @NotAnonymous()
1035 @HasRepoPermissionAnyDecorator(
1037 @HasRepoPermissionAnyDecorator(
1036 'repository.read', 'repository.write', 'repository.admin')
1038 'repository.read', 'repository.write', 'repository.admin')
1037 @CSRFRequired()
1039 @CSRFRequired()
1038 @view_config(
1040 @view_config(
1039 route_name='pullrequest_update', request_method='POST',
1041 route_name='pullrequest_update', request_method='POST',
1040 renderer='json_ext')
1042 renderer='json_ext')
1041 def pull_request_update(self):
1043 def pull_request_update(self):
1042 pull_request = PullRequest.get_or_404(
1044 pull_request = PullRequest.get_or_404(
1043 self.request.matchdict['pull_request_id'])
1045 self.request.matchdict['pull_request_id'])
1044 _ = self.request.translate
1046 _ = self.request.translate
1045
1047
1046 self.load_default_context()
1048 self.load_default_context()
1047 redirect_url = None
1049 redirect_url = None
1048
1050
1049 if pull_request.is_closed():
1051 if pull_request.is_closed():
1050 log.debug('update: forbidden because pull request is closed')
1052 log.debug('update: forbidden because pull request is closed')
1051 msg = _(u'Cannot update closed pull requests.')
1053 msg = _(u'Cannot update closed pull requests.')
1052 h.flash(msg, category='error')
1054 h.flash(msg, category='error')
1053 return {'response': True,
1055 return {'response': True,
1054 'redirect_url': redirect_url}
1056 'redirect_url': redirect_url}
1055
1057
1056 is_state_changing = pull_request.is_state_changing()
1058 is_state_changing = pull_request.is_state_changing()
1057
1059
1058 # only owner or admin can update it
1060 # only owner or admin can update it
1059 allowed_to_update = PullRequestModel().check_user_update(
1061 allowed_to_update = PullRequestModel().check_user_update(
1060 pull_request, self._rhodecode_user)
1062 pull_request, self._rhodecode_user)
1061 if allowed_to_update:
1063 if allowed_to_update:
1062 controls = peppercorn.parse(self.request.POST.items())
1064 controls = peppercorn.parse(self.request.POST.items())
1063 force_refresh = str2bool(self.request.POST.get('force_refresh'))
1065 force_refresh = str2bool(self.request.POST.get('force_refresh'))
1064
1066
1065 if 'review_members' in controls:
1067 if 'review_members' in controls:
1066 self._update_reviewers(
1068 self._update_reviewers(
1067 pull_request, controls['review_members'],
1069 pull_request, controls['review_members'],
1068 pull_request.reviewer_data)
1070 pull_request.reviewer_data)
1069 elif str2bool(self.request.POST.get('update_commits', 'false')):
1071 elif str2bool(self.request.POST.get('update_commits', 'false')):
1070 if is_state_changing:
1072 if is_state_changing:
1071 log.debug('commits update: forbidden because pull request is in state %s',
1073 log.debug('commits update: forbidden because pull request is in state %s',
1072 pull_request.pull_request_state)
1074 pull_request.pull_request_state)
1073 msg = _(u'Cannot update pull requests commits in state other than `{}`. '
1075 msg = _(u'Cannot update pull requests commits in state other than `{}`. '
1074 u'Current state is: `{}`').format(
1076 u'Current state is: `{}`').format(
1075 PullRequest.STATE_CREATED, pull_request.pull_request_state)
1077 PullRequest.STATE_CREATED, pull_request.pull_request_state)
1076 h.flash(msg, category='error')
1078 h.flash(msg, category='error')
1077 return {'response': True,
1079 return {'response': True,
1078 'redirect_url': redirect_url}
1080 'redirect_url': redirect_url}
1079
1081
1080 self._update_commits(pull_request)
1082 self._update_commits(pull_request)
1081 if force_refresh:
1083 if force_refresh:
1082 redirect_url = h.route_path(
1084 redirect_url = h.route_path(
1083 'pullrequest_show', repo_name=self.db_repo_name,
1085 'pullrequest_show', repo_name=self.db_repo_name,
1084 pull_request_id=pull_request.pull_request_id,
1086 pull_request_id=pull_request.pull_request_id,
1085 _query={"force_refresh": 1})
1087 _query={"force_refresh": 1})
1086 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1088 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1087 self._edit_pull_request(pull_request)
1089 self._edit_pull_request(pull_request)
1088 else:
1090 else:
1089 raise HTTPBadRequest()
1091 raise HTTPBadRequest()
1090
1092
1091 return {'response': True,
1093 return {'response': True,
1092 'redirect_url': redirect_url}
1094 'redirect_url': redirect_url}
1093 raise HTTPForbidden()
1095 raise HTTPForbidden()
1094
1096
1095 def _edit_pull_request(self, pull_request):
1097 def _edit_pull_request(self, pull_request):
1096 _ = self.request.translate
1098 _ = self.request.translate
1097
1099
1098 try:
1100 try:
1099 PullRequestModel().edit(
1101 PullRequestModel().edit(
1100 pull_request,
1102 pull_request,
1101 self.request.POST.get('title'),
1103 self.request.POST.get('title'),
1102 self.request.POST.get('description'),
1104 self.request.POST.get('description'),
1103 self.request.POST.get('description_renderer'),
1105 self.request.POST.get('description_renderer'),
1104 self._rhodecode_user)
1106 self._rhodecode_user)
1105 except ValueError:
1107 except ValueError:
1106 msg = _(u'Cannot update closed pull requests.')
1108 msg = _(u'Cannot update closed pull requests.')
1107 h.flash(msg, category='error')
1109 h.flash(msg, category='error')
1108 return
1110 return
1109 else:
1111 else:
1110 Session().commit()
1112 Session().commit()
1111
1113
1112 msg = _(u'Pull request title & description updated.')
1114 msg = _(u'Pull request title & description updated.')
1113 h.flash(msg, category='success')
1115 h.flash(msg, category='success')
1114 return
1116 return
1115
1117
1116 def _update_commits(self, pull_request):
1118 def _update_commits(self, pull_request):
1117 _ = self.request.translate
1119 _ = self.request.translate
1118
1120
1119 with pull_request.set_state(PullRequest.STATE_UPDATING):
1121 with pull_request.set_state(PullRequest.STATE_UPDATING):
1120 resp = PullRequestModel().update_commits(
1122 resp = PullRequestModel().update_commits(
1121 pull_request, self._rhodecode_db_user)
1123 pull_request, self._rhodecode_db_user)
1122
1124
1123 if resp.executed:
1125 if resp.executed:
1124
1126
1125 if resp.target_changed and resp.source_changed:
1127 if resp.target_changed and resp.source_changed:
1126 changed = 'target and source repositories'
1128 changed = 'target and source repositories'
1127 elif resp.target_changed and not resp.source_changed:
1129 elif resp.target_changed and not resp.source_changed:
1128 changed = 'target repository'
1130 changed = 'target repository'
1129 elif not resp.target_changed and resp.source_changed:
1131 elif not resp.target_changed and resp.source_changed:
1130 changed = 'source repository'
1132 changed = 'source repository'
1131 else:
1133 else:
1132 changed = 'nothing'
1134 changed = 'nothing'
1133
1135
1134 msg = _(u'Pull request updated to "{source_commit_id}" with '
1136 msg = _(u'Pull request updated to "{source_commit_id}" with '
1135 u'{count_added} added, {count_removed} removed commits. '
1137 u'{count_added} added, {count_removed} removed commits. '
1136 u'Source of changes: {change_source}')
1138 u'Source of changes: {change_source}')
1137 msg = msg.format(
1139 msg = msg.format(
1138 source_commit_id=pull_request.source_ref_parts.commit_id,
1140 source_commit_id=pull_request.source_ref_parts.commit_id,
1139 count_added=len(resp.changes.added),
1141 count_added=len(resp.changes.added),
1140 count_removed=len(resp.changes.removed),
1142 count_removed=len(resp.changes.removed),
1141 change_source=changed)
1143 change_source=changed)
1142 h.flash(msg, category='success')
1144 h.flash(msg, category='success')
1143
1145
1144 channel = '/repo${}$/pr/{}'.format(
1146 channel = '/repo${}$/pr/{}'.format(
1145 pull_request.target_repo.repo_name, pull_request.pull_request_id)
1147 pull_request.target_repo.repo_name, pull_request.pull_request_id)
1146 message = msg + (
1148 message = msg + (
1147 ' - <a onclick="window.location.reload()">'
1149 ' - <a onclick="window.location.reload()">'
1148 '<strong>{}</strong></a>'.format(_('Reload page')))
1150 '<strong>{}</strong></a>'.format(_('Reload page')))
1149 channelstream.post_message(
1151 channelstream.post_message(
1150 channel, message, self._rhodecode_user.username,
1152 channel, message, self._rhodecode_user.username,
1151 registry=self.request.registry)
1153 registry=self.request.registry)
1152 else:
1154 else:
1153 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1155 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1154 warning_reasons = [
1156 warning_reasons = [
1155 UpdateFailureReason.NO_CHANGE,
1157 UpdateFailureReason.NO_CHANGE,
1156 UpdateFailureReason.WRONG_REF_TYPE,
1158 UpdateFailureReason.WRONG_REF_TYPE,
1157 ]
1159 ]
1158 category = 'warning' if resp.reason in warning_reasons else 'error'
1160 category = 'warning' if resp.reason in warning_reasons else 'error'
1159 h.flash(msg, category=category)
1161 h.flash(msg, category=category)
1160
1162
1161 @LoginRequired()
1163 @LoginRequired()
1162 @NotAnonymous()
1164 @NotAnonymous()
1163 @HasRepoPermissionAnyDecorator(
1165 @HasRepoPermissionAnyDecorator(
1164 'repository.read', 'repository.write', 'repository.admin')
1166 'repository.read', 'repository.write', 'repository.admin')
1165 @CSRFRequired()
1167 @CSRFRequired()
1166 @view_config(
1168 @view_config(
1167 route_name='pullrequest_merge', request_method='POST',
1169 route_name='pullrequest_merge', request_method='POST',
1168 renderer='json_ext')
1170 renderer='json_ext')
1169 def pull_request_merge(self):
1171 def pull_request_merge(self):
1170 """
1172 """
1171 Merge will perform a server-side merge of the specified
1173 Merge will perform a server-side merge of the specified
1172 pull request, if the pull request is approved and mergeable.
1174 pull request, if the pull request is approved and mergeable.
1173 After successful merging, the pull request is automatically
1175 After successful merging, the pull request is automatically
1174 closed, with a relevant comment.
1176 closed, with a relevant comment.
1175 """
1177 """
1176 pull_request = PullRequest.get_or_404(
1178 pull_request = PullRequest.get_or_404(
1177 self.request.matchdict['pull_request_id'])
1179 self.request.matchdict['pull_request_id'])
1178 _ = self.request.translate
1180 _ = self.request.translate
1179
1181
1180 if pull_request.is_state_changing():
1182 if pull_request.is_state_changing():
1181 log.debug('show: forbidden because pull request is in state %s',
1183 log.debug('show: forbidden because pull request is in state %s',
1182 pull_request.pull_request_state)
1184 pull_request.pull_request_state)
1183 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1185 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1184 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1186 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1185 pull_request.pull_request_state)
1187 pull_request.pull_request_state)
1186 h.flash(msg, category='error')
1188 h.flash(msg, category='error')
1187 raise HTTPFound(
1189 raise HTTPFound(
1188 h.route_path('pullrequest_show',
1190 h.route_path('pullrequest_show',
1189 repo_name=pull_request.target_repo.repo_name,
1191 repo_name=pull_request.target_repo.repo_name,
1190 pull_request_id=pull_request.pull_request_id))
1192 pull_request_id=pull_request.pull_request_id))
1191
1193
1192 self.load_default_context()
1194 self.load_default_context()
1193
1195
1194 with pull_request.set_state(PullRequest.STATE_UPDATING):
1196 with pull_request.set_state(PullRequest.STATE_UPDATING):
1195 check = MergeCheck.validate(
1197 check = MergeCheck.validate(
1196 pull_request, auth_user=self._rhodecode_user,
1198 pull_request, auth_user=self._rhodecode_user,
1197 translator=self.request.translate)
1199 translator=self.request.translate)
1198 merge_possible = not check.failed
1200 merge_possible = not check.failed
1199
1201
1200 for err_type, error_msg in check.errors:
1202 for err_type, error_msg in check.errors:
1201 h.flash(error_msg, category=err_type)
1203 h.flash(error_msg, category=err_type)
1202
1204
1203 if merge_possible:
1205 if merge_possible:
1204 log.debug("Pre-conditions checked, trying to merge.")
1206 log.debug("Pre-conditions checked, trying to merge.")
1205 extras = vcs_operation_context(
1207 extras = vcs_operation_context(
1206 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1208 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1207 username=self._rhodecode_db_user.username, action='push',
1209 username=self._rhodecode_db_user.username, action='push',
1208 scm=pull_request.target_repo.repo_type)
1210 scm=pull_request.target_repo.repo_type)
1209 with pull_request.set_state(PullRequest.STATE_UPDATING):
1211 with pull_request.set_state(PullRequest.STATE_UPDATING):
1210 self._merge_pull_request(
1212 self._merge_pull_request(
1211 pull_request, self._rhodecode_db_user, extras)
1213 pull_request, self._rhodecode_db_user, extras)
1212 else:
1214 else:
1213 log.debug("Pre-conditions failed, NOT merging.")
1215 log.debug("Pre-conditions failed, NOT merging.")
1214
1216
1215 raise HTTPFound(
1217 raise HTTPFound(
1216 h.route_path('pullrequest_show',
1218 h.route_path('pullrequest_show',
1217 repo_name=pull_request.target_repo.repo_name,
1219 repo_name=pull_request.target_repo.repo_name,
1218 pull_request_id=pull_request.pull_request_id))
1220 pull_request_id=pull_request.pull_request_id))
1219
1221
1220 def _merge_pull_request(self, pull_request, user, extras):
1222 def _merge_pull_request(self, pull_request, user, extras):
1221 _ = self.request.translate
1223 _ = self.request.translate
1222 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1224 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1223
1225
1224 if merge_resp.executed:
1226 if merge_resp.executed:
1225 log.debug("The merge was successful, closing the pull request.")
1227 log.debug("The merge was successful, closing the pull request.")
1226 PullRequestModel().close_pull_request(
1228 PullRequestModel().close_pull_request(
1227 pull_request.pull_request_id, user)
1229 pull_request.pull_request_id, user)
1228 Session().commit()
1230 Session().commit()
1229 msg = _('Pull request was successfully merged and closed.')
1231 msg = _('Pull request was successfully merged and closed.')
1230 h.flash(msg, category='success')
1232 h.flash(msg, category='success')
1231 else:
1233 else:
1232 log.debug(
1234 log.debug(
1233 "The merge was not successful. Merge response: %s", merge_resp)
1235 "The merge was not successful. Merge response: %s", merge_resp)
1234 msg = merge_resp.merge_status_message
1236 msg = merge_resp.merge_status_message
1235 h.flash(msg, category='error')
1237 h.flash(msg, category='error')
1236
1238
1237 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1239 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1238 _ = self.request.translate
1240 _ = self.request.translate
1239
1241
1240 get_default_reviewers_data, validate_default_reviewers = \
1242 get_default_reviewers_data, validate_default_reviewers = \
1241 PullRequestModel().get_reviewer_functions()
1243 PullRequestModel().get_reviewer_functions()
1242
1244
1243 try:
1245 try:
1244 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1246 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1245 except ValueError as e:
1247 except ValueError as e:
1246 log.error('Reviewers Validation: {}'.format(e))
1248 log.error('Reviewers Validation: {}'.format(e))
1247 h.flash(e, category='error')
1249 h.flash(e, category='error')
1248 return
1250 return
1249
1251
1250 old_calculated_status = pull_request.calculated_review_status()
1252 old_calculated_status = pull_request.calculated_review_status()
1251 PullRequestModel().update_reviewers(
1253 PullRequestModel().update_reviewers(
1252 pull_request, reviewers, self._rhodecode_user)
1254 pull_request, reviewers, self._rhodecode_user)
1253 h.flash(_('Pull request reviewers updated.'), category='success')
1255 h.flash(_('Pull request reviewers updated.'), category='success')
1254 Session().commit()
1256 Session().commit()
1255
1257
1256 # trigger status changed if change in reviewers changes the status
1258 # trigger status changed if change in reviewers changes the status
1257 calculated_status = pull_request.calculated_review_status()
1259 calculated_status = pull_request.calculated_review_status()
1258 if old_calculated_status != calculated_status:
1260 if old_calculated_status != calculated_status:
1259 PullRequestModel().trigger_pull_request_hook(
1261 PullRequestModel().trigger_pull_request_hook(
1260 pull_request, self._rhodecode_user, 'review_status_change',
1262 pull_request, self._rhodecode_user, 'review_status_change',
1261 data={'status': calculated_status})
1263 data={'status': calculated_status})
1262
1264
1263 @LoginRequired()
1265 @LoginRequired()
1264 @NotAnonymous()
1266 @NotAnonymous()
1265 @HasRepoPermissionAnyDecorator(
1267 @HasRepoPermissionAnyDecorator(
1266 'repository.read', 'repository.write', 'repository.admin')
1268 'repository.read', 'repository.write', 'repository.admin')
1267 @CSRFRequired()
1269 @CSRFRequired()
1268 @view_config(
1270 @view_config(
1269 route_name='pullrequest_delete', request_method='POST',
1271 route_name='pullrequest_delete', request_method='POST',
1270 renderer='json_ext')
1272 renderer='json_ext')
1271 def pull_request_delete(self):
1273 def pull_request_delete(self):
1272 _ = self.request.translate
1274 _ = self.request.translate
1273
1275
1274 pull_request = PullRequest.get_or_404(
1276 pull_request = PullRequest.get_or_404(
1275 self.request.matchdict['pull_request_id'])
1277 self.request.matchdict['pull_request_id'])
1276 self.load_default_context()
1278 self.load_default_context()
1277
1279
1278 pr_closed = pull_request.is_closed()
1280 pr_closed = pull_request.is_closed()
1279 allowed_to_delete = PullRequestModel().check_user_delete(
1281 allowed_to_delete = PullRequestModel().check_user_delete(
1280 pull_request, self._rhodecode_user) and not pr_closed
1282 pull_request, self._rhodecode_user) and not pr_closed
1281
1283
1282 # only owner can delete it !
1284 # only owner can delete it !
1283 if allowed_to_delete:
1285 if allowed_to_delete:
1284 PullRequestModel().delete(pull_request, self._rhodecode_user)
1286 PullRequestModel().delete(pull_request, self._rhodecode_user)
1285 Session().commit()
1287 Session().commit()
1286 h.flash(_('Successfully deleted pull request'),
1288 h.flash(_('Successfully deleted pull request'),
1287 category='success')
1289 category='success')
1288 raise HTTPFound(h.route_path('pullrequest_show_all',
1290 raise HTTPFound(h.route_path('pullrequest_show_all',
1289 repo_name=self.db_repo_name))
1291 repo_name=self.db_repo_name))
1290
1292
1291 log.warning('user %s tried to delete pull request without access',
1293 log.warning('user %s tried to delete pull request without access',
1292 self._rhodecode_user)
1294 self._rhodecode_user)
1293 raise HTTPNotFound()
1295 raise HTTPNotFound()
1294
1296
1295 @LoginRequired()
1297 @LoginRequired()
1296 @NotAnonymous()
1298 @NotAnonymous()
1297 @HasRepoPermissionAnyDecorator(
1299 @HasRepoPermissionAnyDecorator(
1298 'repository.read', 'repository.write', 'repository.admin')
1300 'repository.read', 'repository.write', 'repository.admin')
1299 @CSRFRequired()
1301 @CSRFRequired()
1300 @view_config(
1302 @view_config(
1301 route_name='pullrequest_comment_create', request_method='POST',
1303 route_name='pullrequest_comment_create', request_method='POST',
1302 renderer='json_ext')
1304 renderer='json_ext')
1303 def pull_request_comment_create(self):
1305 def pull_request_comment_create(self):
1304 _ = self.request.translate
1306 _ = self.request.translate
1305
1307
1306 pull_request = PullRequest.get_or_404(
1308 pull_request = PullRequest.get_or_404(
1307 self.request.matchdict['pull_request_id'])
1309 self.request.matchdict['pull_request_id'])
1308 pull_request_id = pull_request.pull_request_id
1310 pull_request_id = pull_request.pull_request_id
1309
1311
1310 if pull_request.is_closed():
1312 if pull_request.is_closed():
1311 log.debug('comment: forbidden because pull request is closed')
1313 log.debug('comment: forbidden because pull request is closed')
1312 raise HTTPForbidden()
1314 raise HTTPForbidden()
1313
1315
1314 allowed_to_comment = PullRequestModel().check_user_comment(
1316 allowed_to_comment = PullRequestModel().check_user_comment(
1315 pull_request, self._rhodecode_user)
1317 pull_request, self._rhodecode_user)
1316 if not allowed_to_comment:
1318 if not allowed_to_comment:
1317 log.debug(
1319 log.debug(
1318 'comment: forbidden because pull request is from forbidden repo')
1320 'comment: forbidden because pull request is from forbidden repo')
1319 raise HTTPForbidden()
1321 raise HTTPForbidden()
1320
1322
1321 c = self.load_default_context()
1323 c = self.load_default_context()
1322
1324
1323 status = self.request.POST.get('changeset_status', None)
1325 status = self.request.POST.get('changeset_status', None)
1324 text = self.request.POST.get('text')
1326 text = self.request.POST.get('text')
1325 comment_type = self.request.POST.get('comment_type')
1327 comment_type = self.request.POST.get('comment_type')
1326 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1328 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1327 close_pull_request = self.request.POST.get('close_pull_request')
1329 close_pull_request = self.request.POST.get('close_pull_request')
1328
1330
1329 # the logic here should work like following, if we submit close
1331 # the logic here should work like following, if we submit close
1330 # pr comment, use `close_pull_request_with_comment` function
1332 # pr comment, use `close_pull_request_with_comment` function
1331 # else handle regular comment logic
1333 # else handle regular comment logic
1332
1334
1333 if close_pull_request:
1335 if close_pull_request:
1334 # only owner or admin or person with write permissions
1336 # only owner or admin or person with write permissions
1335 allowed_to_close = PullRequestModel().check_user_update(
1337 allowed_to_close = PullRequestModel().check_user_update(
1336 pull_request, self._rhodecode_user)
1338 pull_request, self._rhodecode_user)
1337 if not allowed_to_close:
1339 if not allowed_to_close:
1338 log.debug('comment: forbidden because not allowed to close '
1340 log.debug('comment: forbidden because not allowed to close '
1339 'pull request %s', pull_request_id)
1341 'pull request %s', pull_request_id)
1340 raise HTTPForbidden()
1342 raise HTTPForbidden()
1341
1343
1342 # This also triggers `review_status_change`
1344 # This also triggers `review_status_change`
1343 comment, status = PullRequestModel().close_pull_request_with_comment(
1345 comment, status = PullRequestModel().close_pull_request_with_comment(
1344 pull_request, self._rhodecode_user, self.db_repo, message=text,
1346 pull_request, self._rhodecode_user, self.db_repo, message=text,
1345 auth_user=self._rhodecode_user)
1347 auth_user=self._rhodecode_user)
1346 Session().flush()
1348 Session().flush()
1347
1349
1348 PullRequestModel().trigger_pull_request_hook(
1350 PullRequestModel().trigger_pull_request_hook(
1349 pull_request, self._rhodecode_user, 'comment',
1351 pull_request, self._rhodecode_user, 'comment',
1350 data={'comment': comment})
1352 data={'comment': comment})
1351
1353
1352 else:
1354 else:
1353 # regular comment case, could be inline, or one with status.
1355 # regular comment case, could be inline, or one with status.
1354 # for that one we check also permissions
1356 # for that one we check also permissions
1355
1357
1356 allowed_to_change_status = PullRequestModel().check_user_change_status(
1358 allowed_to_change_status = PullRequestModel().check_user_change_status(
1357 pull_request, self._rhodecode_user)
1359 pull_request, self._rhodecode_user)
1358
1360
1359 if status and allowed_to_change_status:
1361 if status and allowed_to_change_status:
1360 message = (_('Status change %(transition_icon)s %(status)s')
1362 message = (_('Status change %(transition_icon)s %(status)s')
1361 % {'transition_icon': '>',
1363 % {'transition_icon': '>',
1362 'status': ChangesetStatus.get_status_lbl(status)})
1364 'status': ChangesetStatus.get_status_lbl(status)})
1363 text = text or message
1365 text = text or message
1364
1366
1365 comment = CommentsModel().create(
1367 comment = CommentsModel().create(
1366 text=text,
1368 text=text,
1367 repo=self.db_repo.repo_id,
1369 repo=self.db_repo.repo_id,
1368 user=self._rhodecode_user.user_id,
1370 user=self._rhodecode_user.user_id,
1369 pull_request=pull_request,
1371 pull_request=pull_request,
1370 f_path=self.request.POST.get('f_path'),
1372 f_path=self.request.POST.get('f_path'),
1371 line_no=self.request.POST.get('line'),
1373 line_no=self.request.POST.get('line'),
1372 status_change=(ChangesetStatus.get_status_lbl(status)
1374 status_change=(ChangesetStatus.get_status_lbl(status)
1373 if status and allowed_to_change_status else None),
1375 if status and allowed_to_change_status else None),
1374 status_change_type=(status
1376 status_change_type=(status
1375 if status and allowed_to_change_status else None),
1377 if status and allowed_to_change_status else None),
1376 comment_type=comment_type,
1378 comment_type=comment_type,
1377 resolves_comment_id=resolves_comment_id,
1379 resolves_comment_id=resolves_comment_id,
1378 auth_user=self._rhodecode_user
1380 auth_user=self._rhodecode_user
1379 )
1381 )
1380
1382
1381 if allowed_to_change_status:
1383 if allowed_to_change_status:
1382 # calculate old status before we change it
1384 # calculate old status before we change it
1383 old_calculated_status = pull_request.calculated_review_status()
1385 old_calculated_status = pull_request.calculated_review_status()
1384
1386
1385 # get status if set !
1387 # get status if set !
1386 if status:
1388 if status:
1387 ChangesetStatusModel().set_status(
1389 ChangesetStatusModel().set_status(
1388 self.db_repo.repo_id,
1390 self.db_repo.repo_id,
1389 status,
1391 status,
1390 self._rhodecode_user.user_id,
1392 self._rhodecode_user.user_id,
1391 comment,
1393 comment,
1392 pull_request=pull_request
1394 pull_request=pull_request
1393 )
1395 )
1394
1396
1395 Session().flush()
1397 Session().flush()
1396 # this is somehow required to get access to some relationship
1398 # this is somehow required to get access to some relationship
1397 # loaded on comment
1399 # loaded on comment
1398 Session().refresh(comment)
1400 Session().refresh(comment)
1399
1401
1400 PullRequestModel().trigger_pull_request_hook(
1402 PullRequestModel().trigger_pull_request_hook(
1401 pull_request, self._rhodecode_user, 'comment',
1403 pull_request, self._rhodecode_user, 'comment',
1402 data={'comment': comment})
1404 data={'comment': comment})
1403
1405
1404 # we now calculate the status of pull request, and based on that
1406 # we now calculate the status of pull request, and based on that
1405 # calculation we set the commits status
1407 # calculation we set the commits status
1406 calculated_status = pull_request.calculated_review_status()
1408 calculated_status = pull_request.calculated_review_status()
1407 if old_calculated_status != calculated_status:
1409 if old_calculated_status != calculated_status:
1408 PullRequestModel().trigger_pull_request_hook(
1410 PullRequestModel().trigger_pull_request_hook(
1409 pull_request, self._rhodecode_user, 'review_status_change',
1411 pull_request, self._rhodecode_user, 'review_status_change',
1410 data={'status': calculated_status})
1412 data={'status': calculated_status})
1411
1413
1412 Session().commit()
1414 Session().commit()
1413
1415
1414 data = {
1416 data = {
1415 'target_id': h.safeid(h.safe_unicode(
1417 'target_id': h.safeid(h.safe_unicode(
1416 self.request.POST.get('f_path'))),
1418 self.request.POST.get('f_path'))),
1417 }
1419 }
1418 if comment:
1420 if comment:
1419 c.co = comment
1421 c.co = comment
1420 rendered_comment = render(
1422 rendered_comment = render(
1421 'rhodecode:templates/changeset/changeset_comment_block.mako',
1423 'rhodecode:templates/changeset/changeset_comment_block.mako',
1422 self._get_template_context(c), self.request)
1424 self._get_template_context(c), self.request)
1423
1425
1424 data.update(comment.get_dict())
1426 data.update(comment.get_dict())
1425 data.update({'rendered_text': rendered_comment})
1427 data.update({'rendered_text': rendered_comment})
1426
1428
1427 return data
1429 return data
1428
1430
1429 @LoginRequired()
1431 @LoginRequired()
1430 @NotAnonymous()
1432 @NotAnonymous()
1431 @HasRepoPermissionAnyDecorator(
1433 @HasRepoPermissionAnyDecorator(
1432 'repository.read', 'repository.write', 'repository.admin')
1434 'repository.read', 'repository.write', 'repository.admin')
1433 @CSRFRequired()
1435 @CSRFRequired()
1434 @view_config(
1436 @view_config(
1435 route_name='pullrequest_comment_delete', request_method='POST',
1437 route_name='pullrequest_comment_delete', request_method='POST',
1436 renderer='json_ext')
1438 renderer='json_ext')
1437 def pull_request_comment_delete(self):
1439 def pull_request_comment_delete(self):
1438 pull_request = PullRequest.get_or_404(
1440 pull_request = PullRequest.get_or_404(
1439 self.request.matchdict['pull_request_id'])
1441 self.request.matchdict['pull_request_id'])
1440
1442
1441 comment = ChangesetComment.get_or_404(
1443 comment = ChangesetComment.get_or_404(
1442 self.request.matchdict['comment_id'])
1444 self.request.matchdict['comment_id'])
1443 comment_id = comment.comment_id
1445 comment_id = comment.comment_id
1444
1446
1445 if pull_request.is_closed():
1447 if pull_request.is_closed():
1446 log.debug('comment: forbidden because pull request is closed')
1448 log.debug('comment: forbidden because pull request is closed')
1447 raise HTTPForbidden()
1449 raise HTTPForbidden()
1448
1450
1449 if not comment:
1451 if not comment:
1450 log.debug('Comment with id:%s not found, skipping', comment_id)
1452 log.debug('Comment with id:%s not found, skipping', comment_id)
1451 # comment already deleted in another call probably
1453 # comment already deleted in another call probably
1452 return True
1454 return True
1453
1455
1454 if comment.pull_request.is_closed():
1456 if comment.pull_request.is_closed():
1455 # don't allow deleting comments on closed pull request
1457 # don't allow deleting comments on closed pull request
1456 raise HTTPForbidden()
1458 raise HTTPForbidden()
1457
1459
1458 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1460 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1459 super_admin = h.HasPermissionAny('hg.admin')()
1461 super_admin = h.HasPermissionAny('hg.admin')()
1460 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1462 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1461 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1463 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1462 comment_repo_admin = is_repo_admin and is_repo_comment
1464 comment_repo_admin = is_repo_admin and is_repo_comment
1463
1465
1464 if super_admin or comment_owner or comment_repo_admin:
1466 if super_admin or comment_owner or comment_repo_admin:
1465 old_calculated_status = comment.pull_request.calculated_review_status()
1467 old_calculated_status = comment.pull_request.calculated_review_status()
1466 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1468 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1467 Session().commit()
1469 Session().commit()
1468 calculated_status = comment.pull_request.calculated_review_status()
1470 calculated_status = comment.pull_request.calculated_review_status()
1469 if old_calculated_status != calculated_status:
1471 if old_calculated_status != calculated_status:
1470 PullRequestModel().trigger_pull_request_hook(
1472 PullRequestModel().trigger_pull_request_hook(
1471 comment.pull_request, self._rhodecode_user, 'review_status_change',
1473 comment.pull_request, self._rhodecode_user, 'review_status_change',
1472 data={'status': calculated_status})
1474 data={'status': calculated_status})
1473 return True
1475 return True
1474 else:
1476 else:
1475 log.warning('No permissions for user %s to delete comment_id: %s',
1477 log.warning('No permissions for user %s to delete comment_id: %s',
1476 self._rhodecode_db_user, comment_id)
1478 self._rhodecode_db_user, comment_id)
1477 raise HTTPNotFound()
1479 raise HTTPNotFound()
General Comments 0
You need to be logged in to leave comments. Login now