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