mirror of
https://github.com/go-gitea/gitea
synced 2026-02-09 04:48:10 +00:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
196100a07a | ||
|
|
bc3d8bff73 | ||
|
|
7f81110461 | ||
|
|
5ed0eefc9a | ||
|
|
4b89c0f996 | ||
|
|
7cae4dfc00 | ||
|
|
28b8e0b43e | ||
|
|
23838c2c2e | ||
|
|
f9763f1366 | ||
|
|
a2314ca9c5 | ||
|
|
994ba35f11 | ||
|
|
447422fe27 | ||
|
|
9bfee5014b | ||
|
|
7128929a0d | ||
|
|
efcbaf8fa8 | ||
|
|
c997e90738 | ||
|
|
ffab076b72 | ||
|
|
117d9a117f | ||
|
|
f8c5f202b7 | ||
|
|
7213506680 | ||
|
|
1f82be6604 | ||
|
|
56bedf2bcc | ||
|
|
f7567f798d | ||
|
|
93ede4bc83 | ||
|
|
9f63d27ec4 | ||
|
|
073d8c50dd |
@@ -56,6 +56,10 @@ jobs:
|
||||
- name: upload binaries to s3
|
||||
run: |
|
||||
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
||||
- name: Install GH CLI
|
||||
uses: dev-hanz-ops/install-gh-cli-action@v0.1.0
|
||||
with:
|
||||
gh-cli-version: 2.39.1
|
||||
- name: create github release
|
||||
run: |
|
||||
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --draft --notes-from-tag dist/release/*
|
||||
|
||||
@@ -58,9 +58,13 @@ jobs:
|
||||
- name: upload binaries to s3
|
||||
run: |
|
||||
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
||||
- name: Install GH CLI
|
||||
uses: dev-hanz-ops/install-gh-cli-action@v0.1.0
|
||||
with:
|
||||
gh-cli-version: 2.39.1
|
||||
- name: create github release
|
||||
run: |
|
||||
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --draft --notes-from-tag dist/release/*
|
||||
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --notes-from-tag dist/release/*
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
docker-rootful:
|
||||
|
||||
@@ -4,6 +4,33 @@ This changelog goes through all the changes that have been made in each release
|
||||
without substantial changes to our git log; to see the highlights of what has
|
||||
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
|
||||
## [1.21.1](https://github.com/go-gitea/gitea/releases/tag/1.21.1) - 2023-11-26
|
||||
|
||||
* SECURITY
|
||||
* Fix comment permissions (#28213) (#28216)
|
||||
* BUGFIXES
|
||||
* Fix delete-orphaned-repos (#28200) (#28202)
|
||||
* Make CORS work for oauth2 handlers (#28184) (#28185)
|
||||
* Fix missing buttons (#28179) (#28181)
|
||||
* Fix no ActionTaskOutput table waring (#28149) (#28152)
|
||||
* Fix empty action run title (#28113) (#28148)
|
||||
* Use "is-loading" to avoid duplicate form submit for code comment (#28143) (#28147)
|
||||
* Fix Matrix and MSTeams nil dereference (#28089) (#28105)
|
||||
* Fix incorrect pgsql conn builder behavior (#28085) (#28098)
|
||||
* Fix system config cache expiration timing (#28072) (#28090)
|
||||
* Restricted users only see repos in orgs which their team was assigned to (#28025) (#28051)
|
||||
* API
|
||||
* Fix permissions for Token DELETE endpoint to match GET and POST (#27610) (#28099)
|
||||
* ENHANCEMENTS
|
||||
* Do not display search box when there's no packages yet (#28146) (#28159)
|
||||
* Add missing `packages.cleanup.success` (#28129) (#28132)
|
||||
* DOCS
|
||||
* Docs: Replace deprecated IS_TLS_ENABLED mailer setting in email setup (#28205) (#28208)
|
||||
* Fix the description about the default setting for action in quick start document (#28160) (#28168)
|
||||
* Add guide page to actions when there's no workflows (#28145) (#28153)
|
||||
* MISC
|
||||
* Use full width for PR comparison (#28182) (#28186)
|
||||
|
||||
## [1.21.0](https://github.com/go-gitea/gitea/releases/tag/v1.21.0) - 2023-11-14
|
||||
|
||||
* BREAKING
|
||||
|
||||
@@ -341,7 +341,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
|
||||
- `SSH_AUTHORIZED_PRINCIPALS_ALLOW`: **off** or **username, email**: \[off, username, email, anything\]: Specify the principals values that users are allowed to use as principal. When set to `anything` no checks are done on the principal string. When set to `off` authorized principal are not allowed to be set.
|
||||
- `SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE`: **false/true**: Gitea will create a authorized_principals file by default when it is not using the internal ssh server and `SSH_AUTHORIZED_PRINCIPALS_ALLOW` is not `off`.
|
||||
- `SSH_AUTHORIZED_PRINCIPALS_BACKUP`: **false/true**: Enable SSH Authorized Principals Backup when rewriting all keys, default is true if `SSH_AUTHORIZED_PRINCIPALS_ALLOW` is not `off`.
|
||||
- `SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE`: **{{.AppPath}} --config={{.CustomConf}} serv key-{{.Key.ID}}**: Set the template for the command to passed on authorized keys. Possible keys are: AppPath, AppWorkPath, CustomConf, CustomPath, Key - where Key is a `models/asymkey.PublicKey` and the others are strings which are shellquoted.
|
||||
- `SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE`: **`{{.AppPath}} --config={{.CustomConf}} serv key-{{.Key.ID}}`**: Set the template for the command to passed on authorized keys. Possible keys are: AppPath, AppWorkPath, CustomConf, CustomPath, Key - where Key is a `models/asymkey.PublicKey` and the others are strings which are shellquoted.
|
||||
- `SSH_SERVER_CIPHERS`: **chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com**: For the built-in SSH server, choose the ciphers to support for SSH connections, for system SSH this setting has no effect.
|
||||
- `SSH_SERVER_KEY_EXCHANGES`: **curve25519-sha256, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1**: For the built-in SSH server, choose the key exchange algorithms to support for SSH connections, for system SSH this setting has no effect.
|
||||
- `SSH_SERVER_MACS`: **hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1**: For the built-in SSH server, choose the MACs to support for SSH connections, for system SSH this setting has no effect
|
||||
@@ -1404,7 +1404,7 @@ Please note that using `self` is not recommended for most cases, as it could mak
|
||||
Additionally, it requires you to mirror all the actions you need to your Gitea instance, which may not be worth it.
|
||||
Therefore, please use `self` only if you understand what you are doing.
|
||||
|
||||
In earlier versions (<= 1.19), `DEFAULT_ACTIONS_URL` cound be set to any custom URLs like `https://gitea.com` or `http://your-git-server,https://gitea.com`, and the default value was `https://gitea.com`.
|
||||
In earlier versions (`<= 1.19`), `DEFAULT_ACTIONS_URL` cound be set to any custom URLs like `https://gitea.com` or `http://your-git-server,https://gitea.com`, and the default value was `https://gitea.com`.
|
||||
However, later updates removed those options, and now the only options are `github` and `self`, with the default value being `github`.
|
||||
However, if you want to use actions from other git server, you can use a complete URL in `uses` field, it's supported by Gitea (but not GitHub).
|
||||
Like `uses: https://gitea.com/actions/checkout@v3` or `uses: http://your-git-server/actions/checkout@v3`.
|
||||
|
||||
@@ -335,7 +335,7 @@ menu:
|
||||
- `SSH_AUTHORIZED_PRINCIPALS_ALLOW`: **off** 或 **username, email**:\[off, username, email, anything\]:指定允许用户用作 principal 的值。当设置为 `anything` 时,对 principal 字符串不执行任何检查。当设置为 `off` 时,不允许设置授权的 principal。
|
||||
- `SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE`: **false/true**:当 Gitea 不使用内置 SSH 服务器且 `SSH_AUTHORIZED_PRINCIPALS_ALLOW` 不为 `off` 时,默认情况下 Gitea 会创建一个 authorized_principals 文件。
|
||||
- `SSH_AUTHORIZED_PRINCIPALS_BACKUP`: **false/true**:在重写所有密钥时启用 SSH 授权 principal 备份,默认值为 true(如果 `SSH_AUTHORIZED_PRINCIPALS_ALLOW` 不为 `off`)。
|
||||
- `SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE`: **{{.AppPath}} --config={{.CustomConf}} serv key-{{.Key.ID}}**:设置用于传递授权密钥的命令模板。可能的密钥是:AppPath、AppWorkPath、CustomConf、CustomPath、Key,其中 Key 是 `models/asymkey.PublicKey`,其他是 shellquoted 字符串。
|
||||
- `SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE`: **`{{.AppPath}} --config={{.CustomConf}} serv key-{{.Key.ID}}`**:设置用于传递授权密钥的命令模板。可能的密钥是:AppPath、AppWorkPath、CustomConf、CustomPath、Key,其中 Key 是 `models/asymkey.PublicKey`,其他是 shellquoted 字符串。
|
||||
- `SSH_SERVER_CIPHERS`: **chacha20-poly1305@openssh.com, aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, aes256-gcm@openssh.com**:对于内置的 SSH 服务器,选择支持的 SSH 连接的加密方法,对于系统 SSH,此设置无效。
|
||||
- `SSH_SERVER_KEY_EXCHANGES`: **curve25519-sha256, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group14-sha256, diffie-hellman-group14-sha1**:对于内置 SSH 服务器,选择支持的 SSH 连接的密钥交换算法,对于系统 SSH,此设置无效。
|
||||
- `SSH_SERVER_MACS`: **hmac-sha2-256-etm@openssh.com, hmac-sha2-256, hmac-sha1**:对于内置 SSH 服务器,选择支持的 SSH 连接的 MAC 算法,对于系统 SSH,此设置无效。
|
||||
@@ -1344,7 +1344,7 @@ PROXY_HOSTS = *.github.com
|
||||
此外,它要求您将所有所需的操作镜像到您的 Gitea 实例,这可能不值得。
|
||||
因此,请仅在您了解自己在做什么的情况下使用 `self`。
|
||||
|
||||
在早期版本(<= 1.19)中,`DEFAULT_ACTIONS_URL` 可以设置为任何自定义 URL,例如 `https://gitea.com` 或 `http://your-git-server,https://gitea.com`,默认值为 `https://gitea.com`。
|
||||
在早期版本(`<= 1.19`)中,`DEFAULT_ACTIONS_URL` 可以设置为任何自定义 URL,例如 `https://gitea.com` 或 `http://your-git-server,https://gitea.com`,默认值为 `https://gitea.com`。
|
||||
然而,后来的更新删除了这些选项,现在唯一的选项是 `github` 和 `self`,默认值为 `github`。
|
||||
但是,如果您想要使用其他 Git 服务器中的操作,您可以在 `uses` 字段中使用完整的 URL,Gitea 支持此功能(GitHub 不支持)。
|
||||
例如 `uses: https://gitea.com/actions/checkout@v3` 或 `uses: http://your-git-server/actions/checkout@v3`。
|
||||
|
||||
@@ -61,7 +61,7 @@ Please note: authentication is only supported when the SMTP server communication
|
||||
|
||||
- STARTTLS (also known as Opportunistic TLS) via port 587. Initial connection is done over cleartext, but then be upgraded over TLS if the server supports it.
|
||||
- SMTPS connection (SMTP over TLS) via the default port 465. Connection to the server use TLS from the beginning.
|
||||
- Forced SMTPS connection with `IS_TLS_ENABLED=true`. (These are both known as Implicit TLS.)
|
||||
- Forced SMTPS connection with `PROTOCOL=smtps`. (These are both known as Implicit TLS.)
|
||||
This is due to protections imposed by the Go internal libraries against STRIPTLS attacks.
|
||||
|
||||
Note that Implicit TLS is recommended by [RFC8314](https://tools.ietf.org/html/rfc8314#section-3) since 2018.
|
||||
|
||||
@@ -55,13 +55,13 @@ PASSWD = `password`
|
||||
|
||||
要发送测试邮件以验证设置,请转到 Gitea > 站点管理 > 配置 > SMTP 邮件配置。
|
||||
|
||||
有关所有选项的完整列表,请查看[配置速查表](doc/administration/config-cheat-sheet.zh-cn.md)。
|
||||
有关所有选项的完整列表,请查看[配置速查表](doc/administration/config-cheat-sheet.md)。
|
||||
|
||||
请注意:只有在使用 TLS 或 `HOST=localhost` 加密 SMTP 服务器通信时才支持身份验证。TLS 加密可以通过以下方式进行:
|
||||
|
||||
- 通过端口 587 的 STARTTLS(也称为 Opportunistic TLS)。初始连接是明文的,但如果服务器支持,则可以升级为 TLS。
|
||||
- 通过默认端口 465 的 SMTPS 连接。连接到服务器从一开始就使用 TLS。
|
||||
- 使用 `IS_TLS_ENABLED=true` 进行强制的 SMTPS 连接。(这两种方式都被称为 Implicit TLS)
|
||||
- 使用 `PROTOCOL=smtps` 进行强制的 SMTPS 连接。(这两种方式都被称为 Implicit TLS)
|
||||
这是由于 Go 内部库对 STRIPTLS 攻击的保护机制。
|
||||
|
||||
请注意,自2018年起,[RFC8314](https://tools.ietf.org/html/rfc8314#section-3) 推荐使用 Implicit TLS。
|
||||
|
||||
@@ -39,7 +39,6 @@ If a bug fix is targeted on 1.20.1 but 1.20.1 is not released yet, you can get t
|
||||
|
||||
To migrate from Gogs to Gitea:
|
||||
|
||||
- [Gogs version 0.9.146 or less](installation/upgrade-from-gogs.md)
|
||||
- [Gogs version 0.11.46.0418](https://github.com/go-gitea/gitea/issues/4286)
|
||||
|
||||
To migrate from GitHub to Gitea, you can use Gitea's built-in migration form.
|
||||
@@ -138,9 +137,9 @@ All Gitea instances have the built-in API and there is no way to disable it comp
|
||||
You can, however, disable showing its documentation by setting `ENABLE_SWAGGER` to `false` in the `api` section of your `app.ini`.
|
||||
For more information, refer to Gitea's [API docs](development/api-usage.md).
|
||||
|
||||
You can see the latest API (for example) on <https://try.gitea.io/api/swagger>.
|
||||
You can see the latest API (for example) on https://try.gitea.io/api/swagger
|
||||
|
||||
You can also see an example of the `swagger.json` file at <https://try.gitea.io/swagger.v1.json>.
|
||||
You can also see an example of the `swagger.json` file at https://try.gitea.io/swagger.v1.json
|
||||
|
||||
## Adjusting your server for public/private use
|
||||
|
||||
|
||||
@@ -41,7 +41,6 @@ menu:
|
||||
|
||||
要从Gogs迁移到Gitea:
|
||||
|
||||
- [Gogs版本0.9.146或更低](installation/upgrade-from-gogs.md)
|
||||
- [Gogs版本0.11.46.0418](https://github.com/go-gitea/gitea/issues/4286)
|
||||
|
||||
要从GitHub迁移到Gitea,您可以使用Gitea内置的迁移表单。
|
||||
@@ -142,9 +141,9 @@ Gitea不提供内置的Pages服务器。您需要一个专用的域名来提供
|
||||
但是,您可以在app.ini的api部分将ENABLE_SWAGGER设置为false,以禁用其文档显示。
|
||||
有关更多信息,请参阅Gitea的[API文档](development/api-usage.md)。
|
||||
|
||||
您可以在上查看最新的API(例如)<https://try.gitea.io/api/swagger>。
|
||||
您可以在上查看最新的API(例如)https://try.gitea.io/api/swagger
|
||||
|
||||
您还可以在上查看`swagger.json`文件的示例 <https://try.gitea.io/swagger.v1.json>。
|
||||
您还可以在上查看`swagger.json`文件的示例 https://try.gitea.io/swagger.v1.json
|
||||
|
||||
## 调整服务器用于公共/私有使用
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ If you cannot see the settings page, please make sure that you have the right pe
|
||||
|
||||
The format of the registration token is a random string `D0gvfu2iHfUjNqCYVljVyRV14fISpJxxxxxxxxxx`.
|
||||
|
||||
A registration token can also be obtained from the gitea [command-line interface](../../administration/command-line.en-us.md#actions-generate-runner-token):
|
||||
A registration token can also be obtained from the gitea [command-line interface](../../administration/command-line.md#actions-generate-runner-token):
|
||||
|
||||
```
|
||||
gitea --config /etc/gitea/app.ini actions generate-runner-token
|
||||
|
||||
@@ -113,7 +113,7 @@ Runner级别决定了从哪里获取注册令牌。
|
||||
|
||||
注册令牌的格式是一个随机字符串 `D0gvfu2iHfUjNqCYVljVyRV14fISpJxxxxxxxxxx`。
|
||||
|
||||
注册令牌也可以通过 Gitea 的 [命令行](../../administration/command-line.en-us.md#actions-generate-runner-token) 获得:
|
||||
注册令牌也可以通过 Gitea 的 [命令行](../../administration/command-line.md#actions-generate-runner-token) 获得:
|
||||
|
||||
### 注册Runner
|
||||
|
||||
|
||||
@@ -116,8 +116,8 @@ Pre and Post steps don't have their own section in the job log user interface.
|
||||
|
||||
Previously (Pre 1.21.0), `[actions].DEFAULT_ACTIONS_URL` defaulted to `https://gitea.com`.
|
||||
We have since restricted this option to only allow two values (`github` and `self`).
|
||||
When set to `github`, the new default, Gitea will download non-fully-qualified actions from <https://github.com>.
|
||||
For example, if you use `uses: actions/checkout@v3`, it will download the checkout repository from <https://github.com/actions/checkout.git>.
|
||||
When set to `github`, the new default, Gitea will download non-fully-qualified actions from `https://github.com`.
|
||||
For example, if you use `uses: actions/checkout@v3`, it will download the checkout repository from `https://github.com/actions/checkout.git`.
|
||||
|
||||
If you want to download an action from another git hoster, you can use an absolute URL, e.g. `uses: https://gitea.com/actions/checkout@v3`.
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ First of all, you need a Gitea instance.
|
||||
You can follow the [documentation](installation/from-package.md) to set up a new instance or upgrade your existing one.
|
||||
It doesn't matter how you install or run Gitea, as long as its version is 1.19.0 or higher.
|
||||
|
||||
Actions are disabled by default, so you need to add the following to the configuration file to enable it:
|
||||
Since 1.21.0, Actions are enabled by default. If you are using versions before 1.21.0, you need to add the following to the configuration file to enable it:
|
||||
|
||||
```ini
|
||||
[actions]
|
||||
|
||||
@@ -23,7 +23,7 @@ menu:
|
||||
您可以按照[文档](installation/from-package.md) 来设置一个新实例或升级现有实例。
|
||||
无论您如何安装或运行Gitea,只要版本号是1.19.0或更高即可。
|
||||
|
||||
默认情况下,Actions是禁用的,因此您需要将以下内容添加到配置文件中以启用它:
|
||||
从1.21.0开始,默认情况下,Actions是启用的。如果您正在使用1.21.0之前的版本,您需要将以下内容添加到配置文件中以启用它:
|
||||
|
||||
```ini
|
||||
[actions]
|
||||
|
||||
@@ -198,7 +198,7 @@ administrative user.
|
||||
field is set to `mail.com`, then Gitea will expect the `user email` field
|
||||
for an authenticated GIT instance to be `gituser@mail.com`.[^2]
|
||||
|
||||
**Note**: PAM support is added via [build-time flags](installation/install-from-source.md#build),
|
||||
**Note**: PAM support is added via [build-time flags](installation/from-source.md#build),
|
||||
and the official binaries provided do not have this enabled. PAM requires that
|
||||
the necessary libpam dynamic library be available and the necessary PAM
|
||||
development headers be accessible to the compiler.
|
||||
|
||||
@@ -162,7 +162,7 @@ PAM提供了一种机制,通过对用户进行PAM认证来自动将其添加
|
||||
- PAM电子邮件域:用户认证时要附加的电子邮件后缀。例如,如果登录系统期望一个名为gituse的用户,
|
||||
并且将此字段设置为mail.com,那么Gitea在验证一个GIT实例的用户时将期望user emai字段为gituser@mail.com[^2]。
|
||||
|
||||
**Note**: PAM 支持通过[build-time flags](installation/install-from-source.md#build)添加,
|
||||
**Note**: PAM 支持通过[build-time flags](installation/from-source.md#build)添加,
|
||||
而官方提供的二进制文件通常不会默认启用此功能。PAM需要确保系统上有必要的libpam动态库,并且编译器可以访问必要的PAM开发头文件。
|
||||
|
||||
[^1]: 例如,在Debian "Bullseye"上使用标准Linux登录,可以使用`common-session-noninteractive`。这个值对于其他版本的Debian,
|
||||
|
||||
@@ -20,6 +20,10 @@ type ActionTaskOutput struct {
|
||||
OutputValue string `xorm:"MEDIUMTEXT"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(ActionTaskOutput))
|
||||
}
|
||||
|
||||
// FindTaskOutputByTaskID returns the outputs of the task.
|
||||
func FindTaskOutputByTaskID(ctx context.Context, taskID int64) ([]*ActionTaskOutput, error) {
|
||||
var outputs []*ActionTaskOutput
|
||||
|
||||
@@ -92,10 +92,9 @@ func CountUserGPGKeys(ctx context.Context, userID int64) (int64, error) {
|
||||
return db.GetEngine(ctx).Where("owner_id=? AND primary_key_id=''", userID).Count(&GPGKey{})
|
||||
}
|
||||
|
||||
// GetGPGKeyByID returns public key by given ID.
|
||||
func GetGPGKeyByID(ctx context.Context, keyID int64) (*GPGKey, error) {
|
||||
func GetGPGKeyForUserByID(ctx context.Context, ownerID, keyID int64) (*GPGKey, error) {
|
||||
key := new(GPGKey)
|
||||
has, err := db.GetEngine(ctx).ID(keyID).Get(key)
|
||||
has, err := db.GetEngine(ctx).Where("id=? AND owner_id=?", keyID, ownerID).Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
@@ -225,7 +224,7 @@ func deleteGPGKey(ctx context.Context, keyID string) (int64, error) {
|
||||
|
||||
// DeleteGPGKey deletes GPG key information in database.
|
||||
func DeleteGPGKey(ctx context.Context, doer *user_model.User, id int64) (err error) {
|
||||
key, err := GetGPGKeyByID(ctx, id)
|
||||
key, err := GetGPGKeyForUserByID(ctx, doer.ID, id)
|
||||
if err != nil {
|
||||
if IsErrGPGKeyNotExist(err) {
|
||||
return nil
|
||||
@@ -233,11 +232,6 @@ func DeleteGPGKey(ctx context.Context, doer *user_model.User, id int64) (err err
|
||||
return fmt.Errorf("GetPublicKeyByID: %w", err)
|
||||
}
|
||||
|
||||
// Check if user has access to delete this key.
|
||||
if !doer.IsAdmin && doer.ID != key.OwnerID {
|
||||
return ErrGPGKeyAccessDenied{doer.ID, key.ID}
|
||||
}
|
||||
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -66,3 +66,12 @@
|
||||
tree_path: "README.md"
|
||||
created_unix: 946684812
|
||||
invalidated: true
|
||||
|
||||
-
|
||||
id: 8
|
||||
type: 0 # comment
|
||||
poster_id: 2
|
||||
issue_id: 4 # in repo_id 2
|
||||
content: "comment in private pository"
|
||||
created_unix: 946684811
|
||||
updated_unix: 946684811
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
priority: 0
|
||||
is_closed: true
|
||||
is_pull: false
|
||||
num_comments: 0
|
||||
num_comments: 1
|
||||
created_unix: 946684830
|
||||
updated_unix: 978307200
|
||||
is_locked: false
|
||||
|
||||
@@ -1016,6 +1016,7 @@ type FindCommentsOptions struct {
|
||||
Type CommentType
|
||||
IssueIDs []int64
|
||||
Invalidated util.OptionalBool
|
||||
IsPull util.OptionalBool
|
||||
}
|
||||
|
||||
// ToConds implements FindOptions interface
|
||||
@@ -1050,6 +1051,9 @@ func (opts *FindCommentsOptions) ToConds() builder.Cond {
|
||||
if !opts.Invalidated.IsNone() {
|
||||
cond = cond.And(builder.Eq{"comment.invalidated": opts.Invalidated.IsTrue()})
|
||||
}
|
||||
if opts.IsPull != util.OptionalBoolNone {
|
||||
cond = cond.And(builder.Eq{"issue.is_pull": opts.IsPull.IsTrue()})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
@@ -1057,7 +1061,7 @@ func (opts *FindCommentsOptions) ToConds() builder.Cond {
|
||||
func FindComments(ctx context.Context, opts *FindCommentsOptions) (CommentList, error) {
|
||||
comments := make([]*Comment, 0, 10)
|
||||
sess := db.GetEngine(ctx).Where(opts.ToConds())
|
||||
if opts.RepoID > 0 {
|
||||
if opts.RepoID > 0 || opts.IsPull != util.OptionalBoolNone {
|
||||
sess.Join("INNER", "issue", "issue.id = comment.issue_id")
|
||||
}
|
||||
|
||||
|
||||
@@ -218,9 +218,9 @@ func GetIssueContentHistoryByID(dbCtx context.Context, id int64) (*ContentHistor
|
||||
}
|
||||
|
||||
// GetIssueContentHistoryAndPrev get a history and the previous non-deleted history (to compare)
|
||||
func GetIssueContentHistoryAndPrev(dbCtx context.Context, id int64) (history, prevHistory *ContentHistory, err error) {
|
||||
func GetIssueContentHistoryAndPrev(dbCtx context.Context, issueID, id int64) (history, prevHistory *ContentHistory, err error) {
|
||||
history = &ContentHistory{}
|
||||
has, err := db.GetEngine(dbCtx).ID(id).Get(history)
|
||||
has, err := db.GetEngine(dbCtx).Where("id=? AND issue_id=?", id, issueID).Get(history)
|
||||
if err != nil {
|
||||
log.Error("failed to get issue content history %v. err=%v", id, err)
|
||||
return nil, nil, err
|
||||
|
||||
@@ -58,13 +58,13 @@ func TestContentHistory(t *testing.T) {
|
||||
hasHistory2, _ := issues_model.HasIssueContentHistory(dbCtx, 10, 1)
|
||||
assert.False(t, hasHistory2)
|
||||
|
||||
h6, h6Prev, _ := issues_model.GetIssueContentHistoryAndPrev(dbCtx, 6)
|
||||
h6, h6Prev, _ := issues_model.GetIssueContentHistoryAndPrev(dbCtx, 10, 6)
|
||||
assert.EqualValues(t, 6, h6.ID)
|
||||
assert.EqualValues(t, 5, h6Prev.ID)
|
||||
|
||||
// soft-delete
|
||||
_ = issues_model.SoftDeleteIssueContentHistory(dbCtx, 5)
|
||||
h6, h6Prev, _ = issues_model.GetIssueContentHistoryAndPrev(dbCtx, 6)
|
||||
h6, h6Prev, _ = issues_model.GetIssueContentHistoryAndPrev(dbCtx, 10, 6)
|
||||
assert.EqualValues(t, 6, h6.ID)
|
||||
assert.EqualValues(t, 4, h6Prev.ID)
|
||||
|
||||
|
||||
@@ -311,6 +311,18 @@ func GetProjectByID(ctx context.Context, id int64) (*Project, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// GetProjectForRepoByID returns the projects in a repository
|
||||
func GetProjectForRepoByID(ctx context.Context, repoID, id int64) (*Project, error) {
|
||||
p := new(Project)
|
||||
has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", id, repoID).Get(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrProjectNotExist{ID: id}
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// UpdateProject updates project properties
|
||||
func UpdateProject(ctx context.Context, p *Project) error {
|
||||
if !IsCardTypeValid(p.CardType) {
|
||||
|
||||
@@ -207,6 +207,21 @@ func GetReleaseByID(ctx context.Context, id int64) (*Release, error) {
|
||||
return rel, nil
|
||||
}
|
||||
|
||||
// GetReleaseForRepoByID returns release with given ID.
|
||||
func GetReleaseForRepoByID(ctx context.Context, repoID, id int64) (*Release, error) {
|
||||
rel := new(Release)
|
||||
has, err := db.GetEngine(ctx).
|
||||
Where("id=? AND repo_id=?", id, repoID).
|
||||
Get(rel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrReleaseNotExist{id, ""}
|
||||
}
|
||||
|
||||
return rel, nil
|
||||
}
|
||||
|
||||
// FindReleasesOptions describes the conditions to Find releases
|
||||
type FindReleasesOptions struct {
|
||||
db.ListOptions
|
||||
|
||||
@@ -652,12 +652,12 @@ func AccessibleRepositoryCondition(user *user_model.User, unitType unit.Type) bu
|
||||
userOrgTeamUnitRepoCond("`repository`.id", user.ID, unitType),
|
||||
)
|
||||
}
|
||||
cond = cond.Or(
|
||||
// 4. Repositories that we directly own
|
||||
builder.Eq{"`repository`.owner_id": user.ID},
|
||||
// 4. Repositories that we directly own
|
||||
cond = cond.Or(builder.Eq{"`repository`.owner_id": user.ID})
|
||||
if !user.IsRestricted {
|
||||
// 5. Be able to see all public repos in private organizations that we are an org_user of
|
||||
userOrgPublicRepoCond(user.ID),
|
||||
)
|
||||
cond = cond.Or(userOrgPublicRepoCond(user.ID))
|
||||
}
|
||||
}
|
||||
|
||||
return cond
|
||||
|
||||
@@ -115,24 +115,26 @@ func (d *dbConfigCachedGetter) GetValue(ctx context.Context, key string) (v stri
|
||||
|
||||
func (d *dbConfigCachedGetter) GetRevision(ctx context.Context) int {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
if time.Since(d.cacheTime) < time.Second {
|
||||
return d.revision
|
||||
cachedDuration := time.Since(d.cacheTime)
|
||||
cachedRevision := d.revision
|
||||
d.mu.RUnlock()
|
||||
|
||||
if cachedDuration < time.Second {
|
||||
return cachedRevision
|
||||
}
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if GetRevision(ctx) != d.revision {
|
||||
d.mu.RUnlock()
|
||||
d.mu.Lock()
|
||||
rev, set, err := GetAllSettings(ctx)
|
||||
if err != nil {
|
||||
log.Error("Unable to get all settings: %v", err)
|
||||
} else {
|
||||
d.cacheTime = time.Now()
|
||||
d.revision = rev
|
||||
d.settings = set
|
||||
}
|
||||
d.mu.Unlock()
|
||||
d.mu.RLock()
|
||||
}
|
||||
d.cacheTime = time.Now()
|
||||
return d.revision
|
||||
}
|
||||
|
||||
|
||||
+39
-38
@@ -392,39 +392,40 @@ func CreateWebhooks(ctx context.Context, ws []*Webhook) error {
|
||||
return db.Insert(ctx, ws)
|
||||
}
|
||||
|
||||
// getWebhook uses argument bean as query condition,
|
||||
// ID must be specified and do not assign unnecessary fields.
|
||||
func getWebhook(bean *Webhook) (*Webhook, error) {
|
||||
has, err := db.GetEngine(db.DefaultContext).Get(bean)
|
||||
// GetWebhookByID returns webhook of repository by given ID.
|
||||
func GetWebhookByID(ctx context.Context, id int64) (*Webhook, error) {
|
||||
bean := new(Webhook)
|
||||
has, err := db.GetEngine(ctx).ID(id).Get(bean)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrWebhookNotExist{ID: bean.ID}
|
||||
return nil, ErrWebhookNotExist{ID: id}
|
||||
}
|
||||
return bean, nil
|
||||
}
|
||||
|
||||
// GetWebhookByID returns webhook of repository by given ID.
|
||||
func GetWebhookByID(id int64) (*Webhook, error) {
|
||||
return getWebhook(&Webhook{
|
||||
ID: id,
|
||||
})
|
||||
}
|
||||
|
||||
// GetWebhookByRepoID returns webhook of repository by given ID.
|
||||
func GetWebhookByRepoID(repoID, id int64) (*Webhook, error) {
|
||||
return getWebhook(&Webhook{
|
||||
ID: id,
|
||||
RepoID: repoID,
|
||||
})
|
||||
func GetWebhookByRepoID(ctx context.Context, repoID, id int64) (*Webhook, error) {
|
||||
webhook := new(Webhook)
|
||||
has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", id, repoID).Get(webhook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrWebhookNotExist{ID: id}
|
||||
}
|
||||
return webhook, nil
|
||||
}
|
||||
|
||||
// GetWebhookByOwnerID returns webhook of a user or organization by given ID.
|
||||
func GetWebhookByOwnerID(ownerID, id int64) (*Webhook, error) {
|
||||
return getWebhook(&Webhook{
|
||||
ID: id,
|
||||
OwnerID: ownerID,
|
||||
})
|
||||
func GetWebhookByOwnerID(ctx context.Context, ownerID, id int64) (*Webhook, error) {
|
||||
webhook := new(Webhook)
|
||||
has, err := db.GetEngine(ctx).Where("id=? AND owner_id=?", id, ownerID).Get(webhook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrWebhookNotExist{ID: id}
|
||||
}
|
||||
return webhook, nil
|
||||
}
|
||||
|
||||
// ListWebhookOptions are options to filter webhooks on ListWebhooksByOpts
|
||||
@@ -482,20 +483,20 @@ func UpdateWebhookLastStatus(w *Webhook) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// deleteWebhook uses argument bean as query condition,
|
||||
// DeleteWebhookByID uses argument bean as query condition,
|
||||
// ID must be specified and do not assign unnecessary fields.
|
||||
func deleteWebhook(bean *Webhook) (err error) {
|
||||
ctx, committer, err := db.TxContext(db.DefaultContext)
|
||||
func DeleteWebhookByID(ctx context.Context, id int64) (err error) {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
if count, err := db.DeleteByBean(ctx, bean); err != nil {
|
||||
if count, err := db.DeleteByID(ctx, id, new(Webhook)); err != nil {
|
||||
return err
|
||||
} else if count == 0 {
|
||||
return ErrWebhookNotExist{ID: bean.ID}
|
||||
} else if _, err = db.DeleteByBean(ctx, &HookTask{HookID: bean.ID}); err != nil {
|
||||
return ErrWebhookNotExist{ID: id}
|
||||
} else if _, err = db.DeleteByBean(ctx, &HookTask{HookID: id}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -503,17 +504,17 @@ func deleteWebhook(bean *Webhook) (err error) {
|
||||
}
|
||||
|
||||
// DeleteWebhookByRepoID deletes webhook of repository by given ID.
|
||||
func DeleteWebhookByRepoID(repoID, id int64) error {
|
||||
return deleteWebhook(&Webhook{
|
||||
ID: id,
|
||||
RepoID: repoID,
|
||||
})
|
||||
func DeleteWebhookByRepoID(ctx context.Context, repoID, id int64) error {
|
||||
if _, err := GetWebhookByRepoID(ctx, repoID, id); err != nil {
|
||||
return err
|
||||
}
|
||||
return DeleteWebhookByID(ctx, id)
|
||||
}
|
||||
|
||||
// DeleteWebhookByOwnerID deletes webhook of a user or organization by given ID.
|
||||
func DeleteWebhookByOwnerID(ownerID, id int64) error {
|
||||
return deleteWebhook(&Webhook{
|
||||
ID: id,
|
||||
OwnerID: ownerID,
|
||||
})
|
||||
func DeleteWebhookByOwnerID(ctx context.Context, ownerID, id int64) error {
|
||||
if _, err := GetWebhookByOwnerID(ctx, ownerID, id); err != nil {
|
||||
return err
|
||||
}
|
||||
return DeleteWebhookByID(ctx, id)
|
||||
}
|
||||
|
||||
@@ -101,22 +101,22 @@ func TestCreateWebhook(t *testing.T) {
|
||||
|
||||
func TestGetWebhookByRepoID(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
hook, err := GetWebhookByRepoID(1, 1)
|
||||
hook, err := GetWebhookByRepoID(db.DefaultContext, 1, 1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(1), hook.ID)
|
||||
|
||||
_, err = GetWebhookByRepoID(unittest.NonexistentID, unittest.NonexistentID)
|
||||
_, err = GetWebhookByRepoID(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrWebhookNotExist(err))
|
||||
}
|
||||
|
||||
func TestGetWebhookByOwnerID(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
hook, err := GetWebhookByOwnerID(3, 3)
|
||||
hook, err := GetWebhookByOwnerID(db.DefaultContext, 3, 3)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(3), hook.ID)
|
||||
|
||||
_, err = GetWebhookByOwnerID(unittest.NonexistentID, unittest.NonexistentID)
|
||||
_, err = GetWebhookByOwnerID(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrWebhookNotExist(err))
|
||||
}
|
||||
@@ -174,10 +174,10 @@ func TestUpdateWebhook(t *testing.T) {
|
||||
func TestDeleteWebhookByRepoID(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2, RepoID: 1})
|
||||
assert.NoError(t, DeleteWebhookByRepoID(1, 2))
|
||||
assert.NoError(t, DeleteWebhookByRepoID(db.DefaultContext, 1, 2))
|
||||
unittest.AssertNotExistsBean(t, &Webhook{ID: 2, RepoID: 1})
|
||||
|
||||
err := DeleteWebhookByRepoID(unittest.NonexistentID, unittest.NonexistentID)
|
||||
err := DeleteWebhookByRepoID(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrWebhookNotExist(err))
|
||||
}
|
||||
@@ -185,10 +185,10 @@ func TestDeleteWebhookByRepoID(t *testing.T) {
|
||||
func TestDeleteWebhookByOwnerID(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 3, OwnerID: 3})
|
||||
assert.NoError(t, DeleteWebhookByOwnerID(3, 3))
|
||||
assert.NoError(t, DeleteWebhookByOwnerID(db.DefaultContext, 3, 3))
|
||||
unittest.AssertNotExistsBean(t, &Webhook{ID: 3, OwnerID: 3})
|
||||
|
||||
err := DeleteWebhookByOwnerID(unittest.NonexistentID, unittest.NonexistentID)
|
||||
err := DeleteWebhookByOwnerID(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrWebhookNotExist(err))
|
||||
}
|
||||
|
||||
@@ -560,6 +560,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
ctx.Data["CanWriteCode"] = ctx.Repo.CanWrite(unit_model.TypeCode)
|
||||
ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(unit_model.TypeIssues)
|
||||
ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(unit_model.TypePullRequests)
|
||||
ctx.Data["CanWriteActions"] = ctx.Repo.CanWrite(unit_model.TypeActions)
|
||||
|
||||
canSignedUserFork, err := repo_module.CanUserForkRepo(ctx, ctx.Doer, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
|
||||
@@ -26,7 +26,7 @@ func handleDeleteOrphanedRepos(ctx context.Context, logger log.Logger, autofix b
|
||||
|
||||
// countOrphanedRepos count repository where user of owner_id do not exist
|
||||
func countOrphanedRepos(ctx context.Context) (int64, error) {
|
||||
return db.CountOrphanedObjects(ctx, "repository", "user", "repository.owner_id=user.id")
|
||||
return db.CountOrphanedObjects(ctx, "repository", "user", "repository.owner_id=`user`.id")
|
||||
}
|
||||
|
||||
// deleteOrphanedRepos delete repository where user of owner_id do not exist
|
||||
@@ -39,7 +39,7 @@ func deleteOrphanedRepos(ctx context.Context) (int64, error) {
|
||||
for {
|
||||
var ids []int64
|
||||
if err := e.Table("`repository`").
|
||||
Join("LEFT", "`user`", "repository.owner_id=user.id").
|
||||
Join("LEFT", "`user`", "repository.owner_id=`user`.id").
|
||||
Where(builder.IsNull{"`user`.id"}).
|
||||
Select("`repository`.id").Limit(batchSize).Find(&ids); err != nil {
|
||||
return deleted, err
|
||||
|
||||
@@ -13,7 +13,7 @@ var Attachment = struct {
|
||||
}{
|
||||
Storage: &Storage{},
|
||||
AllowedTypes: ".csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.patch,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip",
|
||||
MaxSize: 4,
|
||||
MaxSize: 2048,
|
||||
MaxFiles: 5,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ func DBConnStr() (string, error) {
|
||||
connStr = fmt.Sprintf("%s:%s@%s(%s)/%s%scharset=%s&parseTime=true&tls=%s",
|
||||
Database.User, Database.Passwd, connType, Database.Host, Database.Name, paramSep, Database.MysqlCharset, tls)
|
||||
case "postgres":
|
||||
connStr = getPostgreSQLConnectionString(Database.Host, Database.User, Database.Passwd, Database.Name, paramSep, Database.SSLMode)
|
||||
connStr = getPostgreSQLConnectionString(Database.Host, Database.User, Database.Passwd, Database.Name, Database.SSLMode)
|
||||
case "mssql":
|
||||
host, port := ParseMSSQLHostPort(Database.Host)
|
||||
connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, Database.Name, Database.User, Database.Passwd)
|
||||
@@ -157,7 +157,8 @@ func parsePostgreSQLHostPort(info string) (host, port string) {
|
||||
return host, port
|
||||
}
|
||||
|
||||
func getPostgreSQLConnectionString(dbHost, dbUser, dbPasswd, dbName, dbParam, dbsslMode string) (connStr string) {
|
||||
func getPostgreSQLConnectionString(dbHost, dbUser, dbPasswd, dbName, dbsslMode string) (connStr string) {
|
||||
dbName, dbParam, _ := strings.Cut(dbName, "?")
|
||||
host, port := parsePostgreSQLHostPort(dbHost)
|
||||
connURL := url.URL{
|
||||
Scheme: "postgres",
|
||||
|
||||
@@ -59,38 +59,39 @@ func Test_parsePostgreSQLHostPort(t *testing.T) {
|
||||
func Test_getPostgreSQLConnectionString(t *testing.T) {
|
||||
tests := []struct {
|
||||
Host string
|
||||
Port string
|
||||
User string
|
||||
Passwd string
|
||||
Name string
|
||||
Param string
|
||||
SSLMode string
|
||||
Output string
|
||||
}{
|
||||
{
|
||||
Host: "/tmp/pg.sock",
|
||||
Port: "4321",
|
||||
User: "testuser",
|
||||
Passwd: "space space !#$%^^%^```-=?=",
|
||||
Name: "gitea",
|
||||
Param: "",
|
||||
SSLMode: "false",
|
||||
Output: "postgres://testuser:space%20space%20%21%23$%25%5E%5E%25%5E%60%60%60-=%3F=@:5432/gitea?host=%2Ftmp%2Fpg.sock&sslmode=false",
|
||||
},
|
||||
{
|
||||
Host: "localhost",
|
||||
Port: "1234",
|
||||
User: "pgsqlusername",
|
||||
Passwd: "I love Gitea!",
|
||||
Name: "gitea",
|
||||
Param: "",
|
||||
SSLMode: "true",
|
||||
Output: "postgres://pgsqlusername:I%20love%20Gitea%21@localhost:5432/gitea?sslmode=true",
|
||||
},
|
||||
{
|
||||
Host: "localhost:1234",
|
||||
User: "user",
|
||||
Passwd: "pass",
|
||||
Name: "gitea?param=1",
|
||||
Output: "postgres://user:pass@localhost:1234/gitea?param=1&sslmode=",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
connStr := getPostgreSQLConnectionString(test.Host, test.User, test.Passwd, test.Name, test.Param, test.SSLMode)
|
||||
connStr := getPostgreSQLConnectionString(test.Host, test.User, test.Passwd, test.Name, test.SSLMode)
|
||||
assert.Equal(t, test.Output, connStr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ var (
|
||||
Enabled: true,
|
||||
TempPath: "data/tmp/uploads",
|
||||
AllowedTypes: "",
|
||||
FileMaxSize: 3,
|
||||
FileMaxSize: 50,
|
||||
MaxFiles: 5,
|
||||
},
|
||||
|
||||
|
||||
@@ -136,6 +136,10 @@ func (r *Route) Get(pattern string, h ...any) {
|
||||
r.Methods("GET", pattern, h...)
|
||||
}
|
||||
|
||||
func (r *Route) Options(pattern string, h ...any) {
|
||||
r.Methods("OPTIONS", pattern, h...)
|
||||
}
|
||||
|
||||
// GetOptions delegate get and options method
|
||||
func (r *Route) GetOptions(pattern string, h ...any) {
|
||||
r.Methods("GET,OPTIONS", pattern, h...)
|
||||
|
||||
@@ -2876,6 +2876,7 @@ packages.package_manage_panel = Package Management
|
||||
packages.total_size = Total Size: %s
|
||||
packages.unreferenced_size = Unreferenced Size: %s
|
||||
packages.cleanup = Clean up expired data
|
||||
packages.cleanup.success = Cleaned up expired data successfully
|
||||
packages.owner = Owner
|
||||
packages.creator = Creator
|
||||
packages.name = Name
|
||||
@@ -3525,7 +3526,11 @@ runs.status = Status
|
||||
runs.actors_no_select = All actors
|
||||
runs.status_no_select = All status
|
||||
runs.no_results = No results matched.
|
||||
runs.no_workflows = There are no workflows yet.
|
||||
runs.no_workflows.quick_start = Don't know how to start with Gitea Action? See <a target="_blank" rel="noopener noreferrer" href="%s">the quick start guide</a>.
|
||||
runs.no_workflows.documentation = For more information on the Gitea Action, see <a target="_blank" rel="noopener noreferrer" href="%s">the documentation</a>.
|
||||
runs.no_runs = The workflow has no runs yet.
|
||||
runs.empty_commit_message = (empty commit message)
|
||||
|
||||
workflow.disable = Disable Workflow
|
||||
workflow.disable_success = Workflow '%s' disabled successfully.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Copyright 2016 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Package v1 Gitea API.
|
||||
// Package v1 Gitea API
|
||||
//
|
||||
// This documentation describes the Gitea API.
|
||||
//
|
||||
@@ -1258,8 +1258,8 @@ func Routes() *web.Route {
|
||||
m.Group("/{username}/{reponame}", func() {
|
||||
m.Group("/issues", func() {
|
||||
m.Combo("").Get(repo.ListIssues).
|
||||
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), repo.CreateIssue)
|
||||
m.Get("/pinned", repo.ListPinnedIssues)
|
||||
Post(reqToken(), mustNotBeArchived, bind(api.CreateIssueOption{}), reqRepoReader(unit.TypeIssues), repo.CreateIssue)
|
||||
m.Get("/pinned", reqRepoReader(unit.TypeIssues), repo.ListPinnedIssues)
|
||||
m.Group("/comments", func() {
|
||||
m.Get("", repo.ListRepoIssueComments)
|
||||
m.Group("/{id}", func() {
|
||||
|
||||
@@ -301,7 +301,7 @@ func DeleteHook(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
if err := webhook.DeleteWebhookByRepoID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")); err != nil {
|
||||
if err := webhook.DeleteWebhookByRepoID(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")); err != nil {
|
||||
if webhook.IsErrWebhookNotExist(err) {
|
||||
ctx.NotFound()
|
||||
} else {
|
||||
|
||||
@@ -462,6 +462,24 @@ func ListIssues(ctx *context.APIContext) {
|
||||
isPull = util.OptionalBoolNone
|
||||
}
|
||||
|
||||
if isPull != util.OptionalBoolNone && !ctx.Repo.CanReadIssuesOrPulls(isPull.IsTrue()) {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if isPull == util.OptionalBoolNone {
|
||||
canReadIssues := ctx.Repo.CanRead(unit.TypeIssues)
|
||||
canReadPulls := ctx.Repo.CanRead(unit.TypePullRequests)
|
||||
if !canReadIssues && !canReadPulls {
|
||||
ctx.NotFound()
|
||||
return
|
||||
} else if !canReadIssues {
|
||||
isPull = util.OptionalBoolTrue
|
||||
} else if !canReadPulls {
|
||||
isPull = util.OptionalBoolFalse
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: we should be more efficient here
|
||||
createdByID := getUserIDForFilter(ctx, "created_by")
|
||||
if ctx.Written() {
|
||||
@@ -593,6 +611,10 @@ func GetIssue(ctx *context.APIContext) {
|
||||
}
|
||||
return
|
||||
}
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, convert.ToAPIIssue(ctx, issue))
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,11 @@ import (
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
access_model "code.gitea.io/gitea/models/perm/access"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/context"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
@@ -71,6 +73,11 @@ func ListIssueComments(ctx *context.APIContext) {
|
||||
ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err)
|
||||
return
|
||||
}
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
issue.Repo = ctx.Repo.Repository
|
||||
|
||||
opts := &issues_model.FindCommentsOptions{
|
||||
@@ -271,12 +278,27 @@ func ListRepoIssueComments(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
var isPull util.OptionalBool
|
||||
canReadIssue := ctx.Repo.CanRead(unit.TypeIssues)
|
||||
canReadPull := ctx.Repo.CanRead(unit.TypePullRequests)
|
||||
if canReadIssue && canReadPull {
|
||||
isPull = util.OptionalBoolNone
|
||||
} else if canReadIssue {
|
||||
isPull = util.OptionalBoolFalse
|
||||
} else if canReadPull {
|
||||
isPull = util.OptionalBoolTrue
|
||||
} else {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
opts := &issues_model.FindCommentsOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
Type: issues_model.CommentTypeComment,
|
||||
Since: since,
|
||||
Before: before,
|
||||
IsPull: isPull,
|
||||
}
|
||||
|
||||
comments, err := issues_model.FindComments(ctx, opts)
|
||||
@@ -365,6 +387,11 @@ func CreateIssueComment(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(issue.IsPull) {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.Doer.IsAdmin {
|
||||
ctx.Error(http.StatusForbidden, "CreateIssueComment", errors.New(ctx.Tr("repo.issues.comment_on_locked")))
|
||||
return
|
||||
@@ -434,6 +461,11 @@ func GetIssueComment(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if comment.Type != issues_model.CommentTypeComment {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
return
|
||||
@@ -552,7 +584,17 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
|
||||
if err := comment.LoadIssue(ctx); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
|
||||
return
|
||||
}
|
||||
|
||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
||||
ctx.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
@@ -655,7 +697,17 @@ func deleteIssueComment(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.IsAdmin()) {
|
||||
if err := comment.LoadIssue(ctx); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "LoadIssue", err)
|
||||
return
|
||||
}
|
||||
|
||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
||||
ctx.Status(http.StatusForbidden)
|
||||
return
|
||||
} else if comment.Type != issues_model.CommentTypeComment {
|
||||
|
||||
@@ -325,6 +325,10 @@ func getIssueCommentSafe(ctx *context.APIContext) *issues_model.Comment {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
||||
return nil
|
||||
}
|
||||
|
||||
comment.Issue.Repo = ctx.Repo.Repository
|
||||
|
||||
return comment
|
||||
|
||||
@@ -61,6 +61,12 @@ func GetIssueCommentReactions(ctx *context.APIContext) {
|
||||
|
||||
if err := comment.LoadIssue(ctx); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "comment.LoadIssue", err)
|
||||
return
|
||||
}
|
||||
|
||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
||||
@@ -190,9 +196,19 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp
|
||||
return
|
||||
}
|
||||
|
||||
err = comment.LoadIssue(ctx)
|
||||
if err != nil {
|
||||
if err = comment.LoadIssue(ctx); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "comment.LoadIssue() failed", err)
|
||||
return
|
||||
}
|
||||
|
||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if comment.Issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull) {
|
||||
|
||||
@@ -159,6 +159,12 @@ func GetDeployKey(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
// this check make it more consistent
|
||||
if key.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
if err = key.GetContent(); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetContent", err)
|
||||
return
|
||||
|
||||
@@ -49,13 +49,12 @@ func GetRelease(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
id := ctx.ParamsInt64(":id")
|
||||
release, err := repo_model.GetReleaseByID(ctx, id)
|
||||
release, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
|
||||
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
|
||||
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
|
||||
return
|
||||
}
|
||||
if err != nil && repo_model.IsErrReleaseNotExist(err) ||
|
||||
release.IsTag || release.RepoID != ctx.Repo.Repository.ID {
|
||||
if err != nil && repo_model.IsErrReleaseNotExist(err) || release.IsTag {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
@@ -315,13 +314,12 @@ func EditRelease(ctx *context.APIContext) {
|
||||
|
||||
form := web.GetForm(ctx).(*api.EditReleaseOption)
|
||||
id := ctx.ParamsInt64(":id")
|
||||
rel, err := repo_model.GetReleaseByID(ctx, id)
|
||||
rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
|
||||
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
|
||||
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
|
||||
return
|
||||
}
|
||||
if err != nil && repo_model.IsErrReleaseNotExist(err) ||
|
||||
rel.IsTag || rel.RepoID != ctx.Repo.Repository.ID {
|
||||
if err != nil && repo_model.IsErrReleaseNotExist(err) || rel.IsTag {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
@@ -393,17 +391,16 @@ func DeleteRelease(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/empty"
|
||||
|
||||
id := ctx.ParamsInt64(":id")
|
||||
rel, err := repo_model.GetReleaseByID(ctx, id)
|
||||
rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, id)
|
||||
if err != nil && !repo_model.IsErrReleaseNotExist(err) {
|
||||
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetReleaseForRepoByID", err)
|
||||
return
|
||||
}
|
||||
if err != nil && repo_model.IsErrReleaseNotExist(err) ||
|
||||
rel.IsTag || rel.RepoID != ctx.Repo.Repository.ID {
|
||||
if err != nil && repo_model.IsErrReleaseNotExist(err) || rel.IsTag {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
if err := release_service.DeleteReleaseByID(ctx, id, ctx.Doer, false); err != nil {
|
||||
if err := release_service.DeleteReleaseByID(ctx, ctx.Repo.Repository, rel, ctx.Doer, false); err != nil {
|
||||
if models.IsErrProtectedTagName(err) {
|
||||
ctx.Error(http.StatusMethodNotAllowed, "delTag", "user not allowed to delete protected tag")
|
||||
return
|
||||
|
||||
@@ -17,6 +17,23 @@ import (
|
||||
"code.gitea.io/gitea/services/convert"
|
||||
)
|
||||
|
||||
func checkReleaseMatchRepo(ctx *context.APIContext, releaseID int64) bool {
|
||||
release, err := repo_model.GetReleaseByID(ctx, releaseID)
|
||||
if err != nil {
|
||||
if repo_model.IsErrReleaseNotExist(err) {
|
||||
ctx.NotFound()
|
||||
return false
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
|
||||
return false
|
||||
}
|
||||
if release.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.NotFound()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// GetReleaseAttachment gets a single attachment of the release
|
||||
func GetReleaseAttachment(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/releases/{id}/assets/{attachment_id} repository repoGetReleaseAttachment
|
||||
@@ -54,6 +71,10 @@ func GetReleaseAttachment(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
releaseID := ctx.ParamsInt64(":id")
|
||||
if !checkReleaseMatchRepo(ctx, releaseID) {
|
||||
return
|
||||
}
|
||||
|
||||
attachID := ctx.ParamsInt64(":attachment_id")
|
||||
attach, err := repo_model.GetAttachmentByID(ctx, attachID)
|
||||
if err != nil {
|
||||
@@ -176,13 +197,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
|
||||
|
||||
// Check if release exists an load release
|
||||
releaseID := ctx.ParamsInt64(":id")
|
||||
release, err := repo_model.GetReleaseByID(ctx, releaseID)
|
||||
if err != nil {
|
||||
if repo_model.IsErrReleaseNotExist(err) {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
ctx.Error(http.StatusInternalServerError, "GetReleaseByID", err)
|
||||
if !checkReleaseMatchRepo(ctx, releaseID) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -203,7 +218,7 @@ func CreateReleaseAttachment(ctx *context.APIContext) {
|
||||
attach, err := attachment.UploadAttachment(file, setting.Repository.Release.AllowedTypes, header.Size, &repo_model.Attachment{
|
||||
Name: filename,
|
||||
UploaderID: ctx.Doer.ID,
|
||||
RepoID: release.RepoID,
|
||||
RepoID: ctx.Repo.Repository.ID,
|
||||
ReleaseID: releaseID,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -264,6 +279,10 @@ func EditReleaseAttachment(ctx *context.APIContext) {
|
||||
|
||||
// Check if release exists an load release
|
||||
releaseID := ctx.ParamsInt64(":id")
|
||||
if !checkReleaseMatchRepo(ctx, releaseID) {
|
||||
return
|
||||
}
|
||||
|
||||
attachID := ctx.ParamsInt64(":attachment_id")
|
||||
attach, err := repo_model.GetAttachmentByID(ctx, attachID)
|
||||
if err != nil {
|
||||
@@ -328,6 +347,10 @@ func DeleteReleaseAttachment(ctx *context.APIContext) {
|
||||
|
||||
// Check if release exists an load release
|
||||
releaseID := ctx.ParamsInt64(":id")
|
||||
if !checkReleaseMatchRepo(ctx, releaseID) {
|
||||
return
|
||||
}
|
||||
|
||||
attachID := ctx.ParamsInt64(":attachment_id")
|
||||
attach, err := repo_model.GetAttachmentByID(ctx, attachID)
|
||||
if err != nil {
|
||||
|
||||
@@ -112,7 +112,7 @@ func DeleteReleaseByTag(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = releaseservice.DeleteReleaseByID(ctx, release.ID, ctx.Doer, false); err != nil {
|
||||
if err = releaseservice.DeleteReleaseByID(ctx, ctx.Repo.Repository, release, ctx.Doer, false); err != nil {
|
||||
if models.IsErrProtectedTagName(err) {
|
||||
ctx.Error(http.StatusMethodNotAllowed, "delTag", "user not allowed to delete protected tag")
|
||||
return
|
||||
|
||||
@@ -268,7 +268,7 @@ func DeleteTag(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if err = releaseservice.DeleteReleaseByID(ctx, tag.ID, ctx.Doer, true); err != nil {
|
||||
if err = releaseservice.DeleteReleaseByID(ctx, ctx.Repo.Repository, tag, ctx.Doer, true); err != nil {
|
||||
if models.IsErrProtectedTagName(err) {
|
||||
ctx.Error(http.StatusMethodNotAllowed, "delTag", "user not allowed to delete protected tag")
|
||||
return
|
||||
|
||||
@@ -193,7 +193,7 @@ func DeleteAccessToken(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := auth_model.DeleteAccessTokenByID(ctx, tokenID, ctx.Doer.ID); err != nil {
|
||||
if err := auth_model.DeleteAccessTokenByID(ctx, tokenID, ctx.ContextUser.ID); err != nil {
|
||||
if auth_model.IsErrAccessTokenNotExist(err) {
|
||||
ctx.NotFound()
|
||||
} else {
|
||||
@@ -343,6 +343,10 @@ func GetOauth2Application(ctx *context.APIContext) {
|
||||
}
|
||||
return
|
||||
}
|
||||
if app.UID != ctx.Doer.ID {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
app.ClientSecret = ""
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ func GetGPGKey(ctx *context.APIContext) {
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
key, err := asymkey_model.GetGPGKeyByID(ctx, ctx.ParamsInt64(":id"))
|
||||
key, err := asymkey_model.GetGPGKeyForUserByID(ctx, ctx.Doer.ID, ctx.ParamsInt64(":id"))
|
||||
if err != nil {
|
||||
if asymkey_model.IsErrGPGKeyNotExist(err) {
|
||||
ctx.NotFound()
|
||||
|
||||
@@ -62,6 +62,11 @@ func GetHook(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Doer.IsAdmin && hook.OwnerID != ctx.Doer.ID {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
|
||||
apiHook, err := webhook_service.ToHook(ctx.Doer.HomeLink(), hook)
|
||||
if err != nil {
|
||||
ctx.InternalServerError(err)
|
||||
|
||||
@@ -53,7 +53,7 @@ func ListOwnerHooks(ctx *context.APIContext, owner *user_model.User) {
|
||||
|
||||
// GetOwnerHook gets an user or organization webhook. Errors are written to ctx.
|
||||
func GetOwnerHook(ctx *context.APIContext, ownerID, hookID int64) (*webhook.Webhook, error) {
|
||||
w, err := webhook.GetWebhookByOwnerID(ownerID, hookID)
|
||||
w, err := webhook.GetWebhookByOwnerID(ctx, ownerID, hookID)
|
||||
if err != nil {
|
||||
if webhook.IsErrWebhookNotExist(err) {
|
||||
ctx.NotFound()
|
||||
@@ -68,7 +68,7 @@ func GetOwnerHook(ctx *context.APIContext, ownerID, hookID int64) (*webhook.Webh
|
||||
// GetRepoHook get a repo's webhook. If there is an error, write to `ctx`
|
||||
// accordingly and return the error
|
||||
func GetRepoHook(ctx *context.APIContext, repoID, hookID int64) (*webhook.Webhook, error) {
|
||||
w, err := webhook.GetWebhookByRepoID(repoID, hookID)
|
||||
w, err := webhook.GetWebhookByRepoID(ctx, repoID, hookID)
|
||||
if err != nil {
|
||||
if webhook.IsErrWebhookNotExist(err) {
|
||||
ctx.NotFound()
|
||||
@@ -401,7 +401,7 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
|
||||
|
||||
// DeleteOwnerHook deletes the hook owned by the owner.
|
||||
func DeleteOwnerHook(ctx *context.APIContext, owner *user_model.User, hookID int64) {
|
||||
if err := webhook.DeleteWebhookByOwnerID(owner.ID, hookID); err != nil {
|
||||
if err := webhook.DeleteWebhookByOwnerID(ctx, owner.ID, hookID); err != nil {
|
||||
if webhook.IsErrWebhookNotExist(err) {
|
||||
ctx.NotFound()
|
||||
} else {
|
||||
|
||||
@@ -33,6 +33,10 @@ func DummyOK(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func DummyBadRequest(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func RobotsTxt(w http.ResponseWriter, req *http.Request) {
|
||||
robotsTxt := util.FilePathJoinAbs(setting.CustomPath, "public/robots.txt")
|
||||
if ok, _ := util.IsExist(robotsTxt); !ok {
|
||||
|
||||
@@ -233,7 +233,7 @@ func Webhooks(ctx *context.Context) {
|
||||
|
||||
// DeleteWebhook response for delete webhook
|
||||
func DeleteWebhook(ctx *context.Context) {
|
||||
if err := webhook.DeleteWebhookByOwnerID(ctx.Org.Organization.ID, ctx.FormInt64("id")); err != nil {
|
||||
if err := webhook.DeleteWebhookByOwnerID(ctx, ctx.Org.Organization.ID, ctx.FormInt64("id")); err != nil {
|
||||
ctx.Flash.Error("DeleteWebhookByOwnerID: " + err.Error())
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
|
||||
|
||||
@@ -287,7 +287,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
|
||||
Operation: operation,
|
||||
FromTreePath: ctx.Repo.TreePath,
|
||||
TreePath: form.TreePath,
|
||||
ContentReader: strings.NewReader(form.Content),
|
||||
ContentReader: strings.NewReader(strings.ReplaceAll(form.Content, "\r", "")),
|
||||
},
|
||||
},
|
||||
Signoff: form.Signoff,
|
||||
|
||||
@@ -3091,6 +3091,11 @@ func UpdateCommentContent(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
||||
ctx.Error(http.StatusForbidden)
|
||||
return
|
||||
@@ -3157,6 +3162,11 @@ func DeleteComment(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanWriteIssuesOrPulls(comment.Issue.IsPull)) {
|
||||
ctx.Error(http.StatusForbidden)
|
||||
return
|
||||
@@ -3283,6 +3293,11 @@ func ChangeCommentReaction(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.IsSigned || (ctx.Doer.ID != comment.PosterID && !ctx.Repo.CanReadIssuesOrPulls(comment.Issue.IsPull)) {
|
||||
if log.IsTrace() {
|
||||
if ctx.IsSigned {
|
||||
@@ -3426,6 +3441,21 @@ func GetCommentAttachments(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := comment.LoadIssue(ctx); err != nil {
|
||||
ctx.NotFoundOrServerError("LoadIssue", issues_model.IsErrIssueNotExist, err)
|
||||
return
|
||||
}
|
||||
|
||||
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
|
||||
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Repo.Permission.CanReadIssuesOrPulls(comment.Issue.IsPull) {
|
||||
ctx.NotFound("CanReadIssuesOrPulls", issues_model.ErrCommentNotExist{})
|
||||
return
|
||||
}
|
||||
|
||||
if !comment.Type.HasAttachmentSupport() {
|
||||
ctx.ServerError("GetCommentAttachments", fmt.Errorf("comment type %v does not support attachments", comment.Type))
|
||||
return
|
||||
|
||||
@@ -122,7 +122,7 @@ func GetContentHistoryDetail(ctx *context.Context) {
|
||||
}
|
||||
|
||||
historyID := ctx.FormInt64("history_id")
|
||||
history, prevHistory, err := issues_model.GetIssueContentHistoryAndPrev(ctx, historyID)
|
||||
history, prevHistory, err := issues_model.GetIssueContentHistoryAndPrev(ctx, issue.ID, historyID)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusNotFound, map[string]any{
|
||||
"message": "Can not find the content history",
|
||||
|
||||
@@ -464,7 +464,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
project, err := project_model.GetProjectByID(ctx, ctx.ParamsInt64(":id"))
|
||||
project, err := project_model.GetProjectForRepoByID(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
|
||||
if err != nil {
|
||||
if project_model.IsErrProjectNotExist(err) {
|
||||
ctx.NotFound("", nil)
|
||||
|
||||
@@ -613,7 +613,27 @@ func DeleteTag(ctx *context.Context) {
|
||||
}
|
||||
|
||||
func deleteReleaseOrTag(ctx *context.Context, isDelTag bool) {
|
||||
if err := releaseservice.DeleteReleaseByID(ctx, ctx.FormInt64("id"), ctx.Doer, isDelTag); err != nil {
|
||||
redirect := func() {
|
||||
if isDelTag {
|
||||
ctx.JSONRedirect(ctx.Repo.RepoLink + "/tags")
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSONRedirect(ctx.Repo.RepoLink + "/releases")
|
||||
}
|
||||
|
||||
rel, err := repo_model.GetReleaseForRepoByID(ctx, ctx.Repo.Repository.ID, ctx.FormInt64("id"))
|
||||
if err != nil {
|
||||
if repo_model.IsErrReleaseNotExist(err) {
|
||||
ctx.NotFound("GetReleaseForRepoByID", err)
|
||||
} else {
|
||||
ctx.Flash.Error("DeleteReleaseByID: " + err.Error())
|
||||
redirect()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := releaseservice.DeleteReleaseByID(ctx, ctx.Repo.Repository, rel, ctx.Doer, isDelTag); err != nil {
|
||||
if models.IsErrProtectedTagName(err) {
|
||||
ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected"))
|
||||
} else {
|
||||
@@ -627,10 +647,5 @@ func deleteReleaseOrTag(ctx *context.Context, isDelTag bool) {
|
||||
}
|
||||
}
|
||||
|
||||
if isDelTag {
|
||||
ctx.JSONRedirect(ctx.Repo.RepoLink + "/tags")
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSONRedirect(ctx.Repo.RepoLink + "/releases")
|
||||
redirect()
|
||||
}
|
||||
|
||||
@@ -589,9 +589,9 @@ func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) {
|
||||
|
||||
var w *webhook.Webhook
|
||||
if orCtx.RepoID > 0 {
|
||||
w, err = webhook.GetWebhookByRepoID(orCtx.RepoID, ctx.ParamsInt64(":id"))
|
||||
w, err = webhook.GetWebhookByRepoID(ctx, orCtx.RepoID, ctx.ParamsInt64(":id"))
|
||||
} else if orCtx.OwnerID > 0 {
|
||||
w, err = webhook.GetWebhookByOwnerID(orCtx.OwnerID, ctx.ParamsInt64(":id"))
|
||||
w, err = webhook.GetWebhookByOwnerID(ctx, orCtx.OwnerID, ctx.ParamsInt64(":id"))
|
||||
} else if orCtx.IsAdmin {
|
||||
w, err = webhook.GetSystemOrDefaultWebhook(ctx, ctx.ParamsInt64(":id"))
|
||||
}
|
||||
@@ -643,7 +643,7 @@ func WebHooksEdit(ctx *context.Context) {
|
||||
// TestWebhook test if web hook is work fine
|
||||
func TestWebhook(ctx *context.Context) {
|
||||
hookID := ctx.ParamsInt64(":id")
|
||||
w, err := webhook.GetWebhookByRepoID(ctx.Repo.Repository.ID, hookID)
|
||||
w, err := webhook.GetWebhookByRepoID(ctx, ctx.Repo.Repository.ID, hookID)
|
||||
if err != nil {
|
||||
ctx.Flash.Error("GetWebhookByRepoID: " + err.Error())
|
||||
ctx.Status(http.StatusInternalServerError)
|
||||
@@ -724,7 +724,7 @@ func ReplayWebhook(ctx *context.Context) {
|
||||
|
||||
// DeleteWebhook delete a webhook
|
||||
func DeleteWebhook(ctx *context.Context) {
|
||||
if err := webhook.DeleteWebhookByRepoID(ctx.Repo.Repository.ID, ctx.FormInt64("id")); err != nil {
|
||||
if err := webhook.DeleteWebhookByRepoID(ctx, ctx.Repo.Repository.ID, ctx.FormInt64("id")); err != nil {
|
||||
ctx.Flash.Error("DeleteWebhookByRepoID: " + err.Error())
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
|
||||
|
||||
@@ -36,7 +36,7 @@ func Webhooks(ctx *context.Context) {
|
||||
|
||||
// DeleteWebhook response for delete webhook
|
||||
func DeleteWebhook(ctx *context.Context) {
|
||||
if err := webhook.DeleteWebhookByOwnerID(ctx.Doer.ID, ctx.FormInt64("id")); err != nil {
|
||||
if err := webhook.DeleteWebhookByOwnerID(ctx, ctx.Doer.ID, ctx.FormInt64("id")); err != nil {
|
||||
ctx.Flash.Error("DeleteWebhookByOwnerID: " + err.Error())
|
||||
} else {
|
||||
ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
|
||||
|
||||
@@ -532,8 +532,10 @@ func registerRoutes(m *web.Route) {
|
||||
m.Post("/authorize", web.Bind(forms.AuthorizationForm{}), auth.AuthorizeOAuth)
|
||||
}, ignSignInAndCsrf, reqSignIn)
|
||||
m.Get("/login/oauth/userinfo", ignSignInAndCsrf, auth.InfoOAuth)
|
||||
m.Options("/login/oauth/access_token", CorsHandler(), misc.DummyBadRequest)
|
||||
m.Post("/login/oauth/access_token", CorsHandler(), web.Bind(forms.AccessTokenForm{}), ignSignInAndCsrf, auth.AccessTokenOAuth)
|
||||
m.Get("/login/oauth/keys", ignSignInAndCsrf, auth.OIDCKeys)
|
||||
m.Options("/login/oauth/introspect", CorsHandler(), misc.DummyBadRequest)
|
||||
m.Post("/login/oauth/introspect", CorsHandler(), web.Bind(forms.IntrospectTokenForm{}), ignSignInAndCsrf, auth.IntrospectOAuth)
|
||||
|
||||
m.Group("/user/settings", func() {
|
||||
|
||||
@@ -291,17 +291,7 @@ func UpdateRelease(ctx context.Context, doer *user_model.User, gitRepo *git.Repo
|
||||
}
|
||||
|
||||
// DeleteReleaseByID deletes a release and corresponding Git tag by given ID.
|
||||
func DeleteReleaseByID(ctx context.Context, id int64, doer *user_model.User, delTag bool) error {
|
||||
rel, err := repo_model.GetReleaseByID(ctx, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetReleaseByID: %w", err)
|
||||
}
|
||||
|
||||
repo, err := repo_model.GetRepositoryByID(ctx, rel.RepoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetRepositoryByID: %w", err)
|
||||
}
|
||||
|
||||
func DeleteReleaseByID(ctx context.Context, repo *repo_model.Repository, rel *repo_model.Release, doer *user_model.User, delTag bool) error {
|
||||
if delTag {
|
||||
protectedTags, err := git_model.GetProtectedTags(ctx, rel.RepoID)
|
||||
if err != nil {
|
||||
@@ -334,19 +324,19 @@ func DeleteReleaseByID(ctx context.Context, id int64, doer *user_model.User, del
|
||||
}, repository.NewPushCommits())
|
||||
notify_service.DeleteRef(ctx, doer, repo, refName)
|
||||
|
||||
if err := repo_model.DeleteReleaseByID(ctx, id); err != nil {
|
||||
if err := repo_model.DeleteReleaseByID(ctx, rel.ID); err != nil {
|
||||
return fmt.Errorf("DeleteReleaseByID: %w", err)
|
||||
}
|
||||
} else {
|
||||
rel.IsTag = true
|
||||
|
||||
if err = repo_model.UpdateRelease(ctx, rel); err != nil {
|
||||
if err := repo_model.UpdateRelease(ctx, rel); err != nil {
|
||||
return fmt.Errorf("Update: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
rel.Repo = repo
|
||||
if err = rel.LoadAttributes(ctx); err != nil {
|
||||
if err := rel.LoadAttributes(ctx); err != nil {
|
||||
return fmt.Errorf("LoadAttributes: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ import (
|
||||
|
||||
// Deliver deliver hook task
|
||||
func Deliver(ctx context.Context, t *webhook_model.HookTask) error {
|
||||
w, err := webhook_model.GetWebhookByID(t.HookID)
|
||||
w, err := webhook_model.GetWebhookByID(ctx, t.HookID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -188,6 +188,21 @@ func TestDingTalkPayload(t *testing.T) {
|
||||
assert.Equal(t, "http://localhost:3000/test/repo", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
|
||||
})
|
||||
|
||||
t.Run("Package", func(t *testing.T) {
|
||||
p := packageTestPayload()
|
||||
|
||||
d := new(DingtalkPayload)
|
||||
pl, err := d.Package(p)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pl)
|
||||
require.IsType(t, &DingtalkPayload{}, pl)
|
||||
|
||||
assert.Equal(t, "Package created: GiteaContainer:latest by user1", pl.(*DingtalkPayload).ActionCard.Text)
|
||||
assert.Equal(t, "Package created: GiteaContainer:latest by user1", pl.(*DingtalkPayload).ActionCard.Title)
|
||||
assert.Equal(t, "view package", pl.(*DingtalkPayload).ActionCard.SingleTitle)
|
||||
assert.Equal(t, "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest", parseRealSingleURL(pl.(*DingtalkPayload).ActionCard.SingleURL))
|
||||
})
|
||||
|
||||
t.Run("Wiki", func(t *testing.T) {
|
||||
p := wikiTestPayload()
|
||||
|
||||
|
||||
@@ -211,6 +211,24 @@ func TestDiscordPayload(t *testing.T) {
|
||||
assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL)
|
||||
})
|
||||
|
||||
t.Run("Package", func(t *testing.T) {
|
||||
p := packageTestPayload()
|
||||
|
||||
d := new(DiscordPayload)
|
||||
pl, err := d.Package(p)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pl)
|
||||
require.IsType(t, &DiscordPayload{}, pl)
|
||||
|
||||
assert.Len(t, pl.(*DiscordPayload).Embeds, 1)
|
||||
assert.Equal(t, "Package created: GiteaContainer:latest", pl.(*DiscordPayload).Embeds[0].Title)
|
||||
assert.Empty(t, pl.(*DiscordPayload).Embeds[0].Description)
|
||||
assert.Equal(t, "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest", pl.(*DiscordPayload).Embeds[0].URL)
|
||||
assert.Equal(t, p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.Name)
|
||||
assert.Equal(t, setting.AppURL+p.Sender.UserName, pl.(*DiscordPayload).Embeds[0].Author.URL)
|
||||
assert.Equal(t, p.Sender.AvatarURL, pl.(*DiscordPayload).Embeds[0].Author.IconURL)
|
||||
})
|
||||
|
||||
t.Run("Wiki", func(t *testing.T) {
|
||||
p := wikiTestPayload()
|
||||
|
||||
|
||||
@@ -144,6 +144,18 @@ func TestFeishuPayload(t *testing.T) {
|
||||
assert.Equal(t, "[test/repo] Repository created", pl.(*FeishuPayload).Content.Text)
|
||||
})
|
||||
|
||||
t.Run("Package", func(t *testing.T) {
|
||||
p := packageTestPayload()
|
||||
|
||||
d := new(FeishuPayload)
|
||||
pl, err := d.Package(p)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pl)
|
||||
require.IsType(t, &FeishuPayload{}, pl)
|
||||
|
||||
assert.Equal(t, "Package created: GiteaContainer:latest by user1", pl.(*FeishuPayload).Content.Text)
|
||||
})
|
||||
|
||||
t.Run("Wiki", func(t *testing.T) {
|
||||
p := wikiTestPayload()
|
||||
|
||||
|
||||
@@ -303,6 +303,36 @@ func repositoryTestPayload() *api.RepositoryPayload {
|
||||
}
|
||||
}
|
||||
|
||||
func packageTestPayload() *api.PackagePayload {
|
||||
return &api.PackagePayload{
|
||||
Action: api.HookPackageCreated,
|
||||
Sender: &api.User{
|
||||
UserName: "user1",
|
||||
AvatarURL: "http://localhost:3000/user1/avatar",
|
||||
},
|
||||
Repository: nil,
|
||||
Organization: &api.User{
|
||||
UserName: "org1",
|
||||
AvatarURL: "http://localhost:3000/org1/avatar",
|
||||
},
|
||||
Package: &api.Package{
|
||||
Owner: &api.User{
|
||||
UserName: "user1",
|
||||
AvatarURL: "http://localhost:3000/user1/avatar",
|
||||
},
|
||||
Repository: nil,
|
||||
Creator: &api.User{
|
||||
UserName: "user1",
|
||||
AvatarURL: "http://localhost:3000/user1/avatar",
|
||||
},
|
||||
Type: "container",
|
||||
Name: "GiteaContainer",
|
||||
Version: "latest",
|
||||
HTMLURL: "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetIssuesPayloadInfo(t *testing.T) {
|
||||
p := issueTestPayload()
|
||||
|
||||
|
||||
@@ -212,14 +212,14 @@ func (m *MatrixPayload) Repository(p *api.RepositoryPayload) (api.Payloader, err
|
||||
|
||||
func (m *MatrixPayload) Package(p *api.PackagePayload) (api.Payloader, error) {
|
||||
senderLink := MatrixLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
|
||||
repoLink := MatrixLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName)
|
||||
packageLink := MatrixLinkFormatter(p.Package.HTMLURL, p.Package.Name)
|
||||
var text string
|
||||
|
||||
switch p.Action {
|
||||
case api.HookPackageCreated:
|
||||
text = fmt.Sprintf("[%s] Package published by %s", repoLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Package published by %s", packageLink, senderLink)
|
||||
case api.HookPackageDeleted:
|
||||
text = fmt.Sprintf("[%s] Package deleted by %s", repoLink, senderLink)
|
||||
text = fmt.Sprintf("[%s] Package deleted by %s", packageLink, senderLink)
|
||||
}
|
||||
|
||||
return getMatrixPayload(text, nil, m.MsgType), nil
|
||||
|
||||
@@ -155,6 +155,19 @@ func TestMatrixPayload(t *testing.T) {
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Repository created by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*MatrixPayload).FormattedBody)
|
||||
})
|
||||
|
||||
t.Run("Package", func(t *testing.T) {
|
||||
p := packageTestPayload()
|
||||
|
||||
d := new(MatrixPayload)
|
||||
pl, err := d.Package(p)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pl)
|
||||
require.IsType(t, &MatrixPayload{}, pl)
|
||||
|
||||
assert.Equal(t, `[[GiteaContainer](http://localhost:3000/user1/-/packages/container/GiteaContainer/latest)] Package published by [user1](https://try.gitea.io/user1)`, pl.(*MatrixPayload).Body)
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/user1/-/packages/container/GiteaContainer/latest">GiteaContainer</a>] Package published by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*MatrixPayload).FormattedBody)
|
||||
})
|
||||
|
||||
t.Run("Wiki", func(t *testing.T) {
|
||||
p := wikiTestPayload()
|
||||
|
||||
|
||||
@@ -316,11 +316,12 @@ func GetMSTeamsPayload(p api.Payloader, event webhook_module.HookEventType, _ st
|
||||
}
|
||||
|
||||
func createMSTeamsPayload(r *api.Repository, s *api.User, title, text, actionTarget string, color int, fact *MSTeamsFact) *MSTeamsPayload {
|
||||
facts := []MSTeamsFact{
|
||||
{
|
||||
facts := make([]MSTeamsFact, 0, 2)
|
||||
if r != nil {
|
||||
facts = append(facts, MSTeamsFact{
|
||||
Name: "Repository:",
|
||||
Value: r.FullName,
|
||||
},
|
||||
})
|
||||
}
|
||||
if fact != nil {
|
||||
facts = append(facts, *fact)
|
||||
|
||||
@@ -329,6 +329,33 @@ func TestMSTeamsPayload(t *testing.T) {
|
||||
assert.Equal(t, "http://localhost:3000/test/repo", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI)
|
||||
})
|
||||
|
||||
t.Run("Package", func(t *testing.T) {
|
||||
p := packageTestPayload()
|
||||
|
||||
d := new(MSTeamsPayload)
|
||||
pl, err := d.Package(p)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pl)
|
||||
require.IsType(t, &MSTeamsPayload{}, pl)
|
||||
|
||||
assert.Equal(t, "Package created: GiteaContainer:latest", pl.(*MSTeamsPayload).Title)
|
||||
assert.Equal(t, "Package created: GiteaContainer:latest", pl.(*MSTeamsPayload).Summary)
|
||||
assert.Len(t, pl.(*MSTeamsPayload).Sections, 1)
|
||||
assert.Equal(t, "user1", pl.(*MSTeamsPayload).Sections[0].ActivitySubtitle)
|
||||
assert.Empty(t, pl.(*MSTeamsPayload).Sections[0].Text)
|
||||
assert.Len(t, pl.(*MSTeamsPayload).Sections[0].Facts, 1)
|
||||
for _, fact := range pl.(*MSTeamsPayload).Sections[0].Facts {
|
||||
if fact.Name == "Package:" {
|
||||
assert.Equal(t, p.Package.Name, fact.Value)
|
||||
} else {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
assert.Len(t, pl.(*MSTeamsPayload).PotentialAction, 1)
|
||||
assert.Len(t, pl.(*MSTeamsPayload).PotentialAction[0].Targets, 1)
|
||||
assert.Equal(t, "http://localhost:3000/user1/-/packages/container/GiteaContainer/latest", pl.(*MSTeamsPayload).PotentialAction[0].Targets[0].URI)
|
||||
})
|
||||
|
||||
t.Run("Wiki", func(t *testing.T) {
|
||||
p := wikiTestPayload()
|
||||
|
||||
|
||||
@@ -115,6 +115,15 @@ func TestPackagistPayload(t *testing.T) {
|
||||
require.Nil(t, pl)
|
||||
})
|
||||
|
||||
t.Run("Package", func(t *testing.T) {
|
||||
p := packageTestPayload()
|
||||
|
||||
d := new(PackagistPayload)
|
||||
pl, err := d.Package(p)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, pl)
|
||||
})
|
||||
|
||||
t.Run("Wiki", func(t *testing.T) {
|
||||
p := wikiTestPayload()
|
||||
|
||||
|
||||
@@ -144,6 +144,18 @@ func TestSlackPayload(t *testing.T) {
|
||||
assert.Equal(t, "[<http://localhost:3000/test/repo|test/repo>] Repository created by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text)
|
||||
})
|
||||
|
||||
t.Run("Package", func(t *testing.T) {
|
||||
p := packageTestPayload()
|
||||
|
||||
d := new(SlackPayload)
|
||||
pl, err := d.Package(p)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pl)
|
||||
require.IsType(t, &SlackPayload{}, pl)
|
||||
|
||||
assert.Equal(t, "Package created: <http://localhost:3000/user1/-/packages/container/GiteaContainer/latest|GiteaContainer:latest> by <https://try.gitea.io/user1|user1>", pl.(*SlackPayload).Text)
|
||||
})
|
||||
|
||||
t.Run("Wiki", func(t *testing.T) {
|
||||
p := wikiTestPayload()
|
||||
|
||||
|
||||
@@ -144,6 +144,18 @@ func TestTelegramPayload(t *testing.T) {
|
||||
assert.Equal(t, `[<a href="http://localhost:3000/test/repo">test/repo</a>] Repository created`, pl.(*TelegramPayload).Message)
|
||||
})
|
||||
|
||||
t.Run("Package", func(t *testing.T) {
|
||||
p := packageTestPayload()
|
||||
|
||||
d := new(TelegramPayload)
|
||||
pl, err := d.Package(p)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pl)
|
||||
require.IsType(t, &TelegramPayload{}, pl)
|
||||
|
||||
assert.Equal(t, `Package created: <a href="http://localhost:3000/user1/-/packages/container/GiteaContainer/latest">GiteaContainer:latest</a> by <a href="https://try.gitea.io/user1">user1</a>`, pl.(*TelegramPayload).Message)
|
||||
})
|
||||
|
||||
t.Run("Wiki", func(t *testing.T) {
|
||||
p := wikiTestPayload()
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{{template "base/alert" .}}
|
||||
{{if .HasPackages}}
|
||||
<form class="ui form ignore-dirty">
|
||||
<div class="ui fluid action input">
|
||||
{{template "shared/searchinput" dict "Value" .Query "AutoFocus" true}}
|
||||
@@ -12,6 +13,7 @@
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "explore.search"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
{{end}}
|
||||
<div>
|
||||
{{range .PackageDescriptors}}
|
||||
<div class="flex-list">
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<div class="ui container">
|
||||
{{template "base/alert" .}}
|
||||
|
||||
{{if .workflows}}
|
||||
<div class="ui stackable grid">
|
||||
<div class="four wide column">
|
||||
<div class="ui fluid vertical menu">
|
||||
@@ -78,6 +79,9 @@
|
||||
{{template "repo/actions/runs_list" .}}
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
{{template "repo/actions/no_workflows" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<div class="empty-placeholder">
|
||||
{{svg "octicon-no-entry" 48}}
|
||||
<h2>{{ctx.Locale.Tr "actions.runs.no_workflows"}}</h2>
|
||||
{{if and .CanWriteCode .CanWriteActions}}
|
||||
<p>{{ctx.Locale.Tr "actions.runs.no_workflows.quick_start" "https://docs.gitea.com/usage/actions/quickstart/" | Safe}}</p>
|
||||
{{end}}
|
||||
<p>{{ctx.Locale.Tr "actions.runs.no_workflows.documentation" "https://docs.gitea.com/usage/actions/overview/" | Safe}}</p>
|
||||
</div>
|
||||
@@ -12,7 +12,7 @@
|
||||
</div>
|
||||
<div class="flex-item-main">
|
||||
<a class="flex-item-title" title="{{.Title}}" href="{{if .Link}}{{.Link}}{{else}}{{$.Link}}/{{.Index}}{{end}}">
|
||||
{{- .Title -}}
|
||||
{{if .Title}}{{.Title}}{{else}}{{ctx.Locale.Tr "actions.runs.empty_commit_message"}}{{end}}
|
||||
</a>
|
||||
<div class="flex-item-body">
|
||||
<b>{{if not $.CurWorkflow}}{{.WorkflowID}} {{end}}#{{.Index}}</b>:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content repository diff {{if .PageIsComparePull}}compare pull{{end}}">
|
||||
{{template "repo/header" .}}
|
||||
{{$showDiffBox := false}}
|
||||
<div class="ui container">
|
||||
<div class="ui container fluid padded">
|
||||
<h2 class="ui header">
|
||||
{{if and $.PageIsComparePull $.IsSigned (not .Repository.IsArchived)}}
|
||||
{{ctx.Locale.Tr "repo.pulls.compare_changes"}}
|
||||
@@ -235,7 +235,7 @@
|
||||
</div>
|
||||
|
||||
{{if $showDiffBox}}
|
||||
<div class="ui container">
|
||||
<div class="ui container fluid padded">
|
||||
{{template "repo/commits_table" .}}
|
||||
{{template "repo/diff/box" .}}
|
||||
</div>
|
||||
|
||||
@@ -34,13 +34,12 @@
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="ui bottom attached active tab segment" data-tab="write">
|
||||
<textarea id="edit_area" name="content" class="gt-hidden"
|
||||
data-id="repo-{{.Repository.Name}}-{{.TreePath}}"
|
||||
<textarea id="edit_area" name="content" class="gt-hidden" data-id="repo-{{.Repository.Name}}-{{.TreePath}}"
|
||||
data-url="{{.Repository.Link}}/markup"
|
||||
data-context="{{.RepoLink}}"
|
||||
data-previewable-extensions="{{.PreviewableExtensions}}"
|
||||
data-line-wrap-extensions="{{.LineWrapExtensions}}"
|
||||
data-initial-value="{{JsonUtils.EncodeToString .FileContent}}"></textarea>
|
||||
data-line-wrap-extensions="{{.LineWrapExtensions}}">
|
||||
{{.FileContent}}</textarea>
|
||||
<div class="editor-loading is-loading"></div>
|
||||
</div>
|
||||
<div class="ui bottom attached tab segment markup" data-tab="preview">
|
||||
|
||||
@@ -120,6 +120,10 @@
|
||||
</button>
|
||||
{{end}}
|
||||
{{else}}
|
||||
{{if not .tag_name}}
|
||||
<button class="ui small button" name="tag_only" value="1">{{ctx.Locale.Tr "repo.release.add_tag"}}</button>
|
||||
{{end}}
|
||||
<button class="ui small button" name="draft" value="1">{{ctx.Locale.Tr "repo.release.save_draft"}}</button>
|
||||
<button class="ui small primary button">
|
||||
{{ctx.Locale.Tr "repo.release.publish"}}
|
||||
</button>
|
||||
|
||||
Generated
+1
-1
@@ -14,7 +14,7 @@
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "This documentation describes the Gitea API.",
|
||||
"title": "Gitea API.",
|
||||
"title": "Gitea API",
|
||||
"license": {
|
||||
"name": "MIT",
|
||||
"url": "http://opensource.org/licenses/MIT"
|
||||
|
||||
@@ -35,6 +35,14 @@ func TestAPIGetCommentAttachment(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: comment.Issue.RepoID})
|
||||
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
|
||||
t.Run("UnrelatedCommentID", func(t *testing.T) {
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
|
||||
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d?token=%s", repoOwner.Name, repo.Name, comment.ID, attachment.ID, token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
|
||||
session := loginUser(t, repoOwner.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue)
|
||||
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/issues/comments/%d/assets/%d?token=%s", repoOwner.Name, repo.Name, comment.ID, attachment.ID, token)
|
||||
|
||||
@@ -177,12 +177,25 @@ func TestAPIEditComment(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
const newCommentBody = "This is the new comment body"
|
||||
|
||||
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{},
|
||||
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 8},
|
||||
unittest.Cond("type = ?", issues_model.CommentTypeComment))
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
|
||||
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
|
||||
t.Run("UnrelatedCommentID", func(t *testing.T) {
|
||||
// Using the ID of a comment that does not belong to the repository must fail
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
|
||||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d?token=%s",
|
||||
repoOwner.Name, repo.Name, comment.ID, token)
|
||||
req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{
|
||||
"body": newCommentBody,
|
||||
})
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
|
||||
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
|
||||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d?token=%s",
|
||||
repoOwner.Name, repo.Name, comment.ID, token)
|
||||
@@ -201,12 +214,22 @@ func TestAPIEditComment(t *testing.T) {
|
||||
func TestAPIDeleteComment(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{},
|
||||
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 8},
|
||||
unittest.Cond("type = ?", issues_model.CommentTypeComment))
|
||||
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID})
|
||||
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
|
||||
t.Run("UnrelatedCommentID", func(t *testing.T) {
|
||||
// Using the ID of a comment that does not belong to the repository must fail
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
|
||||
req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/comments/%d?token=%s",
|
||||
repoOwner.Name, repo.Name, comment.ID, token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
|
||||
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
|
||||
req := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/issues/comments/%d?token=%s",
|
||||
repoOwner.Name, repo.Name, comment.ID, token)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
issues_model "code.gitea.io/gitea/models/issues"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
@@ -107,6 +108,26 @@ func TestAPICommentReactions(t *testing.T) {
|
||||
})
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
t.Run("UnrelatedCommentID", func(t *testing.T) {
|
||||
// Using the ID of a comment that does not belong to the repository must fail
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||
token := getUserToken(t, repoOwner.Name, auth_model.AccessTokenScopeWriteIssue)
|
||||
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d/reactions?token=%s",
|
||||
repoOwner.Name, repo.Name, comment.ID, token)
|
||||
req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
|
||||
Reaction: "+1",
|
||||
})
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
req = NewRequestWithJSON(t, "DELETE", urlStr, &api.EditReactionOption{
|
||||
Reaction: "+1",
|
||||
})
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
req = NewRequestf(t, "GET", urlStr)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
})
|
||||
|
||||
// Add allowed reaction
|
||||
req = NewRequestWithJSON(t, "POST", urlStr, &api.EditReactionOption{
|
||||
Reaction: "+1",
|
||||
|
||||
@@ -72,6 +72,17 @@ func TestCreateReadOnlyDeployKey(t *testing.T) {
|
||||
Content: rawKeyBody.Key,
|
||||
Mode: perm.AccessModeRead,
|
||||
})
|
||||
|
||||
// Using the ID of a key that does not belong to the repository must fail
|
||||
{
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/keys/%d?token=%s", repoOwner.Name, repo.Name, newDeployKey.ID, token))
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
session5 := loginUser(t, "user5")
|
||||
token5 := getTokenForLoggedInUser(t, session5, auth_model.AccessTokenScopeWriteRepository)
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user5/repo4/keys/%d?token=%s", newDeployKey.ID, token5))
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateReadWriteDeployKey(t *testing.T) {
|
||||
|
||||
@@ -34,6 +34,6 @@ func TestNodeinfo(t *testing.T) {
|
||||
assert.Equal(t, "gitea", nodeinfo.Software.Name)
|
||||
assert.Equal(t, 25, nodeinfo.Usage.Users.Total)
|
||||
assert.Equal(t, 20, nodeinfo.Usage.LocalPosts)
|
||||
assert.Equal(t, 2, nodeinfo.Usage.LocalComments)
|
||||
assert.Equal(t, 3, nodeinfo.Usage.LocalComments)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -63,6 +63,33 @@ func TestAPIGetTokensPermission(t *testing.T) {
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
}
|
||||
|
||||
// TestAPIDeleteTokensPermission ensures that only the admin can delete tokens from other users
|
||||
func TestAPIDeleteTokensPermission(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||
|
||||
// admin can delete tokens for other users
|
||||
createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user2, nil)
|
||||
req := NewRequestf(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-1")
|
||||
req = AddBasicAuthHeader(req, admin.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// non-admin can delete tokens for himself
|
||||
createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user2, nil)
|
||||
req = NewRequestf(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-2")
|
||||
req = AddBasicAuthHeader(req, user2.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// non-admin can't delete tokens for other users
|
||||
createAPIAccessTokenWithoutCleanUp(t, "test-key-3", user2, nil)
|
||||
req = NewRequestf(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-3")
|
||||
req = AddBasicAuthHeader(req, user4.Name)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
}
|
||||
|
||||
type permission struct {
|
||||
category auth_model.AccessTokenScopeCategory
|
||||
level auth_model.AccessTokenScopeLevel
|
||||
@@ -526,7 +553,7 @@ func createAPIAccessTokenWithoutCleanUp(t *testing.T, tokenName string, user *us
|
||||
}
|
||||
}
|
||||
log.Debug("Requesting creation of token with scopes: %v", scopes)
|
||||
req := NewRequestWithJSON(t, "POST", "/api/v1/users/user1/tokens", payload)
|
||||
req := NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", payload)
|
||||
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
@@ -546,7 +573,7 @@ func createAPIAccessTokenWithoutCleanUp(t *testing.T, tokenName string, user *us
|
||||
// createAPIAccessTokenWithoutCleanUp Delete an API access token and assert that
|
||||
// deletion succeeded.
|
||||
func deleteAPIAccessToken(t *testing.T, accessToken api.AccessToken, user *user_model.User) {
|
||||
req := NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", accessToken.ID)
|
||||
req := NewRequestf(t, "DELETE", "/api/v1/users/"+user.LoginName+"/tokens/%d", accessToken.ID)
|
||||
req = AddBasicAuthHeader(req, user.Name)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
|
||||
@@ -206,6 +206,56 @@ func TestIssueCommentClose(t *testing.T) {
|
||||
assert.Equal(t, "Description", val)
|
||||
}
|
||||
|
||||
func TestIssueCommentDelete(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
session := loginUser(t, "user2")
|
||||
issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description")
|
||||
comment1 := "Test comment 1"
|
||||
commentID := testIssueAddComment(t, session, issueURL, comment1, "")
|
||||
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID})
|
||||
assert.Equal(t, comment1, comment.Content)
|
||||
|
||||
// Using the ID of a comment that does not belong to the repository must fail
|
||||
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d/delete", "user5", "repo4", commentID), map[string]string{
|
||||
"_csrf": GetCSRF(t, session, issueURL),
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusNotFound)
|
||||
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d/delete", "user2", "repo1", commentID), map[string]string{
|
||||
"_csrf": GetCSRF(t, session, issueURL),
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
unittest.AssertNotExistsBean(t, &issues_model.Comment{ID: commentID})
|
||||
}
|
||||
|
||||
func TestIssueCommentUpdate(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
session := loginUser(t, "user2")
|
||||
issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description")
|
||||
comment1 := "Test comment 1"
|
||||
commentID := testIssueAddComment(t, session, issueURL, comment1, "")
|
||||
|
||||
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID})
|
||||
assert.Equal(t, comment1, comment.Content)
|
||||
|
||||
modifiedContent := comment.Content + "MODIFIED"
|
||||
|
||||
// Using the ID of a comment that does not belong to the repository must fail
|
||||
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user5", "repo4", commentID), map[string]string{
|
||||
"_csrf": GetCSRF(t, session, issueURL),
|
||||
"content": modifiedContent,
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/%s/comments/%d", "user2", "repo1", commentID), map[string]string{
|
||||
"_csrf": GetCSRF(t, session, issueURL),
|
||||
"content": modifiedContent,
|
||||
})
|
||||
session.MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
comment = unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: commentID})
|
||||
assert.Equal(t, modifiedContent, comment.Content)
|
||||
}
|
||||
|
||||
func TestIssueReaction(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
@@ -88,7 +88,7 @@ func TestMirrorPull(t *testing.T) {
|
||||
|
||||
release, err := repo_model.GetRelease(db.DefaultContext, repo.ID, "v0.2")
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, release_service.DeleteReleaseByID(ctx, release.ID, user, true))
|
||||
assert.NoError(t, release_service.DeleteReleaseByID(ctx, repo, release, user, true))
|
||||
|
||||
ok = mirror_service.SyncPullMirror(ctx, mirror.ID)
|
||||
assert.True(t, ok)
|
||||
|
||||
@@ -62,7 +62,7 @@ export async function createMonaco(textarea, filename, editorOpts) {
|
||||
const monaco = await import(/* webpackChunkName: "monaco" */'monaco-editor');
|
||||
|
||||
initLanguages(monaco);
|
||||
let {language, eol, ...other} = editorOpts;
|
||||
let {language, ...other} = editorOpts;
|
||||
if (!language) language = getLanguage(filename);
|
||||
|
||||
const container = document.createElement('div');
|
||||
@@ -105,28 +105,14 @@ export async function createMonaco(textarea, filename, editorOpts) {
|
||||
monaco.languages.register({id: 'vs.editor.nullLanguage'});
|
||||
monaco.languages.setLanguageConfiguration('vs.editor.nullLanguage', {});
|
||||
|
||||
// We encode the initial value in JSON on the backend to prevent browsers from
|
||||
// discarding the \r during HTML parsing:
|
||||
// https://html.spec.whatwg.org/multipage/parsing.html#preprocessing-the-input-stream
|
||||
const value = JSON.parse(textarea.getAttribute('data-initial-value') || '""');
|
||||
textarea.value = value;
|
||||
textarea.removeAttribute('data-initial-value');
|
||||
|
||||
const editor = monaco.editor.create(container, {
|
||||
value,
|
||||
value: textarea.value,
|
||||
theme: 'gitea',
|
||||
language,
|
||||
...other,
|
||||
});
|
||||
|
||||
const model = editor.getModel();
|
||||
|
||||
// Monaco performs auto-detection of dominant EOL in the file, biased towards LF for
|
||||
// empty files. If there is an editorconfig value, override this detected value.
|
||||
if (eol in monaco.editor.EndOfLineSequence) {
|
||||
model.setEOL(monaco.editor.EndOfLineSequence[eol]);
|
||||
}
|
||||
|
||||
model.onDidChangeContent(() => {
|
||||
textarea.value = editor.getValue();
|
||||
textarea.dispatchEvent(new Event('change')); // seems to be needed for jquery-are-you-sure
|
||||
@@ -201,6 +187,5 @@ function getEditorConfigOptions(ec) {
|
||||
opts.trimAutoWhitespace = ec.trim_trailing_whitespace === true;
|
||||
opts.insertSpaces = ec.indent_style === 'space';
|
||||
opts.useTabStops = ec.indent_style === 'tab';
|
||||
opts.eol = ec.end_of_line?.toUpperCase();
|
||||
return opts;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@ import {initDiffCommitSelect} from './repo-diff-commitselect.js';
|
||||
import {validateTextareaNonEmpty} from './comp/ComboMarkdownEditor.js';
|
||||
import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.js';
|
||||
import {initImageDiff} from './imagediff.js';
|
||||
import {showErrorToast} from '../modules/toast.js';
|
||||
|
||||
const {csrfToken, pageData} = window.config;
|
||||
const {csrfToken, pageData, i18n} = window.config;
|
||||
|
||||
function initRepoDiffReviewButton() {
|
||||
const $reviewBox = $('#review-box');
|
||||
@@ -50,26 +51,34 @@ function initRepoDiffConversationForm() {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData($form[0]);
|
||||
if ($form.hasClass('is-loading')) return;
|
||||
try {
|
||||
$form.addClass('is-loading');
|
||||
const formData = new FormData($form[0]);
|
||||
|
||||
// if the form is submitted by a button, append the button's name and value to the form data
|
||||
const submitter = e.originalEvent?.submitter;
|
||||
const isSubmittedByButton = (submitter?.nodeName === 'BUTTON') || (submitter?.nodeName === 'INPUT' && submitter.type === 'submit');
|
||||
if (isSubmittedByButton && submitter.name) {
|
||||
formData.append(submitter.name, submitter.value);
|
||||
}
|
||||
const formDataString = String(new URLSearchParams(formData));
|
||||
const $newConversationHolder = $(await $.post($form.attr('action'), formDataString));
|
||||
const {path, side, idx} = $newConversationHolder.data();
|
||||
// if the form is submitted by a button, append the button's name and value to the form data
|
||||
const submitter = e.originalEvent?.submitter;
|
||||
const isSubmittedByButton = (submitter?.nodeName === 'BUTTON') || (submitter?.nodeName === 'INPUT' && submitter.type === 'submit');
|
||||
if (isSubmittedByButton && submitter.name) {
|
||||
formData.append(submitter.name, submitter.value);
|
||||
}
|
||||
const formDataString = String(new URLSearchParams(formData));
|
||||
const $newConversationHolder = $(await $.post($form.attr('action'), formDataString));
|
||||
const {path, side, idx} = $newConversationHolder.data();
|
||||
|
||||
$form.closest('.conversation-holder').replaceWith($newConversationHolder);
|
||||
if ($form.closest('tr').data('line-type') === 'same') {
|
||||
$(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).addClass('gt-invisible');
|
||||
} else {
|
||||
$(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).addClass('gt-invisible');
|
||||
$form.closest('.conversation-holder').replaceWith($newConversationHolder);
|
||||
if ($form.closest('tr').data('line-type') === 'same') {
|
||||
$(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).addClass('gt-invisible');
|
||||
} else {
|
||||
$(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).addClass('gt-invisible');
|
||||
}
|
||||
$newConversationHolder.find('.dropdown').dropdown();
|
||||
initCompReactionSelector($newConversationHolder);
|
||||
} catch { // here the caught error might be a jQuery AJAX error (thrown by await $.post), which is not good to use for error message handling
|
||||
showErrorToast(i18n.network_error);
|
||||
} finally {
|
||||
$form.removeClass('is-loading');
|
||||
}
|
||||
$newConversationHolder.find('.dropdown').dropdown();
|
||||
initCompReactionSelector($newConversationHolder);
|
||||
});
|
||||
|
||||
$(document).on('click', '.resolve-conversation', async function (e) {
|
||||
|
||||
Reference in New Issue
Block a user