@@ -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] |
@@ -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 |
@@ -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) | |||
} | |||
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 { | |||
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) | |||
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) | |||
} else { | |||
log.Info("Dumping database...") | |||
} | |||
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 := 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) | |||
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) |
@@ -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 { |
@@ -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. |
@@ -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: |
@@ -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表情符号。 |
@@ -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-<theme-name>.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 `<theme-name>` to the comma-separated list of setting `THEMES` in `app.ini` | |||
2. Add `<theme-name>` 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). | |||
@@ -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. |
@@ -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服务器。 |
@@ -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", |
@@ -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() | |||
} |
@@ -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 { |
@@ -110,7 +110,6 @@ func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerific | |||
Reason: "gpg.error.no_committer_account", | |||
} | |||
} | |||
} | |||
} | |||
@@ -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}) |
@@ -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 |
@@ -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") | |||
} |
@@ -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") | |||
} |
@@ -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 |
@@ -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 |
@@ -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. |
@@ -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)) | |||
} |
@@ -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 | |||
} | |||
@@ -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 | |||
} |
@@ -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])) | |||
} |
@@ -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") | |||
} |
@@ -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) | |||
} | |||
} | |||
} | |||
@@ -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) | |||
} | |||
} | |||
} | |||
@@ -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) | |||
} |
@@ -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: |
@@ -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 |
@@ -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) | |||
} |
@@ -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) | |||
} |
@@ -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) |
@@ -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) | |||
} | |||
} | |||
} |
@@ -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 |
@@ -49,9 +49,8 @@ readLoop: | |||
if len(line) > 0 && line[0] == ' ' { | |||
_, _ = signatureSB.Write(line[1:]) | |||
continue | |||
} else { | |||
pgpsig = false | |||
} | |||
pgpsig = false | |||
} | |||
if !message { |
@@ -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) | |||
} |
@@ -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: | |||
} |
@@ -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: | |||
} |
@@ -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 | |||
} |
@@ -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] |
@@ -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() { |
@@ -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 | |||
} |
@@ -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 |
@@ -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 { |
@@ -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) | |||
} | |||
} | |||
@@ -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 | |||
} |
@@ -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) { |
@@ -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 |
@@ -40,8 +40,6 @@ type WorkerPoolQueue[T any] struct { | |||
workerMaxNum int | |||
workerActiveNum int | |||
workerNumMu sync.Mutex | |||
workerStartedCounter int32 | |||
} | |||
type flushType chan struct{} |
@@ -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() | |||
} |
@@ -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 | |||
} |
@@ -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)} | |||
} |
@@ -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. |
@@ -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) | |||
} | |||
} | |||
@@ -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() |
@@ -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 | |||
} |
@@ -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 |
@@ -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:"}, |
@@ -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 | |||
} |
@@ -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 { |
@@ -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) | |||
} | |||
} | |||
} |
@@ -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) | |||
} | |||
@@ -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. |
@@ -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. |
@@ -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ář |
@@ -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 |
@@ -1118,7 +1118,6 @@ fork=Fork | |||
download_archive=Λήψη Αποθετηρίου | |||
more_operations=Περισσότερες Λειτουργίες | |||
no_desc=Χωρίς Περιγραφή | |||
quick_guide=Γρήγορος Οδηγός | |||
clone_this_repo=Κλωνοποίηση αυτού του αποθετηρίου | |||
cite_this_repo=Αναφορά σε αυτό το αποθετήριο |
@@ -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 |
@@ -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 |
@@ -874,7 +874,6 @@ star=ستاره دار کن | |||
fork=انشعاب | |||
download_archive=دانلود مخزن | |||
no_desc=بدون توضیح | |||
quick_guide=راهنمای سریع | |||
clone_this_repo=همسانسازی این مخزن | |||
create_new_repo_command=ایجاد یک مخزن جدید در خط فرمان |
@@ -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 | |||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -1188,7 +1188,6 @@ action.blocked_user=リポジトリのオーナーがあなたをブロックし | |||
download_archive=リポジトリをダウンロード | |||
more_operations=その他の操作 | |||
no_desc=説明なし | |||
quick_guide=クイック ガイド | |||
clone_this_repo=このリポジトリのクローンを作成 | |||
cite_this_repo=このリポジトリを引用 |
@@ -606,7 +606,6 @@ star=좋아요 | |||
fork=포크 | |||
download_archive=저장소 다운로드 | |||
no_desc=설명 없음 | |||
quick_guide=퀵 가이드 | |||
clone_this_repo=이 저장소 복제 | |||
create_new_repo_command=커맨드 라인에서 새 레포리지터리 생성 |
@@ -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 |
@@ -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 |
@@ -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ń |
@@ -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 |
@@ -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 '<b>%s</b>' | |||
settings.branch_protection=Regras de salvaguarda do ramo '<b>%s</b>' | |||
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 <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> para ver a sintaxe. Exemplos: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>. | |||
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 <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> para ver a sintaxe. Exemplos: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>. | |||
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 <a href='https://pkg.go.dev/github.com/gobwas/glob#Compile'>github.com/gobwas/glob</a> para ver a sintaxe. Exemplos: <code>.drone.yml</code>, <code>/docs/**/*.txt</code>. | |||
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 |
@@ -1098,7 +1098,6 @@ fork=Форкнуть | |||
download_archive=Скачать репозиторий | |||
more_operations=Ещё действия | |||
no_desc=Нет описания | |||
quick_guide=Краткое руководство | |||
clone_this_repo=Клонировать репозиторий | |||
cite_this_repo=Сослаться на этот репозиторий |
@@ -846,7 +846,6 @@ star=ස්ටාර් | |||
fork=දෙබලක | |||
download_archive=කෝෂ්ඨය බාගන්න | |||
no_desc=සවිස්තරයක් නැත | |||
quick_guide=ඉක්මන් මාර්ගෝපදේශය | |||
clone_this_repo=මෙම ගබඩාව පරිගණක ක්රිඩාවට සමාන | |||
create_new_repo_command=විධාන රේඛාවේ නව ගබඩාවක් නිර්මාණය කිරීම |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -882,7 +882,6 @@ star=В обрані | |||
fork=Форк | |||
download_archive=Скачати репозиторій | |||
no_desc=Без опису | |||
quick_guide=Короткий посібник | |||
clone_this_repo=Кнонувати цей репозиторій | |||
create_new_repo_command=Створити новий репозиторій з командного рядка |
@@ -1193,7 +1193,6 @@ action.blocked_user=无法执行操作,因为您已被仓库所有者屏蔽。 | |||
download_archive=下载此仓库 | |||
more_operations=更多操作 | |||
no_desc=暂无描述 | |||
quick_guide=快速帮助 | |||
clone_this_repo=克隆当前仓库 | |||
cite_this_repo=引用此仓库 |
@@ -344,7 +344,6 @@ unstar=取消收藏 | |||
star=收藏 | |||
fork=複製 | |||
no_desc=暫無描述 | |||
quick_guide=快速幫助 | |||
clone_this_repo=複製當前儲存庫 | |||
create_new_repo_command=從命令列建立新儲存庫。 |
@@ -1016,7 +1016,6 @@ fork=Fork | |||
download_archive=下載此儲存庫 | |||
more_operations=更多操作 | |||
no_desc=暫無描述 | |||
quick_guide=快速幫助 | |||
clone_this_repo=Clone 此儲存庫 | |||
cite_this_repo=引用此儲存庫 |
@@ -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", |
@@ -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", |
@@ -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) |
@@ -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() | |||
} | |||