Merge branch 'main' into gitlab-comment-reactions

This commit is contained in:
Sebastian Brückner 2024-03-02 05:19:26 +00:00 committed by GitHub
commit 8f74ed3b42
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
482 changed files with 2514 additions and 1337 deletions

View File

@ -19,4 +19,5 @@ jobs:
steps: steps:
- uses: dessant/lock-threads@v5 - uses: dessant/lock-threads@v5
with: with:
issue-inactive-days: 45 issue-inactive-days: 10
pr-inactive-days: 7

2
.gitignore vendored
View File

@ -15,7 +15,7 @@ _test
# MS VSCode # MS VSCode
.vscode .vscode
__debug_bin __debug_bin*
*.cgo1.go *.cgo1.go
*.cgo2.c *.cgo2.c

View File

@ -8,6 +8,7 @@
- [How to report issues](#how-to-report-issues) - [How to report issues](#how-to-report-issues)
- [Types of issues](#types-of-issues) - [Types of issues](#types-of-issues)
- [Discuss your design before the implementation](#discuss-your-design-before-the-implementation) - [Discuss your design before the implementation](#discuss-your-design-before-the-implementation)
- [Issue locking](#issue-locking)
- [Building Gitea](#building-gitea) - [Building Gitea](#building-gitea)
- [Dependencies](#dependencies) - [Dependencies](#dependencies)
- [Backend](#backend) - [Backend](#backend)
@ -103,6 +104,13 @@ the goals for the project and tools.
Pull requests should not be the place for architecture discussions. Pull requests should not be the place for architecture discussions.
### Issue locking
Commenting on closed or merged issues/PRs is strongly discouraged.
Such comments will likely be overlooked as some maintainers may not view notifications on closed issues, thinking that the item is resolved.
As such, commenting on closed/merged issues/PRs may be disabled prior to the scheduled auto-locking if a discussion starts or if unrelated comments are posted.
If further discussion is needed, we encourage you to open a new issue instead and we recommend linking to the issue/PR in question for context.
## Building Gitea ## Building Gitea
See the [development setup instructions](https://docs.gitea.com/development/hacking-on-gitea). See the [development setup instructions](https://docs.gitea.com/development/hacking-on-gitea).

View File

@ -956,6 +956,12 @@ LEVEL = Info
;GO_GET_CLONE_URL_PROTOCOL = https ;GO_GET_CLONE_URL_PROTOCOL = https
;; ;;
;; Close issues as long as a commit on any branch marks it as fixed ;; Close issues as long as a commit on any branch marks it as fixed
;DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false
;;
;; Allow users to push local repositories to Gitea and have them automatically created for a user or an org
;ENABLE_PUSH_CREATE_USER = false
;ENABLE_PUSH_CREATE_ORG = false
;;
;; Comma separated list of globally disabled repo units. Allowed values: repo.issues, repo.ext_issues, repo.pulls, repo.wiki, repo.ext_wiki, repo.projects, repo.packages, repo.actions. ;; Comma separated list of globally disabled repo units. Allowed values: repo.issues, repo.ext_issues, repo.pulls, repo.wiki, repo.ext_wiki, repo.projects, repo.packages, repo.actions.
;DISABLED_REPO_UNITS = ;DISABLED_REPO_UNITS =
;; ;;
@ -1474,8 +1480,9 @@ LEVEL = Info
;; ;;
;; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled ;; Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
;DEFAULT_EMAIL_NOTIFICATIONS = enabled ;DEFAULT_EMAIL_NOTIFICATIONS = enabled
;; Disabled features for users, could be "deletion", more features can be disabled in future ;; Disabled features for users, could be "deletion","manage_gpg_keys" more features can be disabled in future
;; - deletion: a user cannot delete their own account ;; - deletion: a user cannot delete their own account
;; - manage_gpg_keys: a user cannot configure gpg keys
;USER_DISABLED_FEATURES = ;USER_DISABLED_FEATURES =
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@ -518,8 +518,9 @@ And the following unique queues:
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled - `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**: Default configuration for email notifications for users (user configurable). Options: enabled, onmention, disabled
- `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations. - `DISABLE_REGULAR_ORG_CREATION`: **false**: Disallow regular (non-admin) users from creating organizations.
- `USER_DISABLED_FEATURES`: **_empty_** Disabled features for users, could be `deletion` and more features can be added in future. - `USER_DISABLED_FEATURES`: **_empty_** Disabled features for users, could be `deletion`, `manage_gpg_keys` and more features can be added in future.
- `deletion`: User cannot delete their own account. - `deletion`: User cannot delete their own account.
- `manage_gpg_keys`: User cannot configure gpg keys
## Security (`security`) ## Security (`security`)

View File

@ -497,8 +497,9 @@ Gitea 创建以下非唯一队列:
- `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**用户电子邮件通知的默认配置用户可配置。选项enabled、onmention、disabled - `DEFAULT_EMAIL_NOTIFICATIONS`: **enabled**用户电子邮件通知的默认配置用户可配置。选项enabled、onmention、disabled
- `DISABLE_REGULAR_ORG_CREATION`: **false**:禁止普通(非管理员)用户创建组织。 - `DISABLE_REGULAR_ORG_CREATION`: **false**:禁止普通(非管理员)用户创建组织。
- `USER_DISABLED_FEATURES`:**_empty_** 禁用的用户特性,当前允许为空或者 `deletion` 未来可以增加更多设置。 - `USER_DISABLED_FEATURES`:**_empty_** 禁用的用户特性,当前允许为空或者 `deletion``manage_gpg_keys` 未来可以增加更多设置。
- `deletion`: 用户不能通过界面或者API删除他自己。 - `deletion`: 用户不能通过界面或者API删除他自己。
- `manage_gpg_keys`: 用户不能配置 GPG 密钥
## 安全性 (`security`) ## 安全性 (`security`)

View File

@ -224,7 +224,7 @@ Please check [Gitea's logs](administration/logging-config.md) for error messages
{{if not (eq .Body "")}} {{if not (eq .Body "")}}
<h3>Message content</h3> <h3>Message content</h3>
<hr> <hr>
{{.Body | Str2html}} {{.Body | SanitizeHTML}}
{{end}} {{end}}
</p> </p>
<hr> <hr>
@ -260,19 +260,19 @@ The template system contains several functions that can be used to further proce
the messages. Here's a list of some of them: the messages. Here's a list of some of them:
| Name | Parameters | Available | Usage | | Name | Parameters | Available | Usage |
| ---------------- | ----------- | --------- | --------------------------------------------------------------------------- | | ---------------- | ----------- | --------- |-----------------------------------------------------------------------------|
| `AppUrl` | - | Any | Gitea's URL | | `AppUrl` | - | Any | Gitea's URL |
| `AppName` | - | Any | Set from `app.ini`, usually "Gitea" | | `AppName` | - | Any | Set from `app.ini`, usually "Gitea" |
| `AppDomain` | - | Any | Gitea's host name | | `AppDomain` | - | Any | Gitea's host name |
| `EllipsisString` | string, int | Any | Truncates a string to the specified length; adds ellipsis as needed | | `EllipsisString` | string, int | Any | Truncates a string to the specified length; adds ellipsis as needed |
| `Str2html` | string | Body only | Sanitizes text by removing any HTML tags from it. | | `SanitizeHTML` | string | Body only | Sanitizes text by removing any dangerous HTML tags from it. |
| `SafeHTML` | string | Body only | Takes the input as HTML; can be used for `.ReviewComments.RenderedContent`. | | `SafeHTML` | string | Body only | Takes the input as HTML; can be used for `.ReviewComments.RenderedContent`. |
These are _functions_, not metadata, so they have to be used: These are _functions_, not metadata, so they have to be used:
```html ```html
Like this: {{Str2html "Escape<my>text"}} Like this: {{SanitizeHTML "Escape<my>text"}}
Or this: {{"Escape<my>text" | Str2html}} Or this: {{"Escape<my>text" | SanitizeHTML}}
Or this: {{AppUrl}} Or this: {{AppUrl}}
But not like this: {{.AppUrl}} But not like this: {{.AppUrl}}
``` ```

View File

@ -207,7 +207,7 @@ _主题_ 和 _邮件正文_ 由 [Golang的模板引擎](https://go.dev/pkg/text/
{{if not (eq .Body "")}} {{if not (eq .Body "")}}
<h3>消息内容:</h3> <h3>消息内容:</h3>
<hr> <hr>
{{.Body | Str2html}} {{.Body | SanitizeHTML}}
{{end}} {{end}}
</p> </p>
<hr> <hr>
@ -242,20 +242,20 @@ _主题_ 和 _邮件正文_ 由 [Golang的模板引擎](https://go.dev/pkg/text/
模板系统包含一些函数,可用于进一步处理和格式化消息。以下是其中一些函数的列表: 模板系统包含一些函数,可用于进一步处理和格式化消息。以下是其中一些函数的列表:
| 函数名 | 参数 | 可用于 | 用法 | | 函数名 | 参数 | 可用于 | 用法 |
|------------------| ----------- | ------------ | --------------------------------------------------------------------------------- | |------------------| ----------- | ------------ |---------------------------------------------------------|
| `AppUrl` | - | 任何地方 | Gitea 的 URL | | `AppUrl` | - | 任何地方 | Gitea 的 URL |
| `AppName` | - | 任何地方 | 从 `app.ini` 中设置,通常为 "Gitea" | | `AppName` | - | 任何地方 | 从 `app.ini` 中设置,通常为 "Gitea" |
| `AppDomain` | - | 任何地方 | Gitea 的主机名 | | `AppDomain` | - | 任何地方 | Gitea 的主机名 |
| `EllipsisString` | string, int | 任何地方 | 将字符串截断为指定长度;根据需要添加省略号 | | `EllipsisString` | string, int | 任何地方 | 将字符串截断为指定长度;根据需要添加省略号 |
| `Str2html` | string | 仅正文部分 | 通过删除其中的 HTML 标签对文本进行清理 | | `SanitizeHTML` | string | 仅正文部分 | 通过删除其中的危险 HTML 标签对文本进行清理 |
| `SafeHTML` | string | 仅正文部分 | 将输入作为 HTML 处理;可用于 `.ReviewComments.RenderedContent` 等字段 | | `SafeHTML` | string | 仅正文部分 | 将输入作为 HTML 处理;可用于 `.ReviewComments.RenderedContent` 等字段 |
这些都是 _函数_,而不是元数据,因此必须按以下方式使用: 这些都是 _函数_,而不是元数据,因此必须按以下方式使用:
```html ```html
像这样使用: {{Str2html "Escape<my>text"}} 像这样使用: {{SanitizeHTML "Escape<my>text"}}
或者这样使用: {{"Escape<my>text" | Str2html}} 或者这样使用: {{"Escape<my>text" | SanitizeHTML}}
或者这样使用: {{AppUrl}} 或者这样使用: {{AppUrl}}
但不要像这样使用: {{.AppUrl}} 但不要像这样使用: {{.AppUrl}}
``` ```

View File

@ -221,9 +221,11 @@ Our translations are currently crowd-sourced on our [Crowdin project](https://cr
Whether you want to change a translation or add a new one, it will need to be there as all translations are overwritten in our CI via the Crowdin integration. Whether you want to change a translation or add a new one, it will need to be there as all translations are overwritten in our CI via the Crowdin integration.
## Push Hook / Webhook aren't running ## Push Hook / Webhook / Actions aren't running
If you can push but can't see push activities on the home dashboard, or the push doesn't trigger webhook, there are a few possibilities: If you can push but can't see push activities on the home dashboard, or the push doesn't trigger webhook and Actions workflows, it's likely that the git hooks are not working.
There are a few possibilities:
1. The git hooks are out of sync: run "Resynchronize pre-receive, update and post-receive hooks of all repositories" on the site admin panel 1. The git hooks are out of sync: run "Resynchronize pre-receive, update and post-receive hooks of all repositories" on the site admin panel
2. The git repositories (and hooks) are stored on some filesystems (ex: mounted by NAS) which don't support script execution, make sure the filesystem supports `chmod a+x any-script` 2. The git repositories (and hooks) are stored on some filesystems (ex: mounted by NAS) which don't support script execution, make sure the filesystem supports `chmod a+x any-script`

View File

@ -225,9 +225,11 @@ Gitea还提供了自己的SSH服务器用于在SSHD不可用时使用。
无论您想要更改翻译还是添加新的翻译都需要在Crowdin集成中进行因为所有翻译都会被CI覆盖。 无论您想要更改翻译还是添加新的翻译都需要在Crowdin集成中进行因为所有翻译都会被CI覆盖。
## 推送钩子/ Webhook未运行 ## 推送钩子/ Webhook / Actions 未运行
如果您可以推送但无法在主页仪表板上看到推送活动或者推送不触发Webhook有几种可能性 如果您可以推送但无法在主页仪表板上看到推送活动,或者推送不触发 Webhook 和 Actions可能是 git 钩子不工作而导致的。
这可能是由于以下原因:
1. Git钩子不同步在站点管理面板上运行“重新同步所有仓库的pre-receive、update和post-receive钩子” 1. Git钩子不同步在站点管理面板上运行“重新同步所有仓库的pre-receive、update和post-receive钩子”
2. Git仓库和钩子存储在一些不支持脚本执行的文件系统上例如由NAS挂载请确保文件系统支持`chmod a+x any-script` 2. Git仓库和钩子存储在一些不支持脚本执行的文件系统上例如由NAS挂载请确保文件系统支持`chmod a+x any-script`

View File

@ -45,25 +45,24 @@ It is technically possible to implement, but we need to discuss whether it is ne
## Where will the runner download scripts when using actions such as `actions/checkout@v4`? ## Where will the runner download scripts when using actions such as `actions/checkout@v4`?
You may be aware that there are tens of thousands of [marketplace actions](https://github.com/marketplace?type=actions) in GitHub. There are tens of thousands of [actions scripts](https://github.com/marketplace?type=actions) in GitHub, and when you write `uses: actions/checkout@v4`, it downloads the scripts from [github.com/actions/checkout](http://github.com/actions/checkout) by default.
However, when you write `uses: actions/checkout@v4`, it actually downloads the scripts from [gitea.com/actions/checkout](http://gitea.com/actions/checkout) by default (not GitHub). But what if you want to use actions from other places such as gitea.com instead of GitHub?
This is a mirror of [github.com/actions/checkout](http://github.com/actions/checkout), but it's impossible to mirror all of them.
That's why you may encounter failures when trying to use some actions that haven't been mirrored.
The good news is that you can specify the URL prefix to use actions from anywhere. The good news is that you can specify the URL prefix to use actions from anywhere.
This is an extra syntax in Gitea Actions. This is an extra syntax in Gitea Actions.
For example: For example:
- `uses: https://github.com/xxx/xxx@xxx`
- `uses: https://gitea.com/xxx/xxx@xxx` - `uses: https://gitea.com/xxx/xxx@xxx`
- `uses: https://github.com/xxx/xxx@xxx`
- `uses: http://your_gitea_instance.com/xxx@xxx` - `uses: http://your_gitea_instance.com/xxx@xxx`
Be careful, the `https://` or `http://` prefix is necessary! Be careful, the `https://` or `http://` prefix is necessary!
Alternatively, if you want your runners to download actions from GitHub or your own Gitea instance by default, you can configure it by setting `[actions].DEFAULT_ACTIONS_URL`. This is one of the differences from GitHub Actions which supports actions scripts only from GitHub.
See [Configuration Cheat Sheet](administration/config-cheat-sheet.md#actions-actions). But it should allow users much more flexibility in how they run Actions.
This is one of the differences from GitHub Actions, but it should allow users much more flexibility in how they run Actions. Alternatively, if you want your runners to download actions from your own Gitea instance by default, you can configure it by setting `[actions].DEFAULT_ACTIONS_URL`.
See [Configuration Cheat Sheet](administration/config-cheat-sheet.md#actions-actions).
## How to limit the permission of the runners? ## How to limit the permission of the runners?

View File

@ -45,25 +45,25 @@ DEFAULT_REPO_UNITS = ...,repo.actions
## 使用`actions/checkout@v4`等Actions时Job容器会从何处下载脚本 ## 使用`actions/checkout@v4`等Actions时Job容器会从何处下载脚本
您可能知道GitHub上有成千上万个[Actions市场](https://github.com/marketplace?type=actions)。 GitHub 上有成千上万个 [Actions 脚本](https://github.com/marketplace?type=actions)。
然而,当您编写`uses: actions/checkout@v4`时,它实际上默认从[gitea.com/actions/checkout](http://gitea.com/actions/checkout)下载脚本而不是从GitHub下载 当您编写 `uses: actions/checkout@v4` 时,它默认会从 [github.com/actions/checkout](https://github.com/actions/checkout) 下载脚本。
这是[github.com/actions/checkout](http://github.com/actions/checkout)的镜像,但无法将它们全部镜像。 那如果您想使用一些托管在其它平台上的脚本呢,比如在 gitea.com 上的?
这就是为什么在尝试使用尚未镜像的某些Actions时可能会遇到失败的原因。
好消息是您可以指定要从任何位置使用Actions的URL前缀。 好消息是您可以指定要从任何位置使用Actions的URL前缀。
这是Gitea Actions中的额外语法。 这是Gitea Actions中的额外语法。
例如: 例如:
- `uses: https://github.com/xxx/xxx@xxx`
- `uses: https://gitea.com/xxx/xxx@xxx` - `uses: https://gitea.com/xxx/xxx@xxx`
- `uses: https://github.com/xxx/xxx@xxx`
- `uses: http://your_gitea_instance.com/xxx@xxx` - `uses: http://your_gitea_instance.com/xxx@xxx`
注意,`https://`或`http://`前缀是必需的! 注意,`https://`或`http://`前缀是必需的!
另外如果您希望您的Runner默认从GitHub或您自己的Gitea实例下载Actions可以通过设置 `[actions].DEFAULT_ACTIONS_URL`进行配置 这是与 GitHub Actions 的一个区别GitHub Actions 只允许使用托管在 GitHub 上的 actions 脚本
参见[配置速查表](administration/config-cheat-sheet.md#actions-actions) 但用户理应拥有权利去灵活决定如何运行 Actions
这是与GitHub Actions的一个区别但它应该允许用户以更灵活的方式运行Actions。 另外,如果您希望您的 Runner 默认从您自己的 Gitea 实例下载 Actions可以通过设置 `[actions].DEFAULT_ACTIONS_URL`进行配置。
参见[配置速查表](administration/config-cheat-sheet.md#actions-actions)。
## 如何限制Runner的权限 ## 如何限制Runner的权限

View File

@ -0,0 +1,37 @@
---
date: "2023-02-25T00:00:00+00:00"
title: "Badge"
slug: "badge"
sidebar_position: 11
toc: false
draft: false
aliases:
- /en-us/badge
menu:
sidebar:
parent: "usage"
name: "Badge"
sidebar_position: 11
identifier: "Badge"
---
# Badge
Gitea has its builtin Badge system which allows you to display the status of your repository in other places. You can use the following badges:
## Workflow Badge
The Gitea Actions workflow badge is a badge that shows the status of the latest workflow run.
It is designed to be compatible with [GitHub Actions workflow badge](https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/adding-a-workflow-status-badge).
You can use the following URL to get the badge:
```
https://your-gitea-instance.com/{owner}/{repo}/actions/workflows/{workflow_file}?branch={branch}&event={event}
```
- `{owner}`: The owner of the repository.
- `{repo}`: The name of the repository.
- `{workflow_file}`: The name of the workflow file.
- `{branch}`: Optional. The branch of the workflow. Default to your repository's default branch.
- `{event}`: Optional. The event of the workflow. Default to none.

View File

@ -339,6 +339,23 @@ func GetRunByIndex(ctx context.Context, repoID, index int64) (*ActionRun, error)
return run, nil return run, nil
} }
func GetWorkflowLatestRun(ctx context.Context, repoID int64, workflowFile, branch, event string) (*ActionRun, error) {
var run ActionRun
q := db.GetEngine(ctx).Where("repo_id=?", repoID).
And("ref = ?", branch).
And("workflow_id = ?", workflowFile)
if event != "" {
q.And("event = ?", event)
}
has, err := q.Desc("id").Get(&run)
if err != nil {
return nil, err
} else if !has {
return nil, util.NewNotExistErrorf("run with repo_id %d, ref %s, workflow_id %s", repoID, branch, workflowFile)
}
return &run, nil
}
// UpdateRun updates a run. // UpdateRun updates a run.
// It requires the inputted run has Version set. // It requires the inputted run has Version set.
// It will return error if the version is not matched (it means the run has been changed after loaded). // It will return error if the version is not matched (it means the run has been changed after loaded).

View File

@ -8,6 +8,7 @@ package issues
import ( import (
"context" "context"
"fmt" "fmt"
"html/template"
"strconv" "strconv"
"unicode/utf8" "unicode/utf8"
@ -259,8 +260,8 @@ type Comment struct {
CommitID int64 CommitID int64
Line int64 // - previous line / + proposed line Line int64 // - previous line / + proposed line
TreePath string TreePath string
Content string `xorm:"LONGTEXT"` Content string `xorm:"LONGTEXT"`
RenderedContent string `xorm:"-"` RenderedContent template.HTML `xorm:"-"`
// Path represents the 4 lines of code cemented by this comment // Path represents the 4 lines of code cemented by this comment
Patch string `xorm:"-"` Patch string `xorm:"-"`

View File

@ -172,13 +172,9 @@ func FetchIssueContentHistoryList(dbCtx context.Context, issueID, commentID int6
// HasIssueContentHistory check if a ContentHistory entry exists // HasIssueContentHistory check if a ContentHistory entry exists
func HasIssueContentHistory(dbCtx context.Context, issueID, commentID int64) (bool, error) { func HasIssueContentHistory(dbCtx context.Context, issueID, commentID int64) (bool, error) {
exists, err := db.GetEngine(dbCtx).Cols("id").Exist(&ContentHistory{ exists, err := db.GetEngine(dbCtx).Where(builder.Eq{"issue_id": issueID, "comment_id": commentID}).Exist(&ContentHistory{})
IssueID: issueID,
CommentID: commentID,
})
if err != nil { if err != nil {
log.Error("can not fetch issue content history. err=%v", err) return false, fmt.Errorf("can not check issue content history. err: %w", err)
return false, err
} }
return exists, err return exists, err
} }

View File

@ -78,3 +78,22 @@ func TestContentHistory(t *testing.T) {
assert.EqualValues(t, 7, list2[1].HistoryID) assert.EqualValues(t, 7, list2[1].HistoryID)
assert.EqualValues(t, 4, list2[2].HistoryID) assert.EqualValues(t, 4, list2[2].HistoryID)
} }
func TestHasIssueContentHistoryForCommentOnly(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
_ = db.TruncateBeans(db.DefaultContext, &issues_model.ContentHistory{})
hasHistory1, _ := issues_model.HasIssueContentHistory(db.DefaultContext, 10, 0)
assert.False(t, hasHistory1)
hasHistory2, _ := issues_model.HasIssueContentHistory(db.DefaultContext, 10, 100)
assert.False(t, hasHistory2)
_ = issues_model.SaveIssueContentHistory(db.DefaultContext, 1, 10, 100, timeutil.TimeStampNow(), "c-a", true)
_ = issues_model.SaveIssueContentHistory(db.DefaultContext, 1, 10, 100, timeutil.TimeStampNow().Add(5), "c-b", false)
hasHistory1, _ = issues_model.HasIssueContentHistory(db.DefaultContext, 10, 0)
assert.False(t, hasHistory1)
hasHistory2, _ = issues_model.HasIssueContentHistory(db.DefaultContext, 10, 100)
assert.True(t, hasHistory2)
}

View File

@ -7,6 +7,7 @@ package issues
import ( import (
"context" "context"
"fmt" "fmt"
"html/template"
"regexp" "regexp"
"slices" "slices"
@ -105,7 +106,7 @@ type Issue struct {
OriginalAuthorID int64 `xorm:"index"` OriginalAuthorID int64 `xorm:"index"`
Title string `xorm:"name"` Title string `xorm:"name"`
Content string `xorm:"LONGTEXT"` Content string `xorm:"LONGTEXT"`
RenderedContent string `xorm:"-"` RenderedContent template.HTML `xorm:"-"`
Labels []*Label `xorm:"-"` Labels []*Label `xorm:"-"`
MilestoneID int64 `xorm:"INDEX"` MilestoneID int64 `xorm:"INDEX"`
Milestone *Milestone `xorm:"-"` Milestone *Milestone `xorm:"-"`

View File

@ -6,6 +6,7 @@ package issues
import ( import (
"context" "context"
"fmt" "fmt"
"html/template"
"strings" "strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -47,8 +48,8 @@ type Milestone struct {
RepoID int64 `xorm:"INDEX"` RepoID int64 `xorm:"INDEX"`
Repo *repo_model.Repository `xorm:"-"` Repo *repo_model.Repository `xorm:"-"`
Name string Name string
Content string `xorm:"TEXT"` Content string `xorm:"TEXT"`
RenderedContent string `xorm:"-"` RenderedContent template.HTML `xorm:"-"`
IsClosed bool IsClosed bool
NumIssues int NumIssues int
NumClosedIssues int NumClosedIssues int

View File

@ -558,6 +558,8 @@ var migrations = []Migration{
NewMigration("Add PreviousDuration to ActionRun", v1_22.AddPreviousDurationToActionRun), NewMigration("Add PreviousDuration to ActionRun", v1_22.AddPreviousDurationToActionRun),
// v286 -> v287 // v286 -> v287
NewMigration("Add support for SHA256 git repositories", v1_22.AdjustDBForSha256), NewMigration("Add support for SHA256 git repositories", v1_22.AdjustDBForSha256),
// v287 -> v288
NewMigration("Use Slug instead of ID for Badges", v1_22.UseSlugInsteadOfIDForBadges),
} }
// GetCurrentDBVersion returns the current db version // GetCurrentDBVersion returns the current db version

View File

@ -0,0 +1,46 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_22 //nolint
import (
"xorm.io/xorm"
)
type BadgeUnique struct {
ID int64 `xorm:"pk autoincr"`
Slug string `xorm:"UNIQUE"`
}
func (BadgeUnique) TableName() string {
return "badge"
}
func UseSlugInsteadOfIDForBadges(x *xorm.Engine) error {
type Badge struct {
Slug string
}
err := x.Sync(new(Badge))
if err != nil {
return err
}
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
_, err = sess.Exec("UPDATE `badge` SET `slug` = `id` Where `slug` IS NULL")
if err != nil {
return err
}
err = sess.Sync(new(BadgeUnique))
if err != nil {
return err
}
return sess.Commit()
}

View File

@ -0,0 +1,57 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_22 //nolint
import (
"fmt"
"testing"
"code.gitea.io/gitea/models/migrations/base"
"github.com/stretchr/testify/assert"
)
func Test_UpdateBadgeColName(t *testing.T) {
type Badge struct {
ID int64 `xorm:"pk autoincr"`
Description string
ImageURL string
}
// Prepare and load the testing database
x, deferable := base.PrepareTestEnv(t, 0, new(BadgeUnique), new(Badge))
defer deferable()
if x == nil || t.Failed() {
return
}
oldBadges := []Badge{
{ID: 1, Description: "Test Badge 1", ImageURL: "https://example.com/badge1.png"},
{ID: 2, Description: "Test Badge 2", ImageURL: "https://example.com/badge2.png"},
{ID: 3, Description: "Test Badge 3", ImageURL: "https://example.com/badge3.png"},
}
for _, badge := range oldBadges {
_, err := x.Insert(&badge)
assert.NoError(t, err)
}
if err := UseSlugInsteadOfIDForBadges(x); err != nil {
assert.NoError(t, err)
return
}
got := []BadgeUnique{}
if err := x.Table("badge").Asc("id").Find(&got); !assert.NoError(t, err) {
return
}
for i, e := range oldBadges {
got := got[i]
assert.Equal(t, e.ID, got.ID)
assert.Equal(t, fmt.Sprintf("%d", e.ID), got.Slug)
}
// TODO: check if badges have been updated
}

View File

@ -6,6 +6,7 @@ package project
import ( import (
"context" "context"
"fmt" "fmt"
"html/template"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
@ -100,7 +101,7 @@ type Project struct {
CardType CardType CardType CardType
Type Type Type Type
RenderedContent string `xorm:"-"` RenderedContent template.HTML `xorm:"-"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`

View File

@ -7,6 +7,7 @@ package repo
import ( import (
"context" "context"
"fmt" "fmt"
"html/template"
"net/url" "net/url"
"sort" "sort"
"strconv" "strconv"
@ -15,6 +16,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -79,7 +81,7 @@ type Release struct {
NumCommits int64 NumCommits int64
NumCommitsBehind int64 `xorm:"-"` NumCommitsBehind int64 `xorm:"-"`
Note string `xorm:"TEXT"` Note string `xorm:"TEXT"`
RenderedNote string `xorm:"-"` RenderedNote template.HTML `xorm:"-"`
IsDraft bool `xorm:"NOT NULL DEFAULT false"` IsDraft bool `xorm:"NOT NULL DEFAULT false"`
IsPrerelease bool `xorm:"NOT NULL DEFAULT false"` IsPrerelease bool `xorm:"NOT NULL DEFAULT false"`
IsTag bool `xorm:"NOT NULL DEFAULT false"` // will be true only if the record is a tag and has no related releases IsTag bool `xorm:"NOT NULL DEFAULT false"` // will be true only if the record is a tag and has no related releases
@ -228,10 +230,10 @@ type FindReleasesOptions struct {
RepoID int64 RepoID int64
IncludeDrafts bool IncludeDrafts bool
IncludeTags bool IncludeTags bool
IsPreRelease util.OptionalBool IsPreRelease optional.Option[bool]
IsDraft util.OptionalBool IsDraft optional.Option[bool]
TagNames []string TagNames []string
HasSha1 util.OptionalBool // useful to find draft releases which are created with existing tags HasSha1 optional.Option[bool] // useful to find draft releases which are created with existing tags
} }
func (opts FindReleasesOptions) ToConds() builder.Cond { func (opts FindReleasesOptions) ToConds() builder.Cond {
@ -246,14 +248,14 @@ func (opts FindReleasesOptions) ToConds() builder.Cond {
if len(opts.TagNames) > 0 { if len(opts.TagNames) > 0 {
cond = cond.And(builder.In("tag_name", opts.TagNames)) cond = cond.And(builder.In("tag_name", opts.TagNames))
} }
if !opts.IsPreRelease.IsNone() { if opts.IsPreRelease.Has() {
cond = cond.And(builder.Eq{"is_prerelease": opts.IsPreRelease.IsTrue()}) cond = cond.And(builder.Eq{"is_prerelease": opts.IsPreRelease.Value()})
} }
if !opts.IsDraft.IsNone() { if opts.IsDraft.Has() {
cond = cond.And(builder.Eq{"is_draft": opts.IsDraft.IsTrue()}) cond = cond.And(builder.Eq{"is_draft": opts.IsDraft.Value()})
} }
if !opts.HasSha1.IsNone() { if opts.HasSha1.Has() {
if opts.HasSha1.IsTrue() { if opts.HasSha1.Value() {
cond = cond.And(builder.Neq{"sha1": ""}) cond = cond.And(builder.Neq{"sha1": ""})
} else { } else {
cond = cond.And(builder.Eq{"sha1": ""}) cond = cond.And(builder.Eq{"sha1": ""})
@ -275,7 +277,7 @@ func GetTagNamesByRepoID(ctx context.Context, repoID int64) ([]string, error) {
ListOptions: listOptions, ListOptions: listOptions,
IncludeDrafts: true, IncludeDrafts: true,
IncludeTags: true, IncludeTags: true,
HasSha1: util.OptionalBoolTrue, HasSha1: optional.Some(true),
RepoID: repoID, RepoID: repoID,
} }

View File

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -125,11 +126,11 @@ type SearchRepoOptions struct {
// None -> include public and private // None -> include public and private
// True -> include just private // True -> include just private
// False -> include just public // False -> include just public
IsPrivate util.OptionalBool IsPrivate optional.Option[bool]
// None -> include collaborative AND non-collaborative // None -> include collaborative AND non-collaborative
// True -> include just collaborative // True -> include just collaborative
// False -> include just non-collaborative // False -> include just non-collaborative
Collaborate util.OptionalBool Collaborate optional.Option[bool]
// What type of unit the user can be collaborative in, // What type of unit the user can be collaborative in,
// it is ignored if Collaborate is False. // it is ignored if Collaborate is False.
// TypeInvalid means any unit type. // TypeInvalid means any unit type.
@ -137,19 +138,19 @@ type SearchRepoOptions struct {
// None -> include forks AND non-forks // None -> include forks AND non-forks
// True -> include just forks // True -> include just forks
// False -> include just non-forks // False -> include just non-forks
Fork util.OptionalBool Fork optional.Option[bool]
// None -> include templates AND non-templates // None -> include templates AND non-templates
// True -> include just templates // True -> include just templates
// False -> include just non-templates // False -> include just non-templates
Template util.OptionalBool Template optional.Option[bool]
// None -> include mirrors AND non-mirrors // None -> include mirrors AND non-mirrors
// True -> include just mirrors // True -> include just mirrors
// False -> include just non-mirrors // False -> include just non-mirrors
Mirror util.OptionalBool Mirror optional.Option[bool]
// None -> include archived AND non-archived // None -> include archived AND non-archived
// True -> include just archived // True -> include just archived
// False -> include just non-archived // False -> include just non-archived
Archived util.OptionalBool Archived optional.Option[bool]
// only search topic name // only search topic name
TopicOnly bool TopicOnly bool
// only search repositories with specified primary language // only search repositories with specified primary language
@ -159,7 +160,7 @@ type SearchRepoOptions struct {
// None -> include has milestones AND has no milestone // None -> include has milestones AND has no milestone
// True -> include just has milestones // True -> include just has milestones
// False -> include just has no milestone // False -> include just has no milestone
HasMilestones util.OptionalBool HasMilestones optional.Option[bool]
// LowerNames represents valid lower names to restrict to // LowerNames represents valid lower names to restrict to
LowerNames []string LowerNames []string
// When specified true, apply some filters over the conditions: // When specified true, apply some filters over the conditions:
@ -359,12 +360,12 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
))) )))
} }
if opts.IsPrivate != util.OptionalBoolNone { if opts.IsPrivate.Has() {
cond = cond.And(builder.Eq{"is_private": opts.IsPrivate.IsTrue()}) cond = cond.And(builder.Eq{"is_private": opts.IsPrivate.Value()})
} }
if opts.Template != util.OptionalBoolNone { if opts.Template.Has() {
cond = cond.And(builder.Eq{"is_template": opts.Template == util.OptionalBoolTrue}) cond = cond.And(builder.Eq{"is_template": opts.Template.Value()})
} }
// Restrict to starred repositories // Restrict to starred repositories
@ -380,11 +381,11 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
// Restrict repositories to those the OwnerID owns or contributes to as per opts.Collaborate // Restrict repositories to those the OwnerID owns or contributes to as per opts.Collaborate
if opts.OwnerID > 0 { if opts.OwnerID > 0 {
accessCond := builder.NewCond() accessCond := builder.NewCond()
if opts.Collaborate != util.OptionalBoolTrue { if !opts.Collaborate.Value() {
accessCond = builder.Eq{"owner_id": opts.OwnerID} accessCond = builder.Eq{"owner_id": opts.OwnerID}
} }
if opts.Collaborate != util.OptionalBoolFalse { if opts.Collaborate.ValueOrDefault(true) {
// A Collaboration is: // A Collaboration is:
collaborateCond := builder.NewCond() collaborateCond := builder.NewCond()
@ -472,31 +473,32 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
Where(builder.Eq{"language": opts.Language}).And(builder.Eq{"is_primary": true}))) Where(builder.Eq{"language": opts.Language}).And(builder.Eq{"is_primary": true})))
} }
if opts.Fork != util.OptionalBoolNone || opts.OnlyShowRelevant { if opts.Fork.Has() || opts.OnlyShowRelevant {
if opts.OnlyShowRelevant && opts.Fork == util.OptionalBoolNone { if opts.OnlyShowRelevant && !opts.Fork.Has() {
cond = cond.And(builder.Eq{"is_fork": false}) cond = cond.And(builder.Eq{"is_fork": false})
} else { } else {
cond = cond.And(builder.Eq{"is_fork": opts.Fork == util.OptionalBoolTrue}) cond = cond.And(builder.Eq{"is_fork": opts.Fork.Value()})
} }
} }
if opts.Mirror != util.OptionalBoolNone { if opts.Mirror.Has() {
cond = cond.And(builder.Eq{"is_mirror": opts.Mirror == util.OptionalBoolTrue}) cond = cond.And(builder.Eq{"is_mirror": opts.Mirror.Value()})
} }
if opts.Actor != nil && opts.Actor.IsRestricted { if opts.Actor != nil && opts.Actor.IsRestricted {
cond = cond.And(AccessibleRepositoryCondition(opts.Actor, unit.TypeInvalid)) cond = cond.And(AccessibleRepositoryCondition(opts.Actor, unit.TypeInvalid))
} }
if opts.Archived != util.OptionalBoolNone { if opts.Archived.Has() {
cond = cond.And(builder.Eq{"is_archived": opts.Archived == util.OptionalBoolTrue}) cond = cond.And(builder.Eq{"is_archived": opts.Archived.Value()})
} }
switch opts.HasMilestones { if opts.HasMilestones.Has() {
case util.OptionalBoolTrue: if opts.HasMilestones.Value() {
cond = cond.And(builder.Gt{"num_milestones": 0}) cond = cond.And(builder.Gt{"num_milestones": 0})
case util.OptionalBoolFalse: } else {
cond = cond.And(builder.Eq{"num_milestones": 0}.Or(builder.IsNull{"num_milestones"})) cond = cond.And(builder.Eq{"num_milestones": 0}.Or(builder.IsNull{"num_milestones"}))
}
} }
if opts.OnlyShowRelevant { if opts.OnlyShowRelevant {

View File

@ -10,7 +10,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/optional"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -27,62 +27,62 @@ func getTestCases() []struct {
}{ }{
{ {
name: "PublicRepositoriesByName", name: "PublicRepositoriesByName",
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, Collaborate: optional.Some(false)},
count: 7, count: 7,
}, },
{ {
name: "PublicAndPrivateRepositoriesByName", name: "PublicAndPrivateRepositoriesByName",
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, Collaborate: optional.Some(false)},
count: 14, count: 14,
}, },
{ {
name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitFirstPage", name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitFirstPage",
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 5}, Private: true, Collaborate: optional.Some(false)},
count: 14, count: 14,
}, },
{ {
name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitSecondPage", name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitSecondPage",
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 2, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 2, PageSize: 5}, Private: true, Collaborate: optional.Some(false)},
count: 14, count: 14,
}, },
{ {
name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitThirdPage", name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitThirdPage",
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: optional.Some(false)},
count: 14, count: 14,
}, },
{ {
name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitFourthPage", name: "PublicAndPrivateRepositoriesByNameWithPagesizeLimitFourthPage",
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 3, PageSize: 5}, Private: true, Collaborate: optional.Some(false)},
count: 14, count: 14,
}, },
{ {
name: "PublicRepositoriesOfUser", name: "PublicRepositoriesOfUser",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Collaborate: optional.Some(false)},
count: 2, count: 2,
}, },
{ {
name: "PublicRepositoriesOfUser2", name: "PublicRepositoriesOfUser2",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Collaborate: optional.Some(false)},
count: 0, count: 0,
}, },
{ {
name: "PublicRepositoriesOfOrg3", name: "PublicRepositoriesOfOrg3",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Collaborate: optional.Some(false)},
count: 2, count: 2,
}, },
{ {
name: "PublicAndPrivateRepositoriesOfUser", name: "PublicAndPrivateRepositoriesOfUser",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, Collaborate: optional.Some(false)},
count: 4, count: 4,
}, },
{ {
name: "PublicAndPrivateRepositoriesOfUser2", name: "PublicAndPrivateRepositoriesOfUser2",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 18, Private: true, Collaborate: optional.Some(false)},
count: 0, count: 0,
}, },
{ {
name: "PublicAndPrivateRepositoriesOfOrg3", name: "PublicAndPrivateRepositoriesOfOrg3",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 20, Private: true, Collaborate: optional.Some(false)},
count: 4, count: 4,
}, },
{ {
@ -117,32 +117,32 @@ func getTestCases() []struct {
}, },
{ {
name: "PublicRepositoriesOfOrganization", name: "PublicRepositoriesOfOrganization",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Collaborate: optional.Some(false)},
count: 1, count: 1,
}, },
{ {
name: "PublicAndPrivateRepositoriesOfOrganization", name: "PublicAndPrivateRepositoriesOfOrganization",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Private: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, Private: true, Collaborate: optional.Some(false)},
count: 2, count: 2,
}, },
{ {
name: "AllPublic/PublicRepositoriesByName", name: "AllPublic/PublicRepositoriesByName",
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, AllPublic: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{PageSize: 10}, AllPublic: true, Collaborate: optional.Some(false)},
count: 7, count: 7,
}, },
{ {
name: "AllPublic/PublicAndPrivateRepositoriesByName", name: "AllPublic/PublicAndPrivateRepositoriesByName",
opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, AllPublic: true, Collaborate: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{Keyword: "big_test_", ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Private: true, AllPublic: true, Collaborate: optional.Some(false)},
count: 14, count: 14,
}, },
{ {
name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative", name: "AllPublic/PublicRepositoriesOfUserIncludingCollaborative",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, AllPublic: true, Template: optional.Some(false)},
count: 33, count: 33,
}, },
{ {
name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative", name: "AllPublic/PublicAndPrivateRepositoriesOfUserIncludingCollaborative",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 15, Private: true, AllPublic: true, AllLimited: true, Template: optional.Some(false)},
count: 38, count: 38,
}, },
{ {
@ -157,12 +157,12 @@ func getTestCases() []struct {
}, },
{ {
name: "AllPublic/PublicRepositoriesOfOrganization", name: "AllPublic/PublicRepositoriesOfOrganization",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: util.OptionalBoolFalse, Template: util.OptionalBoolFalse}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, OwnerID: 17, AllPublic: true, Collaborate: optional.Some(false), Template: optional.Some(false)},
count: 33, count: 33,
}, },
{ {
name: "AllTemplates", name: "AllTemplates",
opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Template: util.OptionalBoolTrue}, opts: &repo_model.SearchRepoOptions{ListOptions: db.ListOptions{Page: 1, PageSize: 10}, Template: optional.Some(true)},
count: 2, count: 2,
}, },
{ {
@ -190,7 +190,7 @@ func TestSearchRepository(t *testing.T) {
PageSize: 10, PageSize: 10,
}, },
Keyword: "repo_12", Keyword: "repo_12",
Collaborate: util.OptionalBoolFalse, Collaborate: optional.Some(false),
}) })
assert.NoError(t, err) assert.NoError(t, err)
@ -205,7 +205,7 @@ func TestSearchRepository(t *testing.T) {
PageSize: 10, PageSize: 10,
}, },
Keyword: "test_repo", Keyword: "test_repo",
Collaborate: util.OptionalBoolFalse, Collaborate: optional.Some(false),
}) })
assert.NoError(t, err) assert.NoError(t, err)
@ -220,7 +220,7 @@ func TestSearchRepository(t *testing.T) {
}, },
Keyword: "repo_13", Keyword: "repo_13",
Private: true, Private: true,
Collaborate: util.OptionalBoolFalse, Collaborate: optional.Some(false),
}) })
assert.NoError(t, err) assert.NoError(t, err)
@ -236,7 +236,7 @@ func TestSearchRepository(t *testing.T) {
}, },
Keyword: "test_repo", Keyword: "test_repo",
Private: true, Private: true,
Collaborate: util.OptionalBoolFalse, Collaborate: optional.Some(false),
}) })
assert.NoError(t, err) assert.NoError(t, err)
@ -257,7 +257,7 @@ func TestSearchRepository(t *testing.T) {
PageSize: 10, PageSize: 10,
}, },
Keyword: "description_14", Keyword: "description_14",
Collaborate: util.OptionalBoolFalse, Collaborate: optional.Some(false),
IncludeDescription: true, IncludeDescription: true,
}) })
@ -274,7 +274,7 @@ func TestSearchRepository(t *testing.T) {
PageSize: 10, PageSize: 10,
}, },
Keyword: "description_14", Keyword: "description_14",
Collaborate: util.OptionalBoolFalse, Collaborate: optional.Some(false),
IncludeDescription: false, IncludeDescription: false,
}) })
@ -327,30 +327,25 @@ func TestSearchRepository(t *testing.T) {
assert.False(t, repo.IsPrivate) assert.False(t, repo.IsPrivate)
} }
if testCase.opts.Fork == util.OptionalBoolTrue && testCase.opts.Mirror == util.OptionalBoolTrue { if testCase.opts.Fork.Value() && testCase.opts.Mirror.Value() {
assert.True(t, repo.IsFork || repo.IsMirror) assert.True(t, repo.IsFork && repo.IsMirror)
} else { } else {
switch testCase.opts.Fork { if testCase.opts.Fork.Has() {
case util.OptionalBoolFalse: assert.Equal(t, testCase.opts.Fork.Value(), repo.IsFork)
assert.False(t, repo.IsFork)
case util.OptionalBoolTrue:
assert.True(t, repo.IsFork)
} }
switch testCase.opts.Mirror { if testCase.opts.Mirror.Has() {
case util.OptionalBoolFalse: assert.Equal(t, testCase.opts.Mirror.Value(), repo.IsMirror)
assert.False(t, repo.IsMirror)
case util.OptionalBoolTrue:
assert.True(t, repo.IsMirror)
} }
} }
if testCase.opts.OwnerID > 0 && !testCase.opts.AllPublic { if testCase.opts.OwnerID > 0 && !testCase.opts.AllPublic {
switch testCase.opts.Collaborate { if testCase.opts.Collaborate.Has() {
case util.OptionalBoolFalse: if testCase.opts.Collaborate.Value() {
assert.Equal(t, testCase.opts.OwnerID, repo.Owner.ID) assert.NotEqual(t, testCase.opts.OwnerID, repo.Owner.ID)
case util.OptionalBoolTrue: } else {
assert.NotEqual(t, testCase.opts.OwnerID, repo.Owner.ID) assert.Equal(t, testCase.opts.OwnerID, repo.Owner.ID)
}
} }
} }
} }

