diff --git a/models/dbfs/dbfile.go b/models/dbfs/dbfile.go
index ccb13583e1..a6981cb7d6 100644
--- a/models/dbfs/dbfile.go
+++ b/models/dbfs/dbfile.go
@@ -75,7 +75,7 @@ func (f *file) readAt(fileMeta *dbfsMeta, offset int64, p []byte) (n int, err er
}
func (f *file) Read(p []byte) (n int, err error) {
- if f.metaID == 0 || !f.allowRead {
+ if !f.allowRead {
return 0, os.ErrInvalid
}
@@ -89,7 +89,7 @@ func (f *file) Read(p []byte) (n int, err error) {
}
func (f *file) Write(p []byte) (n int, err error) {
- if f.metaID == 0 || !f.allowWrite {
+ if !f.allowWrite {
return 0, os.ErrInvalid
}
@@ -184,10 +184,6 @@ func (f *file) Close() error {
}
func (f *file) Stat() (os.FileInfo, error) {
- if f.metaID == 0 {
- return nil, os.ErrInvalid
- }
-
fileMeta, err := findFileMetaByID(f.ctx, f.metaID)
if err != nil {
return nil, err
@@ -232,15 +228,17 @@ func (f *file) open(flag int) (err error) {
if f.metaID != 0 {
return os.ErrExist
}
- } else {
- // create a new file if none exists.
- if f.metaID == 0 {
- if err = f.createEmpty(); err != nil {
- return err
- }
+ }
+ // create a new file if not exists.
+ if f.metaID == 0 {
+ if err = f.createEmpty(); err != nil {
+ return err
}
}
}
+ if f.metaID == 0 {
+ return os.ErrNotExist
+ }
if flag&os.O_TRUNC != 0 {
if err = f.truncate(); err != nil {
return err
@@ -252,7 +250,7 @@ func (f *file) open(flag int) (err error) {
}
}
return nil
- }
+ } // end if: allowWrite
// read only mode
if f.metaID == 0 {
@@ -322,9 +320,6 @@ func (f *file) delete() error {
}
func (f *file) size() (int64, error) {
- if f.metaID == 0 {
- return 0, os.ErrNotExist
- }
fileMeta, err := findFileMetaByID(f.ctx, f.metaID)
if err != nil {
return 0, err
@@ -339,7 +334,7 @@ func findFileMetaByID(ctx context.Context, metaID int64) (*dbfsMeta, error) {
} else if ok {
return &fileMeta, nil
}
- return nil, nil //nolint:nilnil // return nil to indicate that the object does not exist
+ return nil, os.ErrNotExist
}
func buildPath(path string) string {
diff --git a/models/dbfs/dbfs.go b/models/dbfs/dbfs.go
index f68b4a2b70..3f768b5339 100644
--- a/models/dbfs/dbfs.go
+++ b/models/dbfs/dbfs.go
@@ -40,6 +40,9 @@ The DBFS solution:
* In the future, when Gitea action needs to limit the log size (other CI/CD services also do so), it's easier to calculate the log file size.
* Even sometimes the UI needs to render the tailing lines, the tailing lines can be found be counting the "\n" from the end of the file by seek.
The seeking and finding is not the fastest way, but it's still acceptable and won't affect the performance too much.
+
+Limitations of the DBFS solution:
+* Not fully POSIX-compliant, some behaviors may be different from the real filesystem, especially for concurrent read/write
*/
type dbfsMeta struct {
diff --git a/models/dbfs/dbfs_test.go b/models/dbfs/dbfs_test.go
index e1ecd871e4..ca57ebe171 100644
--- a/models/dbfs/dbfs_test.go
+++ b/models/dbfs/dbfs_test.go
@@ -9,19 +9,14 @@ import (
"os"
"testing"
+ "code.gitea.io/gitea/modules/test"
+
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
-func changeDefaultFileBlockSize(n int64) (restore func()) {
- old := defaultFileBlockSize
- defaultFileBlockSize = n
- return func() {
- defaultFileBlockSize = old
- }
-}
-
func TestDbfsBasic(t *testing.T) {
- defer changeDefaultFileBlockSize(4)()
+ defer test.MockVariableValue(&defaultFileBlockSize, 4)()
// test basic write/read
f, err := OpenFile(t.Context(), "test.txt", os.O_RDWR|os.O_CREATE)
@@ -122,10 +117,55 @@ func TestDbfsBasic(t *testing.T) {
stat, err = f.Stat()
assert.NoError(t, err)
assert.EqualValues(t, 10, stat.Size())
+
+ t.Run("NonExisting", func(t *testing.T) {
+ f, err := OpenFile(t.Context(), "non-existing.txt", os.O_RDONLY)
+ assert.ErrorIs(t, err, os.ErrNotExist)
+ assert.Nil(t, f)
+
+ f, err = OpenFile(t.Context(), "non-existing.txt", os.O_WRONLY)
+ assert.ErrorIs(t, err, os.ErrNotExist)
+ assert.Nil(t, f)
+
+ f, err = OpenFile(t.Context(), "non-existing.txt", os.O_WRONLY|os.O_APPEND|os.O_TRUNC)
+ assert.ErrorIs(t, err, os.ErrNotExist)
+ assert.Nil(t, f)
+ })
+
+ t.Run("Existing", func(t *testing.T) {
+ assertFileContent := func(f File, expected string) {
+ _, err := f.Seek(0, io.SeekStart)
+ require.NoError(t, err)
+ buf, err := io.ReadAll(f)
+ require.NoError(t, err)
+ assert.Equal(t, expected, string(buf))
+ }
+
+ f, err := OpenFile(t.Context(), "existing.txt", os.O_RDWR|os.O_CREATE)
+ require.NoError(t, err)
+ _, _ = f.Write([]byte("test"))
+ assertFileContent(f, "test")
+ assert.NoError(t, f.Close())
+
+ f, err = OpenFile(t.Context(), "existing.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND)
+ require.NoError(t, err)
+ _, _ = f.Write([]byte("\nnew"))
+ assertFileContent(f, "test\nnew")
+ assert.NoError(t, f.Close())
+
+ f, err = OpenFile(t.Context(), "existing.txt", os.O_RDWR|os.O_TRUNC)
+ require.NoError(t, err)
+ assertFileContent(f, "")
+ assert.NoError(t, f.Close())
+
+ f, err = OpenFile(t.Context(), "existing.txt", os.O_RDWR|os.O_CREATE|os.O_EXCL)
+ assert.ErrorIs(t, err, os.ErrExist)
+ assert.Nil(t, f)
+ })
}
func TestDbfsReadWrite(t *testing.T) {
- defer changeDefaultFileBlockSize(4)()
+ defer test.MockVariableValue(&defaultFileBlockSize, 4)()
f1, err := OpenFile(t.Context(), "test.log", os.O_RDWR|os.O_CREATE)
assert.NoError(t, err)
@@ -157,30 +197,32 @@ func TestDbfsReadWrite(t *testing.T) {
}
func TestDbfsSeekWrite(t *testing.T) {
- defer changeDefaultFileBlockSize(4)()
+ defer test.MockVariableValue(&defaultFileBlockSize, 4)()
- f, err := OpenFile(t.Context(), "test2.log", os.O_RDWR|os.O_CREATE)
- assert.NoError(t, err)
- defer f.Close()
+ // write something
+ fw, err := OpenFile(t.Context(), "test2.log", os.O_RDWR|os.O_CREATE)
+ require.NoError(t, err)
+ defer fw.Close()
- n, err := f.Write([]byte("111"))
+ n, err := fw.Write([]byte("111"))
assert.NoError(t, err)
- _, err = f.Seek(int64(n), io.SeekStart)
+ _, err = fw.Seek(int64(n), io.SeekStart)
assert.NoError(t, err)
- _, err = f.Write([]byte("222"))
+ _, err = fw.Write([]byte("222"))
assert.NoError(t, err)
- _, err = f.Seek(int64(n), io.SeekStart)
+ _, err = fw.Seek(int64(n), io.SeekStart)
assert.NoError(t, err)
- _, err = f.Write([]byte("333"))
+ _, err = fw.Write([]byte("333"))
assert.NoError(t, err)
+ // then read it
fr, err := OpenFile(t.Context(), "test2.log", os.O_RDONLY)
- assert.NoError(t, err)
- defer f.Close()
+ require.NoError(t, err)
+ defer fr.Close()
buf, err := io.ReadAll(fr)
assert.NoError(t, err)
diff --git a/modules/actions/log.go b/modules/actions/log.go
index 5a1425e031..3fb56b402a 100644
--- a/modules/actions/log.go
+++ b/modules/actions/log.go
@@ -33,21 +33,22 @@ const (
// It doesn't respect the file format in the filename like ".zst", since it's difficult to reopen a closed compressed file and append new content.
// Why doesn't it store logs in object storage directly? Because it's not efficient to append content to object storage.
func WriteLogs(ctx context.Context, filename string, offset int64, rows []*runnerv1.LogRow) ([]int, error) {
- flag := os.O_WRONLY
+ flag, openFileFor := os.O_WRONLY, "write-only"
if offset == 0 {
- // Create file only if offset is 0, or it could result in content holes if the file doesn't exist.
- flag |= os.O_CREATE
+ // Only allow to create file if offset is 0 (the first write), see #25560.
+ // Otherwise, it might result in content holes if the file has been deleted after transferred (actions.TransferLogs).
+ flag, openFileFor = os.O_WRONLY|os.O_CREATE, "write-create"
}
name := DBFSPrefix + filename
f, err := dbfs.OpenFile(ctx, name, flag)
if err != nil {
- return nil, fmt.Errorf("dbfs OpenFile %q: %w", name, err)
+ return nil, fmt.Errorf("dbfs.OpenFile %q for %s: %w", name, openFileFor, err)
}
defer f.Close()
stat, err := f.Stat()
if err != nil {
- return nil, fmt.Errorf("dbfs Stat %q: %w", name, err)
+ return nil, fmt.Errorf("dbfs.Stat %q: %w", name, err)
}
if stat.Size() < offset {
// If the size is less than offset, refuse to write, or it could result in content holes.
@@ -56,7 +57,7 @@ func WriteLogs(ctx context.Context, filename string, offset int64, rows []*runne
}
if _, err := f.Seek(offset, io.SeekStart); err != nil {
- return nil, fmt.Errorf("dbfs Seek %q: %w", name, err)
+ return nil, fmt.Errorf("dbfs.Seek %q: %w", name, err)
}
writer := bufio.NewWriterSize(f, defaultBufSize)
@@ -121,16 +122,17 @@ const (
// TransferLogs transfers logs from DBFS to object storage.
// It happens when the file is complete and no more logs will be appended.
// It respects the file format in the filename like ".zst", and compresses the content if needed.
+// The task log file must be marked as "log_in_storage=true" after the transfer.
func TransferLogs(ctx context.Context, filename string) (func(), error) {
name := DBFSPrefix + filename
remove := func() {
if err := dbfs.Remove(ctx, name); err != nil {
- log.Warn("dbfs remove %q: %v", name, err)
+ log.Warn("dbfs.Remove %q: %v", name, err)
}
}
f, err := dbfs.Open(ctx, name)
if err != nil {
- return nil, fmt.Errorf("dbfs open %q: %w", name, err)
+ return nil, fmt.Errorf("dbfs.Open %q: %w", name, err)
}
defer f.Close()
@@ -164,7 +166,7 @@ func RemoveLogs(ctx context.Context, inStorage bool, filename string) error {
name := DBFSPrefix + filename
err := dbfs.Remove(ctx, name)
if err != nil {
- return fmt.Errorf("dbfs remove %q: %w", name, err)
+ return fmt.Errorf("dbfs.Remove %q: %w", name, err)
}
return nil
}
@@ -180,7 +182,7 @@ func OpenLogs(ctx context.Context, inStorage bool, filename string) (io.ReadSeek
name := DBFSPrefix + filename
f, err := dbfs.Open(ctx, name)
if err != nil {
- return nil, fmt.Errorf("dbfs open %q: %w", name, err)
+ return nil, fmt.Errorf("dbfs.Open %q: %w", name, err)
}
return f, nil
}
diff --git a/options/locale/locale_fr-FR.json b/options/locale/locale_fr-FR.json
index caa1b7dbf3..e0d6cb541e 100644
--- a/options/locale/locale_fr-FR.json
+++ b/options/locale/locale_fr-FR.json
@@ -84,6 +84,7 @@
"save": "Enregistrer",
"add": "Ajouter",
"add_all": "Tout Ajouter",
+ "dismiss": "Fermer",
"remove": "Retirer",
"remove_all": "Tout Retirer",
"remove_label_str": "Supprimer l’élément « %s »",
@@ -284,12 +285,6 @@
"install.register_confirm": "Exiger la confirmation du courriel lors de l’inscription",
"install.mail_notify": "Activer les notifications par courriel",
"install.server_service_title": "Paramètres Serveur et Tierce Parties",
- "install.offline_mode": "Activer le mode hors-ligne",
- "install.offline_mode_popup": "Désactiver l'utilisation de CDNs, et servir toutes les ressources localement.",
- "install.disable_gravatar": "Désactiver Gravatar",
- "install.disable_gravatar_popup": "Désactiver Gravatar et les autres sources d'avatars tierces. Un avatar par défaut sera utilisé pour les utilisateurs n'ayant pas téléversé un avatar personnalisé.",
- "install.federated_avatar_lookup": "Activer les avatars unifiés",
- "install.federated_avatar_lookup_popup": "Activer la recherche unifiée d'avatars en utilisant le service open source unifié basé sur libravatar.",
"install.disable_registration": "Désactiver le formulaire d'inscription",
"install.disable_registration_popup": "Désactiver les nouvelles inscriptions. Seuls les administrateurs pourront créer de nouveaux comptes utilisateurs.",
"install.allow_only_external_registration_popup": "N'autoriser l'inscription qu'à partir des services externes",
@@ -871,7 +866,7 @@
"settings.permissions_list": "Autorisations :",
"settings.manage_oauth2_applications": "Gérer les applications OAuth2",
"settings.edit_oauth2_application": "Modifier l'application OAuth2",
- "settings.oauth2_applications_desc": "Les applications OAuth2 permettent à votre application tierce d'authentifier en toute sécurité les utilisateurs de cette instance Gitea.",
+ "settings.oauth2_applications_desc": "OAuth2 permet a une application tierce d‘authentifier les utilisateurs de cette instance Gitea.",
"settings.remove_oauth2_application": "Supprimer l'application OAuth2",
"settings.remove_oauth2_application_desc": "La suppression d'une application OAuth2 révoquera l'accès à tous les jetons d'accès signés. Continuer ?",
"settings.remove_oauth2_application_success": "L'application a été supprimée.",
@@ -890,7 +885,7 @@
"settings.oauth2_regenerate_secret_hint": "Avez-vous perdu votre secret ?",
"settings.oauth2_client_secret_hint": "Le secret ne sera plus affiché après avoir quitté ou actualisé cette page. Veuillez vous assurer que vous l'avez enregistré.",
"settings.oauth2_application_edit": "Éditer",
- "settings.oauth2_application_create_description": "Les applications OAuth2 permettent à votre application tierce d'accéder aux comptes d'utilisateurs de cette instance.",
+ "settings.oauth2_application_create_description": "OAuth2 permet à des applications tierces d’accéder aux comptes utilisateurs de cette instance.",
"settings.oauth2_application_remove_description": "La suppression d'une application OAuth2 l'empêchera d'accéder aux comptes d'utilisateurs autorisés sur cette instance. Poursuivre ?",
"settings.oauth2_application_locked": "Gitea préinstalle des applications OAuth2 au démarrage si elles sont activées dans la configuration. Pour éviter des comportements inattendus, celles-ci ne peuvent être éditées ni supprimées. Veuillez vous référer à la documentation OAuth2 pour plus d'informations.",
"settings.authorized_oauth2_applications": "Applications OAuth2 autorisées",
@@ -1524,6 +1519,7 @@
"repo.issues.commented_at": "a commenté %s.",
"repo.issues.delete_comment_confirm": "Êtes-vous certain de vouloir supprimer ce commentaire?",
"repo.issues.context.copy_link": "Copier le lien",
+ "repo.issues.context.copy_source": "Copier la source",
"repo.issues.context.quote_reply": "Citer et répondre",
"repo.issues.context.reference_issue": "Référencer dans un nouveau ticket",
"repo.issues.context.edit": "Éditer",
@@ -3192,7 +3188,6 @@
"admin.config.custom_conf": "Chemin du fichier de configuration",
"admin.config.custom_file_root_path": "Emplacement personnalisé du fichier racine",
"admin.config.domain": "Domaine du serveur",
- "admin.config.offline_mode": "Mode hors-ligne",
"admin.config.disable_router_log": "Désactiver la Journalisation du Routeur",
"admin.config.run_user": "Exécuter avec l'utilisateur",
"admin.config.run_mode": "Mode d'Éxécution",
@@ -3278,6 +3273,13 @@
"admin.config.cache_test_failed": "Impossible d’interroger le cache : %v.",
"admin.config.cache_test_slow": "Test du cache réussi, mais la réponse est lente : %s.",
"admin.config.cache_test_succeeded": "Test du cache réussi, réponse obtenue en %s.",
+ "admin.config.common.start_time": "Heure de début",
+ "admin.config.common.end_time": "Heure de fin",
+ "admin.config.common.skip_time_check": "Laisser le temps vide (effacer le champ) pour passer la vérification",
+ "admin.config.instance_maintenance": "Maintenance de l’instance",
+ "admin.config.instance_maintenance_mode.admin_web_access_only": "Permettre uniquement aux administrateurs d’accéder à l’interface web",
+ "admin.config.instance_web_banner.enabled": "Afficher la bannière",
+ "admin.config.instance_web_banner.message_placeholder": "Message de bannière (supporte markdown)",
"admin.config.session_config": "Configuration de session",
"admin.config.session_provider": "Fournisseur de session",
"admin.config.provider_config": "Configuration du fournisseur",
@@ -3288,7 +3290,7 @@
"admin.config.cookie_life_time": "Expiration du cookie",
"admin.config.picture_config": "Configuration de l'avatar",
"admin.config.picture_service": "Service d'Imagerie",
- "admin.config.disable_gravatar": "Désactiver Gravatar",
+ "admin.config.enable_gravatar": "Activer Gravatar",
"admin.config.enable_federated_avatar": "Activer les avatars unifiés",
"admin.config.open_with_editor_app_help": "Les éditeurs disponibles via « Ouvrir avec ». Si laissé vide, la valeur par défaut sera utilisée. Développez pour voir la valeur par défaut.",
"admin.config.git_guide_remote_name": "Nom du dépôt distant pour les commandes git dans le guide",
@@ -3672,6 +3674,8 @@
"actions.runners.reset_registration_token_confirm": "Voulez-vous révoquer le jeton actuel et en générer un nouveau ?",
"actions.runners.reset_registration_token_success": "Le jeton d’inscription de l’exécuteur a été réinitialisé avec succès",
"actions.runs.all_workflows": "Tous les flux de travail",
+ "actions.runs.workflow_run_count_1": "%d exécution du workflow",
+ "actions.runs.workflow_run_count_n": "%d exécutions du workflow",
"actions.runs.commit": "Révision",
"actions.runs.scheduled": "Planifié",
"actions.runs.pushed_by": "soumis par",
diff --git a/options/locale/locale_zh-CN.json b/options/locale/locale_zh-CN.json
index 2f0dced6e0..8bdd8d7faf 100644
--- a/options/locale/locale_zh-CN.json
+++ b/options/locale/locale_zh-CN.json
@@ -1399,17 +1399,17 @@
"repo.issues.new.clear_labels": "清除选中标签",
"repo.issues.new.projects": "项目",
"repo.issues.new.clear_projects": "清除项目",
- "repo.issues.new.no_projects": "暂无项目",
+ "repo.issues.new.no_projects": "未选择项目",
"repo.issues.new.open_projects": "开启中的项目",
"repo.issues.new.closed_projects": "已关闭的项目",
"repo.issues.new.no_items": "无可选项",
"repo.issues.new.milestone": "里程碑",
"repo.issues.new.no_milestone": "未选择里程碑",
"repo.issues.new.clear_milestone": "取消选中里程碑",
- "repo.issues.new.assignees": "指派成员",
- "repo.issues.new.clear_assignees": "取消指派成员",
- "repo.issues.new.no_assignees": "未指派成员",
- "repo.issues.new.no_reviewers": "无评审人",
+ "repo.issues.new.assignees": "指派人",
+ "repo.issues.new.clear_assignees": "取消指派人",
+ "repo.issues.new.no_assignees": "未指派人员",
+ "repo.issues.new.no_reviewers": "未指定评审人",
"repo.issues.new.blocked_user": "无法创建工单,因为您已被仓库所有者屏蔽。",
"repo.issues.edit.already_changed": "无法保存对工单的更改。其内容似乎已被其他用户更改。请刷新页面并重新编辑以避免覆盖他们的更改。",
"repo.issues.edit.blocked_user": "无法编辑内容,因为您已被仓库所有者或工单创建者屏蔽。",
@@ -1465,9 +1465,9 @@
"repo.issues.filter_milestone_closed": "已关闭的里程碑",
"repo.issues.filter_project": "项目",
"repo.issues.filter_project_all": "所有项目",
- "repo.issues.filter_project_none": "未加项目",
+ "repo.issues.filter_project_none": "无项目",
"repo.issues.filter_assignee": "指派人筛选",
- "repo.issues.filter_assignee_no_assignee": "未指派给任何人",
+ "repo.issues.filter_assignee_no_assignee": "未指派任何人",
"repo.issues.filter_assignee_any_assignee": "已有指派",
"repo.issues.filter_poster": "作者",
"repo.issues.filter_user_placeholder": "搜索用户",
@@ -1487,8 +1487,8 @@
"repo.issues.filter_sort.leastupdate": "最早更新",
"repo.issues.filter_sort.mostcomment": "最多评论",
"repo.issues.filter_sort.leastcomment": "最少评论",
- "repo.issues.filter_sort.nearduedate": "到期日从近到远",
- "repo.issues.filter_sort.farduedate": "到期日从远到近",
+ "repo.issues.filter_sort.nearduedate": "截止日期从近到远",
+ "repo.issues.filter_sort.farduedate": "截止日期从远到近",
"repo.issues.filter_sort.moststars": "点赞由多到少",
"repo.issues.filter_sort.feweststars": "点赞由少到多",
"repo.issues.filter_sort.mostforks": "派生由多到少",
@@ -1519,6 +1519,7 @@
"repo.issues.commented_at": "评论于 %s",
"repo.issues.delete_comment_confirm": "您确定要删除该条评论吗?",
"repo.issues.context.copy_link": "复制链接",
+ "repo.issues.context.copy_source": "复制原文",
"repo.issues.context.quote_reply": "引用回复",
"repo.issues.context.reference_issue": "在新工单中引用",
"repo.issues.context.edit": "编辑",
@@ -1927,8 +1928,8 @@
"repo.milestones.deletion_desc": "删除该里程碑将会移除所有工单中相关的信息。是否继续?",
"repo.milestones.deletion_success": "里程碑已删除。",
"repo.milestones.filter_sort.name": "名称",
- "repo.milestones.filter_sort.earliest_due_data": "到期日从远到近",
- "repo.milestones.filter_sort.latest_due_date": "到期日从近到远",
+ "repo.milestones.filter_sort.earliest_due_data": "截止日期从远到近",
+ "repo.milestones.filter_sort.latest_due_date": "截止日期从近到远",
"repo.milestones.filter_sort.least_complete": "完成度从低到高",
"repo.milestones.filter_sort.most_complete": "完成度从高到低",
"repo.milestones.filter_sort.most_issues": "工单从多到少",
@@ -2011,7 +2012,7 @@
"repo.activity.title.issues_closed_from": "%[2]s 关闭了 %[1]s",
"repo.activity.title.issues_created_by": "%[2]s 创建了 %[1]s",
"repo.activity.closed_issue_label": "已关闭",
- "repo.activity.new_issues_count_1": "开启的工单",
+ "repo.activity.new_issues_count_1": "已开启的工单",
"repo.activity.new_issues_count_n": "已打开的工单",
"repo.activity.new_issue_label": "打开的",
"repo.activity.title.unresolved_conv_1": "%d 未解决的会话",
@@ -3673,6 +3674,8 @@
"actions.runners.reset_registration_token_confirm": "是否吊销当前令牌并生成一个新令牌?",
"actions.runners.reset_registration_token_success": "成功重置运行器注册令牌",
"actions.runs.all_workflows": "所有工作流",
+ "actions.runs.workflow_run_count_1": "%d 次工作流运行",
+ "actions.runs.workflow_run_count_n": "%d 次工作流运行",
"actions.runs.commit": "提交",
"actions.runs.scheduled": "已计划的",
"actions.runs.pushed_by": "推送者",
diff --git a/routers/api/actions/runner/runner.go b/routers/api/actions/runner/runner.go
index 86bab4b340..49d1b13262 100644
--- a/routers/api/actions/runner/runner.go
+++ b/routers/api/actions/runner/runner.go
@@ -270,7 +270,7 @@ func (s *Service) UpdateLog(
rows := req.Msg.Rows[ack-req.Msg.Index:]
ns, err := actions.WriteLogs(ctx, task.LogFilename, task.LogSize, rows)
if err != nil {
- return nil, status.Errorf(codes.Internal, "write logs: %v", err)
+ return nil, status.Errorf(codes.Internal, "unable to append logs to dbfs file: %v", err)
}
task.LogLength += int64(len(rows))
for _, n := range ns {