From cf48aa0188b3c207ba4f3e46aebf0f932a65c199 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Thu, 7 May 2026 01:06:22 +0000 Subject: [PATCH 1/4] [skip ci] Updated translations via Crowdin --- options/locale/locale_zh-CN.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/options/locale/locale_zh-CN.json b/options/locale/locale_zh-CN.json index 5960e554b7..6b9ba53878 100644 --- a/options/locale/locale_zh-CN.json +++ b/options/locale/locale_zh-CN.json @@ -313,7 +313,6 @@ "install.admin_email": "邮箱地址", "install.install_btn_confirm": "立即安装", "install.test_git_failed": "无法识别 'git' 命令:%v", - "install.sqlite3_not_available": "您所使用的发行版不支持 SQLite3,请从 %s 下载官方构建版,而不是 gobuild 版本。", "install.invalid_db_setting": "数据库设置无效: %v", "install.invalid_db_table": "数据库表「%s」无效:%v", "install.invalid_repo_path": "仓库根目录设置无效:%v", @@ -1385,6 +1384,7 @@ "repo.projects.column.delete": "删除列", "repo.projects.column.deletion_desc": "删除项目列会将所有相关工单移至默认列。是否继续?", "repo.projects.column.color": "颜色", + "repo.projects.column": "列", "repo.projects.open": "开启", "repo.projects.close": "关闭", "repo.projects.column.assigned_to": "指派给", @@ -1784,6 +1784,7 @@ "repo.pulls.review_only_possible_for_full_diff": "只有在查看全部差异时才能进行审核", "repo.pulls.filter_changes_by_commit": "按提交筛选", "repo.pulls.nothing_to_compare": "分支内容相同,无需创建合并请求。", + "repo.pulls.no_common_history": "这些分支没有共同的合并基点。请选择不同的基点或比较分支。", "repo.pulls.nothing_to_compare_have_tag": "所选分支 / Git 标签相同。", "repo.pulls.nothing_to_compare_and_allow_empty_pr": "这些分支是相等的,此合并请求将为空。", "repo.pulls.has_pull_request": "这些分支之间的合并请求已存在:%[2]s#%[3]d", @@ -1956,7 +1957,6 @@ "repo.signing.wont_sign.headsigned": "合并将不会被签名,因为最新提交没有签名。", "repo.signing.wont_sign.commitssigned": "合并将不会被签名,因为所有相关的提交都没有签名。", "repo.signing.wont_sign.approved": "合并将不会被签名,因为合并请求未被批准。", - "repo.signing.wont_sign.not_signed_in": "您还没有登录。", "repo.ext_wiki": "访问外部百科", "repo.ext_wiki.desc": "链接到外部百科。", "repo.wiki": "百科", @@ -3313,7 +3313,6 @@ "admin.config.cache_config": "缓存配置", "admin.config.cache_adapter": "缓存适配器", "admin.config.cache_interval": "缓存周期", - "admin.config.cache_conn": "缓存连接字符串", "admin.config.cache_item_ttl": "缓存项目 TTL", "admin.config.cache_test": "测试缓存", "admin.config.cache_test_failed": "缓存测试失败:%v。", @@ -3328,7 +3327,6 @@ "admin.config.instance_web_banner.message_placeholder": "横幅消息(支持 Markdown)", "admin.config.session_config": "会话配置", "admin.config.session_provider": "会话提供者", - "admin.config.provider_config": "提供者配置", "admin.config.cookie_name": "Cookie 名称", "admin.config.gc_interval_time": "GC 周期", "admin.config.session_life_time": "会话生命周期", From 630258410def3e8801efc9a25728b791901f7e3e Mon Sep 17 00:00:00 2001 From: Kausthubh J Rao <105716675+Exgene@users.noreply.github.com> Date: Thu, 7 May 2026 07:06:34 +0530 Subject: [PATCH 2/4] fix(actions): prevent panic when workflow contains null jobs (#37570) ## The issue Closes #37568. Basically due to empty fields being present in the actions file, the jobs would be produced as `nil` inside `jobparser.go` . Because of this when we call `Parse` on the `jobparser` module. ```go Needs: job.Needs(), ``` would propagate the `nil` job down the chain. ## The fix For now i decide to fix it by guarding with an `if job == nil` check. --------- Signed-off-by: wxiaoguang Co-authored-by: wxiaoguang Co-authored-by: Lunny Xiao Co-authored-by: Giteabot --- modules/actions/jobparser/jobparser.go | 3 +++ modules/actions/jobparser/jobparser_test.go | 17 +++++++++++++++++ .../testdata/null_job_explicit.in.yaml | 9 +++++++++ .../testdata/null_job_implicit.in.yaml | 9 +++++++++ 4 files changed, 38 insertions(+) create mode 100644 modules/actions/jobparser/testdata/null_job_explicit.in.yaml create mode 100644 modules/actions/jobparser/testdata/null_job_implicit.in.yaml diff --git a/modules/actions/jobparser/jobparser.go b/modules/actions/jobparser/jobparser.go index f2ab0ad3c9..e7a2b48498 100644 --- a/modules/actions/jobparser/jobparser.go +++ b/modules/actions/jobparser/jobparser.go @@ -31,6 +31,9 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) { } results := map[string]*JobResult{} for id, job := range origin.Jobs { + if job == nil { + return nil, fmt.Errorf("needed job not found: %q", id) + } results[id] = &JobResult{ Needs: job.Needs(), Result: pc.jobResults[id], diff --git a/modules/actions/jobparser/jobparser_test.go b/modules/actions/jobparser/jobparser_test.go index 51ba70fc2a..e74f0644f8 100644 --- a/modules/actions/jobparser/jobparser_test.go +++ b/modules/actions/jobparser/jobparser_test.go @@ -59,6 +59,13 @@ func TestParse(t *testing.T) { wantErr: false, }, } + invalidFileTests := []struct { + name string + }{ + {name: "null_job_implicit"}, + {name: "null_job_explicit"}, + } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { content := ReadTestdata(t, tt.name+".in.yaml") @@ -84,4 +91,14 @@ func TestParse(t *testing.T) { assert.Equal(t, string(want), builder.String()) }) } + + for _, tt := range invalidFileTests { + t.Run(tt.name, func(t *testing.T) { + content := ReadTestdata(t, tt.name+".in.yaml") + require.NotPanics(t, func() { + _, err := Parse(content) + require.Error(t, err) + }) + }) + } } diff --git a/modules/actions/jobparser/testdata/null_job_explicit.in.yaml b/modules/actions/jobparser/testdata/null_job_explicit.in.yaml new file mode 100644 index 0000000000..6731507a32 --- /dev/null +++ b/modules/actions/jobparser/testdata/null_job_explicit.in.yaml @@ -0,0 +1,9 @@ +# null_job_explicit.in.yaml +on: push +jobs: + empty: null + notempty: + needs: empty + runs-on: ubuntu-latest + steps: + - run: echo ok diff --git a/modules/actions/jobparser/testdata/null_job_implicit.in.yaml b/modules/actions/jobparser/testdata/null_job_implicit.in.yaml new file mode 100644 index 0000000000..26591aadcd --- /dev/null +++ b/modules/actions/jobparser/testdata/null_job_implicit.in.yaml @@ -0,0 +1,9 @@ +# null_job_implicit.in.yaml +on: push +jobs: + empty: + notempty: + needs: empty + runs-on: ubuntu-latest + steps: + - run: echo ok From 19f01691d525f92642c184b9b4921857729a6034 Mon Sep 17 00:00:00 2001 From: Kausthubh J Rao <105716675+Exgene@users.noreply.github.com> Date: Thu, 7 May 2026 07:33:08 +0530 Subject: [PATCH 3/4] fix(api): return 409 message instead of empty JSON for wrong commit id (#37572) ## Issue Closes #37217 The error string was getting lost while returning due to `ctx.JSON()` which cannot serialize the `error` object. ## Fix Use `ctx.APIError()` to return proper error messages back to the client. --- routers/api/v1/repo/pull.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 2426a6b3c2..2702f6864f 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -996,7 +996,7 @@ func MergePullRequest(ctx *context.APIContext) { return } if strings.Contains(err.Error(), "Wrong commit ID") { - ctx.JSON(http.StatusConflict, err) + ctx.APIError(http.StatusConflict, err) return } ctx.APIErrorInternal(err) From 2200ed7499e2358170a33e476cdb2bac4d63ed5d Mon Sep 17 00:00:00 2001 From: Nicolas Date: Thu, 7 May 2026 08:10:19 +0200 Subject: [PATCH 4/4] fix: use consistent GetUser family functions (#37553) fixes adding collaborative owners in Actions settings when the user or organization name contains capital letters. Fixes #37548 --------- Signed-off-by: wxiaoguang Co-authored-by: wxiaoguang --- models/user/user.go | 61 ++++++++------------------- routers/common/compare.go | 2 +- routers/web/repo/setting/actions.go | 12 ++---- services/migrations/gitea_uploader.go | 12 ++++-- 4 files changed, 30 insertions(+), 57 deletions(-) diff --git a/models/user/user.go b/models/user/user.go index 0e87d7b7b9..8a39eca634 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -1051,13 +1051,13 @@ func GetPossibleUserByIDs(ctx context.Context, ids []int64) ([]*User, error) { return users, nil } -// GetUserByName returns user by given name. -func GetUserByName(ctx context.Context, name string) (*User, error) { - if len(name) == 0 { - return nil, ErrUserNotExist{Name: name} +func getUserByNameWithTypes(ctx context.Context, name string, types ...UserType) (*User, error) { + u := &User{} + sess := db.GetEngine(ctx).Where(builder.Eq{"lower_name": strings.ToLower(name)}) + if len(types) > 0 { + sess.In("`type`", types) } - u := &User{LowerName: strings.ToLower(name), Type: UserTypeIndividual} - has, err := db.GetEngine(ctx).Get(u) + has, err := sess.Get(u) if err != nil { return nil, err } else if !has { @@ -1066,6 +1066,15 @@ func GetUserByName(ctx context.Context, name string) (*User, error) { return u, nil } +// GetUserByName returns the user object by given name, any user type. +func GetUserByName(ctx context.Context, name string) (*User, error) { + return getUserByNameWithTypes(ctx, name) +} + +func GetIndividualUserByName(ctx context.Context, name string) (*User, error) { + return getUserByNameWithTypes(ctx, name, UserTypeIndividual) +} + // GetUserEmailsByNames returns a list of e-mails corresponds to names of users // that have their email notifications set to enabled or onmention. func GetUserEmailsByNames(ctx context.Context, names []string) []string { @@ -1108,19 +1117,6 @@ func GetMailableUsersByIDs(ctx context.Context, ids []int64, isMention bool) ([] Find(&ous) } -// GetUserNameByID returns username for the id -func GetUserNameByID(ctx context.Context, id int64) (string, error) { - var name string - has, err := db.GetEngine(ctx).Table("user").Where("id = ?", id).Cols("name").Get(&name) - if err != nil { - return "", err - } - if has { - return name, nil - } - return "", nil -} - // GetUserIDsByNames returns a slice of ids corresponds to names. func GetUserIDsByNames(ctx context.Context, names []string, ignoreNonExistent bool) ([]int64, error) { ids := make([]int64, 0, len(names)) @@ -1321,13 +1317,14 @@ func GetUserByEmail(ctx context.Context, email string) (*User, error) { if id != 0 { return GetUserByID(ctx, id) } - return GetUserByName(ctx, name) + return GetIndividualUserByName(ctx, name) } return nil, ErrUserNotExist{Name: email} } func GetIndividualUser(ctx context.Context, user *User) (bool, error) { + // FIXME: the design is wrong, empty User fields won't apply, this function should be removed in the future has, err := db.GetEngine(ctx).Get(user) if has && user.Type != UserTypeIndividual { has = false @@ -1492,27 +1489,3 @@ func DisabledFeaturesWithLoginType(user *User) *container.Set[string] { } return &setting.Admin.UserDisabledFeatures } - -// GetUserOrOrgIDByName returns the id for a user or an org by name -func GetUserOrOrgIDByName(ctx context.Context, name string) (int64, error) { - var id int64 - has, err := db.GetEngine(ctx).Table("user").Where("name = ?", name).Cols("id").Get(&id) - if err != nil { - return 0, err - } else if !has { - return 0, fmt.Errorf("user or org with name %s: %w", name, util.ErrNotExist) - } - return id, nil -} - -// GetUserOrOrgByName returns the user or org by name -func GetUserOrOrgByName(ctx context.Context, name string) (*User, error) { - var u User - has, err := db.GetEngine(ctx).Where("lower_name = ?", strings.ToLower(name)).Get(&u) - if err != nil { - return nil, err - } else if !has { - return nil, ErrUserNotExist{Name: name} - } - return &u, nil -} diff --git a/routers/common/compare.go b/routers/common/compare.go index 7e917c4df8..8b1e1715d2 100644 --- a/routers/common/compare.go +++ b/routers/common/compare.go @@ -129,7 +129,7 @@ func GetHeadOwnerAndRepo(ctx context.Context, baseRepo *repo_model.Repository, c if compareReq.HeadOwner == baseRepo.Owner.Name { headOwner = baseRepo.Owner } else { - headOwner, err = user_model.GetUserOrOrgByName(ctx, compareReq.HeadOwner) + headOwner, err = user_model.GetUserByName(ctx, compareReq.HeadOwner) if err != nil { return nil, nil, err } diff --git a/routers/web/repo/setting/actions.go b/routers/web/repo/setting/actions.go index 2237828d61..27b16b42d9 100644 --- a/routers/web/repo/setting/actions.go +++ b/routers/web/repo/setting/actions.go @@ -6,7 +6,6 @@ package setting import ( "errors" "net/http" - "strings" "code.gitea.io/gitea/models/actions" repo_model "code.gitea.io/gitea/models/repo" @@ -94,15 +93,12 @@ func ActionsUnitPost(ctx *context.Context) { } func AddCollaborativeOwner(ctx *context.Context) { - name := strings.ToLower(ctx.FormString("collaborative_owner")) - - ownerID, err := user_model.GetUserOrOrgIDByName(ctx, name) + collUser, err := user_model.GetUserByName(ctx, ctx.FormString("collaborative_owner")) if err != nil { if errors.Is(err, util.ErrNotExist) { - ctx.Flash.Error(ctx.Tr("form.user_not_exist")) - ctx.JSONErrorNotFound() + ctx.JSONError(ctx.Tr("form.user_not_exist")) } else { - ctx.ServerError("GetUserOrOrgIDByName", err) + ctx.ServerError("GetUserByName", err) } return } @@ -113,7 +109,7 @@ func AddCollaborativeOwner(ctx *context.Context) { return } actionsCfg := actionsUnit.ActionsConfig() - actionsCfg.AddCollaborativeOwner(ownerID) + actionsCfg.AddCollaborativeOwner(collUser.ID) if err := repo_model.UpdateRepoUnitConfig(ctx, actionsUnit); err != nil { ctx.ServerError("UpdateRepoUnitConfig", err) return diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index fac382582b..e8501684d4 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -6,6 +6,7 @@ package migrations import ( "context" + "errors" "fmt" "io" "strconv" @@ -989,12 +990,15 @@ func (g *GiteaLocalUploader) remapUser(ctx context.Context, source user_model.Ex func (g *GiteaLocalUploader) remapLocalUser(ctx context.Context, source user_model.ExternalUserMigrated) (int64, error) { userid, ok := g.userMap[source.GetExternalID()] if !ok { - name, err := user_model.GetUserNameByID(ctx, source.GetExternalID()) - if err != nil { + user, err := user_model.GetUserByID(ctx, source.GetExternalID()) + if errors.Is(err, util.ErrNotExist) { + g.userMap[source.GetExternalID()] = userid + return 0, nil + } else if err != nil { return 0, err } - // let's not reuse an ID when the user was deleted or has a different user name - if name != source.GetExternalName() { + // let's not reuse an ID when the user was deleted or has a different username + if !util.AsciiEqualFold(user.Name, source.GetExternalName()) { userid = 0 } else { userid = source.GetExternalID()