diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 3e4c6ea50b..cd5a0735b4 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -310,7 +310,7 @@ rules: jquery/no-merge: [2] jquery/no-param: [2] jquery/no-parent: [0] - jquery/no-parents: [0] + jquery/no-parents: [2] jquery/no-parse-html: [2] jquery/no-prop: [2] jquery/no-proxy: [2] @@ -319,8 +319,8 @@ rules: jquery/no-show: [2] jquery/no-size: [2] jquery/no-sizzle: [2] - jquery/no-slide: [0] - jquery/no-submit: [0] + jquery/no-slide: [2] + jquery/no-submit: [2] jquery/no-text: [0] jquery/no-toggle: [2] jquery/no-trigger: [0] @@ -458,7 +458,7 @@ rules: no-jquery/no-other-utils: [2] no-jquery/no-param: [2] no-jquery/no-parent: [0] - no-jquery/no-parents: [0] + no-jquery/no-parents: [2] no-jquery/no-parse-html-literal: [0] no-jquery/no-parse-html: [2] no-jquery/no-parse-json: [2] diff --git a/.golangci.yml b/.golangci.yml index 27fee20f75..238f6cb837 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,13 +1,14 @@ linters: + enable-all: false + disable-all: true + fast: false enable: - bidichk - # - deadcode # deprecated - https://github.com/golangci/golangci-lint/issues/1841 - depguard - dupl - errcheck - forbidigo - gocritic - # - gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time. - gofmt - gofumpt - gosimple @@ -17,20 +18,18 @@ linters: - nolintlint - revive - staticcheck - # - structcheck # deprecated - https://github.com/golangci/golangci-lint/issues/1841 - stylecheck - typecheck - unconvert - unused - # - varcheck # deprecated - https://github.com/golangci/golangci-lint/issues/1841 - wastedassign - enable-all: false - disable-all: true - fast: false run: timeout: 10m +output: + sort-results: true + linters-settings: stylecheck: checks: ["all", "-ST1005", "-ST1003"] @@ -47,27 +46,37 @@ linters-settings: errorCode: 1 warningCode: 1 rules: + - name: atomic + - name: bare-return - name: blank-imports + - name: constant-logical-expr - name: context-as-argument - name: context-keys-type - name: dot-imports + - name: duplicated-imports + - name: empty-lines + - name: error-naming - name: error-return - name: error-strings - - name: error-naming + - name: errorf - name: exported + - name: identical-branches - name: if-return - name: increment-decrement - - name: var-naming - - name: var-declaration + - name: indent-error-flow + - name: modifies-value-receiver - name: package-comments - name: range - name: receiver-naming + - name: redefines-builtin-id + - name: string-of-int + - name: superfluous-else - name: time-naming + - name: unconditional-recursion - name: unexported-return - - name: indent-error-flow - - name: errorf - - name: duplicated-imports - - name: modifies-value-receiver + - name: unreachable-code + - name: var-declaration + - name: var-naming gofumpt: extra-rules: true depguard: @@ -93,8 +102,8 @@ issues: max-issues-per-linter: 0 max-same-issues: 0 exclude-dirs: [node_modules, public, web_src] + exclude-case-sensitive: true exclude-rules: - # Exclude some linters from running on tests files. - path: _test\.go linters: - gocyclo @@ -112,19 +121,19 @@ issues: - path: cmd linters: - forbidigo - - linters: + - text: "webhook" + linters: - dupl - text: "webhook" - - linters: + - text: "`ID' should not be capitalized" + linters: - gocritic - text: "`ID' should not be capitalized" - - linters: + - text: "swagger" + linters: - unused - deadcode - text: "swagger" - - linters: + - text: "argument x is overwritten before first use" + linters: - staticcheck - text: "argument x is overwritten before first use" - text: "commentFormatting: put a space between `//` and comment text" linters: - gocritic diff --git a/cmd/dump.go b/cmd/dump.go index da0a51d9ce..ececc80f72 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -87,6 +87,10 @@ var CmdDump = &cli.Command{ Name: "skip-index", Usage: "Skip bleve index data", }, + &cli.BoolFlag{ + Name: "skip-db", + Usage: "Skip database", + }, &cli.StringFlag{ Name: "type", Usage: fmt.Sprintf(`Dump output format, default to "zip", supported types: %s`, strings.Join(dump.SupportedOutputTypes, ", ")), @@ -185,35 +189,41 @@ func runDump(ctx *cli.Context) error { } } - tmpDir := ctx.String("tempdir") - if _, err := os.Stat(tmpDir); os.IsNotExist(err) { - fatal("Path does not exist: %s", tmpDir) - } - - dbDump, err := os.CreateTemp(tmpDir, "gitea-db.sql") - if err != nil { - fatal("Failed to create tmp file: %v", err) - } - defer func() { - _ = dbDump.Close() - if err := util.Remove(dbDump.Name()); err != nil { - log.Warn("Unable to remove temporary file: %s: Error: %v", dbDump.Name(), err) - } - }() - - targetDBType := ctx.String("database") - if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() { - log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType) + if ctx.Bool("skip-db") { + // Ensure that we don't dump the database file that may reside in setting.AppDataPath or elsewhere. + dumper.GlobalExcludeAbsPath(setting.Database.Path) + log.Info("Skipping database") } else { - log.Info("Dumping database...") - } + tmpDir := ctx.String("tempdir") + if _, err := os.Stat(tmpDir); os.IsNotExist(err) { + fatal("Path does not exist: %s", tmpDir) + } - if err := db.DumpDatabase(dbDump.Name(), targetDBType); err != nil { - fatal("Failed to dump database: %v", err) - } + dbDump, err := os.CreateTemp(tmpDir, "gitea-db.sql") + if err != nil { + fatal("Failed to create tmp file: %v", err) + } + defer func() { + _ = dbDump.Close() + if err := util.Remove(dbDump.Name()); err != nil { + log.Warn("Unable to remove temporary file: %s: Error: %v", dbDump.Name(), err) + } + }() - if err = dumper.AddFile("gitea-db.sql", dbDump.Name()); err != nil { - fatal("Failed to include gitea-db.sql: %v", err) + targetDBType := ctx.String("database") + if len(targetDBType) > 0 && targetDBType != setting.Database.Type.String() { + log.Info("Dumping database %s => %s...", setting.Database.Type, targetDBType) + } else { + log.Info("Dumping database...") + } + + if err := db.DumpDatabase(dbDump.Name(), targetDBType); err != nil { + fatal("Failed to dump database: %v", err) + } + + if err = dumper.AddFile("gitea-db.sql", dbDump.Name()); err != nil { + fatal("Failed to include gitea-db.sql: %v", err) + } } log.Info("Adding custom configuration file from %s", setting.CustomConf) diff --git a/cmd/hook.go b/cmd/hook.go index c04591d79e..2a9c25add5 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -465,7 +465,7 @@ func hookPrintResult(output, isCreate bool, branch, url string) { fmt.Fprintf(os.Stderr, " %s\n", url) } fmt.Fprintln(os.Stderr, "") - os.Stderr.Sync() + _ = os.Stderr.Sync() } func pushOptions() map[string]string { diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index b4e330184e..62db26fb02 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1231,7 +1231,8 @@ LEVEL = Info ;DEFAULT_THEME = gitea-auto ;; ;; All available themes. Allow users select personalized themes regardless of the value of `DEFAULT_THEME`. -;THEMES = gitea-auto,gitea-light,gitea-dark +;; Leave it empty to allow users to select any theme from "{CustomPath}/public/assets/css/theme-*.css" +;THEMES = ;; ;; All available reactions users can choose on issues/prs and comments. ;; Values can be emoji alias (:smile:) or a unicode emoji. @@ -1557,8 +1558,8 @@ LEVEL = Info ;; email = use the username part of the email attribute ;; Note: `nickname`, `preferred_username` and `email` options will normalize input strings using the following criteria: ;; - diacritics are removed -;; - the characters in the set `['´\x60]` are removed -;; - the characters in the set `[\s~+]` are replaced with `-` +;; - the characters in the set ['´`] are removed +;; - the characters in the set [\s~+] are replaced with "-" ;USERNAME = nickname ;; ;; Update avatar if available from oauth2 provider. diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index cab141779a..7bf23c9b99 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -214,10 +214,9 @@ The following configuration set `Content-Type: application/vnd.android.package-a - `SITEMAP_PAGING_NUM`: **20**: Number of items that are displayed in a single subsitemap. - `GRAPH_MAX_COMMIT_NUM`: **100**: Number of maximum commits shown in the commit graph. - `CODE_COMMENT_LINES`: **4**: Number of line of codes shown for a code comment. -- `DEFAULT_THEME`: **gitea-auto**: \[gitea-auto, gitea-light, gitea-dark\]: Set the default theme for the Gitea installation. +- `DEFAULT_THEME`: **gitea-auto**: Set the default theme for the Gitea installation, custom themes could be provided by "{CustomPath}/public/assets/css/theme-*.css". - `SHOW_USER_EMAIL`: **true**: Whether the email of the user should be shown in the Explore Users page. -- `THEMES`: **gitea-auto,gitea-light,gitea-dark**: All available themes. Allow users select personalized themes. - regardless of the value of `DEFAULT_THEME`. +- `THEMES`: **_empty_**: All available themes by "{CustomPath}/public/assets/css/theme-*.css". Allow users select personalized themes. - `MAX_DISPLAY_FILE_SIZE`: **8388608**: Max size of files to be displayed (default is 8MiB) - `AMBIGUOUS_UNICODE_DETECTION`: **true**: Detect ambiguous unicode characters in file contents and show warnings on the UI - `REACTIONS`: All available reactions users can choose on issues/prs and comments @@ -613,7 +612,7 @@ And the following unique queues: - `email` - use the username part of the email attribute - Note: `nickname`, `preferred_username` and `email` options will normalize input strings using the following criteria: - diacritics are removed - - the characters in the set `['´\x60]` are removed + - the characters in the set ```['´`]``` are removed - the characters in the set `[\s~+]` are replaced with `-` - `UPDATE_AVATAR`: **false**: Update avatar if available from oauth2 provider. Update will be performed on each login. - `ACCOUNT_LINKING`: **login**: How to handle if an account / email already exists: diff --git a/docs/content/administration/config-cheat-sheet.zh-cn.md b/docs/content/administration/config-cheat-sheet.zh-cn.md index e4945dd1c1..0d08a5e51b 100644 --- a/docs/content/administration/config-cheat-sheet.zh-cn.md +++ b/docs/content/administration/config-cheat-sheet.zh-cn.md @@ -212,10 +212,9 @@ menu: - `SITEMAP_PAGING_NUM`: **20**: 在单个子SiteMap中显示的项数。 - `GRAPH_MAX_COMMIT_NUM`: **100**: 提交图中显示的最大commit数量。 - `CODE_COMMENT_LINES`: **4**: 在代码评论中能够显示的最大代码行数。 -- `DEFAULT_THEME`: **gitea-auto**: \[gitea-auto, gitea-light, gitea-dark\]: 在Gitea安装时候设置的默认主题。 +- `DEFAULT_THEME`: **gitea-auto**: 在Gitea安装时候设置的默认主题,自定义的主题可以通过 "{CustomPath}/public/assets/css/theme-*.css" 提供。 - `SHOW_USER_EMAIL`: **true**: 用户的电子邮件是否应该显示在`Explore Users`页面中。 -- `THEMES`: **gitea-auto,gitea-light,gitea-dark**: 所有可用的主题。允许用户选择个性化的主题, - 而不受DEFAULT_THEME 值的影响。 +- `THEMES`: **_empty_**: 所有可用的主题(由 "{CustomPath}/public/assets/css/theme-*.css" 提供)。允许用户选择个性化的主题, - `MAX_DISPLAY_FILE_SIZE`: **8388608**: 能够显示文件的最大大小(默认为8MiB)。 - `REACTIONS`: 用户可以在问题(Issue)、Pull Request(PR)以及评论中选择的所有可选的反应。 这些值可以是表情符号别名(例如::smile:)或Unicode表情符号。 diff --git a/docs/content/administration/customizing-gitea.en-us.md b/docs/content/administration/customizing-gitea.en-us.md index 7efddb2824..8475f6d131 100644 --- a/docs/content/administration/customizing-gitea.en-us.md +++ b/docs/content/administration/customizing-gitea.en-us.md @@ -381,7 +381,7 @@ To make a custom theme available to all users: 1. Add a CSS file to `$GITEA_CUSTOM/public/assets/css/theme-.css`. The value of `$GITEA_CUSTOM` of your instance can be queried by calling `gitea help` and looking up the value of "CustomPath". -2. Add `` to the comma-separated list of setting `THEMES` in `app.ini` +2. Add `` to the comma-separated list of setting `THEMES` in `app.ini`, or leave `THEMES` empty to allow all themes. Community themes are listed in [gitea/awesome-gitea#themes](https://gitea.com/gitea/awesome-gitea#themes). diff --git a/docs/content/help/faq.en-us.md b/docs/content/help/faq.en-us.md index b3b0980125..ba39ec83b0 100644 --- a/docs/content/help/faq.en-us.md +++ b/docs/content/help/faq.en-us.md @@ -178,17 +178,6 @@ At some point, a customer or third party needs access to a specific repo and onl Use [Fail2Ban](administration/fail2ban-setup.md) to monitor and stop automated login attempts or other malicious behavior based on log patterns -## How to add/use custom themes - -Gitea supports three official themes right now, `gitea-light`, `gitea-dark`, and `gitea-auto` (automatically switches between the previous two depending on operating system settings). -To add your own theme, currently the only way is to provide a complete theme (not just color overrides) - -As an example, let's say our theme is `arc-blue` (this is a real theme, and can be found [in this issue](https://github.com/go-gitea/gitea/issues/6011)) - -Name the `.css` file `theme-arc-blue.css` and add it to your custom folder in `custom/public/assets/css` - -Allow users to use it by adding `arc-blue` to the list of `THEMES` in your `app.ini` - ## SSHD vs built-in SSH SSHD is the built-in SSH server on most Unix systems. diff --git a/docs/content/help/faq.zh-cn.md b/docs/content/help/faq.zh-cn.md index 25230df70b..ef8a149ae2 100644 --- a/docs/content/help/faq.zh-cn.md +++ b/docs/content/help/faq.zh-cn.md @@ -182,17 +182,6 @@ Gitea不提供内置的Pages服务器。您需要一个专用的域名来提供 使用 [Fail2Ban](administration/fail2ban-setup.md) 监视并阻止基于日志模式的自动登录尝试或其他恶意行为。 -## 如何添加/使用自定义主题 - -Gitea 目前支持三个官方主题,分别是 `gitea-light`、`gitea-dark` 和 `gitea-auto`(根据操作系统设置自动切换前两个主题)。 -要添加自己的主题,目前唯一的方法是提供一个完整的主题(不仅仅是颜色覆盖)。 - -假设我们的主题是 `arc-blue`(这是一个真实的主题,可以在[此问题](https://github.com/go-gitea/gitea/issues/6011)中找到) - -将`.css`文件命名为`theme-arc-blue.css`并将其添加到`custom/public/assets/css`文件夹中 - -通过将`arc-blue`添加到`app.ini`中的`THEMES`列表中,允许用户使用该主题 - ## SSHD vs 内建SSH SSHD是大多数Unix系统上内建的SSH服务器。 diff --git a/models/actions/run.go b/models/actions/run.go index 967baa7493..4f886999e9 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -74,6 +74,13 @@ func (run *ActionRun) Link() string { return fmt.Sprintf("%s/actions/runs/%d", run.Repo.Link(), run.Index) } +func (run *ActionRun) WorkflowLink() string { + if run.Repo == nil { + return "" + } + return fmt.Sprintf("%s/actions/?workflow=%s", run.Repo.Link(), run.WorkflowID) +} + // RefLink return the url of run's ref func (run *ActionRun) RefLink() string { refName := git.RefName(run.Ref) @@ -98,13 +105,10 @@ func (run *ActionRun) LoadAttributes(ctx context.Context) error { return nil } - if run.Repo == nil { - repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID) - if err != nil { - return err - } - run.Repo = repo + if err := run.LoadRepo(ctx); err != nil { + return err } + if err := run.Repo.LoadAttributes(ctx); err != nil { return err } @@ -120,6 +124,19 @@ func (run *ActionRun) LoadAttributes(ctx context.Context) error { return nil } +func (run *ActionRun) LoadRepo(ctx context.Context) error { + if run == nil || run.Repo != nil { + return nil + } + + repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID) + if err != nil { + return err + } + run.Repo = repo + return nil +} + func (run *ActionRun) Duration() time.Duration { return calculateDuration(run.Started, run.Stopped, run.Status) + run.PreviousDuration } @@ -146,6 +163,10 @@ func (run *ActionRun) GetPullRequestEventPayload() (*api.PullRequestPayload, err return nil, fmt.Errorf("event %s is not a pull request event", run.Event) } +func (run *ActionRun) IsSchedule() bool { + return run.ScheduleID > 0 +} + func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error { _, err := db.GetEngine(ctx).ID(repo.ID). SetExpr("num_action_runs", diff --git a/models/actions/runner.go b/models/actions/runner.go index 67f003387b..9192925d5a 100644 --- a/models/actions/runner.go +++ b/models/actions/runner.go @@ -270,7 +270,7 @@ func CountRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) { // Only affect action runners were a owner ID is set, as actions runners // could also be created on a repository. return db.GetEngine(ctx).Table("action_runner"). - Join("LEFT", "user", "`action_runner`.owner_id = `user`.id"). + Join("LEFT", "`user`", "`action_runner`.owner_id = `user`.id"). Where("`action_runner`.owner_id != ?", 0). And(builder.IsNull{"`user`.id"}). Count(new(ActionRunner)) @@ -279,7 +279,7 @@ func CountRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) { func FixRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) { subQuery := builder.Select("`action_runner`.id"). From("`action_runner`"). - Join("LEFT", "user", "`action_runner`.owner_id = `user`.id"). + Join("LEFT", "`user`", "`action_runner`.owner_id = `user`.id"). Where(builder.Neq{"`action_runner`.owner_id": 0}). And(builder.IsNull{"`user`.id"}) b := builder.Delete(builder.In("id", subQuery)).From("`action_runner`") @@ -289,3 +289,25 @@ func FixRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) { } return res.RowsAffected() } + +func CountRunnersWithoutBelongingRepo(ctx context.Context) (int64, error) { + return db.GetEngine(ctx).Table("action_runner"). + Join("LEFT", "`repository`", "`action_runner`.repo_id = `repository`.id"). + Where("`action_runner`.repo_id != ?", 0). + And(builder.IsNull{"`repository`.id"}). + Count(new(ActionRunner)) +} + +func FixRunnersWithoutBelongingRepo(ctx context.Context) (int64, error) { + subQuery := builder.Select("`action_runner`.id"). + From("`action_runner`"). + Join("LEFT", "`repository`", "`action_runner`.repo_id = `repository`.id"). + Where(builder.Neq{"`action_runner`.repo_id": 0}). + And(builder.IsNull{"`repository`.id"}) + b := builder.Delete(builder.In("id", subQuery)).From("`action_runner`") + res, err := db.GetEngine(ctx).Exec(b) + if err != nil { + return 0, err + } + return res.RowsAffected() +} diff --git a/models/actions/variable.go b/models/actions/variable.go index b0a455e675..8aff844659 100644 --- a/models/actions/variable.go +++ b/models/actions/variable.go @@ -92,6 +92,11 @@ func DeleteVariable(ctx context.Context, id int64) error { func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, error) { variables := map[string]string{} + if err := run.LoadRepo(ctx); err != nil { + log.Error("LoadRepo: %v", err) + return nil, err + } + // Global globalVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{}) if err != nil { diff --git a/models/asymkey/gpg_key_commit_verification.go b/models/asymkey/gpg_key_commit_verification.go index 06ac31bc6f..26fad3bb3f 100644 --- a/models/asymkey/gpg_key_commit_verification.go +++ b/models/asymkey/gpg_key_commit_verification.go @@ -110,7 +110,6 @@ func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerific Reason: "gpg.error.no_committer_account", } } - } } diff --git a/models/auth/oauth2_test.go b/models/auth/oauth2_test.go index 122d43098c..0829d31d51 100644 --- a/models/auth/oauth2_test.go +++ b/models/auth/oauth2_test.go @@ -13,8 +13,6 @@ import ( "github.com/stretchr/testify/assert" ) -//////////////////// Application - func TestOAuth2Application_GenerateClientSecret(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1}) diff --git a/models/db/engine.go b/models/db/engine.go index 26abf0b96c..25f4066ea1 100755 --- a/models/db/engine.go +++ b/models/db/engine.go @@ -227,7 +227,6 @@ func NamesToBean(names ...string) ([]any, error) { // Need to map provided names to beans... beanMap := make(map[string]any) for _, bean := range tables { - beanMap[strings.ToLower(reflect.Indirect(reflect.ValueOf(bean)).Type().Name())] = bean beanMap[strings.ToLower(x.TableName(bean))] = bean beanMap[strings.ToLower(x.TableName(bean, true))] = bean diff --git a/models/issues/review.go b/models/issues/review.go index 92764db4d1..3c6934b060 100644 --- a/models/issues/review.go +++ b/models/issues/review.go @@ -345,11 +345,9 @@ func CreateReview(ctx context.Context, opts CreateReviewOptions) (*Review, error return nil, err } } - } else if opts.ReviewerTeam != nil { review.Type = ReviewTypeRequest review.ReviewerTeamID = opts.ReviewerTeam.ID - } else { return nil, fmt.Errorf("provide either reviewer or reviewer team") } diff --git a/models/migrations/base/db.go b/models/migrations/base/db.go index 51351cc7d3..eb1c44a79e 100644 --- a/models/migrations/base/db.go +++ b/models/migrations/base/db.go @@ -177,7 +177,6 @@ func RecreateTable(sess *xorm.Session, bean any) error { log.Error("Unable to recreate uniques on table %s. Error: %v", tableName, err) return err } - case setting.Database.Type.IsMySQL(): // MySQL will drop all the constraints on the old table if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil { @@ -228,7 +227,6 @@ func RecreateTable(sess *xorm.Session, bean any) error { return err } sequenceMap[sequence] = sequenceData - } // CASCADE causes postgres to drop all the constraints on the old table @@ -293,9 +291,7 @@ func RecreateTable(sess *xorm.Session, bean any) error { return err } } - } - case setting.Database.Type.IsMSSQL(): // MSSQL will drop all the constraints on the old table if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil { @@ -308,7 +304,6 @@ func RecreateTable(sess *xorm.Session, bean any) error { log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err) return err } - default: log.Fatal("Unrecognized DB") } diff --git a/models/migrations/fixtures/Test_AddConfidentialClientColumnToOAuth2ApplicationTable/o_auth2_application.yml b/models/migrations/fixtures/Test_AddConfidentialClientColumnToOAuth2ApplicationTable/oauth2_application.yml similarity index 100% rename from models/migrations/fixtures/Test_AddConfidentialClientColumnToOAuth2ApplicationTable/o_auth2_application.yml rename to models/migrations/fixtures/Test_AddConfidentialClientColumnToOAuth2ApplicationTable/oauth2_application.yml diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index cb3a64f48c..220d8c2331 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -584,6 +584,8 @@ var migrations = []Migration{ NewMigration("Add missing field of commit status summary table", v1_23.AddCommitStatusSummary2), // v297 -> v298 NewMigration("Add everyone_access_mode for repo_unit", v1_23.AddRepoUnitEveryoneAccessMode), + // v298 -> v299 + NewMigration("Drop wrongly created table o_auth2_application", v1_23.DropWronglyCreatedTable), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_11/v111.go b/models/migrations/v1_11/v111.go index 1722792a38..ff108479a9 100644 --- a/models/migrations/v1_11/v111.go +++ b/models/migrations/v1_11/v111.go @@ -262,7 +262,6 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error { for _, u := range units { var found bool for _, team := range teams { - var teamU []*TeamUnit var unitEnabled bool err = sess.Where("team_id = ?", team.ID).Find(&teamU) @@ -331,7 +330,6 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error { } if !protectedBranch.EnableApprovalsWhitelist { - perm, err := getUserRepoPermission(sess, baseRepo, reviewer) if err != nil { return false, err diff --git a/models/migrations/v1_16/v210.go b/models/migrations/v1_16/v210.go index 533bb4bf80..51b7d81e99 100644 --- a/models/migrations/v1_16/v210.go +++ b/models/migrations/v1_16/v210.go @@ -43,11 +43,6 @@ func RemigrateU2FCredentials(x *xorm.Engine) error { if err != nil { return err } - case schemas.ORACLE: - _, err := x.Exec("ALTER TABLE webauthn_credential MODIFY credential_id VARCHAR(410)") - if err != nil { - return err - } case schemas.MSSQL: // This column has an index on it. I could write all of the code to attempt to change the index OR // I could just use recreate table. diff --git a/models/migrations/v1_18/v230.go b/models/migrations/v1_18/v230.go index cf94926be1..ea5b4d02e1 100644 --- a/models/migrations/v1_18/v230.go +++ b/models/migrations/v1_18/v230.go @@ -9,9 +9,9 @@ import ( // AddConfidentialColumnToOAuth2ApplicationTable: add ConfidentialClient column, setting existing rows to true func AddConfidentialClientColumnToOAuth2ApplicationTable(x *xorm.Engine) error { - type OAuth2Application struct { + type oauth2Application struct { + ID int64 ConfidentialClient bool `xorm:"NOT NULL DEFAULT TRUE"` } - - return x.Sync(new(OAuth2Application)) + return x.Sync(new(oauth2Application)) } diff --git a/models/migrations/v1_18/v230_test.go b/models/migrations/v1_18/v230_test.go index 308f3a5023..40db4c2ffe 100644 --- a/models/migrations/v1_18/v230_test.go +++ b/models/migrations/v1_18/v230_test.go @@ -13,12 +13,12 @@ import ( func Test_AddConfidentialClientColumnToOAuth2ApplicationTable(t *testing.T) { // premigration - type OAuth2Application struct { + type oauth2Application struct { ID int64 } // Prepare and load the testing database - x, deferable := base.PrepareTestEnv(t, 0, new(OAuth2Application)) + x, deferable := base.PrepareTestEnv(t, 0, new(oauth2Application)) defer deferable() if x == nil || t.Failed() { return @@ -36,7 +36,7 @@ func Test_AddConfidentialClientColumnToOAuth2ApplicationTable(t *testing.T) { } got := []ExpectedOAuth2Application{} - if err := x.Table("o_auth2_application").Select("id, confidential_client").Find(&got); !assert.NoError(t, err) { + if err := x.Table("oauth2_application").Select("id, confidential_client").Find(&got); !assert.NoError(t, err) { return } diff --git a/models/migrations/v1_20/v250.go b/models/migrations/v1_20/v250.go index a09957b291..86388ef0b8 100644 --- a/models/migrations/v1_20/v250.go +++ b/models/migrations/v1_20/v250.go @@ -104,7 +104,7 @@ func ChangeContainerMetadataMultiArch(x *xorm.Engine) error { // Convert to new metadata format - new := &MetadataNew{ + newMetadata := &MetadataNew{ Type: old.Type, IsTagged: old.IsTagged, Platform: old.Platform, @@ -119,7 +119,7 @@ func ChangeContainerMetadataMultiArch(x *xorm.Engine) error { Manifests: manifests, } - metadataJSON, err := json.Marshal(new) + metadataJSON, err := json.Marshal(newMetadata) if err != nil { return err } diff --git a/models/migrations/v1_22/v286.go b/models/migrations/v1_22/v286.go index fbbd87344f..f46d494dfe 100644 --- a/models/migrations/v1_22/v286.go +++ b/models/migrations/v1_22/v286.go @@ -53,7 +53,7 @@ func expandHashReferencesToSha256(x *xorm.Engine) error { if setting.Database.Type.IsMySQL() { _, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` MODIFY COLUMN `%s` VARCHAR(64)", alts[0], alts[1])) } else if setting.Database.Type.IsMSSQL() { - _, err = db.Exec(fmt.Sprintf("ALTER TABLE [%s] ALTER COLUMN [%s] VARCHAR(64)", alts[0], alts[1])) + _, err = db.Exec(fmt.Sprintf("ALTER TABLE [%s] ALTER COLUMN [%s] NVARCHAR(64)", alts[0], alts[1])) } else { _, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` ALTER COLUMN `%s` TYPE VARCHAR(64)", alts[0], alts[1])) } diff --git a/models/migrations/v1_23/v298.go b/models/migrations/v1_23/v298.go new file mode 100644 index 0000000000..8761a05d3d --- /dev/null +++ b/models/migrations/v1_23/v298.go @@ -0,0 +1,10 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_23 //nolint + +import "xorm.io/xorm" + +func DropWronglyCreatedTable(x *xorm.Engine) error { + return x.DropTables("o_auth2_application") +} diff --git a/models/migrations/v1_6/v71.go b/models/migrations/v1_6/v71.go index 4e50ca9219..586187228b 100644 --- a/models/migrations/v1_6/v71.go +++ b/models/migrations/v1_6/v71.go @@ -61,7 +61,6 @@ func AddScratchHash(x *xorm.Engine) error { if _, err := sess.ID(tfa.ID).Cols("scratch_salt, scratch_hash").Update(tfa); err != nil { return fmt.Errorf("couldn't add in scratch_hash and scratch_salt: %w", err) } - } } diff --git a/models/migrations/v1_9/v85.go b/models/migrations/v1_9/v85.go index 9419ee1aae..a23d7c5d6e 100644 --- a/models/migrations/v1_9/v85.go +++ b/models/migrations/v1_9/v85.go @@ -81,7 +81,6 @@ func HashAppToken(x *xorm.Engine) error { if _, err := sess.ID(token.ID).Cols("token_hash, token_salt, token_last_eight, sha1").Update(token); err != nil { return fmt.Errorf("couldn't add in sha1, token_hash, token_salt and token_last_eight: %w", err) } - } } diff --git a/models/organization/team.go b/models/organization/team.go index e4e83fedee..fb7f0c0493 100644 --- a/models/organization/team.go +++ b/models/organization/team.go @@ -226,9 +226,8 @@ func GetTeamIDsByNames(ctx context.Context, orgID int64, names []string, ignoreN if err != nil { if ignoreNonExistent { continue - } else { - return nil, err } + return nil, err } ids = append(ids, u.ID) } diff --git a/models/project/board.go b/models/project/board.go index 5f142a356c..7faabc52c5 100644 --- a/models/project/board.go +++ b/models/project/board.go @@ -110,13 +110,11 @@ func createBoardsForProjectsType(ctx context.Context, project *Project) error { var items []string switch project.BoardType { - case BoardTypeBugTriage: items = setting.Project.ProjectBoardBugTriageType case BoardTypeBasicKanban: items = setting.Project.ProjectBoardBasicKanbanType - case BoardTypeNone: fallthrough default: diff --git a/models/repo/user_repo.go b/models/repo/user_repo.go index 6862247657..1c5412fe7d 100644 --- a/models/repo/user_repo.go +++ b/models/repo/user_repo.go @@ -170,7 +170,6 @@ func GetReviewers(ctx context.Context, repo *Repository, doerID, posterID int64) // the owner of a private repo needs to be explicitly added. cond = cond.Or(builder.Eq{"`user`.id": repo.Owner.ID}) } - } else { // This is a "public" repository: // Any user that has read access, is a watcher or organization member can be requested to review diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go index cb90c12f2b..51de18fa9b 100644 --- a/models/unittest/testdb.go +++ b/models/unittest/testdb.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/modules/auth/password/hash" "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting/config" @@ -106,6 +107,7 @@ func MainTest(m *testing.M, testOpts ...*TestOptions) { fatalTestError("Error creating test engine: %v\n", err) } + setting.IsInTesting = true setting.AppURL = "https://try.gitea.io/" setting.RunUser = "runuser" setting.SSH.User = "sshuser" @@ -148,6 +150,9 @@ func MainTest(m *testing.M, testOpts ...*TestOptions) { config.SetDynGetter(system.NewDatabaseDynKeyGetter()) + if err = cache.Init(); err != nil { + fatalTestError("cache.Init: %v\n", err) + } if err = storage.Init(); err != nil { fatalTestError("storage.Init: %v\n", err) } diff --git a/models/user/user.go b/models/user/user.go index d459ec239e..a5a5b5bdf6 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -501,19 +501,19 @@ func GetUserSalt() (string, error) { // Note: The set of characters here can safely expand without a breaking change, // but characters removed from this set can cause user account linking to break var ( - customCharsReplacement = strings.NewReplacer("Æ", "AE") - removeCharsRE = regexp.MustCompile(`['´\x60]`) - removeDiacriticsTransform = transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC) - replaceCharsHyphenRE = regexp.MustCompile(`[\s~+]`) + customCharsReplacement = strings.NewReplacer("Æ", "AE") + removeCharsRE = regexp.MustCompile("['`´]") + transformDiacritics = transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC) + replaceCharsHyphenRE = regexp.MustCompile(`[\s~+]`) ) -// normalizeUserName returns a string with single-quotes and diacritics -// removed, and any other non-supported username characters replaced with -// a `-` character +// NormalizeUserName only takes the name part if it is an email address, transforms it diacritics to ASCII characters. +// It returns a string with the single-quotes removed, and any other non-supported username characters are replaced with a `-` character func NormalizeUserName(s string) (string, error) { - strDiacriticsRemoved, n, err := transform.String(removeDiacriticsTransform, customCharsReplacement.Replace(s)) + s, _, _ = strings.Cut(s, "@") + strDiacriticsRemoved, n, err := transform.String(transformDiacritics, customCharsReplacement.Replace(s)) if err != nil { - return "", fmt.Errorf("Failed to normalize character `%v` in provided username `%v`", s[n], s) + return "", fmt.Errorf("failed to normalize the string of provided username %q at position %d", s, n) } return replaceCharsHyphenRE.ReplaceAllLiteralString(removeCharsRE.ReplaceAllLiteralString(strDiacriticsRemoved, ""), "-"), nil } @@ -988,9 +988,8 @@ func GetUserIDsByNames(ctx context.Context, names []string, ignoreNonExistent bo if err != nil { if ignoreNonExistent { continue - } else { - return nil, err } + return nil, err } ids = append(ids, u.ID) } diff --git a/models/user/user_test.go b/models/user/user_test.go index a4550fa655..b4ffa1f322 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -506,15 +506,16 @@ func Test_NormalizeUserFromEmail(t *testing.T) { Expected string IsNormalizedValid bool }{ - {"test", "test", true}, + {"name@example.com", "name", true}, + {"test'`´name", "testname", true}, {"Sinéad.O'Connor", "Sinead.OConnor", true}, {"Æsir", "AEsir", true}, - // \u00e9\u0065\u0301 - {"éé", "ee", true}, + {"éé", "ee", true}, // \u00e9\u0065\u0301 {"Awareness Hub", "Awareness-Hub", true}, {"double__underscore", "double__underscore", false}, // We should consider squashing double non-alpha characters {".bad.", ".bad.", false}, {"new😀user", "new😀user", false}, // No plans to support + {`"quoted"`, `"quoted"`, false}, // No plans to support } for _, testCase := range testCases { normalizedName, err := user_model.NormalizeUserName(testCase.Input) diff --git a/modules/auth/password/password.go b/modules/auth/password/password.go index 27074358a9..85f9780709 100644 --- a/modules/auth/password/password.go +++ b/modules/auth/password/password.go @@ -63,16 +63,16 @@ func NewComplexity() { func setupComplexity(values []string) { if len(values) != 1 || values[0] != "off" { for _, val := range values { - if complex, ok := charComplexities[val]; ok { - validChars += complex.ValidChars - requiredList = append(requiredList, complex) + if complexity, ok := charComplexities[val]; ok { + validChars += complexity.ValidChars + requiredList = append(requiredList, complexity) } } if len(requiredList) == 0 { // No valid character classes found; use all classes as default - for _, complex := range charComplexities { - validChars += complex.ValidChars - requiredList = append(requiredList, complex) + for _, complexity := range charComplexities { + validChars += complexity.ValidChars + requiredList = append(requiredList, complexity) } } } diff --git a/modules/git/batch_reader.go b/modules/git/batch_reader.go index 043dbb44bd..c988d6ab86 100644 --- a/modules/git/batch_reader.go +++ b/modules/git/batch_reader.go @@ -307,10 +307,10 @@ func ParseTreeLine(objectFormat ObjectFormat, rd *bufio.Reader, modeBuf, fnameBu // Deal with the binary hash idx = 0 - len := objectFormat.FullLength() / 2 - for idx < len { + length := objectFormat.FullLength() / 2 + for idx < length { var read int - read, err = rd.Read(shaBuf[idx:len]) + read, err = rd.Read(shaBuf[idx:length]) n += read if err != nil { return mode, fname, sha, n, err diff --git a/modules/git/commit_reader.go b/modules/git/commit_reader.go index f1f4a0e588..228bbaf314 100644 --- a/modules/git/commit_reader.go +++ b/modules/git/commit_reader.go @@ -49,9 +49,8 @@ readLoop: if len(line) > 0 && line[0] == ' ' { _, _ = signatureSB.Write(line[1:]) continue - } else { - pgpsig = false } + pgpsig = false } if !message { diff --git a/modules/git/pipeline/lfs_common.go b/modules/git/pipeline/lfs_common.go new file mode 100644 index 0000000000..188e7d4d65 --- /dev/null +++ b/modules/git/pipeline/lfs_common.go @@ -0,0 +1,32 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package pipeline + +import ( + "fmt" + "time" + + "code.gitea.io/gitea/modules/git" +) + +// LFSResult represents commits found using a provided pointer file hash +type LFSResult struct { + Name string + SHA string + Summary string + When time.Time + ParentHashes []git.ObjectID + BranchName string + FullCommitName string +} + +type lfsResultSlice []*LFSResult + +func (a lfsResultSlice) Len() int { return len(a) } +func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) } + +func lfsError(msg string, err error) error { + return fmt.Errorf("LFS error occurred, %s: err: %w", msg, err) +} diff --git a/modules/git/pipeline/lfs.go b/modules/git/pipeline/lfs_gogit.go similarity index 80% rename from modules/git/pipeline/lfs.go rename to modules/git/pipeline/lfs_gogit.go index 6dfca24f29..adcf8ed09c 100644 --- a/modules/git/pipeline/lfs.go +++ b/modules/git/pipeline/lfs_gogit.go @@ -7,12 +7,10 @@ package pipeline import ( "bufio" - "fmt" "io" "sort" "strings" "sync" - "time" "code.gitea.io/gitea/modules/git" @@ -21,23 +19,6 @@ import ( "github.com/go-git/go-git/v5/plumbing/object" ) -// LFSResult represents commits found using a provided pointer file hash -type LFSResult struct { - Name string - SHA string - Summary string - When time.Time - ParentHashes []git.ObjectID - BranchName string - FullCommitName string -} - -type lfsResultSlice []*LFSResult - -func (a lfsResultSlice) Len() int { return len(a) } -func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) } - // FindLFSFile finds commits that contain a provided pointer file hash func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) { resultsMap := map[string]*LFSResult{} @@ -51,7 +32,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err All: true, }) if err != nil { - return nil, fmt.Errorf("Failed to get GoGit CommitsIter. Error: %w", err) + return nil, lfsError("failed to get GoGit CommitsIter", err) } err = commitsIter.ForEach(func(gitCommit *object.Commit) error { @@ -85,7 +66,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err return nil }) if err != nil && err != io.EOF { - return nil, fmt.Errorf("Failure in CommitIter.ForEach: %w", err) + return nil, lfsError("failure in CommitIter.ForEach", err) } for _, result := range resultsMap { @@ -156,7 +137,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err select { case err, has := <-errChan: if has { - return nil, fmt.Errorf("Unable to obtain name for LFS files. Error: %w", err) + return nil, lfsError("unable to obtain name for LFS files", err) } default: } diff --git a/modules/git/pipeline/lfs_nogogit.go b/modules/git/pipeline/lfs_nogogit.go index 4c65249089..349cfbd9ce 100644 --- a/modules/git/pipeline/lfs_nogogit.go +++ b/modules/git/pipeline/lfs_nogogit.go @@ -8,33 +8,14 @@ package pipeline import ( "bufio" "bytes" - "fmt" "io" "sort" "strings" "sync" - "time" "code.gitea.io/gitea/modules/git" ) -// LFSResult represents commits found using a provided pointer file hash -type LFSResult struct { - Name string - SHA string - Summary string - When time.Time - ParentIDs []git.ObjectID - BranchName string - FullCommitName string -} - -type lfsResultSlice []*LFSResult - -func (a lfsResultSlice) Len() int { return len(a) } -func (a lfsResultSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a lfsResultSlice) Less(i, j int) bool { return a[j].When.After(a[i].When) } - // FindLFSFile finds commits that contain a provided pointer file hash func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) { resultsMap := map[string]*LFSResult{} @@ -137,11 +118,11 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err n += int64(count) if bytes.Equal(binObjectID, objectID.RawValue()) { result := LFSResult{ - Name: curPath + string(fname), - SHA: curCommit.ID.String(), - Summary: strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0], - When: curCommit.Author.When, - ParentIDs: curCommit.Parents, + Name: curPath + string(fname), + SHA: curCommit.ID.String(), + Summary: strings.Split(strings.TrimSpace(curCommit.CommitMessage), "\n")[0], + When: curCommit.Author.When, + ParentHashes: curCommit.Parents, } resultsMap[curCommit.ID.String()+":"+curPath+string(fname)] = &result } else if string(mode) == git.EntryModeTree.String() { @@ -183,7 +164,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err for _, result := range resultsMap { hasParent := false - for _, parentID := range result.ParentIDs { + for _, parentID := range result.ParentHashes { if _, hasParent = resultsMap[parentID.String()+":"+result.Name]; hasParent { break } @@ -232,7 +213,6 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err errChan <- err break } - } }() @@ -241,7 +221,7 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err select { case err, has := <-errChan: if has { - return nil, fmt.Errorf("Unable to obtain name for LFS files. Error: %w", err) + return nil, lfsError("unable to obtain name for LFS files", err) } default: } diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 44273d2253..f9168bef7e 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -251,18 +251,18 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) return nil, err } - len := objectFormat.FullLength() + length := objectFormat.FullLength() commits := []*Commit{} - shaline := make([]byte, len+1) + shaline := make([]byte, length+1) for { n, err := io.ReadFull(stdoutReader, shaline) - if err != nil || n < len { + if err != nil || n < length { if err == io.EOF { err = nil } return commits, err } - objectID, err := NewIDFromString(string(shaline[0:len])) + objectID, err := NewIDFromString(string(shaline[0:length])) if err != nil { return nil, err } diff --git a/modules/git/submodule.go b/modules/git/submodule.go index 37813ea4c7..b99c81582b 100644 --- a/modules/git/submodule.go +++ b/modules/git/submodule.go @@ -64,7 +64,6 @@ func getRefURL(refURL, urlPrefix, repoFullName, sshDomain string) string { // ex: git@try.gitea.io:go-gitea/gitea match := scpSyntax.FindAllStringSubmatch(refURI, -1) if len(match) > 0 { - m := match[0] refHostname := m[2] pth := m[3] diff --git a/modules/indexer/code/bleve/bleve.go b/modules/indexer/code/bleve/bleve.go index c607d780ef..bd844205a6 100644 --- a/modules/indexer/code/bleve/bleve.go +++ b/modules/indexer/code/bleve/bleve.go @@ -191,7 +191,6 @@ func (b *Indexer) addDelete(filename string, repo *repo_model.Repository, batch func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error { batch := inner_bleve.NewFlushingBatch(b.inner.Indexer, maxBatchSize) if len(changes.Updates) > 0 { - // Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first! if err := git.EnsureValidGitRepository(ctx, repo.RepoPath()); err != nil { log.Error("Unable to open git repo: %s for %-v: %v", repo.RepoPath(), repo, err) @@ -335,7 +334,6 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int if result, err = b.inner.Indexer.Search(facetRequest); err != nil { return 0, nil, nil, err } - } languagesFacet := result.Facets["languages"] for _, term := range languagesFacet.Terms.Terms() { diff --git a/modules/indexer/issues/dboptions.go b/modules/indexer/issues/dboptions.go index 4a98b4588a..8f94088742 100644 --- a/modules/indexer/issues/dboptions.go +++ b/modules/indexer/issues/dboptions.go @@ -68,7 +68,7 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp searchOpt.Paginator = opts.Paginator switch opts.SortType { - case "": + case "", "latest": searchOpt.SortBy = SortByCreatedDesc case "oldest": searchOpt.SortBy = SortByCreatedAsc @@ -86,7 +86,7 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp searchOpt.SortBy = SortByDeadlineDesc case "priority", "priorityrepo", "project-column-sorting": // Unsupported sort type for search - searchOpt.SortBy = SortByUpdatedDesc + fallthrough default: searchOpt.SortBy = SortByUpdatedDesc } diff --git a/modules/indexer/issues/elasticsearch/elasticsearch.go b/modules/indexer/issues/elasticsearch/elasticsearch.go index 53b383c8d5..c7cb59f2cf 100644 --- a/modules/indexer/issues/elasticsearch/elasticsearch.go +++ b/modules/indexer/issues/elasticsearch/elasticsearch.go @@ -145,7 +145,6 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( query := elastic.NewBoolQuery() if options.Keyword != "" { - searchType := esMultiMatchTypePhrasePrefix if options.IsFuzzyKeyword { searchType = esMultiMatchTypeBestFields diff --git a/modules/log/event_format.go b/modules/log/event_format.go index 524ca3dd87..d9dbebf831 100644 --- a/modules/log/event_format.go +++ b/modules/log/event_format.go @@ -125,7 +125,6 @@ func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, ms if mode.Colorize { buf = append(buf, resetBytes...) } - } if flags&(Lshortfile|Llongfile) != 0 { if mode.Colorize { diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index d9b67e43af..bc6ad7fb3c 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -466,7 +466,6 @@ func TestColorPreview(t *testing.T) { res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase) assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase) assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase) - } negativeTests := []string{ @@ -549,7 +548,6 @@ func TestMathBlock(t *testing.T) { res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase) assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase) assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase) - } } diff --git a/modules/packages/rubygems/marshal.go b/modules/packages/rubygems/marshal.go index 8878dcf973..4e6a5fc5f8 100644 --- a/modules/packages/rubygems/marshal.go +++ b/modules/packages/rubygems/marshal.go @@ -147,35 +147,35 @@ func (e *MarshalEncoder) marshalIntInternal(i int64) error { return e.w.WriteByte(byte(i - 5)) } - var len int + var length int if 122 < i && i <= 0xff { - len = 1 + length = 1 } else if 0xff < i && i <= 0xffff { - len = 2 + length = 2 } else if 0xffff < i && i <= 0xffffff { - len = 3 + length = 3 } else if 0xffffff < i && i <= 0x3fffffff { - len = 4 + length = 4 } else if -0x100 <= i && i < -123 { - len = -1 + length = -1 } else if -0x10000 <= i && i < -0x100 { - len = -2 + length = -2 } else if -0x1000000 <= i && i < -0x100000 { - len = -3 + length = -3 } else if -0x40000000 <= i && i < -0x1000000 { - len = -4 + length = -4 } else { return ErrInvalidIntRange } - if err := e.w.WriteByte(byte(len)); err != nil { + if err := e.w.WriteByte(byte(length)); err != nil { return err } - if len < 0 { - len = -len + if length < 0 { + length = -length } - for c := 0; c < len; c++ { + for c := 0; c < length; c++ { if err := e.w.WriteByte(byte(i >> uint(8*c) & 0xff)); err != nil { return err } @@ -244,13 +244,13 @@ func (e *MarshalEncoder) marshalArray(arr reflect.Value) error { return err } - len := arr.Len() + length := arr.Len() - if err := e.marshalIntInternal(int64(len)); err != nil { + if err := e.marshalIntInternal(int64(length)); err != nil { return err } - for i := 0; i < len; i++ { + for i := 0; i < length; i++ { if err := e.marshal(arr.Index(i).Interface()); err != nil { return err } diff --git a/modules/process/manager_stacktraces.go b/modules/process/manager_stacktraces.go index 49bd5071f6..e260893113 100644 --- a/modules/process/manager_stacktraces.go +++ b/modules/process/manager_stacktraces.go @@ -339,7 +339,6 @@ func (pm *Manager) ProcessStacktraces(flat, noSystem bool) ([]*Process, int, int } sort.Slice(processes, after(processes)) if !flat { - var sortChildren func(process *Process) sortChildren = func(process *Process) { diff --git a/modules/queue/workergroup.go b/modules/queue/workergroup.go index e3801ef2b2..153123f883 100644 --- a/modules/queue/workergroup.go +++ b/modules/queue/workergroup.go @@ -63,6 +63,8 @@ func (q *WorkerPoolQueue[T]) doDispatchBatchToWorker(wg *workerGroup[T], flushCh // TODO: the logic could be improved in the future, to avoid a data-race between "doStartNewWorker" and "workerNum" // The root problem is that if we skip "doStartNewWorker" here, the "workerNum" might be decreased by other workers later // So ideally, it should check whether there are enough workers by some approaches, and start new workers if necessary. + // This data-race is not serious, as long as a new worker will be started soon to make sure there are enough workers, + // so no need to hugely refactor at the moment. q.workerNumMu.Lock() noWorker := q.workerNum == 0 if full || noWorker { @@ -136,6 +138,14 @@ func (q *WorkerPoolQueue[T]) basePushForShutdown(items ...T) bool { return true } +func resetIdleTicker(t *time.Ticker, dur time.Duration) { + t.Reset(dur) + select { + case <-t.C: + default: + } +} + // doStartNewWorker starts a new worker for the queue, the worker reads from worker's channel and handles the items. func (q *WorkerPoolQueue[T]) doStartNewWorker(wp *workerGroup[T]) { wp.wg.Add(1) @@ -146,8 +156,6 @@ func (q *WorkerPoolQueue[T]) doStartNewWorker(wp *workerGroup[T]) { log.Debug("Queue %q starts new worker", q.GetName()) defer log.Debug("Queue %q stops idle worker", q.GetName()) - atomic.AddInt32(&q.workerStartedCounter, 1) // Only increase counter, used for debugging - t := time.NewTicker(workerIdleDuration) defer t.Stop() @@ -169,11 +177,7 @@ func (q *WorkerPoolQueue[T]) doStartNewWorker(wp *workerGroup[T]) { } q.doWorkerHandle(batch) // reset the idle ticker, and drain the tick after reset in case a tick is already triggered - t.Reset(workerIdleDuration) - select { - case <-t.C: - default: - } + resetIdleTicker(t, workerIdleDuration) // key code for TestWorkerPoolQueueWorkerIdleReset case <-t.C: q.workerNumMu.Lock() keepWorking = q.workerNum <= 1 // keep the last worker running diff --git a/modules/queue/workerqueue.go b/modules/queue/workerqueue.go index 4160622d81..b28fd88027 100644 --- a/modules/queue/workerqueue.go +++ b/modules/queue/workerqueue.go @@ -40,8 +40,6 @@ type WorkerPoolQueue[T any] struct { workerMaxNum int workerActiveNum int workerNumMu sync.Mutex - - workerStartedCounter int32 } type flushType chan struct{} diff --git a/modules/queue/workerqueue_test.go b/modules/queue/workerqueue_test.go index a08b02a123..d66253ff66 100644 --- a/modules/queue/workerqueue_test.go +++ b/modules/queue/workerqueue_test.go @@ -5,8 +5,10 @@ package queue import ( "context" + "slices" "strconv" "sync" + "sync/atomic" "testing" "time" @@ -250,23 +252,34 @@ func TestWorkerPoolQueueShutdown(t *testing.T) { func TestWorkerPoolQueueWorkerIdleReset(t *testing.T) { defer test.MockVariableValue(&workerIdleDuration, 10*time.Millisecond)() - defer mockBackoffDuration(10 * time.Millisecond)() + defer mockBackoffDuration(5 * time.Millisecond)() + var q *WorkerPoolQueue[int] + var handledCount atomic.Int32 + var hasOnlyOneWorkerRunning atomic.Bool handler := func(items ...int) (unhandled []int) { - time.Sleep(50 * time.Millisecond) + handledCount.Add(int32(len(items))) + // make each work have different duration, and check the active worker number periodically + var activeNums []int + for i := 0; i < 5-items[0]%2; i++ { + time.Sleep(workerIdleDuration * 2) + activeNums = append(activeNums, q.GetWorkerActiveNumber()) + } + // When the queue never becomes empty, the existing workers should keep working + // It is not 100% true at the moment because the data-race in workergroup.go is not resolved, see that TODO */ + // If the "active worker numbers" is like [2 2 ... 1 1], it means that an existing worker exited and the no new worker is started. + if slices.Equal([]int{1, 1}, activeNums[len(activeNums)-2:]) { + hasOnlyOneWorkerRunning.Store(true) + } return nil } - - q, _ := newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: 2, Length: 100}, handler, false) + q, _ = newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: 2, Length: 100}, handler, false) stop := runWorkerPoolQueue(q) - for i := 0; i < 20; i++ { + for i := 0; i < 100; i++ { assert.NoError(t, q.Push(i)) } - time.Sleep(500 * time.Millisecond) - assert.EqualValues(t, 2, q.GetWorkerNumber()) - assert.EqualValues(t, 2, q.GetWorkerActiveNumber()) - // when the queue never becomes empty, the existing workers should keep working - assert.EqualValues(t, 2, q.workerStartedCounter) + assert.Greater(t, int(handledCount.Load()), 4) // make sure there are enough items handled during the test + assert.False(t, hasOnlyOneWorkerRunning.Load(), "a slow handler should not block other workers from starting") stop() } diff --git a/modules/repository/temp.go b/modules/repository/temp.go index 53646718e0..04faa9db3d 100644 --- a/modules/repository/temp.go +++ b/modules/repository/temp.go @@ -32,7 +32,6 @@ func CreateTemporaryPath(prefix string) (string, error) { if err != nil { log.Error("Unable to create temporary directory: %s-*.git (%v)", prefix, err) return "", fmt.Errorf("Failed to create dir %s-*.git: %w", prefix, err) - } return basePath, nil } diff --git a/modules/session/mock.go b/modules/session/mock.go new file mode 100644 index 0000000000..95231a3655 --- /dev/null +++ b/modules/session/mock.go @@ -0,0 +1,26 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package session + +import ( + "net/http" + + "gitea.com/go-chi/session" +) + +type MockStore struct { + *session.MemStore +} + +func (m *MockStore) Destroy(writer http.ResponseWriter, request *http.Request) error { + return nil +} + +type mockStoreContextKeyStruct struct{} + +var MockStoreContextKey = mockStoreContextKeyStruct{} + +func NewMockStore(sid string) *MockStore { + return &MockStore{session.NewMemStore(sid)} +} diff --git a/modules/session/store.go b/modules/session/store.go index 70988fcdc5..09d1ef44dd 100644 --- a/modules/session/store.go +++ b/modules/session/store.go @@ -6,6 +6,8 @@ package session import ( "net/http" + "code.gitea.io/gitea/modules/setting" + "gitea.com/go-chi/session" ) @@ -14,6 +16,10 @@ type Store interface { Get(any) any Set(any, any) error Delete(any) error + ID() string + Release() error + Flush() error + Destroy(http.ResponseWriter, *http.Request) error } // RegenerateSession regenerates the underlying session and returns the new store @@ -21,8 +27,21 @@ func RegenerateSession(resp http.ResponseWriter, req *http.Request) (Store, erro for _, f := range BeforeRegenerateSession { f(resp, req) } - s, err := session.RegenerateSession(resp, req) - return s, err + if setting.IsInTesting { + if store, ok := req.Context().Value(MockStoreContextKey).(*MockStore); ok { + return store, nil + } + } + return session.RegenerateSession(resp, req) +} + +func GetContextSession(req *http.Request) Store { + if setting.IsInTesting { + if store, ok := req.Context().Value(MockStoreContextKey).(*MockStore); ok { + return store + } + } + return session.GetSession(req) } // BeforeRegenerateSession is a list of functions that are called before a session is regenerated. diff --git a/modules/setting/config_provider.go b/modules/setting/config_provider.go index 03f27ba203..3138f8a63e 100644 --- a/modules/setting/config_provider.go +++ b/modules/setting/config_provider.go @@ -318,7 +318,7 @@ func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting any) { // StartupProblems contains the messages for various startup problems, including: setting option, file/folder, etc var StartupProblems []string -func logStartupProblem(skip int, level log.Level, format string, args ...any) { +func LogStartupProblem(skip int, level log.Level, format string, args ...any) { msg := fmt.Sprintf(format, args...) log.Log(skip+1, level, "%s", msg) StartupProblems = append(StartupProblems, msg) @@ -326,14 +326,14 @@ func logStartupProblem(skip int, level log.Level, format string, args ...any) { func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) { if rootCfg.Section(oldSection).HasKey(oldKey) { - logStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version) + LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version) } } // deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) { if rootCfg.Section(oldSection).HasKey(oldKey) { - logStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents but it won't take effect because it has been moved to admin panel -> config setting", oldSection, oldKey) + LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents but it won't take effect because it has been moved to admin panel -> config setting", oldSection, oldKey) } } diff --git a/modules/setting/oauth2.go b/modules/setting/oauth2.go index 6930197b22..e59f54420b 100644 --- a/modules/setting/oauth2.go +++ b/modules/setting/oauth2.go @@ -16,14 +16,10 @@ import ( type OAuth2UsernameType string const ( - // OAuth2UsernameUserid oauth2 userid field will be used as gitea name - OAuth2UsernameUserid OAuth2UsernameType = "userid" - // OAuth2UsernameNickname oauth2 nickname field will be used as gitea name - OAuth2UsernameNickname OAuth2UsernameType = "nickname" - // OAuth2UsernameEmail username of oauth2 email field will be used as gitea name - OAuth2UsernameEmail OAuth2UsernameType = "email" - // OAuth2UsernameEmail username of oauth2 preferred_username field will be used as gitea name - OAuth2UsernamePreferredUsername OAuth2UsernameType = "preferred_username" + OAuth2UsernameUserid OAuth2UsernameType = "userid" // use user id (sub) field as gitea's username + OAuth2UsernameNickname OAuth2UsernameType = "nickname" // use nickname field + OAuth2UsernameEmail OAuth2UsernameType = "email" // use email field + OAuth2UsernamePreferredUsername OAuth2UsernameType = "preferred_username" // use preferred_username field ) func (username OAuth2UsernameType) isValid() bool { @@ -71,8 +67,8 @@ func loadOAuth2ClientFrom(rootCfg ConfigProvider) { OAuth2Client.EnableAutoRegistration = sec.Key("ENABLE_AUTO_REGISTRATION").MustBool() OAuth2Client.Username = OAuth2UsernameType(sec.Key("USERNAME").MustString(string(OAuth2UsernameNickname))) if !OAuth2Client.Username.isValid() { - log.Warn("Username setting is not valid: '%s', will fallback to '%s'", OAuth2Client.Username, OAuth2UsernameNickname) OAuth2Client.Username = OAuth2UsernameNickname + log.Warn("[oauth2_client].USERNAME setting is invalid, falls back to %q", OAuth2Client.Username) } OAuth2Client.UpdateAvatar = sec.Key("UPDATE_AVATAR").MustBool() OAuth2Client.AccountLinking = OAuth2AccountLinkingType(sec.Key("ACCOUNT_LINKING").MustString(string(OAuth2AccountLinkingLogin))) @@ -174,7 +170,7 @@ func GetGeneralTokenSigningSecret() []byte { } if generalSigningSecret.CompareAndSwap(old, &jwtSecret) { // FIXME: in main branch, the signing token should be refactored (eg: one unique for LFS/OAuth2/etc ...) - logStartupProblem(1, log.WARN, "OAuth2 is not enabled, unable to use a persistent signing secret, a new one is generated, which is not persistent between restarts and cluster nodes") + LogStartupProblem(1, log.WARN, "OAuth2 is not enabled, unable to use a persistent signing secret, a new one is generated, which is not persistent between restarts and cluster nodes") return jwtSecret } return *generalSigningSecret.Load() diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 92bb0b6541..f056fbfc6c 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -235,7 +235,7 @@ var configuredPaths = make(map[string]string) func checkOverlappedPath(name, path string) { // TODO: some paths shouldn't overlap (storage.xxx.path), while some could (data path is the base path for storage path) if targetName, ok := configuredPaths[path]; ok && targetName != name { - logStartupProblem(1, log.ERROR, "Configured path %q is used by %q and %q at the same time. The paths must be unique to prevent data loss.", path, targetName, name) + LogStartupProblem(1, log.ERROR, "Configured path %q is used by %q and %q at the same time. The paths must be unique to prevent data loss.", path, targetName, name) } configuredPaths[path] = name } diff --git a/modules/setting/time.go b/modules/setting/time.go index 6d2aa80f5b..39acba12ef 100644 --- a/modules/setting/time.go +++ b/modules/setting/time.go @@ -19,9 +19,8 @@ func loadTimeFrom(rootCfg ConfigProvider) { DefaultUILocation, err = time.LoadLocation(zone) if err != nil { log.Fatal("Load time zone failed: %v", err) - } else { - log.Info("Default UI Location is %v", zone) } + log.Info("Default UI Location is %v", zone) } if DefaultUILocation == nil { DefaultUILocation = time.Local diff --git a/modules/setting/ui.go b/modules/setting/ui.go index 2f9eef93c3..93855bca07 100644 --- a/modules/setting/ui.go +++ b/modules/setting/ui.go @@ -82,7 +82,6 @@ var UI = struct { ReactionMaxUserNum: 10, MaxDisplayFileSize: 8388608, DefaultTheme: `gitea-auto`, - Themes: []string{`gitea-auto`, `gitea-light`, `gitea-dark`}, Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`}, CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"}, diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 360b48c594..94464fe628 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -22,6 +22,7 @@ import ( "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/gitdiff" + "code.gitea.io/gitea/services/webtheme" ) // NewFuncMap returns functions for injecting to templates @@ -137,12 +138,7 @@ func NewFuncMap() template.FuncMap { "DisableImportLocal": func() bool { return !setting.ImportLocalPaths }, - "ThemeName": func(user *user_model.User) string { - if user == nil || user.Theme == "" { - return setting.UI.DefaultTheme - } - return user.Theme - }, + "UserThemeName": UserThemeName, "NotificationSettings": func() map[string]any { return map[string]any{ "MinTimeout": int(setting.UI.Notification.MinTimeout / time.Millisecond), @@ -261,3 +257,13 @@ func Eval(tokens ...any) (any, error) { n, err := eval.Expr(tokens...) return n.Value, err } + +func UserThemeName(user *user_model.User) string { + if user == nil || user.Theme == "" { + return setting.UI.DefaultTheme + } + if webtheme.IsThemeAvailable(user.Theme) { + return user.Theme + } + return setting.UI.DefaultTheme +} diff --git a/modules/templates/htmlrenderer.go b/modules/templates/htmlrenderer.go index 40941285aa..e7e805ed30 100644 --- a/modules/templates/htmlrenderer.go +++ b/modules/templates/htmlrenderer.go @@ -138,10 +138,9 @@ func wrapTmplErrMsg(msg string) { if setting.IsProd { // in prod mode, Gitea must have correct templates to run log.Fatal("Gitea can't run with template errors: %s", msg) - } else { - // in dev mode, do not need to really exit, because the template errors could be fixed by developer soon and the templates get reloaded - log.Error("There are template errors but Gitea continues to run in dev mode: %s", msg) } + // in dev mode, do not need to really exit, because the template errors could be fixed by developer soon and the templates get reloaded + log.Error("There are template errors but Gitea continues to run in dev mode: %s", msg) } type templateErrorPrettier struct { diff --git a/modules/templates/mailer.go b/modules/templates/mailer.go index f1832cba0e..7c97e1ea89 100644 --- a/modules/templates/mailer.go +++ b/modules/templates/mailer.go @@ -84,9 +84,8 @@ func Mailer(ctx context.Context) (*texttmpl.Template, *template.Template) { if err = buildSubjectBodyTemplate(subjectTemplates, bodyTemplates, tmplName, content); err != nil { if firstRun { log.Fatal("Failed to parse mail template, err: %v", err) - } else { - log.Error("Failed to parse mail template, err: %v", err) } + log.Error("Failed to parse mail template, err: %v", err) } } } diff --git a/modules/util/util_test.go b/modules/util/util_test.go index 5c5b13d04b..de8f065cad 100644 --- a/modules/util/util_test.go +++ b/modules/util/util_test.go @@ -121,9 +121,9 @@ func Test_NormalizeEOL(t *testing.T) { } func Test_RandomInt(t *testing.T) { - int, err := CryptoRandomInt(255) - assert.True(t, int >= 0) - assert.True(t, int <= 255) + randInt, err := CryptoRandomInt(255) + assert.True(t, randInt >= 0) + assert.True(t, randInt <= 255) assert.NoError(t, err) } diff --git a/options/license/HPND-UC-export-US b/options/license/HPND-UC-export-US new file mode 100644 index 0000000000..015556c5f9 --- /dev/null +++ b/options/license/HPND-UC-export-US @@ -0,0 +1,10 @@ +Copyright (C) 1985, 1990 Regents of the University of California. + +Permission to use, copy, modify, and distribute this +software and its documentation for any purpose and without +fee is hereby granted, provided that the above copyright +notice appear in all copies. The University of California +makes no representations about the suitability of this +software for any purpose. It is provided "as is" without +express or implied warranty. Export of this software outside +of the United States of America may require an export license. diff --git a/options/license/NCL b/options/license/NCL new file mode 100644 index 0000000000..3bfb658c26 --- /dev/null +++ b/options/license/NCL @@ -0,0 +1,32 @@ +Copyright (c) 2004 the University Corporation for Atmospheric +Research ("UCAR"). All rights reserved. Developed by NCAR's +Computational and Information Systems Laboratory, UCAR, +www.cisl.ucar.edu. + +Redistribution and use of the Software in source and binary forms, +with or without modification, is permitted provided that the +following conditions are met: + +- Neither the names of NCAR's Computational and Information Systems +Laboratory, the University Corporation for Atmospheric Research, +nor the names of its sponsors or contributors may be used to +endorse or promote products derived from this Software without +specific prior written permission. + +- Redistributions of source code must retain the above copyright +notices, this list of conditions, and the disclaimer below. + +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions, and the disclaimer below in the +documentation and/or other materials provided with the +distribution. + +THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 57c44e4b26..82d7867168 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -1186,7 +1186,6 @@ action.blocked_user=Nelze provést akci, protože jste zablokování vlastníkem download_archive=Stáhnout repozitář more_operations=Další operace -no_desc=Bez popisu quick_guide=Krátká příručka clone_this_repo=Naklonovat tento repozitář cite_this_repo=Citovat tento repozitář diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index f591b75577..dd2b34a6f4 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -1187,7 +1187,6 @@ action.blocked_user=Die Aktion kann nicht ausgeführt werden, da du vom Reposito download_archive=Repository herunterladen more_operations=Weitere Operationen -no_desc=Keine Beschreibung quick_guide=Kurzanleitung clone_this_repo=Dieses Repository klonen cite_this_repo=Dieses Repository zitieren diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index 64db2348da..9553ba2f3a 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -1118,7 +1118,6 @@ fork=Fork download_archive=Λήψη Αποθετηρίου more_operations=Περισσότερες Λειτουργίες -no_desc=Χωρίς Περιγραφή quick_guide=Γρήγορος Οδηγός clone_this_repo=Κλωνοποίηση αυτού του αποθετηρίου cite_this_repo=Αναφορά σε αυτό το αποθετήριο diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index c602aba53d..fb591be393 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -436,6 +436,7 @@ oauth_signin_submit = Link Account oauth.signin.error = There was an error processing the authorization request. If this error persists, please contact the site administrator. oauth.signin.error.access_denied = The authorization request was denied. oauth.signin.error.temporarily_unavailable = Authorization failed because the authentication server is temporarily unavailable. Please try again later. +oauth_callback_unable_auto_reg = Auto Registration is enabled, but OAuth2 Provider %[1]s returned missing fields: %[2]s, unable to create an account automatically, please create or link to an account, or contact the site administrator. openid_connect_submit = Connect openid_connect_title = Connect to an existing account openid_connect_desc = The chosen OpenID URI is unknown. Associate it with a new account here. @@ -763,6 +764,8 @@ manage_themes = Select default theme manage_openid = Manage OpenID Addresses email_desc = Your primary email address will be used for notifications, password recovery and, provided that it is not hidden, web-based Git operations. theme_desc = This will be your default theme across the site. +theme_colorblindness_help = Colorblindness Theme Support +theme_colorblindness_prompt = Gitea just gets some themes with basic colorblindness support, which only have a few colors defined. The work is still in progress. More improvements could be done by defining more colors in the theme CSS files. primary = Primary activated = Activated requires_activation = Requires activation @@ -1193,7 +1196,6 @@ action.blocked_user = Cannot perform action because you are blocked by the repos download_archive = Download Repository more_operations = More Operations -no_desc = No Description quick_guide = Quick Guide clone_this_repo = Clone this repository cite_this_repo = Cite this repository diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index d1d680c14c..f3e2d93e80 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -1111,7 +1111,6 @@ fork=Fork download_archive=Descargar repositorio more_operations=Más operaciones -no_desc=Sin descripción quick_guide=Guía rápida clone_this_repo=Clonar este repositorio cite_this_repo=Citar este repositorio diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index 54a4911e5c..25a3361b3f 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -874,7 +874,6 @@ star=ستاره دار کن fork=انشعاب download_archive=دانلود مخزن -no_desc=بدون توضیح quick_guide=راهنمای سریع clone_this_repo=همسان‌سازی این مخزن create_new_repo_command=ایجاد یک مخزن جدید در خط فرمان diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index ace676281f..f29ad8c6cd 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -718,7 +718,6 @@ unstar=Poista tähti star=Tähti download_archive=Lataa repo -no_desc=Ei kuvausta quick_guide=Pikaopas clone_this_repo=Kloonaa tämä repo diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index 61a6a98379..b90039c003 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -1130,7 +1130,6 @@ fork=Bifurcation download_archive=Télécharger ce dépôt more_operations=Plus d'opérations -no_desc=Aucune description quick_guide=Introduction rapide clone_this_repo=Cloner ce dépôt cite_this_repo=Citer ce dépôt diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini index bddd6dd582..4e46227fea 100644 --- a/options/locale/locale_hu-HU.ini +++ b/options/locale/locale_hu-HU.ini @@ -656,7 +656,6 @@ star=Csillagozás fork=Tükrözés download_archive=Tároló letöltése -no_desc=Nincs leírás quick_guide=Gyors útmutató clone_this_repo=Tároló klónozása create_new_repo_command=Egy új tároló létrehozása a parancssorból diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini index 9261077831..fe3a6d0b08 100644 --- a/options/locale/locale_id-ID.ini +++ b/options/locale/locale_id-ID.ini @@ -570,7 +570,6 @@ star=Bintang fork=Garpu download_archive=Unduh Repositori -no_desc=Tidak ada Deskripsi quick_guide=Panduan Cepat clone_this_repo=Klon repositori ini create_new_repo_command=Membuat repositori baru pada baris perintah diff --git a/options/locale/locale_is-IS.ini b/options/locale/locale_is-IS.ini index a1116eddbc..f2fcfb7eda 100644 --- a/options/locale/locale_is-IS.ini +++ b/options/locale/locale_is-IS.ini @@ -647,7 +647,6 @@ star=Bæta við eftirlæti fork=Tvískipta download_archive=Hlaða Miður Geymslu -no_desc=Engin Lýsing quick_guide=Stuttar Leiðbeiningar clone_this_repo=Afrita þetta hugbúnaðarsafn create_new_repo_command=Að búa til nýja geymslu með skipanalínu diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index b15a78ccf4..eceda0faad 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -936,7 +936,6 @@ star=Vota fork=Forka download_archive=Scarica Repository -no_desc=Nessuna descrizione quick_guide=Guida rapida clone_this_repo=Clona questo repository create_new_repo_command=Creazione di un nuovo repository da riga di comando diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 707b37170a..e33a1ae173 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -1188,7 +1188,6 @@ action.blocked_user=リポジトリのオーナーがあなたをブロックし download_archive=リポジトリをダウンロード more_operations=その他の操作 -no_desc=説明なし quick_guide=クイック ガイド clone_this_repo=このリポジトリのクローンを作成 cite_this_repo=このリポジトリを引用 diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini index 3e9679575c..cf3188e9c0 100644 --- a/options/locale/locale_ko-KR.ini +++ b/options/locale/locale_ko-KR.ini @@ -606,7 +606,6 @@ star=좋아요 fork=포크 download_archive=저장소 다운로드 -no_desc=설명 없음 quick_guide=퀵 가이드 clone_this_repo=이 저장소 복제 create_new_repo_command=커맨드 라인에서 새 레포리지터리 생성 diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 6afb488414..3aed4bd6c5 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -1119,7 +1119,6 @@ fork=Atdalīts download_archive=Lejupielādēt repozitoriju more_operations=Vairāk darbību -no_desc=Nav apraksta quick_guide=Īsa pamācība clone_this_repo=Klonēt šo repozitoriju cite_this_repo=Citēt šo repozitoriju diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index b0b081db5d..f511bc5d23 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -934,7 +934,6 @@ star=Ster fork=Vork download_archive=Download repository -no_desc=Geen omschrijving quick_guide=Snelstart gids clone_this_repo=Kloon deze repository create_new_repo_command=Maak een nieuwe repository aan vanaf de console diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index fd5db4109f..b5a758514e 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -877,7 +877,6 @@ star=Polub fork=Forkuj download_archive=Pobierz repozytorium -no_desc=Brak opisu quick_guide=Skrócona instrukcja clone_this_repo=Klonuj repozytorium create_new_repo_command=Tworzenie nowego repozytorium z linii poleceń diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 5a058c807b..2e23cde801 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -1115,7 +1115,6 @@ fork=Fork download_archive=Baixar repositório more_operations=Mais Operações -no_desc=Nenhuma descrição quick_guide=Guia Rápido clone_this_repo=Clonar este repositório cite_this_repo=Citar este repositório diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index a90927a255..444f784af9 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -763,6 +763,8 @@ manage_themes=Escolher o tema padrão manage_openid=Gerir endereços OpenID email_desc=O seu endereço de email principal irá ser usado para notificações, recuperação de senha e, desde que não esteja oculto, operações Git baseados na web. theme_desc=Este será o seu tema padrão em todo o sítio. +theme_colorblindness_help=Suporte a temas para daltónicos +theme_colorblindness_prompt=O Gitea acabou de obter alguns temas com suporte básico para daltónicos que têm apenas algumas cores definidas. O trabalho ainda está em andamento. Poderiam ser feitos mais melhoramentos se fossem definidas mais cores nos ficheiros CSS do tema. primary=Principal activated=Em uso requires_activation=Tem que ser habilitado @@ -1193,7 +1195,6 @@ action.blocked_user=Não pode realizar a operação porque foi bloqueado/a pelo/ download_archive=Descarregar repositório more_operations=Mais operações -no_desc=Sem descrição quick_guide=Guia rápido clone_this_repo=Clonar este repositório cite_this_repo=Citar este repositório @@ -2357,7 +2358,7 @@ settings.protected_branch.delete_rule=Eliminar regra settings.protected_branch_can_push=Permitir envios? settings.protected_branch_can_push_yes=Pode enviar settings.protected_branch_can_push_no=Não pode enviar -settings.branch_protection=Salvaguarda do ramo '%s' +settings.branch_protection=Regras de salvaguarda do ramo '%s' settings.protect_this_branch=Habilitar salvaguarda do ramo settings.protect_this_branch_desc=Impede a eliminação e restringe envios e integrações do Git no ramo. settings.protect_disable_push=Desabilitar envios @@ -2401,7 +2402,7 @@ settings.protect_patterns=Padrões settings.protect_protected_file_patterns=Padrões de ficheiros protegidos (separados com ponto e vírgula ';'): settings.protect_protected_file_patterns_desc=Ficheiros protegidos não podem ser modificados imediatamente, mesmo que o utilizador tenha direitos para adicionar, editar ou eliminar ficheiros neste ramo. Múltiplos padrões podem ser separados com ponto e vírgula (';'). Veja a documentação em github.com/gobwas/glob para ver a sintaxe. Exemplos: .drone.yml, /docs/**/*.txt. settings.protect_unprotected_file_patterns=Padrões de ficheiros desprotegidos (separados com ponto e vírgula ';'): -settings.protect_unprotected_file_patterns_desc=Ficheiros desprotegidos que podem ser modificados imediatamente se o utilizador tiver direitos de escrita, contornando a restrição no envio. Múltiplos padrões podem ser separados com ponto e vírgula (';'). Veja a documentação em github.com/gobwas/glob para ver a sintaxe. Exemplos: .drone.yml, /docs/**/*.txt. +settings.protect_unprotected_file_patterns_desc=Ficheiros desprotegidos que podem ser modificados imediatamente se o utilizador tiver direitos de escrita, contornando a restrição no envio. Padrões múltiplos podem ser separados com ponto e vírgula (';'). Veja a documentação em github.com/gobwas/glob para ver a sintaxe. Exemplos: .drone.yml, /docs/**/*.txt. settings.add_protected_branch=Habilitar salvaguarda settings.delete_protected_branch=Desabilitar salvaguarda settings.update_protect_branch_success=A salvaguarda do ramo "%s" foi modificada. @@ -2417,7 +2418,7 @@ settings.block_outdated_branch=Bloquear integração se o pedido de integração settings.block_outdated_branch_desc=A integração não será possível quando o ramo de topo estiver abaixo do ramo base. settings.default_branch_desc=Escolha um ramo do repositório como sendo o predefinido para pedidos de integração e cometimentos: settings.merge_style_desc=Estilos de integração -settings.default_merge_style_desc=Tipo de integração predefinido para pedidos de integração: +settings.default_merge_style_desc=Tipo de integração predefinido settings.choose_branch=Escolha um ramo… settings.no_protected_branch=Não existem ramos protegidos. settings.edit_protected_branch=Editar @@ -2787,7 +2788,7 @@ self_check=Auto-verificação identity_access=Identidade e acesso users=Contas de utilizador organizations=Organizações -assets=Recursos de código +assets=Recursos do código-fonte repositories=Repositórios hooks=Automatismos web integrations=Integrações @@ -2868,14 +2869,14 @@ dashboard.mspan_structures_obtained=Estruturas MSpan obtidas dashboard.mcache_structures_usage=Uso das estruturas MCache dashboard.mcache_structures_obtained=Estruturas MCache obtidas dashboard.profiling_bucket_hash_table_obtained=Perfil obtido da tabela de hash do balde -dashboard.gc_metadata_obtained=Metadados da recolha de lixo obtidos +dashboard.gc_metadata_obtained=Metadados obtidos da recolha de lixo dashboard.other_system_allocation_obtained=Outras alocações de sistema obtidas dashboard.next_gc_recycle=Próxima reciclagem da recolha de lixo dashboard.last_gc_time=Tempo decorrido desde a última recolha de lixo dashboard.total_gc_time=Pausa total da recolha de lixo dashboard.total_gc_pause=Pausa total da recolha de lixo dashboard.last_gc_pause=Última pausa da recolha de lixo -dashboard.gc_times=Tempos da recolha de lixo +dashboard.gc_times=N.º de recolhas de lixo dashboard.delete_old_actions=Eliminar todas as operações antigas da base de dados dashboard.delete_old_actions.started=Foi iniciado o processo de eliminação de todas as operações antigas da base de dados. dashboard.update_checker=Verificador de novas versões @@ -3024,7 +3025,7 @@ auths.attribute_surname=Atributo do Sobrenome auths.attribute_mail=Atributo do email auths.attribute_ssh_public_key=Atributo da chave pública SSH auths.attribute_avatar=Atributo do avatar -auths.attributes_in_bind=Buscar os atributos no contexto de Bind DN +auths.attributes_in_bind=Buscar atributos no contexto do Bind DN auths.allow_deactivate_all=Permitir que um resultado de pesquisa vazio desabilite todos os utilizadores auths.use_paged_search=Usar pesquisa paginada auths.search_page_size=Tamanho da página @@ -3223,7 +3224,7 @@ config.session_config=Configuração de sessão config.session_provider=Fornecedor da sessão config.provider_config=Configuração do fornecedor config.cookie_name=Nome do cookie -config.gc_interval_time=Intervalo da recolha do lixo +config.gc_interval_time=Intervalo de tempo entre recolhas do lixo config.session_life_time=Tempo de vida da sessão config.https_only=Apenas HTTPS config.cookie_life_time=Tempo de vida do cookie diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index d4098aa952..df6df4cf95 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -1098,7 +1098,6 @@ fork=Форкнуть download_archive=Скачать репозиторий more_operations=Ещё действия -no_desc=Нет описания quick_guide=Краткое руководство clone_this_repo=Клонировать репозиторий cite_this_repo=Сослаться на этот репозиторий diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini index 05538af971..15bbcfebb2 100644 --- a/options/locale/locale_si-LK.ini +++ b/options/locale/locale_si-LK.ini @@ -846,7 +846,6 @@ star=ස්ටාර් fork=දෙබලක download_archive=කෝෂ්ඨය බාගන්න -no_desc=සවිස්තරයක් නැත quick_guide=ඉක්මන් මාර්ගෝපදේශය clone_this_repo=මෙම ගබඩාව පරිගණක ක්රිඩාවට සමාන create_new_repo_command=විධාන රේඛාවේ නව ගබඩාවක් නිර්මාණය කිරීම diff --git a/options/locale/locale_sk-SK.ini b/options/locale/locale_sk-SK.ini index b468b55283..be1efa22bc 100644 --- a/options/locale/locale_sk-SK.ini +++ b/options/locale/locale_sk-SK.ini @@ -964,7 +964,6 @@ star=Hviezdička download_archive=Stiahnuť repozitár more_operations=Viac operácií -no_desc=Bez popisu quick_guide=Rýchly sprievodca clone_this_repo=Klonovať tento repozitár create_new_repo_command=Vytvoriť nový repozitár v príkazovom riadku diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index 5fe6288ad6..b975636cb8 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -718,7 +718,6 @@ star=Stjärnmärk fork=Förgrening download_archive=Ladda Ned Utvecklingskatalogen -no_desc=Ingen beskrivning quick_guide=Snabbguide clone_this_repo=Klona detta repo create_new_repo_command=Skapa en ny utvecklingskatalog på kommandoraden diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 59c931afd2..be89113f0d 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -1193,7 +1193,6 @@ action.blocked_user=İşlem gerçekleştirilemiyor, depo sahibi tarafından enge download_archive=Depoyu İndir more_operations=Daha Fazla İşlem -no_desc=Açıklama Yok quick_guide=Hızlı Başlangıç Kılavuzu clone_this_repo=Bu depoyu klonla cite_this_repo=Bu depoya atıf ver diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 613f39b3c9..3e38973e02 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -882,7 +882,6 @@ star=В обрані fork=Форк download_archive=Скачати репозиторій -no_desc=Без опису quick_guide=Короткий посібник clone_this_repo=Кнонувати цей репозиторій create_new_repo_command=Створити новий репозиторій з командного рядка diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index aeba11fb9a..a76ecafd62 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -1193,7 +1193,6 @@ action.blocked_user=无法执行操作,因为您已被仓库所有者屏蔽。 download_archive=下载此仓库 more_operations=更多操作 -no_desc=暂无描述 quick_guide=快速帮助 clone_this_repo=克隆当前仓库 cite_this_repo=引用此仓库 diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini index 2dbdeb2bae..fb16b82fc5 100644 --- a/options/locale/locale_zh-HK.ini +++ b/options/locale/locale_zh-HK.ini @@ -344,7 +344,6 @@ unstar=取消收藏 star=收藏 fork=複製 -no_desc=暫無描述 quick_guide=快速幫助 clone_this_repo=複製當前儲存庫 create_new_repo_command=從命令列建立新儲存庫。 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 3e7bd4ae20..7823426990 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -1016,7 +1016,6 @@ fork=Fork download_archive=下載此儲存庫 more_operations=更多操作 -no_desc=暫無描述 quick_guide=快速幫助 clone_this_repo=Clone 此儲存庫 cite_this_repo=引用此儲存庫 diff --git a/package-lock.json b/package-lock.json index 61d86f6b7c..780689a0a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "esbuild-loader": "4.1.0", "escape-goat": "4.0.0", "fast-glob": "3.3.2", - "htmx.org": "1.9.11", + "htmx.org": "1.9.12", "idiomorph": "0.3.0", "jquery": "3.7.1", "katex": "0.16.10", @@ -6728,9 +6728,9 @@ } }, "node_modules/htmx.org": { - "version": "1.9.11", - "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.11.tgz", - "integrity": "sha512-WlVuICn8dfNOOgYmdYzYG8zSnP3++AdHkMHooQAzGZObWpVXYathpz/I37ycF4zikR6YduzfCvEcxk20JkIUsw==" + "version": "1.9.12", + "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.12.tgz", + "integrity": "sha512-VZAohXyF7xPGS52IM8d1T1283y+X4D+Owf3qY1NZ9RuBypyu9l8cGsxUMAG5fEAb/DhT7rDoJ9Hpu5/HxFD3cw==" }, "node_modules/human-signals": { "version": "5.0.0", diff --git a/package.json b/package.json index ff1ae4d49e..b0cb67ed4a 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "esbuild-loader": "4.1.0", "escape-goat": "4.0.0", "fast-glob": "3.3.2", - "htmx.org": "1.9.11", + "htmx.org": "1.9.12", "idiomorph": "0.3.0", "jquery": "3.7.1", "katex": "0.16.10", diff --git a/routers/api/actions/artifacts.go b/routers/api/actions/artifacts.go index 56276c45d2..3e717b8d8f 100644 --- a/routers/api/actions/artifacts.go +++ b/routers/api/actions/artifacts.go @@ -144,7 +144,6 @@ func ArtifactContexter() func(next http.Handler) http.Handler { var task *actions.ActionTask if err == nil { - task, err = actions.GetTaskByID(req.Context(), tID) if err != nil { log.Error("Error runner api getting task by ID: %v", err) diff --git a/routers/api/packages/alpine/alpine.go b/routers/api/packages/alpine/alpine.go index dae9c3dfcb..5127319807 100644 --- a/routers/api/packages/alpine/alpine.go +++ b/routers/api/packages/alpine/alpine.go @@ -96,12 +96,12 @@ func UploadPackageFile(ctx *context.Context) { return } - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } - if close { + if needToClose { defer upload.Close() } diff --git a/routers/api/packages/conan/conan.go b/routers/api/packages/conan/conan.go index c45e085a4d..07ea3eda34 100644 --- a/routers/api/packages/conan/conan.go +++ b/routers/api/packages/conan/conan.go @@ -310,12 +310,12 @@ func uploadFile(ctx *context.Context, fileFilter container.Set[string], fileKey return } - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusBadRequest, err) return } - if close { + if needToClose { defer upload.Close() } diff --git a/routers/api/packages/conda/conda.go b/routers/api/packages/conda/conda.go index 30c80fc15e..c7e4544d52 100644 --- a/routers/api/packages/conda/conda.go +++ b/routers/api/packages/conda/conda.go @@ -174,12 +174,12 @@ func EnumeratePackages(ctx *context.Context) { } func UploadPackageFile(ctx *context.Context) { - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } - if close { + if needToClose { defer upload.Close() } diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go index e519766142..2cb16daebc 100644 --- a/routers/api/packages/container/container.go +++ b/routers/api/packages/container/container.go @@ -385,9 +385,9 @@ func EndUploadBlob(ctx *context.Context) { } return } - close := true + doClose := true defer func() { - if close { + if doClose { uploader.Close() } }() @@ -427,7 +427,7 @@ func EndUploadBlob(ctx *context.Context) { apiError(ctx, http.StatusInternalServerError, err) return } - close = false + doClose = false if err := container_service.RemoveBlobUploadByID(ctx, uploader.ID); err != nil { apiError(ctx, http.StatusInternalServerError, err) diff --git a/routers/api/packages/cran/cran.go b/routers/api/packages/cran/cran.go index 2cec75294f..f1d616724a 100644 --- a/routers/api/packages/cran/cran.go +++ b/routers/api/packages/cran/cran.go @@ -151,12 +151,12 @@ func UploadBinaryPackageFile(ctx *context.Context) { } func uploadPackageFile(ctx *context.Context, compositeKey string, properties map[string]string) { - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusBadRequest, err) return } - if close { + if needToClose { defer upload.Close() } diff --git a/routers/api/packages/debian/debian.go b/routers/api/packages/debian/debian.go index 241de3ac5d..8c05476cbc 100644 --- a/routers/api/packages/debian/debian.go +++ b/routers/api/packages/debian/debian.go @@ -127,12 +127,12 @@ func UploadPackageFile(ctx *context.Context) { return } - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } - if close { + if needToClose { defer upload.Close() } diff --git a/routers/api/packages/generic/generic.go b/routers/api/packages/generic/generic.go index 8232931134..e66f3ee676 100644 --- a/routers/api/packages/generic/generic.go +++ b/routers/api/packages/generic/generic.go @@ -90,12 +90,12 @@ func UploadPackage(ctx *context.Context) { return } - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } - if close { + if needToClose { defer upload.Close() } diff --git a/routers/api/packages/goproxy/goproxy.go b/routers/api/packages/goproxy/goproxy.go index d658066bb4..56a07dbd43 100644 --- a/routers/api/packages/goproxy/goproxy.go +++ b/routers/api/packages/goproxy/goproxy.go @@ -154,12 +154,12 @@ func resolvePackage(ctx *context.Context, ownerID int64, name, version string) ( } func UploadPackage(ctx *context.Context) { - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } - if close { + if needToClose { defer upload.Close() } diff --git a/routers/api/packages/nuget/nuget.go b/routers/api/packages/nuget/nuget.go index 09156ece6b..26b0ae226e 100644 --- a/routers/api/packages/nuget/nuget.go +++ b/routers/api/packages/nuget/nuget.go @@ -594,13 +594,13 @@ func UploadSymbolPackage(ctx *context.Context) { func processUploadedFile(ctx *context.Context, expectedType nuget_module.PackageType) (*nuget_module.Package, *packages_module.HashedBuffer, []io.Closer) { closables := make([]io.Closer, 0, 2) - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusBadRequest, err) return nil, nil, closables } - if close { + if needToClose { closables = append(closables, upload) } diff --git a/routers/api/packages/rpm/rpm.go b/routers/api/packages/rpm/rpm.go index 4de361c214..c59366992c 100644 --- a/routers/api/packages/rpm/rpm.go +++ b/routers/api/packages/rpm/rpm.go @@ -117,12 +117,12 @@ func GetRepositoryFile(ctx *context.Context) { } func UploadPackageFile(ctx *context.Context) { - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } - if close { + if needToClose { defer upload.Close() } diff --git a/routers/api/packages/rubygems/rubygems.go b/routers/api/packages/rubygems/rubygems.go index d2fbcd01f0..ba5f4de080 100644 --- a/routers/api/packages/rubygems/rubygems.go +++ b/routers/api/packages/rubygems/rubygems.go @@ -197,12 +197,12 @@ func DownloadPackageFile(ctx *context.Context) { // UploadPackageFile adds a file to the package. If the package does not exist, it gets created. func UploadPackageFile(ctx *context.Context) { - upload, close, err := ctx.UploadStream() + upload, needToClose, err := ctx.UploadStream() if err != nil { apiError(ctx, http.StatusBadRequest, err) return } - if close { + if needToClose { defer upload.Close() } diff --git a/routers/api/v1/repo/compare.go b/routers/api/v1/repo/compare.go index 549b9b7fa9..cfd61d768c 100644 --- a/routers/api/v1/repo/compare.go +++ b/routers/api/v1/repo/compare.go @@ -16,7 +16,7 @@ import ( // CompareDiff compare two branches or commits func CompareDiff(ctx *context.APIContext) { - // swagger:operation GET /repos/{owner}/{repo}/compare/{basehead} Get commit comparison information + // swagger:operation GET /repos/{owner}/{repo}/compare/{basehead} repository repoCompareDiff // --- // summary: Get commit comparison information // produces: diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 5e173abf88..dfe6d31f74 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -217,7 +217,6 @@ func SearchIssues(ctx *context.APIContext) { var includedAnyLabels []int64 { - labels := ctx.FormTrim("labels") var includedLabelNames []string if len(labels) > 0 { diff --git a/routers/api/v1/repo/mirror.go b/routers/api/v1/repo/mirror.go index 864644e1ef..2a896de4fe 100644 --- a/routers/api/v1/repo/mirror.go +++ b/routers/api/v1/repo/mirror.go @@ -180,7 +180,6 @@ func ListPushMirrors(ctx *context.APIContext) { if err == nil { responsePushMirrors = append(responsePushMirrors, m) } - } ctx.SetLinkHeader(len(responsePushMirrors), utils.GetListOptions(ctx).PageSize) ctx.SetTotalCountHeader(count) diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index e43366ff14..4129f94ac3 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -1061,7 +1061,6 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) isSameRepo = true headUser = ctx.Repo.Owner headBranch = headInfos[0] - } else if len(headInfos) == 2 { headUser, err = user_model.GetUserByName(ctx, headInfos[0]) if err != nil { @@ -1075,18 +1074,16 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) headBranch = headInfos[1] // The head repository can also point to the same repo isSameRepo = ctx.Repo.Owner.ID == headUser.ID - } else { ctx.NotFound() return nil, nil, nil, nil, "", "" } ctx.Repo.PullRequest.SameRepo = isSameRepo - log.Info("Base branch: %s", baseBranch) - log.Info("Repo path: %s", ctx.Repo.GitRepo.Path) + log.Trace("Repo path: %q, base branch: %q, head branch: %q", ctx.Repo.GitRepo.Path, baseBranch, headBranch) // Check if base branch is valid. - if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) { - ctx.NotFound("IsBranchExist") + if !ctx.Repo.GitRepo.IsBranchExist(baseBranch) && !ctx.Repo.GitRepo.IsTagExist(baseBranch) { + ctx.NotFound("BaseNotExist") return nil, nil, nil, nil, "", "" } @@ -1149,7 +1146,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) } // Check if head branch is valid. - if !headGitRepo.IsBranchExist(headBranch) { + if !headGitRepo.IsBranchExist(headBranch) && !headGitRepo.IsTagExist(headBranch) { headGitRepo.Close() ctx.NotFound() return nil, nil, nil, nil, "", "" diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go index 17bb2085b6..b527e90f10 100644 --- a/routers/api/v1/repo/pull_review.go +++ b/routers/api/v1/repo/pull_review.go @@ -728,7 +728,6 @@ func apiReviewRequest(ctx *context.APIContext, opts api.PullReviewRequestOptions } if ctx.Repo.Repository.Owner.IsOrganization() && len(opts.TeamReviewers) > 0 { - teamReviewers := make([]*organization.Team, 0, len(opts.TeamReviewers)) for _, t := range opts.TeamReviewers { var teamReviewer *organization.Team diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 2ac0b7ebd1..7f35a7fe41 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -1084,7 +1084,6 @@ func updateMirror(ctx *context.APIContext, opts api.EditRepoOption) error { // update MirrorInterval if opts.MirrorInterval != nil { - // MirrorInterval should be a duration interval, err := time.ParseDuration(*opts.MirrorInterval) if err != nil { diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go index f18ea087c4..c7065c1d9d 100644 --- a/routers/api/v1/repo/wiki.go +++ b/routers/api/v1/repo/wiki.go @@ -478,7 +478,6 @@ func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit) { wikiRepo, err := gitrepo.OpenWikiRepository(ctx, ctx.Repo.Repository) if err != nil { - if git.IsErrNotExist(err) || err.Error() == "no such file or directory" { ctx.NotFound(err) } else { diff --git a/routers/init.go b/routers/init.go index aaf95920c2..030ef3c740 100644 --- a/routers/init.go +++ b/routers/init.go @@ -5,6 +5,7 @@ package routers import ( "context" + "net/http" "reflect" "runtime" @@ -25,6 +26,7 @@ import ( "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/modules/web/routing" actions_router "code.gitea.io/gitea/routers/api/actions" packages_router "code.gitea.io/gitea/routers/api/packages" apiv1 "code.gitea.io/gitea/routers/api/v1" @@ -202,5 +204,9 @@ func NormalRoutes() *web.Route { r.Mount(prefix, actions_router.ArtifactsV4Routes(prefix)) } + r.NotFound(func(w http.ResponseWriter, req *http.Request) { + routing.UpdateFuncInfo(req.Context(), routing.GetFuncInfo(http.NotFound, "GlobalNotFound")) + http.NotFound(w, req) + }) return r } diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index 6f6004baf8..f35eb77d42 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -198,7 +198,6 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r UserMsg: fmt.Sprintf("branch %s is protected from force push", branchName), }) return - } } diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index 9ef32ebdb1..7c873796fe 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -382,17 +382,17 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe return setting.AppSubURL + "/" } -func getUserName(gothUser *goth.User) (string, error) { +// extractUserNameFromOAuth2 tries to extract a normalized username from the given OAuth2 user. +// It returns ("", nil) if the required field doesn't exist. +func extractUserNameFromOAuth2(gothUser *goth.User) (string, error) { switch setting.OAuth2Client.Username { case setting.OAuth2UsernameEmail: - return user_model.NormalizeUserName(strings.Split(gothUser.Email, "@")[0]) + return user_model.NormalizeUserName(gothUser.Email) case setting.OAuth2UsernamePreferredUsername: - preferredUsername, exists := gothUser.RawData["preferred_username"] - if exists { - return user_model.NormalizeUserName(preferredUsername.(string)) - } else { - return "", fmt.Errorf("preferred_username is missing in received user data but configured as username source for user_id %q. Check if OPENID_CONNECT_SCOPES contains profile", gothUser.UserID) + if preferredUsername, ok := gothUser.RawData["preferred_username"].(string); ok { + return user_model.NormalizeUserName(preferredUsername) } + return "", nil case setting.OAuth2UsernameNickname: return user_model.NormalizeUserName(gothUser.NickName) default: // OAuth2UsernameUserid diff --git a/routers/web/auth/auth_test.go b/routers/web/auth/auth_test.go index c6afbf877c..45525a5c6f 100644 --- a/routers/web/auth/auth_test.go +++ b/routers/web/auth/auth_test.go @@ -8,12 +8,31 @@ import ( "net/url" "testing" + auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/session" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/contexttest" + "github.com/markbates/goth" + "github.com/markbates/goth/gothic" "github.com/stretchr/testify/assert" ) +func addOAuth2Source(t *testing.T, authName string, cfg oauth2.Source) { + cfg.Provider = util.IfZero(cfg.Provider, "gitea") + err := auth_model.CreateSource(db.DefaultContext, &auth_model.Source{ + Type: auth_model.OAuth2, + Name: authName, + IsActive: true, + Cfg: &cfg, + }) + assert.NoError(t, err) +} + func TestUserLogin(t *testing.T) { ctx, resp := contexttest.MockContext(t, "/user/login") SignIn(ctx) @@ -41,3 +60,24 @@ func TestUserLogin(t *testing.T) { SignIn(ctx) assert.Equal(t, "/", test.RedirectURL(resp)) } + +func TestSignUpOAuth2ButMissingFields(t *testing.T) { + defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() + defer test.MockVariableValue(&gothic.CompleteUserAuth, func(res http.ResponseWriter, req *http.Request) (goth.User, error) { + return goth.User{Provider: "dummy-auth-source", UserID: "dummy-user"}, nil + })() + + addOAuth2Source(t, "dummy-auth-source", oauth2.Source{}) + + mockOpt := contexttest.MockContextOption{SessionStore: session.NewMockStore("dummy-sid")} + ctx, resp := contexttest.MockContext(t, "/user/oauth2/dummy-auth-source/callback?code=dummy-code", mockOpt) + ctx.SetParams("provider", "dummy-auth-source") + SignInOAuthCallback(ctx) + assert.Equal(t, http.StatusSeeOther, resp.Code) + assert.Equal(t, "/user/link_account", test.RedirectURL(resp)) + + // then the user will be redirected to the link account page, and see a message about the missing fields + ctx, _ = contexttest.MockContext(t, "/user/link_account", mockOpt) + LinkAccount(ctx) + assert.EqualValues(t, "auth.oauth_callback_unable_auto_reg:dummy-auth-source,email", ctx.Data["AutoRegistrationFailedPrompt"]) +} diff --git a/routers/web/auth/linkaccount.go b/routers/web/auth/linkaccount.go index f744a57a43..24130df634 100644 --- a/routers/web/auth/linkaccount.go +++ b/routers/web/auth/linkaccount.go @@ -48,23 +48,27 @@ func LinkAccount(ctx *context.Context) { ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin" ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup" - gothUser := ctx.Session.Get("linkAccountGothUser") - if gothUser == nil { - ctx.ServerError("UserSignIn", errors.New("not in LinkAccount session")) + gothUser, ok := ctx.Session.Get("linkAccountGothUser").(goth.User) + if !ok { + // no account in session, so just redirect to the login page, then the user could restart the process + ctx.Redirect(setting.AppSubURL + "/user/login") return } - gu, _ := gothUser.(goth.User) - uname, err := getUserName(&gu) + if missingFields, ok := gothUser.RawData["__giteaAutoRegMissingFields"].([]string); ok { + ctx.Data["AutoRegistrationFailedPrompt"] = ctx.Tr("auth.oauth_callback_unable_auto_reg", gothUser.Provider, strings.Join(missingFields, ",")) + } + + uname, err := extractUserNameFromOAuth2(&gothUser) if err != nil { ctx.ServerError("UserSignIn", err) return } - email := gu.Email + email := gothUser.Email ctx.Data["user_name"] = uname ctx.Data["email"] = email - if len(email) != 0 { + if email != "" { u, err := user_model.GetUserByEmail(ctx, email) if err != nil && !user_model.IsErrUserNotExist(err) { ctx.ServerError("UserSignIn", err) @@ -73,7 +77,7 @@ func LinkAccount(ctx *context.Context) { if u != nil { ctx.Data["user_exists"] = true } - } else if len(uname) != 0 { + } else if uname != "" { u, err := user_model.GetUserByName(ctx, uname) if err != nil && !user_model.IsErrUserNotExist(err) { ctx.ServerError("UserSignIn", err) diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 3189d1372e..c9cb7859cd 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -934,7 +934,7 @@ func SignInOAuthCallback(ctx *context.Context) { if u == nil { if ctx.Doer != nil { - // attach user to already logged in user + // attach user to the current signed-in user err = externalaccount.LinkAccountToUser(ctx, ctx.Doer, gothUser) if err != nil { ctx.ServerError("UserLinkAccount", err) @@ -952,23 +952,32 @@ func SignInOAuthCallback(ctx *context.Context) { if gothUser.Email == "" { missingFields = append(missingFields, "email") } - if setting.OAuth2Client.Username == setting.OAuth2UsernameNickname && gothUser.NickName == "" { - missingFields = append(missingFields, "nickname") - } - if len(missingFields) > 0 { - log.Error("OAuth2 Provider %s returned empty or missing fields: %s", authSource.Name, missingFields) - if authSource.IsOAuth2() && authSource.Cfg.(*oauth2.Source).Provider == "openidConnect" { - log.Error("You may need to change the 'OPENID_CONNECT_SCOPES' setting to request all required fields") - } - err = fmt.Errorf("OAuth2 Provider %s returned empty or missing fields: %s", authSource.Name, missingFields) - ctx.ServerError("CreateUser", err) - return - } - uname, err := getUserName(&gothUser) + uname, err := extractUserNameFromOAuth2(&gothUser) if err != nil { ctx.ServerError("UserSignIn", err) return } + if uname == "" { + if setting.OAuth2Client.Username == setting.OAuth2UsernameNickname { + missingFields = append(missingFields, "nickname") + } else if setting.OAuth2Client.Username == setting.OAuth2UsernamePreferredUsername { + missingFields = append(missingFields, "preferred_username") + } // else: "UserID" and "Email" have been handled above separately + } + if len(missingFields) > 0 { + log.Error(`OAuth2 auto registration (ENABLE_AUTO_REGISTRATION) is enabled but OAuth2 provider %q doesn't return required fields: %s. `+ + `Suggest to: disable auto registration, or make OPENID_CONNECT_SCOPES (for OpenIDConnect) / Authentication Source Scopes (for Admin panel) to request all required fields, and the fields shouldn't be empty.`, + authSource.Name, strings.Join(missingFields, ",")) + // The RawData is the only way to pass the missing fields to the another page at the moment, other ways all have various problems: + // by session or cookie: difficult to clean or reset; by URL: could be injected with uncontrollable content; by ctx.Flash: the link_account page is a mess ... + // Since the RawData is for the provider's data, so we need to use our own prefix here to avoid conflict. + if gothUser.RawData == nil { + gothUser.RawData = make(map[string]any) + } + gothUser.RawData["__giteaAutoRegMissingFields"] = missingFields + showLinkingLogin(ctx, gothUser) + return + } u = &user_model.User{ Name: uname, FullName: gothUser.Name, diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 41989589be..3909a64be6 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -67,6 +67,9 @@ type ViewResponse struct { CanRerun bool `json:"canRerun"` CanDeleteArtifact bool `json:"canDeleteArtifact"` Done bool `json:"done"` + WorkflowID string `json:"workflowID"` + WorkflowLink string `json:"workflowLink"` + IsSchedule bool `json:"isSchedule"` Jobs []*ViewJob `json:"jobs"` Commit ViewCommit `json:"commit"` } `json:"run"` @@ -90,12 +93,10 @@ type ViewJob struct { } type ViewCommit struct { - LocaleCommit string `json:"localeCommit"` - LocalePushedBy string `json:"localePushedBy"` - ShortSha string `json:"shortSHA"` - Link string `json:"link"` - Pusher ViewUser `json:"pusher"` - Branch ViewBranch `json:"branch"` + ShortSha string `json:"shortSHA"` + Link string `json:"link"` + Pusher ViewUser `json:"pusher"` + Branch ViewBranch `json:"branch"` } type ViewUser struct { @@ -151,6 +152,9 @@ func ViewPost(ctx *context_module.Context) { resp.State.Run.CanRerun = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions) resp.State.Run.CanDeleteArtifact = run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions) resp.State.Run.Done = run.Status.IsDone() + resp.State.Run.WorkflowID = run.WorkflowID + resp.State.Run.WorkflowLink = run.WorkflowLink() + resp.State.Run.IsSchedule = run.IsSchedule() resp.State.Run.Jobs = make([]*ViewJob, 0, len(jobs)) // marshal to '[]' instead fo 'null' in json resp.State.Run.Status = run.Status.String() for _, v := range jobs { @@ -172,12 +176,10 @@ func ViewPost(ctx *context_module.Context) { Link: run.RefLink(), } resp.State.Run.Commit = ViewCommit{ - LocaleCommit: ctx.Locale.TrString("actions.runs.commit"), - LocalePushedBy: ctx.Locale.TrString("actions.runs.pushed_by"), - ShortSha: base.ShortSha(run.CommitSHA), - Link: fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA), - Pusher: pusher, - Branch: branch, + ShortSha: base.ShortSha(run.CommitSHA), + Link: fmt.Sprintf("%s/commit/%s", run.Repo.Link(), run.CommitSHA), + Pusher: pusher, + Branch: branch, } var task *actions_model.ActionTask @@ -644,7 +646,6 @@ func ArtifactsDownloadView(ctx *context_module.Context) { writer := zip.NewWriter(ctx.Resp) defer writer.Close() for _, art := range artifacts { - f, err := storage.ActionsArtifacts.Open(art.StoragePath) if err != nil { ctx.Error(http.StatusInternalServerError, err.Error()) diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 6ab9e31277..de6ef9e93b 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -933,7 +933,6 @@ func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleFiles } } } - } if template.Ref != "" && !strings.HasPrefix(template.Ref, "refs/") { // Assume that the ref intended is always a branch - for tags users should use refs/tags/ @@ -1681,7 +1680,6 @@ func ViewIssue(ctx *context.Context) { if comment.ProjectID > 0 && comment.Project == nil { comment.Project = ghostProject } - } else if comment.Type == issues_model.CommentTypeAssignees || comment.Type == issues_model.CommentTypeReviewRequest { if err = comment.LoadAssigneeUserAndTeam(ctx); err != nil { ctx.ServerError("LoadAssigneeUserAndTeam", err) @@ -2610,7 +2608,6 @@ func SearchIssues(ctx *context.Context) { var includedAnyLabels []int64 { - labels := ctx.FormTrim("labels") var includedLabelNames []string if len(labels) > 0 { @@ -2994,7 +2991,6 @@ func NewComment(ctx *context.Context) { if (ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) || (ctx.IsSigned && issue.IsPoster(ctx.Doer.ID))) && (form.Status == "reopen" || form.Status == "close") && !(issue.IsPull && issue.PullRequest.HasMerged) { - // Duplication and conflict check should apply to reopen pull request. var pr *issues_model.PullRequest @@ -3153,13 +3149,10 @@ func UpdateCommentContent(ctx *context.Context) { } oldContent := comment.Content - comment.Content = ctx.FormString("content") - if len(comment.Content) == 0 { - ctx.JSON(http.StatusOK, map[string]any{ - "content": "", - }) - return - } + newContent := ctx.FormString("content") + + // allow to save empty content + comment.Content = newContent if err = issue_service.UpdateComment(ctx, comment, ctx.Doer, oldContent); err != nil { if errors.Is(err, user_model.ErrBlockedUser) { ctx.JSONError(ctx.Tr("repo.issues.comment.blocked_user")) @@ -3182,21 +3175,27 @@ func UpdateCommentContent(ctx *context.Context) { } } - content, err := markdown.RenderString(&markup.RenderContext{ - Links: markup.Links{ - Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ? - }, - Metas: ctx.Repo.Repository.ComposeMetas(ctx), - GitRepo: ctx.Repo.GitRepo, - Ctx: ctx, - }, comment.Content) - if err != nil { - ctx.ServerError("RenderString", err) - return + var renderedContent template.HTML + if comment.Content != "" { + renderedContent, err = markdown.RenderString(&markup.RenderContext{ + Links: markup.Links{ + Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ? + }, + Metas: ctx.Repo.Repository.ComposeMetas(ctx), + GitRepo: ctx.Repo.GitRepo, + Ctx: ctx, + }, comment.Content) + if err != nil { + ctx.ServerError("RenderString", err) + return + } + } else { + contentEmpty := fmt.Sprintf(`%s`, ctx.Tr("repo.issues.no_content")) + renderedContent = template.HTML(contentEmpty) } ctx.JSON(http.StatusOK, map[string]any{ - "content": content, + "content": renderedContent, "attachments": attachmentsHTML(ctx, comment.Attachments, comment.Content), }) } diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index a0a8e5410c..acdba4bcdc 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -443,7 +443,6 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C } if pb != nil && pb.EnableStatusCheck { - var missingRequiredChecks []string for _, requiredContext := range pb.StatusCheckContexts { contextFound := false @@ -646,7 +645,6 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi // Validate the given commit sha to show (if any passed) if willShowSpecifiedCommit || willShowSpecifiedCommitRange { - foundStartCommit := len(specifiedStartCommit) == 0 foundEndCommit := len(specifiedEndCommit) == 0 @@ -865,21 +863,21 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi if pull.HeadRepo != nil { ctx.Data["SourcePath"] = pull.HeadRepo.Link() + "/src/branch/" + util.PathEscapeSegments(pull.HeadBranch) - } - if !pull.HasMerged && ctx.Doer != nil { - perm, err := access_model.GetUserRepoPermission(ctx, pull.HeadRepo, ctx.Doer) - if err != nil { - ctx.ServerError("GetUserRepoPermission", err) - return - } + if !pull.HasMerged && ctx.Doer != nil { + perm, err := access_model.GetUserRepoPermission(ctx, pull.HeadRepo, ctx.Doer) + if err != nil { + ctx.ServerError("GetUserRepoPermission", err) + return + } - if perm.CanWrite(unit.TypeCode) || issues_model.CanMaintainerWriteToBranch(ctx, perm, pull.HeadBranch, ctx.Doer) { - ctx.Data["CanEditFile"] = true - ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file") - ctx.Data["HeadRepoLink"] = pull.HeadRepo.Link() - ctx.Data["HeadBranchName"] = pull.HeadBranch - ctx.Data["BackToLink"] = setting.AppSubURL + ctx.Req.URL.RequestURI() + if perm.CanWrite(unit.TypeCode) || issues_model.CanMaintainerWriteToBranch(ctx, perm, pull.HeadBranch, ctx.Doer) { + ctx.Data["CanEditFile"] = true + ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file") + ctx.Data["HeadRepoLink"] = pull.HeadRepo.Link() + ctx.Data["HeadBranchName"] = pull.HeadBranch + ctx.Data["BackToLink"] = setting.AppSubURL + ctx.Req.URL.RequestURI() + } } } } @@ -974,7 +972,6 @@ func UpdatePullRequest(ctx *context.Context) { ctx.Flash.Error(flashError) ctx.Redirect(issue.Link()) return - } ctx.Flash.Error(err.Error()) ctx.Redirect(issue.Link()) diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go index c8d149a482..a65d4866d0 100644 --- a/routers/web/repo/pull_review.go +++ b/routers/web/repo/pull_review.go @@ -318,7 +318,6 @@ func UpdateViewedFiles(ctx *context.Context) { updatedFiles := make(map[string]pull_model.ViewedState, len(data.Files)) for file, viewed := range data.Files { - // Only unviewed and viewed are possible, has-changed can not be set from the outside state := pull_model.Unviewed if viewed { diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 9c1f4faa5f..e4e6201c24 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -347,7 +347,6 @@ func loadLatestCommitData(ctx *context.Context, latestCommit *git.Commit) bool { // or of directory if not in root directory. ctx.Data["LatestCommit"] = latestCommit if latestCommit != nil { - verification := asymkey_model.ParseCommitWithSignature(ctx, latestCommit) if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) { diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index 49eb050dcb..e5ff8570cf 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -31,6 +31,7 @@ import ( "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" user_service "code.gitea.io/gitea/services/user" + "code.gitea.io/gitea/services/webtheme" ) const ( @@ -319,6 +320,13 @@ func Appearance(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings.appearance") ctx.Data["PageIsSettingsAppearance"] = true + allThemes := webtheme.GetAvailableThemes() + if webtheme.IsThemeAvailable(setting.UI.DefaultTheme) { + allThemes = util.SliceRemoveAll(allThemes, setting.UI.DefaultTheme) + allThemes = append([]string{setting.UI.DefaultTheme}, allThemes...) // move the default theme to the top + } + ctx.Data["AllThemes"] = allThemes + var hiddenCommentTypes *big.Int val, err := user_model.GetUserSetting(ctx, ctx.Doer.ID, user_model.SettingsKeyHiddenCommentTypes) if err != nil { @@ -341,11 +349,12 @@ func UpdateUIThemePost(ctx *context.Context) { ctx.Data["PageIsSettingsAppearance"] = true if ctx.HasError() { + ctx.Flash.Error(ctx.GetErrMsg()) ctx.Redirect(setting.AppSubURL + "/user/settings/appearance") return } - if !form.IsThemeExists() { + if !webtheme.IsThemeAvailable(form.Theme) { ctx.Flash.Error(ctx.Tr("settings.theme_update_error")) ctx.Redirect(setting.AppSubURL + "/user/settings/appearance") return diff --git a/routers/web/web.go b/routers/web/web.go index 994e639e20..9a6687059b 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -652,7 +652,7 @@ func registerRoutes(m *web.Route) { m.Get("", user_setting.BlockedUsers) m.Post("", web.Bind(forms.BlockUserForm{}), user_setting.BlockedUsersPost) }) - }, reqSignIn, ctxDataSet("PageIsUserSettings", true, "AllThemes", setting.UI.Themes, "EnablePackages", setting.Packages.Enabled)) + }, reqSignIn, ctxDataSet("PageIsUserSettings", true, "EnablePackages", setting.Packages.Enabled)) m.Group("/user", func() { m.Get("/activate", auth.Activate) @@ -1612,7 +1612,7 @@ func registerRoutes(m *web.Route) { m.NotFound(func(w http.ResponseWriter, req *http.Request) { ctx := context.GetWebContext(req) - routing.UpdateFuncInfo(ctx, routing.GetFuncInfo(ctx.NotFound, "GlobalNotFound")) + routing.UpdateFuncInfo(ctx, routing.GetFuncInfo(ctx.NotFound, "WebNotFound")) ctx.NotFound("", nil) }) } diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go index 6fb6421887..1d09a222c0 100644 --- a/services/actions/notifier_helper.go +++ b/services/actions/notifier_helper.go @@ -298,13 +298,15 @@ func handleWorkflows( TriggerEvent: dwf.TriggerEvent.Name, Status: actions_model.StatusWaiting, } - if need, err := ifNeedApproval(ctx, run, input.Repo, input.Doer); err != nil { + + need, err := ifNeedApproval(ctx, run, input.Repo, input.Doer) + if err != nil { log.Error("check if need approval for repo %d with user %d: %v", input.Repo.ID, input.Doer.ID, err) continue - } else { - run.NeedApproval = need } + run.NeedApproval = need + if err := run.LoadAttributes(ctx); err != nil { log.Error("LoadAttributes: %v", err) continue diff --git a/services/actions/schedule_tasks.go b/services/actions/schedule_tasks.go index e4e56e5122..18f3324fd2 100644 --- a/services/actions/schedule_tasks.go +++ b/services/actions/schedule_tasks.go @@ -132,8 +132,14 @@ func CreateScheduleTask(ctx context.Context, cron *actions_model.ActionSchedule) Status: actions_model.StatusWaiting, } + vars, err := actions_model.GetVariablesOfRun(ctx, run) + if err != nil { + log.Error("GetVariablesOfRun: %v", err) + return err + } + // Parse the workflow specification from the cron schedule - workflows, err := jobparser.Parse(cron.Content) + workflows, err := jobparser.Parse(cron.Content, jobparser.WithVars(vars)) if err != nil { return err } diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index 0c9491cd09..2a95326b9e 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -156,7 +156,6 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { !strings.EqualFold(usr.Email, su.Mail) || usr.FullName != fullName || !usr.IsActive { - log.Trace("SyncExternalUsers[%s]: Updating user %s", source.authSource.Name, usr.Name) opts := &user_service.UpdateOptions{ diff --git a/services/context/context.go b/services/context/context.go index 1641e995fb..aab0485f1a 100644 --- a/services/context/context.go +++ b/services/context/context.go @@ -20,14 +20,13 @@ import ( "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/httpcache" + "code.gitea.io/gitea/modules/session" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web/middleware" web_types "code.gitea.io/gitea/modules/web/types" - - "gitea.com/go-chi/session" ) // Render represents a template render @@ -154,7 +153,7 @@ func Contexter() func(next http.Handler) http.Handler { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { base, baseCleanUp := NewBaseContext(resp, req) defer baseCleanUp() - ctx := NewWebContext(base, rnd, session.GetSession(req)) + ctx := NewWebContext(base, rnd, session.GetContextSession(req)) ctx.Data.MergeFrom(middleware.CommonTemplateContextData()) ctx.Data["Context"] = ctx // TODO: use "ctx" in template and remove this @@ -230,6 +229,7 @@ func Contexter() func(next http.Handler) http.Handler { // HasError returns true if error occurs in form validation. // Attention: this function changes ctx.Data and ctx.Flash +// If HasError is called, then before Redirect, the error message should be stored by ctx.Flash.Error(ctx.GetErrMsg()) again. func (ctx *Context) HasError() bool { hasErr, ok := ctx.Data["HasError"] if !ok { diff --git a/services/context/repo.go b/services/context/repo.go index b17f99eb17..4836c1456c 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -825,7 +825,6 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string { case RepoRefBranch: ref := getRefNameFromPath(ctx, repo, path, repo.GitRepo.IsBranchExist) if len(ref) == 0 { - // check if ref is HEAD parts := strings.Split(path, "/") if parts[0] == headRefName { @@ -968,7 +967,6 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context return cancel } ctx.Repo.CommitID = ctx.Repo.Commit.ID.String() - } else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) { ctx.Repo.IsViewTag = true ctx.Repo.TagName = refName diff --git a/services/contexttest/context_tests.go b/services/contexttest/context_tests.go index 3064c56590..0c1e5ee54f 100644 --- a/services/contexttest/context_tests.go +++ b/services/contexttest/context_tests.go @@ -19,7 +19,9 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/gitrepo" + "code.gitea.io/gitea/modules/session" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/web/middleware" @@ -43,7 +45,8 @@ func mockRequest(t *testing.T, reqPath string) *http.Request { } type MockContextOption struct { - Render context.Render + Render context.Render + SessionStore *session.MockStore } // MockContext mock context for unit tests @@ -62,12 +65,17 @@ func MockContext(t *testing.T, reqPath string, opts ...MockContextOption) (*cont base.Data = middleware.GetContextData(req.Context()) base.Locale = &translation.MockLocale{} + chiCtx := chi.NewRouteContext() ctx := context.NewWebContext(base, opt.Render, nil) ctx.AppendContextValue(context.WebContextKey, ctx) + ctx.AppendContextValue(chi.RouteCtxKey, chiCtx) + if opt.SessionStore != nil { + ctx.AppendContextValue(session.MockStoreContextKey, opt.SessionStore) + ctx.Session = opt.SessionStore + } + ctx.Cache = cache.GetCache() ctx.PageData = map[string]any{} ctx.Data["PageStartTime"] = time.Now() - chiCtx := chi.NewRouteContext() - ctx.Base.AppendContextValue(chi.RouteCtxKey, chiCtx) return ctx, resp } diff --git a/services/doctor/dbconsistency.go b/services/doctor/dbconsistency.go index dfdf7b547a..7cb7445148 100644 --- a/services/doctor/dbconsistency.go +++ b/services/doctor/dbconsistency.go @@ -152,6 +152,12 @@ func prepareDBConsistencyChecks() []consistencyCheck { Fixer: actions_model.FixRunnersWithoutBelongingOwner, FixedMessage: "Removed", }, + { + Name: "Action Runners without existing repository", + Counter: actions_model.CountRunnersWithoutBelongingRepo, + Fixer: actions_model.FixRunnersWithoutBelongingRepo, + FixedMessage: "Removed", + }, { Name: "Topics with empty repository count", Counter: repo_model.CountOrphanedTopics, diff --git a/services/forms/user_form.go b/services/forms/user_form.go index e2e6c208f7..418a87b863 100644 --- a/services/forms/user_form.go +++ b/services/forms/user_form.go @@ -11,7 +11,6 @@ import ( auth_model "code.gitea.io/gitea/models/auth" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/services/context" @@ -273,7 +272,7 @@ func (f *AddEmailForm) Validate(req *http.Request, errs binding.Errors) binding. // UpdateThemeForm form for updating a users' theme type UpdateThemeForm struct { - Theme string `binding:"Required;MaxSize(30)"` + Theme string `binding:"Required;MaxSize(255)"` } // Validate validates the field @@ -282,20 +281,6 @@ func (f *UpdateThemeForm) Validate(req *http.Request, errs binding.Errors) bindi return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } -// IsThemeExists checks if the theme is a theme available in the config. -func (f UpdateThemeForm) IsThemeExists() bool { - var exists bool - - for _, v := range setting.UI.Themes { - if strings.EqualFold(v, f.Theme) { - exists = true - break - } - } - - return exists -} - // ChangePasswordForm form for changing password type ChangePasswordForm struct { OldPassword string `form:"old_password" binding:"MaxSize(255)"` diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index b05c210a0c..d115686491 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -1044,10 +1044,10 @@ func createDiffFile(diff *Diff, line string) *DiffFile { // diff --git a/b b/b b/b b/b b/b b/b // midpoint := (len(line) + len(cmdDiffHead) - 1) / 2 - new, old := line[len(cmdDiffHead):midpoint], line[midpoint+1:] - if len(new) > 2 && len(old) > 2 && new[2:] == old[2:] { - curFile.OldName = old[2:] - curFile.Name = old[2:] + newPart, oldPart := line[len(cmdDiffHead):midpoint], line[midpoint+1:] + if len(newPart) > 2 && len(oldPart) > 2 && newPart[2:] == oldPart[2:] { + curFile.OldName = oldPart[2:] + curFile.Name = oldPart[2:] } } } @@ -1181,7 +1181,6 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi defer deferable() for _, diffFile := range diff.Files { - isVendored := optional.None[bool]() isGenerated := optional.None[bool]() if checker != nil { diff --git a/services/issue/commit.go b/services/issue/commit.go index 0a59088d12..0579e0f5c5 100644 --- a/services/issue/commit.go +++ b/services/issue/commit.go @@ -118,7 +118,6 @@ func UpdateIssuesCommit(ctx context.Context, doer *user_model.User, repo *repo_m var refIssue *issues_model.Issue var err error for _, ref := range references.FindAllIssueReferences(c.Message) { - // issue is from another repo if len(ref.Owner) > 0 && len(ref.Name) > 0 { refRepo, err = repo_model.GetRepositoryByOwnerAndName(ctx, ref.Owner, ref.Name) @@ -189,15 +188,15 @@ func UpdateIssuesCommit(ctx context.Context, doer *user_model.User, repo *repo_m continue } } - close := ref.Action == references.XRefActionCloses - if close && len(ref.TimeLog) > 0 { + isClosed := ref.Action == references.XRefActionCloses + if isClosed && len(ref.TimeLog) > 0 { if err := issueAddTime(ctx, refIssue, doer, c.Timestamp, ref.TimeLog); err != nil { return err } } - if close != refIssue.IsClosed { + if isClosed != refIssue.IsClosed { refIssue.Repo = refRepo - if err := ChangeStatus(ctx, refIssue, doer, c.Sha1, close); err != nil { + if err := ChangeStatus(ctx, refIssue, doer, c.Sha1, isClosed); err != nil { return err } } diff --git a/services/markup/processorhelper_codepreview.go b/services/markup/processorhelper_codepreview.go index ef95046128..0500e57e46 100644 --- a/services/markup/processorhelper_codepreview.go +++ b/services/markup/processorhelper_codepreview.go @@ -86,12 +86,13 @@ func renderRepoFileCodePreview(ctx context.Context, opts markup.RenderCodePrevie lineNums := make([]int, 0, lineCount) lineCodes := make([]string, 0, lineCount) for i := opts.LineStart; i <= opts.LineStop; i++ { - if line, err := reader.ReadString('\n'); err != nil && line == "" { + line, err := reader.ReadString('\n') + if err != nil && line == "" { break - } else { - lineNums = append(lineNums, i) - lineCodes = append(lineCodes, line) } + + lineNums = append(lineNums, i) + lineCodes = append(lineCodes, line) } realLineStop := max(opts.LineStart, opts.LineStart+len(lineNums)-1) highlightLines := code.HighlightSearchResultCode(opts.FilePath, language, lineNums, strings.Join(lineCodes, "")) diff --git a/services/migrations/gitea_downloader.go b/services/migrations/gitea_downloader.go index d402a238f2..272bf02e11 100644 --- a/services/migrations/gitea_downloader.go +++ b/services/migrations/gitea_downloader.go @@ -410,7 +410,6 @@ func (g *GiteaDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, err return nil, false, fmt.Errorf("error while listing issues: %w", err) } for _, issue := range issues { - labels := make([]*base.Label, 0, len(issue.Labels)) for i := range issue.Labels { labels = append(labels, g.convertGiteaLabel(issue.Labels[i])) diff --git a/services/migrations/gitlab.go b/services/migrations/gitlab.go index bbc44e958a..065b687fa6 100644 --- a/services/migrations/gitlab.go +++ b/services/migrations/gitlab.go @@ -421,7 +421,6 @@ func (g *GitlabDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, er return nil, false, fmt.Errorf("error while listing issues: %w", err) } for _, issue := range issues { - labels := make([]*base.Label, 0, len(issue.Labels)) for _, l := range issue.Labels { labels = append(labels, &base.Label{ diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index fa23986c54..f5eaeaf091 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -523,13 +523,13 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool { theCommits.Commits = theCommits.Commits[:setting.UI.FeedMaxCommitNum] } - if newCommit, err := gitRepo.GetCommit(newCommitID); err != nil { + newCommit, err := gitRepo.GetCommit(newCommitID) + if err != nil { log.Error("SyncMirrors [repo: %-v]: unable to get commit %s: %v", m.Repo, newCommitID, err) continue - } else { - theCommits.HeadCommit = repo_module.CommitToPushCommit(newCommit) } + theCommits.HeadCommit = repo_module.CommitToPushCommit(newCommit) theCommits.CompareURL = m.Repo.ComposeCompareURL(oldCommitID, newCommitID) notify_service.SyncPushCommits(ctx, m.Repo.MustOwner(ctx), m.Repo, &repo_module.PushUpdateOptions{ @@ -557,7 +557,6 @@ func SyncPullMirror(ctx context.Context, repoID int64) bool { log.Error("SyncMirrors [repo: %-v]: unable to update repository 'updated_unix': %v", m.Repo, err) return false } - } log.Trace("SyncMirrors [repo: %-v]: Successfully updated", m.Repo) diff --git a/services/pull/merge.go b/services/pull/merge.go index e37540a96f..00f23e1e3a 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -231,9 +231,9 @@ func Merge(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.U if err = ref.Issue.LoadRepo(ctx); err != nil { return err } - close := ref.RefAction == references.XRefActionCloses - if close != ref.Issue.IsClosed { - if err = issue_service.ChangeStatus(ctx, ref.Issue, doer, pr.MergedCommitID, close); err != nil { + isClosed := ref.RefAction == references.XRefActionCloses + if isClosed != ref.Issue.IsClosed { + if err = issue_service.ChangeStatus(ctx, ref.Issue, doer, pr.MergedCommitID, isClosed); err != nil { // Allow ErrDependenciesLeft if !issues_model.IsErrDependenciesLeft(err) { return err diff --git a/services/pull/pull.go b/services/pull/pull.go index 266fb8448e..5c0ea42d77 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -807,7 +807,6 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ if err != nil { log.Error("Unable to get commits between: %s %s Error: %v", pr.HeadBranch, pr.MergeBase, err) return "" - } if len(commits) == 0 { break diff --git a/services/repository/adopt.go b/services/repository/adopt.go index b337eac38a..31e3e581b3 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -357,7 +357,6 @@ func ListUnadoptedRepositories(ctx context.Context, query string, opts *db.ListO return err } repoNamesToCheck = repoNamesToCheck[:0] - } return filepath.SkipDir }); err != nil { diff --git a/services/repository/commitstatus/commitstatus.go b/services/repository/commitstatus/commitstatus.go index 8a62a603d4..444ae04d0c 100644 --- a/services/repository/commitstatus/commitstatus.go +++ b/services/repository/commitstatus/commitstatus.go @@ -38,12 +38,10 @@ func getCommitStatusCache(repoID int64, branchName string) *commitStatusCacheVal if ok && statusStr != "" { var cv commitStatusCacheValue err := json.Unmarshal([]byte(statusStr), &cv) - if err == nil && cv.State != "" { + if err == nil { return &cv } - if err != nil { - log.Warn("getCommitStatusCache: json.Unmarshal failed: %v", err) - } + log.Warn("getCommitStatusCache: json.Unmarshal failed: %v", err) } return nil } @@ -128,15 +126,22 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato // FindReposLastestCommitStatuses loading repository default branch latest combinded commit status with cache func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Repository) ([]*git_model.CommitStatus, error) { results := make([]*git_model.CommitStatus, len(repos)) + allCached := true for i, repo := range repos { if cv := getCommitStatusCache(repo.ID, repo.DefaultBranch); cv != nil { results[i] = &git_model.CommitStatus{ State: api.CommitStatusState(cv.State), TargetURL: cv.TargetURL, } + } else { + allCached = false } } + if allCached { + return results, nil + } + // collect the latest commit of each repo // at most there are dozens of repos (limited by MaxResponseItems), so it's not a big problem at the moment repoBranchNames := make(map[int64]string, len(repos)) @@ -165,10 +170,10 @@ func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Rep for i, repo := range repos { if repo.ID == summary.RepoID { results[i] = summary - _ = slices.DeleteFunc(repoSHAs, func(repoSHA git_model.RepoSHA) bool { + repoSHAs = slices.DeleteFunc(repoSHAs, func(repoSHA git_model.RepoSHA) bool { return repoSHA.RepoID == repo.ID }) - if results[i].State != "" { + if results[i] != nil { if err := updateCommitStatusCache(repo.ID, repo.DefaultBranch, results[i].State, results[i].TargetURL); err != nil { log.Error("updateCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err) } @@ -177,6 +182,9 @@ func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Rep } } } + if len(repoSHAs) == 0 { + return results, nil + } // call the database O(1) times to get the commit statuses for all repos repoToItsLatestCommitStatuses, err := git_model.GetLatestCommitStatusForPairs(ctx, repoSHAs) @@ -187,7 +195,7 @@ func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Rep for i, repo := range repos { if results[i] == nil { results[i] = git_model.CalcCommitStatus(repoToItsLatestCommitStatuses[repo.ID]) - if results[i].State != "" { + if results[i] != nil { if err := updateCommitStatusCache(repo.ID, repo.DefaultBranch, results[i].State, results[i].TargetURL); err != nil { log.Error("updateCommitStatusCache[%d:%s] failed: %v", repo.ID, repo.DefaultBranch, err) } diff --git a/services/repository/contributors_graph.go b/services/repository/contributors_graph.go index b0d6de99ca..b0748f8ee3 100644 --- a/services/repository/contributors_graph.go +++ b/services/repository/contributors_graph.go @@ -187,7 +187,6 @@ func getExtendedCommitStats(repo *git.Repository, revision string /*, limit int Stats: &commitStats, } extendedCommitStats = append(extendedCommitStats, res) - } _ = stdoutReader.Close() return nil diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go index 9fcd335c55..d70b1e8d54 100644 --- a/services/repository/files/temp_repo.go +++ b/services/repository/files/temp_repo.go @@ -136,14 +136,18 @@ func (t *TemporaryUploadRepository) LsFiles(filenames ...string) ([]string, erro // RemoveFilesFromIndex removes the given files from the index func (t *TemporaryUploadRepository) RemoveFilesFromIndex(filenames ...string) error { + objFmt, err := t.gitRepo.GetObjectFormat() + if err != nil { + return fmt.Errorf("unable to get object format for temporary repo: %q, error: %w", t.repo.FullName(), err) + } stdOut := new(bytes.Buffer) stdErr := new(bytes.Buffer) stdIn := new(bytes.Buffer) for _, file := range filenames { if file != "" { - stdIn.WriteString("0 0000000000000000000000000000000000000000\t") - stdIn.WriteString(file) - stdIn.WriteByte('\000') + // man git-update-index: input syntax (1): mode SP sha1 TAB path + // mode=0 means "remove from index", then hash part "does not matter as long as it is well formatted." + _, _ = fmt.Fprintf(stdIn, "0 %s\t%s\x00", objFmt.EmptyObjectID(), file) } } @@ -154,8 +158,7 @@ func (t *TemporaryUploadRepository) RemoveFilesFromIndex(filenames ...string) er Stdout: stdOut, Stderr: stdErr, }); err != nil { - log.Error("Unable to update-index for temporary repo: %s (%s) Error: %v\nstdout: %s\nstderr: %s", t.repo.FullName(), t.basePath, err, stdOut.String(), stdErr.String()) - return fmt.Errorf("Unable to update-index for temporary repo: %s Error: %w\nstdout: %s\nstderr: %s", t.repo.FullName(), err, stdOut.String(), stdErr.String()) + return fmt.Errorf("unable to update-index for temporary repo: %q, error: %w\nstdout: %s\nstderr: %s", t.repo.FullName(), err, stdOut.String(), stdErr.String()) } return nil } diff --git a/services/repository/files/update.go b/services/repository/files/update.go index f029a9aefe..d0e3075eae 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -208,7 +208,6 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use return nil, fmt.Errorf("ConvertToSHA1: Invalid last commit ID: %w", err) } opts.LastCommitID = lastCommitID.String() - } for _, file := range opts.Files { @@ -360,7 +359,6 @@ func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRep Path: file.Options.treePath, } } - } } diff --git a/services/user/delete.go b/services/user/delete.go index 889da3eb67..39c6ef052d 100644 --- a/services/user/delete.go +++ b/services/user/delete.go @@ -105,7 +105,6 @@ func deleteUser(ctx context.Context, u *user_model.User, purge bool) (err error) if purge || (setting.Service.UserDeleteWithCommentsMaxTime != 0 && u.CreatedUnix.AsTime().Add(setting.Service.UserDeleteWithCommentsMaxTime).After(time.Now())) { - // Delete Comments const batchSize = 50 for { diff --git a/services/user/update_test.go b/services/user/update_test.go index 7ed764b539..c2ff26a140 100644 --- a/services/user/update_test.go +++ b/services/user/update_test.go @@ -94,7 +94,7 @@ func TestUpdateAuth(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 28}) - copy := *user + userCopy := *user assert.NoError(t, UpdateAuth(db.DefaultContext, user, &UpdateAuthOptions{ LoginName: optional.Some("new-login"), @@ -106,8 +106,8 @@ func TestUpdateAuth(t *testing.T) { MustChangePassword: optional.Some(true), })) assert.True(t, user.MustChangePassword) - assert.NotEqual(t, copy.Passwd, user.Passwd) - assert.NotEqual(t, copy.Salt, user.Salt) + assert.NotEqual(t, userCopy.Passwd, user.Passwd) + assert.NotEqual(t, userCopy.Salt, user.Salt) assert.NoError(t, UpdateAuth(db.DefaultContext, user, &UpdateAuthOptions{ ProhibitLogin: optional.Some(true), diff --git a/services/webhook/discord.go b/services/webhook/discord.go index 659754d5e0..3883ac9eb8 100644 --- a/services/webhook/discord.go +++ b/services/webhook/discord.go @@ -274,14 +274,12 @@ func newDiscordRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook func parseHookPullRequestEventType(event webhook_module.HookEventType) (string, error) { switch event { - case webhook_module.HookEventPullRequestReviewApproved: return "approved", nil case webhook_module.HookEventPullRequestReviewRejected: return "rejected", nil case webhook_module.HookEventPullRequestReviewComment: return "comment", nil - default: return "", errors.New("unknown event type") } diff --git a/services/webhook/matrix.go b/services/webhook/matrix.go index 0329804a8b..5dcfdcb0dd 100644 --- a/services/webhook/matrix.go +++ b/services/webhook/matrix.go @@ -179,7 +179,6 @@ func (m matrixConvertor) Push(p *api.PushPayload) (MatrixPayload, error) { if i < len(p.Commits)-1 { text += "
" } - } return m.newPayload(text, p.Commits...) diff --git a/services/webtheme/webtheme.go b/services/webtheme/webtheme.go new file mode 100644 index 0000000000..dc801e1ff7 --- /dev/null +++ b/services/webtheme/webtheme.go @@ -0,0 +1,74 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package webtheme + +import ( + "sort" + "strings" + "sync" + + "code.gitea.io/gitea/modules/container" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/public" + "code.gitea.io/gitea/modules/setting" +) + +var ( + availableThemes []string + availableThemesSet container.Set[string] + themeOnce sync.Once +) + +func initThemes() { + availableThemes = nil + defer func() { + availableThemesSet = container.SetOf(availableThemes...) + if !availableThemesSet.Contains(setting.UI.DefaultTheme) { + setting.LogStartupProblem(1, log.ERROR, "Default theme %q is not available, please correct the '[ui].DEFAULT_THEME' setting in the config file", setting.UI.DefaultTheme) + } + }() + cssFiles, err := public.AssetFS().ListFiles("/assets/css") + if err != nil { + log.Error("Failed to list themes: %v", err) + availableThemes = []string{setting.UI.DefaultTheme} + return + } + var foundThemes []string + for _, name := range cssFiles { + name, ok := strings.CutPrefix(name, "theme-") + if !ok { + continue + } + name, ok = strings.CutSuffix(name, ".css") + if !ok { + continue + } + foundThemes = append(foundThemes, name) + } + if len(setting.UI.Themes) > 0 { + allowedThemes := container.SetOf(setting.UI.Themes...) + for _, theme := range foundThemes { + if allowedThemes.Contains(theme) { + availableThemes = append(availableThemes, theme) + } + } + } else { + availableThemes = foundThemes + } + sort.Strings(availableThemes) + if len(availableThemes) == 0 { + setting.LogStartupProblem(1, log.ERROR, "No theme candidate in asset files, but Gitea requires there should be at least one usable theme") + availableThemes = []string{setting.UI.DefaultTheme} + } +} + +func GetAvailableThemes() []string { + themeOnce.Do(initThemes) + return availableThemes +} + +func IsThemeAvailable(name string) bool { + themeOnce.Do(initThemes) + return availableThemesSet.Contains(name) +} diff --git a/templates/admin/layout_head.tmpl b/templates/admin/layout_head.tmpl index c1f5fb3314..7cc6624d50 100644 --- a/templates/admin/layout_head.tmpl +++ b/templates/admin/layout_head.tmpl @@ -1,11 +1,9 @@ {{template "base/head" .ctxData}}
-
- {{template "base/alert" .ctxData}} -
{{template "admin/navbar" .ctxData}}
+ {{template "base/alert" .ctxData}} {{/* block: admin-setting-content */}} {{if false}}{{/* to make html structure "likely" complete to prevent IDE warnings */}} diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index 2de8f58235..174267fd2f 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -1,5 +1,5 @@ - + {{if .Title}}{{.Title}} - {{end}}{{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}} diff --git a/templates/base/head_style.tmpl b/templates/base/head_style.tmpl index 0793eaca20..f97e1880ce 100644 --- a/templates/base/head_style.tmpl +++ b/templates/base/head_style.tmpl @@ -1,2 +1,2 @@ - + diff --git a/templates/org/team/members.tmpl b/templates/org/team/members.tmpl index 5719328a27..7e9a59a6bf 100644 --- a/templates/org/team/members.tmpl +++ b/templates/org/team/members.tmpl @@ -46,7 +46,7 @@
{{else}}
- {{ctx.Locale.Tr "org.teams.members.none"}} + {{ctx.Locale.Tr "org.teams.members.none"}}
{{end}}
diff --git a/templates/org/team/repositories.tmpl b/templates/org/team/repositories.tmpl index 98b4854eb8..f5d68ce416 100644 --- a/templates/org/team/repositories.tmpl +++ b/templates/org/team/repositories.tmpl @@ -48,7 +48,7 @@
{{else}}
- {{ctx.Locale.Tr "org.teams.repos.none"}} + {{ctx.Locale.Tr "org.teams.repos.none"}}
{{end}} diff --git a/templates/org/team/sidebar.tmpl b/templates/org/team/sidebar.tmpl index b9e55dd587..ac41cda716 100644 --- a/templates/org/team/sidebar.tmpl +++ b/templates/org/team/sidebar.tmpl @@ -22,7 +22,7 @@ {{if .Team.Description}} {{.Team.Description}} {{else}} - {{ctx.Locale.Tr "org.teams.no_desc"}} + {{ctx.Locale.Tr "org.teams.no_desc"}} {{end}} {{if eq .Team.LowerName "owners"}} diff --git a/templates/projects/list.tmpl b/templates/projects/list.tmpl index ec02e9a6fc..b2f48fe2c9 100644 --- a/templates/projects/list.tmpl +++ b/templates/projects/list.tmpl @@ -41,9 +41,9 @@
{{range .Projects}}
  • -

    +

    {{svg .IconName 16}} - {{.Title}} + {{.Title}}

    diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl index f9b85360e0..3e000660e2 100644 --- a/templates/projects/view.tmpl +++ b/templates/projects/view.tmpl @@ -1,8 +1,8 @@ {{$canWriteProject := and .CanWriteProjects (or (not .Repository) (not .Repository.IsArchived))}} -
    -
    -

    {{.Project.Title}}

    +
    +
    +

    {{.Project.Title}}

    {{if $canWriteProject}}