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