View File

@ -5,13 +5,15 @@ package user
import ( import (
"context" "context"
"fmt"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
) )
// Badge represents a user badge // Badge represents a user badge
type Badge struct { type Badge struct {
ID int64 `xorm:"pk autoincr"` ID int64 `xorm:"pk autoincr"`
Slug string `xorm:"UNIQUE"`
Description string Description string
ImageURL string ImageURL string
} }
@ -39,3 +41,84 @@ func GetUserBadges(ctx context.Context, u *User) ([]*Badge, int64, error) {
count, err := sess.FindAndCount(&badges) count, err := sess.FindAndCount(&badges)
return badges, count, err return badges, count, err
} }
// CreateBadge creates a new badge.
func CreateBadge(ctx context.Context, badge *Badge) error {
_, err := db.GetEngine(ctx).Insert(badge)
return err
}
// GetBadge returns a badge
func GetBadge(ctx context.Context, slug string) (*Badge, error) {
badge := new(Badge)
has, err := db.GetEngine(ctx).Where("slug=?", slug).Get(badge)
if !has {
return nil, err
}
return badge, err
}
// UpdateBadge updates a badge based on its slug.
func UpdateBadge(ctx context.Context, badge *Badge) error {
_, err := db.GetEngine(ctx).Where("slug=?", badge.Slug).Update(badge)
return err
}
// DeleteBadge deletes a badge.
func DeleteBadge(ctx context.Context, badge *Badge) error {
_, err := db.GetEngine(ctx).Where("slug=?", badge.Slug).Delete(badge)
return err
}
// AddUserBadge adds a badge to a user.
func AddUserBadge(ctx context.Context, u *User, badge *Badge) error {
return AddUserBadges(ctx, u, []*Badge{badge})
}
// AddUserBadges adds badges to a user.
func AddUserBadges(ctx context.Context, u *User, badges []*Badge) error {
return db.WithTx(ctx, func(ctx context.Context) error {
for _, badge := range badges {
// hydrate badge and check if it exists
has, err := db.GetEngine(ctx).Where("slug=?", badge.Slug).Get(badge)
if err != nil {
return err
} else if !has {
return fmt.Errorf("badge with slug %s doesn't exist", badge.Slug)
}
if err := db.Insert(ctx, &UserBadge{
BadgeID: badge.ID,
UserID: u.ID,
}); err != nil {
return err
}
}
return nil
})
}
// RemoveUserBadge removes a badge from a user.
func RemoveUserBadge(ctx context.Context, u *User, badge *Badge) error {
return RemoveUserBadges(ctx, u, []*Badge{badge})
}
// RemoveUserBadges removes badges from a user.
func RemoveUserBadges(ctx context.Context, u *User, badges []*Badge) error {
return db.WithTx(ctx, func(ctx context.Context) error {
for _, badge := range badges {
if _, err := db.GetEngine(ctx).
Join("INNER", "badge", "badge.id = `user_badge`.badge_id").
Where("`user_badge`.user_id=? AND `badge`.slug=?", u.ID, badge.Slug).
Delete(&UserBadge{}); err != nil {
return err
}
}
return nil
})
}
// RemoveAllUserBadges removes all badges from a user.
func RemoveAllUserBadges(ctx context.Context, u *User) error {
_, err := db.GetEngine(ctx).Where("user_id=?", u.ID).Delete(&UserBadge{})
return err
}

