Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
52ad5cb
Add localized "Run" prefix for unnamed action steps
silverwind Feb 14, 2026
991e3e5
Rename locale key to actions.runs.run
silverwind Feb 14, 2026
954fe95
Avoid mutating task steps, extract hasName slice instead
silverwind Feb 14, 2026
604deef
Revert "Avoid mutating task steps, extract hasName slice instead"
silverwind Feb 14, 2026
c0bfd08
x
silverwind Feb 14, 2026
6544e42
Add "Run " prefix for unnamed action steps at creation time
silverwind Feb 14, 2026
82d7f8a
fine tune
wxiaoguang Feb 14, 2026
93f8b79
fix test
wxiaoguang Feb 14, 2026
67a6235
Extract stepDisplayName and add proper unit test
silverwind Feb 14, 2026
0f9db3f
Check Uses and Run fields explicitly in makeTaskStepDisplayName
silverwind Feb 14, 2026
9f452bf
add test
wxiaoguang Feb 14, 2026
53abe77
revert test
wxiaoguang Feb 14, 2026
de27d0a
revert test
wxiaoguang Feb 14, 2026
d55d3af
Revert "Check Uses and Run fields explicitly in makeTaskStepDisplayName"
silverwind Feb 14, 2026
8bd8c00
Match GitHub's step display name derivation
silverwind Feb 15, 2026
3eb5e14
Add reference link to GitHub Actions runner source
silverwind Feb 15, 2026
7f2d3d8
Restore comment on explicit step name
silverwind Feb 15, 2026
5ac5df7
Restore else clause structure, add "Run " prefix to all unnamed steps
silverwind Feb 15, 2026
d104802
Use production limit of 255 in step display name tests
silverwind Feb 15, 2026
41bb013
clean up comments
silverwind Feb 15, 2026
2905714
Simplify step display name logic
silverwind Feb 16, 2026
489a802
Restore comment, handle CRLF line endings in step display name
silverwind Feb 16, 2026
a5080ce
Merge branch 'main' into runprefix
silverwind Feb 17, 2026
89b243c
Use TrimSpace instead of TrimRight for run step display name
silverwind Feb 17, 2026
107f829
Update models/actions/task_test.go
wxiaoguang Feb 17, 2026
2fabf1c
merge test cases
wxiaoguang Feb 17, 2026
12d28ee
Merge branch 'main' into runprefix
GiteaBot Feb 17, 2026
402c861
Merge branch 'main' into runprefix
GiteaBot Feb 17, 2026
5da1860
Merge branch 'main' into runprefix
GiteaBot Feb 17, 2026
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
19 changes: 17 additions & 2 deletions models/actions/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"crypto/subtle"
"errors"
"fmt"
"strings"
"time"

auth_model "code.gitea.io/gitea/models/auth"
Expand All @@ -20,6 +21,7 @@ import (

runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
lru "github.com/hashicorp/golang-lru/v2"
"github.com/nektos/act/pkg/jobparser"
"google.golang.org/protobuf/types/known/timestamppb"
"xorm.io/builder"
)
Expand Down Expand Up @@ -214,6 +216,20 @@ func GetRunningTaskByToken(ctx context.Context, token string) (*ActionTask, erro
return nil, errNotExist
}

func makeTaskStepDisplayName(step *jobparser.Step, limit int) (name string) {
if step.Name != "" {
name = step.Name // the step has an explicit name
} else {
// for unnamed step, its "String()" method tries to get a display name by its "name", "uses",
// "run" or "id" (last fallback), we add the "Run " prefix for unnamed steps for better display
// for multi-line "run" scripts, only use the first line to match GitHub's behavior
// https://github.com/actions/runner/blob/66800900843747f37591b077091dd2c8cf2c1796/src/Runner.Worker/Handlers/ScriptHandler.cs#L45-L58
runStr, _, _ := strings.Cut(strings.TrimSpace(step.Run), "\n")
name = "Run " + util.IfZero(strings.TrimRight(runStr, "\r"), step.String())
Comment thread
silverwind marked this conversation as resolved.
Outdated
}
return util.EllipsisDisplayString(name, limit) // database column has a length limit
}

