diff --git a/.github/workflows/release-tag-rc.yml b/.github/workflows/release-tag-rc.yml index 9d408e0f17..3b77786520 100644 --- a/.github/workflows/release-tag-rc.yml +++ b/.github/workflows/release-tag-rc.yml @@ -78,6 +78,8 @@ jobs: id: meta with: images: gitea/gitea + flavor: | + latest=false # 1.2.3-rc0 tags: | type=semver,pattern={{version}} @@ -109,6 +111,7 @@ jobs: images: gitea/gitea # each tag below will have the suffix of -rootless flavor: | + latest=false suffix=-rootless # 1.2.3-rc0 tags: | diff --git a/.github/workflows/release-tag-version.yml b/.github/workflows/release-tag-version.yml index c51760b29a..3569cb6a40 100644 --- a/.github/workflows/release-tag-version.yml +++ b/.github/workflows/release-tag-version.yml @@ -118,14 +118,13 @@ jobs: # images: gitea/gitea # # each tag below will have the suffix of -rootless # flavor: | - # suffix=-rootless + # suffix=-rootless,onlatest=true # # this will generate tags in the following format (with -rootless suffix added): # # latest # # 1 # # 1.2 # # 1.2.3 # tags: | - # type=raw,value=latest # type=semver,pattern={{major}} # type=semver,pattern={{major}}.{{minor}} # type=semver,pattern={{version}} diff --git a/cmd/doctor.go b/cmd/doctor.go index d040a3af1c..4085d37332 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/migrations" migrate_base "code.gitea.io/gitea/models/migrations/base" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/doctor" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -22,6 +23,19 @@ import ( "xorm.io/xorm" ) +// CmdDoctor represents the available doctor sub-command. +var CmdDoctor = &cli.Command{ + Name: "doctor", + Usage: "Diagnose and optionally fix problems", + Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.", + + Subcommands: []*cli.Command{ + cmdDoctorCheck, + cmdRecreateTable, + cmdDoctorConvert, + }, +} + var cmdDoctorCheck = &cli.Command{ Name: "check", Usage: "Diagnose and optionally fix problems", @@ -60,19 +74,6 @@ var cmdDoctorCheck = &cli.Command{ }, } -// CmdDoctor represents the available doctor sub-command. -var CmdDoctor = &cli.Command{ - Name: "doctor", - Usage: "Diagnose and optionally fix problems", - Description: "A command to diagnose problems with the current Gitea instance according to the given configuration. Some problems can optionally be fixed by modifying the database or data storage.", - - Subcommands: []*cli.Command{ - cmdDoctorCheck, - cmdRecreateTable, - cmdDoctorConvert, - }, -} - var cmdRecreateTable = &cli.Command{ Name: "recreate-table", Usage: "Recreate tables from XORM definitions and copy the data.", @@ -177,6 +178,7 @@ func runDoctorCheck(ctx *cli.Context) error { if ctx.IsSet("list") { w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) _, _ = w.Write([]byte("Default\tName\tTitle\n")) + doctor.SortChecks(doctor.Checks) for _, check := range doctor.Checks { if check.IsDefault { _, _ = w.Write([]byte{'*'}) @@ -192,26 +194,20 @@ func runDoctorCheck(ctx *cli.Context) error { var checks []*doctor.Check if ctx.Bool("all") { - checks = doctor.Checks + checks = make([]*doctor.Check, len(doctor.Checks)) + copy(checks, doctor.Checks) } else if ctx.IsSet("run") { addDefault := ctx.Bool("default") - names := ctx.StringSlice("run") - for i, name := range names { - names[i] = strings.ToLower(strings.TrimSpace(name)) - } - + runNamesSet := container.SetOf(ctx.StringSlice("run")...) for _, check := range doctor.Checks { - if addDefault && check.IsDefault { + if (addDefault && check.IsDefault) || runNamesSet.Contains(check.Name) { checks = append(checks, check) - continue - } - for _, name := range names { - if name == check.Name { - checks = append(checks, check) - break - } + runNamesSet.Remove(check.Name) } } + if len(runNamesSet) > 0 { + return fmt.Errorf("unknown checks: %q", strings.Join(runNamesSet.Values(), ",")) + } } else { for _, check := range doctor.Checks { if check.IsDefault { @@ -219,6 +215,5 @@ func runDoctorCheck(ctx *cli.Context) error { } } } - return doctor.RunChecks(stdCtx, colorize, ctx.Bool("fix"), checks) } diff --git a/cmd/doctor_test.go b/cmd/doctor_test.go new file mode 100644 index 0000000000..75376a567e --- /dev/null +++ b/cmd/doctor_test.go @@ -0,0 +1,33 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "context" + "testing" + + "code.gitea.io/gitea/modules/doctor" + "code.gitea.io/gitea/modules/log" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v2" +) + +func TestDoctorRun(t *testing.T) { + doctor.Register(&doctor.Check{ + Title: "Test Check", + Name: "test-check", + Run: func(ctx context.Context, logger log.Logger, autofix bool) error { return nil }, + + SkipDatabaseInitialization: true, + }) + app := cli.NewApp() + app.Commands = []*cli.Command{cmdDoctorCheck} + err := app.Run([]string{"./gitea", "check", "--run", "test-check"}) + assert.NoError(t, err) + err = app.Run([]string{"./gitea", "check", "--run", "no-such"}) + assert.ErrorContains(t, err, `unknown checks: "no-such"`) + err = app.Run([]string{"./gitea", "check", "--run", "test-check,no-such"}) + assert.ErrorContains(t, err, `unknown checks: "no-such"`) +} diff --git a/docs/content/administration/https-support.zh-cn.md b/docs/content/administration/https-support.zh-cn.md index add7906cc6..8beb06e80f 100644 --- a/docs/content/administration/https-support.zh-cn.md +++ b/docs/content/administration/https-support.zh-cn.md @@ -33,7 +33,7 @@ CERT_FILE = cert.pem KEY_FILE = key.pem ``` -请注意,如果您的证书由第三方证书颁发机构签名(即不是自签名的),则 cert.pem 应包含证书链。服务器证书必须是 cert.pem 中的第一个条目,后跟中介(如果有)。不必包含根证书,因为连接客户端必须已经拥有根证书才能建立信任关系。要了解有关配置值的更多信息,请查看 [配置备忘单](../config-cheat-sheet#server-server)。 +请注意,如果您的证书由第三方证书颁发机构签名(即不是自签名的),则 cert.pem 应包含证书链。服务器证书必须是 cert.pem 中的第一个条目,后跟中介(如果有)。不必包含根证书,因为连接客户端必须已经拥有根证书才能建立信任关系。要了解有关配置值的更多信息,请查看 [配置备忘单](administration/config-cheat-sheet#server-server)。 对于“CERT_FILE”或“KEY_FILE”字段,当文件路径是相对路径时,文件路径相对于“GITEA_CUSTOM”环境变量。它也可以是绝对路径。 diff --git a/docs/content/development/api-usage.en-us.md b/docs/content/development/api-usage.en-us.md index 465f4d380c..94dac70b88 100644 --- a/docs/content/development/api-usage.en-us.md +++ b/docs/content/development/api-usage.en-us.md @@ -19,10 +19,7 @@ menu: ## Enabling/configuring API access -By default, `ENABLE_SWAGGER` is true, and -`MAX_RESPONSE_ITEMS` is set to 50. See [Config Cheat -Sheet](administration/config-cheat-sheet.md) for more -information. +By default, `ENABLE_SWAGGER` is true, and `MAX_RESPONSE_ITEMS` is set to 50. See [Config Cheat Sheet](administration/config-cheat-sheet.md) for more information. ## Authentication diff --git a/docs/content/development/api-usage.zh-cn.md b/docs/content/development/api-usage.zh-cn.md index c7eeceeb7d..96c1997294 100644 --- a/docs/content/development/api-usage.zh-cn.md +++ b/docs/content/development/api-usage.zh-cn.md @@ -19,8 +19,7 @@ menu: ## 开启/配置 API 访问 -通常情况下, `ENABLE_SWAGGER` 默认开启并且参数 `MAX_RESPONSE_ITEMS` 默认为 50。您可以从 [Config Cheat -Sheet](administration/config-cheat-sheet.md) 中获取更多配置相关信息。 +通常情况下, `ENABLE_SWAGGER` 默认开启并且参数 `MAX_RESPONSE_ITEMS` 默认为 50。您可以从 [Config Cheat Sheet](administration/config-cheat-sheet.md) 中获取更多配置相关信息。 ## 通过 API 认证 diff --git a/docs/content/installation/from-binary.zh-cn.md b/docs/content/installation/from-binary.zh-cn.md index 56c7cc0ae0..216a6be51e 100644 --- a/docs/content/installation/from-binary.zh-cn.md +++ b/docs/content/installation/from-binary.zh-cn.md @@ -117,7 +117,7 @@ chmod 770 /etc/gitea - 使用 `gitea generate secret` 创建 `SECRET_KEY` 和 `INTERNAL_TOKEN` - 提供所有必要的密钥 -详情参考 [命令行文档](/zh-cn/command-line/) 中有关 `gitea generate secret` 的内容。 +详情参考 [命令行文档](administration/command-line.md) 中有关 `gitea generate secret` 的内容。 ### 配置 Gitea 工作路径 @@ -209,6 +209,6 @@ remote: ./hooks/pre-receive.d/gitea: line 2: [...]: No such file or directory 如果您没有使用 Gitea 内置的 SSH 服务器,您还需要通过在管理选项中运行任务 `Update the '.ssh/authorized_keys' file with Gitea SSH keys.` 来重新编写授权密钥文件。 -> 更多经验总结,请参考英文版 [Troubleshooting](/en-us/install-from-binary/#troubleshooting) +> 更多经验总结,请参考英文版 [Troubleshooting](https://docs.gitea.com/installation/install-from-binary#troubleshooting) 如果从本页中没有找到你需要的内容,请访问 [帮助页面](help/support.md) diff --git a/docs/content/installation/from-source.zh-cn.md b/docs/content/installation/from-source.zh-cn.md index 74f76652db..8e2d8b4eea 100644 --- a/docs/content/installation/from-source.zh-cn.md +++ b/docs/content/installation/from-source.zh-cn.md @@ -64,7 +64,7 @@ git checkout v@version@ # or git checkout pr-xyz - `go` @minGoVersion@ 或更高版本,请参阅 [这里](https://golang.org/dl/) - `node` @minNodeVersion@ 或更高版本,并且安装 `npm`, 请参阅 [这里](https://nodejs.org/zh-cn/download/) -- `make`, 请参阅 [这里](/zh-cn/hacking-on-gitea/) +- `make`, 请参阅 [这里](development/hacking-on-gitea.md) 为了尽可能简化编译过程,提供了各种 [make任务](https://github.com/go-gitea/gitea/blob/main/Makefile)。 diff --git a/models/db/context.go b/models/db/context.go index 521857fae8..9f72b43555 100644 --- a/models/db/context.go +++ b/models/db/context.go @@ -178,6 +178,15 @@ func GetByBean(ctx context.Context, bean any) (bool, error) { return GetEngine(ctx).Get(bean) } +func Exist[T any](ctx context.Context, cond builder.Cond) (bool, error) { + if !cond.IsValid() { + return false, ErrConditionRequired{} + } + + var bean T + return GetEngine(ctx).Where(cond).NoAutoCondition().Exist(&bean) +} + // DeleteByBean deletes all records according non-empty fields of the bean as conditions. func DeleteByBean(ctx context.Context, bean any) (int64, error) { return GetEngine(ctx).Delete(bean) diff --git a/models/db/error.go b/models/db/error.go index 665e970e17..f601a15c01 100644 --- a/models/db/error.go +++ b/models/db/error.go @@ -72,3 +72,21 @@ func (err ErrNotExist) Error() string { func (err ErrNotExist) Unwrap() error { return util.ErrNotExist } + +// ErrConditionRequired represents an error which require condition. +type ErrConditionRequired struct{} + +// IsErrConditionRequired checks if an error is an ErrConditionRequired +func IsErrConditionRequired(err error) bool { + _, ok := err.(ErrConditionRequired) + return ok +} + +func (err ErrConditionRequired) Error() string { + return "condition is required" +} + +// Unwrap unwraps this as a ErrNotExist err +func (err ErrConditionRequired) Unwrap() error { + return util.ErrInvalidArgument +} diff --git a/models/git/branch.go b/models/git/branch.go index 6d50fb9fb6..ffd1d7ed16 100644 --- a/models/git/branch.go +++ b/models/git/branch.go @@ -205,10 +205,9 @@ func DeleteBranches(ctx context.Context, repoID, doerID int64, branchIDs []int64 }) } -// UpdateBranch updates the branch information in the database. If the branch exist, it will update latest commit of this branch information -// If it doest not exist, insert a new record into database -func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string, commit *git.Commit) error { - cnt, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branchName). +// UpdateBranch updates the branch information in the database. +func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string, commit *git.Commit) (int64, error) { + return db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branchName). Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted, updated_unix"). Update(&Branch{ CommitID: commit.ID.String(), @@ -217,21 +216,6 @@ func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()), IsDeleted: false, }) - if err != nil { - return err - } - if cnt > 0 { - return nil - } - - return db.Insert(ctx, &Branch{ - RepoID: repoID, - Name: branchName, - CommitID: commit.ID.String(), - CommitMessage: commit.Summary(), - PusherID: pusherID, - CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()), - }) } // AddDeletedBranch adds a deleted branch to the database @@ -308,6 +292,17 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str sess := db.GetEngine(ctx) + var branch Branch + exist, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repo.ID, from).Get(&branch) + if err != nil { + return err + } else if !exist || branch.IsDeleted { + return ErrBranchNotExist{ + RepoID: repo.ID, + BranchName: from, + } + } + // 1. update branch in database if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{ Name: to, diff --git a/models/git/branch_list.go b/models/git/branch_list.go index b5c1301a1d..2efe495264 100644 --- a/models/git/branch_list.go +++ b/models/git/branch_list.go @@ -73,7 +73,7 @@ type FindBranchOptions struct { Keyword string } -func (opts *FindBranchOptions) Cond() builder.Cond { +func (opts FindBranchOptions) ToConds() builder.Cond { cond := builder.NewCond() if opts.RepoID > 0 { cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) @@ -92,7 +92,7 @@ func (opts *FindBranchOptions) Cond() builder.Cond { } func CountBranches(ctx context.Context, opts FindBranchOptions) (int64, error) { - return db.GetEngine(ctx).Where(opts.Cond()).Count(&Branch{}) + return db.GetEngine(ctx).Where(opts.ToConds()).Count(&Branch{}) } func orderByBranches(sess *xorm.Session, opts FindBranchOptions) *xorm.Session { @@ -108,7 +108,7 @@ func orderByBranches(sess *xorm.Session, opts FindBranchOptions) *xorm.Session { } func FindBranches(ctx context.Context, opts FindBranchOptions) (BranchList, error) { - sess := db.GetEngine(ctx).Where(opts.Cond()) + sess := db.GetEngine(ctx).Where(opts.ToConds()) if opts.PageSize > 0 && !opts.IsListAll() { sess = db.SetSessionPagination(sess, &opts.ListOptions) } @@ -119,7 +119,7 @@ func FindBranches(ctx context.Context, opts FindBranchOptions) (BranchList, erro } func FindBranchNames(ctx context.Context, opts FindBranchOptions) ([]string, error) { - sess := db.GetEngine(ctx).Select("name").Where(opts.Cond()) + sess := db.GetEngine(ctx).Select("name").Where(opts.ToConds()) if opts.PageSize > 0 && !opts.IsListAll() { sess = db.SetSessionPagination(sess, &opts.ListOptions) } diff --git a/models/git/branch_test.go b/models/git/branch_test.go index ba69026927..07b243e5e6 100644 --- a/models/git/branch_test.go +++ b/models/git/branch_test.go @@ -37,7 +37,7 @@ func TestAddDeletedBranch(t *testing.T) { }, } - err := git_model.UpdateBranch(db.DefaultContext, repo.ID, secondBranch.PusherID, secondBranch.Name, commit) + _, err := git_model.UpdateBranch(db.DefaultContext, repo.ID, secondBranch.PusherID, secondBranch.Name, commit) assert.NoError(t, err) } diff --git a/models/repo/repo.go b/models/repo/repo.go index a2459ad564..ed8f336ca0 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -47,6 +47,14 @@ func (err ErrUserDoesNotHaveAccessToRepo) Unwrap() error { return util.ErrPermissionDenied } +type ErrRepoIsArchived struct { + Repo *Repository +} + +func (err ErrRepoIsArchived) Error() string { + return fmt.Sprintf("%s is archived", err.Repo.LogString()) +} + var ( reservedRepoNames = []string{".", "..", "-"} reservedRepoPatterns = []string{"*.git", "*.wiki", "*.rss", "*.atom"} @@ -667,6 +675,14 @@ func (repo *Repository) GetTrustModel() TrustModelType { return trustModel } +// MustNotBeArchived returns ErrRepoIsArchived if the repo is archived +func (repo *Repository) MustNotBeArchived() error { + if repo.IsArchived { + return ErrRepoIsArchived{Repo: repo} + } + return nil +} + // __________ .__ __ // \______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__. // | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | | diff --git a/models/user/user_system.go b/models/user/user_system.go index f54f4e3ffb..e0efa9d2ee 100644 --- a/models/user/user_system.go +++ b/models/user/user_system.go @@ -36,6 +36,7 @@ func NewReplaceUser(name string) *User { } const ( + GhostUserID = -1 ActionsUserID = -2 ActionsUserName = "gitea-actions" ActionsFullName = "Gitea Actions" diff --git a/modules/doctor/doctor.go b/modules/doctor/doctor.go index ceee322852..559f8e06da 100644 --- a/modules/doctor/doctor.go +++ b/modules/doctor/doctor.go @@ -79,6 +79,7 @@ var Checks []*Check // RunChecks runs the doctor checks for the provided list func RunChecks(ctx context.Context, colorize, autofix bool, checks []*Check) error { + SortChecks(checks) // the checks output logs by a special logger, they do not use the default logger logger := log.BaseLoggerToGeneralLogger(&doctorCheckLogger{colorize: colorize}) loggerStep := log.BaseLoggerToGeneralLogger(&doctorCheckStepLogger{colorize: colorize}) @@ -104,20 +105,23 @@ func RunChecks(ctx context.Context, colorize, autofix bool, checks []*Check) err logger.Info("OK") } } - logger.Info("\nAll done.") + logger.Info("\nAll done (checks: %d).", len(checks)) return nil } // Register registers a command with the list func Register(command *Check) { Checks = append(Checks, command) - sort.SliceStable(Checks, func(i, j int) bool { - if Checks[i].Priority == Checks[j].Priority { - return Checks[i].Name < Checks[j].Name +} + +func SortChecks(checks []*Check) { + sort.SliceStable(checks, func(i, j int) bool { + if checks[i].Priority == checks[j].Priority { + return checks[i].Name < checks[j].Name } - if Checks[i].Priority == 0 { + if checks[i].Priority == 0 { return false } - return Checks[i].Priority < Checks[j].Priority + return checks[i].Priority < checks[j].Priority }) } diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index c851525c0f..922f15f749 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -262,12 +262,11 @@ func CreateBranch(ctx *context.APIContext) { } } - err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName) + err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, oldCommit.ID.String(), opt.BranchName) if err != nil { if git_model.IsErrBranchNotExist(err) { ctx.Error(http.StatusNotFound, "", "The old branch does not exist") - } - if models.IsErrTagAlreadyExists(err) { + } else if models.IsErrTagAlreadyExists(err) { ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.") } else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { ctx.Error(http.StatusConflict, "", "The branch already exists.") diff --git a/routers/api/v1/user/user.go b/routers/api/v1/user/user.go index bc31e2f130..d469cba626 100644 --- a/routers/api/v1/user/user.go +++ b/routers/api/v1/user/user.go @@ -94,27 +94,41 @@ func Search(ctx *context.APIContext) { listOptions := utils.GetListOptions(ctx) - users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{ - Actor: ctx.Doer, - Keyword: ctx.FormTrim("q"), - UID: ctx.FormInt64("uid"), - Type: user_model.UserTypeIndividual, - ListOptions: listOptions, - // DCS Customizations - RepoLanguages: ctx.FormStrings("lang"), - RepoSubjects: ctx.FormStrings("subject"), - RepoMetadataTypes: ctx.FormStrings("metadataType"), - RepoFlavorTypes: ctx.FormStrings("flavorType"), - RepoFlavors: ctx.FormStrings("flavor"), - RepoLanguageIsGL: ctx.FormOptionalBool("is_gl"), - // END DCS Customizations - }) - if err != nil { - ctx.JSON(http.StatusInternalServerError, map[string]any{ - "ok": false, - "error": err.Error(), + uid := ctx.FormInt64("uid") + var users []*user_model.User + var maxResults int64 + var err error + + switch uid { + case user_model.GhostUserID: + maxResults = 1 + users = []*user_model.User{user_model.NewGhostUser()} + case user_model.ActionsUserID: + maxResults = 1 + users = []*user_model.User{user_model.NewActionsUser()} + default: + users, maxResults, err = user_model.SearchUsers(ctx, &user_model.SearchUserOptions{ + Actor: ctx.Doer, + Keyword: ctx.FormTrim("q"), + UID: uid, + Type: user_model.UserTypeIndividual, + ListOptions: listOptions, + // DCS Customizations + RepoLanguages: ctx.FormStrings("lang"), + RepoSubjects: ctx.FormStrings("subject"), + RepoMetadataTypes: ctx.FormStrings("metadataType"), + RepoFlavorTypes: ctx.FormStrings("flavorType"), + RepoFlavors: ctx.FormStrings("flavor"), + RepoLanguageIsGL: ctx.FormOptionalBool("is_gl"), + // END DCS Customizations }) - return + if err != nil { + ctx.JSON(http.StatusInternalServerError, map[string]any{ + "ok": false, + "error": err.Error(), + }) + return + } } ctx.SetLinkHeader(int(maxResults), listOptions.PageSize) diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index e0e27fd482..6afe4ad68a 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -191,9 +191,9 @@ func CreateBranch(ctx *context.Context) { } err = release_service.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, target, form.NewBranchName, "") } else if ctx.Repo.IsViewBranch { - err = repo_service.CreateNewBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.BranchName, form.NewBranchName) + err = repo_service.CreateNewBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.BranchName, form.NewBranchName) } else { - err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.CommitID, form.NewBranchName) + err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.CommitID, form.NewBranchName) } if err != nil { if models.IsErrProtectedTagName(err) { diff --git a/routers/web/repo/issue_content_history.go b/routers/web/repo/issue_content_history.go index 473ab260f3..0f376db145 100644 --- a/routers/web/repo/issue_content_history.go +++ b/routers/web/repo/issue_content_history.go @@ -193,15 +193,29 @@ func SoftDeleteContentHistory(ctx *context.Context) { var comment *issues_model.Comment var history *issues_model.ContentHistory var err error + + if history, err = issues_model.GetIssueContentHistoryByID(ctx, historyID); err != nil { + log.Error("can not get issue content history %v. err=%v", historyID, err) + return + } + if history.IssueID != issue.ID { + ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{}) + return + } if commentID != 0 { + if history.CommentID != commentID { + ctx.NotFound("CompareCommentID", issues_model.ErrCommentNotExist{}) + return + } + if comment, err = issues_model.GetCommentByID(ctx, commentID); err != nil { log.Error("can not get comment for issue content history %v. err=%v", historyID, err) return } - } - if history, err = issues_model.GetIssueContentHistoryByID(ctx, historyID); err != nil { - log.Error("can not get issue content history %v. err=%v", historyID, err) - return + if comment.IssueID != issue.ID { + ctx.NotFound("CompareIssueID", issues_model.ErrCommentNotExist{}) + return + } } canSoftDelete := canSoftDeleteContentHistory(ctx, issue, comment, history) diff --git a/routers/web/repo/issue_pin.go b/routers/web/repo/issue_pin.go index f853f72335..9f334129f9 100644 --- a/routers/web/repo/issue_pin.go +++ b/routers/web/repo/issue_pin.go @@ -90,6 +90,12 @@ func IssuePinMove(ctx *context.Context) { return } + if issue.RepoID != ctx.Repo.Repository.ID { + ctx.Status(http.StatusNotFound) + log.Error("Issue does not belong to this repository") + return + } + err = issue.MovePin(ctx, form.Position) if err != nil { ctx.Status(http.StatusInternalServerError) diff --git a/services/repository/branch.go b/services/repository/branch.go index 011dc5568e..d56a0660c6 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/queue" repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" notify_service "code.gitea.io/gitea/services/notify" files_service "code.gitea.io/gitea/services/repository/files" @@ -28,30 +29,13 @@ import ( ) // CreateNewBranch creates a new repository branch -func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldBranchName, branchName string) (err error) { - // Check if branch name can be used - if err := checkBranchName(ctx, repo, branchName); err != nil { +func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, oldBranchName, branchName string) (err error) { + branch, err := git_model.GetBranch(ctx, repo.ID, oldBranchName) + if err != nil { return err } - if !git.IsBranchExist(ctx, repo.RepoPath(), oldBranchName) { - return git_model.ErrBranchNotExist{ - BranchName: oldBranchName, - } - } - - if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{ - Remote: repo.RepoPath(), - Branch: fmt.Sprintf("%s%s:%s%s", git.BranchPrefix, oldBranchName, git.BranchPrefix, branchName), - Env: repo_module.PushingEnvironment(doer, repo), - }); err != nil { - if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { - return err - } - return fmt.Errorf("push: %w", err) - } - - return nil + return CreateNewBranchFromCommit(ctx, doer, repo, gitRepo, branch.CommitID, branchName) } // Branch contains the branch information @@ -244,25 +228,81 @@ func checkBranchName(ctx context.Context, repo *repo_model.Repository, name stri return err } +// syncBranchToDB sync the branch information in the database. It will try to update the branch first, +// if updated success with affect records > 0, then all are done. Because that means the branch has been in the database. +// If no record is affected, that means the branch does not exist in database. So there are two possibilities. +// One is this is a new branch, then we just need to insert the record. Another is the branches haven't been synced, +// then we need to sync all the branches into database. +func syncBranchToDB(ctx context.Context, repoID, pusherID int64, branchName string, commit *git.Commit) error { + cnt, err := git_model.UpdateBranch(ctx, repoID, pusherID, branchName, commit) + if err != nil { + return fmt.Errorf("git_model.UpdateBranch %d:%s failed: %v", repoID, branchName, err) + } + if cnt > 0 { // This means branch does exist, so it's a normal update. It also means the branch has been synced. + return nil + } + + // if user haven't visit UI but directly push to a branch after upgrading from 1.20 -> 1.21, + // we cannot simply insert the branch but need to check we have branches or not + hasBranch, err := db.Exist[git_model.Branch](ctx, git_model.FindBranchOptions{ + RepoID: repoID, + IsDeletedBranch: util.OptionalBoolFalse, + }.ToConds()) + if err != nil { + return err + } + if !hasBranch { + if _, err = repo_module.SyncRepoBranches(ctx, repoID, pusherID); err != nil { + return fmt.Errorf("repo_module.SyncRepoBranches %d:%s failed: %v", repoID, branchName, err) + } + return nil + } + + // if database have branches but not this branch, it means this is a new branch + return db.Insert(ctx, &git_model.Branch{ + RepoID: repoID, + Name: branchName, + CommitID: commit.ID.String(), + CommitMessage: commit.Summary(), + PusherID: pusherID, + CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()), + }) +} + // CreateNewBranchFromCommit creates a new repository branch -func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, commit, branchName string) (err error) { +func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, commitID, branchName string) (err error) { + err = repo.MustNotBeArchived() + if err != nil { + return err + } + // Check if branch name can be used if err := checkBranchName(ctx, repo, branchName); err != nil { return err } - if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{ - Remote: repo.RepoPath(), - Branch: fmt.Sprintf("%s:%s%s", commit, git.BranchPrefix, branchName), - Env: repo_module.PushingEnvironment(doer, repo), - }); err != nil { - if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { + return db.WithTx(ctx, func(ctx context.Context) error { + commit, err := gitRepo.GetCommit(commitID) + if err != nil { + return err + } + // database operation should be done before git operation so that we can rollback if git operation failed + if err := syncBranchToDB(ctx, repo.ID, doer.ID, branchName, commit); err != nil { return err } - return fmt.Errorf("push: %w", err) - } - return nil + if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{ + Remote: repo.RepoPath(), + Branch: fmt.Sprintf("%s:%s%s", commitID, git.BranchPrefix, branchName), + Env: repo_module.PushingEnvironment(doer, repo), + }); err != nil { + if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { + return err + } + return fmt.Errorf("push: %w", err) + } + return nil + }) } // RenameBranch rename a branch diff --git a/services/repository/push.go b/services/repository/push.go index 97da45f52b..90aac95323 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -257,7 +257,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { commits.Commits = commits.Commits[:setting.UI.FeedMaxCommitNum] } - if err = git_model.UpdateBranch(ctx, repo.ID, opts.PusherID, branch, newCommit); err != nil { + if err = syncBranchToDB(ctx, repo.ID, opts.PusherID, branch, newCommit); err != nil { return fmt.Errorf("git_model.UpdateBranch %s:%s failed: %v", repo.FullName(), branch, err) } diff --git a/tests/integration/api_repo_get_contents_list_test.go b/tests/integration/api_repo_get_contents_list_test.go index f3a5159115..7874eddfd4 100644 --- a/tests/integration/api_repo_get_contents_list_test.go +++ b/tests/integration/api_repo_get_contents_list_test.go @@ -70,15 +70,16 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { session = loginUser(t, user4.Name) token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - // Make a new branch in repo1 - newBranch := "test_branch" - err := repo_service.CreateNewBranch(git.DefaultContext, user2, repo1, repo1.DefaultBranch, newBranch) - assert.NoError(t, err) // Get the commit ID of the default branch gitRepo, err := git.OpenRepository(git.DefaultContext, repo1.RepoPath()) assert.NoError(t, err) defer gitRepo.Close() + // Make a new branch in repo1 + newBranch := "test_branch" + err = repo_service.CreateNewBranch(git.DefaultContext, user2, repo1, gitRepo, repo1.DefaultBranch, newBranch) + assert.NoError(t, err) + commitID, _ := gitRepo.GetBranchCommitID(repo1.DefaultBranch) // Make a new tag in repo1 newTag := "test_tag" diff --git a/tests/integration/api_repo_get_contents_test.go b/tests/integration/api_repo_get_contents_test.go index 709bbe082a..1d708a4cdb 100644 --- a/tests/integration/api_repo_get_contents_test.go +++ b/tests/integration/api_repo_get_contents_test.go @@ -72,15 +72,16 @@ func testAPIGetContents(t *testing.T, u *url.URL) { session = loginUser(t, user4.Name) token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) - // Make a new branch in repo1 - newBranch := "test_branch" - err := repo_service.CreateNewBranch(git.DefaultContext, user2, repo1, repo1.DefaultBranch, newBranch) - assert.NoError(t, err) // Get the commit ID of the default branch gitRepo, err := git.OpenRepository(git.DefaultContext, repo1.RepoPath()) assert.NoError(t, err) defer gitRepo.Close() + // Make a new branch in repo1 + newBranch := "test_branch" + err = repo_service.CreateNewBranch(git.DefaultContext, user2, repo1, gitRepo, repo1.DefaultBranch, newBranch) + assert.NoError(t, err) + commitID, err := gitRepo.GetBranchCommitID(repo1.DefaultBranch) assert.NoError(t, err) // Make a new tag in repo1 diff --git a/tests/integration/api_user_search_test.go b/tests/integration/api_user_search_test.go index c5b202b319..ddfeb25234 100644 --- a/tests/integration/api_user_search_test.go +++ b/tests/integration/api_user_search_test.go @@ -56,6 +56,28 @@ func TestAPIUserSearchNotLoggedIn(t *testing.T) { } } +func TestAPIUserSearchSystemUsers(t *testing.T) { + defer tests.PrepareTestEnv(t)() + for _, systemUser := range []*user_model.User{ + user_model.NewGhostUser(), + user_model.NewActionsUser(), + } { + t.Run(systemUser.Name, func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/users/search?uid=%d", systemUser.ID) + resp := MakeRequest(t, req, http.StatusOK) + + var results SearchResults + DecodeJSON(t, resp, &results) + assert.NotEmpty(t, results.Data) + if assert.EqualValues(t, 1, len(results.Data)) { + user := results.Data[0] + assert.EqualValues(t, user.UserName, systemUser.Name) + assert.EqualValues(t, user.ID, systemUser.ID) + } + }) + } +} + func TestAPIUserSearchAdminLoggedInUserHidden(t *testing.T) { defer tests.PrepareTestEnv(t)() adminUsername := "user1"