mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-06 17:08:27 +02:00
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:
parent
89a49de0fd
commit
a90d5dd131
@ -1958,7 +1958,6 @@
|
||||
"repo.signing.wont_sign.headsigned": "The merge will not be signed as the head commit is not signed.",
|
||||
"repo.signing.wont_sign.commitssigned": "The merge will not be signed as all the associated commits are not signed.",
|
||||
"repo.signing.wont_sign.approved": "The merge will not be signed as the PR is not approved.",
|
||||
"repo.signing.wont_sign.not_signed_in": "You are not signed in.",
|
||||
"repo.ext_wiki": "Access to External Wiki",
|
||||
"repo.ext_wiki.desc": "Link to an external wiki.",
|
||||
"repo.wiki": "Wiki",
|
||||
|
||||
@ -624,7 +624,6 @@ func (cpi *comparePageInfoType) prepareCreatePullRequestPage(ctx *context.Contex
|
||||
return
|
||||
}
|
||||
ctx.Data["PullRequest"] = pr
|
||||
ctx.HTML(http.StatusOK, tplCompareDiff)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -28,6 +28,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/svg"
|
||||
"code.gitea.io/gitea/modules/templates/vars"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
@ -484,26 +485,57 @@ func prepareIssueViewSidebarDependency(ctx *context.Context, issue *issues_model
|
||||
ctx.Data["BlockingDependencies"], ctx.Data["BlockingDependenciesNotPermitted"] = checkBlockedByIssues(ctx, blocking)
|
||||
}
|
||||
|
||||
func (prInfo *pullRequestViewInfo) prepareMergeBoxRequireSigning(ctx *context.Context) {
|
||||
func (prInfo *pullRequestViewInfo) prepareMergeBoxCommitSigning(ctx *context.Context) {
|
||||
pull := prInfo.issue.PullRequest
|
||||
willSign := false
|
||||
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)
|
||||
willSign = sign
|
||||
ctx.Data["SigningKeyMergeDisplay"] = asymkey_model.GetDisplaySigningKey(key)
|
||||
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"
|
||||
wontSignReason = "error"
|
||||
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"
|
||||
}
|
||||
ctx.Data["WillSign"] = willSign
|
||||
prInfo.MergeBoxData.willSign = willSign
|
||||
|
||||
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(
|
||||
svg.RenderHTML("octicon-x"),
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func prepareIssueViewSidebarWatch(ctx *context.Context, issue *issues_model.Issue) {
|
||||
@ -571,8 +603,7 @@ func (prInfo *pullRequestViewInfo) prepareMergeBoxDeleteBranch(ctx *context.Cont
|
||||
|
||||
isPullBranchDeletable = !exist
|
||||
}
|
||||
ctx.Data["IsPullBranchDeletable"] = isPullBranchDeletable
|
||||
prInfo.MergeBoxData.isPullBranchDeletable = isPullBranchDeletable
|
||||
prInfo.MergeBoxData.IsPullBranchDeletable = isPullBranchDeletable
|
||||
}
|
||||
|
||||
func prepareIssueViewSidebarPin(ctx *context.Context, issue *issues_model.Issue) {
|
||||
@ -829,11 +860,6 @@ func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue *
|
||||
data := &pullMergeBoxData{}
|
||||
prInfo.MergeBoxData = data
|
||||
|
||||
statusCheckData := prInfo.StatusCheckData
|
||||
if statusCheckData == nil {
|
||||
statusCheckData = &pullCommitStatusCheckData{} // make the following logic easier, no need to keep checking "nil"
|
||||
}
|
||||
|
||||
canDelete := false
|
||||
canWriteToHeadRepo := false
|
||||
|
||||
@ -849,11 +875,6 @@ func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue *
|
||||
}
|
||||
}
|
||||
|
||||
if pull.IsFilesConflicted() {
|
||||
ctx.Data["IsPullFilesConflicted"] = true
|
||||
ctx.Data["ConflictedFiles"] = pull.ConflictedFiles
|
||||
}
|
||||
|
||||
if ctx.IsSigned {
|
||||
if err := pull.LoadHeadRepo(ctx); err != nil {
|
||||
log.Error("LoadHeadRepo: %v", err)
|
||||
@ -903,38 +924,13 @@ func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue *
|
||||
data.ReloadingInterval = util.Iif(pull.IsChecking(), 2000, 0)
|
||||
data.ShowMergeInstructions = canWriteToHeadRepo
|
||||
data.ShowPullCommands = pull.HeadRepo != nil && !pull.HasMerged && !issue.IsClosed
|
||||
ctx.Data["AllowMerge"] = data.allowMerge
|
||||
|
||||
pb := prInfo.ProtectedBranchRule
|
||||
if pb != nil {
|
||||
pb.Repo = pull.BaseRepo
|
||||
ctx.Data["ProtectedBranch"] = pb
|
||||
|
||||
data.isBlockedByApprovals = !issues_model.HasEnoughApprovals(ctx, pb, pull)
|
||||
ctx.Data["IsBlockedByApprovals"] = data.isBlockedByApprovals
|
||||
|
||||
data.isBlockedByRejection = issues_model.MergeBlockedByRejectedReview(ctx, pb, pull)
|
||||
ctx.Data["IsBlockedByRejection"] = data.isBlockedByRejection
|
||||
|
||||
data.isBlockedByOfficialReviewRequests = issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pull)
|
||||
ctx.Data["IsBlockedByOfficialReviewRequests"] = data.isBlockedByOfficialReviewRequests
|
||||
|
||||
data.isBlockedByOutdatedBranch = issues_model.MergeBlockedByOutdatedBranch(pb, pull)
|
||||
ctx.Data["IsBlockedByOutdatedBranch"] = data.isBlockedByOutdatedBranch
|
||||
|
||||
data.isBlockedByChangedProtectedFiles = len(pull.ChangedProtectedFiles) != 0
|
||||
ctx.Data["IsBlockedByChangedProtectedFiles"] = data.isBlockedByChangedProtectedFiles
|
||||
|
||||
data.requireSigned = pb.RequireSignedCommits
|
||||
ctx.Data["RequireSigned"] = data.requireSigned
|
||||
|
||||
ctx.Data["GrantedApprovals"] = issues_model.GetGrantedApprovalsCount(ctx, pb, pull)
|
||||
ctx.Data["ChangedProtectedFiles"] = pull.ChangedProtectedFiles
|
||||
ctx.Data["ChangedProtectedFilesNum"] = len(pull.ChangedProtectedFiles)
|
||||
ctx.Data["RequireApprovalsWhitelist"] = pb.EnableApprovalsWhitelist
|
||||
prInfo.prepareMergeBoxProtectionChecks(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
prInfo.prepareMergeBoxRequireSigning(ctx)
|
||||
prInfo.prepareMergeBoxCommitSigning(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
@ -947,18 +943,15 @@ func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue *
|
||||
prConfig := issue.Repo.MustGetUnit(ctx, unit.TypePullRequests).PullRequestsConfig()
|
||||
data.AutodetectManualMerge = prConfig.AutodetectManualMerge
|
||||
|
||||
enableStatusCheck := pb != nil && pb.EnableStatusCheck
|
||||
ctx.Data["EnableStatusCheck"] = enableStatusCheck
|
||||
|
||||
// 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
|
||||
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)
|
||||
data.hasStatusCheckBlocker = enableStatusCheck && !statusCheckData.RequiredChecksState.IsSuccess()
|
||||
data.hasStatusCheckBlocker = data.enableStatusCheck && !data.StatusCheckData.RequiredChecksState.IsSuccess()
|
||||
|
||||
// this logic is from:
|
||||
// {{$notAllOverridableChecksOk := or .IsBlockedByApprovals .IsBlockedByRejection .IsBlockedByOfficialReviewRequests .IsBlockedByOutdatedBranch .IsBlockedByChangedProtectedFiles (and .EnableStatusCheck (not $requiredStatusCheckState.IsSuccess))}}
|
||||
@ -971,13 +964,82 @@ func (prInfo *pullRequestViewInfo) prepareMergeBox(ctx *context.Context, issue *
|
||||
// {{$canMergeNow := and (or (and (not $.ProtectedBranch.BlockAdminMergeOverride) $.IsRepoAdmin) (not $notAllOverridableChecksOk)) (or (not .AllowMerge) (not .RequireSigned) .WillSign)}}
|
||||
// HINT: legacy "(not .AllowMerge)" is not right (always false, does nothing), fixed here
|
||||
// CanMergeNow means: if the doer has write permission, whether the PR can be merged now
|
||||
adminCanOverrideBlockers := (pb == nil || !pb.BlockAdminMergeOverride) && isRepoAdmin
|
||||
adminCanOverrideBlockers := (prInfo.ProtectedBranchRule == nil || !prInfo.ProtectedBranchRule.BlockAdminMergeOverride) && isRepoAdmin
|
||||
data.CanMergeNow = (!data.HasOverridableBlockers || adminCanOverrideBlockers) && // status checks are satisfied
|
||||
(!data.requireSigned || data.willSign) // signing requirement is satisfied
|
||||
|
||||
ctx.Data["PullMergeBoxData"] = prInfo.MergeBoxData
|
||||
prInfo.prepareMergeBoxFormProps(ctx)
|
||||
prInfo.prepareMergeBoxInfoItems(ctx)
|
||||
prInfo.prepareMergeBoxIconColor()
|
||||
|
||||
ctx.Data["PullMergeBoxData"] = prInfo.MergeBoxData
|
||||
}
|
||||
|
||||
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 = prInfo.issue.PullRequest.BaseRepo
|
||||
prInfo.ProtectedBranchRule = pb
|
||||
}
|
||||
|
||||
prInfo.prepareMergeBoxStatusCheckData(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
|
||||
prInfo.prepareMergeBoxProtectedRules(ctx)
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (prInfo *pullRequestViewInfo) prepareMergeBoxProtectedRules(ctx *context.Context) {
|
||||
pb := prInfo.ProtectedBranchRule
|
||||
if pb == nil {
|
||||
return
|
||||
}
|
||||
|
||||
pull := prInfo.issue.PullRequest
|
||||
data := prInfo.MergeBoxData
|
||||
|
||||
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(svg.RenderHTML("octicon-x"), blockerInfo)
|
||||
}
|
||||
|
||||
data.isBlockedByRejection = issues_model.MergeBlockedByRejectedReview(ctx, pb, pull)
|
||||
if data.isBlockedByRejection {
|
||||
data.infoProtectionBlockers.AddErrorItem(svg.RenderHTML("octicon-x"), ctx.Locale.Tr("repo.pulls.blocked_by_rejection"))
|
||||
}
|
||||
|
||||
data.isBlockedByOfficialReviewRequests = issues_model.MergeBlockedByOfficialReviewRequests(ctx, pb, pull)
|
||||
if data.isBlockedByOfficialReviewRequests {
|
||||
data.infoProtectionBlockers.AddErrorItem(svg.RenderHTML("octicon-x"), ctx.Locale.Tr("repo.pulls.blocked_by_official_review_requests"))
|
||||
}
|
||||
|
||||
data.isBlockedByOutdatedBranch = issues_model.MergeBlockedByOutdatedBranch(pb, pull)
|
||||
if data.isBlockedByOutdatedBranch {
|
||||
data.infoProtectionBlockers.AddErrorItem(svg.RenderHTML("octicon-x"), 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(
|
||||
svg.RenderHTML("octicon-x"),
|
||||
ctx.Locale.TrN(len(pull.ChangedProtectedFiles), "repo.pulls.blocked_by_changed_protected_files_1", "repo.pulls.blocked_by_changed_protected_files_n"),
|
||||
detailItems,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func prepareIssueViewContent(ctx *context.Context, issue *issues_model.Issue) {
|
||||
|
||||
@ -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 != ""
|
||||
|
||||
@ -3,31 +3,190 @@
|
||||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
|
||||
"code.gitea.io/gitea/modules/htmlutil"
|
||||
"code.gitea.io/gitea/modules/svg"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
type pullMergeBoxInfoItem struct {
|
||||
ItemClass string
|
||||
SvgIconHTML template.HTML
|
||||
InfoHTML template.HTML
|
||||
ListItems []template.HTML
|
||||
}
|
||||
|
||||
type pullMergeBoxInfoItemCollection struct {
|
||||
items []*pullMergeBoxInfoItem
|
||||
}
|
||||
|
||||
type pullInfoSection struct {
|
||||
InfoItems []*pullMergeBoxInfoItem
|
||||
}
|
||||
|
||||
func escapeStringSliceToHTML(s []string) (ret []template.HTML) {
|
||||
for _, v := range s {
|
||||
ret = append(ret, template.HTML(template.HTMLEscapeString(v)))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *pullMergeBoxInfoItemCollection) AddInfoItem(svg, info template.HTML, optItems ...[]template.HTML) {
|
||||
c.items = append(c.items, &pullMergeBoxInfoItem{
|
||||
SvgIconHTML: svg,
|
||||
InfoHTML: info,
|
||||
ListItems: util.OptionalArg(optItems),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *pullMergeBoxInfoItemCollection) AddErrorItem(svg, info template.HTML, optItems ...[]template.HTML) {
|
||||
c.items = append(c.items, &pullMergeBoxInfoItem{
|
||||
ItemClass: "tw-text-red",
|
||||
SvgIconHTML: svg,
|
||||
InfoHTML: info,
|
||||
ListItems: util.OptionalArg(optItems),
|
||||
})
|
||||
}
|
||||
|
||||
func (prInfo *pullRequestViewInfo) prepareMergeBoxIconColor() {
|
||||
pull := prInfo.issue.PullRequest
|
||||
mergeBoxData := prInfo.MergeBoxData
|
||||
statusCheckData := prInfo.StatusCheckData
|
||||
|
||||
showAsNormalColor := prInfo.issue.IsClosed || prInfo.workInProgressPrefix != "" || pull.IsEmpty() || pull.IsFilesConflicted()
|
||||
showAsErrorColor := false
|
||||
showAsWarningColor := pull.IsChecking()
|
||||
|
||||
if statusCheckData := mergeBoxData.StatusCheckData; statusCheckData != nil {
|
||||
showAsErrorColor = statusCheckData.pullCommitStatusState.IsError() || statusCheckData.pullCommitStatusState.IsFailure() ||
|
||||
statusCheckData.RequiredChecksState.IsError() || statusCheckData.RequiredChecksState.IsFailure()
|
||||
|
||||
showAsWarningColor = showAsWarningColor ||
|
||||
statusCheckData.pullCommitStatusState.IsWarning() || statusCheckData.pullCommitStatusState.IsPending() ||
|
||||
(mergeBoxData.enableStatusCheck && (statusCheckData.RequiredChecksState.IsWarning() || statusCheckData.RequiredChecksState.IsPending()))
|
||||
}
|
||||
|
||||
hasBlockers := len(mergeBoxData.infoCommitBlockers.items) > 0 || len(mergeBoxData.infoProtectionBlockers.items) > 0
|
||||
|
||||
switch {
|
||||
case pull.HasMerged:
|
||||
prInfo.MergeBoxData.TimelineIconClass = "tw-text-purple"
|
||||
case prInfo.issue.IsClosed, prInfo.workInProgressPrefix != "", pull.IsFilesConflicted():
|
||||
case showAsNormalColor:
|
||||
prInfo.MergeBoxData.TimelineIconClass = "tw-text-text-light"
|
||||
case prInfo.IsPullRequestBroken, mergeBoxData.isBlockedByApprovals, mergeBoxData.isBlockedByRejection,
|
||||
mergeBoxData.isBlockedByOfficialReviewRequests, mergeBoxData.isBlockedByOutdatedBranch, mergeBoxData.isBlockedByChangedProtectedFiles:
|
||||
case showAsErrorColor:
|
||||
prInfo.MergeBoxData.TimelineIconClass = "tw-text-red"
|
||||
case prInfo.enableStatusCheck && (statusCheckData.RequiredChecksState.IsFailure() || statusCheckData.RequiredChecksState.IsError()):
|
||||
prInfo.MergeBoxData.TimelineIconClass = "tw-text-red"
|
||||
case prInfo.enableStatusCheck && (statusCheckData.LatestCommitStatus == nil || statusCheckData.RequiredChecksState.IsPending() || statusCheckData.RequiredChecksState.IsWarning()):
|
||||
case showAsWarningColor:
|
||||
prInfo.MergeBoxData.TimelineIconClass = "tw-text-yellow"
|
||||
case mergeBoxData.allowMerge && mergeBoxData.requireSigned && !mergeBoxData.willSign:
|
||||
case hasBlockers:
|
||||
prInfo.MergeBoxData.TimelineIconClass = "tw-text-red"
|
||||
case pull.IsChecking():
|
||||
prInfo.MergeBoxData.TimelineIconClass = "tw-text-yellow"
|
||||
case pull.IsEmpty():
|
||||
prInfo.MergeBoxData.TimelineIconClass = "tw-text-text-light"
|
||||
case pull.IsStatusMergeable():
|
||||
prInfo.MergeBoxData.TimelineIconClass = "tw-text-green"
|
||||
default:
|
||||
prInfo.MergeBoxData.TimelineIconClass = "tw-text-red"
|
||||
prInfo.MergeBoxData.TimelineIconClass = "tw-text-text-light"
|
||||
}
|
||||
}
|
||||
|
||||
func (prInfo *pullRequestViewInfo) prepareMergeBoxInfoItems(ctx *context.Context) {
|
||||
pull := prInfo.issue.PullRequest
|
||||
data := prInfo.MergeBoxData
|
||||
|
||||
if pull.HasMerged && data.IsPullBranchDeletable {
|
||||
data.ClosedInfoTitle = ctx.Locale.Tr("repo.pulls.merged_success")
|
||||
data.ClosedInfoBody = ctx.Locale.Tr("repo.pulls.merged_info_text", htmlutil.HTMLFormat("<code>%s</code>", prInfo.headTarget))
|
||||
return
|
||||
} else if prInfo.issue.IsClosed {
|
||||
data.ClosedInfoTitle = ctx.Locale.Tr("repo.pulls.closed")
|
||||
if prInfo.IsPullRequestBroken {
|
||||
data.ClosedInfoBody = ctx.Locale.Tr("repo.pulls.cant_reopen_deleted_branch")
|
||||
} else {
|
||||
data.ClosedInfoBody = ctx.Locale.Tr("repo.pulls.reopen_to_merge")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if pull.IsFilesConflicted() {
|
||||
detailItems := escapeStringSliceToHTML(pull.ConflictedFiles)
|
||||
if len(detailItems) == 0 {
|
||||
detailItems = append(detailItems, ctx.Locale.Tr("repo.pulls.files_conflicted_no_listed_files"))
|
||||
}
|
||||
if len(detailItems) > 10 {
|
||||
detailItems = detailItems[:10]
|
||||
detailItems = append(detailItems, "...")
|
||||
}
|
||||
prInfo.MergeBoxData.infoCommitBlockers.AddInfoItem(
|
||||
svg.RenderHTML("octicon-x"),
|
||||
ctx.Locale.Tr("repo.pulls.files_conflicted"),
|
||||
detailItems,
|
||||
)
|
||||
}
|
||||
|
||||
if prInfo.IsPullRequestBroken {
|
||||
prInfo.MergeBoxData.infoCommitBlockers.AddInfoItem(
|
||||
svg.RenderHTML("octicon-x"),
|
||||
ctx.Locale.Tr("repo.pulls.data_broken"),
|
||||
)
|
||||
}
|
||||
|
||||
if pull.IsChecking() {
|
||||
prInfo.MergeBoxData.infoCommitBlockers.AddInfoItem(
|
||||
svg.RenderHTML("gitea-running", 16, "rotate-clockwise"),
|
||||
ctx.Locale.Tr("repo.pulls.is_checking"),
|
||||
)
|
||||
}
|
||||
|
||||
if pull.IsAncestor() {
|
||||
prInfo.MergeBoxData.infoCommitBlockers.AddInfoItem(
|
||||
svg.RenderHTML("octicon-alert"),
|
||||
ctx.Locale.Tr("repo.pulls.is_ancestor"),
|
||||
)
|
||||
}
|
||||
|
||||
if !pull.IsStatusMergeable() {
|
||||
// it is only a "protection" level blocker, it can be bypassed by admin (e.g.: manually merged)
|
||||
if pull.IsEmpty() {
|
||||
prInfo.MergeBoxData.infoProtectionBlockers.AddInfoItem(
|
||||
svg.RenderHTML("octicon-alert"),
|
||||
ctx.Locale.Tr("repo.pulls.is_empty"),
|
||||
)
|
||||
} else {
|
||||
prInfo.MergeBoxData.infoProtectionBlockers.AddErrorItem(
|
||||
svg.RenderHTML("octicon-x"),
|
||||
ctx.Locale.Tr("repo.pulls.cannot_auto_merge_desc"),
|
||||
)
|
||||
prInfo.MergeBoxData.infoProtectionBlockers.AddInfoItem(
|
||||
svg.RenderHTML("octicon-info"),
|
||||
ctx.Locale.Tr("repo.pulls.cannot_auto_merge_helper"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if !data.allowMerge {
|
||||
prInfo.MergeBoxData.infoProtectionBlockers.AddInfoItem(
|
||||
svg.RenderHTML("octicon-info"),
|
||||
ctx.Locale.Tr("repo.pulls.no_merge_access"),
|
||||
)
|
||||
}
|
||||
|
||||
if data.CanMergeNow {
|
||||
if data.HasOverridableBlockers {
|
||||
prInfo.MergeBoxData.infoMergePrompts.AddInfoItem(
|
||||
svg.RenderHTML("octicon-dot-fill"),
|
||||
ctx.Locale.Tr("repo.pulls.required_status_check_administrator"),
|
||||
)
|
||||
} else if pull.IsStatusMergeable() || pull.IsEmpty() {
|
||||
prInfo.MergeBoxData.infoMergePrompts.AddInfoItem(
|
||||
svg.RenderHTML("octicon-check"),
|
||||
ctx.Locale.Tr("repo.pulls.can_auto_merge_desc"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if len(data.infoCommitBlockers.items) > 0 {
|
||||
data.InfoSections = append(data.InfoSections, &pullInfoSection{data.infoCommitBlockers.items})
|
||||
} else {
|
||||
data.InfoSections = append(data.InfoSections, &pullInfoSection{data.infoProtectionBlockers.items})
|
||||
}
|
||||
data.InfoSections = append(data.InfoSections, &pullInfoSection{data.infoMergePrompts.items})
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
pull_model "code.gitea.io/gitea/models/pull"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/modules/svg"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
pull_service "code.gitea.io/gitea/services/pull"
|
||||
@ -91,7 +92,7 @@ func (prInfo *pullRequestViewInfo) prepareMergeBoxFormProps(ctx *context.Context
|
||||
"allOverridableChecksOk": allOverridableChecksOk,
|
||||
"emptyCommit": pull.IsEmpty(),
|
||||
"pullHeadCommitID": prInfo.CompareInfo.HeadCommitID,
|
||||
"isPullBranchDeletable": prInfo.MergeBoxData.isPullBranchDeletable,
|
||||
"isPullBranchDeletable": prInfo.MergeBoxData.IsPullBranchDeletable,
|
||||
"defaultMergeStyle": mergeStyle,
|
||||
"defaultDeleteBranchAfterMerge": prConfig.DefaultDeleteBranchAfterMerge,
|
||||
"mergeMessageFieldPlaceHolder": ctx.Locale.Tr("repo.editor.commit_message_desc"),
|
||||
@ -148,12 +149,6 @@ func (prInfo *pullRequestViewInfo) prepareMergeBoxFormProps(ctx *context.Context
|
||||
}
|
||||
}
|
||||
|
||||
canUseManualMerge := func() bool {
|
||||
if pull.IsWorkInProgress(ctx) || pull.IsChecking() {
|
||||
return false
|
||||
}
|
||||
return prConfig.AllowManualMerge
|
||||
}
|
||||
// Manually Merged is not a well-known feature, it is used to mark a non-mergeable PR (already merged, conflicted) as merged
|
||||
// To test it:
|
||||
// Enable "Manually Merged" feature in the Repository Settings
|
||||
@ -161,7 +156,8 @@ func (prInfo *pullRequestViewInfo) prepareMergeBoxFormProps(ctx *context.Context
|
||||
// - Merge the pull request branch locally and push the merged commit to Gitea
|
||||
// - Make some conflicts between the base branch and the pull request branch
|
||||
// Then the Manually Merged form will be shown in the merge form
|
||||
if canUseManualMerge() {
|
||||
canUseManualMerge := !pull.IsWorkInProgress(ctx) && !pull.IsChecking() && prConfig.AllowManualMerge
|
||||
if canUseManualMerge {
|
||||
mergeStyles = append(mergeStyles, map[string]any{
|
||||
"name": "manually-merged",
|
||||
"allowed": prConfig.AllowManualMerge,
|
||||
@ -174,5 +170,15 @@ func (prInfo *pullRequestViewInfo) prepareMergeBoxFormProps(ctx *context.Context
|
||||
if len(mergeStyles) > 0 {
|
||||
mergeFormProps["mergeStyles"] = mergeStyles
|
||||
prInfo.MergeBoxData.MergeFormProps = mergeFormProps
|
||||
} else if pull.IsStatusMergeable() {
|
||||
// no merge style was set in repo setting
|
||||
prInfo.MergeBoxData.infoCommitBlockers.AddInfoItem(
|
||||
svg.RenderHTML("octicon-x", 16, "tw-text-red"),
|
||||
ctx.Locale.Tr("repo.pulls.no_merge_desc"),
|
||||
)
|
||||
prInfo.MergeBoxData.infoCommitBlockers.AddInfoItem(
|
||||
svg.RenderHTML("octicon-info"),
|
||||
ctx.Locale.Tr("repo.pulls.no_merge_helper"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,219 +7,67 @@
|
||||
data-pull-link="{{$.Issue.Link}}"
|
||||
{{end}}
|
||||
>
|
||||
{{$statusCheckData := .StatusCheckData}}
|
||||
{{$requiredStatusCheckState := $statusCheckData.RequiredChecksState}}
|
||||
<div class="timeline-avatar {{$data.TimelineIconClass}}">{{svg "octicon-git-merge" 40}}</div>
|
||||
<div class="content">
|
||||
<div class="ui segment fitted avatar-content-left-arrow">
|
||||
<div class="merge-section flex-divided-list flex-items-block items-px-default">
|
||||
{{if .LatestCommitStatus}}
|
||||
{{template "repo/issue/view_content/pull_merge_status_checks" (dict
|
||||
"CommitStatuses" .LatestCommitStatuses
|
||||
"StatusCheckData" $statusCheckData
|
||||
)}}
|
||||
<div class="merge-section flex-divided-list items-px-default">
|
||||
{{if $data.ShowStatusCheck}}
|
||||
{{template "repo/issue/view_content/pull_merge_status_checks" (dict "StatusCheckData" $data.StatusCheckData)}}
|
||||
{{end}}
|
||||
|
||||
{{if .Issue.PullRequest.HasMerged}}
|
||||
{{if .IsPullBranchDeletable}}
|
||||
<div class="item item-section text tw-flex-1">
|
||||
<div class="item-section-left">
|
||||
<h3 class="tw-mb-2">
|
||||
{{ctx.Locale.Tr "repo.pulls.merged_success"}}
|
||||
</h3>
|
||||
<div class="merge-section-info">
|
||||
{{ctx.Locale.Tr "repo.pulls.merged_info_text" (HTMLFormat "<code>%s</code>" .HeadTarget)}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-section-right">
|
||||
{{if $data.ClosedInfoTitle}}
|
||||
<div class="item flex-left-right">
|
||||
<div>
|
||||
<h3 class="tw-mb-2">{{$data.ClosedInfoTitle}}</h3>
|
||||
<div>{{$data.ClosedInfoBody}}</div>
|
||||
</div>
|
||||
{{if $data.IsPullBranchDeletable}}
|
||||
<div>
|
||||
<button class="ui button link-action delete-branch-after-merge" data-url="{{.DeleteBranchLink}}">{{ctx.Locale.Tr "repo.branch.delete_html"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{range $infoSection := $data.InfoSections}}
|
||||
{{if $infoSection.InfoItems}}
|
||||
<div class="item">
|
||||
{{range $infoItem := $infoSection.InfoItems}}
|
||||
<div class="flex-text-block {{$infoItem.ItemClass}}">{{$infoItem.SvgIconHTML}} {{$infoItem.InfoHTML}}</div>
|
||||
{{if $infoItem.ListItems}}
|
||||
<ul class="tw-pl-[36px]">{{/* align with the info icon and text */}}
|
||||
{{range $listItem := $infoItem.ListItems}}
|
||||
<li>{{$listItem}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{else if .Issue.IsClosed}}
|
||||
<div class="item item-section text tw-flex-1">
|
||||
<div class="item-section-left">
|
||||
<h3 class="tw-mb-2">{{ctx.Locale.Tr "repo.pulls.closed"}}</h3>
|
||||
<div class="merge-section-info">
|
||||
{{if .IsPullRequestBroken}}
|
||||
{{ctx.Locale.Tr "repo.pulls.cant_reopen_deleted_branch"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.pulls.reopen_to_merge"}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{if and .IsPullBranchDeletable (not .IsPullRequestBroken)}}
|
||||
<div class="item-section-right">
|
||||
<button class="ui button link-action delete-branch-after-merge" data-url="{{.DeleteBranchLink}}">{{ctx.Locale.Tr "repo.branch.delete_html"}}</button>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{else if .IsPullFilesConflicted}}
|
||||
{{end}}
|
||||
{{if $data.ShowUpdatePullInfo}}
|
||||
<div class="item">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.files_conflicted"}}
|
||||
{{template "repo/issue/view_content/update_branch_by_merge" (dict "MergeBoxData" $data "Issue" $.Issue)}}
|
||||
</div>
|
||||
<ul class="item">
|
||||
{{range .ConflictedFiles}}
|
||||
<li>{{.}}</li>
|
||||
{{else}}
|
||||
<li>{{ctx.Locale.Tr "repo.pulls.files_conflicted_no_listed_files"}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{else if .IsPullRequestBroken}}
|
||||
<div class="item">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.data_broken"}}
|
||||
</div>
|
||||
{{else if .IsPullWorkInProgress}}
|
||||
<div class="item">
|
||||
<div class="item-section-left flex-text-inline tw-flex-1">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.cannot_merge_work_in_progress"}}
|
||||
{{end}}
|
||||
|
||||
{{if $.IsPullWorkInProgress}}
|
||||
<div class="item flex-left-right">
|
||||
<div class="flex-text-block">
|
||||
{{svg "octicon-x"}} {{ctx.Locale.Tr "repo.pulls.cannot_merge_work_in_progress"}}
|
||||
</div>
|
||||
{{if or .HasIssuesOrPullsWritePermission .IsIssuePoster}}
|
||||
<button class="ui compact button" data-global-init="initPullRequestWipToggle" data-title="{{.Issue.Title}}" data-wip-prefix="{{.WorkInProgressPrefix}}" data-update-url="{{.Issue.Link}}/title">
|
||||
{{ctx.Locale.Tr "repo.pulls.remove_prefix" .WorkInProgressPrefix}}
|
||||
</button>
|
||||
<button class="ui compact button" data-global-init="initPullRequestWipToggle" data-title="{{.Issue.Title}}" data-wip-prefix="{{.WorkInProgressPrefix}}" data-update-url="{{.Issue.Link}}/title">
|
||||
{{ctx.Locale.Tr "repo.pulls.remove_prefix" .WorkInProgressPrefix}}
|
||||
</button>
|
||||
{{end}}
|
||||
</div>
|
||||
{{template "repo/issue/view_content/update_branch_by_merge" $}}
|
||||
{{else if .Issue.PullRequest.IsChecking}}
|
||||
<div class="item">
|
||||
{{svg "gitea-running" 16 "rotate-clockwise"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.is_checking"}}
|
||||
</div>
|
||||
{{else if .Issue.PullRequest.IsAncestor}}
|
||||
<div class="item">
|
||||
{{svg "octicon-alert"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.is_ancestor"}}
|
||||
</div>
|
||||
{{else}}
|
||||
{{if .IsBlockedByApprovals}}
|
||||
<div class="item">
|
||||
{{svg "octicon-x"}}
|
||||
{{if .RequireApprovalsWhitelist}}
|
||||
{{ctx.Locale.Tr "repo.pulls.blocked_by_approvals_whitelisted" .GrantedApprovals .ProtectedBranch.RequiredApprovals}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "repo.pulls.blocked_by_approvals" .GrantedApprovals .ProtectedBranch.RequiredApprovals}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{else if .IsBlockedByRejection}}
|
||||
<div class="item">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.blocked_by_rejection"}}
|
||||
</div>
|
||||
{{else if .IsBlockedByOfficialReviewRequests}}
|
||||
<div class="item">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.blocked_by_official_review_requests"}}
|
||||
</div>
|
||||
{{else if .IsBlockedByOutdatedBranch}}
|
||||
<div class="item">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.blocked_by_outdated_branch"}}
|
||||
</div>
|
||||
{{else if .IsBlockedByChangedProtectedFiles}}
|
||||
<div class="item">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.TrN $.ChangedProtectedFilesNum "repo.pulls.blocked_by_changed_protected_files_1" "repo.pulls.blocked_by_changed_protected_files_n"}}
|
||||
</div>
|
||||
<ul class="item">
|
||||
{{range .ChangedProtectedFiles}}
|
||||
<li>{{.}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{else if and .EnableStatusCheck (or $requiredStatusCheckState.IsError $requiredStatusCheckState.IsFailure)}}
|
||||
<div class="item">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.required_status_check_failed"}}
|
||||
</div>
|
||||
{{else if and .EnableStatusCheck (not $requiredStatusCheckState.IsSuccess)}}
|
||||
<div class="item">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.required_status_check_missing"}}
|
||||
</div>
|
||||
{{else if and .AllowMerge .RequireSigned (not .WillSign)}}
|
||||
<div class="item">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.require_signed_wont_sign"}}
|
||||
</div>
|
||||
<div class="item">
|
||||
{{svg "octicon-unlock"}}
|
||||
{{ctx.Locale.Tr (printf "repo.signing.wont_sign.%s" .WontSignReason)}}
|
||||
</div>
|
||||
{{else if not .Issue.PullRequest.IsStatusMergeable}}
|
||||
<div class="item tw-text-red">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.cannot_auto_merge_desc"}}
|
||||
</div>
|
||||
<div class="item">
|
||||
{{svg "octicon-info"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.cannot_auto_merge_helper"}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{$notAllOverridableChecksOk := $data.HasOverridableBlockers}}
|
||||
{{$canMergeNow := $data.CanMergeNow}}
|
||||
|
||||
{{if $canMergeNow}}
|
||||
{{if $notAllOverridableChecksOk}}
|
||||
<div class="item">
|
||||
{{svg "octicon-dot-fill"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.required_status_check_administrator"}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="item">
|
||||
{{svg "octicon-check"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.can_auto_merge_desc"}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .WillSign}}
|
||||
<div class="item">
|
||||
{{svg "octicon-lock" 16 "tw-text-green"}}
|
||||
{{ctx.Locale.Tr "repo.signing.will_sign" .SigningKeyMergeDisplay}}
|
||||
</div>
|
||||
{{else if .IsSigned}}
|
||||
<div class="item">
|
||||
{{svg "octicon-unlock"}}
|
||||
{{ctx.Locale.Tr (printf "repo.signing.wont_sign.%s" .WontSignReason)}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if .Issue.PullRequest.IsEmpty}}
|
||||
<div class="item">
|
||||
{{svg "octicon-alert"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.is_empty"}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{template "repo/issue/view_content/update_branch_by_merge" (dict "MergeBoxData" $data "Issue" $.Issue)}}
|
||||
|
||||
{{if not .AllowMerge}} {{/* user is allowed to merge */}}
|
||||
{{/* user is not allowed to merge */}}
|
||||
<div class="item">
|
||||
{{svg "octicon-info"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.no_merge_access"}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}{{/* end if: pull request status */}}
|
||||
{{end}}
|
||||
|
||||
{{if $data.MergeFormProps}}
|
||||
{{/* The merge form is a Vue component. After mounted, it has a button for choosing merge style, so make it have min-height to avoid layout shifting */}}
|
||||
<div class="item">
|
||||
<div id="pull-request-merge-form" class="tw-min-h-[40px] tw-w-full" data-merge-form-props="{{JsonUtils.EncodeToString $data.MergeFormProps}}"></div>
|
||||
</div>
|
||||
{{else if and .AllowMerge .Issue.PullRequest.IsStatusMergeable}}
|
||||
{{/* no merge style was set in repo setting */}}
|
||||
<div class="item tw-text-red">
|
||||
{{svg "octicon-x"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.no_merge_desc"}}
|
||||
</div>
|
||||
<div class="item">
|
||||
{{svg "octicon-info"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.no_merge_helper"}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if $data.ShowPullCommands}}
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
{{/* Template Attributes:
|
||||
* CommitStatuses: all commit status elements
|
||||
* StatusCheckData: additional status check data, see backend pullCommitStatusCheckData struct
|
||||
* StatusCheckData: see backend pullCommitStatusCheckData struct
|
||||
*/}}
|
||||
{{$commitStatuses := $.CommitStatuses}}
|
||||
{{$statusCheckData := $.StatusCheckData}}
|
||||
{{if $statusCheckData}}
|
||||
{{$commitStatuses := $statusCheckData.PullCommitStatuses}}
|
||||
<div class="item flex-left-right commit-status-toggle">
|
||||
<div>{{$statusCheckData.CommitStatusCheckPrompt ctx.Locale}}</div>
|
||||
<button data-global-click="onCommitStatusChecksToggle" class="btn interact-fg"
|
||||
|
||||
@ -1,35 +1,33 @@
|
||||
{{$data := $.MergeBoxData}}
|
||||
{{$issue := $.Issue}}
|
||||
{{if $data.ShowUpdatePullInfo}}
|
||||
<div class="item flex-left-right">
|
||||
<div class="flex-text-block">
|
||||
{{svg "octicon-alert"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.outdated_with_base_branch"}}
|
||||
</div>
|
||||
<div>
|
||||
{{if and $data.UpdateAllowed $data.UpdateByRebaseAllowed}}
|
||||
<div id="update-pr-branch-with-base" class="ui buttons">
|
||||
<button class="ui button" data-do="{{$issue.Link}}/update">
|
||||
<span class="button-text">
|
||||
{{ctx.Locale.Tr "repo.pulls.update_branch"}}
|
||||
</span>
|
||||
</button>
|
||||
<div class="ui dropdown icon button">
|
||||
{{svg "octicon-triangle-down"}}
|
||||
<div class="menu">
|
||||
<a class="item active selected" data-do="{{$issue.Link}}/update">{{ctx.Locale.Tr "repo.pulls.update_branch"}}</a>
|
||||
<a class="item" data-do="{{$issue.Link}}/update?style=rebase">{{ctx.Locale.Tr "repo.pulls.update_branch_rebase"}}</a>
|
||||
</div>
|
||||
<div class="tw-w-full flex-left-right">
|
||||
<div class="flex-text-block">
|
||||
{{svg "octicon-alert"}}
|
||||
{{ctx.Locale.Tr "repo.pulls.outdated_with_base_branch"}}
|
||||
</div>
|
||||
<div>
|
||||
{{if and $data.UpdateAllowed $data.UpdateByRebaseAllowed}}
|
||||
<div id="update-pr-branch-with-base" class="ui buttons">
|
||||
<button class="ui button" data-do="{{$issue.Link}}/update">
|
||||
<span class="button-text">
|
||||
{{ctx.Locale.Tr "repo.pulls.update_branch"}}
|
||||
</span>
|
||||
</button>
|
||||
<div class="ui dropdown icon button">
|
||||
{{svg "octicon-triangle-down"}}
|
||||
<div class="menu">
|
||||
<a class="item active selected" data-do="{{$issue.Link}}/update">{{ctx.Locale.Tr "repo.pulls.update_branch"}}</a>
|
||||
<a class="item" data-do="{{$issue.Link}}/update?style=rebase">{{ctx.Locale.Tr "repo.pulls.update_branch_rebase"}}</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if and $data.UpdateAllowed (not $data.UpdateByRebaseAllowed)}}
|
||||
<form action="{{$issue.Link}}/update" method="post">
|
||||
<button class="ui compact button">
|
||||
<span class="ui text">{{ctx.Locale.Tr "repo.pulls.update_branch"}}</span>
|
||||
</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if and $data.UpdateAllowed (not $data.UpdateByRebaseAllowed)}}
|
||||
<form action="{{$issue.Link}}/update" method="post">
|
||||
<button class="ui compact button">
|
||||
<span class="ui text">{{ctx.Locale.Tr "repo.pulls.update_branch"}}</span>
|
||||
</button>
|
||||
</form>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
@ -31,8 +31,7 @@ func TestPullCreate_CommitStatus(t *testing.T) {
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "status1", "README.md", "status1")
|
||||
|
||||
url := "/" + path.Join("user1", "repo1", "compare", "master...status1")
|
||||
req := NewRequestWithValues(t, "POST", url,
|
||||
req := NewRequestWithValues(t, "POST", "/user1/repo1/compare/master...status1",
|
||||
map[string]string{
|
||||
"title": "pull request from status1",
|
||||
},
|
||||
@ -121,8 +120,7 @@ func TestPullCreate_EmptyChangesWithDifferentCommits(t *testing.T) {
|
||||
testEditFileToNewBranch(t, session, "user1", "repo1", "master", "status1", "README.md", "status1")
|
||||
testEditFile(t, session, "user1", "repo1", "status1", "README.md", "# repo1\n\nDescription for repo1")
|
||||
|
||||
url := "/" + path.Join("user1", "repo1", "compare", "master...status1")
|
||||
req := NewRequestWithValues(t, "POST", url,
|
||||
req := NewRequestWithValues(t, "POST", "/user1/repo1/compare/master...status1",
|
||||
map[string]string{
|
||||
"title": "pull request from status1",
|
||||
},
|
||||
@ -134,6 +132,7 @@ func TestPullCreate_EmptyChangesWithDifferentCommits(t *testing.T) {
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
|
||||
text := strings.TrimSpace(doc.doc.Find(".merge-section").Text())
|
||||
assert.Contains(t, text, "The changes on this branch are already on the target branch. This will be an empty commit.")
|
||||
assert.Contains(t, text, "This pull request can be merged automatically.")
|
||||
})
|
||||
}
|
||||
@ -143,8 +142,7 @@ func TestPullCreate_EmptyChangesWithSameCommits(t *testing.T) {
|
||||
session := loginUser(t, "user1")
|
||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
||||
testCreateBranch(t, session, "user1", "repo1", "branch/master", "status1", http.StatusSeeOther)
|
||||
url := "/" + path.Join("user1", "repo1", "compare", "master...status1")
|
||||
req := NewRequestWithValues(t, "POST", url,
|
||||
req := NewRequestWithValues(t, "POST", "/user1/repo1/compare/master...status1",
|
||||
map[string]string{
|
||||
"title": "pull request from status1",
|
||||
},
|
||||
|
||||
@ -596,27 +596,6 @@ td .commit-summary {
|
||||
}
|
||||
}
|
||||
|
||||
.repository.view.issue .comment-list .comment .merge-section .item + ul.item {
|
||||
border-top: 0;
|
||||
padding: 0 1em 0 52px;
|
||||
margin-top: -0.5em;
|
||||
}
|
||||
|
||||
.repository.view.issue .comment-list .comment .merge-section .item-section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.merge-section-info code {
|
||||
border: 1px solid var(--color-light-border);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 2px 4px;
|
||||
background: var(--color-light);
|
||||
}
|
||||
|
||||
.repository.view.issue .comment-list .comment .no-content {
|
||||
color: var(--color-text-light-2);
|
||||
font-style: italic;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user