Skip to content

Commit 5b972b8

Browse files
committed
Prevent re-review and dismiss review actions on merged PRs
1 parent 475b6e8 commit 5b972b8

File tree

5 files changed

+85
-5
lines changed

5 files changed

+85
-5
lines changed

models/issues/review.go

+44-3
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,23 @@ func (err ErrNotValidReviewRequest) Unwrap() error {
6666
return util.ErrInvalidArgument
6767
}
6868

69+
// ErrReviewRequestOnClosedPR represents an error when an user tries to request a re-review on a closed or merged PR.
70+
type ErrReviewRequestOnClosedPR struct{}
71+
72+
// IsErrReviewRequestOnClosedPR checks if an error is an ErrReviewRequestOnClosedPR.
73+
func IsErrReviewRequestOnClosedPR(err error) bool {
74+
_, ok := err.(ErrReviewRequestOnClosedPR)
75+
return ok
76+
}
77+
78+
func (err ErrReviewRequestOnClosedPR) Error() string {
79+
return "cannot request a re-review on a merged PR"
80+
}
81+
82+
func (err ErrReviewRequestOnClosedPR) Unwrap() error {
83+
return util.ErrPermissionDenied
84+
}
85+
6986
// ReviewType defines the sort of feedback a review gives
7087
type ReviewType int
7188

@@ -618,9 +635,33 @@ func AddReviewRequest(ctx context.Context, issue *Issue, reviewer, doer *user_mo
618635
return nil, err
619636
}
620637

