jquery/no-merge: [2] | jquery/no-merge: [2] | ||||
jquery/no-param: [2] | jquery/no-param: [2] | ||||
jquery/no-parent: [0] | jquery/no-parent: [0] | ||||
jquery/no-parents: [0] | |||||
jquery/no-parents: [2] | |||||
jquery/no-parse-html: [2] | jquery/no-parse-html: [2] | ||||
jquery/no-prop: [2] | jquery/no-prop: [2] | ||||
jquery/no-proxy: [2] | jquery/no-proxy: [2] | ||||
jquery/no-show: [2] | jquery/no-show: [2] | ||||
jquery/no-size: [2] | jquery/no-size: [2] | ||||
jquery/no-sizzle: [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-text: [0] | ||||
jquery/no-toggle: [2] | jquery/no-toggle: [2] | ||||
jquery/no-trigger: [0] | jquery/no-trigger: [0] | ||||
no-jquery/no-other-utils: [2] | no-jquery/no-other-utils: [2] | ||||
no-jquery/no-param: [2] | no-jquery/no-param: [2] | ||||
no-jquery/no-parent: [0] | 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-literal: [0] | ||||
no-jquery/no-parse-html: [2] | no-jquery/no-parse-html: [2] | ||||
no-jquery/no-parse-json: [2] | no-jquery/no-parse-json: [2] |
linters: | linters: | ||||
enable-all: false | |||||
disable-all: true | |||||
fast: false | |||||
enable: | enable: | ||||
- bidichk | - bidichk | ||||
# - deadcode # deprecated - https://github.com/golangci/golangci-lint/issues/1841 | |||||
- depguard | - depguard | ||||
- dupl | - dupl | ||||
- errcheck | - errcheck | ||||
- forbidigo | - forbidigo | ||||
- gocritic | - gocritic | ||||
# - gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time. | |||||
- gofmt | - gofmt | ||||
- gofumpt | - gofumpt | ||||
- gosimple | - gosimple | ||||
- nolintlint | - nolintlint | ||||
- revive | - revive | ||||
- staticcheck | - staticcheck | ||||
# - structcheck # deprecated - https://github.com/golangci/golangci-lint/issues/1841 | |||||
- stylecheck | - stylecheck | ||||
- typecheck | - typecheck | ||||
- unconvert | - unconvert | ||||
- unused | - unused | ||||
# - varcheck # deprecated - https://github.com/golangci/golangci-lint/issues/1841 | |||||
- wastedassign | - wastedassign | ||||
enable-all: false | |||||
disable-all: true | |||||
fast: false | |||||
run: | run: | ||||
timeout: 10m | timeout: 10m | ||||
output: | |||||
sort-results: true | |||||
linters-settings: | linters-settings: | ||||
stylecheck: | stylecheck: | ||||
checks: ["all", "-ST1005", "-ST1003"] | checks: ["all", "-ST1005", "-ST1003"] | ||||
errorCode: 1 | errorCode: 1 | ||||
warningCode: 1 | warningCode: 1 | ||||
rules: | rules: | ||||
- name: atomic | |||||
- name: bare-return | |||||
- name: blank-imports | - name: blank-imports | ||||
- name: constant-logical-expr | |||||
- name: context-as-argument | - name: context-as-argument | ||||
- name: context-keys-type | - name: context-keys-type | ||||
- name: dot-imports | - name: dot-imports | ||||
- name: duplicated-imports | |||||
- name: empty-lines | |||||
- name: error-naming | |||||
- name: error-return | - name: error-return | ||||
- name: error-strings | - name: error-strings | ||||
- name: error-naming | |||||
- name: errorf | |||||
- name: exported | - name: exported | ||||
- name: identical-branches | |||||
- name: if-return | - name: if-return | ||||
- name: increment-decrement | - name: increment-decrement | ||||
- name: var-naming | |||||
- name: var-declaration | |||||
- name: indent-error-flow | |||||
- name: modifies-value-receiver | |||||
- name: package-comments | - name: package-comments | ||||
- name: range | - name: range | ||||
- name: receiver-naming | - name: receiver-naming | ||||
- name: redefines-builtin-id | |||||
- name: string-of-int | |||||
- name: superfluous-else | |||||
- name: time-naming | - name: time-naming | ||||
- name: unconditional-recursion | |||||
- name: unexported-return | - 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: | gofumpt: | ||||
extra-rules: true | extra-rules: true | ||||
depguard: | depguard: | ||||
max-issues-per-linter: 0 | max-issues-per-linter: 0 | ||||
max-same-issues: 0 | max-same-issues: 0 | ||||
exclude-dirs: [node_modules, public, web_src] | exclude-dirs: [node_modules, public, web_src] | ||||
exclude-case-sensitive: true | |||||
exclude-rules: | exclude-rules: | ||||
# Exclude some linters from running on tests files. | |||||
- path: _test\.go | - path: _test\.go | ||||
linters: | linters: | ||||
- gocyclo | - gocyclo | ||||
- path: cmd | - path: cmd | ||||
linters: | linters: | ||||
- forbidigo | - forbidigo | ||||
- linters: | |||||
- text: "webhook" | |||||
linters: | |||||
- dupl | - dupl | ||||
text: "webhook" | |||||
- linters: | |||||
- text: "`ID' should not be capitalized" | |||||
linters: | |||||
- gocritic | - gocritic | ||||
text: "`ID' should not be capitalized" | |||||
- linters: | |||||
- text: "swagger" | |||||
linters: | |||||
- unused | - unused | ||||
- deadcode | - deadcode | ||||
text: "swagger" | |||||
- linters: | |||||
- text: "argument x is overwritten before first use" | |||||
linters: | |||||
- staticcheck | - staticcheck | ||||
text: "argument x is overwritten before first use" | |||||
- text: "commentFormatting: put a space between `//` and comment text" | - text: "commentFormatting: put a space between `//` and comment text" | ||||
linters: | linters: | ||||
- gocritic | - gocritic |
Name: "skip-index", | Name: "skip-index", | ||||
Usage: "Skip bleve index data", | Usage: "Skip bleve index data", | ||||
}, | }, | ||||
&cli.BoolFlag{ | |||||
Name: "skip-db", | |||||
Usage: "Skip database", | |||||
}, | |||||
&cli.StringFlag{ | &cli.StringFlag{ | ||||
Name: "type", | Name: "type", | ||||
Usage: fmt.Sprintf(`Dump output format, default to "zip", supported types: %s`, strings.Join(dump.SupportedOutputTypes, ", ")), | Usage: fmt.Sprintf(`Dump output format, default to "zip", supported types: %s`, strings.Join(dump.SupportedOutputTypes, ", ")), | ||||
} | } | ||||
} | } | ||||
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) | log.Info("Adding custom configuration file from %s", setting.CustomConf) |
fmt.Fprintf(os.Stderr, " %s\n", url) | fmt.Fprintf(os.Stderr, " %s\n", url) | ||||
} | } | ||||
fmt.Fprintln(os.Stderr, "") | fmt.Fprintln(os.Stderr, "") | ||||
os.Stderr.Sync() | |||||
_ = os.Stderr.Sync() | |||||
} | } | ||||
func pushOptions() map[string]string { | func pushOptions() map[string]string { |
;DEFAULT_THEME = gitea-auto | ;DEFAULT_THEME = gitea-auto | ||||
;; | ;; | ||||
;; All available themes. Allow users select personalized themes regardless of the value of `DEFAULT_THEME`. | ;; 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. | ;; All available reactions users can choose on issues/prs and comments. | ||||
;; Values can be emoji alias (:smile:) or a unicode emoji. | ;; Values can be emoji alias (:smile:) or a unicode emoji. | ||||
;; email = use the username part of the email attribute | ;; email = use the username part of the email attribute | ||||
;; Note: `nickname`, `preferred_username` and `email` options will normalize input strings using the following criteria: | ;; Note: `nickname`, `preferred_username` and `email` options will normalize input strings using the following criteria: | ||||
;; - diacritics are removed | ;; - 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 | ;USERNAME = nickname | ||||
;; | ;; | ||||
;; Update avatar if available from oauth2 provider. | ;; Update avatar if available from oauth2 provider. |
- `SITEMAP_PAGING_NUM`: **20**: Number of items that are displayed in a single subsitemap. | - `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. | - `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. | - `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. | - `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) | - `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 | - `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 | - `REACTIONS`: All available reactions users can choose on issues/prs and comments | ||||
- `email` - use the username part of the email attribute | - `email` - use the username part of the email attribute | ||||
- Note: `nickname`, `preferred_username` and `email` options will normalize input strings using the following criteria: | - Note: `nickname`, `preferred_username` and `email` options will normalize input strings using the following criteria: | ||||
- diacritics are removed | - 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 `-` | - 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. | - `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: | - `ACCOUNT_LINKING`: **login**: How to handle if an account / email already exists: |
- `SITEMAP_PAGING_NUM`: **20**: 在单个子SiteMap中显示的项数。 | - `SITEMAP_PAGING_NUM`: **20**: 在单个子SiteMap中显示的项数。 | ||||
- `GRAPH_MAX_COMMIT_NUM`: **100**: 提交图中显示的最大commit数量。 | - `GRAPH_MAX_COMMIT_NUM`: **100**: 提交图中显示的最大commit数量。 | ||||
- `CODE_COMMENT_LINES`: **4**: 在代码评论中能够显示的最大代码行数。 | - `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`页面中。 | - `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)。 | - `MAX_DISPLAY_FILE_SIZE`: **8388608**: 能够显示文件的最大大小(默认为8MiB)。 | ||||
- `REACTIONS`: 用户可以在问题(Issue)、Pull Request(PR)以及评论中选择的所有可选的反应。 | - `REACTIONS`: 用户可以在问题(Issue)、Pull Request(PR)以及评论中选择的所有可选的反应。 | ||||
这些值可以是表情符号别名(例如::smile:)或Unicode表情符号。 | 这些值可以是表情符号别名(例如::smile:)或Unicode表情符号。 |
1. Add a CSS file to `$GITEA_CUSTOM/public/assets/css/theme-<theme-name>.css`. | 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". | 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). | Community themes are listed in [gitea/awesome-gitea#themes](https://gitea.com/gitea/awesome-gitea#themes). | ||||
Use [Fail2Ban](administration/fail2ban-setup.md) to monitor and stop automated login attempts or other malicious behavior based on log patterns | 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 vs built-in SSH | ||||
SSHD is the built-in SSH server on most Unix systems. | SSHD is the built-in SSH server on most Unix systems. |
使用 [Fail2Ban](administration/fail2ban-setup.md) 监视并阻止基于日志模式的自动登录尝试或其他恶意行为。 | 使用 [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 vs 内建SSH | ||||
SSHD是大多数Unix系统上内建的SSH服务器。 | SSHD是大多数Unix系统上内建的SSH服务器。 |
return fmt.Sprintf("%s/actions/runs/%d", run.Repo.Link(), run.Index) | 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 | // RefLink return the url of run's ref | ||||
func (run *ActionRun) RefLink() string { | func (run *ActionRun) RefLink() string { | ||||
refName := git.RefName(run.Ref) | refName := git.RefName(run.Ref) | ||||
return nil | 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 { | if err := run.Repo.LoadAttributes(ctx); err != nil { | ||||
return err | return err | ||||
} | } | ||||
return nil | 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 { | func (run *ActionRun) Duration() time.Duration { | ||||
return calculateDuration(run.Started, run.Stopped, run.Status) + run.PreviousDuration | return calculateDuration(run.Started, run.Stopped, run.Status) + run.PreviousDuration | ||||
} | } | ||||
return nil, fmt.Errorf("event %s is not a pull request event", run.Event) | 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 { | func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error { | ||||
_, err := db.GetEngine(ctx).ID(repo.ID). | _, err := db.GetEngine(ctx).ID(repo.ID). | ||||
SetExpr("num_action_runs", | SetExpr("num_action_runs", |
// Only affect action runners were a owner ID is set, as actions runners | // Only affect action runners were a owner ID is set, as actions runners | ||||
// could also be created on a repository. | // could also be created on a repository. | ||||
return db.GetEngine(ctx).Table("action_runner"). | 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). | Where("`action_runner`.owner_id != ?", 0). | ||||
And(builder.IsNull{"`user`.id"}). | And(builder.IsNull{"`user`.id"}). | ||||
Count(new(ActionRunner)) | Count(new(ActionRunner)) | ||||
func FixRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) { | func FixRunnersWithoutBelongingOwner(ctx context.Context) (int64, error) { | ||||
subQuery := builder.Select("`action_runner`.id"). | subQuery := builder.Select("`action_runner`.id"). | ||||
From("`action_runner`"). | 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}). | Where(builder.Neq{"`action_runner`.owner_id": 0}). | ||||
And(builder.IsNull{"`user`.id"}) | And(builder.IsNull{"`user`.id"}) | ||||
b := builder.Delete(builder.In("id", subQuery)).From("`action_runner`") | b := builder.Delete(builder.In("id", subQuery)).From("`action_runner`") | ||||
} | } | ||||
return res.RowsAffected() | 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() | |||||
} |
func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, error) { | func GetVariablesOfRun(ctx context.Context, run *ActionRun) (map[string]string, error) { | ||||
variables := map[string]string{} | variables := map[string]string{} | ||||
if err := run.LoadRepo(ctx); err != nil { | |||||
log.Error("LoadRepo: %v", err) | |||||
return nil, err | |||||
} | |||||
// Global | // Global | ||||
globalVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{}) | globalVariables, err := db.Find[ActionVariable](ctx, FindVariablesOpts{}) | ||||
if err != nil { | if err != nil { |
Reason: "gpg.error.no_committer_account", | Reason: "gpg.error.no_committer_account", | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
"github.com/stretchr/testify/assert" | "github.com/stretchr/testify/assert" | ||||
) | ) | ||||
//////////////////// Application | |||||
func TestOAuth2Application_GenerateClientSecret(t *testing.T) { | func TestOAuth2Application_GenerateClientSecret(t *testing.T) { | ||||
assert.NoError(t, unittest.PrepareTestDatabase()) | assert.NoError(t, unittest.PrepareTestDatabase()) | ||||
app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1}) | app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1}) |
// Need to map provided names to beans... | // Need to map provided names to beans... | ||||
beanMap := make(map[string]any) | beanMap := make(map[string]any) | ||||
for _, bean := range tables { | for _, bean := range tables { | ||||
beanMap[strings.ToLower(reflect.Indirect(reflect.ValueOf(bean)).Type().Name())] = bean | beanMap[strings.ToLower(reflect.Indirect(reflect.ValueOf(bean)).Type().Name())] = bean | ||||
beanMap[strings.ToLower(x.TableName(bean))] = bean | beanMap[strings.ToLower(x.TableName(bean))] = bean | ||||
beanMap[strings.ToLower(x.TableName(bean, true))] = bean | beanMap[strings.ToLower(x.TableName(bean, true))] = bean |
return nil, err | return nil, err | ||||
} | } | ||||
} | } | ||||
} else if opts.ReviewerTeam != nil { | } else if opts.ReviewerTeam != nil { | ||||
review.Type = ReviewTypeRequest | review.Type = ReviewTypeRequest | ||||
review.ReviewerTeamID = opts.ReviewerTeam.ID | review.ReviewerTeamID = opts.ReviewerTeam.ID | ||||
} else { | } else { | ||||
return nil, fmt.Errorf("provide either reviewer or reviewer team") | return nil, fmt.Errorf("provide either reviewer or reviewer team") | ||||
} | } |
log.Error("Unable to recreate uniques on table %s. Error: %v", tableName, err) | log.Error("Unable to recreate uniques on table %s. Error: %v", tableName, err) | ||||
return err | return err | ||||
} | } | ||||
case setting.Database.Type.IsMySQL(): | case setting.Database.Type.IsMySQL(): | ||||
// MySQL will drop all the constraints on the old table | // MySQL will drop all the constraints on the old table | ||||
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil { | if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil { | ||||
return err | return err | ||||
} | } | ||||
sequenceMap[sequence] = sequenceData | sequenceMap[sequence] = sequenceData | ||||
} | } | ||||
// CASCADE causes postgres to drop all the constraints on the old table | // CASCADE causes postgres to drop all the constraints on the old table | ||||
return err | return err | ||||
} | } | ||||
} | } | ||||
} | } | ||||
case setting.Database.Type.IsMSSQL(): | case setting.Database.Type.IsMSSQL(): | ||||
// MSSQL will drop all the constraints on the old table | // MSSQL will drop all the constraints on the old table | ||||
if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil { | if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil { | ||||
log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err) | log.Error("Unable to rename %s to %s. Error: %v", tempTableName, tableName, err) | ||||
return err | return err | ||||
} | } | ||||
default: | default: | ||||
log.Fatal("Unrecognized DB") | log.Fatal("Unrecognized DB") | ||||
} | } |
NewMigration("Add missing field of commit status summary table", v1_23.AddCommitStatusSummary2), | NewMigration("Add missing field of commit status summary table", v1_23.AddCommitStatusSummary2), | ||||
// v297 -> v298 | // v297 -> v298 | ||||
NewMigration("Add everyone_access_mode for repo_unit", v1_23.AddRepoUnitEveryoneAccessMode), | 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 | // GetCurrentDBVersion returns the current db version |
for _, u := range units { | for _, u := range units { | ||||
var found bool | var found bool | ||||
for _, team := range teams { | for _, team := range teams { | ||||
var teamU []*TeamUnit | var teamU []*TeamUnit | ||||
var unitEnabled bool | var unitEnabled bool | ||||
err = sess.Where("team_id = ?", team.ID).Find(&teamU) | err = sess.Where("team_id = ?", team.ID).Find(&teamU) | ||||
} | } | ||||
if !protectedBranch.EnableApprovalsWhitelist { | if !protectedBranch.EnableApprovalsWhitelist { | ||||
perm, err := getUserRepoPermission(sess, baseRepo, reviewer) | perm, err := getUserRepoPermission(sess, baseRepo, reviewer) | ||||
if err != nil { | if err != nil { | ||||
return false, err | return false, err |
if err != nil { | if err != nil { | ||||
return err | 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: | case schemas.MSSQL: | ||||
// This column has an index on it. I could write all of the code to attempt to change the index OR | // 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. | // I could just use recreate table. |
// AddConfidentialColumnToOAuth2ApplicationTable: add ConfidentialClient column, setting existing rows to true | // AddConfidentialColumnToOAuth2ApplicationTable: add ConfidentialClient column, setting existing rows to true | ||||
func AddConfidentialClientColumnToOAuth2ApplicationTable(x *xorm.Engine) error { | func AddConfidentialClientColumnToOAuth2ApplicationTable(x *xorm.Engine) error { | ||||
type OAuth2Application struct { | |||||
type oauth2Application struct { | |||||
ID int64 | |||||
ConfidentialClient bool `xorm:"NOT NULL DEFAULT TRUE"` | ConfidentialClient bool `xorm:"NOT NULL DEFAULT TRUE"` | ||||
} | } | ||||
return x.Sync(new(OAuth2Application)) | |||||
return x.Sync(new(oauth2Application)) | |||||
} | } |
func Test_AddConfidentialClientColumnToOAuth2ApplicationTable(t *testing.T) { | func Test_AddConfidentialClientColumnToOAuth2ApplicationTable(t *testing.T) { | ||||
// premigration | // premigration | ||||
type OAuth2Application struct { | |||||
type oauth2Application struct { | |||||
ID int64 | ID int64 | ||||
} | } | ||||
// Prepare and load the testing database | // Prepare and load the testing database | ||||
x, deferable := base.PrepareTestEnv(t, 0, new(OAuth2Application)) | |||||
x, deferable := base.PrepareTestEnv(t, 0, new(oauth2Application)) | |||||
defer deferable() | defer deferable() | ||||
if x == nil || t.Failed() { | if x == nil || t.Failed() { | ||||
return | return | ||||
} | } | ||||
got := []ExpectedOAuth2Application{} | 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 | return | ||||
} | } | ||||
// Convert to new metadata format | // Convert to new metadata format | ||||
new := &MetadataNew{ | |||||
newMetadata := &MetadataNew{ | |||||
Type: old.Type, | Type: old.Type, | ||||
IsTagged: old.IsTagged, | IsTagged: old.IsTagged, | ||||
Platform: old.Platform, | Platform: old.Platform, | ||||
Manifests: manifests, | Manifests: manifests, | ||||
} | } | ||||
metadataJSON, err := json.Marshal(new) | |||||
metadataJSON, err := json.Marshal(newMetadata) | |||||
if err != nil { | if err != nil { | ||||
return err | return err | ||||
} | } |
if setting.Database.Type.IsMySQL() { | if setting.Database.Type.IsMySQL() { | ||||
_, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` MODIFY COLUMN `%s` VARCHAR(64)", alts[0], alts[1])) | _, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` MODIFY COLUMN `%s` VARCHAR(64)", alts[0], alts[1])) | ||||
} else if setting.Database.Type.IsMSSQL() { | } 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 { | } else { | ||||
_, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` ALTER COLUMN `%s` TYPE VARCHAR(64)", alts[0], alts[1])) | _, err = db.Exec(fmt.Sprintf("ALTER TABLE `%s` ALTER COLUMN `%s` TYPE VARCHAR(64)", alts[0], alts[1])) | ||||
} | } |
// 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") | |||||
} |
if _, err := sess.ID(tfa.ID).Cols("scratch_salt, scratch_hash").Update(tfa); err != nil { | 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) | return fmt.Errorf("couldn't add in scratch_hash and scratch_salt: %w", err) | ||||
} | } | ||||
} | } | ||||
} | } | ||||
if _, err := sess.ID(token.ID).Cols("token_hash, token_salt, token_last_eight, sha1").Update(token); err != nil { | 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) | return fmt.Errorf("couldn't add in sha1, token_hash, token_salt and token_last_eight: %w", err) | ||||
} | } | ||||
} | } | ||||
} | } | ||||
if err != nil { | if err != nil { | ||||
if ignoreNonExistent { | if ignoreNonExistent { | ||||
continue | continue | ||||
} else { | |||||
return nil, err | |||||
} | } | ||||
return nil, err | |||||
} | } | ||||
ids = append(ids, u.ID) | ids = append(ids, u.ID) | ||||
} | } |
var items []string | var items []string | ||||
switch project.BoardType { | switch project.BoardType { | ||||
case BoardTypeBugTriage: | case BoardTypeBugTriage: | ||||
items = setting.Project.ProjectBoardBugTriageType | items = setting.Project.ProjectBoardBugTriageType | ||||
case BoardTypeBasicKanban: | case BoardTypeBasicKanban: | ||||
items = setting.Project.ProjectBoardBasicKanbanType | items = setting.Project.ProjectBoardBasicKanbanType | ||||
case BoardTypeNone: | case BoardTypeNone: | ||||
fallthrough | fallthrough | ||||
default: | default: |
// the owner of a private repo needs to be explicitly added. | // the owner of a private repo needs to be explicitly added. | ||||
cond = cond.Or(builder.Eq{"`user`.id": repo.Owner.ID}) | cond = cond.Or(builder.Eq{"`user`.id": repo.Owner.ID}) | ||||
} | } | ||||
} else { | } else { | ||||
// This is a "public" repository: | // This is a "public" repository: | ||||
// Any user that has read access, is a watcher or organization member can be requested to review | // Any user that has read access, is a watcher or organization member can be requested to review |
"code.gitea.io/gitea/models/system" | "code.gitea.io/gitea/models/system" | ||||
"code.gitea.io/gitea/modules/auth/password/hash" | "code.gitea.io/gitea/modules/auth/password/hash" | ||||
"code.gitea.io/gitea/modules/base" | "code.gitea.io/gitea/modules/base" | ||||
"code.gitea.io/gitea/modules/cache" | |||||
"code.gitea.io/gitea/modules/git" | "code.gitea.io/gitea/modules/git" | ||||
"code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
"code.gitea.io/gitea/modules/setting/config" | "code.gitea.io/gitea/modules/setting/config" | ||||
fatalTestError("Error creating test engine: %v\n", err) | fatalTestError("Error creating test engine: %v\n", err) | ||||
} | } | ||||
setting.IsInTesting = true | |||||
setting.AppURL = "https://try.gitea.io/" | setting.AppURL = "https://try.gitea.io/" | ||||
setting.RunUser = "runuser" | setting.RunUser = "runuser" | ||||
setting.SSH.User = "sshuser" | setting.SSH.User = "sshuser" | ||||
config.SetDynGetter(system.NewDatabaseDynKeyGetter()) | config.SetDynGetter(system.NewDatabaseDynKeyGetter()) | ||||
if err = cache.Init(); err != nil { | |||||
fatalTestError("cache.Init: %v\n", err) | |||||
} | |||||
if err = storage.Init(); err != nil { | if err = storage.Init(); err != nil { | ||||
fatalTestError("storage.Init: %v\n", err) | fatalTestError("storage.Init: %v\n", err) | ||||
} | } |
// Note: The set of characters here can safely expand without a breaking change, | // 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 | // but characters removed from this set can cause user account linking to break | ||||
var ( | 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) { | 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 { | 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 | return replaceCharsHyphenRE.ReplaceAllLiteralString(removeCharsRE.ReplaceAllLiteralString(strDiacriticsRemoved, ""), "-"), nil | ||||
} | } | ||||
if err != nil { | if err != nil { | ||||
if ignoreNonExistent { | if ignoreNonExistent { | ||||
continue | continue | ||||
} else { | |||||
return nil, err | |||||
} | } | ||||
return nil, err | |||||
} | } | ||||
ids = append(ids, u.ID) | ids = append(ids, u.ID) | ||||
} | } |
Expected string | Expected string | ||||
IsNormalizedValid bool | IsNormalizedValid bool | ||||
}{ | }{ | ||||
{"test", "test", true}, | |||||
{"name@example.com", "name", true}, | |||||
{"test'`´name", "testname", true}, | |||||
{"Sinéad.O'Connor", "Sinead.OConnor", true}, | {"Sinéad.O'Connor", "Sinead.OConnor", true}, | ||||
{"Æsir", "AEsir", true}, | {"Æsir", "AEsir", true}, | ||||
// \u00e9\u0065\u0301 | |||||
{"éé", "ee", true}, | |||||
{"éé", "ee", true}, // \u00e9\u0065\u0301 | |||||
{"Awareness Hub", "Awareness-Hub", true}, | {"Awareness Hub", "Awareness-Hub", true}, | ||||
{"double__underscore", "double__underscore", false}, // We should consider squashing double non-alpha characters | {"double__underscore", "double__underscore", false}, // We should consider squashing double non-alpha characters | ||||
{".bad.", ".bad.", false}, | {".bad.", ".bad.", false}, | ||||
{"new😀user", "new😀user", false}, // No plans to support | {"new😀user", "new😀user", false}, // No plans to support | ||||
{`"quoted"`, `"quoted"`, false}, // No plans to support | |||||
} | } | ||||
for _, testCase := range testCases { | for _, testCase := range testCases { | ||||
normalizedName, err := user_model.NormalizeUserName(testCase.Input) | normalizedName, err := user_model.NormalizeUserName(testCase.Input) |
func setupComplexity(values []string) { | func setupComplexity(values []string) { | ||||
if len(values) != 1 || values[0] != "off" { | if len(values) != 1 || values[0] != "off" { | ||||
for _, val := range values { | 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 { | if len(requiredList) == 0 { | ||||
// No valid character classes found; use all classes as default | // 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) | |||||
} | } | ||||
} | } | ||||
} | } |
// Deal with the binary hash | // Deal with the binary hash | ||||
idx = 0 | idx = 0 | ||||
len := objectFormat.FullLength() / 2 | |||||
for idx < len { | |||||
length := objectFormat.FullLength() / 2 | |||||
for idx < length { | |||||
var read int | var read int | ||||
read, err = rd.Read(shaBuf[idx:len]) | |||||
read, err = rd.Read(shaBuf[idx:length]) | |||||
n += read | n += read | ||||
if err != nil { | if err != nil { | ||||
return mode, fname, sha, n, err | return mode, fname, sha, n, err |
if len(line) > 0 && line[0] == ' ' { | if len(line) > 0 && line[0] == ' ' { | ||||
_, _ = signatureSB.Write(line[1:]) | _, _ = signatureSB.Write(line[1:]) | ||||
continue | continue | ||||
} else { | |||||
pgpsig = false | |||||
} | } | ||||
pgpsig = false | |||||
} | } | ||||
if !message { | if !message { |
// 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) | |||||
} |
import ( | import ( | ||||
"bufio" | "bufio" | ||||
"fmt" | |||||
"io" | "io" | ||||
"sort" | "sort" | ||||
"strings" | "strings" | ||||
"sync" | "sync" | ||||
"time" | |||||
"code.gitea.io/gitea/modules/git" | "code.gitea.io/gitea/modules/git" | ||||
"github.com/go-git/go-git/v5/plumbing/object" | "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 | // FindLFSFile finds commits that contain a provided pointer file hash | ||||
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) { | func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) { | ||||
resultsMap := map[string]*LFSResult{} | resultsMap := map[string]*LFSResult{} | ||||
All: true, | All: true, | ||||
}) | }) | ||||
if err != nil { | 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 { | err = commitsIter.ForEach(func(gitCommit *object.Commit) error { | ||||
return nil | return nil | ||||
}) | }) | ||||
if err != nil && err != io.EOF { | 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 { | for _, result := range resultsMap { | ||||
select { | select { | ||||
case err, has := <-errChan: | case err, has := <-errChan: | ||||
if has { | 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: | default: | ||||
} | } |
import ( | import ( | ||||
"bufio" | "bufio" | ||||
"bytes" | "bytes" | ||||
"fmt" | |||||
"io" | "io" | ||||
"sort" | "sort" | ||||
"strings" | "strings" | ||||
"sync" | "sync" | ||||
"time" | |||||
"code.gitea.io/gitea/modules/git" | "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 | // FindLFSFile finds commits that contain a provided pointer file hash | ||||
func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) { | func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, error) { | ||||
resultsMap := map[string]*LFSResult{} | resultsMap := map[string]*LFSResult{} | ||||
n += int64(count) | n += int64(count) | ||||
if bytes.Equal(binObjectID, objectID.RawValue()) { | if bytes.Equal(binObjectID, objectID.RawValue()) { | ||||
result := LFSResult{ | 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 | resultsMap[curCommit.ID.String()+":"+curPath+string(fname)] = &result | ||||
} else if string(mode) == git.EntryModeTree.String() { | } else if string(mode) == git.EntryModeTree.String() { | ||||
for _, result := range resultsMap { | for _, result := range resultsMap { | ||||
hasParent := false | hasParent := false | ||||
for _, parentID := range result.ParentIDs { | |||||
for _, parentID := range result.ParentHashes { | |||||
if _, hasParent = resultsMap[parentID.String()+":"+result.Name]; hasParent { | if _, hasParent = resultsMap[parentID.String()+":"+result.Name]; hasParent { | ||||
break | break | ||||
} | } | ||||
errChan <- err | errChan <- err | ||||
break | break | ||||
} | } | ||||
} | } | ||||
}() | }() | ||||
select { | select { | ||||
case err, has := <-errChan: | case err, has := <-errChan: | ||||
if has { | 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: | default: | ||||
} | } |
return nil, err | return nil, err | ||||
} | } | ||||
len := objectFormat.FullLength() | |||||
length := objectFormat.FullLength() | |||||
commits := []*Commit{} | commits := []*Commit{} | ||||
shaline := make([]byte, len+1) | |||||
shaline := make([]byte, length+1) | |||||
for { | for { | ||||
n, err := io.ReadFull(stdoutReader, shaline) | n, err := io.ReadFull(stdoutReader, shaline) | ||||
if err != nil || n < len { | |||||
if err != nil || n < length { | |||||
if err == io.EOF { | if err == io.EOF { | ||||
err = nil | err = nil | ||||
} | } | ||||
return commits, err | return commits, err | ||||
} | } | ||||
objectID, err := NewIDFromString(string(shaline[0:len])) | |||||
objectID, err := NewIDFromString(string(shaline[0:length])) | |||||
if err != nil { | if err != nil { | ||||
return nil, err | return nil, err | ||||
} | } |
// ex: git@try.gitea.io:go-gitea/gitea | // ex: git@try.gitea.io:go-gitea/gitea | ||||
match := scpSyntax.FindAllStringSubmatch(refURI, -1) | match := scpSyntax.FindAllStringSubmatch(refURI, -1) | ||||
if len(match) > 0 { | if len(match) > 0 { | ||||
m := match[0] | m := match[0] | ||||
refHostname := m[2] | refHostname := m[2] | ||||
pth := m[3] | pth := m[3] |
func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error { | 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) | batch := inner_bleve.NewFlushingBatch(b.inner.Indexer, maxBatchSize) | ||||
if len(changes.Updates) > 0 { | 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! | // 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 { | if err := git.EnsureValidGitRepository(ctx, repo.RepoPath()); err != nil { | ||||
log.Error("Unable to open git repo: %s for %-v: %v", repo.RepoPath(), repo, err) | log.Error("Unable to open git repo: %s for %-v: %v", repo.RepoPath(), repo, err) | ||||
if result, err = b.inner.Indexer.Search(facetRequest); err != nil { | if result, err = b.inner.Indexer.Search(facetRequest); err != nil { | ||||
return 0, nil, nil, err | return 0, nil, nil, err | ||||
} | } | ||||
} | } | ||||
languagesFacet := result.Facets["languages"] | languagesFacet := result.Facets["languages"] | ||||
for _, term := range languagesFacet.Terms.Terms() { | for _, term := range languagesFacet.Terms.Terms() { |
searchOpt.Paginator = opts.Paginator | searchOpt.Paginator = opts.Paginator | ||||
switch opts.SortType { | switch opts.SortType { | ||||
case "": | |||||
case "", "latest": | |||||
searchOpt.SortBy = SortByCreatedDesc | searchOpt.SortBy = SortByCreatedDesc | ||||
case "oldest": | case "oldest": | ||||
searchOpt.SortBy = SortByCreatedAsc | searchOpt.SortBy = SortByCreatedAsc | ||||
searchOpt.SortBy = SortByDeadlineDesc | searchOpt.SortBy = SortByDeadlineDesc | ||||
case "priority", "priorityrepo", "project-column-sorting": | case "priority", "priorityrepo", "project-column-sorting": | ||||
// Unsupported sort type for search | // Unsupported sort type for search | ||||
searchOpt.SortBy = SortByUpdatedDesc | |||||
fallthrough | |||||
default: | default: | ||||
searchOpt.SortBy = SortByUpdatedDesc | searchOpt.SortBy = SortByUpdatedDesc | ||||
} | } |
query := elastic.NewBoolQuery() | query := elastic.NewBoolQuery() | ||||
if options.Keyword != "" { | if options.Keyword != "" { | ||||
searchType := esMultiMatchTypePhrasePrefix | searchType := esMultiMatchTypePhrasePrefix | ||||
if options.IsFuzzyKeyword { | if options.IsFuzzyKeyword { | ||||
searchType = esMultiMatchTypeBestFields | searchType = esMultiMatchTypeBestFields |
if mode.Colorize { | if mode.Colorize { | ||||
buf = append(buf, resetBytes...) | buf = append(buf, resetBytes...) | ||||
} | } | ||||
} | } | ||||
if flags&(Lshortfile|Llongfile) != 0 { | if flags&(Lshortfile|Llongfile) != 0 { | ||||
if mode.Colorize { | if mode.Colorize { |
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase) | res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase) | ||||
assert.NoError(t, err, "Unexpected error in testcase: %q", 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) | assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase) | ||||
} | } | ||||
negativeTests := []string{ | negativeTests := []string{ | ||||
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase) | res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase) | ||||
assert.NoError(t, err, "Unexpected error in testcase: %q", 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) | assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase) | ||||
} | } | ||||
} | } | ||||
return e.w.WriteByte(byte(i - 5)) | return e.w.WriteByte(byte(i - 5)) | ||||
} | } | ||||
var len int | |||||
var length int | |||||
if 122 < i && i <= 0xff { | if 122 < i && i <= 0xff { | ||||
len = 1 | |||||
length = 1 | |||||
} else if 0xff < i && i <= 0xffff { | } else if 0xff < i && i <= 0xffff { | ||||
len = 2 | |||||
length = 2 | |||||
} else if 0xffff < i && i <= 0xffffff { | } else if 0xffff < i && i <= 0xffffff { | ||||
len = 3 | |||||
length = 3 | |||||
} else if 0xffffff < i && i <= 0x3fffffff { | } else if 0xffffff < i && i <= 0x3fffffff { | ||||
len = 4 | |||||
length = 4 | |||||
} else if -0x100 <= i && i < -123 { | } else if -0x100 <= i && i < -123 { | ||||
len = -1 | |||||
length = -1 | |||||
} else if -0x10000 <= i && i < -0x100 { | } else if -0x10000 <= i && i < -0x100 { | ||||
len = -2 | |||||
length = -2 | |||||
} else if -0x1000000 <= i && i < -0x100000 { | } else if -0x1000000 <= i && i < -0x100000 { | ||||
len = -3 | |||||
length = -3 | |||||
} else if -0x40000000 <= i && i < -0x1000000 { | } else if -0x40000000 <= i && i < -0x1000000 { | ||||
len = -4 | |||||
length = -4 | |||||
} else { | } else { | ||||
return ErrInvalidIntRange | return ErrInvalidIntRange | ||||
} | } | ||||
if err := e.w.WriteByte(byte(len)); err != nil { | |||||
if err := e.w.WriteByte(byte(length)); err != nil { | |||||
return err | 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 { | if err := e.w.WriteByte(byte(i >> uint(8*c) & 0xff)); err != nil { | ||||
return err | return err | ||||
} | } | ||||
return err | 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 | return err | ||||
} | } | ||||
for i := 0; i < len; i++ { | |||||
for i := 0; i < length; i++ { | |||||
if err := e.marshal(arr.Index(i).Interface()); err != nil { | if err := e.marshal(arr.Index(i).Interface()); err != nil { | ||||
return err | return err | ||||
} | } |
} | } | ||||
sort.Slice(processes, after(processes)) | sort.Slice(processes, after(processes)) | ||||
if !flat { | if !flat { | ||||
var sortChildren func(process *Process) | var sortChildren func(process *Process) | ||||
sortChildren = func(process *Process) { | sortChildren = func(process *Process) { |
// TODO: the logic could be improved in the future, to avoid a data-race between "doStartNewWorker" and "workerNum" | // 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 | // 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. | // 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() | q.workerNumMu.Lock() | ||||
noWorker := q.workerNum == 0 | noWorker := q.workerNum == 0 | ||||
if full || noWorker { | if full || noWorker { | ||||
return true | 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. | // 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]) { | func (q *WorkerPoolQueue[T]) doStartNewWorker(wp *workerGroup[T]) { | ||||
wp.wg.Add(1) | wp.wg.Add(1) | ||||
log.Debug("Queue %q starts new worker", q.GetName()) | log.Debug("Queue %q starts new worker", q.GetName()) | ||||
defer log.Debug("Queue %q stops idle 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) | t := time.NewTicker(workerIdleDuration) | ||||
defer t.Stop() | defer t.Stop() | ||||
} | } | ||||
q.doWorkerHandle(batch) | q.doWorkerHandle(batch) | ||||
// reset the idle ticker, and drain the tick after reset in case a tick is already triggered | // 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: | case <-t.C: | ||||
q.workerNumMu.Lock() | q.workerNumMu.Lock() | ||||
keepWorking = q.workerNum <= 1 // keep the last worker running | keepWorking = q.workerNum <= 1 // keep the last worker running |
workerMaxNum int | workerMaxNum int | ||||
workerActiveNum int | workerActiveNum int | ||||
workerNumMu sync.Mutex | workerNumMu sync.Mutex | ||||
workerStartedCounter int32 | |||||
} | } | ||||
type flushType chan struct{} | type flushType chan struct{} |
import ( | import ( | ||||
"context" | "context" | ||||
"slices" | |||||
"strconv" | "strconv" | ||||
"sync" | "sync" | ||||
"sync/atomic" | |||||
"testing" | "testing" | ||||
"time" | "time" | ||||
func TestWorkerPoolQueueWorkerIdleReset(t *testing.T) { | func TestWorkerPoolQueueWorkerIdleReset(t *testing.T) { | ||||
defer test.MockVariableValue(&workerIdleDuration, 10*time.Millisecond)() | 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) { | 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 | 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) | stop := runWorkerPoolQueue(q) | ||||
for i := 0; i < 20; i++ { | |||||
for i := 0; i < 100; i++ { | |||||
assert.NoError(t, q.Push(i)) | assert.NoError(t, q.Push(i)) | ||||
} | } | ||||
time.Sleep(500 * time.Millisecond) | 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() | stop() | ||||
} | } |
if err != nil { | if err != nil { | ||||
log.Error("Unable to create temporary directory: %s-*.git (%v)", prefix, err) | 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 "", fmt.Errorf("Failed to create dir %s-*.git: %w", prefix, err) | ||||
} | } | ||||
return basePath, nil | return basePath, nil | ||||
} | } |
// 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)} | |||||
} |
import ( | import ( | ||||
"net/http" | "net/http" | ||||
"code.gitea.io/gitea/modules/setting" | |||||
"gitea.com/go-chi/session" | "gitea.com/go-chi/session" | ||||
) | ) | ||||
Get(any) any | Get(any) any | ||||
Set(any, any) error | Set(any, any) error | ||||
Delete(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 | // RegenerateSession regenerates the underlying session and returns the new store | ||||
for _, f := range BeforeRegenerateSession { | for _, f := range BeforeRegenerateSession { | ||||
f(resp, req) | 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. | // BeforeRegenerateSession is a list of functions that are called before a session is regenerated. |
// StartupProblems contains the messages for various startup problems, including: setting option, file/folder, etc | // StartupProblems contains the messages for various startup problems, including: setting option, file/folder, etc | ||||
var StartupProblems []string | 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...) | msg := fmt.Sprintf(format, args...) | ||||
log.Log(skip+1, level, "%s", msg) | log.Log(skip+1, level, "%s", msg) | ||||
StartupProblems = append(StartupProblems, msg) | StartupProblems = append(StartupProblems, msg) | ||||
func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) { | func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) { | ||||
if rootCfg.Section(oldSection).HasKey(oldKey) { | 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 | // 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) { | func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) { | ||||
if rootCfg.Section(oldSection).HasKey(oldKey) { | 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) | |||||
} | } | ||||
} | } | ||||
type OAuth2UsernameType string | type OAuth2UsernameType string | ||||
const ( | 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 { | func (username OAuth2UsernameType) isValid() bool { | ||||
OAuth2Client.EnableAutoRegistration = sec.Key("ENABLE_AUTO_REGISTRATION").MustBool() | OAuth2Client.EnableAutoRegistration = sec.Key("ENABLE_AUTO_REGISTRATION").MustBool() | ||||
OAuth2Client.Username = OAuth2UsernameType(sec.Key("USERNAME").MustString(string(OAuth2UsernameNickname))) | OAuth2Client.Username = OAuth2UsernameType(sec.Key("USERNAME").MustString(string(OAuth2UsernameNickname))) | ||||
if !OAuth2Client.Username.isValid() { | if !OAuth2Client.Username.isValid() { | ||||
log.Warn("Username setting is not valid: '%s', will fallback to '%s'", OAuth2Client.Username, OAuth2UsernameNickname) | |||||
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.UpdateAvatar = sec.Key("UPDATE_AVATAR").MustBool() | ||||
OAuth2Client.AccountLinking = OAuth2AccountLinkingType(sec.Key("ACCOUNT_LINKING").MustString(string(OAuth2AccountLinkingLogin))) | OAuth2Client.AccountLinking = OAuth2AccountLinkingType(sec.Key("ACCOUNT_LINKING").MustString(string(OAuth2AccountLinkingLogin))) | ||||
} | } | ||||
if generalSigningSecret.CompareAndSwap(old, &jwtSecret) { | if generalSigningSecret.CompareAndSwap(old, &jwtSecret) { | ||||
// FIXME: in main branch, the signing token should be refactored (eg: one unique for LFS/OAuth2/etc ...) | // 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 jwtSecret | ||||
} | } | ||||
return *generalSigningSecret.Load() | return *generalSigningSecret.Load() |
func checkOverlappedPath(name, path 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) | // 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 { | 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 | configuredPaths[path] = name | ||||
} | } |
DefaultUILocation, err = time.LoadLocation(zone) | DefaultUILocation, err = time.LoadLocation(zone) | ||||
if err != nil { | if err != nil { | ||||
log.Fatal("Load time zone failed: %v", err) | 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 { | if DefaultUILocation == nil { | ||||
DefaultUILocation = time.Local | DefaultUILocation = time.Local |
ReactionMaxUserNum: 10, | ReactionMaxUserNum: 10, | ||||
MaxDisplayFileSize: 8388608, | MaxDisplayFileSize: 8388608, | ||||
DefaultTheme: `gitea-auto`, | DefaultTheme: `gitea-auto`, | ||||
Themes: []string{`gitea-auto`, `gitea-light`, `gitea-dark`}, | |||||
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, | Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, | ||||
CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`}, | CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`}, | ||||
CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"}, | CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"}, |
"code.gitea.io/gitea/modules/timeutil" | "code.gitea.io/gitea/modules/timeutil" | ||||
"code.gitea.io/gitea/modules/util" | "code.gitea.io/gitea/modules/util" | ||||
"code.gitea.io/gitea/services/gitdiff" | "code.gitea.io/gitea/services/gitdiff" | ||||
"code.gitea.io/gitea/services/webtheme" | |||||
) | ) | ||||
// NewFuncMap returns functions for injecting to templates | // NewFuncMap returns functions for injecting to templates | ||||
"DisableImportLocal": func() bool { | "DisableImportLocal": func() bool { | ||||
return !setting.ImportLocalPaths | 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 { | "NotificationSettings": func() map[string]any { | ||||
return map[string]any{ | return map[string]any{ | ||||
"MinTimeout": int(setting.UI.Notification.MinTimeout / time.Millisecond), | "MinTimeout": int(setting.UI.Notification.MinTimeout / time.Millisecond), | ||||
n, err := eval.Expr(tokens...) | n, err := eval.Expr(tokens...) | ||||
return n.Value, err | 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 | |||||
} |
if setting.IsProd { | if setting.IsProd { | ||||
// in prod mode, Gitea must have correct templates to run | // in prod mode, Gitea must have correct templates to run | ||||
log.Fatal("Gitea can't run with template errors: %s", msg) | 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 { | type templateErrorPrettier struct { |
if err = buildSubjectBodyTemplate(subjectTemplates, bodyTemplates, tmplName, content); err != nil { | if err = buildSubjectBodyTemplate(subjectTemplates, bodyTemplates, tmplName, content); err != nil { | ||||
if firstRun { | if firstRun { | ||||
log.Fatal("Failed to parse mail template, err: %v", err) | 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) | |||||
} | } | ||||
} | } | ||||
} | } |
} | } | ||||
func Test_RandomInt(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) | assert.NoError(t, err) | ||||
} | } | ||||
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. |
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. |
download_archive=Stáhnout repozitář | download_archive=Stáhnout repozitář | ||||
more_operations=Další operace | more_operations=Další operace | ||||
no_desc=Bez popisu | |||||
quick_guide=Krátká příručka | quick_guide=Krátká příručka | ||||
clone_this_repo=Naklonovat tento repozitář | clone_this_repo=Naklonovat tento repozitář | ||||
cite_this_repo=Citovat tento repozitář | cite_this_repo=Citovat tento repozitář |
download_archive=Repository herunterladen | download_archive=Repository herunterladen | ||||
more_operations=Weitere Operationen | more_operations=Weitere Operationen | ||||
no_desc=Keine Beschreibung | |||||
quick_guide=Kurzanleitung | quick_guide=Kurzanleitung | ||||
clone_this_repo=Dieses Repository klonen | clone_this_repo=Dieses Repository klonen | ||||
cite_this_repo=Dieses Repository zitieren | cite_this_repo=Dieses Repository zitieren |
download_archive=Λήψη Αποθετηρίου | download_archive=Λήψη Αποθετηρίου | ||||
more_operations=Περισσότερες Λειτουργίες | more_operations=Περισσότερες Λειτουργίες | ||||
no_desc=Χωρίς Περιγραφή | |||||
quick_guide=Γρήγορος Οδηγός | quick_guide=Γρήγορος Οδηγός | ||||
clone_this_repo=Κλωνοποίηση αυτού του αποθετηρίου | clone_this_repo=Κλωνοποίηση αυτού του αποθετηρίου | ||||
cite_this_repo=Αναφορά σε αυτό το αποθετήριο | cite_this_repo=Αναφορά σε αυτό το αποθετήριο |
oauth.signin.error = There was an error processing the authorization request. If this error persists, please contact the site administrator. | 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.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.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_submit = Connect | ||||
openid_connect_title = Connect to an existing account | openid_connect_title = Connect to an existing account | ||||
openid_connect_desc = The chosen OpenID URI is unknown. Associate it with a new account here. | openid_connect_desc = The chosen OpenID URI is unknown. Associate it with a new account here. | ||||
manage_openid = Manage OpenID Addresses | 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. | 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_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 | primary = Primary | ||||
activated = Activated | activated = Activated | ||||
requires_activation = Requires activation | requires_activation = Requires activation | ||||
download_archive = Download Repository | download_archive = Download Repository | ||||
more_operations = More Operations | more_operations = More Operations | ||||
no_desc = No Description | |||||
quick_guide = Quick Guide | quick_guide = Quick Guide | ||||
clone_this_repo = Clone this repository | clone_this_repo = Clone this repository | ||||
cite_this_repo = Cite this repository | cite_this_repo = Cite this repository |
download_archive=Descargar repositorio | download_archive=Descargar repositorio | ||||
more_operations=Más operaciones | more_operations=Más operaciones | ||||
no_desc=Sin descripción | |||||
quick_guide=Guía rápida | quick_guide=Guía rápida | ||||
clone_this_repo=Clonar este repositorio | clone_this_repo=Clonar este repositorio | ||||
cite_this_repo=Citar este repositorio | cite_this_repo=Citar este repositorio |
fork=انشعاب | fork=انشعاب | ||||
download_archive=دانلود مخزن | download_archive=دانلود مخزن | ||||
no_desc=بدون توضیح | |||||
quick_guide=راهنمای سریع | quick_guide=راهنمای سریع | ||||
clone_this_repo=همسانسازی این مخزن | clone_this_repo=همسانسازی این مخزن | ||||
create_new_repo_command=ایجاد یک مخزن جدید در خط فرمان | create_new_repo_command=ایجاد یک مخزن جدید در خط فرمان |
star=Tähti | star=Tähti | ||||
download_archive=Lataa repo | download_archive=Lataa repo | ||||
no_desc=Ei kuvausta | |||||
quick_guide=Pikaopas | quick_guide=Pikaopas | ||||
clone_this_repo=Kloonaa tämä repo | clone_this_repo=Kloonaa tämä repo | ||||
download_archive=Télécharger ce dépôt | download_archive=Télécharger ce dépôt | ||||
more_operations=Plus d'opérations | more_operations=Plus d'opérations | ||||
no_desc=Aucune description | |||||
quick_guide=Introduction rapide | quick_guide=Introduction rapide | ||||
clone_this_repo=Cloner ce dépôt | clone_this_repo=Cloner ce dépôt | ||||
cite_this_repo=Citer ce dépôt | cite_this_repo=Citer ce dépôt |
fork=Tükrözés | fork=Tükrözés | ||||
download_archive=Tároló letöltése | download_archive=Tároló letöltése | ||||
no_desc=Nincs leírás | |||||
quick_guide=Gyors útmutató | quick_guide=Gyors útmutató | ||||
clone_this_repo=Tároló klónozása | clone_this_repo=Tároló klónozása | ||||
create_new_repo_command=Egy új tároló létrehozása a parancssorból | create_new_repo_command=Egy új tároló létrehozása a parancssorból |
fork=Garpu | fork=Garpu | ||||
download_archive=Unduh Repositori | download_archive=Unduh Repositori | ||||
no_desc=Tidak ada Deskripsi | |||||
quick_guide=Panduan Cepat | quick_guide=Panduan Cepat | ||||
clone_this_repo=Klon repositori ini | clone_this_repo=Klon repositori ini | ||||
create_new_repo_command=Membuat repositori baru pada baris perintah | create_new_repo_command=Membuat repositori baru pada baris perintah |
fork=Tvískipta | fork=Tvískipta | ||||
download_archive=Hlaða Miður Geymslu | download_archive=Hlaða Miður Geymslu | ||||
no_desc=Engin Lýsing | |||||
quick_guide=Stuttar Leiðbeiningar | quick_guide=Stuttar Leiðbeiningar | ||||
clone_this_repo=Afrita þetta hugbúnaðarsafn | clone_this_repo=Afrita þetta hugbúnaðarsafn | ||||
create_new_repo_command=Að búa til nýja geymslu með skipanalínu | create_new_repo_command=Að búa til nýja geymslu með skipanalínu |
fork=Forka | fork=Forka | ||||
download_archive=Scarica Repository | download_archive=Scarica Repository | ||||
no_desc=Nessuna descrizione | |||||
quick_guide=Guida rapida | quick_guide=Guida rapida | ||||
clone_this_repo=Clona questo repository | clone_this_repo=Clona questo repository | ||||
create_new_repo_command=Creazione di un nuovo repository da riga di comando | create_new_repo_command=Creazione di un nuovo repository da riga di comando |
download_archive=リポジトリをダウンロード | download_archive=リポジトリをダウンロード | ||||
more_operations=その他の操作 | more_operations=その他の操作 | ||||
no_desc=説明なし | |||||
quick_guide=クイック ガイド | quick_guide=クイック ガイド | ||||
clone_this_repo=このリポジトリのクローンを作成 | clone_this_repo=このリポジトリのクローンを作成 | ||||
cite_this_repo=このリポジトリを引用 | cite_this_repo=このリポジトリを引用 |
fork=포크 | fork=포크 | ||||
download_archive=저장소 다운로드 | download_archive=저장소 다운로드 | ||||
no_desc=설명 없음 | |||||
quick_guide=퀵 가이드 | quick_guide=퀵 가이드 | ||||
clone_this_repo=이 저장소 복제 | clone_this_repo=이 저장소 복제 | ||||
create_new_repo_command=커맨드 라인에서 새 레포리지터리 생성 | create_new_repo_command=커맨드 라인에서 새 레포리지터리 생성 |
download_archive=Lejupielādēt repozitoriju | download_archive=Lejupielādēt repozitoriju | ||||
more_operations=Vairāk darbību | more_operations=Vairāk darbību | ||||
no_desc=Nav apraksta | |||||
quick_guide=Īsa pamācība | quick_guide=Īsa pamācība | ||||
clone_this_repo=Klonēt šo repozitoriju | clone_this_repo=Klonēt šo repozitoriju | ||||
cite_this_repo=Citēt šo repozitoriju | cite_this_repo=Citēt šo repozitoriju |
fork=Vork | fork=Vork | ||||
download_archive=Download repository | download_archive=Download repository | ||||
no_desc=Geen omschrijving | |||||
quick_guide=Snelstart gids | quick_guide=Snelstart gids | ||||
clone_this_repo=Kloon deze repository | clone_this_repo=Kloon deze repository | ||||
create_new_repo_command=Maak een nieuwe repository aan vanaf de console | create_new_repo_command=Maak een nieuwe repository aan vanaf de console |
fork=Forkuj | fork=Forkuj | ||||
download_archive=Pobierz repozytorium | download_archive=Pobierz repozytorium | ||||
no_desc=Brak opisu | |||||
quick_guide=Skrócona instrukcja | quick_guide=Skrócona instrukcja | ||||
clone_this_repo=Klonuj repozytorium | clone_this_repo=Klonuj repozytorium | ||||
create_new_repo_command=Tworzenie nowego repozytorium z linii poleceń | create_new_repo_command=Tworzenie nowego repozytorium z linii poleceń |
download_archive=Baixar repositório | download_archive=Baixar repositório | ||||
more_operations=Mais Operações | more_operations=Mais Operações | ||||
no_desc=Nenhuma descrição | |||||
quick_guide=Guia Rápido | quick_guide=Guia Rápido | ||||
clone_this_repo=Clonar este repositório | clone_this_repo=Clonar este repositório | ||||
cite_this_repo=Citar este repositório | cite_this_repo=Citar este repositório |
manage_openid=Gerir endereços OpenID | 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. | 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_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 | primary=Principal | ||||
activated=Em uso | activated=Em uso | ||||
requires_activation=Tem que ser habilitado | requires_activation=Tem que ser habilitado | ||||
download_archive=Descarregar repositório | download_archive=Descarregar repositório | ||||
more_operations=Mais operações | more_operations=Mais operações | ||||
no_desc=Sem descrição | |||||
quick_guide=Guia rápido | quick_guide=Guia rápido | ||||
clone_this_repo=Clonar este repositório | clone_this_repo=Clonar este repositório | ||||
cite_this_repo=Citar este repositório | cite_this_repo=Citar este repositório | ||||
settings.protected_branch_can_push=Permitir envios? | settings.protected_branch_can_push=Permitir envios? | ||||
settings.protected_branch_can_push_yes=Pode enviar | settings.protected_branch_can_push_yes=Pode enviar | ||||
settings.protected_branch_can_push_no=Não 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=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_this_branch_desc=Impede a eliminação e restringe envios e integrações do Git no ramo. | ||||
settings.protect_disable_push=Desabilitar envios | settings.protect_disable_push=Desabilitar envios | ||||
settings.protect_protected_file_patterns=Padrões de ficheiros protegidos (separados com ponto e vírgula ';'): | 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_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=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.add_protected_branch=Habilitar salvaguarda | ||||
settings.delete_protected_branch=Desabilitar salvaguarda | settings.delete_protected_branch=Desabilitar salvaguarda | ||||
settings.update_protect_branch_success=A salvaguarda do ramo "%s" foi modificada. | settings.update_protect_branch_success=A salvaguarda do ramo "%s" foi modificada. | ||||
settings.block_outdated_branch_desc=A integração não será possível quando o ramo de topo estiver abaixo do ramo base. | 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.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.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.choose_branch=Escolha um ramo… | ||||
settings.no_protected_branch=Não existem ramos protegidos. | settings.no_protected_branch=Não existem ramos protegidos. | ||||
settings.edit_protected_branch=Editar | settings.edit_protected_branch=Editar | ||||
identity_access=Identidade e acesso | identity_access=Identidade e acesso | ||||
users=Contas de utilizador | users=Contas de utilizador | ||||
organizations=Organizações | organizations=Organizações | ||||
assets=Recursos de código | |||||
assets=Recursos do código-fonte | |||||
repositories=Repositórios | repositories=Repositórios | ||||
hooks=Automatismos web | hooks=Automatismos web | ||||
integrations=Integrações | integrations=Integrações | ||||
dashboard.mcache_structures_usage=Uso das estruturas MCache | dashboard.mcache_structures_usage=Uso das estruturas MCache | ||||
dashboard.mcache_structures_obtained=Estruturas MCache obtidas | dashboard.mcache_structures_obtained=Estruturas MCache obtidas | ||||
dashboard.profiling_bucket_hash_table_obtained=Perfil obtido da tabela de hash do balde | 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.other_system_allocation_obtained=Outras alocações de sistema obtidas | ||||
dashboard.next_gc_recycle=Próxima reciclagem da recolha de lixo | dashboard.next_gc_recycle=Próxima reciclagem da recolha de lixo | ||||
dashboard.last_gc_time=Tempo decorrido desde a última 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_time=Pausa total da recolha de lixo | ||||
dashboard.total_gc_pause=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.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=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.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 | dashboard.update_checker=Verificador de novas versões | ||||
auths.attribute_mail=Atributo do email | auths.attribute_mail=Atributo do email | ||||
auths.attribute_ssh_public_key=Atributo da chave pública SSH | auths.attribute_ssh_public_key=Atributo da chave pública SSH | ||||
auths.attribute_avatar=Atributo do avatar | 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.allow_deactivate_all=Permitir que um resultado de pesquisa vazio desabilite todos os utilizadores | ||||
auths.use_paged_search=Usar pesquisa paginada | auths.use_paged_search=Usar pesquisa paginada | ||||
auths.search_page_size=Tamanho da página | auths.search_page_size=Tamanho da página | ||||
config.session_provider=Fornecedor da sessão | config.session_provider=Fornecedor da sessão | ||||
config.provider_config=Configuração do fornecedor | config.provider_config=Configuração do fornecedor | ||||
config.cookie_name=Nome do cookie | 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.session_life_time=Tempo de vida da sessão | ||||
config.https_only=Apenas HTTPS | config.https_only=Apenas HTTPS | ||||
config.cookie_life_time=Tempo de vida do cookie | config.cookie_life_time=Tempo de vida do cookie |
download_archive=Скачать репозиторий | download_archive=Скачать репозиторий | ||||
more_operations=Ещё действия | more_operations=Ещё действия | ||||
no_desc=Нет описания | |||||
quick_guide=Краткое руководство | quick_guide=Краткое руководство | ||||
clone_this_repo=Клонировать репозиторий | clone_this_repo=Клонировать репозиторий | ||||
cite_this_repo=Сослаться на этот репозиторий | cite_this_repo=Сослаться на этот репозиторий |
fork=දෙබලක | fork=දෙබලක | ||||
download_archive=කෝෂ්ඨය බාගන්න | download_archive=කෝෂ්ඨය බාගන්න | ||||
no_desc=සවිස්තරයක් නැත | |||||
quick_guide=ඉක්මන් මාර්ගෝපදේශය | quick_guide=ඉක්මන් මාර්ගෝපදේශය | ||||
clone_this_repo=මෙම ගබඩාව පරිගණක ක්රිඩාවට සමාන | clone_this_repo=මෙම ගබඩාව පරිගණක ක්රිඩාවට සමාන | ||||
create_new_repo_command=විධාන රේඛාවේ නව ගබඩාවක් නිර්මාණය කිරීම | create_new_repo_command=විධාන රේඛාවේ නව ගබඩාවක් නිර්මාණය කිරීම |
download_archive=Stiahnuť repozitár | download_archive=Stiahnuť repozitár | ||||
more_operations=Viac operácií | more_operations=Viac operácií | ||||
no_desc=Bez popisu | |||||
quick_guide=Rýchly sprievodca | quick_guide=Rýchly sprievodca | ||||
clone_this_repo=Klonovať tento repozitár | clone_this_repo=Klonovať tento repozitár | ||||
create_new_repo_command=Vytvoriť nový repozitár v príkazovom riadku | create_new_repo_command=Vytvoriť nový repozitár v príkazovom riadku |
fork=Förgrening | fork=Förgrening | ||||
download_archive=Ladda Ned Utvecklingskatalogen | download_archive=Ladda Ned Utvecklingskatalogen | ||||
no_desc=Ingen beskrivning | |||||
quick_guide=Snabbguide | quick_guide=Snabbguide | ||||
clone_this_repo=Klona detta repo | clone_this_repo=Klona detta repo | ||||
create_new_repo_command=Skapa en ny utvecklingskatalog på kommandoraden | create_new_repo_command=Skapa en ny utvecklingskatalog på kommandoraden |
download_archive=Depoyu İndir | download_archive=Depoyu İndir | ||||
more_operations=Daha Fazla İşlem | more_operations=Daha Fazla İşlem | ||||
no_desc=Açıklama Yok | |||||
quick_guide=Hızlı Başlangıç Kılavuzu | quick_guide=Hızlı Başlangıç Kılavuzu | ||||
clone_this_repo=Bu depoyu klonla | clone_this_repo=Bu depoyu klonla | ||||
cite_this_repo=Bu depoya atıf ver | cite_this_repo=Bu depoya atıf ver |
fork=Форк | fork=Форк | ||||
download_archive=Скачати репозиторій | download_archive=Скачати репозиторій | ||||
no_desc=Без опису | |||||
quick_guide=Короткий посібник | quick_guide=Короткий посібник | ||||
clone_this_repo=Кнонувати цей репозиторій | clone_this_repo=Кнонувати цей репозиторій | ||||
create_new_repo_command=Створити новий репозиторій з командного рядка | create_new_repo_command=Створити новий репозиторій з командного рядка |
download_archive=下载此仓库 | download_archive=下载此仓库 | ||||
more_operations=更多操作 | more_operations=更多操作 | ||||
no_desc=暂无描述 | |||||
quick_guide=快速帮助 | quick_guide=快速帮助 | ||||
clone_this_repo=克隆当前仓库 | clone_this_repo=克隆当前仓库 | ||||
cite_this_repo=引用此仓库 | cite_this_repo=引用此仓库 |
star=收藏 | star=收藏 | ||||
fork=複製 | fork=複製 | ||||
no_desc=暫無描述 | |||||
quick_guide=快速幫助 | quick_guide=快速幫助 | ||||
clone_this_repo=複製當前儲存庫 | clone_this_repo=複製當前儲存庫 | ||||
create_new_repo_command=從命令列建立新儲存庫。 | create_new_repo_command=從命令列建立新儲存庫。 |
download_archive=下載此儲存庫 | download_archive=下載此儲存庫 | ||||
more_operations=更多操作 | more_operations=更多操作 | ||||
no_desc=暫無描述 | |||||
quick_guide=快速幫助 | quick_guide=快速幫助 | ||||
clone_this_repo=Clone 此儲存庫 | clone_this_repo=Clone 此儲存庫 | ||||
cite_this_repo=引用此儲存庫 | cite_this_repo=引用此儲存庫 |
"esbuild-loader": "4.1.0", | "esbuild-loader": "4.1.0", | ||||
"escape-goat": "4.0.0", | "escape-goat": "4.0.0", | ||||
"fast-glob": "3.3.2", | "fast-glob": "3.3.2", | ||||
"htmx.org": "1.9.11", | |||||
"htmx.org": "1.9.12", | |||||
"idiomorph": "0.3.0", | "idiomorph": "0.3.0", | ||||
"jquery": "3.7.1", | "jquery": "3.7.1", | ||||
"katex": "0.16.10", | "katex": "0.16.10", | ||||
} | } | ||||
}, | }, | ||||
"node_modules/htmx.org": { | "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": { | "node_modules/human-signals": { | ||||
"version": "5.0.0", | "version": "5.0.0", |
"esbuild-loader": "4.1.0", | "esbuild-loader": "4.1.0", | ||||
"escape-goat": "4.0.0", | "escape-goat": "4.0.0", | ||||
"fast-glob": "3.3.2", | "fast-glob": "3.3.2", | ||||
"htmx.org": "1.9.11", | |||||
"htmx.org": "1.9.12", | |||||
"idiomorph": "0.3.0", | "idiomorph": "0.3.0", | ||||
"jquery": "3.7.1", | "jquery": "3.7.1", | ||||
"katex": "0.16.10", | "katex": "0.16.10", |
var task *actions.ActionTask | var task *actions.ActionTask | ||||
if err == nil { | if err == nil { | ||||
task, err = actions.GetTaskByID(req.Context(), tID) | task, err = actions.GetTaskByID(req.Context(), tID) | ||||
if err != nil { | if err != nil { | ||||
log.Error("Error runner api getting task by ID: %v", err) | log.Error("Error runner api getting task by ID: %v", err) |
return | return | ||||
} | } | ||||
upload, close, err := ctx.UploadStream() | |||||
upload, needToClose, err := ctx.UploadStream() | |||||
if err != nil { | if err != nil { | ||||
apiError(ctx, http.StatusInternalServerError, err) | apiError(ctx, http.StatusInternalServerError, err) | ||||
return | return | ||||
} | } | ||||
if close { | |||||
if needToClose { | |||||
defer upload.Close() | defer upload.Close() | ||||
} | } | ||||