func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask, bool, error) {
ctx, committer, err := db.TxContext(ctx)
if err != nil {
Expand Down Expand Up @@ -293,9 +309,8 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
if len(workflowJob.Steps) > 0 {
steps := make([]*ActionTaskStep, len(workflowJob.Steps))
for i, v := range workflowJob.Steps {
name := util.EllipsisDisplayString(v.String(), 255)
steps[i] = &ActionTaskStep{
Name: name,
Name: makeTaskStepDisplayName(v, 255),
TaskID: task.ID,
Index: int64(i),
RepoID: task.RepoID,
Expand Down
2 changes: 1 addition & 1 deletion models/actions/task_step.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
// ActionTaskStep represents a step of ActionTask
type ActionTaskStep struct {
ID int64
Name string `xorm:"VARCHAR(255)"`
Name string `xorm:"VARCHAR(255)"` // the step name, for display purpose only, it will be truncated if it is too long
TaskID int64 `xorm:"index unique(task_index)"`
Index int64 `xorm:"index unique(task_index)"`
RepoID int64 `xorm:"index"`
Expand Down
97 changes: 97 additions & 0 deletions models/actions/task_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package actions

import (
"strings"
"testing"

"github.com/nektos/act/pkg/jobparser"
"github.com/stretchr/testify/assert"
)

func TestMakeTaskStepDisplayName(t *testing.T) {
tests := []struct {
name string
jobStep *jobparser.Step
expected string
}{
{
name: "explicit name",
jobStep: &jobparser.Step{
Name: "Test Step",
},
expected: "Test Step",
},
{
name: "uses step",
jobStep: &jobparser.Step{
Uses: "actions/checkout@v4",
},
expected: "Run actions/checkout@v4",
},
{
name: "single-line run",
jobStep: &jobparser.Step{
Run: "echo hello",
},
expected: "Run echo hello",
},
{
name: "multi-line run",
jobStep: &jobparser.Step{
Run: "echo hello\necho world",
},
expected: "Run echo hello",
},
{
name: "multi-line run block scalar", // run: |\n echo hello\n echo world\n
jobStep: &jobparser.Step{
Run: "echo hello\necho world\n",
Comment thread
wxiaoguang marked this conversation as resolved.
Outdated
},
expected: "Run echo hello",
},
{
name: "multi-line run with leading newline",
jobStep: &jobparser.Step{
Run: "\n echo hello\n echo world",
},
expected: "Run echo hello",
},
{
name: "multi-line run with CRLF",
jobStep: &jobparser.Step{
Run: "echo hello\r\necho world",
},
expected: "Run echo hello",
},
{
name: "fallback to id",
jobStep: &jobparser.Step{
ID: "step-id",
},
expected: "Run step-id",
},
{
name: "very long name truncated",
jobStep: &jobparser.Step{
Name: strings.Repeat("a", 300),
},
expected: strings.Repeat("a", 252) + "…",
},
{
name: "very long run truncated",
jobStep: &jobparser.Step{
Run: strings.Repeat("a", 300),
},
expected: "Run " + strings.Repeat("a", 248) + "…",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := makeTaskStepDisplayName(tt.jobStep, 255)
assert.Equal(t, tt.expected, result)
})
}
}
7 changes: 4 additions & 3 deletions routers/web/repo/actions/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/common"
Expand Down Expand Up @@ -302,7 +303,7 @@ func ViewPost(ctx *context_module.Context) {
resp.State.CurrentJob.Steps = make([]*ViewJobStep, 0) // marshal to '[]' instead fo 'null' in json
resp.Logs.StepsLog = make([]*ViewStepLog, 0) // marshal to '[]' instead fo 'null' in json
if task != nil {
steps, logs, err := convertToViewModel(ctx, req.LogCursors, task)
steps, logs, err := convertToViewModel(ctx, ctx.Locale, req.LogCursors, task)
if err != nil {
ctx.ServerError("convertToViewModel", err)
return
Expand All @@ -314,7 +315,7 @@ func ViewPost(ctx *context_module.Context) {
ctx.JSON(http.StatusOK, resp)
}

func convertToViewModel(ctx *context_module.Context, cursors []LogCursor, task *actions_model.ActionTask) ([]*ViewJobStep, []*ViewStepLog, error) {
func convertToViewModel(ctx context.Context, locale translation.Locale, cursors []LogCursor, task *actions_model.ActionTask) ([]*ViewJobStep, []*ViewStepLog, error) {
Comment thread
ChristopherHX marked this conversation as resolved.
var viewJobs []*ViewJobStep
var logs []*ViewStepLog

Expand Down Expand Up @@ -344,7 +345,7 @@ func convertToViewModel(ctx *context_module.Context, cursors []LogCursor, task *
Lines: []*ViewStepLogLine{
{
Index: 1,
Message: ctx.Locale.TrString("actions.runs.expire_log_message"),
Message: locale.TrString("actions.runs.expire_log_message"),
// Timestamp doesn't mean anything when the log is expired.
// Set it to the task's updated time since it's probably the time when the log has expired.
Timestamp: float64(task.Updated.AsTime().UnixNano()) / float64(time.Second),
Expand Down
47 changes: 47 additions & 0 deletions routers/web/repo/actions/view_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package actions

import (
"testing"

actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/translation"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestConvertToViewModel(t *testing.T) {
task := &actions_model.ActionTask{
Status: actions_model.StatusSuccess,
Steps: []*actions_model.ActionTaskStep{
{Name: "Run step-name", Index: 0, Status: actions_model.StatusSuccess, LogLength: 1, Started: timeutil.TimeStamp(1), Stopped: timeutil.TimeStamp(5)},
},
Stopped: timeutil.TimeStamp(20),
}

viewJobSteps, _, err := convertToViewModel(t.Context(), translation.MockLocale{}, nil, task)
require.NoError(t, err)

expectedViewJobs := []*ViewJobStep{
{
Summary: "Set up job",
Duration: "0s",
Status: "success",
},
{
Summary: "Run step-name",
Duration: "4s",
Status: "success",
},
{
Summary: "Complete job",
Duration: "15s",
Status: "success",
},
}
assert.Equal(t, expectedViewJobs, viewJobSteps)
}