diff --git a/routers/web/repo/issue_view.go b/routers/web/repo/issue_view.go index 250a54fc24a3c..c463190aa3fcf 100644 --- a/routers/web/repo/issue_view.go +++ b/routers/web/repo/issue_view.go @@ -387,8 +387,6 @@ func ViewIssue(ctx *context.Context) { prepareIssueViewSidebarTimeTracker, prepareIssueViewSidebarDependency, prepareIssueViewSidebarPin, - func(ctx *context.Context, issue *issues_model.Issue) { preparePullViewPullInfo(ctx, issue) }, - preparePullViewReviewAndMerge, } for _, prepareFunc := range prepareFuncs { @@ -398,6 +396,11 @@ func ViewIssue(ctx *context.Context) { } } + preparePullViewReviewAndMergeAll(ctx, issue) + if ctx.Written() { + return + } + // Get more information if it's a pull request. if issue.IsPull { if issue.PullRequest.HasMerged { @@ -443,8 +446,7 @@ func ViewPullMergeBox(ctx *context.Context) { ctx.NotFound(nil) return } - preparePullViewPullInfo(ctx, issue) - preparePullViewReviewAndMerge(ctx, issue) + preparePullViewReviewAndMergeAll(ctx, issue) 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 @@ -488,14 +490,15 @@ 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) { +func preparePullViewSigning(ctx *context.Context, issue *issues_model.Issue) (willSign bool) { if !issue.IsPull { - return + return false } pull := issue.PullRequest ctx.Data["WillSign"] = false if ctx.Doer != nil { sign, key, _, err := asymkey_service.SignMerge(ctx, pull, ctx.Doer, ctx.Repo.GitRepo) + willSign = sign ctx.Data["WillSign"] = sign ctx.Data["SigningKeyMergeDisplay"] = asymkey_model.GetDisplaySigningKey(key) if err != nil { @@ -509,6 +512,7 @@ func preparePullViewSigning(ctx *context.Context, issue *issues_model.Issue) { } else { ctx.Data["WontSignReason"] = "not_signed_in" } + return willSign } func prepareIssueViewSidebarWatch(ctx *context.Context, issue *issues_model.Issue) { @@ -558,9 +562,9 @@ func prepareIssueViewSidebarTimeTracker(ctx *context.Context, issue *issues_mode } } -func preparePullViewDeleteBranch(ctx *context.Context, issue *issues_model.Issue, canDelete bool) { +func preparePullViewDeleteBranch(ctx *context.Context, issue *issues_model.Issue, canDelete bool) bool { if !issue.IsPull { - return + return false } pull := issue.PullRequest isPullBranchDeletable := canDelete && @@ -574,12 +578,13 @@ func preparePullViewDeleteBranch(ctx *context.Context, issue *issues_model.Issue exist, err := issues_model.HasUnmergedPullRequestsByHeadInfo(ctx, pull.HeadRepoID, pull.HeadBranch) if err != nil { ctx.ServerError("HasUnmergedPullRequestsByHeadInfo", err) - return + return false } isPullBranchDeletable = !exist } ctx.Data["IsPullBranchDeletable"] = isPullBranchDeletable + return isPullBranchDeletable } func prepareIssueViewSidebarPin(ctx *context.Context, issue *issues_model.Issue) { @@ -827,7 +832,15 @@ func prepareIssueViewCommentsAndSidebarParticipants(ctx *context.Context, issue ctx.Data["NumParticipants"] = len(participants) } -func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Issue) { +func preparePullViewReviewAndMergeAll(ctx *context.Context, issue *issues_model.Issue) { + _, mergeInputs := preparePullViewPullInfo(ctx, issue) + if ctx.Written() { + return + } + preparePullViewReviewAndMerge(ctx, issue, mergeInputs) +} + +func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Issue, mergeInputs pullViewMergeInputs) { getBranchData(ctx, issue) if !issue.IsPull { return @@ -901,13 +914,9 @@ func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Iss 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 := prConfig.DefaultMergeStyle + if !prConfig.IsMergeStyleAllowed(mergeStyle) { + if prConfig.AllowMerge { mergeStyle = repo_model.MergeStyleMerge } else if prConfig.AllowRebase { mergeStyle = repo_model.MergeStyleRebase @@ -922,23 +931,17 @@ func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Iss } } - 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 { @@ -946,27 +949,33 @@ func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Iss return } + var isBlockedByApprovals, isBlockedByRejection, isBlockedByOfficialReviewRequests, isBlockedByOutdatedBranch, isBlockedByChangedProtectedFiles bool 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) + isBlockedByApprovals = !issues_model.HasEnoughApprovals(ctx, pb, pull) + isBlockedByRejection = issues_model.MergeBlockedByRejectedReview(ctx, pb, pull) + isBlockedByOfficialReviewRequests = issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pull) + isBlockedByOutdatedBranch = issues_model.MergeBlockedByOutdatedBranch(pb, pull) + isBlockedByChangedProtectedFiles = len(pull.ChangedProtectedFiles) != 0 + ctx.Data["IsBlockedByApprovals"] = isBlockedByApprovals + ctx.Data["IsBlockedByRejection"] = isBlockedByRejection + ctx.Data["IsBlockedByOfficialReviewRequests"] = isBlockedByOfficialReviewRequests + ctx.Data["IsBlockedByOutdatedBranch"] = isBlockedByOutdatedBranch + ctx.Data["IsBlockedByChangedProtectedFiles"] = isBlockedByChangedProtectedFiles 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 } - preparePullViewSigning(ctx, issue) + willSign := preparePullViewSigning(ctx, issue) if ctx.Written() { return } - preparePullViewDeleteBranch(ctx, issue, canDelete) + isPullBranchDeletable := preparePullViewDeleteBranch(ctx, issue, canDelete) if ctx.Written() { return } @@ -988,11 +997,31 @@ func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Iss ctx.Data["StillCanManualMerge"] = stillCanManualMerge() // Check if there is a pending pr merge - ctx.Data["HasPendingPullRequestMerge"], ctx.Data["PendingPullRequestMerge"], err = pull_model.GetScheduledMergeByPullID(ctx, pull.ID) + _, pendingPullRequestMerge, err := pull_model.GetScheduledMergeByPullID(ctx, pull.ID) if err != nil { ctx.ServerError("GetScheduledMergeByPullID", err) return } + + preparePullViewMergeFormData(ctx, issue, &mergeFormParams{ + pullViewMergeInputs: mergeInputs, + AllowMerge: allowMerge, + ProtectedBranch: pb, + PrConfig: prConfig, + MergeStyle: mergeStyle, + DefaultMergeMessage: defaultMergeMessage, + DefaultMergeBody: defaultMergeBody, + DefaultSquashMergeMessage: defaultSquashMergeMessage, + DefaultSquashMergeBody: defaultSquashMergeBody, + PendingPullRequestMerge: pendingPullRequestMerge, + IsBlockedByApprovals: isBlockedByApprovals, + IsBlockedByRejection: isBlockedByRejection, + IsBlockedByOfficialReviewRequests: isBlockedByOfficialReviewRequests, + IsBlockedByOutdatedBranch: isBlockedByOutdatedBranch, + IsBlockedByChangedProtectedFiles: isBlockedByChangedProtectedFiles, + WillSign: willSign, + IsPullBranchDeletable: isPullBranchDeletable, + }) } func prepareIssueViewContent(ctx *context.Context, issue *issues_model.Issue) { diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index e312fc9d2a816..69660b976b7f2 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -260,12 +260,12 @@ func GetMergedBaseCommitID(ctx *context.Context, issue *issues_model.Issue) stri return baseCommit } -func preparePullViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_service.CompareInfo { +func preparePullViewPullInfo(ctx *context.Context, issue *issues_model.Issue) (*git_service.CompareInfo, pullViewMergeInputs) { if !issue.IsPull { - return nil + return nil, pullViewMergeInputs{} } if issue.PullRequest.HasMerged { - return prepareMergedViewPullInfo(ctx, issue) + return prepareMergedViewPullInfo(ctx, issue), pullViewMergeInputs{} } return prepareViewPullInfo(ctx, issue) } @@ -374,7 +374,7 @@ func getViewPullHeadBranchInfo(ctx *context.Context, pull *issues_model.PullRequ } // prepareViewPullInfo show meta information for a pull request preview page -func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_service.CompareInfo { +func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) (compareInfo *git_service.CompareInfo, mergeInputs pullViewMergeInputs) { ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes repo := ctx.Repo.Repository @@ -382,12 +382,12 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s if err := pull.LoadHeadRepo(ctx); err != nil { ctx.ServerError("LoadHeadRepo", err) - return nil + return compareInfo, mergeInputs } if err := pull.LoadBaseRepo(ctx); err != nil { ctx.ServerError("LoadBaseRepo", err) - return nil + return compareInfo, mergeInputs } setMergeTarget(ctx, pull) @@ -395,7 +395,7 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, pull.BaseBranch) if err != nil { ctx.ServerError("LoadProtectedBranch", err) - return nil + return compareInfo, mergeInputs } ctx.Data["EnableStatusCheck"] = pb != nil && pb.EnableStatusCheck @@ -406,7 +406,7 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s baseGitRepo, err := gitrepo.OpenRepository(ctx, pull.BaseRepo) if err != nil { ctx.ServerError("OpenRepository", err) - return nil + return compareInfo, mergeInputs } defer baseGitRepo.Close() } @@ -422,12 +422,12 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s sha, err := baseGitRepo.GetRefCommitID(pull.GetGitHeadRefName()) if err != nil { ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitHeadRefName()), err) - return nil + return compareInfo, mergeInputs } commitStatuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll) if err != nil { ctx.ServerError("GetLatestCommitStatus", err) - return nil + return compareInfo, mergeInputs } if !ctx.Repo.CanRead(unit.TypeActions) { git_model.CommitStatusesHideActionsURL(ctx, commitStatuses) @@ -439,7 +439,7 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s ctx.Data["LatestCommitStatus"] = statusCheckData.LatestCommitStatus } - compareInfo, err := git_service.GetCompareInfo(ctx, pull.BaseRepo, pull.BaseRepo, baseGitRepo, + ci, err := git_service.GetCompareInfo(ctx, pull.BaseRepo, pull.BaseRepo, baseGitRepo, git.RefName(pull.MergeBase), git.RefName(pull.GetGitHeadRefName()), false, false) if err != nil { if gitcmd.IsStdErrorNotValidObjectName(err) { @@ -447,34 +447,34 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s ctx.Data["BaseTarget"] = pull.BaseBranch ctx.Data["NumCommits"] = 0 ctx.Data["NumFiles"] = 0 - return nil + return compareInfo, mergeInputs } ctx.ServerError("GetCompareInfo", err) - return nil + return compareInfo, mergeInputs } - ctx.Data["NumCommits"] = len(compareInfo.Commits) - ctx.Data["NumFiles"] = compareInfo.NumFiles - return compareInfo + ctx.Data["NumCommits"] = len(ci.Commits) + ctx.Data["NumFiles"] = ci.NumFiles + compareInfo = ci + return compareInfo, mergeInputs } headBranchSha, headBranchExist, err := getViewPullHeadBranchInfo(ctx, pull, baseGitRepo) if err != nil { ctx.ServerError("getViewPullHeadBranchInfo", err) - return nil + return compareInfo, mergeInputs } + var getCommitMessages string if headBranchExist { var err error ctx.Data["UpdateAllowed"], ctx.Data["UpdateByRebaseAllowed"], err = pull_service.IsUserAllowedToUpdate(ctx, pull, ctx.Doer) if err != nil { ctx.ServerError("IsUserAllowedToUpdate", err) - return nil + return compareInfo, mergeInputs } - ctx.Data["GetCommitMessages"] = pull_service.GetSquashMergeCommitMessages(ctx, pull) - } else { - ctx.Data["GetCommitMessages"] = "" + getCommitMessages = pull_service.GetSquashMergeCommitMessages(ctx, pull) } sha, err := baseGitRepo.GetRefCommitID(pull.GetGitHeadRefName()) @@ -491,10 +491,10 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s ctx.Data["BaseTarget"] = pull.BaseBranch ctx.Data["NumCommits"] = 0 ctx.Data["NumFiles"] = 0 - return nil + return compareInfo, mergeInputs } ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitHeadRefName()), err) - return nil + return compareInfo, mergeInputs } ctx.Data["StatusCheckData"] = statusCheckData @@ -503,7 +503,7 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s commitStatuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll) if err != nil { ctx.ServerError("GetLatestCommitStatus", err) - return nil + return compareInfo, mergeInputs } if !ctx.Repo.CanRead(unit.TypeActions) { git_model.CommitStatusesHideActionsURL(ctx, commitStatuses) @@ -512,7 +512,7 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s runs, err := actions_service.GetRunsFromCommitStatuses(ctx, commitStatuses) if err != nil { ctx.ServerError("GetRunsFromCommitStatuses", err) - return nil + return compareInfo, mergeInputs } for _, run := range runs { if run.NeedApproval { @@ -581,7 +581,7 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s } } - compareInfo, err := git_service.GetCompareInfo(ctx, pull.BaseRepo, pull.BaseRepo, baseGitRepo, + ci, err := git_service.GetCompareInfo(ctx, pull.BaseRepo, pull.BaseRepo, baseGitRepo, git.RefNameFromBranch(pull.BaseBranch), git.RefName(pull.GetGitHeadRefName()), false, false) if err != nil { if gitcmd.IsStdErrorNotValidObjectName(err) { @@ -589,14 +589,14 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s ctx.Data["BaseTarget"] = pull.BaseBranch ctx.Data["NumCommits"] = 0 ctx.Data["NumFiles"] = 0 - return nil + return compareInfo, mergeInputs } ctx.ServerError("GetCompareInfo", err) - return nil + return compareInfo, mergeInputs } - if compareInfo.HeadCommitID == compareInfo.MergeBase { + if ci.HeadCommitID == ci.MergeBase { ctx.Data["IsNothingToCompare"] = true } @@ -610,9 +610,16 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git_s ctx.Data["ConflictedFiles"] = pull.ConflictedFiles } - ctx.Data["NumCommits"] = len(compareInfo.Commits) - ctx.Data["NumFiles"] = compareInfo.NumFiles - return compareInfo + ctx.Data["NumCommits"] = len(ci.Commits) + ctx.Data["NumFiles"] = ci.NumFiles + compareInfo = ci + mergeInputs = pullViewMergeInputs{ + PullHeadCommitID: sha, + HeadTarget: fmt.Sprint(ctx.Data["HeadTarget"]), + SquashCommitMessages: getCommitMessages, + StatusCheckData: statusCheckData, + } + return compareInfo, mergeInputs } func createRequiredContextMatcher(requiredContext string) func(string) bool { @@ -672,7 +679,7 @@ func ViewPullCommits(ctx *context.Context) { return } - prInfo := preparePullViewPullInfo(ctx, issue) + prInfo, _ := preparePullViewPullInfo(ctx, issue) if ctx.Written() { return } else if prInfo == nil { @@ -725,7 +732,7 @@ func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) { gitRepo := ctx.Repo.GitRepo - prInfo := preparePullViewPullInfo(ctx, issue) + prInfo, _ := preparePullViewPullInfo(ctx, issue) if ctx.Written() { return } else if prInfo == nil { diff --git a/routers/web/repo/pull_merge_form.go b/routers/web/repo/pull_merge_form.go new file mode 100644 index 0000000000000..4deeed436d7a9 --- /dev/null +++ b/routers/web/repo/pull_merge_form.go @@ -0,0 +1,187 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + git_model "code.gitea.io/gitea/models/git" + issues_model "code.gitea.io/gitea/models/issues" + pull_model "code.gitea.io/gitea/models/pull" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/services/context" +) + +type mergeStyleField struct { + Name string `json:"name"` + Allowed bool `json:"allowed"` + TextDoMerge string `json:"textDoMerge"` + MergeTitleFieldText string `json:"mergeTitleFieldText,omitempty"` + MergeMessageFieldText string `json:"mergeMessageFieldText,omitempty"` + HideMergeMessageTexts bool `json:"hideMergeMessageTexts,omitempty"` + HideAutoMerge bool `json:"hideAutoMerge"` +} + +type mergeFormField struct { + BaseLink string `json:"baseLink"` + TextCancel string `json:"textCancel"` + TextDeleteBranch string `json:"textDeleteBranch"` + TextAutoMergeButtonWhenSucceed string `json:"textAutoMergeButtonWhenSucceed"` + TextAutoMergeWhenSucceed string `json:"textAutoMergeWhenSucceed"` + TextAutoMergeCancelSchedule string `json:"textAutoMergeCancelSchedule"` + TextClearMergeMessage string `json:"textClearMergeMessage"` + TextClearMergeMessageHint string `json:"textClearMergeMessageHint"` + TextMergeCommitID string `json:"textMergeCommitId"` + CanMergeNow bool `json:"canMergeNow"` + AllOverridableChecksOk bool `json:"allOverridableChecksOk"` + EmptyCommit bool `json:"emptyCommit"` + PullHeadCommitID string `json:"pullHeadCommitID"` + IsPullBranchDeletable bool `json:"isPullBranchDeletable"` + DefaultMergeStyle string `json:"defaultMergeStyle"` + DefaultDeleteBranchAfterMerge bool `json:"defaultDeleteBranchAfterMerge"` + MergeMessageFieldPlaceHolder string `json:"mergeMessageFieldPlaceHolder"` + DefaultMergeBody string `json:"defaultMergeBody"` + HasPendingPullRequestMerge bool `json:"hasPendingPullRequestMerge"` + HasPendingPullRequestMergeTip string `json:"hasPendingPullRequestMergeTip"` + MergeStyles []mergeStyleField `json:"mergeStyles"` +} + +type pullViewMergeInputs struct { + PullHeadCommitID string + HeadTarget string + SquashCommitMessages string + StatusCheckData *pullCommitStatusCheckData +} + +type mergeFormParams struct { + pullViewMergeInputs + AllowMerge bool + ProtectedBranch *git_model.ProtectedBranch + PrConfig *repo_model.PullRequestsConfig + MergeStyle repo_model.MergeStyle + DefaultMergeMessage string + DefaultMergeBody string + DefaultSquashMergeMessage string + DefaultSquashMergeBody string + PendingPullRequestMerge *pull_model.AutoMerge + IsBlockedByApprovals bool + IsBlockedByRejection bool + IsBlockedByOfficialReviewRequests bool + IsBlockedByOutdatedBranch bool + IsBlockedByChangedProtectedFiles bool + WillSign bool + IsPullBranchDeletable bool +} + +// preparePullViewMergeFormData builds the JSON data for the merge form Vue component. +func preparePullViewMergeFormData(ctx *context.Context, issue *issues_model.Issue, params *mergeFormParams) { + pull := issue.PullRequest + + if pull.HasMerged || issue.IsClosed || (!pull.CanAutoMerge() && !pull.IsEmpty()) || !params.AllowMerge { + return + } + + prConfig := params.PrConfig + if !(prConfig.AllowMerge || prConfig.AllowRebase || prConfig.AllowRebaseMerge || prConfig.AllowSquash || prConfig.AllowFastForwardOnly) { + return + } + + pb := params.ProtectedBranch + requiredStatusCheckSuccess := params.StatusCheckData != nil && params.StatusCheckData.RequiredChecksState.IsSuccess() + allOverridableChecksOk := !params.IsBlockedByApprovals && !params.IsBlockedByRejection && + !params.IsBlockedByOfficialReviewRequests && !params.IsBlockedByOutdatedBranch && + !params.IsBlockedByChangedProtectedFiles && + (pb == nil || !pb.EnableStatusCheck || requiredStatusCheckSuccess) + isRepoAdmin := ctx.IsSigned && (ctx.Repo.IsAdmin() || ctx.Doer.IsAdmin) + // admin can merge without checks, writer can merge when checks succeed + canMergeNow := (((pb == nil || !pb.BlockAdminMergeOverride) && isRepoAdmin) || allOverridableChecksOk) && + (pb == nil || !pb.RequireSignedCommits || params.WillSign) + ctx.Data["AllOverridableChecksOk"] = allOverridableChecksOk + ctx.Data["CanMergeNow"] = canMergeNow + // admin and writer both can make an auto merge schedule + hideAutoMerge := canMergeNow && allOverridableChecksOk + + hasPendingPullRequestMergeTip := "" + if params.PendingPullRequestMerge != nil { + hasPendingPullRequestMergeTip = ctx.Locale.TrString("repo.pulls.auto_merge_has_pending_schedule", + params.PendingPullRequestMerge.Doer.Name, templates.TimeSince(params.PendingPullRequestMerge.CreatedUnix)) + } + + form := &mergeFormField{ + BaseLink: issue.Link(), + TextCancel: ctx.Locale.TrString("cancel"), + TextDeleteBranch: ctx.Locale.TrString("repo.branch.delete", params.HeadTarget), + TextAutoMergeButtonWhenSucceed: ctx.Locale.TrString("repo.pulls.auto_merge_button_when_succeed"), + TextAutoMergeWhenSucceed: ctx.Locale.TrString("repo.pulls.auto_merge_when_succeed"), + TextAutoMergeCancelSchedule: ctx.Locale.TrString("repo.pulls.auto_merge_cancel_schedule"), + TextClearMergeMessage: ctx.Locale.TrString("repo.pulls.clear_merge_message"), + TextClearMergeMessageHint: ctx.Locale.TrString("repo.pulls.clear_merge_message_hint"), + TextMergeCommitID: ctx.Locale.TrString("repo.pulls.merge_commit_id"), + CanMergeNow: canMergeNow, + AllOverridableChecksOk: allOverridableChecksOk, + EmptyCommit: pull.IsEmpty(), + PullHeadCommitID: params.PullHeadCommitID, + IsPullBranchDeletable: params.IsPullBranchDeletable, + DefaultMergeStyle: string(params.MergeStyle), + DefaultDeleteBranchAfterMerge: prConfig.DefaultDeleteBranchAfterMerge, + MergeMessageFieldPlaceHolder: ctx.Locale.TrString("repo.editor.commit_message_desc"), + DefaultMergeBody: params.DefaultMergeBody, + HasPendingPullRequestMerge: params.PendingPullRequestMerge != nil, + HasPendingPullRequestMergeTip: hasPendingPullRequestMergeTip, + MergeStyles: []mergeStyleField{ + { + Name: "merge", + Allowed: prConfig.AllowMerge, + TextDoMerge: ctx.Locale.TrString("repo.pulls.merge_pull_request"), + MergeTitleFieldText: params.DefaultMergeMessage, + MergeMessageFieldText: params.DefaultMergeBody, + HideAutoMerge: hideAutoMerge, + }, + { + Name: "rebase", + Allowed: prConfig.AllowRebase, + TextDoMerge: ctx.Locale.TrString("repo.pulls.rebase_merge_pull_request"), + HideMergeMessageTexts: true, + HideAutoMerge: hideAutoMerge, + }, + { + Name: "rebase-merge", + Allowed: prConfig.AllowRebaseMerge, + TextDoMerge: ctx.Locale.TrString("repo.pulls.rebase_merge_commit_pull_request"), + MergeTitleFieldText: params.DefaultMergeMessage, + MergeMessageFieldText: params.DefaultMergeBody, + HideAutoMerge: hideAutoMerge, + }, + { + Name: "squash", + Allowed: prConfig.AllowSquash, + TextDoMerge: ctx.Locale.TrString("repo.pulls.squash_merge_pull_request"), + MergeTitleFieldText: params.DefaultSquashMergeMessage, + MergeMessageFieldText: params.SquashCommitMessages + params.DefaultSquashMergeBody, + HideAutoMerge: hideAutoMerge, + }, + { + Name: "fast-forward-only", + Allowed: prConfig.AllowFastForwardOnly && pull.CommitsBehind == 0, + TextDoMerge: ctx.Locale.TrString("repo.pulls.fast_forward_only_merge_pull_request"), + HideMergeMessageTexts: true, + HideAutoMerge: hideAutoMerge, + }, + { + Name: "manually-merged", + Allowed: prConfig.AllowManualMerge, + TextDoMerge: ctx.Locale.TrString("repo.pulls.merge_manually"), + HideMergeMessageTexts: true, + HideAutoMerge: true, + }, + }, + } + + jsonBytes, err := json.Marshal(form) + if err != nil { + ctx.ServerError("json.Marshal", err) + return + } + ctx.Data["MergeFormJSON"] = string(jsonBytes) +} diff --git a/templates/repo/issue/view_content/pull_merge_box.tmpl b/templates/repo/issue/view_content/pull_merge_box.tmpl index 670c7f18ef153..1eeef508653b5 100644 --- a/templates/repo/issue/view_content/pull_merge_box.tmpl +++ b/templates/repo/issue/view_content/pull_merge_box.tmpl @@ -38,7 +38,6 @@ )}} {{end}} - {{$showGeneralMergeForm := false}}