diff options
author | David Svantesson <davidsvantesson@gmail.com> | 2020-01-11 08:29:34 +0100 |
---|---|---|
committer | techknowlogick <techknowlogick@gitea.io> | 2020-01-11 02:29:34 -0500 |
commit | 32fb813133c74ddc3af1964e81fff72fea4f24f1 (patch) | |
tree | 31eec446f12e448d7cf398db43620606e9704ff1 /routers | |
parent | 4d06d10dbafe7cfd404889b636d8e243058ee96f (diff) | |
download | gitea-32fb813133c74ddc3af1964e81fff72fea4f24f1.tar.gz gitea-32fb813133c74ddc3af1964e81fff72fea4f24f1.zip |
Allow repo admin to merge PR regardless of review status (#9611)
* Allow repo admin to merge even if review is not ok.
Diffstat (limited to 'routers')
-rw-r--r-- | routers/api/v1/repo/pull.go | 33 | ||||
-rw-r--r-- | routers/private/hook.go | 51 | ||||
-rw-r--r-- | routers/repo/issue.go | 24 | ||||
-rw-r--r-- | routers/repo/pull.go | 32 |
4 files changed, 108 insertions, 32 deletions
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 8e6677116d..6b643371e5 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -600,22 +600,45 @@ func MergePullRequest(ctx *context.APIContext, form auth.MergePullRequestForm) { return } - if !pr.CanAutoMerge() || pr.HasMerged || pr.IsWorkInProgress() { - ctx.Status(http.StatusMethodNotAllowed) + perm, err := models.GetUserRepoPermission(ctx.Repo.Repository, ctx.User) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err) return } - isPass, err := pull_service.IsPullCommitStatusPass(pr) + allowedMerge, err := pull_service.IsUserAllowedToMerge(pr, perm, ctx.User) if err != nil { - ctx.Error(http.StatusInternalServerError, "IsPullCommitStatusPass", err) + ctx.Error(http.StatusInternalServerError, "IsUSerAllowedToMerge", err) + return + } + if !allowedMerge { + ctx.Error(http.StatusMethodNotAllowed, "Merge", "User not allowed to merge PR") return } - if !isPass && !ctx.IsUserRepoAdmin() { + if !pr.CanAutoMerge() || pr.HasMerged || pr.IsWorkInProgress() { ctx.Status(http.StatusMethodNotAllowed) return } + if err := pull_service.CheckPRReadyToMerge(pr); err != nil { + if !models.IsErrNotAllowedToMerge(err) { + ctx.Error(http.StatusInternalServerError, "CheckPRReadyToMerge", err) + return + } + if form.ForceMerge != nil && *form.ForceMerge { + if isRepoAdmin, err := models.IsUserRepoAdmin(pr.BaseRepo, ctx.User); err != nil { + ctx.Error(http.StatusInternalServerError, "IsUserRepoAdmin", err) + return + } else if !isRepoAdmin { + ctx.Error(http.StatusMethodNotAllowed, "Merge", "Only repository admin can merge if not all checks are ok (force merge)") + } + } else { + ctx.Error(http.StatusMethodNotAllowed, "PR is not ready to be merged", err) + return + } + } + if len(form.Do) == 0 { form.Do = string(models.MergeStyleMerge) } diff --git a/routers/private/hook.go b/routers/private/hook.go index c1e283f357..b4626fddf4 100644 --- a/routers/private/hook.go +++ b/routers/private/hook.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/repofiles" "code.gitea.io/gitea/modules/util" + pull_service "code.gitea.io/gitea/services/pull" "gitea.com/macaron/macaron" ) @@ -98,6 +99,7 @@ func HookPreReceive(ctx *macaron.Context, opts private.HookOptions) { canPush = protectBranch.CanUserPush(opts.UserID) } if !canPush && opts.ProtectedBranchID > 0 { + // Manual merge pr, err := models.GetPullRequestByID(opts.ProtectedBranchID) if err != nil { log.Error("Unable to get PullRequest %d Error: %v", opts.ProtectedBranchID, err) @@ -106,24 +108,55 @@ func HookPreReceive(ctx *macaron.Context, opts private.HookOptions) { }) return } - if !protectBranch.HasEnoughApprovals(pr) { - log.Warn("Forbidden: User %d cannot push to protected branch: %s in %-v and pr #%d does not have enough approvals", opts.UserID, branchName, repo, pr.Index) - ctx.JSON(http.StatusForbidden, map[string]interface{}{ - "err": fmt.Sprintf("protected branch %s can not be pushed to and pr #%d does not have enough approvals", branchName, opts.ProtectedBranchID), + user, err := models.GetUserByID(opts.UserID) + if err != nil { + log.Error("Unable to get User id %d Error: %v", opts.UserID, err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "err": fmt.Sprintf("Unable to get User id %d Error: %v", opts.UserID, err), + }) + return + } + perm, err := models.GetUserRepoPermission(repo, user) + if err != nil { + log.Error("Unable to get Repo permission of repo %s/%s of User %s", repo.OwnerName, repo.Name, user.Name, err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "err": fmt.Sprintf("Unable to get Repo permission of repo %s/%s of User %s: %v", repo.OwnerName, repo.Name, user.Name, err), }) return } - if protectBranch.MergeBlockedByRejectedReview(pr) { - log.Warn("Forbidden: User %d cannot push to protected branch: %s in %-v and pr #%d has requested changes", opts.UserID, branchName, repo, pr.Index) + allowedMerge, err := pull_service.IsUserAllowedToMerge(pr, perm, user) + if err != nil { + log.Error("Error calculating if allowed to merge: %v", err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "err": fmt.Sprintf("Error calculating if allowed to merge: %v", err), + }) + return + } + if !allowedMerge { + log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v and is not allowed to merge pr #%d", opts.UserID, branchName, repo, pr.Index) ctx.JSON(http.StatusForbidden, map[string]interface{}{ - "err": fmt.Sprintf("protected branch %s can not be pushed to and pr #%d has requested changes", branchName, opts.ProtectedBranchID), + "err": fmt.Sprintf("Not allowed to push to protected branch %s", branchName), }) return } + // Manual merge only allowed if PR is ready (even if admin) + if err := pull_service.CheckPRReadyToMerge(pr); err != nil { + if models.IsErrNotAllowedToMerge(err) { + log.Warn("Forbidden: User %d is not allowed push to protected branch %s in %-v and pr #%d is not ready to be merged: %s", opts.UserID, branchName, repo, pr.Index, err.Error()) + ctx.JSON(http.StatusForbidden, map[string]interface{}{ + "err": fmt.Sprintf("Not allowed to push to protected branch %s and pr #%d is not ready to be merged: %s", branchName, opts.ProtectedBranchID, err.Error()), + }) + return + } + log.Error("Unable to check if mergable: protected branch %s in %-v and pr #%d. Error: %v", opts.UserID, branchName, repo, pr.Index, err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "err": fmt.Sprintf("Unable to get status of pull request %d. Error: %v", opts.ProtectedBranchID, err), + }) + } } else if !canPush { - log.Warn("Forbidden: User %d cannot push to protected branch: %s in %-v", opts.UserID, branchName, repo) + log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v", opts.UserID, branchName, repo) ctx.JSON(http.StatusForbidden, map[string]interface{}{ - "err": fmt.Sprintf("protected branch %s can not be pushed to", branchName), + "err": fmt.Sprintf("Not allowed to push to protected branch %s", branchName), }) return } diff --git a/routers/repo/issue.go b/routers/repo/issue.go index 0a78e06b41..ce3eb5bd2c 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -903,6 +903,7 @@ func ViewIssue(ctx *context.Context) { pull := issue.PullRequest pull.Issue = issue canDelete := false + ctx.Data["AllowMerge"] = false if ctx.IsSigned { if err := pull.GetHeadRepo(); err != nil { @@ -923,6 +924,20 @@ func ViewIssue(ctx *context.Context) { } } } + + if err := pull.GetBaseRepo(); err != nil { + log.Error("GetBaseRepo: %v", err) + } + perm, err := models.GetUserRepoPermission(pull.BaseRepo, ctx.User) + if err != nil { + ctx.ServerError("GetUserRepoPermission", err) + return + } + ctx.Data["AllowMerge"], err = pull_service.IsUserAllowedToMerge(pull, perm, ctx.User) + if err != nil { + ctx.ServerError("IsUserAllowedToMerge", err) + return + } } prUnit, err := repo.GetUnit(models.UnitTypePullRequests) @@ -932,15 +947,6 @@ func ViewIssue(ctx *context.Context) { } prConfig := prUnit.PullRequestsConfig() - ctx.Data["AllowMerge"] = ctx.Repo.CanWrite(models.UnitTypeCode) - if err := pull.CheckUserAllowedToMerge(ctx.User); err != nil { - if !models.IsErrNotAllowedToMerge(err) { - ctx.ServerError("CheckUserAllowedToMerge", err) - return - } - ctx.Data["AllowMerge"] = false - } - // Check correct values and select default if ms, ok := ctx.Data["MergeStyle"].(models.MergeStyle); !ok || !prConfig.IsMergeStyleAllowed(ms) { diff --git a/routers/repo/pull.go b/routers/repo/pull.go index 93e98e0c33..901ab48856 100644 --- a/routers/repo/pull.go +++ b/routers/repo/pull.go @@ -600,6 +600,16 @@ func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) { pr := issue.PullRequest + allowedMerge, err := pull_service.IsUserAllowedToMerge(pr, ctx.Repo.Permission, ctx.User) + if err != nil { + ctx.ServerError("IsUserAllowedToMerge", err) + return + } + if !allowedMerge { + ctx.NotFound("MergePullRequest", nil) + return + } + if !pr.CanAutoMerge() || pr.HasMerged { ctx.NotFound("MergePullRequest", nil) return @@ -611,15 +621,19 @@ func MergePullRequest(ctx *context.Context, form auth.MergePullRequestForm) { return } - isPass, err := pull_service.IsPullCommitStatusPass(pr) - if err != nil { - ctx.ServerError("IsPullCommitStatusPass", err) - return - } - if !isPass && !ctx.IsUserRepoAdmin() { - ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_status_check")) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index)) - return + if err := pull_service.CheckPRReadyToMerge(pr); err != nil { + if !models.IsErrNotAllowedToMerge(err) { + ctx.ServerError("Merge PR status", err) + return + } + if isRepoAdmin, err := models.IsUserRepoAdmin(pr.BaseRepo, ctx.User); err != nil { + ctx.ServerError("IsUserRepoAdmin", err) + return + } else if !isRepoAdmin { + ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready")) + ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(pr.Index)) + return + } } if ctx.HasError() { |