From 9cc80fe895265e87b227e03ae18439022dda0d92 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Wed, 15 Oct 2025 21:38:19 -0600 Subject: [PATCH 01/16] add quick approve button --- models/git/commit_status.go | 15 ++- routers/web/repo/actions/view.go | 91 +++++++++++++++---- routers/web/repo/pull.go | 16 ++++ routers/web/web.go | 1 + services/actions/commit_status.go | 70 ++++++++++++++ .../issue/view_content/pull_merge_box.tmpl | 5 + templates/repo/pulls/status.tmpl | 21 +++-- 7 files changed, 191 insertions(+), 28 deletions(-) diff --git a/models/git/commit_status.go b/models/git/commit_status.go index e255bca5d0201..ca7279b516f2f 100644 --- a/models/git/commit_status.go +++ b/models/git/commit_status.go @@ -211,21 +211,26 @@ func (status *CommitStatus) LocaleString(lang translation.Locale) string { // HideActionsURL set `TargetURL` to an empty string if the status comes from Gitea Actions func (status *CommitStatus) HideActionsURL(ctx context.Context) { + if status.CreatedByGiteaActions(ctx) { + status.TargetURL = "" + } +} + +// CreatedByGiteaActions returns true if the commit status is created by Gitea Actions +func (status *CommitStatus) CreatedByGiteaActions(ctx context.Context) bool { if status.RepoID == 0 { - return + return false } if status.Repo == nil { if err := status.loadRepository(ctx); err != nil { log.Error("loadRepository: %v", err) - return + return false } } prefix := status.Repo.Link() + "/actions" - if strings.HasPrefix(status.TargetURL, prefix) { - status.TargetURL = "" - } + return strings.HasPrefix(status.TargetURL, prefix) } // CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index b409e887bee34..3acbd765b33ae 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -605,33 +605,53 @@ func Cancel(ctx *context_module.Context) { func Approve(ctx *context_module.Context) { runIndex := getRunIndex(ctx) - current, jobs := getRunJobs(ctx, runIndex, -1) + approveRuns(ctx, []int64{runIndex}) if ctx.Written() { return } - run := current.Run + + ctx.JSONOK() +} + +func approveRuns(ctx *context_module.Context, runIndexes []int64) { doer := ctx.Doer + repo := ctx.Repo.Repository - var updatedJobs []*actions_model.ActionRunJob + updatedJobs := make([]*actions_model.ActionRunJob, 0) + runMap := make(map[int64]*actions_model.ActionRun, len(runIndexes)) + runJobs := make(map[int64][]*actions_model.ActionRunJob, len(runIndexes)) err := db.WithTx(ctx, func(ctx context.Context) (err error) { - run.NeedApproval = false - run.ApprovedBy = doer.ID - if err := actions_model.UpdateRun(ctx, run, "need_approval", "approved_by"); err != nil { - return err - } - for _, job := range jobs { - job.Status, err = actions_service.PrepareToStartJobWithConcurrency(ctx, job) + for _, runIndex := range runIndexes { + run, err := actions_model.GetRunByIndex(ctx, repo.ID, runIndex) + if err != nil { + return err + } + runMap[run.ID] = run + run.Repo = repo + run.NeedApproval = false + run.ApprovedBy = doer.ID + if err := actions_model.UpdateRun(ctx, run, "need_approval", "approved_by"); err != nil { + return err + } + jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID) if err != nil { return err } - if job.Status == actions_model.StatusWaiting { - n, err := actions_model.UpdateRunJob(ctx, job, nil, "status") + runJobs[run.ID] = jobs + for _, job := range jobs { + job.Status, err = actions_service.PrepareToStartJobWithConcurrency(ctx, job) if err != nil { return err } - if n > 0 { - updatedJobs = append(updatedJobs, job) + if job.Status == actions_model.StatusWaiting { + n, err := actions_model.UpdateRunJob(ctx, job, nil, "status") + if err != nil { + return err + } + if n > 0 { + updatedJobs = append(updatedJobs, job) + } } } } @@ -642,7 +662,9 @@ func Approve(ctx *context_module.Context) { return } - actions_service.CreateCommitStatusForRunJobs(ctx, current.Run, jobs...) + for runID, run := range runMap { + actions_service.CreateCommitStatusForRunJobs(ctx, run, runJobs[runID]...) + } if len(updatedJobs) > 0 { job := updatedJobs[0] @@ -653,8 +675,6 @@ func Approve(ctx *context_module.Context) { _ = job.LoadAttributes(ctx) notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil) } - - ctx.JSONOK() } func Delete(ctx *context_module.Context) { @@ -817,6 +837,43 @@ func ArtifactsDownloadView(ctx *context_module.Context) { } } +func ApproveAllChecks(ctx *context_module.Context) { + repo := ctx.Repo.Repository + sha := ctx.FormString("sha") + redirect := ctx.FormString("redirect") + + commitStatuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll) + if err != nil { + ctx.ServerError("GetLatestCommitStatus", err) + return + } + runs, _, err := actions_service.GetRunsAndJobsFromCommitStatuses(ctx, commitStatuses) + if err != nil { + ctx.ServerError("GetRunsAndJobsFromCommitStatuses", err) + return + } + + runIndexes := make([]int64, 0, len(runs)) + for _, run := range runs { + if run.NeedApproval { + runIndexes = append(runIndexes, run.Index) + } + } + + if len(runIndexes) == 0 { + ctx.Redirect(redirect) + return + } + + approveRuns(ctx, runIndexes) + if ctx.Written() { + return + } + + ctx.Flash.Success("Successfully approved all pending checks") + ctx.Redirect(redirect) +} + func DisableWorkflowFile(ctx *context_module.Context) { disableOrEnableWorkflowFile(ctx, false) } diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index ea3e700b771cd..ce76d27546ac7 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -38,6 +38,7 @@ import ( "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/utils" shared_user "code.gitea.io/gitea/routers/web/shared/user" + actions_service "code.gitea.io/gitea/services/actions" asymkey_service "code.gitea.io/gitea/services/asymkey" "code.gitea.io/gitea/services/automerge" "code.gitea.io/gitea/services/context" @@ -455,6 +456,7 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *pull_ ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitHeadRefName()), err) return nil } + ctx.Data["SHA"] = sha commitStatuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll) if err != nil { @@ -465,6 +467,20 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *pull_ git_model.CommitStatusesHideActionsURL(ctx, commitStatuses) } + runs, _, err := actions_service.GetRunsAndJobsFromCommitStatuses(ctx, commitStatuses) + if err != nil { + ctx.ServerError("GetRunsAndJobsFromCommitStatuses", err) + return nil + } + for _, run := range runs { + if run.NeedApproval { + ctx.Data["RequireApproval"] = true + } + } + if ctx.Data["RequireApproval"] == true { + ctx.Data["CanApprove"] = ctx.Repo.CanWrite(unit.TypeActions) + } + if len(commitStatuses) > 0 { ctx.Data["LatestCommitStatuses"] = commitStatuses ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(commitStatuses) diff --git a/routers/web/web.go b/routers/web/web.go index 5ee211b576a0c..9b3cfb6d1670a 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1459,6 +1459,7 @@ func registerWebRoutes(m *web.Router) { m.Post("/enable", reqRepoAdmin, actions.EnableWorkflowFile) m.Post("/run", reqRepoActionsWriter, actions.Run) m.Get("/workflow-dispatch-inputs", reqRepoActionsWriter, actions.WorkflowDispatchInputs) + m.Post("/approve-all-checks", reqRepoActionsWriter, actions.ApproveAllChecks) m.Group("/runs/{run}", func() { m.Combo(""). diff --git a/services/actions/commit_status.go b/services/actions/commit_status.go index d3f2b0f3cc403..ff55bb7c42a45 100644 --- a/services/actions/commit_status.go +++ b/services/actions/commit_status.go @@ -8,7 +8,9 @@ import ( "errors" "fmt" "path" + "regexp" "strconv" + "strings" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" @@ -52,6 +54,74 @@ func CreateCommitStatusForRunJobs(ctx context.Context, run *actions_model.Action } } +func GetRunsAndJobsFromCommitStatuses(ctx context.Context, statuses []*git_model.CommitStatus) ([]*actions_model.ActionRun, []*actions_model.ActionRunJob, error) { + jobMap := make(map[int64]*actions_model.ActionRunJob) + runMap := make(map[int64]*actions_model.ActionRun) + jobsMap := make(map[int64][]*actions_model.ActionRunJob) + for _, status := range statuses { + if !status.CreatedByGiteaActions(ctx) { + continue + } + runIndex, jobIndex, err := getActionRunAndJobIndexFromCommitStatus(status) + if err != nil { + return nil, nil, fmt.Errorf("getActionRunAndJobIndexFromCommitStatus: %w", err) + } + run, ok := runMap[runIndex] + if !ok { + run, err = actions_model.GetRunByIndex(ctx, status.RepoID, runIndex) + if err != nil { + return nil, nil, fmt.Errorf("GetRunByIndex: %w", err) + } + runMap[runIndex] = run + } + jobs, ok := jobsMap[runIndex] + if !ok { + jobs, err = actions_model.GetRunJobsByRunID(ctx, run.ID) + if err != nil { + return nil, nil, fmt.Errorf("GetRunJobByIndex: %w", err) + } + jobsMap[runIndex] = jobs + } + if jobIndex < 0 || jobIndex >= int64(len(jobs)) { + return nil, nil, fmt.Errorf("job index %d out of range for run %d", jobIndex, runIndex) + } + job := jobs[jobIndex] + jobMap[job.ID] = job + } + runs := make([]*actions_model.ActionRun, 0, len(runMap)) + for _, run := range runMap { + runs = append(runs, run) + } + jobs := make([]*actions_model.ActionRunJob, 0, len(jobMap)) + for _, job := range jobMap { + jobs = append(jobs, job) + } + return runs, jobs, nil +} + +func getActionRunAndJobIndexFromCommitStatus(status *git_model.CommitStatus) (int64, int64, error) { + actionsLink, _ := strings.CutPrefix(status.TargetURL, status.Repo.Link()+"/actions/") + // actionsLink should be like "runs//jobs/" + + re := regexp.MustCompile(`runs/(\d+)/jobs/(\d+)`) + matches := re.FindStringSubmatch(actionsLink) + + if len(matches) != 3 { + return 0, 0, fmt.Errorf("%s is not a Gitea Actions link", status.TargetURL) + } + + runIndex, err := strconv.ParseInt(matches[1], 10, 64) + if err != nil { + return 0, 0, fmt.Errorf("parse run index: %w", err) + } + jobIndex, err := strconv.ParseInt(matches[2], 10, 64) + if err != nil { + return 0, 0, fmt.Errorf("parse job index: %w", err) + } + + return runIndex, jobIndex, nil +} + func getCommitStatusEventNameAndCommitID(run *actions_model.ActionRun) (event, commitID string, _ error) { switch run.Event { case webhook_module.HookEventPush: diff --git a/templates/repo/issue/view_content/pull_merge_box.tmpl b/templates/repo/issue/view_content/pull_merge_box.tmpl index b0ac24c9f6c3f..6ed773a377544 100644 --- a/templates/repo/issue/view_content/pull_merge_box.tmpl +++ b/templates/repo/issue/view_content/pull_merge_box.tmpl @@ -34,6 +34,11 @@ "MissingRequiredChecks" .MissingRequiredChecks "ShowHideChecks" true "is_context_required" .is_context_required + "SHA" .SHA + "RequireApproval" .RequireApproval + "CanApprove" .CanApprove + "RepoLink" .RepoLink + "IssueLink" .Issue.Link )}} {{end}} diff --git a/templates/repo/pulls/status.tmpl b/templates/repo/pulls/status.tmpl index 96030f9422d54..eb79a1ab207ef 100644 --- a/templates/repo/pulls/status.tmpl +++ b/templates/repo/pulls/status.tmpl @@ -23,14 +23,23 @@ {{ctx.Locale.Tr "repo.pulls.status_checking"}} {{end}} - {{if .ShowHideChecks}}
- + {{if and .RequireApproval .CanApprove}} +
+ {{ctx.RootData.CsrfTokenHtml}} + + +
+ {{end}} + {{if .ShowHideChecks}} + + {{end}}
- {{end}}
From 16bd04f69e0a1b4fe472123811787f3a2902f438 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Thu, 16 Oct 2025 21:46:06 -0600 Subject: [PATCH 02/16] change style & use locale --- options/locale/locale_en-US.ini | 2 ++ routers/web/repo/actions/view.go | 2 +- templates/repo/pulls/status.tmpl | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 03017ce6746df..84f71fdd452c0 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1969,6 +1969,7 @@ pulls.status_checks_requested = Required pulls.status_checks_details = Details pulls.status_checks_hide_all = Hide all checks pulls.status_checks_show_all = Show all checks +pulls.status_checks_approve_all = Approve all workflows pulls.update_branch = Update branch by merge pulls.update_branch_rebase = Update branch by rebase pulls.update_branch_success = Branch update was successful @@ -3890,6 +3891,7 @@ workflow.has_workflow_dispatch = This workflow has a workflow_dispatch event tri workflow.has_no_workflow_dispatch = Workflow '%s' has no workflow_dispatch event trigger. need_approval_desc = Need approval to run workflows for fork pull request. +approve_all_success = All workflow runs are approved successfully. variables = Variables variables.management = Variables Management diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 3acbd765b33ae..8a08e7d0d00cf 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -870,7 +870,7 @@ func ApproveAllChecks(ctx *context_module.Context) { return } - ctx.Flash.Success("Successfully approved all pending checks") + ctx.Flash.Success(ctx.Tr("actions.approve_all_success")) ctx.Redirect(redirect) } diff --git a/templates/repo/pulls/status.tmpl b/templates/repo/pulls/status.tmpl index eb79a1ab207ef..3193746ae92ed 100644 --- a/templates/repo/pulls/status.tmpl +++ b/templates/repo/pulls/status.tmpl @@ -28,8 +28,8 @@
{{ctx.RootData.CsrfTokenHtml}} -
{{end}} From 148588937800ae2fa93be6b022552038ba02fd3c Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Fri, 17 Oct 2025 12:40:31 -0600 Subject: [PATCH 03/16] check deleted run --- services/actions/commit_status.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/services/actions/commit_status.go b/services/actions/commit_status.go index ff55bb7c42a45..73ce659b11369 100644 --- a/services/actions/commit_status.go +++ b/services/actions/commit_status.go @@ -20,6 +20,7 @@ import ( actions_module "code.gitea.io/gitea/modules/actions" "code.gitea.io/gitea/modules/commitstatus" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" webhook_module "code.gitea.io/gitea/modules/webhook" commitstatus_service "code.gitea.io/gitea/services/repository/commitstatus" @@ -70,6 +71,10 @@ func GetRunsAndJobsFromCommitStatuses(ctx context.Context, statuses []*git_model if !ok { run, err = actions_model.GetRunByIndex(ctx, status.RepoID, runIndex) if err != nil { + if errors.Is(err, util.ErrNotExist) { + // the run may be deleted manually, just skip it + continue + } return nil, nil, fmt.Errorf("GetRunByIndex: %w", err) } runMap[runIndex] = run From 709fdb69e67c1da95610d44cfeda0a38b6769911 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Fri, 17 Oct 2025 13:01:59 -0600 Subject: [PATCH 04/16] use StatusCheckData for template --- routers/web/repo/pull.go | 25 +++++++++++---- .../issue/view_content/pull_merge_box.tmpl | 8 +---- templates/repo/pulls/status.tmpl | 31 +++++++++++-------- 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index ce76d27546ac7..976f794ec5ab7 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -312,6 +312,14 @@ func prepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue) return compareInfo } +type pullCommitStatusCheckData struct { + MissingRequiredChecks []string + IsContextRequired func(string) bool + RequireApproval bool + CanApprove bool + ApproveActionLink string +} + // prepareViewPullInfo show meta information for a pull request preview page func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *pull_service.CompareInfo { ctx.Data["PullRequestWorkInProgressPrefixes"] = setting.Repository.PullRequest.WorkInProgressPrefixes @@ -456,7 +464,11 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *pull_ ctx.ServerError(fmt.Sprintf("GetRefCommitID(%s)", pull.GetGitHeadRefName()), err) return nil } - ctx.Data["SHA"] = sha + + statusCheckData := &pullCommitStatusCheckData{ + ApproveActionLink: fmt.Sprintf("%s/actions/approve-all-checks?sha=%s&redirect=%s", repo.Link(), sha, issue.Link()), + } + ctx.Data["StatusCheckData"] = statusCheckData commitStatuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll) if err != nil { @@ -474,11 +486,12 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *pull_ } for _, run := range runs { if run.NeedApproval { - ctx.Data["RequireApproval"] = true + statusCheckData.RequireApproval = true + break } } - if ctx.Data["RequireApproval"] == true { - ctx.Data["CanApprove"] = ctx.Repo.CanWrite(unit.TypeActions) + if statusCheckData.RequireApproval { + statusCheckData.CanApprove = ctx.Repo.CanWrite(unit.TypeActions) } if len(commitStatuses) > 0 { @@ -502,9 +515,9 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *pull_ missingRequiredChecks = append(missingRequiredChecks, requiredContext) } } - ctx.Data["MissingRequiredChecks"] = missingRequiredChecks + statusCheckData.MissingRequiredChecks = missingRequiredChecks - ctx.Data["is_context_required"] = func(context string) bool { + statusCheckData.IsContextRequired = func(context string) bool { for _, c := range pb.StatusCheckContexts { if c == context { return true diff --git a/templates/repo/issue/view_content/pull_merge_box.tmpl b/templates/repo/issue/view_content/pull_merge_box.tmpl index 6ed773a377544..c0d717e85438f 100644 --- a/templates/repo/issue/view_content/pull_merge_box.tmpl +++ b/templates/repo/issue/view_content/pull_merge_box.tmpl @@ -31,14 +31,8 @@ {{template "repo/pulls/status" (dict "CommitStatus" .LatestCommitStatus "CommitStatuses" .LatestCommitStatuses - "MissingRequiredChecks" .MissingRequiredChecks "ShowHideChecks" true - "is_context_required" .is_context_required - "SHA" .SHA - "RequireApproval" .RequireApproval - "CanApprove" .CanApprove - "RepoLink" .RepoLink - "IssueLink" .Issue.Link + "StatusCheckData" .StatusCheckData )}}
{{end}} diff --git a/templates/repo/pulls/status.tmpl b/templates/repo/pulls/status.tmpl index 3193746ae92ed..fee7157b3bfcb 100644 --- a/templates/repo/pulls/status.tmpl +++ b/templates/repo/pulls/status.tmpl @@ -1,9 +1,13 @@ {{/* Template Attributes: * CommitStatus: summary of all commit status state * CommitStatuses: all commit status elements -* MissingRequiredChecks: commit check contexts that are required by branch protection but not present * ShowHideChecks: whether use a button to show/hide the checks -* is_context_required: Used in pull request commit status check table +* StatusCheckData: additional status check data for repo/issue/view_content/pull_merge_box + - MissingRequiredChecks: list of missing required checks + - IsContextRequired: function to check whether a context is required + - RequireApproval: whether approval is required for workflow runs + - CanApprove: whether the user can approve workflow runs + - ApproveActionLink: link to approve all checks */}} {{if .CommitStatus}} @@ -24,10 +28,9 @@ {{end}}
- {{if and .RequireApproval .CanApprove}} -
+ {{if and .StatusCheckData .StatusCheckData.RequireApproval .StatusCheckData.CanApprove}} + {{ctx.RootData.CsrfTokenHtml}} - @@ -48,19 +51,21 @@ {{template "repo/commit_status" .}}
{{.Context}} {{.Description}}
- {{if $.is_context_required}} - {{if (call $.is_context_required .Context)}}
{{ctx.Locale.Tr "repo.pulls.status_checks_requested"}}
{{end}} + {{if and $.StatusCheckData $.StatusCheckData.IsContextRequired}} + {{if (call $.StatusCheckData.IsContextRequired .Context)}}
{{ctx.Locale.Tr "repo.pulls.status_checks_requested"}}
{{end}} {{end}} {{if .TargetURL}}{{ctx.Locale.Tr "repo.pulls.status_checks_details"}}{{end}}
{{end}} - {{range .MissingRequiredChecks}} -
- {{svg "octicon-dot-fill" 18 "commit-status icon text yellow"}} -
{{.}}
-
{{ctx.Locale.Tr "repo.pulls.status_checks_requested"}}
-
+ {{if .StatusCheckData}} + {{range .StatusCheckData.MissingRequiredChecks}} +
+ {{svg "octicon-dot-fill" 18 "commit-status icon text yellow"}} +
{{.}}
+
{{ctx.Locale.Tr "repo.pulls.status_checks_requested"}}
+
+ {{end}} {{end}} From 525e87e411767ca7604bf81fb089873cad2880cb Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Fri, 17 Oct 2025 13:39:48 -0600 Subject: [PATCH 05/16] fix lint --- templates/repo/pulls/status.tmpl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/repo/pulls/status.tmpl b/templates/repo/pulls/status.tmpl index fee7157b3bfcb..61c4fbba389d7 100644 --- a/templates/repo/pulls/status.tmpl +++ b/templates/repo/pulls/status.tmpl @@ -3,11 +3,11 @@ * CommitStatuses: all commit status elements * ShowHideChecks: whether use a button to show/hide the checks * StatusCheckData: additional status check data for repo/issue/view_content/pull_merge_box - - MissingRequiredChecks: list of missing required checks - - IsContextRequired: function to check whether a context is required - - RequireApproval: whether approval is required for workflow runs - - CanApprove: whether the user can approve workflow runs - - ApproveActionLink: link to approve all checks + * MissingRequiredChecks: list of missing required checks + * IsContextRequired: function to check whether a context is required + * RequireApproval: whether approval is required for workflow runs + * CanApprove: whether the user can approve workflow runs + * ApproveActionLink: link to approve all checks */}} {{if .CommitStatus}} From 62eaae6a036d348f37f9fb1b80187726796dc64e Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Fri, 17 Oct 2025 15:06:27 -0600 Subject: [PATCH 06/16] only query runs --- routers/web/repo/actions/view.go | 4 ++-- routers/web/repo/pull.go | 4 ++-- services/actions/commit_status.go | 29 +++++------------------------ 3 files changed, 9 insertions(+), 28 deletions(-) diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 8a08e7d0d00cf..de4e45c412d7e 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -847,9 +847,9 @@ func ApproveAllChecks(ctx *context_module.Context) { ctx.ServerError("GetLatestCommitStatus", err) return } - runs, _, err := actions_service.GetRunsAndJobsFromCommitStatuses(ctx, commitStatuses) + runs, err := actions_service.GetRunsFromCommitStatuses(ctx, commitStatuses) if err != nil { - ctx.ServerError("GetRunsAndJobsFromCommitStatuses", err) + ctx.ServerError("GetRunsFromCommitStatuses", err) return } diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 976f794ec5ab7..bc1274541a4bd 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -479,9 +479,9 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *pull_ git_model.CommitStatusesHideActionsURL(ctx, commitStatuses) } - runs, _, err := actions_service.GetRunsAndJobsFromCommitStatuses(ctx, commitStatuses) + runs, err := actions_service.GetRunsFromCommitStatuses(ctx, commitStatuses) if err != nil { - ctx.ServerError("GetRunsAndJobsFromCommitStatuses", err) + ctx.ServerError("GetRunsFromCommitStatuses", err) return nil } for _, run := range runs { diff --git a/services/actions/commit_status.go b/services/actions/commit_status.go index 73ce659b11369..984c5ffb2d9bb 100644 --- a/services/actions/commit_status.go +++ b/services/actions/commit_status.go @@ -55,17 +55,15 @@ func CreateCommitStatusForRunJobs(ctx context.Context, run *actions_model.Action } } -func GetRunsAndJobsFromCommitStatuses(ctx context.Context, statuses []*git_model.CommitStatus) ([]*actions_model.ActionRun, []*actions_model.ActionRunJob, error) { - jobMap := make(map[int64]*actions_model.ActionRunJob) +func GetRunsFromCommitStatuses(ctx context.Context, statuses []*git_model.CommitStatus) ([]*actions_model.ActionRun, error) { runMap := make(map[int64]*actions_model.ActionRun) - jobsMap := make(map[int64][]*actions_model.ActionRunJob) for _, status := range statuses { if !status.CreatedByGiteaActions(ctx) { continue } - runIndex, jobIndex, err := getActionRunAndJobIndexFromCommitStatus(status) + runIndex, _, err := getActionRunAndJobIndexFromCommitStatus(status) if err != nil { - return nil, nil, fmt.Errorf("getActionRunAndJobIndexFromCommitStatus: %w", err) + return nil, fmt.Errorf("getActionRunAndJobIndexFromCommitStatus: %w", err) } run, ok := runMap[runIndex] if !ok { @@ -75,33 +73,16 @@ func GetRunsAndJobsFromCommitStatuses(ctx context.Context, statuses []*git_model // the run may be deleted manually, just skip it continue } - return nil, nil, fmt.Errorf("GetRunByIndex: %w", err) + return nil, fmt.Errorf("GetRunByIndex: %w", err) } runMap[runIndex] = run } - jobs, ok := jobsMap[runIndex] - if !ok { - jobs, err = actions_model.GetRunJobsByRunID(ctx, run.ID) - if err != nil { - return nil, nil, fmt.Errorf("GetRunJobByIndex: %w", err) - } - jobsMap[runIndex] = jobs - } - if jobIndex < 0 || jobIndex >= int64(len(jobs)) { - return nil, nil, fmt.Errorf("job index %d out of range for run %d", jobIndex, runIndex) - } - job := jobs[jobIndex] - jobMap[job.ID] = job } runs := make([]*actions_model.ActionRun, 0, len(runMap)) for _, run := range runMap { runs = append(runs, run) } - jobs := make([]*actions_model.ActionRunJob, 0, len(jobMap)) - for _, job := range jobMap { - jobs = append(jobs, job) - } - return runs, jobs, nil + return runs, nil } func getActionRunAndJobIndexFromCommitStatus(status *git_model.CommitStatus) (int64, int64, error) { From 43226343c6825f89f8ead7a24d3e380f450738da Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Fri, 17 Oct 2025 15:14:10 -0600 Subject: [PATCH 07/16] fix lint --- services/actions/commit_status.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/actions/commit_status.go b/services/actions/commit_status.go index 984c5ffb2d9bb..6bb785f7d8863 100644 --- a/services/actions/commit_status.go +++ b/services/actions/commit_status.go @@ -65,9 +65,9 @@ func GetRunsFromCommitStatuses(ctx context.Context, statuses []*git_model.Commit if err != nil { return nil, fmt.Errorf("getActionRunAndJobIndexFromCommitStatus: %w", err) } - run, ok := runMap[runIndex] + _, ok := runMap[runIndex] if !ok { - run, err = actions_model.GetRunByIndex(ctx, status.RepoID, runIndex) + run, err := actions_model.GetRunByIndex(ctx, status.RepoID, runIndex) if err != nil { if errors.Is(err, util.ErrNotExist) { // the run may be deleted manually, just skip it From 6cb1cf3c438b4b5a8ad8deb242a71124d5cfca76 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Fri, 17 Oct 2025 15:28:32 -0600 Subject: [PATCH 08/16] add TestApproveAllRunsOnPullRequestPage --- tests/integration/actions_approve_test.go | 146 ++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 tests/integration/actions_approve_test.go diff --git a/tests/integration/actions_approve_test.go b/tests/integration/actions_approve_test.go new file mode 100644 index 0000000000000..78c507d530a66 --- /dev/null +++ b/tests/integration/actions_approve_test.go @@ -0,0 +1,146 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "encoding/base64" + "fmt" + "net/http" + "net/url" + "testing" + "time" + + actions_model "code.gitea.io/gitea/models/actions" + auth_model "code.gitea.io/gitea/models/auth" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + + "github.com/stretchr/testify/assert" +) + +func TestApproveAllRunsOnPullRequestPage(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + // user2 is the owner of the base repo + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + user2Session := loginUser(t, user2.Name) + user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + // user4 is the owner of the fork repo + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + user4Session := loginUser(t, user4.Name) + user4Token := getTokenForLoggedInUser(t, loginUser(t, user4.Name), auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + apiBaseRepo := createActionsTestRepo(t, user2Token, "approve-all-runs", false) + baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID}) + user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository) + defer doAPIDeleteRepository(user2APICtx)(t) + + runner := newMockRunner() + runner.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner", []string{"ubuntu-latest"}, false) + + // init workflows + wf1TreePath := ".gitea/workflows/pull_1.yml" + wf1FileContent := `name: Pull 1 +on: pull_request +jobs: + unit-test: + runs-on: ubuntu-latest + steps: + - run: echo unit-test +` + opts1 := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create %s"+wf1TreePath, wf1FileContent) + createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wf1TreePath, opts1) + wf2TreePath := ".gitea/workflows/pull_2.yml" + wf2FileContent := `name: Pull 2 +on: pull_request +jobs: + integration-test: + runs-on: ubuntu-latest + steps: + - run: echo integration-test +` + opts2 := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create %s"+wf2TreePath, wf2FileContent) + createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wf2TreePath, opts2) + + // user4 forks the repo + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/forks", baseRepo.OwnerName, baseRepo.Name), + &api.CreateForkOption{ + Name: util.ToPointer("approve-all-runs-fork"), + }).AddTokenAuth(user4Token) + resp := MakeRequest(t, req, http.StatusAccepted) + var apiForkRepo api.Repository + DecodeJSON(t, resp, &apiForkRepo) + forkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiForkRepo.ID}) + user4APICtx := NewAPITestContext(t, user4.Name, forkRepo.Name, auth_model.AccessTokenScopeWriteRepository) + defer doAPIDeleteRepository(user4APICtx)(t) + + // user4 creates a pull request from branch "bugfix/user4" + doAPICreateFile(user4APICtx, "user4-fix.txt", &api.CreateFileOptions{ + FileOptions: api.FileOptions{ + NewBranchName: "bugfix/user4", + Message: "create user4-fix.txt", + Author: api.Identity{ + Name: user4.Name, + Email: user4.Email, + }, + Committer: api.Identity{ + Name: user4.Name, + Email: user4.Email, + }, + Dates: api.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }, + ContentBase64: base64.StdEncoding.EncodeToString([]byte("user4-fix")), + })(t) + apiPull, err := doAPICreatePullRequest(user4APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, user4.Name+":bugfix/user4")(t) + assert.NoError(t, err) + + // check runs + run1 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: baseRepo.ID, TriggerUserID: user4.ID, WorkflowID: "pull_1.yml"}) + assert.True(t, run1.NeedApproval) + assert.Equal(t, actions_model.StatusBlocked, run1.Status) + run2 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: baseRepo.ID, TriggerUserID: user4.ID, WorkflowID: "pull_2.yml"}) + assert.True(t, run2.NeedApproval) + assert.Equal(t, actions_model.StatusBlocked, run2.Status) + + // user4 cannot see the approve button + req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d", baseRepo.OwnerName, baseRepo.Name, apiPull.Index)) + resp = user4Session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + assert.Zero(t, htmlDoc.doc.Find(".commit-status-header form").Length()) + + // user2 can see the approve button + req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d", baseRepo.OwnerName, baseRepo.Name, apiPull.Index)) + resp = user2Session.MakeRequest(t, req, http.StatusOK) + htmlDoc = NewHTMLParser(t, resp.Body) + formAction, exist := htmlDoc.doc.Find(".commit-status-header form").Attr("action") + assert.True(t, exist) + assert.Equal(t, + fmt.Sprintf("%s/actions/approve-all-checks?sha=%s&redirect=/%s/%s/pulls/%d", + baseRepo.Link(), apiPull.Head.Sha, baseRepo.OwnerName, baseRepo.Name, apiPull.Index), + formAction, + ) + + // user2 approves all runs + req = NewRequestWithValues(t, "POST", formAction, + map[string]string{ + "_csrf": GetUserCSRFToken(t, user2Session), + }) + user2Session.MakeRequest(t, req, http.StatusSeeOther) + + // check runs + run1 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run1.ID}) + assert.False(t, run1.NeedApproval) + assert.Equal(t, user2.ID, run1.ApprovedBy) + assert.Equal(t, actions_model.StatusWaiting, run1.Status) + run2 = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: run2.ID}) + assert.False(t, run2.NeedApproval) + assert.Equal(t, user2.ID, run2.ApprovedBy) + assert.Equal(t, actions_model.StatusWaiting, run2.Status) + }) +} From cf5226355a5be386d373d1a6cdb8cff90f05566d Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Sat, 18 Oct 2025 13:56:32 -0600 Subject: [PATCH 09/16] use link-action button --- routers/web/repo/actions/view.go | 9 ++++----- routers/web/repo/pull.go | 4 ++-- templates/repo/pulls/status.tmpl | 11 ++++------- tests/integration/actions_approve_test.go | 14 +++++++------- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 07209fdcb19b7..013dab4acff84 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -840,10 +840,9 @@ func ArtifactsDownloadView(ctx *context_module.Context) { func ApproveAllChecks(ctx *context_module.Context) { repo := ctx.Repo.Repository - sha := ctx.FormString("sha") - redirect := ctx.FormString("redirect") + commitID := ctx.FormString("commit_id") - commitStatuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, sha, db.ListOptionsAll) + commitStatuses, err := git_model.GetLatestCommitStatus(ctx, repo.ID, commitID, db.ListOptionsAll) if err != nil { ctx.ServerError("GetLatestCommitStatus", err) return @@ -862,7 +861,7 @@ func ApproveAllChecks(ctx *context_module.Context) { } if len(runIndexes) == 0 { - ctx.Redirect(redirect) + ctx.JSONOK() return } @@ -872,7 +871,7 @@ func ApproveAllChecks(ctx *context_module.Context) { } ctx.Flash.Success(ctx.Tr("actions.approve_all_success")) - ctx.Redirect(redirect) + ctx.JSONOK() } func DisableWorkflowFile(ctx *context_module.Context) { diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 72cce680dbcc5..dcd9b6c7aadf7 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -317,7 +317,7 @@ type pullCommitStatusCheckData struct { IsContextRequired func(string) bool RequireApproval bool CanApprove bool - ApproveActionLink string + ApproveLink string } // prepareViewPullInfo show meta information for a pull request preview page @@ -466,7 +466,7 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *pull_ } statusCheckData := &pullCommitStatusCheckData{ - ApproveActionLink: fmt.Sprintf("%s/actions/approve-all-checks?sha=%s&redirect=%s", repo.Link(), sha, issue.Link()), + ApproveLink: fmt.Sprintf("%s/actions/approve-all-checks?commit_id=%s", repo.Link(), sha), } ctx.Data["StatusCheckData"] = statusCheckData diff --git a/templates/repo/pulls/status.tmpl b/templates/repo/pulls/status.tmpl index 61c4fbba389d7..846e417b853da 100644 --- a/templates/repo/pulls/status.tmpl +++ b/templates/repo/pulls/status.tmpl @@ -7,7 +7,7 @@ * IsContextRequired: function to check whether a context is required * RequireApproval: whether approval is required for workflow runs * CanApprove: whether the user can approve workflow runs - * ApproveActionLink: link to approve all checks + * ApproveLink: link to approve all checks */}} {{if .CommitStatus}} @@ -29,12 +29,9 @@
{{if and .StatusCheckData .StatusCheckData.RequireApproval .StatusCheckData.CanApprove}} - - {{ctx.RootData.CsrfTokenHtml}} - - + {{end}} {{if .ShowHideChecks}} {{end}} @@ -48,15 +43,15 @@ {{template "repo/commit_status" .}}
{{.Context}} {{.Description}}
- {{if and $.StatusCheckData $.StatusCheckData.IsContextRequired}} - {{if (call $.StatusCheckData.IsContextRequired .Context)}}
{{ctx.Locale.Tr "repo.pulls.status_checks_requested"}}
{{end}} + {{if and $statusCheckData $statusCheckData.IsContextRequired}} + {{if (call $statusCheckData.IsContextRequired .Context)}}
{{ctx.Locale.Tr "repo.pulls.status_checks_requested"}}
{{end}} {{end}} {{if .TargetURL}}{{ctx.Locale.Tr "repo.pulls.status_checks_details"}}{{end}}
{{end}} - {{if .StatusCheckData}} - {{range .StatusCheckData.MissingRequiredChecks}} + {{if $statusCheckData}} + {{range $statusCheckData.MissingRequiredChecks}}
{{svg "octicon-dot-fill" 18 "commit-status icon text yellow"}}
{{.}}
From 0e8bde97b3ea3305aa82ef4c4f6239f3bc6da8f6 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 19 Oct 2025 13:43:53 +0800 Subject: [PATCH 13/16] fix typo --- models/git/commit_status.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/git/commit_status.go b/models/git/commit_status.go index 1963847fa7817..9d1b1481254c6 100644 --- a/models/git/commit_status.go +++ b/models/git/commit_status.go @@ -38,7 +38,7 @@ type CommitStatus struct { SHA string `xorm:"VARCHAR(64) NOT NULL INDEX UNIQUE(repo_sha_index)"` // TargetURL points to the commit status page reported by a CI system - // If Gitea Actions is used, it is a relative like "{RepoLink}/actions/runs/{RunID}/jobs{JobID}" + // If Gitea Actions is used, it is a relative link like "{RepoLink}/actions/runs/{RunID}/jobs{JobID}" TargetURL string `xorm:"TEXT"` Description string `xorm:"TEXT"` From e92d7720c19a455ef7c0a6126226aa940718ba32 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 19 Oct 2025 14:15:29 +0800 Subject: [PATCH 14/16] fix typo --- models/git/commit_status.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/git/commit_status.go b/models/git/commit_status.go index 9d1b1481254c6..2ae5937a3d8ff 100644 --- a/models/git/commit_status.go +++ b/models/git/commit_status.go @@ -233,7 +233,7 @@ func (status *CommitStatus) cutTargetURLGiteaActionsPrefix(ctx context.Context) } prefix := status.Repo.Link() + "/actions" - return strings.CutPrefix(prefix, status.TargetURL) + return strings.CutPrefix(status.TargetURL, prefix) } // ParseGiteaActionsTargetURL parses the commit status target URL as Gitea Actions link From 8d55fbffa0fe0af511d8a158653fe9a7df0e5c80 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Sun, 19 Oct 2025 14:13:16 -0600 Subject: [PATCH 15/16] improve ui --- options/locale/locale_en-US.ini | 2 ++ routers/web/repo/pull.go | 15 ++++++------ templates/repo/pulls/status.tmpl | 29 ++++++++++++++++------- tests/integration/actions_approve_test.go | 4 ++-- 4 files changed, 31 insertions(+), 19 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 84f71fdd452c0..46fdf060229e8 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1970,6 +1970,8 @@ pulls.status_checks_details = Details pulls.status_checks_hide_all = Hide all checks pulls.status_checks_show_all = Show all checks pulls.status_checks_approve_all = Approve all workflows +pulls.status_checks_need_approvals = %d workflow awaiting approval +pulls.status_checks_need_approvals_helper = The workflow will only run after approval from the repository maintainer. pulls.update_branch = Update branch by merge pulls.update_branch_rebase = Update branch by rebase pulls.update_branch_success = Branch update was successful diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index ea61f267d90c6..1821193f2956b 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -313,11 +313,11 @@ func prepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue) } type pullCommitStatusCheckData struct { - MissingRequiredChecks []string // list of missing required checks - IsContextRequired func(string) bool // function to check whether a context is required - RequireApproval bool // whether approval is required for workflow runs - CanApprove bool // whether the user can approve workflow runs - ApproveLink string // link to approve all checks + MissingRequiredChecks []string // list of missing required checks + IsContextRequired func(string) bool // function to check whether a context is required + RequireApprovalRunCount int // number of workflow runs that require approval + CanApprove bool // whether the user can approve workflow runs + ApproveLink string // link to approve all checks } // prepareViewPullInfo show meta information for a pull request preview page @@ -486,11 +486,10 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *pull_ } for _, run := range runs { if run.NeedApproval { - statusCheckData.RequireApproval = true - break + statusCheckData.RequireApprovalRunCount++ } } - if statusCheckData.RequireApproval { + if statusCheckData.RequireApprovalRunCount > 0 { statusCheckData.CanApprove = ctx.Repo.CanWrite(unit.TypeActions) } diff --git a/templates/repo/pulls/status.tmpl b/templates/repo/pulls/status.tmpl index 079e49147606e..bdc0c06b57c83 100644 --- a/templates/repo/pulls/status.tmpl +++ b/templates/repo/pulls/status.tmpl @@ -22,20 +22,31 @@ {{ctx.Locale.Tr "repo.pulls.status_checking"}} {{end}} + {{if .ShowHideChecks}}
- {{if and $statusCheckData $statusCheckData.RequireApproval $statusCheckData.CanApprove}} - +
+ {{end}} +
+ + {{if and $statusCheckData (gt $statusCheckData.RequireApprovalRunCount 0)}} +
+
+ + {{ctx.Locale.Tr "repo.pulls.status_checks_need_approvals" $statusCheckData.RequireApprovalRunCount}} + +

{{ctx.Locale.Tr "repo.pulls.status_checks_need_approvals_helper"}}

+
+ {{if $statusCheckData.CanApprove}} + {{end}} - {{if .ShowHideChecks}} - - {{end}}
- + {{end}}
{{range .CommitStatuses}} diff --git a/tests/integration/actions_approve_test.go b/tests/integration/actions_approve_test.go index 9cf5ecd78fa5c..04b8bcb715676 100644 --- a/tests/integration/actions_approve_test.go +++ b/tests/integration/actions_approve_test.go @@ -112,13 +112,13 @@ jobs: req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d", baseRepo.OwnerName, baseRepo.Name, apiPull.Index)) resp = user4Session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - assert.Zero(t, htmlDoc.doc.Find(".commit-status-header button.link-action").Length()) + assert.Zero(t, htmlDoc.doc.Find("#approve-status-checks button.link-action").Length()) // user2 can see the approve button req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d", baseRepo.OwnerName, baseRepo.Name, apiPull.Index)) resp = user2Session.MakeRequest(t, req, http.StatusOK) htmlDoc = NewHTMLParser(t, resp.Body) - dataURL, exist := htmlDoc.doc.Find(".commit-status-header button.link-action").Attr("data-url") + dataURL, exist := htmlDoc.doc.Find("#approve-status-checks button.link-action").Attr("data-url") assert.True(t, exist) assert.Equal(t, fmt.Sprintf("%s/actions/approve-all-checks?commit_id=%s", From 59625a1c3fb928e928d7da57e0da4293a124a637 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Sun, 19 Oct 2025 19:25:40 -0600 Subject: [PATCH 16/16] Update templates/repo/pulls/status.tmpl Co-authored-by: wxiaoguang Signed-off-by: Zettat123 --- templates/repo/pulls/status.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/repo/pulls/status.tmpl b/templates/repo/pulls/status.tmpl index bdc0c06b57c83..f3c1973c2b7fd 100644 --- a/templates/repo/pulls/status.tmpl +++ b/templates/repo/pulls/status.tmpl @@ -32,8 +32,8 @@ {{end}}
- {{if and $statusCheckData (gt $statusCheckData.RequireApprovalRunCount 0)}} -
+ {{if and $statusCheckData $statusCheckData.RequireApprovalRunCount}} +
{{ctx.Locale.Tr "repo.pulls.status_checks_need_approvals" $statusCheckData.RequireApprovalRunCount}}