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