Skip to content

Commit cc9d9dd

Browse files
committed
Merge remote-tracking branch 'giteaofficial/main'
* giteaofficial/main: [skip ci] Updated translations via Crowdin Download actions job logs from API (go-gitea#33858) Fail mirroring more gracefully (go-gitea#34002) Fix dropdown module accessing (go-gitea#34026) Polyfill WeakRef (go-gitea#34025) Fix dropdown delegating and some UI problems (go-gitea#34014)
2 parents 6d8eaa4 + 3c95b07 commit cc9d9dd

File tree

19 files changed

+415
-111
lines changed

19 files changed

+415
-111
lines changed

models/actions/run_job.go

+19-6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"time"
1111

1212
"code.gitea.io/gitea/models/db"
13+
repo_model "code.gitea.io/gitea/models/repo"
1314
"code.gitea.io/gitea/modules/timeutil"
1415
"code.gitea.io/gitea/modules/util"
1516

@@ -19,11 +20,12 @@ import (
1920
// ActionRunJob represents a job of a run
2021
type ActionRunJob struct {
2122
ID int64
22-
RunID int64 `xorm:"index"`
23-
Run *ActionRun `xorm:"-"`
24-
RepoID int64 `xorm:"index"`
25-
OwnerID int64 `xorm:"index"`
26-
CommitSHA string `xorm:"index"`
23+
RunID int64 `xorm:"index"`
24+
Run *ActionRun `xorm:"-"`
25+
RepoID int64 `xorm:"index"`
26+
Repo *repo_model.Repository `xorm:"-"`
27+
OwnerID int64 `xorm:"index"`
28+
CommitSHA string `xorm:"index"`
2729
IsForkPullRequest bool
2830
Name string `xorm:"VARCHAR(255)"`
2931
Attempt int64
@@ -58,6 +60,17 @@ func (job *ActionRunJob) LoadRun(ctx context.Context) error {
5860
return nil
5961
}
6062

63+
func (job *ActionRunJob) LoadRepo(ctx context.Context) error {
64+
if job.Repo == nil {
65+
repo, err := repo_model.GetRepositoryByID(ctx, job.RepoID)
66+
if err != nil {
67+
return err
68+
}
69+
job.Repo = repo
70+
}
71+
return nil
72+
}
73+
6174
// LoadAttributes load Run if not loaded
6275
func (job *ActionRunJob) LoadAttributes(ctx context.Context) error {
6376
if job == nil {
@@ -83,7 +96,7 @@ func GetRunJobByID(ctx context.Context, id int64) (*ActionRunJob, error) {
8396
return &job, nil
8497
}
8598

86-
func GetRunJobsByRunID(ctx context.Context, runID int64) ([]*ActionRunJob, error) {
99+
func GetRunJobsByRunID(ctx context.Context, runID int64) (ActionJobList, error) {
87100
var jobs []*ActionRunJob
88101
if err := db.GetEngine(ctx).Where("run_id=?", runID).OrderBy("id").Find(&jobs); err != nil {
89102
return nil, err

models/actions/run_job_list.go

+28-7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88

99
"code.gitea.io/gitea/models/db"
10+
repo_model "code.gitea.io/gitea/models/repo"
1011
"code.gitea.io/gitea/modules/container"
1112
"code.gitea.io/gitea/modules/timeutil"
1213

@@ -21,7 +22,33 @@ func (jobs ActionJobList) GetRunIDs() []int64 {
2122
})
2223
}
2324

25+
func (jobs ActionJobList) LoadRepos(ctx context.Context) error {
26+
repoIDs := container.FilterSlice(jobs, func(j *ActionRunJob) (int64, bool) {
27+
return j.RepoID, j.RepoID != 0 && j.Repo == nil
28+
})
29+
if len(repoIDs) == 0 {
30+
return nil
31+
}
32+
33+
repos := make(map[int64]*repo_model.Repository, len(repoIDs))
34+
if err := db.GetEngine(ctx).In("id", repoIDs).Find(&repos); err != nil {
35+
return err
36+
}
37+
for _, j := range jobs {
38+
if j.RepoID > 0 && j.Repo == nil {
39+
j.Repo = repos[j.RepoID]
40+
}
41+
}
42+
return nil
43+
}
44+
2445
func (jobs ActionJobList) LoadRuns(ctx context.Context, withRepo bool) error {
46+
if withRepo {
47+
if err := jobs.LoadRepos(ctx); err != nil {
48+
return err
49+
}
50+
}
51+
2552
runIDs := jobs.GetRunIDs()
2653
runs := make(map[int64]*ActionRun, len(runIDs))
2754
if err := db.GetEngine(ctx).In("id", runIDs).Find(&runs); err != nil {
@@ -30,15 +57,9 @@ func (jobs ActionJobList) LoadRuns(ctx context.Context, withRepo bool) error {
3057
for _, j := range jobs {
3158
if j.RunID > 0 && j.Run == nil {
3259
j.Run = runs[j.RunID]
60+
j.Run.Repo = j.Repo
3361
}
3462
}
35-
if withRepo {
36-
var runsList RunList = make([]*ActionRun, 0, len(runs))
37-
for _, r := range runs {
38-
runsList = append(runsList, r)
39-
}
40-
return runsList.LoadRepos(ctx)
41-
}
4263
return nil
4364
}
4465

modules/templates/util_avatar.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ func AvatarHTML(src string, size int, class, name string) template.HTML {
3434
name = "avatar"
3535
}
3636

37-
return template.HTML(`<img loading="lazy" class="` + class + `" src="` + src + `" title="` + html.EscapeString(name) + `" width="` + sizeStr + `" height="` + sizeStr + `"/>`)
37+
// use empty alt, otherwise if the image fails to load, the width will follow the "alt" text's width
38+
return template.HTML(`<img loading="lazy" alt="" class="` + class + `" src="` + src + `" title="` + html.EscapeString(name) + `" width="` + sizeStr + `" height="` + sizeStr + `"/>`)
3839
}
3940

4041
// Avatar renders user avatars. args: user, size (int), class (string)

options/locale/locale_ja-JP.ini

+3
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ copy_type_unsupported=このファイルタイプはコピーできません
113113
write=書き込み
114114
preview=プレビュー
115115
loading=読み込み中…
116+
files=ファイル
116117

117118
error=エラー
118119
error404=アクセスしようとしたページは<strong>存在しない</strong>か、閲覧が<strong>許可されていません</strong>。
@@ -456,6 +457,7 @@ oauth_signup_submit=アカウント登録完了
456457
oauth_signin_tab=既存アカウントにリンク
457458
oauth_signin_title=リンク先アカウント認可のためサインイン
458459
oauth_signin_submit=アカウントにリンク
460+
oauth.signin.error.general=認可リクエストの処理中にエラーが発生しました: %s 。このエラーが解決しない場合は、サイト管理者に問い合わせてください。
459461
oauth.signin.error.access_denied=認可リクエストが拒否されました。
460462
oauth.signin.error.temporarily_unavailable=認証サーバーが一時的に利用できないため、認可に失敗しました。後でもう一度やり直してください。
461463
oauth_callback_unable_auto_reg=自動登録が有効になっていますが、OAuth2プロバイダー %[1]s の応答はフィールド %[2]s が不足しており、自動でアカウントを作成することができません。 アカウントを作成またはリンクするか、サイト管理者に問い合わせてください。
@@ -1406,6 +1408,7 @@ commits.signed_by_untrusted_user_unmatched=コミッターと一致しない信
14061408
commits.gpg_key_id=GPGキーID
14071409
commits.ssh_key_fingerprint=SSH鍵のフィンガープリント
14081410
commits.view_path=この時点を表示
1411+
commits.view_file_diff=このファイルの、このコミットでの変更内容を表示
14091412

14101413
commit.operations=操作
14111414
commit.revert=リバート

routers/api/v1/api.go

+4
Original file line numberDiff line numberDiff line change
@@ -1168,6 +1168,10 @@ func Routes() *web.Router {
11681168
m.Post("/{workflow_id}/dispatches", reqRepoWriter(unit.TypeActions), bind(api.CreateActionWorkflowDispatch{}), repo.ActionsDispatchWorkflow)
11691169
}, context.ReferencesGitRepo(), reqToken(), reqRepoReader(unit.TypeActions))
11701170

1171+
m.Group("/actions/jobs", func() {
1172+
m.Get("/{job_id}/logs", repo.DownloadActionsRunJobLogs)
1173+
}, reqToken(), reqRepoReader(unit.TypeActions))
1174+
11711175
m.Group("/hooks/git", func() {
11721176
m.Combo("").Get(repo.ListGitHooks)
11731177
m.Group("/{id}", func() {

routers/api/v1/repo/actions_run.go

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package repo
5+
6+
import (
7+
"errors"
8+
9+
actions_model "code.gitea.io/gitea/models/actions"
10+
"code.gitea.io/gitea/modules/util"
11+
"code.gitea.io/gitea/routers/common"
12+
"code.gitea.io/gitea/services/context"
13+
)
14+
15+
func DownloadActionsRunJobLogs(ctx *context.APIContext) {
16+
// swagger:operation GET /repos/{owner}/{repo}/actions/jobs/{job_id}/logs repository downloadActionsRunJobLogs
17+
// ---
18+
// summary: Downloads the job logs for a workflow run
19+
// produces:
20+
// - application/json
21+
// parameters:
22+
// - name: owner
23+
// in: path
24+
// description: name of the owner
25+
// type: string
26+
// required: true
27+
// - name: repo
28+
// in: path
29+
// description: name of the repository
30+
// type: string
31+
// required: true
32+
// - name: job_id
33+
// in: path
34+
// description: id of the job
35+
// type: integer
36+
// required: true
37+
// responses:
38+
// "200":
39+
// description: output blob content
40+
// "400":
41+
// "$ref": "#/responses/error"
42+
// "404":
43+
// "$ref": "#/responses/notFound"
44+
45+
jobID := ctx.PathParamInt64("job_id")
46+
curJob, err := actions_model.GetRunJobByID(ctx, jobID)
47+
if err != nil {
48+
ctx.APIErrorInternal(err)
49+
return
50+
}
51+
if err = curJob.LoadRepo(ctx); err != nil {
52+
ctx.APIErrorInternal(err)
53+
return
54+
}
55+
56+
err = common.DownloadActionsRunJobLogs(ctx.Base, ctx.Repo.Repository, curJob)
57+
if err != nil {
58+
if errors.Is(err, util.ErrNotExist) {
59+
ctx.APIErrorNotFound(err)
60+
} else {
61+
ctx.APIErrorInternal(err)
62+
}
63+
}
64+
}

routers/common/actions.go

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package common
5+
6+
import (
7+
"fmt"
8+
"strings"
9+
10+
actions_model "code.gitea.io/gitea/models/actions"
11+
repo_model "code.gitea.io/gitea/models/repo"
12+
"code.gitea.io/gitea/modules/actions"
13+
"code.gitea.io/gitea/modules/util"
14+
"code.gitea.io/gitea/services/context"
15+
)
16+
17+
func DownloadActionsRunJobLogsWithIndex(ctx *context.Base, ctxRepo *repo_model.Repository, runID, jobIndex int64) error {
18+
runJobs, err := actions_model.GetRunJobsByRunID(ctx, runID)
19+
if err != nil {
20+
return fmt.Errorf("GetRunJobsByRunID: %w", err)
21+
}
22+
if err = runJobs.LoadRepos(ctx); err != nil {
23+
return fmt.Errorf("LoadRepos: %w", err)
24+
}
25+
if 0 < jobIndex || jobIndex >= int64(len(runJobs)) {
26+
return util.NewNotExistErrorf("job index is out of range: %d", jobIndex)
27+
}
28+
return DownloadActionsRunJobLogs(ctx, ctxRepo, runJobs[jobIndex])
29+
}
30+
31+
func DownloadActionsRunJobLogs(ctx *context.Base, ctxRepo *repo_model.Repository, curJob *actions_model.ActionRunJob) error {
32+
if curJob.Repo.ID != ctxRepo.ID {
33+
return util.NewNotExistErrorf("job not found")
34+
}
35+
36+
if curJob.TaskID == 0 {
37+
return util.NewNotExistErrorf("job not started")
38+
}
39+
40+
if err := curJob.LoadRun(ctx); err != nil {
41+
return fmt.Errorf("LoadRun: %w", err)
42+
}
43+
44+
task, err := actions_model.GetTaskByID(ctx, curJob.TaskID)
45+
if err != nil {
46+
return fmt.Errorf("GetTaskByID: %w", err)
47+
}
48+
49+
if task.LogExpired {
50+
return util.NewNotExistErrorf("logs have been cleaned up")
51+
}
52+
53+
reader, err := actions.OpenLogs(ctx, task.LogInStorage, task.LogFilename)
54+
if err != nil {
55+
return fmt.Errorf("OpenLogs: %w", err)
56+
}
57+
defer reader.Close()
58+
59+
workflowName := curJob.Run.WorkflowID
60+
if p := strings.Index(workflowName, "."); p > 0 {
61+
workflowName = workflowName[0:p]
62+
}
63+
ctx.ServeContent(reader, &context.ServeHeaderOptions{
64+
Filename: fmt.Sprintf("%v-%v-%v.log", workflowName, curJob.Name, task.ID),
65+
ContentLength: &task.LogSize,
66+
ContentType: "text/plain",
67+
ContentTypeCharset: "utf-8",
68+
Disposition: "attachment",
69+
})
70+
return nil
71+
}

routers/web/repo/actions/view.go

+9-39
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"net/http"
1515
"net/url"
1616
"strconv"
17-
"strings"
1817
"time"
1918

2019
actions_model "code.gitea.io/gitea/models/actions"
@@ -31,6 +30,7 @@ import (
3130
"code.gitea.io/gitea/modules/timeutil"
3231
"code.gitea.io/gitea/modules/util"
3332
"code.gitea.io/gitea/modules/web"
33+
"code.gitea.io/gitea/routers/common"
3434
actions_service "code.gitea.io/gitea/services/actions"
3535
context_module "code.gitea.io/gitea/services/context"
3636
notify_service "code.gitea.io/gitea/services/notify"
@@ -469,49 +469,19 @@ func Logs(ctx *context_module.Context) {
469469
runIndex := getRunIndex(ctx)
470470
jobIndex := ctx.PathParamInt64("job")
471471

472-
job, _ := getRunJobs(ctx, runIndex, jobIndex)
473-
if ctx.Written() {
474-
return
475-
}
476-
if job.TaskID == 0 {
477-
ctx.HTTPError(http.StatusNotFound, "job is not started")
478-
return
479-
}
480-
481-
err := job.LoadRun(ctx)
482-
if err != nil {
483-
ctx.HTTPError(http.StatusInternalServerError, err.Error())
484-
return
485-
}
486-
487-
task, err := actions_model.GetTaskByID(ctx, job.TaskID)
488-
if err != nil {
489-
ctx.HTTPError(http.StatusInternalServerError, err.Error())
490-
return
491-
}
492-
if task.LogExpired {
493-
ctx.HTTPError(http.StatusNotFound, "logs have been cleaned up")
494-
return
495-
}
496-
497-
reader, err := actions.OpenLogs(ctx, task.LogInStorage, task.LogFilename)
472+
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
498473
if err != nil {
499-
ctx.HTTPError(http.StatusInternalServerError, err.Error())
474+
ctx.NotFoundOrServerError("GetRunByIndex", func(err error) bool {
475+
return errors.Is(err, util.ErrNotExist)
476+
}, err)
500477
return
501478
}
502-
defer reader.Close()
503479

504-
workflowName := job.Run.WorkflowID
505-
if p := strings.Index(workflowName, "."); p > 0 {
506-
workflowName = workflowName[0:p]
480+
if err = common.DownloadActionsRunJobLogsWithIndex(ctx.Base, ctx.Repo.Repository, run.ID, jobIndex); err != nil {
481+
ctx.NotFoundOrServerError("DownloadActionsRunJobLogsWithIndex", func(err error) bool {
482+
return errors.Is(err, util.ErrNotExist)
483+
}, err)
507484
}
508-
ctx.ServeContent(reader, &context_module.ServeHeaderOptions{
509-
Filename: fmt.Sprintf("%v-%v-%v.log", workflowName, job.Name, task.ID),
510-
ContentLength: &task.LogSize,
511-
ContentType: "text/plain",
512-
ContentTypeCharset: "utf-8",
513-
Disposition: "attachment",
514-
})
515485
}
516486

517487
func Cancel(ctx *context_module.Context) {

0 commit comments

Comments
 (0)