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