View File

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation" "code.gitea.io/gitea/modules/validation"
@ -21,9 +22,6 @@ import (
"xorm.io/builder" "xorm.io/builder"
) )
// ErrEmailNotActivated e-mail address has not been activated error
var ErrEmailNotActivated = util.NewInvalidArgumentErrorf("e-mail address has not been activated")
// ErrEmailCharIsNotSupported e-mail address contains unsupported character // ErrEmailCharIsNotSupported e-mail address contains unsupported character
type ErrEmailCharIsNotSupported struct { type ErrEmailCharIsNotSupported struct {
Email string Email string
@ -313,27 +311,27 @@ func updateActivation(ctx context.Context, email *EmailAddress, activate bool) e
return UpdateUserCols(ctx, user, "rands") return UpdateUserCols(ctx, user, "rands")
} }
// MakeEmailPrimary sets primary email address of given user. func MakeActiveEmailPrimary(ctx context.Context, emailID int64) error {
func MakeEmailPrimary(ctx context.Context, email *EmailAddress) error { return makeEmailPrimaryInternal(ctx, emailID, true)
has, err := db.GetEngine(ctx).Get(email) }
if err != nil {
func MakeInactiveEmailPrimary(ctx context.Context, emailID int64) error {
return makeEmailPrimaryInternal(ctx, emailID, false)
}
func makeEmailPrimaryInternal(ctx context.Context, emailID int64, isActive bool) error {
email := &EmailAddress{}
if has, err := db.GetEngine(ctx).ID(emailID).Where(builder.Eq{"is_activated": isActive}).Get(email); err != nil {
return err return err
} else if !has { } else if !has {
return ErrEmailAddressNotExist{Email: email.Email} return ErrEmailAddressNotExist{}
}
if !email.IsActivated {
return ErrEmailNotActivated
} }
user := &User{} user := &User{}
has, err = db.GetEngine(ctx).ID(email.UID).Get(user) if has, err := db.GetEngine(ctx).ID(email.UID).Get(user); err != nil {
if err != nil {
return err return err
} else if !has { } else if !has {
return ErrUserNotExist{ return ErrUserNotExist{UID: email.UID}
UID: email.UID,
}
} }
ctx, committer, err := db.TxContext(ctx) ctx, committer, err := db.TxContext(ctx)
@ -365,6 +363,21 @@ func MakeEmailPrimary(ctx context.Context, email *EmailAddress) error {
return committer.Commit() return committer.Commit()
} }
// ChangeInactivePrimaryEmail replaces the inactive primary email of a given user
func ChangeInactivePrimaryEmail(ctx context.Context, uid int64, oldEmailAddr, newEmailAddr string) error {
return db.WithTx(ctx, func(ctx context.Context) error {
_, err := db.GetEngine(ctx).Where(builder.Eq{"uid": uid, "lower_email": strings.ToLower(oldEmailAddr)}).Delete(&EmailAddress{})
if err != nil {
return err
}
newEmail, err := InsertEmailAddress(ctx, &EmailAddress{UID: uid, Email: newEmailAddr})
if err != nil {
return err
}
return MakeInactiveEmailPrimary(ctx, newEmail.ID)
})
}
// VerifyActiveEmailCode verifies active email code when active account // VerifyActiveEmailCode verifies active email code when active account
func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddress { func VerifyActiveEmailCode(ctx context.Context, code, email string) *EmailAddress {
minutes := setting.Service.ActiveCodeLives minutes := setting.Service.ActiveCodeLives
@ -404,8 +417,8 @@ type SearchEmailOptions struct {
db.ListOptions db.ListOptions
Keyword string Keyword string
SortType SearchEmailOrderBy SortType SearchEmailOrderBy
IsPrimary util.OptionalBool IsPrimary optional.Option[bool]
IsActivated util.OptionalBool IsActivated optional.Option[bool]
} }
// SearchEmailResult is an e-mail address found in the user or email_address table // SearchEmailResult is an e-mail address found in the user or email_address table
@ -432,18 +445,12 @@ func SearchEmails(ctx context.Context, opts *SearchEmailOptions) ([]*SearchEmail
)) ))
} }
switch { if opts.IsPrimary.Has() {
case opts.IsPrimary.IsTrue(): cond = cond.And(builder.Eq{"email_address.is_primary": opts.IsPrimary.Value()})
cond = cond.And(builder.Eq{"email_address.is_primary": true})
case opts.IsPrimary.IsFalse():
cond = cond.And(builder.Eq{"email_address.is_primary": false})
} }
switch { if opts.IsActivated.Has() {
case opts.IsActivated.IsTrue(): cond = cond.And(builder.Eq{"email_address.is_activated": opts.IsActivated.Value()})
cond = cond.And(builder.Eq{"email_address.is_activated": true})
case opts.IsActivated.IsFalse():
cond = cond.And(builder.Eq{"email_address.is_activated": false})
} }
count, err := db.GetEngine(ctx).Join("INNER", "`user`", "`user`.ID = email_address.uid"). count, err := db.GetEngine(ctx).Join("INNER", "`user`", "`user`.ID = email_address.uid").

View File

@ -9,7 +9,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/optional"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -45,31 +45,22 @@ func TestIsEmailUsed(t *testing.T) {
func TestMakeEmailPrimary(t *testing.T) { func TestMakeEmailPrimary(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
email := &user_model.EmailAddress{ err := user_model.MakeActiveEmailPrimary(db.DefaultContext, 9999999)
Email: "user567890@example.com",
}
err := user_model.MakeEmailPrimary(db.DefaultContext, email)
assert.Error(t, err) assert.Error(t, err)
assert.EqualError(t, err, user_model.ErrEmailAddressNotExist{Email: email.Email}.Error()) assert.ErrorIs(t, err, user_model.ErrEmailAddressNotExist{})
email = &user_model.EmailAddress{ email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "user11@example.com"})
Email: "user11@example.com", err = user_model.MakeActiveEmailPrimary(db.DefaultContext, email.ID)
}
err = user_model.MakeEmailPrimary(db.DefaultContext, email)
assert.Error(t, err) assert.Error(t, err)
assert.EqualError(t, err, user_model.ErrEmailNotActivated.Error()) assert.ErrorIs(t, err, user_model.ErrEmailAddressNotExist{}) // inactive email is considered as not exist for "MakeActiveEmailPrimary"
email = &user_model.EmailAddress{ email = unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "user9999999@example.com"})
Email: "user9999999@example.com", err = user_model.MakeActiveEmailPrimary(db.DefaultContext, email.ID)
}
err = user_model.MakeEmailPrimary(db.DefaultContext, email)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, user_model.IsErrUserNotExist(err)) assert.True(t, user_model.IsErrUserNotExist(err))
email = &user_model.EmailAddress{ email = unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{Email: "user101@example.com"})
Email: "user101@example.com", err = user_model.MakeActiveEmailPrimary(db.DefaultContext, email.ID)
}
err = user_model.MakeEmailPrimary(db.DefaultContext, email)
assert.NoError(t, err) assert.NoError(t, err)
user, _ := user_model.GetUserByID(db.DefaultContext, int64(10)) user, _ := user_model.GetUserByID(db.DefaultContext, int64(10))
@ -137,14 +128,14 @@ func TestListEmails(t *testing.T) {
assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 27 })) assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 27 }))
// Must find only primary addresses (i.e. from the `user` table) // Must find only primary addresses (i.e. from the `user` table)
opts = &user_model.SearchEmailOptions{IsPrimary: util.OptionalBoolTrue} opts = &user_model.SearchEmailOptions{IsPrimary: optional.Some(true)}
emails, _, err = user_model.SearchEmails(db.DefaultContext, opts) emails, _, err = user_model.SearchEmails(db.DefaultContext, opts)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.IsPrimary })) assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.IsPrimary }))
assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return !s.IsPrimary })) assert.False(t, contains(func(s *user_model.SearchEmailResult) bool { return !s.IsPrimary }))
// Must find only inactive addresses (i.e. not validated) // Must find only inactive addresses (i.e. not validated)
opts = &user_model.SearchEmailOptions{IsActivated: util.OptionalBoolFalse} opts = &user_model.SearchEmailOptions{IsActivated: optional.Some(false)}
emails, _, err = user_model.SearchEmails(db.DefaultContext, opts) emails, _, err = user_model.SearchEmails(db.DefaultContext, opts)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return !s.IsActivated })) assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return !s.IsActivated }))

