summaryrefslogtreecommitdiffstats
path: root/routers
diff options
context:
space:
mode:
authorDavid Svantesson <davidsvantesson@gmail.com>2020-01-11 08:29:34 +0100
committertechknowlogick <techknowlogick@gitea.io>2020-01-11 02:29:34 -0500
commit32fb813133c74ddc3af1964e81fff72fea4f24f1 (patch)
tree31eec446f12e448d7cf398db43620606e9704ff1 /routers
parent4d06d10dbafe7cfd404889b636d8e243058ee96f (diff)
downloadgitea-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.go33
-rw-r--r--routers/private/hook.go51
-rw-r--r--routers/repo/issue.go24
-rw-r--r--routers/repo/pull.go32
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() {