Refactor pull request view (7) (#37524)

Almost done

`pull_merge_box.tmpl` only has about 80 lines now, and (almost) all
variable accesses are strictly typed.

---------

Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
Co-authored-by: Nicolas <bircni@icloud.com>
This commit is contained in:
wxiaoguang
2026-05-05 04:13:38 +08:00
committed by GitHub
parent 89a49de0fd
commit a90d5dd131
11 changed files with 439 additions and 380 deletions
+58 -46
View File
@@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"html"
"html/template"
"net/http"
"strconv"
"strings"
@@ -35,6 +36,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/svg"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
@@ -56,7 +58,6 @@ import (
)
const (
tplCompareDiff templates.TplName = "repo/diff/compare"
tplPullCommits templates.TplName = "repo/pulls/commits"
tplPullFiles templates.TplName = "repo/pulls/files"
@@ -268,6 +269,13 @@ type pullMergeBoxData struct {
TimelineIconClass string
ClosedInfoTitle template.HTML
ClosedInfoBody template.HTML
enableStatusCheck bool
StatusCheckData *pullCommitStatusCheckData
ShowStatusCheck bool
HasOverridableBlockers bool
CanMergeNow bool // PR is mergeable, either no blocker, or doer is admin and can bypass the blockers
allowMerge bool // doer has permission to merge
@@ -283,7 +291,7 @@ type pullMergeBoxData struct {
// don't expose unneeded fields to templates, need more refactoring changes
hasStatusCheckBlocker bool
isPullBranchDeletable bool
IsPullBranchDeletable bool
isBlockedByApprovals bool
isBlockedByRejection bool
@@ -291,6 +299,13 @@ type pullMergeBoxData struct {
isBlockedByOutdatedBranch bool
isBlockedByChangedProtectedFiles bool
requireSigned, willSign bool
signingKeyMergeDisplay string
infoCommitBlockers pullMergeBoxInfoItemCollection
infoProtectionBlockers pullMergeBoxInfoItemCollection
infoMergePrompts pullMergeBoxInfoItemCollection
InfoSections []*pullInfoSection
}
// pullRequestViewInfo is a structured type for viewing pull request
@@ -306,11 +321,8 @@ type pullRequestViewInfo struct {
CompareInfo git_service.CompareInfo
ProtectedBranchRule *git_model.ProtectedBranch
StatusCheckData *pullCommitStatusCheckData
CommitStatuses []*git_model.CommitStatus
MergeBoxData *pullMergeBoxData
enableStatusCheck bool
workInProgressPrefix string
}
@@ -349,7 +361,6 @@ func (prInfo *pullRequestViewInfo) prepareViewFillInfo(ctx *context.Context, bas
if ctx.Written() {
return
}
prInfo.prepareViewFillCommitStatusInfo(ctx)
}
func (prInfo *pullRequestViewInfo) prepareViewFillCompareInfo(ctx *context.Context, baseRef git.RefName) {
@@ -379,56 +390,47 @@ func (prInfo *pullRequestViewInfo) prepareViewFillCompareInfo(ctx *context.Conte
prInfo.IsPullRequestBroken = true
}
ctx.Data["IsPullRequestBroken"] = prInfo.IsPullRequestBroken
ctx.Data["NumCommits"] = len(prInfo.CompareInfo.Commits)
ctx.Data["NumFiles"] = prInfo.CompareInfo.NumFiles
prInfo.setTemplateDataMergeTarget(ctx)
}
func (prInfo *pullRequestViewInfo) prepareViewFillCommitStatusInfo(ctx *context.Context) {
func (prInfo *pullRequestViewInfo) prepareMergeBoxStatusCheckData(ctx *context.Context) {
headCommitID := prInfo.CompareInfo.HeadCommitID
if headCommitID == "" {
if headCommitID == "" || prInfo.issue.IsClosed {
return
}
repo := ctx.Repo.Repository
data := prInfo.MergeBoxData
var pbRequiredContexts []string
data.enableStatusCheck = prInfo.ProtectedBranchRule != nil && prInfo.ProtectedBranchRule.EnableStatusCheck
if prInfo.ProtectedBranchRule != nil {
pbRequiredContexts = prInfo.ProtectedBranchRule.StatusCheckContexts
}
statusCheckData := &pullCommitStatusCheckData{}
prInfo.StatusCheckData = statusCheckData
data.StatusCheckData = statusCheckData
commitStatuses, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, prInfo.CompareInfo.HeadCommitID, db.ListOptionsAll)
if err != nil {
ctx.ServerError("GetLatestCommitStatus", err)
return
log.Error("GetLatestCommitStatus: %v", err)
}
if !ctx.Repo.Permission.CanRead(unit.TypeActions) {
git_model.CommitStatusesHideActionsURL(ctx, commitStatuses)
}
prInfo.CommitStatuses = commitStatuses
statusCheckData.ApproveLink = fmt.Sprintf("%s/actions/approve-all-checks?commit_id=%s", repo.Link(), headCommitID)
statusCheckData.LatestCommitStatus = git_model.CalcCommitStatus(commitStatuses)
ctx.Data["LatestCommitStatuses"] = commitStatuses
ctx.Data["LatestCommitStatus"] = statusCheckData.LatestCommitStatus
ctx.Data["StatusCheckData"] = prInfo.StatusCheckData
prInfo.ProtectedBranchRule, err = git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, prInfo.issue.PullRequest.BaseBranch)
if err != nil {
ctx.ServerError("GetFirstMatchProtectedBranchRule", err)
return
combinedCommitStatus := git_model.CalcCommitStatus(commitStatuses)
statusCheckData.ApproveLink = fmt.Sprintf("%s/actions/approve-all-checks?commit_id=%s", ctx.Repo.Repository.Link(), headCommitID)
statusCheckData.PullCommitStatuses = commitStatuses
if combinedCommitStatus != nil {
statusCheckData.pullCommitStatusState = combinedCommitStatus.State
}
if !prInfo.issue.IsClosed {
prInfo.prepareViewFillCommitStatusInfoForOpen(ctx)
}
}
data.ShowStatusCheck = data.enableStatusCheck || len(statusCheckData.PullCommitStatuses) > 0
func (prInfo *pullRequestViewInfo) prepareViewFillCommitStatusInfoForOpen(ctx *context.Context) {
statusCheckData := prInfo.StatusCheckData
commitStatuses := prInfo.CommitStatuses
runs, err := actions_service.GetRunsFromCommitStatuses(ctx, commitStatuses)
if err != nil {
ctx.ServerError("GetRunsFromCommitStatuses", err)
return
log.Error("GetRunsFromCommitStatuses: %v", err)
}
for _, run := range runs {
if run.NeedApproval {
@@ -439,14 +441,8 @@ func (prInfo *pullRequestViewInfo) prepareViewFillCommitStatusInfoForOpen(ctx *c
statusCheckData.CanApprove = ctx.Repo.Permission.CanWrite(unit.TypeActions)
}
pb := prInfo.ProtectedBranchRule
prInfo.enableStatusCheck = pb != nil && pb.EnableStatusCheck
if !prInfo.enableStatusCheck {
return
}
var missingRequiredChecks []string
for _, requiredContext := range pb.StatusCheckContexts {
for _, requiredContext := range pbRequiredContexts {
contextFound := false
matchesRequiredContext := createRequiredContextMatcher(requiredContext)
for _, presentStatus := range commitStatuses {
@@ -463,7 +459,7 @@ func (prInfo *pullRequestViewInfo) prepareViewFillCommitStatusInfoForOpen(ctx *c
statusCheckData.MissingRequiredChecks = missingRequiredChecks
statusCheckData.IsContextRequired = func(context string) bool {
for _, c := range pb.StatusCheckContexts {
for _, c := range pbRequiredContexts {
if c == context {
return true
}
@@ -478,7 +474,21 @@ func (prInfo *pullRequestViewInfo) prepareViewFillCommitStatusInfoForOpen(ctx *c
}
return false
}
statusCheckData.RequiredChecksState = pull_service.MergeRequiredContextsCommitStatus(commitStatuses, pb.StatusCheckContexts)
statusCheckData.RequiredChecksState = pull_service.MergeRequiredContextsCommitStatus(commitStatuses, pbRequiredContexts)
if data.enableStatusCheck {
if statusCheckData.RequiredChecksState.IsError() || statusCheckData.RequiredChecksState.IsFailure() {
data.infoProtectionBlockers.AddErrorItem(
svg.RenderHTML("octicon-x"),
ctx.Locale.Tr("repo.pulls.required_status_check_failed"),
)
} else if !statusCheckData.RequiredChecksState.IsSuccess() {
data.infoProtectionBlockers.AddErrorItem(
svg.RenderHTML("octicon-x"),
ctx.Locale.Tr("repo.pulls.required_status_check_missing"),
)
}
}
}
// prepareViewMergedPullInfo show meta information for a merged pull request view page
@@ -495,14 +505,16 @@ type pullCommitStatusCheckData struct {
CanApprove bool // whether the user can approve workflow runs
ApproveLink string // link to approve all checks
RequiredChecksState commitstatus.CommitStatusState
LatestCommitStatus *git_model.CommitStatus
pullCommitStatusState commitstatus.CommitStatusState
PullCommitStatuses []*git_model.CommitStatus
}
func (d *pullCommitStatusCheckData) CommitStatusCheckPrompt(locale translation.Locale) string {
if d.RequiredChecksState.IsPending() || len(d.MissingRequiredChecks) > 0 {
return locale.TrString("repo.pulls.status_checking")
} else if d.RequiredChecksState.IsSuccess() {
if d.LatestCommitStatus != nil && d.LatestCommitStatus.State.IsFailure() {
if d.pullCommitStatusState.IsFailure() {
return locale.TrString("repo.pulls.status_checks_failure_optional")
}
return locale.TrString("repo.pulls.status_checks_success")
@@ -558,7 +570,7 @@ func (prInfo *pullRequestViewInfo) prepareViewOpenPullInfo(ctx *context.Context)
ctx.Data["IsNothingToCompare"] = true
}
// this one is used by both sidebar and merge-box
// this one is used by: title edit, sidebar toggle, and merge-box toggle
prInfo.workInProgressPrefix = pull.GetWorkInProgressPrefix(ctx)
if pull.IsWorkInProgress(ctx) {
ctx.Data["IsPullWorkInProgress"] = prInfo.workInProgressPrefix != ""