From 7106d44222d688d3746a944ed0f633c03fc3d197 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Thu, 5 Mar 2026 22:12:26 -0700 Subject: [PATCH 01/12] replace index with id --- models/actions/run.go | 4 +- models/git/commit_status_test.go | 2 +- routers/api/v1/repo/action.go | 8 +- routers/common/actions.go | 16 +- routers/web/devtest/mock_actions.go | 4 +- routers/web/repo/actions/view.go | 144 ++++++++++-------- services/actions/commit_status.go | 31 +--- services/convert/convert.go | 14 +- templates/devtest/repo-action-view.tmpl | 4 +- templates/repo/actions/view.tmpl | 4 +- templates/repo/actions/view_component.tmpl | 4 +- tests/integration/actions_concurrency_test.go | 42 +++-- tests/integration/actions_delete_run_test.go | 31 ++-- tests/integration/actions_log_test.go | 18 +-- tests/integration/actions_rerun_test.go | 11 +- tests/integration/actions_view_test.go | 100 ++++++++++++ tests/integration/repo_webhook_test.go | 10 +- web_src/js/components/RepoActionView.vue | 14 +- web_src/js/components/WorkflowGraph.vue | 12 +- web_src/js/features/repo-actions.ts | 4 +- 20 files changed, 285 insertions(+), 192 deletions(-) create mode 100644 tests/integration/actions_view_test.go diff --git a/models/actions/run.go b/models/actions/run.go index 99e6267071e5d..2f6ed8ac3db7a 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -70,14 +70,14 @@ func (run *ActionRun) HTMLURL() string { if run.Repo == nil { return "" } - return fmt.Sprintf("%s/actions/runs/%d", run.Repo.HTMLURL(), run.Index) + return fmt.Sprintf("%s/actions/runs/%d", run.Repo.HTMLURL(), run.ID) } func (run *ActionRun) Link() string { if run.Repo == nil { return "" } - return fmt.Sprintf("%s/actions/runs/%d", run.Repo.Link(), run.Index) + return fmt.Sprintf("%s/actions/runs/%d", run.Repo.Link(), run.ID) } func (run *ActionRun) WorkflowLink() string { diff --git a/models/git/commit_status_test.go b/models/git/commit_status_test.go index d1b9dfc3bf9f5..f565550c539b4 100644 --- a/models/git/commit_status_test.go +++ b/models/git/commit_status_test.go @@ -243,7 +243,7 @@ func TestCommitStatusesHideActionsURL(t *testing.T) { statuses := []*git_model.CommitStatus{ { RepoID: repo.ID, - TargetURL: fmt.Sprintf("%s/jobs/%d", run.Link(), run.Index), + TargetURL: fmt.Sprintf("%s/jobs/%d", run.Link(), run.ID), }, { RepoID: repo.ID, diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go index 13da5aa81512d..d8b4edcd8d977 100644 --- a/routers/api/v1/repo/action.go +++ b/routers/api/v1/repo/action.go @@ -1041,15 +1041,9 @@ func ActionsDispatchWorkflow(ctx *context.APIContext) { return } - workflowRun, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID) - if err != nil { - ctx.APIErrorInternal(err) - return - } - ctx.JSON(http.StatusOK, &api.RunDetails{ WorkflowRunID: runID, - HTMLURL: fmt.Sprintf("%s/actions/runs/%d", ctx.Repo.Repository.HTMLURL(ctx), workflowRun.Index), + HTMLURL: fmt.Sprintf("%s/actions/runs/%d", ctx.Repo.Repository.HTMLURL(ctx), runID), RunURL: fmt.Sprintf("%s/actions/runs/%d", ctx.Repo.Repository.APIURL(), runID), }) } diff --git a/routers/common/actions.go b/routers/common/actions.go index a4eabb6ba21f1..7fc64c50a6257 100644 --- a/routers/common/actions.go +++ b/routers/common/actions.go @@ -14,18 +14,18 @@ import ( "code.gitea.io/gitea/services/context" ) -func DownloadActionsRunJobLogsWithIndex(ctx *context.Base, ctxRepo *repo_model.Repository, runID, jobIndex int64) error { - runJobs, err := actions_model.GetRunJobsByRunID(ctx, runID) +func DownloadActionsRunJobLogsWithID(ctx *context.Base, ctxRepo *repo_model.Repository, runID, jobID int64) error { + job, err := actions_model.GetRunJobByID(ctx, jobID) if err != nil { - return fmt.Errorf("GetRunJobsByRunID: %w", err) + return err } - if err = runJobs.LoadRepos(ctx); err != nil { - return fmt.Errorf("LoadRepos: %w", err) + if job.RunID != runID { + return util.NewNotExistErrorf("job not found") } - if jobIndex < 0 || jobIndex >= int64(len(runJobs)) { - return util.NewNotExistErrorf("job index is out of range: %d", jobIndex) + if err := job.LoadRepo(ctx); err != nil { + return fmt.Errorf("LoadRepo: %w", err) } - return DownloadActionsRunJobLogs(ctx, ctxRepo, runJobs[jobIndex]) + return DownloadActionsRunJobLogs(ctx, ctxRepo, job) } func DownloadActionsRunJobLogs(ctx *context.Base, ctxRepo *repo_model.Repository, curJob *actions_model.ActionRunJob) error { diff --git a/routers/web/devtest/mock_actions.go b/routers/web/devtest/mock_actions.go index 7a71dad804daf..0dd33425dc267 100644 --- a/routers/web/devtest/mock_actions.go +++ b/routers/web/devtest/mock_actions.go @@ -59,8 +59,8 @@ func generateMockStepsLog(logCur actions.LogCursor, opts generateMockStepsLogOpt } func MockActionsView(ctx *context.Context) { - ctx.Data["RunIndex"] = ctx.PathParam("run") - ctx.Data["JobIndex"] = ctx.PathParam("job") + ctx.Data["RunID"] = ctx.PathParam("run") + ctx.Data["JobID"] = ctx.PathParam("job") ctx.HTML(http.StatusOK, "devtest/repo-action-view") } diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 0eaa6cab41b3a..156e9e49305de 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -38,11 +38,11 @@ import ( "github.com/nektos/act/pkg/model" ) -func getRunIndex(ctx *context_module.Context) int64 { - // if run param is "latest", get the latest run index +func getRunID(ctx *context_module.Context) int64 { + // if run param is "latest", get the latest run id if ctx.PathParam("run") == "latest" { if run, _ := actions_model.GetLatestRun(ctx, ctx.Repo.Repository.ID); run != nil { - return run.Index + return run.ID } } return ctx.PathParamInt64("run") @@ -50,24 +50,27 @@ func getRunIndex(ctx *context_module.Context) int64 { func View(ctx *context_module.Context) { ctx.Data["PageIsActions"] = true - runIndex := getRunIndex(ctx) - jobIndex := ctx.PathParamInt("job") - ctx.Data["RunIndex"] = runIndex - ctx.Data["JobIndex"] = jobIndex - ctx.Data["ActionsURL"] = ctx.Repo.RepoLink + "/actions" + runID := getRunID(ctx) + jobIDHas := ctx.PathParam("job") != "" + jobID := ctx.PathParamInt64("job") - if getRunJobs(ctx, runIndex, jobIndex); ctx.Written() { + current, _ := getRunJobsByID(ctx, runID, jobID, jobIDHas) + if ctx.Written() { return } + ctx.Data["RunID"] = runID + ctx.Data["JobID"] = current.ID + ctx.Data["ActionsURL"] = ctx.Repo.RepoLink + "/actions" + ctx.HTML(http.StatusOK, tplViewActions) } func ViewWorkflowFile(ctx *context_module.Context) { - runIndex := getRunIndex(ctx) - run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex) + runID := getRunID(ctx) + run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID) if err != nil { - ctx.NotFoundOrServerError("GetRunByIndex", func(err error) bool { + ctx.NotFoundOrServerError("GetRunByRepoAndID", func(err error) bool { return errors.Is(err, util.ErrNotExist) }, err) return @@ -187,8 +190,8 @@ type ViewStepLogLine struct { Timestamp float64 `json:"timestamp"` } -func getActionsViewArtifacts(ctx context.Context, repoID, runIndex int64) (artifactsViewItems []*ArtifactsViewItem, err error) { - run, err := actions_model.GetRunByIndex(ctx, repoID, runIndex) +func getActionsViewArtifacts(ctx context.Context, repoID, runID int64) (artifactsViewItems []*ArtifactsViewItem, err error) { + run, err := actions_model.GetRunByRepoAndID(ctx, repoID, runID) if err != nil { return nil, err } @@ -208,10 +211,11 @@ func getActionsViewArtifacts(ctx context.Context, repoID, runIndex int64) (artif func ViewPost(ctx *context_module.Context) { req := web.GetForm(ctx).(*ViewRequest) - runIndex := getRunIndex(ctx) - jobIndex := ctx.PathParamInt("job") + runID := getRunID(ctx) + jobIDHas := ctx.PathParam("job") != "" + jobID := ctx.PathParamInt64("job") - current, jobs := getRunJobs(ctx, runIndex, jobIndex) + current, jobs := getRunJobsByID(ctx, runID, jobID, jobIDHas) if ctx.Written() { return } @@ -223,7 +227,7 @@ func ViewPost(ctx *context_module.Context) { var err error resp := &ViewResponse{} - resp.Artifacts, err = getActionsViewArtifacts(ctx, ctx.Repo.Repository.ID, runIndex) + resp.Artifacts, err = getActionsViewArtifacts(ctx, ctx.Repo.Repository.ID, runID) if err != nil { if !errors.Is(err, util.ErrNotExist) { ctx.ServerError("getActionsViewArtifacts", err) @@ -400,16 +404,19 @@ func convertToViewModel(ctx context.Context, locale translation.Locale, cursors } // Rerun will rerun jobs in the given run -// If jobIndexStr is a blank string, it means rerun all jobs +// If jobIDStr is a blank string, it means rerun all jobs func Rerun(ctx *context_module.Context) { - runIndex := getRunIndex(ctx) - jobIndexHas := ctx.PathParam("job") != "" - jobIndex := ctx.PathParamInt("job") + runID := getRunID(ctx) + jobIDHas := ctx.PathParam("job") != "" + jobID := ctx.PathParamInt64("job") - run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex) + run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID) if err != nil { - ctx.ServerError("GetRunByIndex", err) - return + if errors.Is(err, util.ErrNotExist) { + ctx.HTTPError(http.StatusNotFound, err.Error()) + return + } + ctx.ServerError("GetRunByRepoAndID", err) } // rerun is not allowed if the run is not done @@ -433,12 +440,17 @@ func Rerun(ctx *context_module.Context) { } var targetJob *actions_model.ActionRunJob // nil means rerun all jobs - if jobIndexHas { - if jobIndex < 0 || jobIndex >= len(jobs) { + if jobIDHas { + for _, job := range jobs { + if job.ID == jobID { + targetJob = job + break + } + } + if targetJob == nil { ctx.JSONError(ctx.Locale.Tr("error.not_found")) return } - targetJob = jobs[jobIndex] // only rerun the selected job } if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, jobs, targetJob); err != nil { @@ -450,28 +462,28 @@ func Rerun(ctx *context_module.Context) { } func Logs(ctx *context_module.Context) { - runIndex := getRunIndex(ctx) - jobIndex := ctx.PathParamInt64("job") + runID := getRunID(ctx) + jobID := ctx.PathParamInt64("job") - run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex) + run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID) if err != nil { - ctx.NotFoundOrServerError("GetRunByIndex", func(err error) bool { + ctx.NotFoundOrServerError("GetRunByRepoAndID", func(err error) bool { return errors.Is(err, util.ErrNotExist) }, err) return } - if err = common.DownloadActionsRunJobLogsWithIndex(ctx.Base, ctx.Repo.Repository, run.ID, jobIndex); err != nil { - ctx.NotFoundOrServerError("DownloadActionsRunJobLogsWithIndex", func(err error) bool { + if err = common.DownloadActionsRunJobLogsWithID(ctx.Base, ctx.Repo.Repository, run.ID, jobID); err != nil { + ctx.NotFoundOrServerError("DownloadActionsRunJobLogsWithID", func(err error) bool { return errors.Is(err, util.ErrNotExist) }, err) } } func Cancel(ctx *context_module.Context) { - runIndex := getRunIndex(ctx) + runID := getRunID(ctx) - firstJob, jobs := getRunJobs(ctx, runIndex, -1) + firstJob, jobs := getRunJobsByID(ctx, runID, 0, false) if ctx.Written() { return } @@ -505,9 +517,9 @@ func Cancel(ctx *context_module.Context) { } func Approve(ctx *context_module.Context) { - runIndex := getRunIndex(ctx) + runID := getRunID(ctx) - approveRuns(ctx, []int64{runIndex}) + approveRuns(ctx, []int64{runID}) if ctx.Written() { return } @@ -515,17 +527,17 @@ func Approve(ctx *context_module.Context) { ctx.JSONOK() } -func approveRuns(ctx *context_module.Context, runIndexes []int64) { +func approveRuns(ctx *context_module.Context, runIDs []int64) { doer := ctx.Doer repo := ctx.Repo.Repository updatedJobs := make([]*actions_model.ActionRunJob, 0) - runMap := make(map[int64]*actions_model.ActionRun, len(runIndexes)) - runJobs := make(map[int64][]*actions_model.ActionRunJob, len(runIndexes)) + runMap := make(map[int64]*actions_model.ActionRun, len(runIDs)) + runJobs := make(map[int64][]*actions_model.ActionRunJob, len(runIDs)) err := db.WithTx(ctx, func(ctx context.Context) (err error) { - for _, runIndex := range runIndexes { - run, err := actions_model.GetRunByIndex(ctx, repo.ID, runIndex) + for _, runID := range runIDs { + run, err := actions_model.GetRunByRepoAndID(ctx, repo.ID, runID) if err != nil { return err } @@ -580,16 +592,16 @@ func approveRuns(ctx *context_module.Context, runIndexes []int64) { } func Delete(ctx *context_module.Context) { - runIndex := getRunIndex(ctx) + runID := getRunID(ctx) repoID := ctx.Repo.Repository.ID - run, err := actions_model.GetRunByIndex(ctx, repoID, runIndex) + run, err := actions_model.GetRunByRepoAndID(ctx, repoID, runID) if err != nil { if errors.Is(err, util.ErrNotExist) { ctx.JSONErrorNotFound() return } - ctx.ServerError("GetRunByIndex", err) + ctx.ServerError("GetRunByRepoAndID", err) return } @@ -606,17 +618,17 @@ func Delete(ctx *context_module.Context) { ctx.JSONOK() } -// getRunJobs gets the jobs of runIndex, and returns jobs[jobIndex], jobs. +// getRunJobsByID gets the jobs of runID, and returns the selected job and all jobs. // Any error will be written to the ctx. -// It never returns a nil job of an empty jobs, if the jobIndex is out of range, it will be treated as 0. -func getRunJobs(ctx *context_module.Context, runIndex int64, jobIndex int) (*actions_model.ActionRunJob, []*actions_model.ActionRunJob) { - run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex) +// If the jobID is not found when jobIDHas is true, it returns 404. +func getRunJobsByID(ctx *context_module.Context, runID, jobID int64, jobIDHas bool) (*actions_model.ActionRunJob, []*actions_model.ActionRunJob) { + run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID) if err != nil { if errors.Is(err, util.ErrNotExist) { ctx.NotFound(nil) return nil, nil } - ctx.ServerError("GetRunByIndex", err) + ctx.ServerError("GetRunByRepoAndID", err) return nil, nil } run.Repo = ctx.Repo.Repository @@ -634,19 +646,25 @@ func getRunJobs(ctx *context_module.Context, runIndex int64, jobIndex int) (*act v.Run = run } - if jobIndex >= 0 && jobIndex < len(jobs) { - return jobs[jobIndex], jobs + if jobIDHas { + for _, v := range jobs { + if v.ID == jobID { + return v, jobs + } + } + ctx.NotFound(nil) + return nil, nil } return jobs[0], jobs } func ArtifactsDeleteView(ctx *context_module.Context) { - runIndex := getRunIndex(ctx) + runID := getRunID(ctx) artifactName := ctx.PathParam("artifact_name") - run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex) + run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID) if err != nil { - ctx.NotFoundOrServerError("GetRunByIndex", func(err error) bool { + ctx.NotFoundOrServerError("GetRunByRepoAndID", func(err error) bool { return errors.Is(err, util.ErrNotExist) }, err) return @@ -659,16 +677,16 @@ func ArtifactsDeleteView(ctx *context_module.Context) { } func ArtifactsDownloadView(ctx *context_module.Context) { - runIndex := getRunIndex(ctx) + runID := getRunID(ctx) artifactName := ctx.PathParam("artifact_name") - run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex) + run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID) if err != nil { if errors.Is(err, util.ErrNotExist) { ctx.HTTPError(http.StatusNotFound, err.Error()) return } - ctx.ServerError("GetRunByIndex", err) + ctx.ServerError("GetRunByRepoAndID", err) return } @@ -754,19 +772,19 @@ func ApproveAllChecks(ctx *context_module.Context) { return } - runIndexes := make([]int64, 0, len(runs)) + runIDs := make([]int64, 0, len(runs)) for _, run := range runs { if run.NeedApproval { - runIndexes = append(runIndexes, run.Index) + runIDs = append(runIDs, run.ID) } } - if len(runIndexes) == 0 { + if len(runIDs) == 0 { ctx.JSONOK() return } - approveRuns(ctx, runIndexes) + approveRuns(ctx, runIDs) if ctx.Written() { return } diff --git a/services/actions/commit_status.go b/services/actions/commit_status.go index 22ee8ad1d6c44..95b848f4fb1d2 100644 --- a/services/actions/commit_status.go +++ b/services/actions/commit_status.go @@ -56,21 +56,21 @@ 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 { - runIndex, _, ok := status.ParseGiteaActionsTargetURL(ctx) + runID, _, ok := status.ParseGiteaActionsTargetURL(ctx) if !ok { continue } - _, ok = runMap[runIndex] + _, ok = runMap[runID] if !ok { - run, err := actions_model.GetRunByIndex(ctx, status.RepoID, runIndex) + run, err := actions_model.GetRunByRepoAndID(ctx, status.RepoID, runID) 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) + return nil, fmt.Errorf("GetRunByRepoAndID: %w", err) } - runMap[runIndex] = run + runMap[runID] = run } } runs := make([]*actions_model.ActionRun, 0, len(runMap)) @@ -181,15 +181,10 @@ func createCommitStatus(ctx context.Context, repo *repo_model.Repository, event, description = "Unknown status: " + strconv.Itoa(int(job.Status)) } - index, err := getIndexOfJob(ctx, job) - if err != nil { - return fmt.Errorf("getIndexOfJob: %w", err) - } - creator := user_model.NewActionsUser() status := git_model.CommitStatus{ SHA: commitID, - TargetURL: fmt.Sprintf("%s/jobs/%d", run.Link(), index), + TargetURL: fmt.Sprintf("%s/jobs/%d", run.Link(), job.ID), Description: description, Context: ctxName, CreatorID: creator.ID, @@ -213,17 +208,3 @@ func toCommitStatus(status actions_model.Status) commitstatus.CommitStatusState return commitstatus.CommitStatusError } } - -func getIndexOfJob(ctx context.Context, job *actions_model.ActionRunJob) (int, error) { - // TODO: store job index as a field in ActionRunJob to avoid this - jobs, err := actions_model.GetRunJobsByRunID(ctx, job.RunID) - if err != nil { - return 0, err - } - for i, v := range jobs { - if v.ID == job.ID { - return i, nil - } - } - return 0, nil -} diff --git a/services/convert/convert.go b/services/convert/convert.go index e1cd30705e7ec..fac13b14ebe50 100644 --- a/services/convert/convert.go +++ b/services/convert/convert.go @@ -324,18 +324,6 @@ func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task return nil, err } - jobIndex := 0 - jobs, err := actions_model.GetRunJobsByRunID(ctx, job.RunID) - if err != nil { - return nil, err - } - for i, j := range jobs { - if j.ID == job.ID { - jobIndex = i - break - } - } - status, conclusion := ToActionsStatus(job.Status) var runnerID int64 var runnerName string @@ -379,7 +367,7 @@ func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task ID: job.ID, // missing api endpoint for this location URL: fmt.Sprintf("%s/actions/jobs/%d", repo.APIURL(), job.ID), - HTMLURL: fmt.Sprintf("%s/jobs/%d", job.Run.HTMLURL(), jobIndex), + HTMLURL: fmt.Sprintf("%s/jobs/%d", job.Run.HTMLURL(), job.ID), RunID: job.RunID, // Missing api endpoint for this location, artifacts are available under a nested url RunURL: fmt.Sprintf("%s/actions/runs/%d", repo.APIURL(), job.RunID), diff --git a/templates/devtest/repo-action-view.tmpl b/templates/devtest/repo-action-view.tmpl index 292c84c928873..b3a52db1e291e 100644 --- a/templates/devtest/repo-action-view.tmpl +++ b/templates/devtest/repo-action-view.tmpl @@ -6,8 +6,8 @@ Run:CanRerun {{template "repo/actions/view_component" (dict - "RunIndex" (or .RunIndex 10) - "JobIndex" (or .JobIndex 0) + "RunID" (or .RunID 10) + "JobID" (or .JobID 0) "ActionsURL" (print AppSubUrl "/devtest/actions-mock") )}} diff --git a/templates/repo/actions/view.tmpl b/templates/repo/actions/view.tmpl index bde579f88214c..1eb84a9b93773 100644 --- a/templates/repo/actions/view.tmpl +++ b/templates/repo/actions/view.tmpl @@ -3,8 +3,8 @@
{{template "repo/header" .}} {{template "repo/actions/view_component" (dict - "RunIndex" .RunIndex - "JobIndex" .JobIndex + "RunID" .RunID + "JobID" .JobID "ActionsURL" .ActionsURL )}}
diff --git a/templates/repo/actions/view_component.tmpl b/templates/repo/actions/view_component.tmpl index ebe5158c8a3f8..457159ef54fc6 100644 --- a/templates/repo/actions/view_component.tmpl +++ b/templates/repo/actions/view_component.tmpl @@ -1,6 +1,6 @@
>, @@ -366,7 +366,7 @@ export default defineComponent({ // for example: make cursor=null means the first time to fetch logs, cursor=eof means no more logs, etc return {step: idx, cursor: it.cursor, expanded: it.expanded}; }); - const resp = await POST(`${this.actionsURL}/runs/${this.runIndex}/jobs/${this.jobIndex}`, { + const resp = await POST(`${this.actionsURL}/runs/${this.runId}/jobs/${this.jobId}`, { signal: abortController.signal, data: {logCursors}, }); @@ -538,13 +538,13 @@ export default defineComponent({
- +
{{ job.name }}
- + {{ job.duration }}
@@ -581,7 +581,7 @@ export default defineComponent({
- + {{ locale.downloadLogs }} diff --git a/web_src/js/components/WorkflowGraph.vue b/web_src/js/components/WorkflowGraph.vue index dc876d9437b0a..571db2cd03604 100644 --- a/web_src/js/components/WorkflowGraph.vue +++ b/web_src/js/components/WorkflowGraph.vue @@ -40,7 +40,7 @@ interface StoredState { const props = defineProps<{ jobs: ActionsJob[]; - currentJobIndex: number; + currentJobId: number; runLink: string; workflowId: string; }>() @@ -588,9 +588,9 @@ function computeJobLevels(jobs: ActionsJob[]): Map { } function onNodeClick(job: JobNode, event: MouseEvent) { - if (job.index === props.currentJobIndex) return; + if (job.id === props.currentJobId) return; - const link = `${props.runLink}/jobs/${job.index}`; + const link = `${props.runLink}/jobs/${job.id}`; if (event.ctrlKey || event.metaKey) { window.open(link, '_blank'); return; @@ -652,7 +652,7 @@ function onNodeClick(job: JobNode, event: MouseEvent) { diff --git a/web_src/js/features/repo-actions.ts b/web_src/js/features/repo-actions.ts index 242dd300168fd..fca41c0c66a8d 100644 --- a/web_src/js/features/repo-actions.ts +++ b/web_src/js/features/repo-actions.ts @@ -11,8 +11,8 @@ export function initRepositoryActionView() { if (parentFullHeight) parentFullHeight.classList.add('tw-pb-0'); const view = createApp(RepoActionView, { - runIndex: parseInt(el.getAttribute('data-run-index')!), - jobIndex: parseInt(el.getAttribute('data-job-index')!), + runId: parseInt(el.getAttribute('data-run-id')!), + jobId: parseInt(el.getAttribute('data-job-id')!), actionsURL: el.getAttribute('data-actions-url'), locale: { approve: el.getAttribute('data-locale-approve'), From 6b2baa4281aa0ac3c9e1b0beb7abe2068aa962dd Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Sat, 7 Mar 2026 13:03:09 -0700 Subject: [PATCH 02/12] fix review comments --- models/actions/run.go | 2 +- models/actions/run_job.go | 20 ++++- models/actions/task.go | 4 +- routers/api/v1/repo/actions_run.go | 2 +- routers/common/actions.go | 5 +- routers/web/repo/actions/view.go | 83 ++++++------------- ...ons_view_test.go => actions_route_test.go} | 6 +- tests/integration/api_actions_run_test.go | 8 +- 8 files changed, 56 insertions(+), 74 deletions(-) rename tests/integration/{actions_view_test.go => actions_route_test.go} (95%) diff --git a/models/actions/run.go b/models/actions/run.go index 2f6ed8ac3db7a..e293a6056fbba 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -299,7 +299,7 @@ func CancelJobs(ctx context.Context, jobs []*ActionRunJob) ([]*ActionRunJob, err if err := StopTask(ctx, job.TaskID, StatusCancelled); err != nil { return cancelledJobs, err } - updatedJob, err := GetRunJobByID(ctx, job.ID) + updatedJob, err := GetRunJobByRunAndID(ctx, job.RunID, job.ID) if err != nil { return cancelledJobs, fmt.Errorf("get job: %w", err) } diff --git a/models/actions/run_job.go b/models/actions/run_job.go index 430f585663e29..c752e61b7d749 100644 --- a/models/actions/run_job.go +++ b/models/actions/run_job.go @@ -118,13 +118,25 @@ func (job *ActionRunJob) ParseJob() (*jobparser.Job, error) { return workflowJob, nil } -func GetRunJobByID(ctx context.Context, id int64) (*ActionRunJob, error) { +func GetRunJobByRepoAndID(ctx context.Context, repoID, jobID int64) (*ActionRunJob, error) { var job ActionRunJob - has, err := db.GetEngine(ctx).Where("id=?", id).Get(&job) + has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", jobID, repoID).Get(&job) if err != nil { return nil, err } else if !has { - return nil, fmt.Errorf("run job with id %d: %w", id, util.ErrNotExist) + return nil, fmt.Errorf("run job with id %d: %w", jobID, util.ErrNotExist) + } + + return &job, nil +} + +func GetRunJobByRunAndID(ctx context.Context, runID, jobID int64) (*ActionRunJob, error) { + var job ActionRunJob + has, err := db.GetEngine(ctx).Where("id=? AND run_id=?", jobID, runID).Get(&job) + if err != nil { + return nil, err + } else if !has { + return nil, fmt.Errorf("run job with id %d: %w", jobID, util.ErrNotExist) } return &job, nil @@ -168,7 +180,7 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col if job.RunID == 0 { var err error - if job, err = GetRunJobByID(ctx, job.ID); err != nil { + if job, err = GetRunJobByRepoAndID(ctx, job.RepoID, job.ID); err != nil { return 0, err } } diff --git a/models/actions/task.go b/models/actions/task.go index c35395ceed080..e092d6fbbd948 100644 --- a/models/actions/task.go +++ b/models/actions/task.go @@ -114,7 +114,7 @@ func (task *ActionTask) GetRepoLink() string { func (task *ActionTask) LoadJob(ctx context.Context) error { if task.Job == nil { - job, err := GetRunJobByID(ctx, task.JobID) + job, err := GetRunJobByRepoAndID(ctx, task.RepoID, task.JobID) if err != nil { return err } @@ -388,6 +388,7 @@ func UpdateTaskByState(ctx context.Context, runnerID int64, state *runnerv1.Task } if _, err := UpdateRunJob(ctx, &ActionRunJob{ ID: task.JobID, + RepoID: task.RepoID, Status: task.Status, Stopped: task.Stopped, }, nil); err != nil { @@ -449,6 +450,7 @@ func StopTask(ctx context.Context, taskID int64, status Status) error { task.Stopped = now if _, err := UpdateRunJob(ctx, &ActionRunJob{ ID: task.JobID, + RepoID: task.RepoID, Status: task.Status, Stopped: task.Stopped, }, nil); err != nil { diff --git a/routers/api/v1/repo/actions_run.go b/routers/api/v1/repo/actions_run.go index a12a6fdd6d796..2c6a28813dfa6 100644 --- a/routers/api/v1/repo/actions_run.go +++ b/routers/api/v1/repo/actions_run.go @@ -43,7 +43,7 @@ func DownloadActionsRunJobLogs(ctx *context.APIContext) { // "$ref": "#/responses/notFound" jobID := ctx.PathParamInt64("job_id") - curJob, err := actions_model.GetRunJobByID(ctx, jobID) + curJob, err := actions_model.GetRunJobByRepoAndID(ctx, ctx.Repo.Repository.ID, jobID) if err != nil { ctx.APIErrorInternal(err) return diff --git a/routers/common/actions.go b/routers/common/actions.go index 7fc64c50a6257..39d2111f5a1b1 100644 --- a/routers/common/actions.go +++ b/routers/common/actions.go @@ -15,13 +15,10 @@ import ( ) func DownloadActionsRunJobLogsWithID(ctx *context.Base, ctxRepo *repo_model.Repository, runID, jobID int64) error { - job, err := actions_model.GetRunJobByID(ctx, jobID) + job, err := actions_model.GetRunJobByRunAndID(ctx, runID, jobID) if err != nil { return err } - if job.RunID != runID { - return util.NewNotExistErrorf("job not found") - } if err := job.LoadRepo(ctx); err != nil { return fmt.Errorf("LoadRepo: %w", err) } diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 156e9e49305de..8ee68b483e226 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -51,10 +51,8 @@ func getRunID(ctx *context_module.Context) int64 { func View(ctx *context_module.Context) { ctx.Data["PageIsActions"] = true runID := getRunID(ctx) - jobIDHas := ctx.PathParam("job") != "" - jobID := ctx.PathParamInt64("job") - current, _ := getRunJobsByID(ctx, runID, jobID, jobIDHas) + _, _, current := getRunJobsAndCurrentJob(ctx, runID) if ctx.Written() { return } @@ -212,14 +210,11 @@ func getActionsViewArtifacts(ctx context.Context, repoID, runID int64) (artifact func ViewPost(ctx *context_module.Context) { req := web.GetForm(ctx).(*ViewRequest) runID := getRunID(ctx) - jobIDHas := ctx.PathParam("job") != "" - jobID := ctx.PathParamInt64("job") - current, jobs := getRunJobsByID(ctx, runID, jobID, jobIDHas) + run, jobs, current := getRunJobsAndCurrentJob(ctx, runID) if ctx.Written() { return } - run := current.Run if err := run.LoadAttributes(ctx); err != nil { ctx.ServerError("run.LoadAttributes", err) return @@ -407,16 +402,10 @@ func convertToViewModel(ctx context.Context, locale translation.Locale, cursors // If jobIDStr is a blank string, it means rerun all jobs func Rerun(ctx *context_module.Context) { runID := getRunID(ctx) - jobIDHas := ctx.PathParam("job") != "" - jobID := ctx.PathParamInt64("job") - run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID) - if err != nil { - if errors.Is(err, util.ErrNotExist) { - ctx.HTTPError(http.StatusNotFound, err.Error()) - return - } - ctx.ServerError("GetRunByRepoAndID", err) + run, jobs, currentJob := getRunJobsAndCurrentJob(ctx, runID) + if ctx.Written() { + return } // rerun is not allowed if the run is not done @@ -433,24 +422,9 @@ func Rerun(ctx *context_module.Context) { return } - jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID) - if err != nil { - ctx.ServerError("GetRunJobsByRunID", err) - return - } - var targetJob *actions_model.ActionRunJob // nil means rerun all jobs - if jobIDHas { - for _, job := range jobs { - if job.ID == jobID { - targetJob = job - break - } - } - if targetJob == nil { - ctx.JSONError(ctx.Locale.Tr("error.not_found")) - return - } + if ctx.PathParam("job") != "" { + targetJob = currentJob } if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, jobs, targetJob); err != nil { @@ -483,7 +457,7 @@ func Logs(ctx *context_module.Context) { func Cancel(ctx *context_module.Context) { runID := getRunID(ctx) - firstJob, jobs := getRunJobsByID(ctx, runID, 0, false) + run, jobs, _ := getRunJobsAndCurrentJob(ctx, runID) if ctx.Written() { return } @@ -502,7 +476,7 @@ func Cancel(ctx *context_module.Context) { return } - actions_service.CreateCommitStatusForRunJobs(ctx, firstJob.Run, jobs...) + actions_service.CreateCommitStatusForRunJobs(ctx, run, jobs...) actions_service.EmitJobsIfReadyByJobs(updatedJobs) for _, job := range updatedJobs { @@ -621,41 +595,38 @@ func Delete(ctx *context_module.Context) { // getRunJobsByID gets the jobs of runID, and returns the selected job and all jobs. // Any error will be written to the ctx. // If the jobID is not found when jobIDHas is true, it returns 404. -func getRunJobsByID(ctx *context_module.Context, runID, jobID int64, jobIDHas bool) (*actions_model.ActionRunJob, []*actions_model.ActionRunJob) { +func getRunJobsAndCurrentJob(ctx *context_module.Context, runID int64) (*actions_model.ActionRun, []*actions_model.ActionRunJob, *actions_model.ActionRunJob) { run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID) if err != nil { - if errors.Is(err, util.ErrNotExist) { - ctx.NotFound(nil) - return nil, nil - } - ctx.ServerError("GetRunByRepoAndID", err) - return nil, nil + ctx.NotFoundOrServerError("GetRunByRepoAndID", func(err error) bool { + return errors.Is(err, util.ErrNotExist) + }, err) + return nil, nil, nil } run.Repo = ctx.Repo.Repository jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID) if err != nil { ctx.ServerError("GetRunJobsByRunID", err) - return nil, nil + return nil, nil, nil } if len(jobs) == 0 { ctx.NotFound(nil) - return nil, nil - } - - for _, v := range jobs { - v.Run = run + return nil, nil, nil } - if jobIDHas { - for _, v := range jobs { - if v.ID == jobID { - return v, jobs - } + current := jobs[0] + if ctx.PathParam("job") != "" { + jobID := ctx.PathParamInt64("job") + current, err = actions_model.GetRunJobByRunAndID(ctx, run.ID, jobID) + if err != nil { + ctx.NotFoundOrServerError("GetRunJobByRunAndID", func(err error) bool { + return errors.Is(err, util.ErrNotExist) + }, err) + return nil, nil, nil } - ctx.NotFound(nil) - return nil, nil } - return jobs[0], jobs + + return run, jobs, current } func ArtifactsDeleteView(ctx *context_module.Context) { diff --git a/tests/integration/actions_view_test.go b/tests/integration/actions_route_test.go similarity index 95% rename from tests/integration/actions_view_test.go rename to tests/integration/actions_route_test.go index 363ffd477603d..472617d53148d 100644 --- a/tests/integration/actions_view_test.go +++ b/tests/integration/actions_route_test.go @@ -18,16 +18,16 @@ import ( "github.com/stretchr/testify/assert" ) -func TestActionsView(t *testing.T) { +func TestActionsRoute(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) user2Session := loginUser(t, user2.Name) user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - repo1 := createActionsTestRepo(t, user2Token, "actions-view-1", false) + repo1 := createActionsTestRepo(t, user2Token, "actions-route-test-1", false) runner1 := newMockRunner() runner1.registerAsRepoRunner(t, user2.Name, repo1.Name, "mock-runner", []string{"ubuntu-latest"}, false) - repo2 := createActionsTestRepo(t, user2Token, "actions-view-2", false) + repo2 := createActionsTestRepo(t, user2Token, "actions-route-test-2", false) runner2 := newMockRunner() runner2.registerAsRepoRunner(t, user2.Name, repo2.Name, "mock-runner", []string{"ubuntu-latest"}, false) diff --git a/tests/integration/api_actions_run_test.go b/tests/integration/api_actions_run_test.go index 205f3f02ffd57..582adeab37f2e 100644 --- a/tests/integration/api_actions_run_test.go +++ b/tests/integration/api_actions_run_test.go @@ -209,12 +209,12 @@ func TestAPIActionsRerunWorkflowRun(t *testing.T) { assert.Equal(t, timeutil.TimeStamp(0), run.Started) assert.Equal(t, timeutil.TimeStamp(0), run.Stopped) - job198, err := actions_model.GetRunJobByID(t.Context(), 198) + job198, err := actions_model.GetRunJobByRunAndID(t.Context(), 795, 198) require.NoError(t, err) assert.Equal(t, actions_model.StatusWaiting, job198.Status) assert.Equal(t, int64(0), job198.TaskID) - job199, err := actions_model.GetRunJobByID(t.Context(), 199) + job199, err := actions_model.GetRunJobByRunAndID(t.Context(), 795, 199) require.NoError(t, err) assert.Equal(t, actions_model.StatusWaiting, job199.Status) assert.Equal(t, int64(0), job199.TaskID) @@ -269,12 +269,12 @@ func TestAPIActionsRerunWorkflowJob(t *testing.T) { require.NoError(t, err) assert.Equal(t, actions_model.StatusWaiting, run.Status) - job198, err := actions_model.GetRunJobByID(t.Context(), 198) + job198, err := actions_model.GetRunJobByRunAndID(t.Context(), 795, 198) require.NoError(t, err) assert.Equal(t, actions_model.StatusSuccess, job198.Status) assert.Equal(t, int64(53), job198.TaskID) - job199, err := actions_model.GetRunJobByID(t.Context(), 199) + job199, err := actions_model.GetRunJobByRunAndID(t.Context(), 795, 199) require.NoError(t, err) assert.Equal(t, actions_model.StatusWaiting, job199.Status) assert.Equal(t, int64(0), job199.TaskID) From 3d3cfd690a036c6d14bd837a93d6ae7969f053c1 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Sat, 7 Mar 2026 13:18:21 -0700 Subject: [PATCH 03/12] update comments --- routers/web/repo/actions/view.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 8ee68b483e226..afb6d45f2c04c 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -592,9 +592,8 @@ func Delete(ctx *context_module.Context) { ctx.JSONOK() } -// getRunJobsByID gets the jobs of runID, and returns the selected job and all jobs. -// Any error will be written to the ctx. -// If the jobID is not found when jobIDHas is true, it returns 404. +// getRunJobsAndCurrentJob loads the run and its jobs for runID, and returns the selected job based on the optional "job" path param (or the first job by default). +// Any error will be written to the ctx, and nils are returned in that case. func getRunJobsAndCurrentJob(ctx *context_module.Context, runID int64) (*actions_model.ActionRun, []*actions_model.ActionRunJob, *actions_model.ActionRunJob) { run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID) if err != nil { From 5455c8fd2ad239d727018fbe5e44e3cc7cdf57a6 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Sat, 7 Mar 2026 13:48:28 -0700 Subject: [PATCH 04/12] fix test failure --- services/actions/rerun.go | 3 +++ tests/integration/actions_route_test.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/services/actions/rerun.go b/services/actions/rerun.go index 277da39b82acf..5177b90d61f17 100644 --- a/services/actions/rerun.go +++ b/services/actions/rerun.go @@ -148,6 +148,9 @@ func rerunWorkflowJob(ctx context.Context, job *actions_model.ActionRunJob, shou if err := job.LoadRun(ctx); err != nil { return err } + if err := job.Run.LoadAttributes(ctx); err != nil { + return err + } vars, err := actions_model.GetVariablesOfRun(ctx, job.Run) if err != nil { diff --git a/tests/integration/actions_route_test.go b/tests/integration/actions_route_test.go index 472617d53148d..eabeb14297f91 100644 --- a/tests/integration/actions_route_test.go +++ b/tests/integration/actions_route_test.go @@ -93,7 +93,7 @@ jobs: req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, repo1.Name, run2.ID, job2.ID)) user2Session.MakeRequest(t, req, http.StatusNotFound) req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, repo1.Name, run1.ID, job2.ID)) - user2Session.MakeRequest(t, req, http.StatusBadRequest) + user2Session.MakeRequest(t, req, http.StatusNotFound) req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/jobs/%d/rerun", user2.Name, repo1.Name, run2.ID, job1.ID)) user2Session.MakeRequest(t, req, http.StatusNotFound) }) From 7e119d9d8f8bf6ff549158237310becd5f1cab7c Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Sun, 8 Mar 2026 12:03:48 -0600 Subject: [PATCH 05/12] Update routers/api/v1/repo/actions_run.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Zettat123 --- routers/api/v1/repo/actions_run.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/routers/api/v1/repo/actions_run.go b/routers/api/v1/repo/actions_run.go index 2c6a28813dfa6..64ac1a3ad5f02 100644 --- a/routers/api/v1/repo/actions_run.go +++ b/routers/api/v1/repo/actions_run.go @@ -45,7 +45,11 @@ func DownloadActionsRunJobLogs(ctx *context.APIContext) { jobID := ctx.PathParamInt64("job_id") curJob, err := actions_model.GetRunJobByRepoAndID(ctx, ctx.Repo.Repository.ID, jobID) if err != nil { - ctx.APIErrorInternal(err) + if errors.Is(err, util.ErrNotExist) { + ctx.APIErrorNotFound(err) + } else { + ctx.APIErrorInternal(err) + } return } if err = curJob.LoadRepo(ctx); err != nil { From 470e563b6ef0cf94a951215aad5b81c4e29dc8bd Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Sun, 8 Mar 2026 20:57:00 -0600 Subject: [PATCH 06/12] add migration --- models/migrations/migrations.go | 1 + models/migrations/v1_26/v326.go | 213 +++++++++++++++++++++++++++ models/migrations/v1_26/v326_test.go | 131 ++++++++++++++++ 3 files changed, 345 insertions(+) create mode 100644 models/migrations/v1_26/v326.go create mode 100644 models/migrations/v1_26/v326_test.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 9975729fd62a7..ab14e8a8e74dd 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -400,6 +400,7 @@ func prepareMigrationTasks() []*migration { newMigration(323, "Add support for actions concurrency", v1_26.AddActionsConcurrency), newMigration(324, "Fix closed milestone completeness for milestones with no issues", v1_26.FixClosedMilestoneCompleteness), newMigration(325, "Fix missed repo_id when migrate attachments", v1_26.FixMissedRepoIDWhenMigrateAttachments), + newMigration(326, "Migrate commit status target URL to use run ID and job ID", v1_26.FixCommitStatusTargetURLToUseRunAndJobID), } return preparedMigrations } diff --git a/models/migrations/v1_26/v326.go b/models/migrations/v1_26/v326.go new file mode 100644 index 0000000000000..f2550cf437e3c --- /dev/null +++ b/models/migrations/v1_26/v326.go @@ -0,0 +1,213 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_26 + +import ( + "fmt" + "net/url" + "strconv" + "strings" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + + "xorm.io/xorm" +) + +const actionsRunPath = "/actions/runs/" + +type migrationRepository struct { + ID int64 + OwnerName string + Name string +} + +type migrationActionRun struct { + ID int64 + RepoID int64 + Index int64 +} + +type migrationActionRunJob struct { + ID int64 + RunID int64 +} + +type migrationCommitStatus struct { + ID int64 + RepoID int64 + TargetURL string +} + +func FixCommitStatusTargetURLToUseRunAndJobID(x *xorm.Engine) error { + runByIndexCache := make(map[int64]map[int64]*migrationActionRun) + jobsByRunIDCache := make(map[int64][]int64) + repoLinkCache := make(map[int64]string) + + if err := migrateCommitStatusTargetURL(x, "commit_status", runByIndexCache, jobsByRunIDCache, repoLinkCache); err != nil { + return err + } + return migrateCommitStatusTargetURL(x, "commit_status_summary", runByIndexCache, jobsByRunIDCache, repoLinkCache) +} + +func migrateCommitStatusTargetURL( + x *xorm.Engine, + table string, + runByIndexCache map[int64]map[int64]*migrationActionRun, + jobsByRunIDCache map[int64][]int64, + repoLinkCache map[int64]string, +) error { + const batchSize = 500 + var lastID int64 + + for { + var rows []migrationCommitStatus + sess := x.Table(table). + Where("target_url LIKE ?", "%"+actionsRunPath+"%"). + And("id > ?", lastID). + Asc("id"). + Limit(batchSize) + if err := sess.Find(&rows); err != nil { + return fmt.Errorf("query %s: %w", table, err) + } + if len(rows) == 0 { + return nil + } + + for _, row := range rows { + lastID = row.ID + if row.TargetURL == "" { + continue + } + + repoLink, err := getRepoLinkCached(x, repoLinkCache, row.RepoID) + if err != nil || repoLink == "" { + if err != nil { + log.Warn("convert %s id=%d getRepoLinkCached: %v", table, row.ID, err) + } else { + log.Warn("convert %s id=%d: repo=%d not found", table, row.ID, row.RepoID) + } + continue + } + + runNum, jobNum, ok := parseTargetURL(row.TargetURL, repoLink) + if !ok { + continue + } + + run, err := getRunByIndexCached(x, runByIndexCache, row.RepoID, runNum) + if err != nil || run == nil { + if err != nil { + log.Warn("convert %s id=%d getRunByIndexCached: %v", table, row.ID, err) + } else { + log.Warn("convert %s id=%d: run not found for repo_id=%d run_index=%d", table, row.ID, row.RepoID, runNum) + } + continue + } + + jobID, ok, err := getJobIDByIndexCached(x, jobsByRunIDCache, run.ID, jobNum) + if err != nil || !ok { + if err != nil { + log.Warn("convert %s id=%d getJobIDByIndexCached: %v", table, row.ID, err) + } else { + log.Warn("convert %s id=%d: job not found for run_id=%d job_index=%d", table, row.ID, run.ID, jobNum) + } + continue + } + + oldURL := row.TargetURL + newURL := fmt.Sprintf("%s%s%d/jobs/%d", repoLink, actionsRunPath, run.ID, jobID) // expect: {repo_link}/actions/runs/{run_id}/jobs/{job_id} + if oldURL == newURL { + continue + } + + if _, err := x.Table(table).ID(row.ID).Cols("target_url").Update(&migrationCommitStatus{TargetURL: newURL}); err != nil { + return fmt.Errorf("update %s id=%d target_url from %s to %s: %w", table, row.ID, oldURL, newURL, err) + } + } + } +} + +func getRepoLinkCached(x *xorm.Engine, cache map[int64]string, repoID int64) (string, error) { + if link, ok := cache[repoID]; ok { + return link, nil + } + repo := &migrationRepository{} + has, err := x.Table("repository").Where("id=?", repoID).Get(repo) + if err != nil { + return "", err + } + if !has { + cache[repoID] = "" + return "", nil + } + link := setting.AppSubURL + "/" + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name) + cache[repoID] = link + return link, nil +} + +func getRunByIndexCached(x *xorm.Engine, cache map[int64]map[int64]*migrationActionRun, repoID, runIndex int64) (*migrationActionRun, error) { + if repoCache, ok := cache[repoID]; ok { + if run, ok := repoCache[runIndex]; ok { + return run, nil + } + } + + var run migrationActionRun + has, err := x.Table("action_run").Where("repo_id=? AND `index`=?", repoID, runIndex).Get(&run) + if err != nil { + return nil, err + } + if !has { + if cache[repoID] == nil { + cache[repoID] = make(map[int64]*migrationActionRun) + } + cache[repoID][runIndex] = nil + return nil, fmt.Errorf("run repo_id=%d run_index=%d not found", repoID, runIndex) + } + if cache[repoID] == nil { + cache[repoID] = make(map[int64]*migrationActionRun) + } + cache[repoID][runIndex] = &run + return &run, nil +} + +func getJobIDByIndexCached(x *xorm.Engine, cache map[int64][]int64, runID, jobIndex int64) (int64, bool, error) { + jobIDs, ok := cache[runID] + if !ok { + var jobs []migrationActionRunJob + if err := x.Table("action_run_job").Where("run_id=?", runID).Asc("id").Cols("id").Find(&jobs); err != nil { + return 0, false, err + } + jobIDs = make([]int64, 0, len(jobs)) + for _, job := range jobs { + jobIDs = append(jobIDs, job.ID) + } + cache[runID] = jobIDs + } + if jobIndex < 0 || jobIndex >= int64(len(jobIDs)) { + return 0, false, nil + } + return jobIDs[jobIndex], true, nil +} + +func parseTargetURL(targetURL, repoLink string) (runNum, jobNum int64, ok bool) { + prefix := repoLink + actionsRunPath + if !strings.HasPrefix(targetURL, prefix) { + return 0, 0, false + } + rest := targetURL[len(prefix):] + + parts := strings.Split(rest, "/") // expect: {run_num}/jobs/{job_num} + if len(parts) == 3 && parts[1] == "jobs" { + runNum, err1 := strconv.ParseInt(parts[0], 10, 64) + jobNum, err2 := strconv.ParseInt(parts[2], 10, 64) + if err1 != nil || err2 != nil { + return 0, 0, false + } + return runNum, jobNum, true + } + + return 0, 0, false +} diff --git a/models/migrations/v1_26/v326_test.go b/models/migrations/v1_26/v326_test.go new file mode 100644 index 0000000000000..8ca5a3b9cbaed --- /dev/null +++ b/models/migrations/v1_26/v326_test.go @@ -0,0 +1,131 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_26 + +import ( + "testing" + + "code.gitea.io/gitea/models/migrations/base" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + + "github.com/stretchr/testify/require" + "xorm.io/xorm" +) + +func Test_FixCommitStatusTargetURLToUseRunAndJobID(t *testing.T) { + defer test.MockVariableValue(&setting.AppSubURL, "")() + + type Repository struct { + ID int64 `xorm:"pk autoincr"` + OwnerName string + Name string + } + + type ActionRun struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"index"` + Index int64 + } + + type ActionRunJob struct { + ID int64 `xorm:"pk autoincr"` + RunID int64 `xorm:"index"` + } + + type CommitStatus struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"index"` + TargetURL string + } + + type CommitStatusSummary struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"index"` + SHA string `xorm:"VARCHAR(64) NOT NULL"` + State string `xorm:"VARCHAR(7) NOT NULL"` + TargetURL string + } + + x, deferable := base.PrepareTestEnv(t, 0, + new(Repository), + new(ActionRun), + new(ActionRunJob), + new(CommitStatus), + new(CommitStatusSummary), + ) + defer deferable() + + repo := &Repository{ID: 1, OwnerName: "testuser", Name: "repo1"} + _, err := x.Insert(repo) + require.NoError(t, err) + + run := &ActionRun{ID: 106, RepoID: repo.ID, Index: 7} + _, err = x.Insert(run) + require.NoError(t, err) + + job0 := &ActionRunJob{ID: 530, RunID: run.ID} + job1 := &ActionRunJob{ID: 531, RunID: run.ID} + _, err = x.Insert(job0, job1) + require.NoError(t, err) + + oldURL1 := "/testuser/repo1/actions/runs/7/jobs/0" + newURL1 := "/testuser/repo1/actions/runs/106/jobs/530" + oldURL2 := "/testuser/repo1/actions/runs/7/jobs/1" + newURL2 := "/testuser/repo1/actions/runs/106/jobs/531" + + invalidWrongRepo := "/otheruser/badrepo/actions/runs/7/jobs/0" + invalidNonexistentRun := "/testuser/repo1/actions/runs/10/jobs/0" + invalidNonexistentJob := "/testuser/repo1/actions/runs/7/jobs/3" + externalTargetURL := "https://ci.example.com/build/123" + + _, err = x.Insert( + &CommitStatus{ID: 10, RepoID: repo.ID, TargetURL: oldURL1}, + &CommitStatus{ID: 11, RepoID: repo.ID, TargetURL: oldURL2}, + &CommitStatus{ID: 12, RepoID: repo.ID, TargetURL: invalidWrongRepo}, + &CommitStatus{ID: 13, RepoID: repo.ID, TargetURL: invalidNonexistentRun}, + &CommitStatus{ID: 14, RepoID: repo.ID, TargetURL: invalidNonexistentJob}, + &CommitStatus{ID: 15, RepoID: repo.ID, TargetURL: externalTargetURL}, + ) + require.NoError(t, err) + + _, err = x.Insert( + &CommitStatusSummary{ID: 20, RepoID: repo.ID, SHA: "012345", State: "success", TargetURL: oldURL1}, + &CommitStatusSummary{ID: 21, RepoID: repo.ID, SHA: "678901", State: "success", TargetURL: externalTargetURL}, + ) + require.NoError(t, err) + + require.NoError(t, FixCommitStatusTargetURLToUseRunAndJobID(x)) + + cases := []struct { + table string + id int64 + want string + }{ + {table: "commit_status", id: 10, want: newURL1}, + {table: "commit_status", id: 11, want: newURL2}, + {table: "commit_status", id: 12, want: invalidWrongRepo}, + {table: "commit_status", id: 13, want: invalidNonexistentRun}, + {table: "commit_status", id: 14, want: invalidNonexistentJob}, + {table: "commit_status", id: 15, want: externalTargetURL}, + {table: "commit_status_summary", id: 20, want: newURL1}, + {table: "commit_status_summary", id: 21, want: externalTargetURL}, + } + + for _, tc := range cases { + assertTargetURL(t, x, tc.table, tc.id, tc.want) + } +} + +func assertTargetURL(t *testing.T, x *xorm.Engine, table string, id int64, want string) { + t.Helper() + + var row struct { + TargetURL string + } + has, err := x.Table(table).Where("id=?", id).Cols("target_url").Get(&row) + require.NoError(t, err) + require.True(t, has) + require.Equal(t, want, row.TargetURL) +} From 3ecabb0158349540573848171cbb979a99292075 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Sun, 8 Mar 2026 21:18:22 -0600 Subject: [PATCH 07/12] fix test --- .../action_run.yml | 10 ++++++ .../action_run_job.yml | 10 ++++++ .../commit_status.yml | 30 ++++++++++++++++ .../commit_status_summary.yml | 19 ++++++++++ .../repository.yml | 9 +++++ models/migrations/v1_26/v326_test.go | 36 +++---------------- 6 files changed, 82 insertions(+), 32 deletions(-) create mode 100644 models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/action_run.yml create mode 100644 models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/action_run_job.yml create mode 100644 models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/commit_status.yml create mode 100644 models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/commit_status_summary.yml create mode 100644 models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/repository.yml diff --git a/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/action_run.yml b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/action_run.yml new file mode 100644 index 0000000000000..9ea35e66a608f --- /dev/null +++ b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/action_run.yml @@ -0,0 +1,10 @@ +# type ActionRun struct { +# ID int64 `xorm:"pk autoincr"` +# RepoID int64 `xorm:"index"` +# Index int64 +# } +- + id: 106 + repo_id: 1 + index: 7 + diff --git a/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/action_run_job.yml b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/action_run_job.yml new file mode 100644 index 0000000000000..4f90a4495cabc --- /dev/null +++ b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/action_run_job.yml @@ -0,0 +1,10 @@ +# type ActionRunJob struct { +# ID int64 `xorm:"pk autoincr"` +# RunID int64 `xorm:"index"` +# } +- + id: 530 + run_id: 106 +- + id: 531 + run_id: 106 diff --git a/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/commit_status.yml b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/commit_status.yml new file mode 100644 index 0000000000000..543a5ed22ed5a --- /dev/null +++ b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/commit_status.yml @@ -0,0 +1,30 @@ +# type CommitStatus struct { +# ID int64 `xorm:"pk autoincr"` +# RepoID int64 `xorm:"index"` +# Context string +# TargetURL string +# } +- + id: 10 + repo_id: 1 + target_url: /testuser/repo1/actions/runs/7/jobs/0 +- + id: 11 + repo_id: 1 + target_url: /testuser/repo1/actions/runs/7/jobs/1 +- + id: 12 + repo_id: 1 + target_url: /otheruser/badrepo/actions/runs/7/jobs/0 +- + id: 13 + repo_id: 1 + target_url: /testuser/repo1/actions/runs/10/jobs/0 +- + id: 14 + repo_id: 1 + target_url: /testuser/repo1/actions/runs/7/jobs/3 +- + id: 15 + repo_id: 1 + target_url: https://ci.example.com/build/123 diff --git a/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/commit_status_summary.yml b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/commit_status_summary.yml new file mode 100644 index 0000000000000..580b2a4f04793 --- /dev/null +++ b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/commit_status_summary.yml @@ -0,0 +1,19 @@ +# type CommitStatusSummary struct { +# ID int64 `xorm:"pk autoincr"` +# RepoID int64 `xorm:"index"` +# SHA string `xorm:"VARCHAR(64) NOT NULL"` +# State string `xorm:"VARCHAR(7) NOT NULL"` +# TargetURL string +# } +- + id: 20 + repo_id: 1 + sha: "012345" + state: success + target_url: /testuser/repo1/actions/runs/7/jobs/0 +- + id: 21 + repo_id: 1 + sha: "678901" + state: success + target_url: https://ci.example.com/build/123 diff --git a/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/repository.yml b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/repository.yml new file mode 100644 index 0000000000000..86cfb926e4413 --- /dev/null +++ b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/repository.yml @@ -0,0 +1,9 @@ +# type Repository struct { +# ID int64 `xorm:"pk autoincr"` +# OwnerName string +# Name string +# } +- + id: 1 + owner_name: testuser + name: repo1 diff --git a/models/migrations/v1_26/v326_test.go b/models/migrations/v1_26/v326_test.go index 8ca5a3b9cbaed..b5bc3b661f09c 100644 --- a/models/migrations/v1_26/v326_test.go +++ b/models/migrations/v1_26/v326_test.go @@ -6,7 +6,10 @@ package v1_26 import ( "testing" + _ "code.gitea.io/gitea/models/actions" + _ "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/migrations/base" + _ "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" @@ -57,22 +60,7 @@ func Test_FixCommitStatusTargetURLToUseRunAndJobID(t *testing.T) { ) defer deferable() - repo := &Repository{ID: 1, OwnerName: "testuser", Name: "repo1"} - _, err := x.Insert(repo) - require.NoError(t, err) - - run := &ActionRun{ID: 106, RepoID: repo.ID, Index: 7} - _, err = x.Insert(run) - require.NoError(t, err) - - job0 := &ActionRunJob{ID: 530, RunID: run.ID} - job1 := &ActionRunJob{ID: 531, RunID: run.ID} - _, err = x.Insert(job0, job1) - require.NoError(t, err) - - oldURL1 := "/testuser/repo1/actions/runs/7/jobs/0" newURL1 := "/testuser/repo1/actions/runs/106/jobs/530" - oldURL2 := "/testuser/repo1/actions/runs/7/jobs/1" newURL2 := "/testuser/repo1/actions/runs/106/jobs/531" invalidWrongRepo := "/otheruser/badrepo/actions/runs/7/jobs/0" @@ -80,22 +68,6 @@ func Test_FixCommitStatusTargetURLToUseRunAndJobID(t *testing.T) { invalidNonexistentJob := "/testuser/repo1/actions/runs/7/jobs/3" externalTargetURL := "https://ci.example.com/build/123" - _, err = x.Insert( - &CommitStatus{ID: 10, RepoID: repo.ID, TargetURL: oldURL1}, - &CommitStatus{ID: 11, RepoID: repo.ID, TargetURL: oldURL2}, - &CommitStatus{ID: 12, RepoID: repo.ID, TargetURL: invalidWrongRepo}, - &CommitStatus{ID: 13, RepoID: repo.ID, TargetURL: invalidNonexistentRun}, - &CommitStatus{ID: 14, RepoID: repo.ID, TargetURL: invalidNonexistentJob}, - &CommitStatus{ID: 15, RepoID: repo.ID, TargetURL: externalTargetURL}, - ) - require.NoError(t, err) - - _, err = x.Insert( - &CommitStatusSummary{ID: 20, RepoID: repo.ID, SHA: "012345", State: "success", TargetURL: oldURL1}, - &CommitStatusSummary{ID: 21, RepoID: repo.ID, SHA: "678901", State: "success", TargetURL: externalTargetURL}, - ) - require.NoError(t, err) - require.NoError(t, FixCommitStatusTargetURLToUseRunAndJobID(x)) cases := []struct { @@ -126,6 +98,6 @@ func assertTargetURL(t *testing.T, x *xorm.Engine, table string, id int64, want } has, err := x.Table(table).Where("id=?", id).Cols("target_url").Get(&row) require.NoError(t, err) - require.True(t, has) + require.Truef(t, has, "row not found: table=%s id=%d", table, id) require.Equal(t, want, row.TargetURL) } From a3d58765e70a2e8266d493e77d832d7c25845f06 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Sun, 8 Mar 2026 21:21:39 -0600 Subject: [PATCH 08/12] fix lint --- .../Test_FixCommitStatusTargetURLToUseRunAndJobID/action_run.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/action_run.yml b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/action_run.yml index 9ea35e66a608f..342adb2a049d2 100644 --- a/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/action_run.yml +++ b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/action_run.yml @@ -7,4 +7,3 @@ id: 106 repo_id: 1 index: 7 - From 98b4b084a454b3f63761c37145c296906995999e Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Sun, 8 Mar 2026 21:26:25 -0600 Subject: [PATCH 09/12] fix checks --- models/migrations/v1_26/v326_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/models/migrations/v1_26/v326_test.go b/models/migrations/v1_26/v326_test.go index b5bc3b661f09c..c2b8770754210 100644 --- a/models/migrations/v1_26/v326_test.go +++ b/models/migrations/v1_26/v326_test.go @@ -6,10 +6,7 @@ package v1_26 import ( "testing" - _ "code.gitea.io/gitea/models/actions" - _ "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/migrations/base" - _ "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" From 7bc3a929c88c73c0cccfa89201e77fb52e8a4087 Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Sun, 8 Mar 2026 21:58:28 -0600 Subject: [PATCH 10/12] fix test --- models/migrations/v1_26/v326_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/models/migrations/v1_26/v326_test.go b/models/migrations/v1_26/v326_test.go index c2b8770754210..ddc2640160681 100644 --- a/models/migrations/v1_26/v326_test.go +++ b/models/migrations/v1_26/v326_test.go @@ -10,6 +10,10 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" + _ "code.gitea.io/gitea/models/actions" + _ "code.gitea.io/gitea/models/git" + _ "code.gitea.io/gitea/models/repo" + "github.com/stretchr/testify/require" "xorm.io/xorm" ) From 4343b575afb7ebc59bec4918f35d74696bdffd5c Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Sun, 8 Mar 2026 22:27:55 -0600 Subject: [PATCH 11/12] fix test fixture --- .../commit_status.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/commit_status.yml b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/commit_status.yml index 543a5ed22ed5a..ceff4c9993130 100644 --- a/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/commit_status.yml +++ b/models/migrations/fixtures/Test_FixCommitStatusTargetURLToUseRunAndJobID/commit_status.yml @@ -1,7 +1,6 @@ # type CommitStatus struct { # ID int64 `xorm:"pk autoincr"` # RepoID int64 `xorm:"index"` -# Context string # TargetURL string # } - From b29c2471134d36100a0cea36ebc25ef1c6b5ff1c Mon Sep 17 00:00:00 2001 From: Zettat123 Date: Tue, 10 Mar 2026 12:56:15 -0600 Subject: [PATCH 12/12] fix review comments --- models/migrations/v1_26/v326.go | 3 +++ routers/web/repo/actions/view.go | 9 ++++++++- tests/integration/actions_route_test.go | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/models/migrations/v1_26/v326.go b/models/migrations/v1_26/v326.go index f2550cf437e3c..1ec0af76a01b8 100644 --- a/models/migrations/v1_26/v326.go +++ b/models/migrations/v1_26/v326.go @@ -150,6 +150,9 @@ func getRepoLinkCached(x *xorm.Engine, cache map[int64]string, repoID int64) (st func getRunByIndexCached(x *xorm.Engine, cache map[int64]map[int64]*migrationActionRun, repoID, runIndex int64) (*migrationActionRun, error) { if repoCache, ok := cache[repoID]; ok { if run, ok := repoCache[runIndex]; ok { + if run == nil { + return nil, fmt.Errorf("run repo_id=%d run_index=%d not found", repoID, runIndex) + } return run, nil } } diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index afb6d45f2c04c..2685dd8857b14 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -546,7 +546,9 @@ func approveRuns(ctx *context_module.Context, runIDs []int64) { return nil }) if err != nil { - ctx.ServerError("UpdateRunJob", err) + ctx.NotFoundOrServerError("approveRuns", func(err error) bool { + return errors.Is(err, util.ErrNotExist) + }, err) return } @@ -613,6 +615,10 @@ func getRunJobsAndCurrentJob(ctx *context_module.Context, runID int64) (*actions return nil, nil, nil } + for _, job := range jobs { + job.Run = run + } + current := jobs[0] if ctx.PathParam("job") != "" { jobID := ctx.PathParamInt64("job") @@ -623,6 +629,7 @@ func getRunJobsAndCurrentJob(ctx *context_module.Context, runID int64) (*actions }, err) return nil, nil, nil } + current.Run = run } return run, jobs, current diff --git a/tests/integration/actions_route_test.go b/tests/integration/actions_route_test.go index eabeb14297f91..91d56507ede9f 100644 --- a/tests/integration/actions_route_test.go +++ b/tests/integration/actions_route_test.go @@ -71,7 +71,7 @@ jobs: req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/actions/runs/%d/workflow", user2.Name, repo1.Name, run2.ID)) user2Session.MakeRequest(t, req, http.StatusNotFound) req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/approve", user2.Name, repo1.Name, run2.ID)) - user2Session.MakeRequest(t, req, http.StatusInternalServerError) + user2Session.MakeRequest(t, req, http.StatusNotFound) req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/cancel", user2.Name, repo1.Name, run2.ID)) user2Session.MakeRequest(t, req, http.StatusNotFound) req = NewRequest(t, "POST", fmt.Sprintf("/%s/%s/actions/runs/%d/delete", user2.Name, repo1.Name, run2.ID))