View File

@ -9,8 +9,9 @@ import (
"strings" "strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"xorm.io/builder" "xorm.io/builder"
"xorm.io/xorm" "xorm.io/xorm"
@ -30,11 +31,13 @@ type SearchUserOptions struct {
Actor *User // The user doing the search Actor *User // The user doing the search
SearchByEmail bool // Search by email as well as username/full name SearchByEmail bool // Search by email as well as username/full name
IsActive util.OptionalBool SupportedSortOrders container.Set[string] // if not nil, only allow to use the sort orders in this set
IsAdmin util.OptionalBool
IsRestricted util.OptionalBool IsActive optional.Option[bool]
IsTwoFactorEnabled util.OptionalBool IsAdmin optional.Option[bool]
IsProhibitLogin util.OptionalBool IsRestricted optional.Option[bool]
IsTwoFactorEnabled optional.Option[bool]
IsProhibitLogin optional.Option[bool]
IncludeReserved bool IncludeReserved bool
ExtraParamStrings map[string]string ExtraParamStrings map[string]string
@ -86,24 +89,24 @@ func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Sess
cond = cond.And(builder.Eq{"login_name": opts.LoginName}) cond = cond.And(builder.Eq{"login_name": opts.LoginName})
} }
if !opts.IsActive.IsNone() { if opts.IsActive.Has() {
cond = cond.And(builder.Eq{"is_active": opts.IsActive.IsTrue()}) cond = cond.And(builder.Eq{"is_active": opts.IsActive.Value()})
} }
if !opts.IsAdmin.IsNone() { if opts.IsAdmin.Has() {
cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.IsTrue()}) cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.Value()})
} }
if !opts.IsRestricted.IsNone() { if opts.IsRestricted.Has() {
cond = cond.And(builder.Eq{"is_restricted": opts.IsRestricted.IsTrue()}) cond = cond.And(builder.Eq{"is_restricted": opts.IsRestricted.Value()})
} }
if !opts.IsProhibitLogin.IsNone() { if opts.IsProhibitLogin.Has() {
cond = cond.And(builder.Eq{"prohibit_login": opts.IsProhibitLogin.IsTrue()}) cond = cond.And(builder.Eq{"prohibit_login": opts.IsProhibitLogin.Value()})
} }
e := db.GetEngine(ctx) e := db.GetEngine(ctx)
if opts.IsTwoFactorEnabled.IsNone() { if !opts.IsTwoFactorEnabled.Has() {
return e.Where(cond) return e.Where(cond)
} }
@ -111,7 +114,7 @@ func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Sess
// While using LEFT JOIN, sometimes the performance might not be good, but it won't be a problem now, such SQL is seldom executed. // While using LEFT JOIN, sometimes the performance might not be good, but it won't be a problem now, such SQL is seldom executed.
// There are some possible methods to refactor this SQL in future when we really need to optimize the performance (but not now): // There are some possible methods to refactor this SQL in future when we really need to optimize the performance (but not now):
// (1) add a column in user table (2) add a setting value in user_setting table (3) use search engines (bleve/elasticsearch) // (1) add a column in user table (2) add a setting value in user_setting table (3) use search engines (bleve/elasticsearch)
if opts.IsTwoFactorEnabled.IsTrue() { if opts.IsTwoFactorEnabled.Value() {
cond = cond.And(builder.Expr("two_factor.uid IS NOT NULL")) cond = cond.And(builder.Expr("two_factor.uid IS NOT NULL"))
} else { } else {
cond = cond.And(builder.Expr("two_factor.uid IS NULL")) cond = cond.And(builder.Expr("two_factor.uid IS NULL"))
@ -128,7 +131,7 @@ func SearchUsers(ctx context.Context, opts *SearchUserOptions) (users []*User, _
defer sessCount.Close() defer sessCount.Close()
count, err := sessCount.Count(new(User)) count, err := sessCount.Count(new(User))
if err != nil { if err != nil {
return nil, 0, fmt.Errorf("Count: %w", err) return nil, 0, fmt.Errorf("count: %w", err)
} }
if len(opts.OrderBy) == 0 { if len(opts.OrderBy) == 0 {

View File

@ -16,10 +16,10 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password/hash" "code.gitea.io/gitea/modules/auth/password/hash"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -103,29 +103,29 @@ func TestSearchUsers(t *testing.T) {
testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}}, testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}},
[]int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40}) []int64{1, 2, 4, 5, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40})
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolFalse}, testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(false)},
[]int64{9}) []int64{9})
testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, testUserSuccess(&user_model.SearchUserOptions{OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
[]int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40}) []int64{1, 2, 4, 5, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 21, 24, 27, 28, 29, 30, 32, 34, 37, 38, 39, 40})
testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", OrderBy: "id ASC", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18}) []int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
// order by name asc default // order by name asc default
testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", ListOptions: db.ListOptions{Page: 1}, IsActive: util.OptionalBoolTrue}, testUserSuccess(&user_model.SearchUserOptions{Keyword: "user1", ListOptions: db.ListOptions{Page: 1}, IsActive: optional.Some(true)},
[]int64{1, 10, 11, 12, 13, 14, 15, 16, 18}) []int64{1, 10, 11, 12, 13, 14, 15, 16, 18})
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsAdmin: util.OptionalBoolTrue}, testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsAdmin: optional.Some(true)},
[]int64{1}) []int64{1})
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsRestricted: util.OptionalBoolTrue}, testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsRestricted: optional.Some(true)},
[]int64{29}) []int64{29})
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsProhibitLogin: util.OptionalBoolTrue}, testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsProhibitLogin: optional.Some(true)},
[]int64{37}) []int64{37})
testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsTwoFactorEnabled: util.OptionalBoolTrue}, testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsTwoFactorEnabled: optional.Some(true)},
[]int64{24}) []int64{24})
} }

View File

@ -35,6 +35,9 @@ func FullSteps(task *actions_model.ActionTask) []*actions_model.ActionTaskStep {
} else if task.Status.IsDone() { } else if task.Status.IsDone() {
preStep.Stopped = task.Stopped preStep.Stopped = task.Stopped
preStep.Status = actions_model.StatusFailure preStep.Status = actions_model.StatusFailure
if task.Status.IsSkipped() {
preStep.Status = actions_model.StatusSkipped
}
} }
logIndex += preStep.LogLength logIndex += preStep.LogLength

View File

@ -441,6 +441,9 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa
// all acts conditions should be satisfied // all acts conditions should be satisfied
for cond, vals := range acts { for cond, vals := range acts {
switch cond { switch cond {
case "types":
// types have been checked
continue
case "branches": case "branches":
refName := git.RefName(prPayload.PullRequest.Base.Ref) refName := git.RefName(prPayload.PullRequest.Base.Ref)
patterns, err := workflowpattern.CompilePatterns(vals...) patterns, err := workflowpattern.CompilePatterns(vals...)

104
modules/badge/badge.go Normal file
View File

@ -0,0 +1,104 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package badge
import (
actions_model "code.gitea.io/gitea/models/actions"
)
// The Badge layout: |offset|label|message|
// We use 10x scale to calculate more precisely
// Then scale down to normal size in tmpl file
type Label struct {
text string
width int
}
func (l Label) Text() string {
return l.text
}
func (l Label) Width() int {
return l.width
}
func (l Label) TextLength() int {
return int(float64(l.width-defaultOffset) * 9.5)
}
func (l Label) X() int {
return l.width*5 + 10
}
type Message struct {
text string
width int
x int
}
func (m Message) Text() string {
return m.text
}
func (m Message) Width() int {
return m.width
}
func (m Message) X() int {
return m.x
}
func (m Message) TextLength() int {
return int(float64(m.width-defaultOffset) * 9.5)
}
type Badge struct {
Color string
FontSize int
Label Label
Message Message
}
func (b Badge) Width() int {
return b.Label.width + b.Message.width
}
const (
defaultOffset = 9
defaultFontSize = 11
DefaultColor = "#9f9f9f" // Grey
defaultFontWidth = 7 // approximate speculation
)
var StatusColorMap = map[actions_model.Status]string{
actions_model.StatusSuccess: "#4c1", // Green
actions_model.StatusSkipped: "#dfb317", // Yellow
actions_model.StatusUnknown: "#97ca00", // Light Green
actions_model.StatusFailure: "#e05d44", // Red
actions_model.StatusCancelled: "#fe7d37", // Orange
actions_model.StatusWaiting: "#dfb317", // Yellow
actions_model.StatusRunning: "#dfb317", // Yellow
actions_model.StatusBlocked: "#dfb317", // Yellow
}
// GenerateBadge generates badge with given template
func GenerateBadge(label, message, color string) Badge {
lw := defaultFontWidth*len(label) + defaultOffset
mw := defaultFontWidth*len(message) + defaultOffset
x := lw*10 + mw*5 - 10
return Badge{
Label: Label{
text: label,
width: lw,
},
Message: Message{
text: message,
width: mw,
x: x,
},
FontSize: defaultFontSize * 10,
Color: color,
}
}

View File

@ -20,10 +20,10 @@ import (
"code.gitea.io/gitea/modules/indexer/issues/internal" "code.gitea.io/gitea/modules/indexer/issues/internal"
"code.gitea.io/gitea/modules/indexer/issues/meilisearch" "code.gitea.io/gitea/modules/indexer/issues/meilisearch"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
) )
// IndexerMetadata is used to send data to the queue, so it contains only the ids. // IndexerMetadata is used to send data to the queue, so it contains only the ids.
@ -220,7 +220,7 @@ func PopulateIssueIndexer(ctx context.Context) error {
ListOptions: db_model.ListOptions{Page: page, PageSize: repo_model.RepositoryListDefaultPageSize}, ListOptions: db_model.ListOptions{Page: page, PageSize: repo_model.RepositoryListDefaultPageSize},
OrderBy: db_model.SearchOrderByID, OrderBy: db_model.SearchOrderByID,
Private: true, Private: true,
Collaborate: util.OptionalBoolFalse, Collaborate: optional.Some(false),
}) })
if err != nil { if err != nil {
log.Error("SearchRepositoryByName: %v", err) log.Error("SearchRepositoryByName: %v", err)

View File

@ -397,7 +397,7 @@ func TestRender_ShortLinks(t *testing.T) {
}, },
}, input) }, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
buffer, err = markdown.RenderString(&markup.RenderContext{ buffer, err = markdown.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext, Ctx: git.DefaultContext,
Links: markup.Links{ Links: markup.Links{
@ -407,7 +407,7 @@ func TestRender_ShortLinks(t *testing.T) {
IsWiki: true, IsWiki: true,
}, input) }, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer)) assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
} }
mediatree := util.URLJoin(markup.TestRepoURL, "media", "master") mediatree := util.URLJoin(markup.TestRepoURL, "media", "master")
@ -510,7 +510,7 @@ func TestRender_RelativeImages(t *testing.T) {
Metas: localMetas, Metas: localMetas,
}, input) }, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
buffer, err = markdown.RenderString(&markup.RenderContext{ buffer, err = markdown.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext, Ctx: git.DefaultContext,
Links: markup.Links{ Links: markup.Links{
@ -520,7 +520,7 @@ func TestRender_RelativeImages(t *testing.T) {
IsWiki: true, IsWiki: true,
}, input) }, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer)) assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
} }
rawwiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw") rawwiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw")

View File

@ -6,6 +6,7 @@ package markdown
import ( import (
"fmt" "fmt"
"html/template"
"io" "io"
"strings" "strings"
"sync" "sync"
@ -262,12 +263,12 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
} }
// RenderString renders Markdown string to HTML with all specific handling stuff and return string // RenderString renders Markdown string to HTML with all specific handling stuff and return string
func RenderString(ctx *markup.RenderContext, content string) (string, error) { func RenderString(ctx *markup.RenderContext, content string) (template.HTML, error) {
var buf strings.Builder var buf strings.Builder
if err := Render(ctx, strings.NewReader(content), &buf); err != nil { if err := Render(ctx, strings.NewReader(content), &buf); err != nil {
return "", err return "", err
} }
return buf.String(), nil return template.HTML(buf.String()), nil
} }
// RenderRaw renders Markdown to HTML without handling special links. // RenderRaw renders Markdown to HTML without handling special links.

View File

