Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions models/git/commit_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
91 changes: 74 additions & 17 deletions routers/web/repo/actions/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
}
Expand All @@ -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]
Expand All @@ -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) {
Expand Down Expand Up @@ -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.GetRunsFromCommitStatuses(ctx, commitStatuses)
if err != nil {
ctx.ServerError("GetRunsFromCommitStatuses", 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(ctx.Tr("actions.approve_all_success"))
ctx.Redirect(redirect)
}

func DisableWorkflowFile(ctx *context_module.Context) {
disableOrEnableWorkflowFile(ctx, false)
}
Expand Down
33 changes: 31 additions & 2 deletions routers/web/repo/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -311,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
Expand Down Expand Up @@ -456,6 +465,11 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *pull_
return nil
}

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 {
ctx.ServerError("GetLatestCommitStatus", err)
Expand All @@ -465,6 +479,21 @@ func prepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *pull_
git_model.CommitStatusesHideActionsURL(ctx, commitStatuses)
}

runs, err := actions_service.GetRunsFromCommitStatuses(ctx, commitStatuses)
if err != nil {
ctx.ServerError("GetRunsFromCommitStatuses", err)
return nil
}
for _, run := range runs {
if run.NeedApproval {
statusCheckData.RequireApproval = true
break
}
}
if statusCheckData.RequireApproval {
statusCheckData.CanApprove = ctx.Repo.CanWrite(unit.TypeActions)
}

if len(commitStatuses) > 0 {
ctx.Data["LatestCommitStatuses"] = commitStatuses
ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(commitStatuses)
Expand All @@ -486,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
Expand Down
1 change: 1 addition & 0 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -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("").
Expand Down
56 changes: 56 additions & 0 deletions services/actions/commit_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -18,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"

Expand Down Expand Up @@ -52,6 +55,59 @@ func CreateCommitStatusForRunJobs(ctx context.Context, run *actions_model.Action
}
}

func GetRunsFromCommitStatuses(ctx context.Context, statuses []*git_model.CommitStatus) ([]*actions_model.ActionRun, error) {
runMap := make(map[int64]*actions_model.ActionRun)
for _, status := range statuses {
if !status.CreatedByGiteaActions(ctx) {
continue
}
runIndex, _, err := getActionRunAndJobIndexFromCommitStatus(status)
if err != nil {
return nil, fmt.Errorf("getActionRunAndJobIndexFromCommitStatus: %w", err)
}
_, ok := runMap[runIndex]
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, fmt.Errorf("GetRunByIndex: %w", err)
}
runMap[runIndex] = run
}
}
runs := make([]*actions_model.ActionRun, 0, len(runMap))
for _, run := range runMap {
runs = append(runs, run)
}
return runs, nil
}

func getActionRunAndJobIndexFromCommitStatus(status *git_model.CommitStatus) (int64, int64, error) {
actionsLink, _ := strings.CutPrefix(status.TargetURL, status.Repo.Link()+"/actions/")
// actionsLink should be like "runs/<run_index>/jobs/<job_index>"

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:
Expand Down
3 changes: 1 addition & 2 deletions templates/repo/issue/view_content/pull_merge_box.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,8 @@
{{template "repo/pulls/status" (dict
"CommitStatus" .LatestCommitStatus
"CommitStatuses" .LatestCommitStatuses
"MissingRequiredChecks" .MissingRequiredChecks
"ShowHideChecks" true
"is_context_required" .is_context_required
"StatusCheckData" .StatusCheckData
)}}
</div>
{{end}}
Expand Down
Loading