Show More
@@ -69,6 +69,8 b' class PullrequestsController(BaseRepoCon' | |||
|
69 | 69 | |
|
70 | 70 | def __before__(self): |
|
71 | 71 | super(PullrequestsController, self).__before__() |
|
72 | c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED | |
|
73 | c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED | |
|
72 | 74 | |
|
73 | 75 | def _extract_ordering(self, request): |
|
74 | 76 | column_index = safe_int(request.GET.get('order[0][column]')) |
@@ -686,9 +688,12 b' class PullrequestsController(BaseRepoCon' | |||
|
686 | 688 | c.allowed_to_merge = False |
|
687 | 689 | c.allowed_to_delete = False |
|
688 | 690 | c.allowed_to_comment = False |
|
691 | c.allowed_to_close = False | |
|
689 | 692 | else: |
|
690 | 693 | c.allowed_to_change_status = PullRequestModel(). \ |
|
691 | check_user_change_status(pull_request_at_ver, c.rhodecode_user) | |
|
694 | check_user_change_status(pull_request_at_ver, c.rhodecode_user) \ | |
|
695 | and not pr_closed | |
|
696 | ||
|
692 | 697 | c.allowed_to_update = PullRequestModel().check_user_update( |
|
693 | 698 | pull_request_latest, c.rhodecode_user) and not pr_closed |
|
694 | 699 | c.allowed_to_merge = PullRequestModel().check_user_merge( |
@@ -696,6 +701,7 b' class PullrequestsController(BaseRepoCon' | |||
|
696 | 701 | c.allowed_to_delete = PullRequestModel().check_user_delete( |
|
697 | 702 | pull_request_latest, c.rhodecode_user) and not pr_closed |
|
698 | 703 | c.allowed_to_comment = not pr_closed |
|
704 | c.allowed_to_close = c.allowed_to_change_status and not pr_closed | |
|
699 | 705 | |
|
700 | 706 | # check merge capabilities |
|
701 | 707 | _merge_check = MergeCheck.validate( |
@@ -704,6 +710,7 b' class PullrequestsController(BaseRepoCon' | |||
|
704 | 710 | c.pr_merge_possible = not _merge_check.failed |
|
705 | 711 | c.pr_merge_message = _merge_check.merge_msg |
|
706 | 712 | |
|
713 | c.pull_request_review_status = _merge_check.review_status | |
|
707 | 714 | if merge_checks: |
|
708 | 715 | return render('/pullrequests/pullrequest_merge_checks.mako') |
|
709 | 716 | |
@@ -712,7 +719,6 b' class PullrequestsController(BaseRepoCon' | |||
|
712 | 719 | # reviewers and statuses |
|
713 | 720 | c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses() |
|
714 | 721 | allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers] |
|
715 | c.pull_request_review_status = pull_request_at_ver.calculated_review_status() | |
|
716 | 722 | |
|
717 | 723 | # GENERAL COMMENTS with versions # |
|
718 | 724 | q = comments_model._all_general_comments_of_pull_request(pull_request_latest) |
@@ -860,12 +866,7 b' class PullrequestsController(BaseRepoCon' | |||
|
860 | 866 | # We need to swap that here to generate it properly on the html side |
|
861 | 867 | c.target_repo = c.source_repo |
|
862 | 868 | |
|
863 | if c.allowed_to_update: | |
|
864 | force_close = ('forced_closed', _('Close Pull Request')) | |
|
865 | statuses = ChangesetStatus.STATUSES + [force_close] | |
|
866 | else: | |
|
867 | statuses = ChangesetStatus.STATUSES | |
|
868 | c.commit_statuses = statuses | |
|
869 | c.commit_statuses = ChangesetStatus.STATUSES | |
|
869 | 870 | |
|
870 | 871 | c.show_version_changes = not pr_closed |
|
871 | 872 | if c.show_version_changes: |
@@ -924,22 +925,21 b' class PullrequestsController(BaseRepoCon' | |||
|
924 | 925 | if pull_request.is_closed(): |
|
925 | 926 | raise HTTPForbidden() |
|
926 | 927 | |
|
927 | # TODO: johbo: Re-think this bit, "approved_closed" does not exist | |
|
928 | # as a changeset status, still we want to send it in one value. | |
|
929 | 928 | status = request.POST.get('changeset_status', None) |
|
930 | 929 | text = request.POST.get('text') |
|
931 | 930 | comment_type = request.POST.get('comment_type') |
|
932 | 931 | resolves_comment_id = request.POST.get('resolves_comment_id', None) |
|
932 | close_pull_request = request.POST.get('close_pull_request') | |
|
933 | 933 | |
|
934 | if status and '_closed' in status: | |
|
934 | close_pr = False | |
|
935 | if close_pull_request: | |
|
935 | 936 | close_pr = True |
|
936 | status = status.replace('_closed', '') | |
|
937 | else: | |
|
938 | close_pr = False | |
|
939 | ||
|
940 | forced = (status == 'forced') | |
|
941 | if forced: | |
|
942 | status = 'rejected' | |
|
937 | pull_request_review_status = pull_request.calculated_review_status() | |
|
938 | if pull_request_review_status == ChangesetStatus.STATUS_APPROVED: | |
|
939 | # approved only if we have voting consent | |
|
940 | status = ChangesetStatus.STATUS_APPROVED | |
|
941 | else: | |
|
942 | status = ChangesetStatus.STATUS_REJECTED | |
|
943 | 943 | |
|
944 | 944 | allowed_to_change_status = PullRequestModel().check_user_change_status( |
|
945 | 945 | pull_request, c.rhodecode_user) |
@@ -995,7 +995,7 b' class PullrequestsController(BaseRepoCon' | |||
|
995 | 995 | status_completed = ( |
|
996 | 996 | calculated_status in [ChangesetStatus.STATUS_APPROVED, |
|
997 | 997 | ChangesetStatus.STATUS_REJECTED]) |
|
998 |
if |
|
|
998 | if close_pull_request or status_completed: | |
|
999 | 999 | PullRequestModel().close_pull_request( |
|
1000 | 1000 | pull_request_id, c.rhodecode_user) |
|
1001 | 1001 | else: |
@@ -1333,6 +1333,7 b' class MergeCheck(object):' | |||
|
1333 | 1333 | MERGE_CHECK = 'merge' |
|
1334 | 1334 | |
|
1335 | 1335 | def __init__(self): |
|
1336 | self.review_status = None | |
|
1336 | 1337 | self.merge_possible = None |
|
1337 | 1338 | self.merge_msg = '' |
|
1338 | 1339 | self.failed = None |
@@ -1355,7 +1356,7 b' class MergeCheck(object):' | |||
|
1355 | 1356 | |
|
1356 | 1357 | merge_check = cls() |
|
1357 | 1358 | |
|
1358 | # permissions | |
|
1359 | # permissions to merge | |
|
1359 | 1360 | user_allowed_to_merge = PullRequestModel().check_user_merge( |
|
1360 | 1361 | pull_request, user) |
|
1361 | 1362 | if not user_allowed_to_merge: |
@@ -1366,8 +1367,10 b' class MergeCheck(object):' | |||
|
1366 | 1367 | if fail_early: |
|
1367 | 1368 | return merge_check |
|
1368 | 1369 | |
|
1369 | # review status | |
|
1370 | # review status, must be always present | |
|
1370 | 1371 | review_status = pull_request.calculated_review_status() |
|
1372 | merge_check.review_status = review_status | |
|
1373 | ||
|
1371 | 1374 | status_approved = review_status == ChangesetStatus.STATUS_APPROVED |
|
1372 | 1375 | if not status_approved: |
|
1373 | 1376 | log.debug("MergeCheck: cannot merge, approval is pending.") |
@@ -174,6 +174,19 b' input[type="button"] {' | |||
|
174 | 174 | } |
|
175 | 175 | } |
|
176 | 176 | |
|
177 | .btn-approved-status { | |
|
178 | .border ( @border-thickness, @alert1 ); | |
|
179 | background-color: white; | |
|
180 | color: @alert1; | |
|
181 | ||
|
182 | } | |
|
183 | ||
|
184 | .btn-rejected-status { | |
|
185 | .border ( @border-thickness, @alert2 ); | |
|
186 | background-color: white; | |
|
187 | color: @alert2; | |
|
188 | } | |
|
189 | ||
|
177 | 190 | .btn-sm, |
|
178 | 191 | .btn-mini, |
|
179 | 192 | .field-sm .btn { |
@@ -384,6 +384,10 b' form.comment-form {' | |||
|
384 | 384 | float: right; |
|
385 | 385 | display: inline-block; |
|
386 | 386 | } |
|
387 | ||
|
388 | .action-buttons-extra { | |
|
389 | display: inline-block; | |
|
390 | } | |
|
387 | 391 | } |
|
388 | 392 | |
|
389 | 393 | .comment-form { |
@@ -92,6 +92,8 b' var bindToggleButtons = function() {' | |||
|
92 | 92 | this.resolvesId = null; |
|
93 | 93 | this.resolvesActionId = null; |
|
94 | 94 | |
|
95 | this.closesPr = '#close_pull_request'; | |
|
96 | ||
|
95 | 97 | this.cmBox = this.withLineNo('#text'); |
|
96 | 98 | this.cm = initCommentBoxCodeMirror(this, this.cmBox, this.initAutocompleteActions); |
|
97 | 99 | |
@@ -175,6 +177,11 b' var bindToggleButtons = function() {' | |||
|
175 | 177 | this.getResolvesId = function() { |
|
176 | 178 | return $(this.submitForm).find(this.resolvesId).val() || null; |
|
177 | 179 | }; |
|
180 | ||
|
181 | this.getClosePr = function() { | |
|
182 | return $(this.submitForm).find(this.closesPr).val() || null; | |
|
183 | }; | |
|
184 | ||
|
178 | 185 | this.markCommentResolved = function(resolvedCommentId){ |
|
179 | 186 | $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolved').show(); |
|
180 | 187 | $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolve').hide(); |
@@ -209,6 +216,7 b' var bindToggleButtons = function() {' | |||
|
209 | 216 | }); |
|
210 | 217 | $(this.submitForm).find(this.statusChange).on('change', function() { |
|
211 | 218 | var status = self.getCommentStatus(); |
|
219 | ||
|
212 | 220 | if (status && !self.isInline()) { |
|
213 | 221 | $(self.submitButton).prop('disabled', false); |
|
214 | 222 | } |
@@ -236,6 +244,8 b' var bindToggleButtons = function() {' | |||
|
236 | 244 | // destroy the resolve action |
|
237 | 245 | $(this.resolvesId).parent().remove(); |
|
238 | 246 | } |
|
247 | // reset closingPR flag | |
|
248 | $('.close-pr-input').remove(); | |
|
239 | 249 | |
|
240 | 250 | $(this.statusChange).select2('readonly', false); |
|
241 | 251 | }; |
@@ -284,6 +294,7 b' var bindToggleButtons = function() {' | |||
|
284 | 294 | var status = self.getCommentStatus(); |
|
285 | 295 | var commentType = self.getCommentType(); |
|
286 | 296 | var resolvesCommentId = self.getResolvesId(); |
|
297 | var closePullRequest = self.getClosePr(); | |
|
287 | 298 | |
|
288 | 299 | if (text === "" && !status) { |
|
289 | 300 | return; |
@@ -300,10 +311,15 b' var bindToggleButtons = function() {' | |||
|
300 | 311 | 'comment_type': commentType, |
|
301 | 312 | 'csrf_token': CSRF_TOKEN |
|
302 | 313 | }; |
|
303 | if (resolvesCommentId){ | |
|
314 | ||
|
315 | if (resolvesCommentId) { | |
|
304 | 316 | postData['resolves_comment_id'] = resolvesCommentId; |
|
305 | 317 | } |
|
306 | 318 | |
|
319 | if (closePullRequest) { | |
|
320 | postData['close_pull_request'] = true; | |
|
321 | } | |
|
322 | ||
|
307 | 323 | var submitSuccessCallback = function(o) { |
|
308 | 324 | // reload page if we change status for single commit. |
|
309 | 325 | if (status && self.commitId) { |
@@ -359,6 +375,7 b' var bindToggleButtons = function() {' | |||
|
359 | 375 | // submit button, but only on Main form, isInline means inline |
|
360 | 376 | submitState = false |
|
361 | 377 | } |
|
378 | ||
|
362 | 379 | $(this.submitButton).prop('disabled', submitState); |
|
363 | 380 | if (submitEvent) { |
|
364 | 381 | $(this.submitButton).val(_gettext('Submitting...')); |
@@ -385,6 +385,11 b'' | |||
|
385 | 385 | ${_('Cancel')} |
|
386 | 386 | </button> |
|
387 | 387 | % endif |
|
388 | ||
|
389 | % if form_type != 'inline': | |
|
390 | <div class="action-buttons-extra"></div> | |
|
391 | % endif | |
|
392 | ||
|
388 | 393 | ${h.submit('save', _('Comment'), class_='btn btn-success comment-button-input')} |
|
389 | 394 | |
|
390 | 395 | </div> |
@@ -1,7 +1,6 b'' | |||
|
1 | 1 | |
|
2 | 2 | <div class="pull-request-wrap"> |
|
3 | 3 | |
|
4 | ||
|
5 | 4 | % if c.pr_merge_possible: |
|
6 | 5 | <h2 class="merge-status"> |
|
7 | 6 | <span class="merge-icon success"><i class="icon-true"></i></span> |
@@ -46,5 +45,19 b'' | |||
|
46 | 45 | <input type="submit" value="${_('Login to Merge this Pull Request')}" class="btn disabled" disabled="disabled"> |
|
47 | 46 | % endif |
|
48 | 47 | </div> |
|
48 | ||
|
49 | % if c.allowed_to_close: | |
|
50 | ## close PR action, injected later next to COMMENT button | |
|
51 | <div id="close-pull-request-action" style="display: none"> | |
|
52 | % if c.pull_request_review_status == c.REVIEW_STATUS_APPROVED: | |
|
53 | <a class="btn btn-approved-status" href="#close-as-approved" onclick="closePullRequest('${c.REVIEW_STATUS_APPROVED}'); return false;"> | |
|
54 | ${_('Close with status {}').format(h.commit_status_lbl(c.REVIEW_STATUS_APPROVED))} | |
|
55 | </a> | |
|
56 | % else: | |
|
57 | <a class="btn btn-rejected-status" href="#close-as-rejected" onclick="closePullRequest('${c.REVIEW_STATUS_REJECTED}'); return false;"> | |
|
58 | ${_('Close with status {}').format(h.commit_status_lbl(c.REVIEW_STATUS_REJECTED))} | |
|
59 | </a> | |
|
60 | % endif | |
|
61 | </div> | |
|
62 | % endif | |
|
49 | 63 | </div> |
|
50 |
@@ -694,13 +694,33 b'' | |||
|
694 | 694 | refreshMergeChecks = function(){ |
|
695 | 695 | var loadUrl = "${h.url.current(merge_checks=1)}"; |
|
696 | 696 | $('.pull-request-merge').css('opacity', 0.3); |
|
697 | $('.action-buttons-extra').css('opacity', 0.3); | |
|
698 | ||
|
697 | 699 | $('.pull-request-merge').load( |
|
698 | loadUrl,function() { | |
|
700 | loadUrl, function() { | |
|
699 | 701 | $('.pull-request-merge').css('opacity', 1); |
|
702 | ||
|
703 | $('.action-buttons-extra').css('opacity', 1); | |
|
704 | injectCloseAction(); | |
|
700 | 705 | } |
|
701 | 706 | ); |
|
702 | 707 | }; |
|
703 | 708 | |
|
709 | injectCloseAction = function() { | |
|
710 | var closeAction = $('#close-pull-request-action').html(); | |
|
711 | var $actionButtons = $('.action-buttons-extra'); | |
|
712 | // clear the action before | |
|
713 | $actionButtons.html(""); | |
|
714 | $actionButtons.html(closeAction); | |
|
715 | }; | |
|
716 | ||
|
717 | closePullRequest = function (status) { | |
|
718 | // inject closing flag | |
|
719 | $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">'); | |
|
720 | $(generalCommentForm.statusChange).select2("val", status).trigger('change'); | |
|
721 | $(generalCommentForm.submitForm).submit(); | |
|
722 | }; | |
|
723 | ||
|
704 | 724 | $('#show-outdated-comments').on('click', function(e){ |
|
705 | 725 | var button = $(this); |
|
706 | 726 | var outdated = $('.comment-outdated'); |
@@ -786,6 +806,8 b'' | |||
|
786 | 806 | window.commentFormGlobalSubmitSuccessCallback = function(){ |
|
787 | 807 | refreshMergeChecks(); |
|
788 | 808 | }; |
|
809 | // initial injection | |
|
810 | injectCloseAction(); | |
|
789 | 811 | |
|
790 | 812 | }) |
|
791 | 813 | </script> |
@@ -115,8 +115,10 b' class TestPullrequestsController:' | |||
|
115 | 115 | repo_name=pull_request.target_repo.scm_instance().name, |
|
116 | 116 | pull_request_id=str(pull_request.pull_request_id))) |
|
117 | 117 | |
|
118 |
|
|
|
119 | assert 'value="forced_closed"' in response | |
|
118 | response.mustcontain('Server-side pull request merging is disabled.') | |
|
119 | ||
|
120 | assert_response = response.assert_response() | |
|
121 | assert_response.one_element_exists('#close-pull-request-action') | |
|
120 | 122 | |
|
121 | 123 | def test_show_invalid_commit_id(self, pr_util): |
|
122 | 124 | # Simulating invalid revisions which will cause a lookup error |
@@ -242,10 +244,9 b' class TestPullrequestsController:' | |||
|
242 | 244 | repo_name=pull_request.target_repo.scm_instance().name, |
|
243 | 245 | pull_request_id=str(pull_request_id)), |
|
244 | 246 | params={ |
|
245 | 'changeset_status': | |
|
246 | ChangesetStatus.STATUS_APPROVED + '_closed', | |
|
247 |
' |
|
|
248 | 'text': '', | |
|
247 | 'changeset_status': ChangesetStatus.STATUS_APPROVED, | |
|
248 | 'close_pull_request': '1', | |
|
249 | 'text': 'Closing a PR', | |
|
249 | 250 | 'csrf_token': csrf_token}, |
|
250 | 251 | status=302) |
|
251 | 252 | |
@@ -257,6 +258,14 b' class TestPullrequestsController:' | |||
|
257 | 258 | .all() |
|
258 | 259 | assert len(journal) == 1 |
|
259 | 260 | |
|
261 | pull_request = PullRequest.get(pull_request_id) | |
|
262 | assert pull_request.is_closed() | |
|
263 | ||
|
264 | # check only the latest status, not the review status | |
|
265 | status = ChangesetStatusModel().get_status( | |
|
266 | pull_request.source_repo, pull_request=pull_request) | |
|
267 | assert status == ChangesetStatus.STATUS_APPROVED | |
|
268 | ||
|
260 | 269 | def test_reject_and_close_pull_request(self, pr_util, csrf_token): |
|
261 | 270 | pull_request = pr_util.create_pull_request() |
|
262 | 271 | pull_request_id = pull_request.pull_request_id |
@@ -291,7 +300,8 b' class TestPullrequestsController:' | |||
|
291 | 300 | repo_name=pull_request.target_repo.scm_instance().name, |
|
292 | 301 | pull_request_id=str(pull_request_id)), |
|
293 | 302 | params={ |
|
294 |
'changeset_status': ' |
|
|
303 | 'changeset_status': 'rejected', | |
|
304 | 'close_pull_request': '1', | |
|
295 | 305 | 'csrf_token': csrf_token}, |
|
296 | 306 | status=302) |
|
297 | 307 |
General Comments 0
You need to be logged in to leave comments.
Login now