@ -5,6 +5,7 @@ package markdown_test
import ( import (
"context" "context"
"html/template"
"os" "os"
"strings" "strings"
"testing" "testing"
@ -21,12 +22,11 @@ import (
) )
const ( const (
AppURL = "http://localhost:3000/" AppURL = "http://localhost:3000/"
Repo = "gogits/gogs" FullURL = AppURL + "gogits/gogs/"
AppSubURL = AppURL + Repo + "/"
) )
// these values should match the Repo const above // these values should match the const above
var localMetas = map[string]string{ var localMetas = map[string]string{
"user": "gogits", "user": "gogits",
"repo": "gogs", "repo": "gogs",
@ -48,34 +48,33 @@ func TestMain(m *testing.M) {
func TestRender_StandardLinks(t *testing.T) { func TestRender_StandardLinks(t *testing.T) {
setting.AppURL = AppURL setting.AppURL = AppURL
setting.AppSubURL = AppSubURL
test := func(input, expected, expectedWiki string) { test := func(input, expected, expectedWiki string) {
buffer, err := markdown.RenderString(&markup.RenderContext{ buffer, err := markdown.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext, Ctx: git.DefaultContext,
Links: markup.Links{ Links: markup.Links{
Base: setting.AppSubURL, Base: FullURL,
}, },
}, input) }, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
buffer, err = markdown.RenderString(&markup.RenderContext{ buffer, err = markdown.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext, Ctx: git.DefaultContext,
Links: markup.Links{ Links: markup.Links{
Base: setting.AppSubURL, Base: FullURL,
}, },
IsWiki: true, IsWiki: true,
}, input) }, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer)) assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
} }
googleRendered := `<p><a href="https://google.com/" rel="nofollow">https://google.com/</a></p>` googleRendered := `<p><a href="https://google.com/" rel="nofollow">https://google.com/</a></p>`
test("<https://google.com/>", googleRendered, googleRendered) test("<https://google.com/>", googleRendered, googleRendered)
lnk := util.URLJoin(AppSubURL, "WikiPage") lnk := util.URLJoin(FullURL, "WikiPage")
lnkWiki := util.URLJoin(AppSubURL, "wiki", "WikiPage") lnkWiki := util.URLJoin(FullURL, "wiki", "WikiPage")
test("[WikiPage](WikiPage)", test("[WikiPage](WikiPage)",
`<p><a href="`+lnk+`" rel="nofollow">WikiPage</a></p>`, `<p><a href="`+lnk+`" rel="nofollow">WikiPage</a></p>`,
`<p><a href="`+lnkWiki+`" rel="nofollow">WikiPage</a></p>`) `<p><a href="`+lnkWiki+`" rel="nofollow">WikiPage</a></p>`)
@ -83,23 +82,22 @@ func TestRender_StandardLinks(t *testing.T) {
func TestRender_Images(t *testing.T) { func TestRender_Images(t *testing.T) {
setting.AppURL = AppURL setting.AppURL = AppURL
setting.AppSubURL = AppSubURL
test := func(input, expected string) { test := func(input, expected string) {
buffer, err := markdown.RenderString(&markup.RenderContext{ buffer, err := markdown.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext, Ctx: git.DefaultContext,
Links: markup.Links{ Links: markup.Links{
Base: setting.AppSubURL, Base: FullURL,
}, },
}, input) }, input)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
} }
url := "../../.images/src/02/train.jpg" url := "../../.images/src/02/train.jpg"
title := "Train" title := "Train"
href := "https://gitea.io" href := "https://gitea.io"
result := util.URLJoin(AppSubURL, url) result := util.URLJoin(FullURL, url)
// hint: With Markdown v2.5.2, there is a new syntax: [link](URL){:target="_blank"} , but we do not support it now // hint: With Markdown v2.5.2, there is a new syntax: [link](URL){:target="_blank"} , but we do not support it now
test( test(
@ -289,33 +287,32 @@ This PR has been generated by [Renovate Bot](https://github.com/renovatebot/reno
func TestTotal_RenderWiki(t *testing.T) { func TestTotal_RenderWiki(t *testing.T) {
setting.AppURL = AppURL setting.AppURL = AppURL
setting.AppSubURL = AppSubURL
answers := testAnswers(util.URLJoin(AppSubURL, "wiki"), util.URLJoin(AppSubURL, "wiki", "raw")) answers := testAnswers(util.URLJoin(FullURL, "wiki"), util.URLJoin(FullURL, "wiki", "raw"))
for i := 0; i < len(sameCases); i++ { for i := 0; i < len(sameCases); i++ {
line, err := markdown.RenderString(&markup.RenderContext{ line, err := markdown.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext, Ctx: git.DefaultContext,
Links: markup.Links{ Links: markup.Links{
Base: setting.AppSubURL, Base: FullURL,
}, },
Metas: localMetas, Metas: localMetas,
IsWiki: true, IsWiki: true,
}, sameCases[i]) }, sameCases[i])
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, answers[i], line) assert.Equal(t, template.HTML(answers[i]), line)
} }
testCases := []string{ testCases := []string{
// Guard wiki sidebar: special syntax // Guard wiki sidebar: special syntax
`[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`, `[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
// rendered // rendered
`<p><a href="` + AppSubURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p> `<p><a href="` + FullURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
`, `,
// special syntax // special syntax
`[[Name|Link]]`, `[[Name|Link]]`,
// rendered // rendered
`<p><a href="` + AppSubURL + `wiki/Link" rel="nofollow">Name</a></p> `<p><a href="` + FullURL + `wiki/Link" rel="nofollow">Name</a></p>
`, `,
} }
@ -323,32 +320,31 @@ func TestTotal_RenderWiki(t *testing.T) {
line, err := markdown.RenderString(&markup.RenderContext{ line, err := markdown.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext, Ctx: git.DefaultContext,
Links: markup.Links{ Links: markup.Links{
Base: setting.AppSubURL, Base: FullURL,
}, },
IsWiki: true, IsWiki: true,
}, testCases[i]) }, testCases[i])
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, testCases[i+1], line) assert.Equal(t, template.HTML(testCases[i+1]), line)
} }
} }
func TestTotal_RenderString(t *testing.T) { func TestTotal_RenderString(t *testing.T) {
setting.AppURL = AppURL setting.AppURL = AppURL
setting.AppSubURL = AppSubURL
answers := testAnswers(util.URLJoin(AppSubURL, "src", "master"), util.URLJoin(AppSubURL, "media", "master")) answers := testAnswers(util.URLJoin(FullURL, "src", "master"), util.URLJoin(FullURL, "media", "master"))
for i := 0; i < len(sameCases); i++ { for i := 0; i < len(sameCases); i++ {
line, err := markdown.RenderString(&markup.RenderContext{ line, err := markdown.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext, Ctx: git.DefaultContext,
Links: markup.Links{ Links: markup.Links{
Base: AppSubURL, Base: FullURL,
BranchPath: "master", BranchPath: "master",
}, },
Metas: localMetas, Metas: localMetas,
}, sameCases[i]) }, sameCases[i])
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, answers[i], line) assert.Equal(t, template.HTML(answers[i]), line)
} }
testCases := []string{} testCases := []string{}
@ -357,11 +353,11 @@ func TestTotal_RenderString(t *testing.T) {
line, err := markdown.RenderString(&markup.RenderContext{ line, err := markdown.RenderString(&markup.RenderContext{
Ctx: git.DefaultContext, Ctx: git.DefaultContext,
Links: markup.Links{ Links: markup.Links{
Base: AppSubURL, Base: FullURL,
}, },
}, testCases[i]) }, testCases[i])
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, testCases[i+1], line) assert.Equal(t, template.HTML(testCases[i+1]), line)
} }
} }
@ -428,7 +424,7 @@ func TestRenderEmojiInLinks_Issue12331(t *testing.T) {
` `
res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, testcase) res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, testcase)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expected, res) assert.Equal(t, template.HTML(expected), res)
} }
func TestColorPreview(t *testing.T) { func TestColorPreview(t *testing.T) {
@ -462,7 +458,7 @@ func TestColorPreview(t *testing.T) {
for _, test := range positiveTests { for _, test := range positiveTests {
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, 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)
} }
@ -529,7 +525,7 @@ func TestMathBlock(t *testing.T) {
for _, test := range testcases { for _, test := range testcases {
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, 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)
} }
} }
@ -567,12 +563,12 @@ foo: bar
for _, test := range testcases { for _, test := range testcases {
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, 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)
} }
} }
func TestRenderLinks(t *testing.T) { func TestRenderLinks(t *testing.T) {
input := ` space @mention-user input := ` space @mention-user${SPACE}${SPACE}
/just/a/path.bin /just/a/path.bin
https://example.com/file.bin https://example.com/file.bin
[local link](file.bin) [local link](file.bin)
@ -593,8 +589,9 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
mail@domain.com mail@domain.com
@mention-user test @mention-user test
#123 #123
space space${SPACE}${SPACE}
` `
input = strings.ReplaceAll(input, "${SPACE}", " ") // replace ${SPACE} with " ", to avoid some editor's auto-trimming
cases := []struct { cases := []struct {
Links markup.Links Links markup.Links
IsWiki bool IsWiki bool
@ -957,6 +954,6 @@ space</p>
for i, c := range cases { for i, c := range cases {
result, err := markdown.RenderString(&markup.RenderContext{Ctx: context.Background(), Links: c.Links, IsWiki: c.IsWiki}, input) result, err := markdown.RenderString(&markup.RenderContext{Ctx: context.Background(), Links: c.Links, IsWiki: c.IsWiki}, input)
assert.NoError(t, err, "Unexpected error in testcase: %v", i) assert.NoError(t, err, "Unexpected error in testcase: %v", i)
assert.Equal(t, c.Expected, result, "Unexpected result in testcase %v", i) assert.Equal(t, template.HTML(c.Expected), result, "Unexpected result in testcase %v", i)
} }
} }

View File

@ -27,6 +27,16 @@ func TestOption(t *testing.T) {
assert.Equal(t, int(1), some.Value()) assert.Equal(t, int(1), some.Value())
assert.Equal(t, int(1), some.ValueOrDefault(2)) assert.Equal(t, int(1), some.ValueOrDefault(2))
noneBool := optional.None[bool]()
assert.False(t, noneBool.Has())
assert.False(t, noneBool.Value())
assert.True(t, noneBool.ValueOrDefault(true))
someBool := optional.Some(true)
assert.True(t, someBool.Has())
assert.True(t, someBool.Value())
assert.True(t, someBool.ValueOrDefault(false))
var ptr *int var ptr *int
assert.False(t, optional.FromPtr(ptr).Has()) assert.False(t, optional.FromPtr(ptr).Has())

View File

@ -6,22 +6,18 @@ package repository
import ( import (
"context" "context"
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
"time"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/label" "code.gitea.io/gitea/modules/label"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/options" "code.gitea.io/gitea/modules/options"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
asymkey_service "code.gitea.io/gitea/services/asymkey"
) )
type OptionFile struct { type OptionFile struct {
@ -124,70 +120,6 @@ func LoadRepoConfig() error {
return nil return nil
} }
// InitRepoCommit temporarily changes with work directory.
func InitRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Repository, u *user_model.User, defaultBranch string) (err error) {
commitTimeStr := time.Now().Format(time.RFC3339)
sig := u.NewGitSig()
// Because this may call hooks we should pass in the environment
env := append(os.Environ(),
"GIT_AUTHOR_NAME="+sig.Name,
"GIT_AUTHOR_EMAIL="+sig.Email,
"GIT_AUTHOR_DATE="+commitTimeStr,
"GIT_COMMITTER_DATE="+commitTimeStr,
)
committerName := sig.Name
committerEmail := sig.Email
if stdout, _, err := git.NewCommand(ctx, "add", "--all").
SetDescription(fmt.Sprintf("initRepoCommit (git add): %s", tmpPath)).
RunStdString(&git.RunOpts{Dir: tmpPath}); err != nil {
log.Error("git add --all failed: Stdout: %s\nError: %v", stdout, err)
return fmt.Errorf("git add --all: %w", err)
}
cmd := git.NewCommand(ctx, "commit", "--message=Initial commit").
AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email)
sign, keyID, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u)
if sign {
cmd.AddOptionFormat("-S%s", keyID)
if repo.GetTrustModel() == repo_model.CommitterTrustModel || repo.GetTrustModel() == repo_model.CollaboratorCommitterTrustModel {
// need to set the committer to the KeyID owner
committerName = signer.Name
committerEmail = signer.Email
}
} else {
cmd.AddArguments("--no-gpg-sign")
}
env = append(env,
"GIT_COMMITTER_NAME="+committerName,
"GIT_COMMITTER_EMAIL="+committerEmail,
)
if stdout, _, err := cmd.
SetDescription(fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath)).
RunStdString(&git.RunOpts{Dir: tmpPath, Env: env}); err != nil {
log.Error("Failed to commit: %v: Stdout: %s\nError: %v", cmd.String(), stdout, err)
return fmt.Errorf("git commit: %w", err)
}
if len(defaultBranch) == 0 {
defaultBranch = setting.Repository.DefaultBranch
}
if stdout, _, err := git.NewCommand(ctx, "push", "origin").AddDynamicArguments("HEAD:" + defaultBranch).
SetDescription(fmt.Sprintf("initRepoCommit (git push): %s", tmpPath)).
RunStdString(&git.RunOpts{Dir: tmpPath, Env: InternalPushingEnvironment(u, repo)}); err != nil {
log.Error("Failed to push back to HEAD: Stdout: %s\nError: %v", stdout, err)
return fmt.Errorf("git push: %w", err)
}
return nil
}
func CheckInitRepository(ctx context.Context, owner, name, objectFormatName string) (err error) { func CheckInitRepository(ctx context.Context, owner, name, objectFormatName string) (err error) {
// Somehow the directory could exist. // Somehow the directory could exist.
repoPath := repo_model.RepoPath(owner, name) repoPath := repo_model.RepoPath(owner, name)

View File

@ -5,16 +5,13 @@ package repository
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"net/http"
"strings" "strings"
"time" "time"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git" git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/organization"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
@ -22,10 +19,8 @@ import (
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/migration"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
) )
/* /*
@ -47,244 +42,6 @@ func WikiRemoteURL(ctx context.Context, remote string) string {
return "" return ""
} }
// MigrateRepositoryGitData starts migrating git related data after created migrating repository
func MigrateRepositoryGitData(ctx context.Context, u *user_model.User,
repo *repo_model.Repository, opts migration.MigrateOptions,
httpTransport *http.Transport,
) (*repo_model.Repository, error) {
repoPath := repo_model.RepoPath(u.Name, opts.RepoName)
if u.IsOrganization() {
t, err := organization.OrgFromUser(u).GetOwnerTeam(ctx)
if err != nil {
return nil, err
}
repo.NumWatches = t.NumMembers
} else {
repo.NumWatches = 1
}
migrateTimeout := time.Duration(setting.Git.Timeout.Migrate) * time.Second
var err error
if err = util.RemoveAll(repoPath); err != nil {
return repo, fmt.Errorf("Failed to remove %s: %w", repoPath, err)
}
if err = git.Clone(ctx, opts.CloneAddr, repoPath, git.CloneRepoOptions{
Mirror: true,
Quiet: true,
Timeout: migrateTimeout,
SkipTLSVerify: setting.Migrations.SkipTLSVerify,
}); err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return repo, fmt.Errorf("Clone timed out. Consider increasing [git.timeout] MIGRATE in app.ini. Underlying Error: %w", err)
}
return repo, fmt.Errorf("Clone: %w", err)
}
if err := git.WriteCommitGraph(ctx, repoPath); err != nil {
return repo, err
}
if opts.Wiki {
wikiPath := repo_model.WikiPath(u.Name, opts.RepoName)
wikiRemotePath := WikiRemoteURL(ctx, opts.CloneAddr)
if len(wikiRemotePath) > 0 {
if err := util.RemoveAll(wikiPath); err != nil {
return repo, fmt.Errorf("Failed to remove %s: %w", wikiPath, err)
}
if err := git.Clone(ctx, wikiRemotePath, wikiPath, git.CloneRepoOptions{
Mirror: true,
Quiet: true,
Timeout: migrateTimeout,
Branch: "master",
SkipTLSVerify: setting.Migrations.SkipTLSVerify,
}); err != nil {
log.Warn("Clone wiki: %v", err)
if err := util.RemoveAll(wikiPath); err != nil {
return repo, fmt.Errorf("Failed to remove %s: %w", wikiPath, err)
}
} else {
if err := git.WriteCommitGraph(ctx, wikiPath); err != nil {
return repo, err
}
}
}
}
if repo.OwnerID == u.ID {
repo.Owner = u
}
if err = CheckDaemonExportOK(ctx, repo); err != nil {
return repo, fmt.Errorf("checkDaemonExportOK: %w", err)
}
if stdout, _, err := git.NewCommand(ctx, "update-server-info").
SetDescription(fmt.Sprintf("MigrateRepositoryGitData(git update-server-info): %s", repoPath)).
RunStdString(&git.RunOpts{Dir: repoPath}); err != nil {
log.Error("MigrateRepositoryGitData(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
return repo, fmt.Errorf("error in MigrateRepositoryGitData(git update-server-info): %w", err)
}
gitRepo, err := git.OpenRepository(ctx, repoPath)
if err != nil {
return repo, fmt.Errorf("OpenRepository: %w", err)
}
defer gitRepo.Close()
repo.IsEmpty, err = gitRepo.IsEmpty()
if err != nil {
return repo, fmt.Errorf("git.IsEmpty: %w", err)
}
if !repo.IsEmpty {
if len(repo.DefaultBranch) == 0 {
// Try to get HEAD branch and set it as default branch.
headBranch, err := gitRepo.GetHEADBranch()
if err != nil {
return repo, fmt.Errorf("GetHEADBranch: %w", err)
}
if headBranch != nil {
repo.DefaultBranch = headBranch.Name
}
}
if _, err := SyncRepoBranchesWithRepo(ctx, repo, gitRepo, u.ID); err != nil {
return repo, fmt.Errorf("SyncRepoBranchesWithRepo: %v", err)
}
if !opts.Releases {
// note: this will greatly improve release (tag) sync
// for pull-mirrors with many tags
repo.IsMirror = opts.Mirror
if err = SyncReleasesWithTags(ctx, repo, gitRepo); err != nil {
log.Error("Failed to synchronize tags to releases for repository: %v", err)
}
}
if opts.LFS {
endpoint := lfs.DetermineEndpoint(opts.CloneAddr, opts.LFSEndpoint)
lfsClient := lfs.NewClient(endpoint, httpTransport)
if err = StoreMissingLfsObjectsInRepository(ctx, repo, gitRepo, lfsClient); err != nil {
log.Error("Failed to store missing LFS objects for repository: %v", err)
}
}
}
ctx, committer, err := db.TxContext(ctx)
if err != nil {
return nil, err
}
defer committer.Close()
if opts.Mirror {
remoteAddress, err := util.SanitizeURL(opts.CloneAddr)
if err != nil {
return repo, err
}
mirrorModel := repo_model.Mirror{
RepoID: repo.ID,
Interval: setting.Mirror.DefaultInterval,
EnablePrune: true,
NextUpdateUnix: timeutil.TimeStampNow().AddDuration(setting.Mirror.DefaultInterval),
LFS: opts.LFS,
RemoteAddress: remoteAddress,
}
if opts.LFS {
mirrorModel.LFSEndpoint = opts.LFSEndpoint
}
if opts.MirrorInterval != "" {
parsedInterval, err := time.ParseDuration(opts.MirrorInterval)
if err != nil {
log.Error("Failed to set Interval: %v", err)
return repo, err
}
if parsedInterval == 0 {
mirrorModel.Interval = 0
mirrorModel.NextUpdateUnix = 0
} else if parsedInterval < setting.Mirror.MinInterval {
err := fmt.Errorf("interval %s is set below Minimum Interval of %s", parsedInterval, setting.Mirror.MinInterval)
log.Error("Interval: %s is too frequent", opts.MirrorInterval)
return repo, err
} else {
mirrorModel.Interval = parsedInterval
mirrorModel.NextUpdateUnix = timeutil.TimeStampNow().AddDuration(parsedInterval)
}
}
if err = repo_model.InsertMirror(ctx, &mirrorModel); err != nil {
return repo, fmt.Errorf("InsertOne: %w", err)
}
repo.IsMirror = true
if err = UpdateRepository(ctx, repo, false); err != nil {
return nil, err
}
// this is necessary for sync local tags from remote
configName := fmt.Sprintf("remote.%s.fetch", mirrorModel.GetRemoteName())
if stdout, _, err := git.NewCommand(ctx, "config").
AddOptionValues("--add", configName, `+refs/tags/*:refs/tags/*`).
RunStdString(&git.RunOpts{Dir: repoPath}); err != nil {
log.Error("MigrateRepositoryGitData(git config --add <remote> +refs/tags/*:refs/tags/*) in %v: Stdout: %s\nError: %v", repo, stdout, err)
return repo, fmt.Errorf("error in MigrateRepositoryGitData(git config --add <remote> +refs/tags/*:refs/tags/*): %w", err)
}
} else {
if err = UpdateRepoSize(ctx, repo); err != nil {
log.Error("Failed to update size for repository: %v", err)
}
if repo, err = CleanUpMigrateInfo(ctx, repo); err != nil {
return nil, err
}
}
return repo, committer.Commit()
}
// cleanUpMigrateGitConfig removes mirror info which prevents "push --all".
// This also removes possible user credentials.
func cleanUpMigrateGitConfig(ctx context.Context, repoPath string) error {
cmd := git.NewCommand(ctx, "remote", "rm", "origin")
// if the origin does not exist
_, stderr, err := cmd.RunStdString(&git.RunOpts{
Dir: repoPath,
})
if err != nil && !strings.HasPrefix(stderr, "fatal: No such remote") {
return err
}
return nil
}
// CleanUpMigrateInfo finishes migrating repository and/or wiki with things that don't need to be done for mirrors.
func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo_model.Repository, error) {
repoPath := repo.RepoPath()
if err := CreateDelegateHooks(repoPath); err != nil {
return repo, fmt.Errorf("createDelegateHooks: %w", err)
}
if repo.HasWiki() {
if err := CreateDelegateHooks(repo.WikiPath()); err != nil {
return repo, fmt.Errorf("createDelegateHooks.(wiki): %w", err)
}
}
_, _, err := git.NewCommand(ctx, "remote", "rm", "origin").RunStdString(&git.RunOpts{Dir: repoPath})
if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") {
return repo, fmt.Errorf("CleanUpMigrateInfo: %w", err)
}
if repo.HasWiki() {
if err := cleanUpMigrateGitConfig(ctx, repo.WikiPath()); err != nil {
return repo, fmt.Errorf("cleanUpMigrateGitConfig (wiki): %w", err)
}
}
return repo, UpdateRepository(ctx, repo, false)
}
// SyncRepoTags synchronizes releases table with repository tags // SyncRepoTags synchronizes releases table with repository tags
func SyncRepoTags(ctx context.Context, repoID int64) error { func SyncRepoTags(ctx context.Context, repoID int64) error {
repo, err := repo_model.GetRepositoryByID(ctx, repoID) repo, err := repo_model.GetRepositoryByID(ctx, repoID)

View File

@ -20,5 +20,6 @@ func loadAdminFrom(rootCfg ConfigProvider) {
} }
const ( const (
UserFeatureDeletion = "deletion" UserFeatureDeletion = "deletion"
UserFeatureManageGPGKeys = "manage_gpg_keys"
) )

View File

@ -1,4 +1,5 @@
// Copyright 2014 The Gogs Authors. All rights reserved. // Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package structs package structs
@ -108,3 +109,33 @@ type UpdateUserAvatarOption struct {
// image must be base64 encoded // image must be base64 encoded
Image string `json:"image" binding:"Required"` Image string `json:"image" binding:"Required"`
} }
// Badge represents a user badge
// swagger:model
type Badge struct {
ID int64 `json:"id"`
Slug string `json:"slug"`
Description string `json:"description"`
ImageURL string `json:"image_url"`
}
// UserBadge represents a user badge
// swagger:model
type UserBadge struct {
ID int64 `json:"id"`
BadgeID int64 `json:"badge_id"`
UserID int64 `json:"user_id"`
}
// UserBadgeOption options for link between users and badges
type UserBadgeOption struct {
// example: ["badge1","badge2"]
BadgeSlugs []string `json:"badge_slugs" binding:"Required"`
}
// BadgeList
// swagger:response BadgeList
type BadgeList struct {
// in:body
Body []Badge `json:"body"`
}

View File

@ -33,16 +33,16 @@ func NewFuncMap() template.FuncMap {
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// html/template related functions // html/template related functions
"dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names. "dict": dict, // it's lowercase because this name has been widely used. Our other functions should have uppercase names.
"Eval": Eval, "Eval": Eval,
"SafeHTML": SafeHTML, "SafeHTML": SafeHTML,
"HTMLFormat": HTMLFormat, "HTMLFormat": HTMLFormat,
"HTMLEscape": HTMLEscape, "HTMLEscape": HTMLEscape,
"QueryEscape": url.QueryEscape, "QueryEscape": url.QueryEscape,
"JSEscape": JSEscapeSafe, "JSEscape": JSEscapeSafe,
"Str2html": Str2html, // TODO: rename it to SanitizeHTML "SanitizeHTML": SanitizeHTML,
"URLJoin": util.URLJoin, "URLJoin": util.URLJoin,
"DotEscape": DotEscape, "DotEscape": DotEscape,
"PathEscape": url.PathEscape, "PathEscape": url.PathEscape,
"PathEscapeSegments": util.PathEscapeSegments, "PathEscapeSegments": util.PathEscapeSegments,
@ -207,8 +207,8 @@ func SafeHTML(s any) template.HTML {
panic(fmt.Sprintf("unexpected type %T", s)) panic(fmt.Sprintf("unexpected type %T", s))
} }
// Str2html sanitizes the input by pre-defined markdown rules // SanitizeHTML sanitizes the input by pre-defined markdown rules
func Str2html(s any) template.HTML { func SanitizeHTML(s any) template.HTML {
switch v := s.(type) { switch v := s.(type) {
case string: case string:
return template.HTML(markup.Sanitize(v)) return template.HTML(markup.Sanitize(v))

View File

@ -61,3 +61,8 @@ func TestJSEscapeSafe(t *testing.T) {
func TestHTMLFormat(t *testing.T) { func TestHTMLFormat(t *testing.T) {
assert.Equal(t, template.HTML("<a>&lt; < 1</a>"), HTMLFormat("<a>%s %s %d</a>", "<", template.HTML("<"), 1)) assert.Equal(t, template.HTML("<a>&lt; < 1</a>"), HTMLFormat("<a>%s %s %d</a>", "<", template.HTML("<"), 1))
} }
func TestSanitizeHTML(t *testing.T) {
assert.Equal(t, template.HTML(`<a href="/" rel="nofollow">link</a> xss <div>inline</div>`), SanitizeHTML(`<a href="/">link</a> <a href="javascript:">xss</a> <div style="dangerous">inline</div>`))
assert.Equal(t, template.HTML(`<a href="/" rel="nofollow">link</a> xss <div>inline</div>`), SanitizeHTML(template.HTML(`<a href="/">link</a> <a href="javascript:">xss</a> <div style="dangerous">inline</div>`)))
}

View File

@ -44,11 +44,17 @@ func buildSubjectBodyTemplate(stpl *texttmpl.Template, btpl *template.Template,
} }
if _, err := stpl.New(name). if _, err := stpl.New(name).
Parse(string(subjectContent)); err != nil { Parse(string(subjectContent)); err != nil {
log.Warn("Failed to parse template [%s/subject]: %v", name, err) log.Error("Failed to parse template [%s/subject]: %v", name, err)
if !setting.IsProd {
log.Fatal("Please fix the mail template error")
}
} }
if _, err := btpl.New(name). if _, err := btpl.New(name).
Parse(string(bodyContent)); err != nil { Parse(string(bodyContent)); err != nil {
log.Warn("Failed to parse template [%s/body]: %v", name, err) log.Error("Failed to parse template [%s/body]: %v", name, err)
if !setting.IsProd {
log.Fatal("Please fix the mail template error")
}
} }
} }

View File

@ -208,7 +208,7 @@ func RenderMarkdownToHtml(ctx context.Context, input string) template.HTML { //n
if err != nil { if err != nil {
log.Error("RenderString: %v", err) log.Error("RenderString: %v", err)
} }
return template.HTML(output) return output
} }
func RenderLabels(ctx context.Context, labels []*issues_model.Label, repoLink string) template.HTML { func RenderLabels(ctx context.Context, labels []*issues_model.Label, repoLink string) template.HTML {

View File

@ -4,6 +4,8 @@
package templates package templates
import ( import (
"fmt"
"html/template"
"strings" "strings"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
@ -17,6 +19,19 @@ func NewStringUtils() *StringUtils {
return &stringUtils return &stringUtils
} }
func (su *StringUtils) ToString(v any) string {
switch v := v.(type) {
case string:
return v
case template.HTML:
return string(v)
case fmt.Stringer:
return v.String()
default:
return fmt.Sprint(v)
}
}
func (su *StringUtils) HasPrefix(s, prefix string) bool { func (su *StringUtils) HasPrefix(s, prefix string) bool {
return strings.HasPrefix(s, prefix) return strings.HasPrefix(s, prefix)
} }

View File

@ -68,13 +68,13 @@ func OptionalBoolOf(b bool) OptionalBool {
return OptionalBoolFalse return OptionalBoolFalse
} }
// OptionalBoolParse get the corresponding OptionalBool of a string using strconv.ParseBool // OptionalBoolParse get the corresponding optional.Option[bool] of a string using strconv.ParseBool
func OptionalBoolParse(s string) OptionalBool { func OptionalBoolParse(s string) optional.Option[bool] {
b, e := strconv.ParseBool(s) v, e := strconv.ParseBool(s)
if e != nil { if e != nil {
return OptionalBoolNone return optional.None[bool]()
} }
return OptionalBoolOf(b) return optional.Some(v)
} }
// IsEmptyString checks if the provided string is empty // IsEmptyString checks if the provided string is empty

View File

@ -8,6 +8,8 @@ import (
"strings" "strings"
"testing" "testing"
"code.gitea.io/gitea/modules/optional"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -173,17 +175,17 @@ func Test_RandomBytes(t *testing.T) {
assert.NotEqual(t, bytes3, bytes4) assert.NotEqual(t, bytes3, bytes4)
} }
func Test_OptionalBool(t *testing.T) { func TestOptionalBoolParse(t *testing.T) {
assert.Equal(t, OptionalBoolNone, OptionalBoolParse("")) assert.Equal(t, optional.None[bool](), OptionalBoolParse(""))
assert.Equal(t, OptionalBoolNone, OptionalBoolParse("x")) assert.Equal(t, optional.None[bool](), OptionalBoolParse("x"))
assert.Equal(t, OptionalBoolFalse, OptionalBoolParse("0")) assert.Equal(t, optional.Some(false), OptionalBoolParse("0"))
assert.Equal(t, OptionalBoolFalse, OptionalBoolParse("f")) assert.Equal(t, optional.Some(false), OptionalBoolParse("f"))
assert.Equal(t, OptionalBoolFalse, OptionalBoolParse("False")) assert.Equal(t, optional.Some(false), OptionalBoolParse("False"))
assert.Equal(t, OptionalBoolTrue, OptionalBoolParse("1")) assert.Equal(t, optional.Some(true), OptionalBoolParse("1"))
assert.Equal(t, OptionalBoolTrue, OptionalBoolParse("t")) assert.Equal(t, optional.Some(true), OptionalBoolParse("t"))
assert.Equal(t, OptionalBoolTrue, OptionalBoolParse("True")) assert.Equal(t, optional.Some(true), OptionalBoolParse("True"))
} }
// Test case for any function which accepts and returns a single string. // Test case for any function which accepts and returns a single string.

View File

@ -123,6 +123,7 @@ pin=Připnout
unpin=Odepnout unpin=Odepnout
artifacts=Artefakty artifacts=Artefakty
confirm_delete_artifact=Jste si jisti, že chcete odstranit artefakt „%s“?
archived=Archivováno archived=Archivováno
@ -423,6 +424,7 @@ authorization_failed_desc=Autorizace selhala, protože jsme detekovali neplatný
sspi_auth_failed=SSPI autentizace selhala sspi_auth_failed=SSPI autentizace selhala
password_pwned=Heslo, které jste zvolili, je na <a target="_blank" rel="noopener noreferrer" href="https://haveibeenpwned.com/Passwords">seznamu odcizených hesel</a>, která byla dříve odhalena při narušení veřejných dat. Zkuste to prosím znovu s jiným heslem. password_pwned=Heslo, které jste zvolili, je na <a target="_blank" rel="noopener noreferrer" href="https://haveibeenpwned.com/Passwords">seznamu odcizených hesel</a>, která byla dříve odhalena při narušení veřejných dat. Zkuste to prosím znovu s jiným heslem.
password_pwned_err=Nelze dokončit požadavek na HaveIBeenPwned password_pwned_err=Nelze dokončit požadavek na HaveIBeenPwned
last_admin=Nelze odstranit posledního správce. Musí existovat alespoň jeden správce.
[mail] [mail]
view_it_on=Zobrazit na %s view_it_on=Zobrazit na %s
@ -588,6 +590,7 @@ org_still_own_packages=Organizace stále vlastní jeden nebo více balíčků. N
target_branch_not_exist=Cílová větev neexistuje. target_branch_not_exist=Cílová větev neexistuje.
admin_cannot_delete_self=Nemůžete se smazat, dokud jste správce. Nejdříve prosím odeberte svá administrátorská oprávnění.
[user] [user]
change_avatar=Změnit váš avatar… change_avatar=Změnit váš avatar…
@ -967,6 +970,8 @@ issue_labels_helper=Vyberte sadu štítků úkolů.
license=Licence license=Licence
license_helper=Vyberte licenční soubor. license_helper=Vyberte licenční soubor.
license_helper_desc=Licence řídí, co ostatní mohou a nemohou dělat s vaším kódem. Nejste si jisti, která je pro váš projekt správná? Podívejte se na <a target="_blank" rel="noopener noreferrer" href="%s">Zvolte licenci</a> license_helper_desc=Licence řídí, co ostatní mohou a nemohou dělat s vaším kódem. Nejste si jisti, která je pro váš projekt správná? Podívejte se na <a target="_blank" rel="noopener noreferrer" href="%s">Zvolte licenci</a>
object_format=Formát objektu
object_format_helper=Objektový formát repozitáře. Nelze později změnit. SHA1 je nejvíce kompatibilní.
readme=README readme=README
readme_helper=Vyberte šablonu souboru README. readme_helper=Vyberte šablonu souboru README.
readme_helper_desc=Toto je místo, kde můžete napsat úplný popis vašeho projektu. readme_helper_desc=Toto je místo, kde můžete napsat úplný popis vašeho projektu.
@ -1033,6 +1038,7 @@ desc.public=Veřejný
desc.template=Šablona desc.template=Šablona
desc.internal=Interní desc.internal=Interní
desc.archived=Archivováno desc.archived=Archivováno
desc.sha256=SHA256
template.items=Položky šablony template.items=Položky šablony
template.git_content=Obsah gitu (výchozí větev) template.git_content=Obsah gitu (výchozí větev)
@ -1183,6 +1189,8 @@ audio_not_supported_in_browser=Váš prohlížeč nepodporuje značku pro HTML5
stored_lfs=Uloženo pomocí Git LFS stored_lfs=Uloženo pomocí Git LFS
symbolic_link=Symbolický odkaz symbolic_link=Symbolický odkaz
executable_file=Spustitelný soubor executable_file=Spustitelný soubor
vendored=Vendorováno
generated=Generováno
commit_graph=Graf commitů commit_graph=Graf commitů
commit_graph.select=Vybrat větve commit_graph.select=Vybrat větve
commit_graph.hide_pr_refs=Skrýt požadavky na natažení commit_graph.hide_pr_refs=Skrýt požadavky na natažení
@ -1518,7 +1526,11 @@ issues.label_title=Název štítku
issues.label_description=Popis štítku issues.label_description=Popis štítku
issues.label_color=Barva štítku issues.label_color=Barva štítku
issues.label_exclusive=Exkluzivní issues.label_exclusive=Exkluzivní
issues.label_archive=Archivovat štítek
issues.label_archived_filter=Zobrazit archivované popisky issues.label_archived_filter=Zobrazit archivované popisky
issues.label_archive_tooltip=Archivované štítky jsou ve výchozím nastavení vyloučeny z návrhů při hledání podle popisku.
issues.label_exclusive_desc=Pojmenujte štítek <code>rozsah/položka</code>, aby se stal vzájemně exkluzivním s jinými štítky <code>rozsah/</code>.
issues.label_exclusive_warning=Jakékoliv protichůdné rozsahy štítků budou odstraněny při úpravě štítků u úkolů nebo u požadavku na natažení.
issues.label_count=%d štítků issues.label_count=%d štítků
issues.label_open_issues=%d otevřených úkolů issues.label_open_issues=%d otevřených úkolů
issues.label_edit=Upravit issues.label_edit=Upravit
@ -1619,6 +1631,7 @@ issues.dependency.issue_closing_blockedby=Uzavření tohoto úkolu je blokováno
issues.dependency.issue_close_blocks=Tento úkol blokuje uzavření následujících úkolů issues.dependency.issue_close_blocks=Tento úkol blokuje uzavření následujících úkolů
issues.dependency.pr_close_blocks=Tento požadavek na natažení blokuje uzavření následujících úkolů issues.dependency.pr_close_blocks=Tento požadavek na natažení blokuje uzavření následujících úkolů
issues.dependency.issue_close_blocked=Musíte zavřít všechny úkoly, které blokují tento úkol, aby jej bylo možné zavřít. issues.dependency.issue_close_blocked=Musíte zavřít všechny úkoly, které blokují tento úkol, aby jej bylo možné zavřít.
issues.dependency.issue_batch_close_blocked=Nelze uzavřít úkoly, které jste vybrali, protože úkol #%d má stále otevřené závislosti
issues.dependency.pr_close_blocked=Musíte zavřít všechny úkoly, které blokují tento požadavek na natažení, aby jej bylo možné sloučit. issues.dependency.pr_close_blocked=Musíte zavřít všechny úkoly, které blokují tento požadavek na natažení, aby jej bylo možné sloučit.
issues.dependency.blocks_short=Blokuje issues.dependency.blocks_short=Blokuje
issues.dependency.blocked_by_short=Závisí na issues.dependency.blocked_by_short=Závisí na
@ -1700,6 +1713,7 @@ pulls.select_commit_hold_shift_for_range=Vyberte commit. Podržte klávesu shift
pulls.review_only_possible_for_full_diff=Posouzení je možné pouze při zobrazení plného rozlišení pulls.review_only_possible_for_full_diff=Posouzení je možné pouze při zobrazení plného rozlišení
pulls.filter_changes_by_commit=Filtrovat podle commitu pulls.filter_changes_by_commit=Filtrovat podle commitu
pulls.nothing_to_compare=Tyto větve jsou stejné. Není potřeba vytvářet požadavek na natažení. pulls.nothing_to_compare=Tyto větve jsou stejné. Není potřeba vytvářet požadavek na natažení.
pulls.nothing_to_compare_have_tag=Vybraná větev/značka je stejná.
pulls.nothing_to_compare_and_allow_empty_pr=Tyto větve jsou stejné. Tento požadavek na natažení bude prázdný. pulls.nothing_to_compare_and_allow_empty_pr=Tyto větve jsou stejné. Tento požadavek na natažení bude prázdný.
pulls.has_pull_request=`Požadavek na natažení mezi těmito větvemi již existuje: <a href="%[1]s">%[2]s#%[3]d</a>` pulls.has_pull_request=`Požadavek na natažení mezi těmito větvemi již existuje: <a href="%[1]s">%[2]s#%[3]d</a>`
pulls.create=Vytvořit požadavek na natažení pulls.create=Vytvořit požadavek na natažení
@ -1822,6 +1836,7 @@ milestones.update_ago=Aktualizováno %s
milestones.no_due_date=Bez lhůty dokončení milestones.no_due_date=Bez lhůty dokončení
milestones.open=Otevřít milestones.open=Otevřít
milestones.close=Zavřít milestones.close=Zavřít
milestones.new_subheader=Milníky vám pomohou organizovat úkoly a sledovat jejich pokrok.
milestones.completeness=%d%% Dokončeno milestones.completeness=%d%% Dokončeno
milestones.create=Vytvořit milník milestones.create=Vytvořit milník
milestones.title=Název milestones.title=Název
@ -1955,6 +1970,7 @@ activity.git_stats_and_deletions=a
activity.git_stats_deletion_1=%d odebrání activity.git_stats_deletion_1=%d odebrání
activity.git_stats_deletion_n=%d odebrání activity.git_stats_deletion_n=%d odebrání
contributors.contribution_type.filter_label=Typ příspěvku:
contributors.contribution_type.commits=Commity contributors.contribution_type.commits=Commity
search=Vyhledat search=Vyhledat
@ -2341,6 +2357,7 @@ settings.matrix.room_id=ID místnosti
settings.matrix.message_type=Typ zprávy settings.matrix.message_type=Typ zprávy
settings.archive.button=Archivovat repozitář settings.archive.button=Archivovat repozitář
settings.archive.header=Archivovat tento repozitář settings.archive.header=Archivovat tento repozitář
settings.archive.text=Archivace repozitáře způsobí, že bude zcela určen pouze pro čtení. Bude skryt z ovládacího panelu. Nikdo (ani vy!) nebude moci vytvářet nové revize ani otevírat nové úkoly nebo žádosti o natažení.
settings.archive.success=Repozitář byl úspěšně archivován. settings.archive.success=Repozitář byl úspěšně archivován.
settings.archive.error=Nastala chyba při archivování repozitáře. Prohlédněte si záznam pro více detailů. settings.archive.error=Nastala chyba při archivování repozitáře. Prohlédněte si záznam pro více detailů.
settings.archive.error_ismirror=Nemůžete archivovat zrcadlený repozitář. settings.archive.error_ismirror=Nemůžete archivovat zrcadlený repozitář.
@ -2545,6 +2562,11 @@ error.csv.unexpected=Tento soubor nelze vykreslit, protože obsahuje neočekáva
error.csv.invalid_field_count=Soubor nelze vykreslit, protože má nesprávný počet polí na řádku %d. error.csv.invalid_field_count=Soubor nelze vykreslit, protože má nesprávný počet polí na řádku %d.
[graphs] [graphs]
component_loading=Načítání %s...
component_loading_failed=Nelze načíst %s
component_loading_info=Může to chvíli trvat…
component_failed_to_load=Došlo k neočekávané chybě.
contributors.what=příspěvky
[org] [org]
org_name_holder=Název organizace org_name_holder=Název organizace
@ -2715,6 +2737,7 @@ dashboard.delete_repo_archives.started=Spuštěna úloha smazání všech archiv
dashboard.delete_missing_repos=Smazat všechny repozitáře, které nemají Git soubory dashboard.delete_missing_repos=Smazat všechny repozitáře, které nemají Git soubory
dashboard.delete_missing_repos.started=Spuštěna úloha mazání všech repozitářů, které nemají Git soubory. dashboard.delete_missing_repos.started=Spuštěna úloha mazání všech repozitářů, které nemají Git soubory.
dashboard.delete_generated_repository_avatars=Odstranit vygenerované avatary repozitářů dashboard.delete_generated_repository_avatars=Odstranit vygenerované avatary repozitářů
dashboard.sync_repo_tags=Synchronizovat značky z git dat do databáze
dashboard.update_mirrors=Aktualizovat zrcadla dashboard.update_mirrors=Aktualizovat zrcadla
dashboard.repo_health_check=Kontrola stavu všech repozitářů dashboard.repo_health_check=Kontrola stavu všech repozitářů
dashboard.check_repo_stats=Zkontrolovat všechny statistiky repositáře dashboard.check_repo_stats=Zkontrolovat všechny statistiky repositáře
@ -2762,11 +2785,14 @@ dashboard.delete_old_actions=Odstranit všechny staré akce z databáze
dashboard.delete_old_actions.started=Začalo odstraňování všech starých akcí z databáze. dashboard.delete_old_actions.started=Začalo odstraňování všech starých akcí z databáze.
dashboard.update_checker=Kontrola aktualizací dashboard.update_checker=Kontrola aktualizací
dashboard.delete_old_system_notices=Odstranit všechna stará systémová upozornění z databáze dashboard.delete_old_system_notices=Odstranit všechna stará systémová upozornění z databáze
dashboard.gc_lfs=Úklid LFS meta objektů
dashboard.stop_zombie_tasks=Zastavit zombie úlohy dashboard.stop_zombie_tasks=Zastavit zombie úlohy
dashboard.stop_endless_tasks=Zastavit nekonečné úlohy dashboard.stop_endless_tasks=Zastavit nekonečné úlohy
dashboard.cancel_abandoned_jobs=Zrušit opuštěné úlohy dashboard.cancel_abandoned_jobs=Zrušit opuštěné úlohy
dashboard.start_schedule_tasks=Spustit naplánované úlohy dashboard.start_schedule_tasks=Spustit naplánované úlohy
dashboard.sync_branch.started=Synchronizace větví byla spuštěna dashboard.sync_branch.started=Synchronizace větví byla spuštěna
dashboard.sync_tag.started=Synchronizace značek spuštěna
dashboard.rebuild_issue_indexer=Znovu sestavit index úkolů
users.user_manage_panel=Správa uživatelských účtů users.user_manage_panel=Správa uživatelských účtů
users.new_account=Vytvořit uživatelský účet users.new_account=Vytvořit uživatelský účet
@ -3184,6 +3210,12 @@ notices.desc=Popis
notices.op=Akce notices.op=Akce
notices.delete_success=Systémové upozornění bylo smazáno. notices.delete_success=Systémové upozornění bylo smazáno.
self_check.no_problem_found=Zatím nebyl nalezen žádný problém.
self_check.database_collation_mismatch=Očekávejte, že databáze použije collation: %s
self_check.database_collation_case_insensitive=Databáze používá collation %s, což je collation nerozlišující velká a malá písmena. Ačkoli s ní Gitea může pracovat, mohou se vyskytnout vzácné případy, kdy nebude fungovat podle očekávání.
self_check.database_inconsistent_collation_columns=Databáze používá collation %s, ale tyto sloupce používají chybné collation. To může způsobit neočekávané problémy.
self_check.database_fix_mysql=Pro uživatele MySQL/MariaDB můžete použít příkaz "gitea doctor convert", který opraví problémy s collation, nebo můžete také problém vyřešit příkazem "ALTER ... COLLATE ..." SQL ručně.
self_check.database_fix_mssql=Uživatelé MSSQL mohou problém vyřešit pouze pomocí příkazu "ALTER ... COLLATE ..." SQL ručně.
[action] [action]
create_repo=vytvořil/a repozitář <a href="%s">%s</a> create_repo=vytvořil/a repozitář <a href="%s">%s</a>
@ -3371,6 +3403,7 @@ rpm.distros.suse=na distribuce založené na SUSE
rpm.install=Pro instalaci balíčku spusťte následující příkaz: rpm.install=Pro instalaci balíčku spusťte následující příkaz:
rpm.repository=Informace o repozitáři rpm.repository=Informace o repozitáři
rpm.repository.architectures=Architektury rpm.repository.architectures=Architektury
rpm.repository.multiple_groups=Tento balíček je k dispozici ve více skupinách.
rubygems.install=Pro instalaci balíčku pomocí gem spusťte následující příkaz: rubygems.install=Pro instalaci balíčku pomocí gem spusťte následující příkaz:
rubygems.install2=nebo ho přidejte do Gemfie: rubygems.install2=nebo ho přidejte do Gemfie:
rubygems.dependencies.runtime=Běhové závislosti rubygems.dependencies.runtime=Běhové závislosti
@ -3498,6 +3531,8 @@ runs.actors_no_select=Všichni aktéři
runs.status_no_select=Všechny stavy runs.status_no_select=Všechny stavy
runs.no_results=Nebyly nalezeny žádné výsledky. runs.no_results=Nebyly nalezeny žádné výsledky.
runs.no_workflows=Zatím neexistují žádné pracovní postupy. runs.no_workflows=Zatím neexistují žádné pracovní postupy.
runs.no_workflows.quick_start=Nevíte jak začít s Gitea Actions? Podívejte se na <a target="_blank" rel="noopener noreferrer" href="%s">průvodce rychlým startem</a>.
runs.no_workflows.documentation=Další informace o Gitea Actions naleznete v <a target="_blank" rel="noopener noreferrer" href="%s">dokumentaci</a>.
runs.no_runs=Pracovní postup zatím nebyl spuštěn. runs.no_runs=Pracovní postup zatím nebyl spuštěn.
runs.empty_commit_message=(prázdná zpráva commitu) runs.empty_commit_message=(prázdná zpráva commitu)
@ -3515,6 +3550,7 @@ variables.none=Zatím nejsou žádné proměnné.
variables.deletion=Odstranit proměnnou variables.deletion=Odstranit proměnnou
variables.deletion.description=Odstranění proměnné je trvalé a nelze jej vrátit zpět. Pokračovat? variables.deletion.description=Odstranění proměnné je trvalé a nelze jej vrátit zpět. Pokračovat?
variables.description=Proměnné budou předány určitým akcím a nelze je přečíst jinak. variables.description=Proměnné budou předány určitým akcím a nelze je přečíst jinak.
variables.id_not_exist=Proměnná s ID %d neexistuje.
variables.edit=Upravit proměnnou variables.edit=Upravit proměnnou
variables.deletion.failed=Nepodařilo se odstranit proměnnou. variables.deletion.failed=Nepodařilo se odstranit proměnnou.
variables.deletion.success=Proměnná byla odstraněna. variables.deletion.success=Proměnná byla odstraněna.

View File

@ -368,7 +368,7 @@ forgot_password_title= Forgot Password
forgot_password = Forgot password? forgot_password = Forgot password?
sign_up_now = Need an account? Register now. sign_up_now = Need an account? Register now.
sign_up_successful = Account was successfully created. Welcome! sign_up_successful = Account was successfully created. Welcome!
confirmation_mail_sent_prompt = A new confirmation email has been sent to <b>%s</b>. Please check your inbox within the next %s to complete the registration process. confirmation_mail_sent_prompt_ex = A new confirmation email has been sent to <b>%s</b>. Please check your inbox within the next %s to complete the registration process. If your registration email address is incorrect, you can sign in again and change it.
must_change_password = Update your password must_change_password = Update your password
allow_password_change = Require user to change password (recommended) allow_password_change = Require user to change password (recommended)
reset_password_mail_sent_prompt = A confirmation email has been sent to <b>%s</b>. Please check your inbox within the next %s to complete the account recovery process. reset_password_mail_sent_prompt = A confirmation email has been sent to <b>%s</b>. Please check your inbox within the next %s to complete the account recovery process.
@ -378,6 +378,7 @@ prohibit_login = Sign In Prohibited
prohibit_login_desc = Your account is prohibited from signing in, please contact your site administrator. prohibit_login_desc = Your account is prohibited from signing in, please contact your site administrator.
resent_limit_prompt = You have already requested an activation email recently. Please wait 3 minutes and try again. resent_limit_prompt = You have already requested an activation email recently. Please wait 3 minutes and try again.
has_unconfirmed_mail = Hi %s, you have an unconfirmed email address (<b>%s</b>). If you haven't received a confirmation email or need to resend a new one, please click on the button below. has_unconfirmed_mail = Hi %s, you have an unconfirmed email address (<b>%s</b>). If you haven't received a confirmation email or need to resend a new one, please click on the button below.
change_unconfirmed_mail_address = If your registration email address is incorrect, you can change it here and resend a new confirmation email.
resend_mail = Click here to resend your activation email resend_mail = Click here to resend your activation email
email_not_associate = The email address is not associated with any account. email_not_associate = The email address is not associated with any account.
send_reset_mail = Send Account Recovery Email send_reset_mail = Send Account Recovery Email

View File

@ -424,6 +424,7 @@ authorization_failed_desc=L'autorisation a échoué car nous avons détecté une
sspi_auth_failed=Échec de l'authentification SSPI sspi_auth_failed=Échec de l'authentification SSPI
password_pwned=Le mot de passe que vous avez choisi se trouve sur la liste <a target="_blank" rel="noopener noreferrer" href="https://haveibeenpwned.com/Passwords">des mots de passe ayant fuité</a> sur internet. Veuillez réessayer avec un mot de passe différent et considérer remplacer ce mot de passe si vous l'utilisez ailleurs. password_pwned=Le mot de passe que vous avez choisi se trouve sur la liste <a target="_blank" rel="noopener noreferrer" href="https://haveibeenpwned.com/Passwords">des mots de passe ayant fuité</a> sur internet. Veuillez réessayer avec un mot de passe différent et considérer remplacer ce mot de passe si vous l'utilisez ailleurs.
password_pwned_err=Impossible d'envoyer la demande à HaveIBeenPwned password_pwned_err=Impossible d'envoyer la demande à HaveIBeenPwned
last_admin=Vous ne pouvez pas supprimer ce compte car au moins un administrateur est requis.
[mail] [mail]
view_it_on=Voir sur %s view_it_on=Voir sur %s
@ -1714,6 +1715,7 @@ pulls.select_commit_hold_shift_for_range=Maintenir Maj et cliquer sur des révis
pulls.review_only_possible_for_full_diff=Une évaluation n'est possible que lorsque vous affichez le différentiel complet. pulls.review_only_possible_for_full_diff=Une évaluation n'est possible que lorsque vous affichez le différentiel complet.
pulls.filter_changes_by_commit=Filtrer par révision pulls.filter_changes_by_commit=Filtrer par révision
pulls.nothing_to_compare=Ces branches sont identiques. Il ny a pas besoin de créer une demande d'ajout. pulls.nothing_to_compare=Ces branches sont identiques. Il ny a pas besoin de créer une demande d'ajout.
pulls.nothing_to_compare_have_tag=Les branches/étiquettes sélectionnées sont équivalentes.
pulls.nothing_to_compare_and_allow_empty_pr=Ces branches sont égales. Cette demande d'ajout sera vide. pulls.nothing_to_compare_and_allow_empty_pr=Ces branches sont égales. Cette demande d'ajout sera vide.
pulls.has_pull_request='Il existe déjà une demande d'ajout entre ces deux branches : <a href="%[1]s">%[2]s#%[3]d</a>' pulls.has_pull_request='Il existe déjà une demande d'ajout entre ces deux branches : <a href="%[1]s">%[2]s#%[3]d</a>'
pulls.create=Créer une demande d'ajout pulls.create=Créer une demande d'ajout

View File

@ -2119,7 +2119,7 @@ settings.trust_model.collaborator.long=协作者:信任协作者的签名
settings.trust_model.collaborator.desc=此仓库中协作者的有效签名将被标记为「可信」(无论它们是否是提交者),签名只符合提交者时将标记为「不可信」,都不匹配时标记为「不匹配」。 settings.trust_model.collaborator.desc=此仓库中协作者的有效签名将被标记为「可信」(无论它们是否是提交者),签名只符合提交者时将标记为「不可信」,都不匹配时标记为「不匹配」。
settings.trust_model.committer=提交者 settings.trust_model.committer=提交者
settings.trust_model.committer.long=提交者: 信任与提交者相符的签名 (此特性类似 GitHub这会强制采用 Gitea 作为提交者和签名者) settings.trust_model.committer.long=提交者: 信任与提交者相符的签名 (此特性类似 GitHub这会强制采用 Gitea 作为提交者和签名者)
settings.trust_model.committer.desc=有效签名只有和提交者相匹配才会被标记为“受信任”,否则它们将被标记为“不匹配”。这强制 Gitea 成为签名提交的提交者,而实际提交者被加上 Co-authored-by: 和 Co-committed-by: 的标记。 默认的 Gitea 密钥必须撇撇数据库种的一名用户。 settings.trust_model.committer.desc=有效签名只有和提交者相匹配才会被标记为“受信任”,否则它们将被标记为“不匹配”。这强制 Gitea 成为签名提交的提交者,而实际提交者被加上 Co-authored-by: 和 Co-committed-by: 的标记。 默认的 Gitea 密钥必须匹配数据库中的一名用户。
settings.trust_model.collaboratorcommitter=协作者+提交者 settings.trust_model.collaboratorcommitter=协作者+提交者
settings.trust_model.collaboratorcommitter.long=协作者+提交者:信任协作者同时是提交者的签名 settings.trust_model.collaboratorcommitter.long=协作者+提交者:信任协作者同时是提交者的签名
settings.trust_model.collaboratorcommitter.desc=此仓库中协作者的有效签名在他同时是提交者时将被标记为「可信」,签名只匹配了提交者时将标记为「不可信」,都不匹配时标记为「不匹配」。这会强制 Gitea 成为签名者和提交者实际的提交者将被标记于提交消息结尾处的「Co-Authored-By:」和「Co-Committed-By:」。默认的 Gitea 签名密钥必须匹配数据库中的一个用户密钥。 settings.trust_model.collaboratorcommitter.desc=此仓库中协作者的有效签名在他同时是提交者时将被标记为「可信」,签名只匹配了提交者时将标记为「不可信」,都不匹配时标记为「不匹配」。这会强制 Gitea 成为签名者和提交者实际的提交者将被标记于提交消息结尾处的「Co-Authored-By:」和「Co-Committed-By:」。默认的 Gitea 签名密钥必须匹配数据库中的一个用户密钥。
@ -3250,7 +3250,9 @@ notices.delete_success=系统通知已被删除。
self_check.no_problem_found=尚未发现问题。 self_check.no_problem_found=尚未发现问题。
self_check.database_collation_mismatch=期望数据库使用的校验方式:%s self_check.database_collation_mismatch=期望数据库使用的校验方式:%s
self_check.database_collation_case_insensitive=数据库正在使用一个校验 %s, 这是一个不敏感的校验. 虽然Gitea可以与它合作但可能有一些罕见的情况不如预期的那样起作用。 self_check.database_collation_case_insensitive=数据库正在使用一个校验 %s, 这是一个不敏感的校验. 虽然Gitea可以与它合作但可能有一些罕见的情况不如预期的那样起作用。
self_check.database_inconsistent_collation_columns=数据库正在使用%s的排序规则但是这些列使用了不匹配的排序规则。这可能会造成一些意外问题。
self_check.database_fix_mysql=对于MySQL/MariaDB用户您可以使用“gitea doctor convert”命令来解决校验问题。 或者您也可以通过 "ALTER ... COLLATE ..." 这样的SQL 来手动解决这个问题。 self_check.database_fix_mysql=对于MySQL/MariaDB用户您可以使用“gitea doctor convert”命令来解决校验问题。 或者您也可以通过 "ALTER ... COLLATE ..." 这样的SQL 来手动解决这个问题。
self_check.database_fix_mssql=对于MSSQL用户您现在只能通过"ALTER ... COLLATE ..."SQLs手动解决这个问题。
[action] [action]
create_repo=创建了仓库 <a href="%s">%s</a> create_repo=创建了仓库 <a href="%s">%s</a>

View File

@ -71,7 +71,6 @@ import (
"code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -80,6 +79,7 @@ import (
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
web_types "code.gitea.io/gitea/modules/web/types" web_types "code.gitea.io/gitea/modules/web/types"
actions_service "code.gitea.io/gitea/services/actions" actions_service "code.gitea.io/gitea/services/actions"
"code.gitea.io/gitea/services/context"
) )
const artifactRouteBase = "/_apis/pipelines/workflows/{run_id}/artifacts" const artifactRouteBase = "/_apis/pipelines/workflows/{run_id}/artifacts"

View File

@ -14,12 +14,12 @@ import (
"strings" "strings"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
alpine_module "code.gitea.io/gitea/modules/packages/alpine" alpine_module "code.gitea.io/gitea/modules/packages/alpine"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
alpine_service "code.gitea.io/gitea/services/packages/alpine" alpine_service "code.gitea.io/gitea/services/packages/alpine"
) )

View File

@ -10,7 +10,6 @@ import (
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
@ -36,7 +35,7 @@ import (
"code.gitea.io/gitea/routers/api/packages/swift" "code.gitea.io/gitea/routers/api/packages/swift"
"code.gitea.io/gitea/routers/api/packages/vagrant" "code.gitea.io/gitea/routers/api/packages/vagrant"
"code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth"
context_service "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
) )
func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) { func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) {
@ -642,7 +641,7 @@ func CommonRoutes() *web.Route {
}) })
}) })
}, reqPackageAccess(perm.AccessModeRead)) }, reqPackageAccess(perm.AccessModeRead))
}, context_service.UserAssignmentWeb(), context.PackageAssignment()) }, context.UserAssignmentWeb(), context.PackageAssignment())
return r return r
} }
@ -812,7 +811,7 @@ func ContainerRoutes() *web.Route {
ctx.Status(http.StatusNotFound) ctx.Status(http.StatusNotFound)
}) })
}, container.ReqContainerAccess, context_service.UserAssignmentWeb(), context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead)) }, container.ReqContainerAccess, context.UserAssignmentWeb(), context.PackageAssignment(), reqPackageAccess(perm.AccessModeRead))
return r return r
} }

View File

@ -12,7 +12,6 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
cargo_module "code.gitea.io/gitea/modules/packages/cargo" cargo_module "code.gitea.io/gitea/modules/packages/cargo"
@ -20,6 +19,7 @@ import (
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
cargo_service "code.gitea.io/gitea/services/packages/cargo" cargo_service "code.gitea.io/gitea/services/packages/cargo"

View File

@ -15,12 +15,12 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
chef_module "code.gitea.io/gitea/modules/packages/chef" chef_module "code.gitea.io/gitea/modules/packages/chef"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
) )

View File

@ -14,12 +14,12 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
composer_module "code.gitea.io/gitea/modules/packages/composer" composer_module "code.gitea.io/gitea/modules/packages/composer"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"

View File

@ -15,13 +15,13 @@ import (
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
conan_model "code.gitea.io/gitea/models/packages/conan" conan_model "code.gitea.io/gitea/models/packages/conan"
"code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
conan_module "code.gitea.io/gitea/modules/packages/conan" conan_module "code.gitea.io/gitea/modules/packages/conan"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
notify_service "code.gitea.io/gitea/services/notify" notify_service "code.gitea.io/gitea/services/notify"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
) )

View File

@ -9,9 +9,9 @@ import (
conan_model "code.gitea.io/gitea/models/packages/conan" conan_model "code.gitea.io/gitea/models/packages/conan"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
conan_module "code.gitea.io/gitea/modules/packages/conan" conan_module "code.gitea.io/gitea/modules/packages/conan"
"code.gitea.io/gitea/services/context"
) )
// SearchResult contains the found recipe names // SearchResult contains the found recipe names

View File

@ -12,13 +12,13 @@ import (
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
conda_model "code.gitea.io/gitea/models/packages/conda" conda_model "code.gitea.io/gitea/models/packages/conda"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
conda_module "code.gitea.io/gitea/modules/packages/conda" conda_module "code.gitea.io/gitea/modules/packages/conda"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
"github.com/dsnet/compress/bzip2" "github.com/dsnet/compress/bzip2"

View File

@ -17,7 +17,6 @@ import (
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
container_model "code.gitea.io/gitea/models/packages/container" container_model "code.gitea.io/gitea/models/packages/container"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
@ -25,6 +24,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
container_service "code.gitea.io/gitea/services/packages/container" container_service "code.gitea.io/gitea/services/packages/container"

View File

@ -13,11 +13,11 @@ import (
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
cran_model "code.gitea.io/gitea/models/packages/cran" cran_model "code.gitea.io/gitea/models/packages/cran"
"code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
cran_module "code.gitea.io/gitea/modules/packages/cran" cran_module "code.gitea.io/gitea/modules/packages/cran"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
) )

View File

@ -13,11 +13,11 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
debian_module "code.gitea.io/gitea/modules/packages/debian" debian_module "code.gitea.io/gitea/modules/packages/debian"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
notify_service "code.gitea.io/gitea/services/notify" notify_service "code.gitea.io/gitea/services/notify"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
debian_service "code.gitea.io/gitea/services/packages/debian" debian_service "code.gitea.io/gitea/services/packages/debian"

View File

@ -10,10 +10,10 @@ import (
"strings" "strings"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
) )

View File

@ -12,11 +12,11 @@ import (
"time" "time"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
goproxy_module "code.gitea.io/gitea/modules/packages/goproxy" goproxy_module "code.gitea.io/gitea/modules/packages/goproxy"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
) )

View File

@ -13,7 +13,6 @@ import (
"time" "time"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
@ -21,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"

View File

@ -10,9 +10,9 @@ import (
"net/url" "net/url"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/context"
) )
// LogAndProcessError logs an error and calls a custom callback with the processed error message. // LogAndProcessError logs an error and calls a custom callback with the processed error message.

View File

@ -20,12 +20,12 @@ import (
"strings" "strings"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
maven_module "code.gitea.io/gitea/modules/packages/maven" maven_module "code.gitea.io/gitea/modules/packages/maven"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
) )

View File

@ -17,12 +17,12 @@ import (
access_model "code.gitea.io/gitea/models/perm/access" access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
npm_module "code.gitea.io/gitea/modules/packages/npm" npm_module "code.gitea.io/gitea/modules/packages/npm"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
"github.com/hashicorp/go-version" "github.com/hashicorp/go-version"

View File

@ -17,13 +17,13 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
nuget_model "code.gitea.io/gitea/models/packages/nuget" nuget_model "code.gitea.io/gitea/models/packages/nuget"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
nuget_module "code.gitea.io/gitea/modules/packages/nuget" nuget_module "code.gitea.io/gitea/modules/packages/nuget"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
) )

View File

@ -14,7 +14,6 @@ import (
"time" "time"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
@ -22,6 +21,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
) )

View File

@ -12,12 +12,12 @@ import (
"strings" "strings"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
pypi_module "code.gitea.io/gitea/modules/packages/pypi" pypi_module "code.gitea.io/gitea/modules/packages/pypi"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/validation" "code.gitea.io/gitea/modules/validation"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
) )

View File

@ -13,13 +13,13 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
rpm_module "code.gitea.io/gitea/modules/packages/rpm" rpm_module "code.gitea.io/gitea/modules/packages/rpm"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
notify_service "code.gitea.io/gitea/services/notify" notify_service "code.gitea.io/gitea/services/notify"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
rpm_service "code.gitea.io/gitea/services/packages/rpm" rpm_service "code.gitea.io/gitea/services/packages/rpm"

View File

@ -13,11 +13,11 @@ import (
"strings" "strings"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
rubygems_module "code.gitea.io/gitea/modules/packages/rubygems" rubygems_module "code.gitea.io/gitea/modules/packages/rubygems"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
) )

View File

@ -13,7 +13,6 @@ import (
"strings" "strings"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
@ -21,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
"github.com/hashicorp/go-version" "github.com/hashicorp/go-version"

View File

@ -12,11 +12,11 @@ import (
"strings" "strings"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
"code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages" packages_module "code.gitea.io/gitea/modules/packages"
vagrant_module "code.gitea.io/gitea/modules/packages/vagrant" vagrant_module "code.gitea.io/gitea/modules/packages/vagrant"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers/api/packages/helper" "code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages" packages_service "code.gitea.io/gitea/services/packages"
"github.com/hashicorp/go-version" "github.com/hashicorp/go-version"

View File

@ -9,9 +9,9 @@ import (
"strings" "strings"
"code.gitea.io/gitea/modules/activitypub" "code.gitea.io/gitea/modules/activitypub"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/context"
ap "github.com/go-ap/activitypub" ap "github.com/go-ap/activitypub"
"github.com/go-ap/jsonld" "github.com/go-ap/jsonld"

View File

@ -13,9 +13,9 @@ import (
"net/url" "net/url"
"code.gitea.io/gitea/modules/activitypub" "code.gitea.io/gitea/modules/activitypub"
gitea_context "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
gitea_context "code.gitea.io/gitea/services/context"
ap "github.com/go-ap/activitypub" ap "github.com/go-ap/activitypub"
"github.com/go-fed/httpsig" "github.com/go-fed/httpsig"

View File

@ -8,9 +8,9 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/context"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
) )

View File

@ -6,11 +6,11 @@ package admin
import ( import (
"net/http" "net/http"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/cron" "code.gitea.io/gitea/services/cron"
) )

View File

@ -7,9 +7,9 @@ import (
"net/http" "net/http"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
) )

View File

@ -8,12 +8,12 @@ import (
"net/http" "net/http"
"code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/context"
webhook_service "code.gitea.io/gitea/services/webhook" webhook_service "code.gitea.io/gitea/services/webhook"
) )

View File

@ -10,10 +10,10 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
) )

View File

@ -4,10 +4,10 @@
package admin package admin
import ( import (
"code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/v1/repo" "code.gitea.io/gitea/routers/api/v1/repo"
"code.gitea.io/gitea/services/context"
) )
// CreateRepo api for creating a repository // CreateRepo api for creating a repository

View File

@ -4,8 +4,8 @@
package admin package admin
import ( import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/routers/api/v1/shared" "code.gitea.io/gitea/routers/api/v1/shared"
"code.gitea.io/gitea/services/context"
) )
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization // https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization

View File

@ -15,7 +15,6 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/password" "code.gitea.io/gitea/modules/auth/password"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -25,6 +24,7 @@ import (
"code.gitea.io/gitea/routers/api/v1/user" "code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
asymkey_service "code.gitea.io/gitea/services/asymkey" asymkey_service "code.gitea.io/gitea/services/asymkey"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
"code.gitea.io/gitea/services/mailer" "code.gitea.io/gitea/services/mailer"
user_service "code.gitea.io/gitea/services/user" user_service "code.gitea.io/gitea/services/user"

View File

@ -0,0 +1,124 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package admin
import (
"net/http"
user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
)
// ListUserBadges lists all badges belonging to a user
func ListUserBadges(ctx *context.APIContext) {
// swagger:operation GET /admin/users/{username}/badges admin adminListUserBadges
// ---
// summary: List a user's badges
// produces:
// - application/json
// parameters:
// - name: username
// in: path
// description: username of user
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/BadgeList"
// "404":
// "$ref": "#/responses/notFound"
badges, maxResults, err := user_model.GetUserBadges(ctx, ctx.ContextUser)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetUserBadges", err)
return
}
ctx.SetTotalCountHeader(maxResults)
ctx.JSON(http.StatusOK, &badges)
}
// AddUserBadges add badges to a user
func AddUserBadges(ctx *context.APIContext) {
// swagger:operation POST /admin/users/{username}/badges admin adminAddUserBadges
// ---
// summary: Add a badge to a user
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: username
// in: path
// description: username of user
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/UserBadgeOption"
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
form := web.GetForm(ctx).(*api.UserBadgeOption)
badges := prepareBadgesForReplaceOrAdd(ctx, *form)
if err := user_model.AddUserBadges(ctx, ctx.ContextUser, badges); err != nil {
ctx.Error(http.StatusInternalServerError, "ReplaceUserBadges", err)
return
}
ctx.Status(http.StatusNoContent)
}
// DeleteUserBadges delete a badge from a user
func DeleteUserBadges(ctx *context.APIContext) {
// swagger:operation DELETE /admin/users/{username}/badges admin adminDeleteUserBadges
// ---
// summary: Remove a badge from a user
// produces:
// - application/json
// parameters:
// - name: username
// in: path
// description: username of user
// type: string
// required: true
// - name: body
// in: body
// schema:
// "$ref": "#/definitions/UserBadgeOption"
// responses:
// "204":
// "$ref": "#/responses/empty"
// "403":
// "$ref": "#/responses/forbidden"
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.UserBadgeOption)
badges := prepareBadgesForReplaceOrAdd(ctx, *form)
if err := user_model.RemoveUserBadges(ctx, ctx.ContextUser, badges); err != nil {
ctx.Error(http.StatusInternalServerError, "ReplaceUserBadges", err)
return
}
ctx.Status(http.StatusNoContent)
}
func prepareBadgesForReplaceOrAdd(ctx *context.APIContext, form api.UserBadgeOption) []*user_model.Badge {
badges := make([]*user_model.Badge, len(form.BadgeSlugs))
for i, badge := range form.BadgeSlugs {
badges[i] = &user_model.Badge{
Slug: badge,
}
}
return badges
}

View File

@ -79,7 +79,6 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
@ -95,7 +94,7 @@ import (
"code.gitea.io/gitea/routers/api/v1/user" "code.gitea.io/gitea/routers/api/v1/user"
"code.gitea.io/gitea/routers/common" "code.gitea.io/gitea/routers/common"
"code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth"
context_service "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
_ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation _ "code.gitea.io/gitea/routers/api/v1/swagger" // for swagger generation
@ -855,11 +854,11 @@ func Routes() *web.Route {
m.Group("/user/{username}", func() { m.Group("/user/{username}", func() {
m.Get("", activitypub.Person) m.Get("", activitypub.Person)
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox) m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
}, context_service.UserAssignmentAPI()) }, context.UserAssignmentAPI())
m.Group("/user-id/{user-id}", func() { m.Group("/user-id/{user-id}", func() {
m.Get("", activitypub.Person) m.Get("", activitypub.Person)
m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox) m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox)
}, context_service.UserIDAssignmentAPI()) }, context.UserIDAssignmentAPI())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub)) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub))
} }
@ -915,7 +914,7 @@ func Routes() *web.Route {
}, reqSelfOrAdmin(), reqBasicOrRevProxyAuth()) }, reqSelfOrAdmin(), reqBasicOrRevProxyAuth())
m.Get("/activities/feeds", user.ListUserActivityFeeds) m.Get("/activities/feeds", user.ListUserActivityFeeds)
}, context_service.UserAssignmentAPI(), individualPermsChecker) }, context.UserAssignmentAPI(), individualPermsChecker)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser)) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
// Users (requires user scope) // Users (requires user scope)
@ -933,7 +932,7 @@ func Routes() *web.Route {
m.Get("/starred", user.GetStarredRepos) m.Get("/starred", user.GetStarredRepos)
m.Get("/subscriptions", user.GetWatchedRepos) m.Get("/subscriptions", user.GetWatchedRepos)
}, context_service.UserAssignmentAPI()) }, context.UserAssignmentAPI())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken()) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
// Users (requires user scope) // Users (requires user scope)
@ -968,7 +967,7 @@ func Routes() *web.Route {
m.Get("", user.CheckMyFollowing) m.Get("", user.CheckMyFollowing)
m.Put("", user.Follow) m.Put("", user.Follow)
m.Delete("", user.Unfollow) m.Delete("", user.Unfollow)
}, context_service.UserAssignmentAPI()) }, context.UserAssignmentAPI())
}) })
// (admin:public_key scope) // (admin:public_key scope)
@ -1415,14 +1414,14 @@ func Routes() *web.Route {
m.Get("/files", reqToken(), packages.ListPackageFiles) m.Get("/files", reqToken(), packages.ListPackageFiles)
}) })
m.Get("/", reqToken(), packages.ListPackages) m.Get("/", reqToken(), packages.ListPackages)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context_service.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead)) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead))
// Organizations // Organizations
m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs) m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs)
m.Group("/users/{username}/orgs", func() { m.Group("/users/{username}/orgs", func() {
m.Get("", reqToken(), org.ListUserOrgs) m.Get("", reqToken(), org.ListUserOrgs)
m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions) m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context_service.UserAssignmentAPI()) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context.UserAssignmentAPI())
m.Post("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), bind(api.CreateOrgOption{}), org.Create) m.Post("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), bind(api.CreateOrgOption{}), org.Create)
m.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization)) m.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization))
m.Group("/orgs/{org}", func() { m.Group("/orgs/{org}", func() {
@ -1520,7 +1519,10 @@ func Routes() *web.Route {
m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg) m.Post("/orgs", bind(api.CreateOrgOption{}), admin.CreateOrg)
m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo) m.Post("/repos", bind(api.CreateRepoOption{}), admin.CreateRepo)
m.Post("/rename", bind(api.RenameUserOption{}), admin.RenameUser) m.Post("/rename", bind(api.RenameUserOption{}), admin.RenameUser)
}, context_service.UserAssignmentAPI()) m.Get("/badges", admin.ListUserBadges)
m.Post("/badges", bind(api.UserBadgeOption{}), admin.AddUserBadges)
m.Delete("/badges", bind(api.UserBadgeOption{}), admin.DeleteUserBadges)
}, context.UserAssignmentAPI())
}) })
m.Group("/emails", func() { m.Group("/emails", func() {
m.Get("", admin.GetAllEmails) m.Get("", admin.GetAllEmails)

View File

@ -6,11 +6,11 @@ package misc
import ( import (
"net/http" "net/http"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/options" "code.gitea.io/gitea/modules/options"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
) )
// Shows a list of all Gitignore templates // Shows a list of all Gitignore templates

View File

@ -6,9 +6,9 @@ package misc
import ( import (
"net/http" "net/http"
"code.gitea.io/gitea/modules/context"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
) )

View File

@ -8,12 +8,12 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/options" "code.gitea.io/gitea/modules/options"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
) )
// Returns a list of all License templates // Returns a list of all License templates

View File

@ -6,12 +6,12 @@ package misc
import ( import (
"net/http" "net/http"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/markup/markdown"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/common" "code.gitea.io/gitea/routers/common"
"code.gitea.io/gitea/services/context"
) )
// Markup render markup document to HTML // Markup render markup document to HTML

View File

@ -10,19 +10,19 @@ import (
"strings" "strings"
"testing" "testing"
"code.gitea.io/gitea/modules/contexttest"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
const ( const (
AppURL = "http://localhost:3000/" AppURL = "http://localhost:3000/"
Repo = "gogits/gogs" Repo = "gogits/gogs"
AppSubURL = AppURL + Repo + "/" FullURL = AppURL + Repo + "/"
) )
func testRenderMarkup(t *testing.T, mode, filePath, text, responseBody string, responseCode int) { func testRenderMarkup(t *testing.T, mode, filePath, text, responseBody string, responseCode int) {
@ -74,20 +74,20 @@ func TestAPI_RenderGFM(t *testing.T) {
// rendered // rendered
`<p>Wiki! Enjoy :)</p> `<p>Wiki! Enjoy :)</p>
<ul> <ul>
<li><a href="` + AppSubURL + `wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li> <li><a href="` + FullURL + `wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
<li><a href="` + AppSubURL + `wiki/Tips" rel="nofollow">Tips</a></li> <li><a href="` + FullURL + `wiki/Tips" rel="nofollow">Tips</a></li>
<li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="https://github.com/ocornut/imgui/issues/786" rel="nofollow">https://github.com/ocornut/imgui/issues/786</a></li> <li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="https://github.com/ocornut/imgui/issues/786" rel="nofollow">https://github.com/ocornut/imgui/issues/786</a></li>
</ul> </ul>
`, `,
// Guard wiki sidebar: special syntax // Guard wiki sidebar: special syntax
`[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`, `[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
// rendered // rendered
`<p><a href="` + AppSubURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p> `<p><a href="` + FullURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
`, `,
// special syntax // special syntax
`[[Name|Link]]`, `[[Name|Link]]`,
// rendered // rendered
`<p><a href="` + AppSubURL + `wiki/Link" rel="nofollow">Name</a></p> `<p><a href="` + FullURL + `wiki/Link" rel="nofollow">Name</a></p>
`, `,
// empty // empty
``, ``,
@ -111,8 +111,8 @@ Here are some links to the most important topics. You can find the full list of
<p><strong>Wine Staging</strong> on website <a href="http://wine-staging.com" rel="nofollow">wine-staging.com</a>.</p> <p><strong>Wine Staging</strong> on website <a href="http://wine-staging.com" rel="nofollow">wine-staging.com</a>.</p>
<h2 id="user-content-quick-links">Quick Links</h2> <h2 id="user-content-quick-links">Quick Links</h2>
<p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p> <p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p>
<p><a href="` + AppSubURL + `wiki/Configuration" rel="nofollow">Configuration</a> <p><a href="` + FullURL + `wiki/Configuration" rel="nofollow">Configuration</a>
<a href="` + AppSubURL + `wiki/raw/images/icon-bug.png" rel="nofollow"><img src="` + AppSubURL + `wiki/raw/images/icon-bug.png" title="icon-bug.png" alt="images/icon-bug.png"/></a></p> <a href="` + FullURL + `wiki/raw/images/icon-bug.png" rel="nofollow"><img src="` + FullURL + `wiki/raw/images/icon-bug.png" title="icon-bug.png" alt="images/icon-bug.png"/></a></p>
`, `,
} }

View File

@ -9,9 +9,9 @@ import (
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/context"
) )
const cacheKeyNodeInfoUsage = "API_NodeInfoUsage" const cacheKeyNodeInfoUsage = "API_NodeInfoUsage"

View File

@ -7,8 +7,8 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"code.gitea.io/gitea/modules/context"
asymkey_service "code.gitea.io/gitea/services/asymkey" asymkey_service "code.gitea.io/gitea/services/asymkey"
"code.gitea.io/gitea/services/context"
) )
// SigningKey returns the public key of the default signing key if it exists // SigningKey returns the public key of the default signing key if it exists

View File

@ -6,9 +6,9 @@ package misc
import ( import (
"net/http" "net/http"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/context"
) )
// Version shows the version of the Gitea server // Version shows the version of the Gitea server

View File

@ -9,9 +9,9 @@ import (
activities_model "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
"code.gitea.io/gitea/services/context"
) )
// NewAvailable check if unread notifications exist // NewAvailable check if unread notifications exist

Some files were not shown because too many files have changed in this diff Show More