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