621-
// skip it when reviewer hase been request to review
622-
if review != nil && review.Type == ReviewTypeRequest {
623-
return nil, committer.Commit() // still commit the transaction, or committer.Close() will rollback it, even if it's a reused transaction.
638+
if review != nil {
639+
// NOTE: in case of failure we still commit the transaction here, else committer.Close() will rollback it,
640+
// even if it's a reused transaction.
641+
642+
// skip it when reviewer hase been request to review
643+
if review.Type == ReviewTypeRequest {
644+
return nil, committer.Commit()
645+
}
646+
647+
if issue.IsClosed {
648+
var err error = ErrReviewRequestOnClosedPR{}
649+
if err2 := committer.Commit(); err2 != nil {
650+
err = err2
651+
}
652+
return nil, err
653+
}
654+
655+
if err := issue.LoadPullRequest(ctx); err != nil {
656+
return nil, err
657+
}
658+
if issue.PullRequest.HasMerged {
659+
var err error = ErrReviewRequestOnClosedPR{}
660+
if err2 := committer.Commit(); err2 != nil {
661+
err = err2
662+
}
663+
return nil, err
664+
}
624665
}
625666

626667
// if the reviewer is an official reviewer,

routers/web/repo/issue.go

+4
Original file line numberDiff line numberDiff line change
@@ -2498,6 +2498,10 @@ func UpdatePullReviewRequest(ctx *context.Context) {
24982498

24992499
_, err = issue_service.ReviewRequest(ctx, issue, ctx.Doer, reviewer, action == "attach")
25002500
if err != nil {
2501+
if issues_model.IsErrReviewRequestOnClosedPR(err) {
2502+
ctx.Status(http.StatusForbidden)
2503+
return
2504+
}
25012505
ctx.ServerError("ReviewRequest", err)
25022506
return
25032507
}

routers/web/repo/pull_review.go

+4
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,10 @@ func DismissReview(ctx *context.Context) {
277277
form := web.GetForm(ctx).(*forms.DismissReviewForm)
278278
comm, err := pull_service.DismissReview(ctx, form.ReviewID, ctx.Repo.Repository.ID, form.Message, ctx.Doer, true, true)
279279
if err != nil {
280+
if pull_service.IsErrDismissRequestOnClosedPR(err) {
281+
ctx.Status(http.StatusForbidden)
282+
return
283+
}
280284
ctx.ServerError("pull_service.DismissReview", err)
281285
return
282286
}

services/pull/review.go

+31
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,29 @@ import (
2020
"code.gitea.io/gitea/modules/log"
2121
"code.gitea.io/gitea/modules/optional"
2222
"code.gitea.io/gitea/modules/setting"
23+
"code.gitea.io/gitea/modules/util"
2324
notify_service "code.gitea.io/gitea/services/notify"
2425
)
2526

2627
var notEnoughLines = regexp.MustCompile(`fatal: file .* has only \d+ lines?`)
2728

29+
// ErrDismissRequestOnClosedPR represents an error when an user tries to dismiss a review associated to a closed or merged PR.
30+
type ErrDismissRequestOnClosedPR struct{}
31+
32+
// IsErrDismissRequestOnClosedPR checks if an error is an ErrDismissRequestOnClosedPR.
33+
func IsErrDismissRequestOnClosedPR(err error) bool {
34+
_, ok := err.(ErrDismissRequestOnClosedPR)
35+
return ok
36+
}
37+
38+
func (err ErrDismissRequestOnClosedPR) Error() string {
39+
return "can't dismiss a review associated to a merged PR"
40+
}
41+
42+
func (err ErrDismissRequestOnClosedPR) Unwrap() error {
43+
return util.ErrPermissionDenied
44+
}
45+
2846
// checkInvalidation checks if the line of code comment got changed by another commit.
2947
// If the line got changed the comment is going to be invalidated.
3048
func checkInvalidation(ctx context.Context, c *issues_model.Comment, doer *user_model.User, repo *git.Repository, branch string) error {
@@ -382,6 +400,19 @@ func DismissReview(ctx context.Context, reviewID, repoID int64, message string,
382400
return nil, fmt.Errorf("reviews's repository is not the same as the one we expect")
383401
}
384402

403+
issue := review.Issue
404+
405+
if issue.IsClosed {
406+
return nil, ErrDismissRequestOnClosedPR{}
407+
}
408+
409+
if err := issue.LoadPullRequest(ctx); err != nil {
410+
return nil, err
411+
}
412+
if issue.PullRequest.HasMerged {
413+
return nil, ErrDismissRequestOnClosedPR{}
414+
}
415+
385416
if err := issues_model.DismissReview(ctx, review, isDismiss); err != nil {
386417
return nil, err
387418
}

templates/repo/issue/view_content/sidebar.tmpl

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
{{end}}
6060
</div>
6161
<div class="tw-flex tw-items-center tw-gap-2">
62-
{{if (and $.Permission.IsAdmin (or (eq .Review.Type 1) (eq .Review.Type 3)) (not $.Issue.IsClosed))}}
62+
{{if (and $.Permission.IsAdmin (or (eq .Review.Type 1) (eq .Review.Type 3)) (not $.Issue.IsClosed) (not $.Issue.PullRequest.HasMerged))}}
6363
<a href="#" class="ui muted icon tw-flex tw-items-center show-modal" data-tooltip-content="{{ctx.Locale.Tr "repo.issues.dismiss_review"}}" data-modal="#dismiss-review-modal-{{.Review.ID}}">
6464
{{svg "octicon-x" 20}}
6565
</a>
@@ -91,7 +91,7 @@
9191
{{svg "octicon-hourglass" 16}}
9292
</span>
9393
{{end}}
94-
{{if .CanChange}}
94+
{{if and .CanChange (or .Checked (not $.Issue.PullRequest.HasMerged))}}
9595
<a href="#" class="ui muted icon re-request-review{{if .Checked}} checked{{end}}" data-tooltip-content="{{if .Checked}}{{ctx.Locale.Tr "repo.issues.remove_request_review"}}{{else}}{{ctx.Locale.Tr "repo.issues.re_request_review"}}{{end}}" data-issue-id="{{$.Issue.ID}}" data-id="{{.ItemID}}" data-update-url="{{$.RepoLink}}/issues/request_review">{{if .Checked}}{{svg "octicon-trash"}}{{else}}{{svg "octicon-sync"}}{{end}}</a>
9696
{{end}}
9797
{{svg (printf "octicon-%s" .Review.Type.Icon) 16 (printf "text %s" (.Review.HTMLTypeColorName))}}

0 commit comments

Comments
 (0)