This commit is contained in:
@@ -4,41 +4,41 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
project_model "code.gitea.io/gitea/models/project"
|
||||
pull_model "code.gitea.io/gitea/models/pull"
|
||||
"code.gitea.io/gitea/models/renderhelper"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/emoji"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/templates/vars"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/context/upload"
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
user_service "code.gitea.io/gitea/services/user"
|
||||
activities_model "gitea.dev/models/activities"
|
||||
asymkey_model "gitea.dev/models/asymkey"
|
||||
"gitea.dev/models/db"
|
||||
git_model "gitea.dev/models/git"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
"gitea.dev/models/organization"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
project_model "gitea.dev/models/project"
|
||||
"gitea.dev/models/renderhelper"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unit"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/emoji"
|
||||
"gitea.dev/modules/git"
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/markup"
|
||||
"gitea.dev/modules/markup/markdown"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/svg"
|
||||
"gitea.dev/modules/templates/vars"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web/middleware"
|
||||
asymkey_service "gitea.dev/services/asymkey"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/context/upload"
|
||||
issue_service "gitea.dev/services/issue"
|
||||
pull_service "gitea.dev/services/pull"
|
||||
user_service "gitea.dev/services/user"
|
||||
)
|
||||
|
||||
// roleDescriptor returns the role descriptor for a comment in/with the given repo, poster and issue
|
||||
@@ -118,19 +118,6 @@ func roleDescriptor(ctx *context.Context, repo *repo_model.Repository, poster *u
|
||||
return roleDesc, nil
|
||||
}
|
||||
|
||||
func getBranchData(ctx *context.Context, issue *issues_model.Issue) {
|
||||
ctx.Data["BaseBranch"] = nil
|
||||
ctx.Data["HeadBranch"] = nil
|
||||
ctx.Data["HeadUserName"] = nil
|
||||
ctx.Data["BaseName"] = ctx.Repo.Repository.OwnerName
|
||||
if issue.IsPull {
|
||||
pull := issue.PullRequest
|
||||
ctx.Data["BaseBranch"] = pull.BaseBranch
|
||||
ctx.Data["HeadBranch"] = pull.HeadBranch
|
||||
ctx.Data["HeadUserName"] = pull.MustHeadUserName(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// checkBlockedByIssues return canRead and notPermitted
|
||||
func checkBlockedByIssues(ctx *context.Context, blockers []*issues_model.DependencyInfo) (canRead, notPermitted []*issues_model.DependencyInfo) {
|
||||
repoPerms := make(map[int64]access_model.Permission)
|
||||
@@ -350,7 +337,7 @@ func ViewIssue(ctx *context.Context) {
|
||||
ctx.Data["NewIssueChooseTemplate"] = issue_service.HasTemplatesOrContactLinks(ctx.Repo.Repository, ctx.Repo.GitRepo)
|
||||
}
|
||||
|
||||
ctx.Data["IsProjectsEnabled"] = ctx.Repo.CanRead(unit.TypeProjects)
|
||||
ctx.Data["IsProjectsEnabled"] = ctx.Repo.Permission.CanRead(unit.TypeProjects)
|
||||
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
|
||||
upload.AddUploadContext(ctx, "comment")
|
||||
|
||||
@@ -380,6 +367,7 @@ func ViewIssue(ctx *context.Context) {
|
||||
}
|
||||
pageMetaData.LabelsData.SetSelectedLabels(issue.Labels)
|
||||
|
||||
prViewInfo := newPullRequestViewInfo()
|
||||
prepareFuncs := []func(*context.Context, *issues_model.Issue){
|
||||
prepareIssueViewContent,
|
||||
prepareIssueViewCommentsAndSidebarParticipants,
|
||||
@@ -387,10 +375,13 @@ func ViewIssue(ctx *context.Context) {
|
||||
prepareIssueViewSidebarTimeTracker,
|
||||
prepareIssueViewSidebarDependency,
|
||||
prepareIssueViewSidebarPin,
|
||||
func(ctx *context.Context, issue *issues_model.Issue) { preparePullViewPullInfo(ctx, issue) },
|
||||
preparePullViewReviewAndMerge,
|
||||
}
|
||||
|
||||
if issue.IsPull {
|
||||
prepareFuncs = append(prepareFuncs,
|
||||
prViewInfo.prepareViewInfo,
|
||||
prViewInfo.prepareMergeBox,
|
||||
)
|
||||
}
|
||||
for _, prepareFunc := range prepareFuncs {
|
||||
prepareFunc(ctx, issue)
|
||||
if ctx.Written() {
|
||||
@@ -403,16 +394,16 @@ func ViewIssue(ctx *context.Context) {
|
||||
if issue.PullRequest.HasMerged {
|
||||
ctx.Data["DisableStatusChange"] = issue.PullRequest.HasMerged
|
||||
} else {
|
||||
ctx.Data["DisableStatusChange"] = ctx.Data["IsPullRequestBroken"] == true && issue.IsClosed
|
||||
ctx.Data["DisableStatusChange"] = prViewInfo.IsPullRequestBroken && issue.IsClosed
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["Reference"] = issue.Ref
|
||||
ctx.Data["SignInLink"] = middleware.RedirectLinkUserLogin(ctx.Req)
|
||||
ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.Doer.ID)
|
||||
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
|
||||
ctx.Data["HasProjectsWritePermission"] = ctx.Repo.CanWrite(unit.TypeProjects)
|
||||
ctx.Data["IsRepoAdmin"] = ctx.IsSigned && (ctx.Repo.IsAdmin() || ctx.Doer.IsAdmin)
|
||||
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull)
|
||||
ctx.Data["HasProjectsWritePermission"] = ctx.Repo.Permission.CanWrite(unit.TypeProjects)
|
||||
ctx.Data["IsRepoAdmin"] = ctx.IsSigned && (ctx.Repo.Permission.IsAdmin() || ctx.Doer.IsAdmin)
|
||||
ctx.Data["LockReasons"] = setting.Repository.Issue.LockReasons
|
||||
ctx.Data["RefEndName"] = git.RefName(issue.Ref).ShortName()
|
||||
|
||||
@@ -427,8 +418,8 @@ func ViewIssue(ctx *context.Context) {
|
||||
return user_service.CanBlockUser(ctx, ctx.Doer, blocker, blockee)
|
||||
}
|
||||
|
||||
if issue.PullRequest != nil && !issue.PullRequest.IsChecking() && !setting.IsProd {
|
||||
ctx.Data["PullMergeBoxReloadingInterval"] = 1 // in dev env, force using the reloading logic to make sure it won't break
|
||||
if !setting.IsProd && issue.PullRequest != nil && prViewInfo.MergeBoxData != nil && prViewInfo.MergeBoxData.ReloadingInterval == 0 {
|
||||
prViewInfo.MergeBoxData.ReloadingInterval = 1 // in dev env, force using the reloading logic to make sure it won't break
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, tplIssueView)
|
||||
@@ -443,20 +434,27 @@ func ViewPullMergeBox(ctx *context.Context) {
|
||||
ctx.NotFound(nil)
|
||||
return
|
||||
}
|
||||
preparePullViewPullInfo(ctx, issue)
|
||||
preparePullViewReviewAndMerge(ctx, issue)
|
||||
prViewInfo := newPullRequestViewInfo()
|
||||
prViewInfo.prepareViewInfo(ctx, issue)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
prViewInfo.prepareMergeBox(ctx, issue)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
ctx.Data["PullMergeBoxReloading"] = issue.PullRequest.IsChecking()
|
||||
|
||||
// TODO: it should use a dedicated struct to render the pull merge box, to make sure all data is prepared correctly
|
||||
ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.Doer.ID)
|
||||
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
|
||||
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.Permission.CanWriteIssuesOrPulls(issue.IsPull)
|
||||
ctx.HTML(http.StatusOK, tplPullMergeBox)
|
||||
}
|
||||
|
||||
func prepareIssueViewSidebarDependency(ctx *context.Context, issue *issues_model.Issue) {
|
||||
if issue.IsPull && !ctx.Repo.CanRead(unit.TypeIssues) {
|
||||
if issue.IsPull && !ctx.Repo.Permission.CanRead(unit.TypeIssues) {
|
||||
ctx.Data["IssueDependencySearchType"] = "pulls"
|
||||
} else if !issue.IsPull && !ctx.Repo.CanRead(unit.TypePullRequests) {
|
||||
} else if !issue.IsPull && !ctx.Repo.Permission.CanRead(unit.TypePullRequests) {
|
||||
ctx.Data["IssueDependencySearchType"] = "issues"
|
||||
} else {
|
||||
ctx.Data["IssueDependencySearchType"] = "all"
|
||||
@@ -488,26 +486,55 @@ func prepareIssueViewSidebarDependency(ctx *context.Context, issue *issues_model
|
||||
ctx.Data["BlockingDependencies"], ctx.Data["BlockingDependenciesNotPermitted"] = checkBlockedByIssues(ctx, blocking)
|
||||
}
|
||||
|
||||
func preparePullViewSigning(ctx *context.Context, issue *issues_model.Issue) {
|
||||
if !issue.IsPull {
|
||||
return
|
||||
}
|
||||
pull := issue.PullRequest
|
||||
ctx.Data["WillSign"] = false
|
||||
func (prInfo *pullRequestViewInfo) prepareMergeBoxCommitSigning(ctx *context.Context) {
|
||||
pull := prInfo.issue.PullRequest
|
||||
data := prInfo.MergeBoxData
|
||||
|
||||
pb := prInfo.ProtectedBranchRule
|
||||
data.requireSigned = pb != nil && pb.RequireSignedCommits
|
||||
|
||||
wontSignReason := ""
|
||||
if ctx.Doer != nil {
|
||||
sign, key, _, err := asymkey_service.SignMerge(ctx, pull, ctx.Doer, ctx.Repo.GitRepo)
|
||||
ctx.Data["WillSign"] = sign
|
||||
ctx.Data["SigningKeyMergeDisplay"] = asymkey_model.GetDisplaySigningKey(key)
|
||||
sign, key, _, err := asymkey_service.SignMerge(ctx, pull, ctx.Doer, ctx.Repo.GitRepo, pull.BaseBranch, pull.GetGitHeadRefName())
|
||||
data.willSign = sign
|
||||
data.signingKeyMergeDisplay = asymkey_model.GetDisplaySigningKey(key)
|
||||
if err != nil {
|
||||
if asymkey_service.IsErrWontSign(err) {
|
||||
ctx.Data["WontSignReason"] = err.(*asymkey_service.ErrWontSign).Reason
|
||||
wontSignReason = string(err.(*asymkey_service.ErrWontSign).Reason)
|
||||
} else {
|
||||
ctx.Data["WontSignReason"] = "error"
|
||||
log.Error("Error whilst checking if could sign pr %d in repo %s. Error: %v", pull.ID, pull.BaseRepo.FullName(), err)
|
||||
wontSignReason = "error"
|
||||
if !errors.Is(err, util.ErrNotExist) {
|
||||
log.Error("Error whilst checking if could sign pr %d in repo %s. Error: %v", pull.ID, pull.BaseRepo.FullName(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx.Data["WontSignReason"] = "not_signed_in"
|
||||
}
|
||||
|
||||
if data.willSign {
|
||||
prInfo.MergeBoxData.infoMergePrompts.AddInfoItem(
|
||||
svg.RenderHTML("octicon-lock", 16, "tw-text-green"),
|
||||
ctx.Locale.Tr("repo.signing.will_sign", data.signingKeyMergeDisplay),
|
||||
)
|
||||
}
|
||||
|
||||
if !data.requireSigned {
|
||||
if wontSignReason != "" {
|
||||
data.infoMergePrompts.AddInfoItem(
|
||||
svg.RenderHTML("octicon-unlock"),
|
||||
ctx.Locale.Tr("repo.signing.wont_sign."+wontSignReason),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if data.requireSigned && !data.willSign {
|
||||
data.infoProtectionBlockers.AddErrorItem(ctx.Locale.Tr("repo.pulls.require_signed_wont_sign"))
|
||||
if wontSignReason != "" {
|
||||
data.infoProtectionBlockers.AddInfoItem(
|
||||
svg.RenderHTML("octicon-unlock"),
|
||||
ctx.Locale.Tr("repo.signing.wont_sign."+wontSignReason),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,14 +585,11 @@ func prepareIssueViewSidebarTimeTracker(ctx *context.Context, issue *issues_mode
|
||||
}
|
||||
}
|
||||
|
||||
func preparePullViewDeleteBranch(ctx *context.Context, issue *issues_model.Issue, canDelete bool) {
|
||||
if !issue.IsPull {
|
||||
return
|
||||
}
|
||||
pull := issue.PullRequest
|
||||
func (prInfo *pullRequestViewInfo) prepareMergeBoxDeleteBranch(ctx *context.Context, canDelete bool) {
|
||||
pull := prInfo.issue.PullRequest
|
||||
isPullBranchDeletable := canDelete &&
|
||||
pull.HeadRepo != nil &&
|
||||
(!pull.HasMerged || ctx.Data["HeadBranchCommitID"] == ctx.Data["PullHeadCommitID"])
|
||||
(!pull.HasMerged || prInfo.HeadBranchCommitID == prInfo.CompareInfo.HeadCommitID)
|
||||
if isPullBranchDeletable {
|
||||
isPullBranchDeletable, _ = git_model.IsBranchExist(ctx, pull.HeadRepo.ID, pull.HeadBranch)
|
||||
}
|
||||
@@ -579,7 +603,7 @@ func preparePullViewDeleteBranch(ctx *context.Context, issue *issues_model.Issue
|
||||
|
||||
isPullBranchDeletable = !exist
|
||||
}
|
||||
ctx.Data["IsPullBranchDeletable"] = isPullBranchDeletable
|
||||
prInfo.MergeBoxData.IsPullBranchDeletable = isPullBranchDeletable
|
||||
}
|
||||
|
||||
func prepareIssueViewSidebarPin(ctx *context.Context, issue *issues_model.Issue) {
|
||||
@@ -769,7 +793,7 @@ func prepareIssueViewCommentsAndSidebarParticipants(ctx *context.Context, issue
|
||||
ctx.ServerError("LoadCommentPushCommits", err)
|
||||
return
|
||||
}
|
||||
if !ctx.Repo.CanRead(unit.TypeActions) {
|
||||
if !ctx.Repo.Permission.CanRead(unit.TypeActions) {
|
||||
for _, commit := range comment.Commits {
|
||||
if commit.Status == nil {
|
||||
continue
|
||||
@@ -781,14 +805,14 @@ func prepareIssueViewCommentsAndSidebarParticipants(ctx *context.Context, issue
|
||||
} else if comment.Type == issues_model.CommentTypeAddTimeManual ||
|
||||
comment.Type == issues_model.CommentTypeStopTracking ||
|
||||
comment.Type == issues_model.CommentTypeDeleteTimeManual {
|
||||
// drop error since times could be pruned from DB..
|
||||
// drop error since times could be pruned from DB
|
||||
_ = comment.LoadTime(ctx)
|
||||
if comment.Content != "" {
|
||||
// Content before v1.21 did store the formatted string instead of seconds,
|
||||
// so "|" is used as delimiter to mark the new format
|
||||
if comment.Content[0] != '|' {
|
||||
// handle old time comments that have formatted text stored
|
||||
comment.RenderedContent = templates.SanitizeHTML(comment.Content)
|
||||
comment.RenderedContent = markup.Sanitize(comment.Content)
|
||||
comment.Content = ""
|
||||
} else {
|
||||
// else it's just a duration in seconds to pass on to the frontend
|
||||
@@ -827,20 +851,28 @@ func prepareIssueViewCommentsAndSidebarParticipants(ctx *context.Context, issue
|
||||
ctx.Data["NumParticipants"] = len(participants)
|
||||
}
|
||||
|
||||
func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Issue) {
|
||||
getBranchData(ctx, issue)
|
||||
if !issue.IsPull {
|
||||
return
|
||||
func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue *issues_model.Issue) {
|
||||
if prInfo.issue != issue {
|
||||
panic("impossible, issue must be the same")
|
||||
}
|
||||
|
||||
pull := issue.PullRequest
|
||||
pull.Issue = issue
|
||||
data := &pullMergeBoxData{}
|
||||
prInfo.MergeBoxData = data
|
||||
|
||||
canDelete := false
|
||||
allowMerge := false
|
||||
canWriteToHeadRepo := false
|
||||
|
||||
pull_service.StartPullRequestCheckOnView(ctx, pull)
|
||||
|
||||
if !prInfo.IsPullRequestBroken {
|
||||
data.ShowUpdatePullInfo = pull.CommitsBehind > 0 && !issue.IsClosed && !pull.IsChecking() && !pull.IsFilesConflicted() && !prInfo.IsPullRequestBroken
|
||||
prInfo.preparePullUpdateActions(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.IsSigned {
|
||||
if err := pull.LoadHeadRepo(ctx); err != nil {
|
||||
log.Error("LoadHeadRepo: %v", err)
|
||||
@@ -875,7 +907,7 @@ func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Iss
|
||||
if !canWriteToHeadRepo { // maintainers maybe allowed to push to head repo even if they can't write to it
|
||||
canWriteToHeadRepo = pull.AllowMaintainerEdit && perm.CanWrite(unit.TypeCode)
|
||||
}
|
||||
allowMerge, err = pull_service.IsUserAllowedToMerge(ctx, pull, perm, ctx.Doer)
|
||||
data.hasPermToMerge, err = pull_service.IsUserAllowedToMerge(ctx, pull, perm, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("IsUserAllowedToMerge", err)
|
||||
return
|
||||
@@ -887,111 +919,170 @@ func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Iss
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["PullMergeBoxReloadingInterval"] = util.Iif(pull != nil && pull.IsChecking(), 2000, 0)
|
||||
ctx.Data["CanWriteToHeadRepo"] = canWriteToHeadRepo
|
||||
ctx.Data["ShowMergeInstructions"] = canWriteToHeadRepo
|
||||
ctx.Data["AllowMerge"] = allowMerge
|
||||
data.ShowMergeInstructions = canWriteToHeadRepo
|
||||
data.ShowPullCommands = pull.HeadRepo != nil && !pull.HasMerged && !issue.IsClosed
|
||||
|
||||
prUnit, err := issue.Repo.GetUnit(ctx, unit.TypePullRequests)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetUnit", err)
|
||||
return
|
||||
}
|
||||
prConfig := prUnit.PullRequestsConfig()
|
||||
|
||||
ctx.Data["AutodetectManualMerge"] = prConfig.AutodetectManualMerge
|
||||
|
||||
var mergeStyle repo_model.MergeStyle
|
||||
// Check correct values and select default
|
||||
if ms, ok := ctx.Data["MergeStyle"].(repo_model.MergeStyle); !ok ||
|
||||
!prConfig.IsMergeStyleAllowed(ms) {
|
||||
if prConfig.IsMergeStyleAllowed(prConfig.DefaultMergeStyle) && !ok {
|
||||
mergeStyle = prConfig.DefaultMergeStyle
|
||||
} else if prConfig.AllowMerge {
|
||||
mergeStyle = repo_model.MergeStyleMerge
|
||||
} else if prConfig.AllowRebase {
|
||||
mergeStyle = repo_model.MergeStyleRebase
|
||||
} else if prConfig.AllowRebaseMerge {
|
||||
mergeStyle = repo_model.MergeStyleRebaseMerge
|
||||
} else if prConfig.AllowSquash {
|
||||
mergeStyle = repo_model.MergeStyleSquash
|
||||
} else if prConfig.AllowFastForwardOnly {
|
||||
mergeStyle = repo_model.MergeStyleFastForwardOnly
|
||||
} else if prConfig.AllowManualMerge {
|
||||
mergeStyle = repo_model.MergeStyleManuallyMerged
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["MergeStyle"] = mergeStyle
|
||||
|
||||
defaultMergeMessage, defaultMergeBody, err := pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pull, mergeStyle)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetDefaultMergeMessage", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["DefaultMergeMessage"] = defaultMergeMessage
|
||||
ctx.Data["DefaultMergeBody"] = defaultMergeBody
|
||||
|
||||
defaultSquashMergeMessage, defaultSquashMergeBody, err := pull_service.GetDefaultMergeMessage(ctx, ctx.Repo.GitRepo, pull, repo_model.MergeStyleSquash)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetDefaultSquashMergeMessage", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["DefaultSquashMergeMessage"] = defaultSquashMergeMessage
|
||||
ctx.Data["DefaultSquashMergeBody"] = defaultSquashMergeBody
|
||||
|
||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pull.BaseRepoID, pull.BaseBranch)
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadProtectedBranch", err)
|
||||
prInfo.prepareMergeBoxProtectionChecks(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
prInfo.prepareMergeBoxCommitSigning(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
prInfo.prepareMergeBoxDeleteBranch(ctx, canDelete)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
prConfig := issue.Repo.MustGetUnit(ctx, unit.TypePullRequests).PullRequestsConfig()
|
||||
data.AutodetectManualMerge = prConfig.AutodetectManualMerge
|
||||
|
||||
needRefreshMergeBox := pull.IsChecking()
|
||||
needRefreshMergeBox = needRefreshMergeBox || (data.StatusCheckData != nil && data.StatusCheckData.pullCommitStatusState.IsPending())
|
||||
data.ReloadingInterval = util.Iif(needRefreshMergeBox, 5000, 0)
|
||||
|
||||
// Only show the merge box if the PR is not merged, or the branch is deletable.
|
||||
// Otherwise, there is nothing to do, because the PR view page already contains enough information.
|
||||
data.ShowMergeBox = !pull.HasMerged || data.IsPullBranchDeletable
|
||||
|
||||
isRepoAdmin := ctx.IsSigned && (ctx.Repo.Permission.IsAdmin() || ctx.Doer.IsAdmin)
|
||||
|
||||
// admin can merge without checks, writer can merge when checks succeed
|
||||
// admin and writer both can make an auto merge schedule (not affected by overridable blockers)
|
||||
// Required scoped workflow checks gate the merge even when the rule's own status check is disabled (see IsPullCommitStatusPass),
|
||||
// so block on any required status context, not only when enableStatusCheck is on.
|
||||
data.hasStatusCheckBlocker = (data.enableStatusCheck || data.hasRequiredStatusContexts) && !data.StatusCheckData.RequiredChecksState.IsSuccess()
|
||||
|
||||
// this logic is from:
|
||||
// {{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection .IsBlockedByOfficialReviewRequests .IsBlockedByOutdatedBranch .IsBlockedByChangedProtectedFiles (and .EnableStatusCheck (not $requiredStatusCheckState.IsSuccess))}}
|
||||
// HINT: if a PR's status is not mergeable, then it is a non-overridable blocker, such logic is handled separately (see IsStatusMergeable)
|
||||
data.hasOverridableBlockers = data.isBlockedByApprovals || data.isBlockedByRejection ||
|
||||
data.isBlockedByOfficialReviewRequests || data.isBlockedByOutdatedBranch || data.isBlockedByChangedProtectedFiles ||
|
||||
data.hasStatusCheckBlocker
|
||||
|
||||
data.canBypassProtection = isRepoAdmin
|
||||
data.canBypassProtectionAsAdmin = isRepoAdmin
|
||||
if ctx.IsSigned && prInfo.ProtectedBranchRule != nil {
|
||||
data.canBypassProtection = git_model.CanBypassBranchProtection(ctx, prInfo.ProtectedBranchRule, ctx.Doer, isRepoAdmin)
|
||||
data.canBypassProtectionAsAdmin = isRepoAdmin && !prInfo.ProtectedBranchRule.BlockAdminMergeOverride
|
||||
}
|
||||
|
||||
// CanMergeNow means: if the doer has write permission, whether the PR can be merged now
|
||||
data.canMergeNow = (!data.hasOverridableBlockers || data.canBypassProtection) && // status checks are satisfied
|
||||
(!data.requireSigned || data.willSign) // signing requirement is satisfied
|
||||
|
||||
prInfo.prepareMergeBoxFormProps(ctx)
|
||||
prInfo.prepareMergeBoxInfoItems(ctx)
|
||||
prInfo.prepareMergeBoxIconColor()
|
||||
|
||||
ctx.Data["PullMergeBoxData"] = prInfo.MergeBoxData
|
||||
}
|
||||
|
||||
func (prInfo *pullRequestViewInfo) preparePullUpdateActions(ctx *context.Context) {
|
||||
pull := prInfo.issue.PullRequest
|
||||
data := prInfo.MergeBoxData
|
||||
userUpdateStyles, err := pull_service.CheckUserAllowedToUpdate(ctx, pull, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.ServerError("IsUserAllowedToUpdate", err)
|
||||
return
|
||||
}
|
||||
if !userUpdateStyles.MergeAllowed && !userUpdateStyles.RebaseAllowed {
|
||||
return
|
||||
}
|
||||
|
||||
issueLink := prInfo.issue.Link()
|
||||
mergeAction := &pullUpdateAction{
|
||||
URL: issueLink + "/update?style=merge",
|
||||
Text: ctx.Tr("repo.pulls.update_branch"),
|
||||
}
|
||||
rebaseAction := &pullUpdateAction{
|
||||
URL: issueLink + "/update?style=rebase",
|
||||
Text: ctx.Tr("repo.pulls.update_branch_rebase"),
|
||||
}
|
||||
|
||||
if userUpdateStyles.MergeAllowed {
|
||||
data.UpdateStyleOptions = append(data.UpdateStyleOptions, mergeAction)
|
||||
}
|
||||
if userUpdateStyles.RebaseAllowed {
|
||||
data.UpdateStyleOptions = append(data.UpdateStyleOptions, rebaseAction)
|
||||
}
|
||||
|
||||
if userUpdateStyles.DefaultUpdateStyle == repo_model.UpdateStyleRebase && userUpdateStyles.RebaseAllowed {
|
||||
data.UpdatePrimaryAction = rebaseAction
|
||||
} else if userUpdateStyles.DefaultUpdateStyle == repo_model.UpdateStyleMerge && userUpdateStyles.MergeAllowed {
|
||||
data.UpdatePrimaryAction = mergeAction
|
||||
} else {
|
||||
data.UpdatePrimaryAction = data.UpdateStyleOptions[0]
|
||||
}
|
||||
data.UpdatePrimaryAction.Selected = true
|
||||
}
|
||||
|
||||
func (prInfo *pullRequestViewInfo) prepareMergeBoxProtectionChecks(ctx *context.Context) {
|
||||
pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, prInfo.issue.PullRequest.BaseBranch)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetFirstMatchProtectedBranchRule", err)
|
||||
return
|
||||
}
|
||||
if pb != nil {
|
||||
pb.Repo = pull.BaseRepo
|
||||
ctx.Data["ProtectedBranch"] = pb
|
||||
ctx.Data["IsBlockedByApprovals"] = !issues_model.HasEnoughApprovals(ctx, pb, pull)
|
||||
ctx.Data["IsBlockedByRejection"] = issues_model.MergeBlockedByRejectedReview(ctx, pb, pull)
|
||||
ctx.Data["IsBlockedByOfficialReviewRequests"] = issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pull)
|
||||
ctx.Data["IsBlockedByOutdatedBranch"] = issues_model.MergeBlockedByOutdatedBranch(pb, pull)
|
||||
ctx.Data["GrantedApprovals"] = issues_model.GetGrantedApprovalsCount(ctx, pb, pull)
|
||||
ctx.Data["RequireSigned"] = pb.RequireSignedCommits
|
||||
ctx.Data["ChangedProtectedFiles"] = pull.ChangedProtectedFiles
|
||||
ctx.Data["IsBlockedByChangedProtectedFiles"] = len(pull.ChangedProtectedFiles) != 0
|
||||
ctx.Data["ChangedProtectedFilesNum"] = len(pull.ChangedProtectedFiles)
|
||||
ctx.Data["RequireApprovalsWhitelist"] = pb.EnableApprovalsWhitelist
|
||||
pb.Repo = prInfo.issue.PullRequest.BaseRepo
|
||||
prInfo.ProtectedBranchRule = pb
|
||||
}
|
||||
|
||||
preparePullViewSigning(ctx, issue)
|
||||
prInfo.prepareMergeBoxStatusCheckData(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
preparePullViewDeleteBranch(ctx, issue, canDelete)
|
||||
prInfo.prepareMergeBoxProtectedRules(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
stillCanManualMerge := func() bool {
|
||||
if pull.HasMerged || issue.IsClosed || !ctx.IsSigned {
|
||||
return false
|
||||
}
|
||||
if pull.CanAutoMerge() || pull.IsWorkInProgress(ctx) || pull.IsChecking() {
|
||||
return false
|
||||
}
|
||||
if allowMerge && prConfig.AllowManualMerge {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
func (prInfo *pullRequestViewInfo) prepareMergeBoxProtectedRules(ctx *context.Context) {
|
||||
pb := prInfo.ProtectedBranchRule
|
||||
if pb == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["StillCanManualMerge"] = stillCanManualMerge()
|
||||
pull := prInfo.issue.PullRequest
|
||||
data := prInfo.MergeBoxData
|
||||
|
||||
// Check if there is a pending pr merge
|
||||
ctx.Data["HasPendingPullRequestMerge"], ctx.Data["PendingPullRequestMerge"], err = pull_model.GetScheduledMergeByPullID(ctx, pull.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetScheduledMergeByPullID", err)
|
||||
return
|
||||
data.isBlockedByApprovals = !issues_model.HasEnoughApprovals(ctx, pb, pull)
|
||||
if data.isBlockedByApprovals {
|
||||
grantedApprovals := issues_model.GetGrantedApprovalsCount(ctx, pb, pull)
|
||||
blockerInfo := ctx.Locale.Tr("repo.pulls.blocked_by_approvals", grantedApprovals, pb.RequiredApprovals)
|
||||
if pb.EnableApprovalsWhitelist {
|
||||
blockerInfo = ctx.Locale.Tr("repo.pulls.blocked_by_approvals_whitelisted", grantedApprovals, pb.RequiredApprovals)
|
||||
}
|
||||
data.infoProtectionBlockers.AddErrorItem(blockerInfo)
|
||||
}
|
||||
|
||||
data.isBlockedByRejection = issues_model.MergeBlockedByRejectedReview(ctx, pb, pull)
|
||||
if data.isBlockedByRejection {
|
||||
data.infoProtectionBlockers.AddErrorItem(ctx.Locale.Tr("repo.pulls.blocked_by_rejection"))
|
||||
}
|
||||
|
||||
data.isBlockedByOfficialReviewRequests = issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pull)
|
||||
if data.isBlockedByOfficialReviewRequests {
|
||||
data.infoProtectionBlockers.AddErrorItem(ctx.Locale.Tr("repo.pulls.blocked_by_official_review_requests"))
|
||||
}
|
||||
|
||||
data.isBlockedByOutdatedBranch = issues_model.MergeBlockedByOutdatedBranch(pb, pull)
|
||||
if data.isBlockedByOutdatedBranch {
|
||||
data.infoProtectionBlockers.AddErrorItem(ctx.Locale.Tr("repo.pulls.blocked_by_outdated_branch"))
|
||||
}
|
||||
|
||||
data.isBlockedByChangedProtectedFiles = len(pull.ChangedProtectedFiles) != 0
|
||||
if data.isBlockedByChangedProtectedFiles {
|
||||
detailItems := escapeStringSliceToHTML(pull.ChangedProtectedFiles)
|
||||
data.infoProtectionBlockers.AddErrorItem(
|
||||
ctx.Locale.TrN(len(pull.ChangedProtectedFiles), "repo.pulls.blocked_by_changed_protected_files_1", "repo.pulls.blocked_by_changed_protected_files_n"),
|
||||
detailItems,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user