diff --git a/.gitignore b/.gitignore index 53365ed0b4e8e..814d91031538f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ _test .idea # Goland's output filename can not be set manually /go_build_* +/gitea_* # MS VSCode .vscode diff --git a/cmd/admin.go b/cmd/admin.go index b5903cd4fd759..74bfa5a6c6703 100644 --- a/cmd/admin.go +++ b/cmd/admin.go @@ -157,10 +157,10 @@ func runRepoSyncReleases(_ *cli.Context) error { } func getReleaseCount(ctx context.Context, id int64) (int64, error) { - return repo_model.GetReleaseCountByRepoID( + return db.Count[repo_model.Release]( ctx, - id, repo_model.FindReleasesOptions{ + RepoID: id, IncludeTags: true, }, ) diff --git a/cmd/doctor_convert.go b/cmd/doctor_convert.go index 2385f23e52193..48c835ad0e2eb 100644 --- a/cmd/doctor_convert.go +++ b/cmd/doctor_convert.go @@ -37,8 +37,8 @@ func runDoctorConvert(ctx *cli.Context) error { switch { case setting.Database.Type.IsMySQL(): - if err := db.ConvertUtf8ToUtf8mb4(); err != nil { - log.Fatal("Failed to convert database from utf8 to utf8mb4: %v", err) + if err := db.ConvertDatabaseTable(); err != nil { + log.Fatal("Failed to convert database & table: %v", err) return err } fmt.Println("Converted successfully, please confirm your database's character set is now utf8mb4") diff --git a/contrib/systemd/gitea.service b/contrib/systemd/gitea.service index c097fb0d17632..d205c6ee8ba6e 100644 --- a/contrib/systemd/gitea.service +++ b/contrib/systemd/gitea.service @@ -52,7 +52,7 @@ After=network.target # Uncomment the next line if you have repos with lots of files and get a HTTP 500 error because of that # LimitNOFILE=524288:524288 RestartSec=2s -Type=notify +Type=simple User=git Group=git WorkingDirectory=/var/lib/gitea/ @@ -62,7 +62,6 @@ WorkingDirectory=/var/lib/gitea/ ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini Restart=always Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea -WatchdogSec=30s # If you install Git to directory prefix other than default PATH (which happens # for example if you install other versions of Git side-to-side with # distribution version), uncomment below line and add that prefix to PATH diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index d1dd0a141b963..301e88b14e645 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -351,6 +351,7 @@ NAME = gitea USER = root ;PASSWD = ;Use PASSWD = `your password` for quoting if you use special characters in the password. ;SSL_MODE = false ; either "false" (default), "true", or "skip-verify" +;CHARSET_COLLATION = ; Empty as default, Gitea will try to find a case-sensitive collation. Don't change it unless you clearly know what you need. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; @@ -382,6 +383,7 @@ USER = root ;NAME = gitea ;USER = SA ;PASSWD = MwantsaSecurePassword1 +;CHARSET_COLLATION = ; Empty as default, Gitea will try to find a case-sensitive collation. Don't change it unless you clearly know what you need. ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index c11b4012a5514..c9e99ea54fcef 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -430,6 +430,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a - `NAME`: **gitea**: Database name. - `USER`: **root**: Database username. - `PASSWD`: **_empty_**: Database user password. Use \`your password\` or """your password""" for quoting if you use special characters in the password. +- `CHARSET_COLLATION`: **_empty_**: (MySQL/MSSQL only) Gitea expects to use a case-sensitive collation for database. Leave it empty to use the default collation decided by the Gitea. Don't change it unless you clearly know what you need. - `SCHEMA`: **_empty_**: For PostgreSQL only, schema to use if different from "public". The schema must exist beforehand, the user must have creation privileges on it, and the user search path must be set to the look into the schema first (e.g. `ALTER USER user SET SEARCH_PATH = schema_name,"$user",public;`). diff --git a/docs/content/contributing/guidelines-frontend.en-us.md b/docs/content/contributing/guidelines-frontend.en-us.md index 0d9e510e70039..aa1759d9c974d 100644 --- a/docs/content/contributing/guidelines-frontend.en-us.md +++ b/docs/content/contributing/guidelines-frontend.en-us.md @@ -48,11 +48,12 @@ We recommend [Google HTML/CSS Style Guide](https://google.github.io/styleguide/h 10. Avoid mixing different events in one event listener, prefer to use individual event listeners for every event. 11. Custom event names are recommended to use `ce-` prefix. 12. Gitea's tailwind-style CSS classes use `gt-` prefix (`gt-relative`), while Gitea's own private framework-level CSS classes use `g-` prefix (`g-modal-confirm`). +13. Avoid inline scripts & styles as much as possible, it's recommended to put JS code into JS files and use CSS classes. If inline scripts & styles are unavoidable, explain the reason why it can't be avoided. ### Accessibility / ARIA In history, Gitea heavily uses Fomantic UI which is not an accessibility-friendly framework. -Gitea uses some patches to make Fomantic UI more accessible (see the `aria.js` and `aria.md`), +Gitea uses some patches to make Fomantic UI more accessible (see `aria.md` and related JS files), but there are still many problems which need a lot of work and time to fix. ### Framework Usage diff --git a/docs/content/help/faq.en-us.md b/docs/content/help/faq.en-us.md index e6350936ef816..5ea2c10f5ee52 100644 --- a/docs/content/help/faq.en-us.md +++ b/docs/content/help/faq.en-us.md @@ -371,24 +371,6 @@ If you are receiving an error line containing `Error 1071: Specified key was too then you are attempting to run Gitea on tables which use the ISAM engine. While this may have worked by chance in previous versions of Gitea, it has never been officially supported and you must use InnoDB. You should run `ALTER TABLE table_name ENGINE=InnoDB;` for each table in the database. -If you are using MySQL 5, another possible fix is - -```mysql -SET GLOBAL innodb_file_format=Barracuda; -SET GLOBAL innodb_file_per_table=1; -SET GLOBAL innodb_large_prefix=1; -``` - -## Why Are Emoji Broken On MySQL - -Unfortunately MySQL's `utf8` charset does not completely allow all possible UTF-8 characters, in particular Emoji. -They created a new charset and collation called `utf8mb4` that allows for emoji to be stored but tables which use -the `utf8` charset, and connections which use the `utf8` charset will not use this. - -Please run `gitea doctor convert`, or run `ALTER DATABASE database_name CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;` -for the database_name and run `ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;` -for each table in the database. - ## Why are Emoji displaying only as placeholders or in monochrome Gitea requires the system or browser to have one of the supported Emoji fonts installed, which are Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji and Twemoji Mozilla. Generally, the operating system should already provide one of these fonts, but especially on Linux, it may be necessary to install them manually. diff --git a/docs/content/help/faq.zh-cn.md b/docs/content/help/faq.zh-cn.md index 909ca7e5e2138..b8dd3cd180353 100644 --- a/docs/content/help/faq.zh-cn.md +++ b/docs/content/help/faq.zh-cn.md @@ -375,25 +375,6 @@ Gitea 提供了一个子命令`gitea migrate`来初始化数据库,然后您 的错误行,则表示您正在尝试在使用 ISAM 引擎的表上运行 Gitea。尽管在先前版本的 Gitea 中可能是凑巧能够工作的,但它从未得到官方支持, 您必须使用 InnoDB。您应该对数据库中的每个表运行`ALTER TABLE table_name ENGINE=InnoDB;`。 -如果您使用的是 MySQL 5,另一个可能的修复方法是: - -```mysql -SET GLOBAL innodb_file_format=Barracuda; -SET GLOBAL innodb_file_per_table=1; -SET GLOBAL innodb_large_prefix=1; -``` - -## 为什么 MySQL 上的 Emoji 显示错误 - -不幸的是,MySQL 的`utf8`字符集不完全允许所有可能的 UTF-8 字符,特别是 Emoji。 -他们创建了一个名为 `utf8mb4`的字符集和校对规则,允许存储 Emoji,但使用 -utf8 字符集的表和连接将不会使用它。 - -请运行 `gitea doctor convert` 或对数据库运行 `ALTER DATABASE database_name CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;` -并对每个表运行 `ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;`。 - -您还需要将`app.ini`文件中的数据库字符集设置为`CHARSET=utf8mb4`。 - ## 为什么 Emoji 只显示占位符或单色图像 Gitea 需要系统或浏览器安装其中一个受支持的 Emoji 字体,例如 Apple Color Emoji、Segoe UI Emoji、Segoe UI Symbol、Noto Color Emoji 和 Twemoji Mozilla。通常,操作系统应该已经提供了其中一个字体,但特别是在 Linux 上,可能需要手动安装它们。 diff --git a/docs/content/installation/database-preparation.en-us.md b/docs/content/installation/database-preparation.en-us.md index 5e0b94665ff41..e6abb8a613750 100644 --- a/docs/content/installation/database-preparation.en-us.md +++ b/docs/content/installation/database-preparation.en-us.md @@ -61,10 +61,14 @@ Note: All steps below requires that the database engine of your choice is instal Replace username and password above as appropriate. -4. Create database with UTF-8 charset and collation. Make sure to use `utf8mb4` charset instead of `utf8` as the former supports all Unicode characters (including emojis) beyond _Basic Multilingual Plane_. Also, collation chosen depending on your expected content. When in doubt, use either `unicode_ci` or `general_ci`. +4. Create database with UTF-8 charset and case-sensitive collation. + + `utf8mb4_bin` is a common collation for both MySQL/MariaDB. + When Gitea starts, it will try to find a better collation (`utf8mb4_0900_as_cs` or `uca1400_as_cs`) and alter the database if it is possible. + If you would like to use other collation, you can set `[database].CHARSET_COLLATION` in the `app.ini` file. ```sql - CREATE DATABASE giteadb CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_unicode_ci'; + CREATE DATABASE giteadb CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_bin'; ``` Replace database name as appropriate. diff --git a/docs/content/installation/database-preparation.zh-cn.md b/docs/content/installation/database-preparation.zh-cn.md index b58344133ee70..d651088395fd2 100644 --- a/docs/content/installation/database-preparation.zh-cn.md +++ b/docs/content/installation/database-preparation.zh-cn.md @@ -59,10 +59,12 @@ menu: 根据需要替换上述用户名和密码。 -4. 使用 UTF-8 字符集和排序规则创建数据库。确保使用 `**utf8mb4**` 字符集,而不是 `utf8`,因为前者支持 _Basic Multilingual Plane_ 之外的所有 Unicode 字符(包括表情符号)。排序规则根据您预期的内容选择。如果不确定,可以使用 `unicode_ci` 或 `general_ci`。 +4. 使用 UTF-8 字符集和大小写敏感的排序规则创建数据库。 + + Gitea 启动后会尝试把数据库修改为更合适的字符集,如果你想指定自己的字符集规则,可以在 app.ini 中设置 `[database].CHARSET_COLLATION`。 ```sql - CREATE DATABASE giteadb CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_unicode_ci'; + CREATE DATABASE giteadb CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_bin'; ``` 根据需要替换数据库名称。 diff --git a/docs/content/usage/packages/rpm.en-us.md b/docs/content/usage/packages/rpm.en-us.md index 5a4a31ee39dfe..586e48d47fe3e 100644 --- a/docs/content/usage/packages/rpm.en-us.md +++ b/docs/content/usage/packages/rpm.en-us.md @@ -27,17 +27,18 @@ The following examples use `dnf`. To register the RPM registry add the url to the list of known apt sources: ```shell -dnf config-manager --add-repo https://gitea.example.com/api/packages/{owner}/rpm.repo +dnf config-manager --add-repo https://gitea.example.com/api/packages/{owner}/rpm/{group}.repo ``` -| Placeholder | Description | -| ----------- | ----------- | -| `owner` | The owner of the package. | +| Placeholder | Description | +| ----------- |----------------------------------------------------| +| `owner` | The owner of the package. | +| `group` | Everything, e.g. `el7`, `rocky/el9` , `test/fc38`.| If the registry is private, provide credentials in the url. You can use a password or a [personal access token](development/api-usage.md#authentication): ```shell -dnf config-manager --add-repo https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/rpm.repo +dnf config-manager --add-repo https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/rpm/{group}.repo ``` You have to add the credentials to the urls in the `rpm.repo` file in `/etc/yum.repos.d` too. @@ -47,19 +48,20 @@ You have to add the credentials to the urls in the `rpm.repo` file in `/etc/yum. To publish a RPM package (`*.rpm`), perform a HTTP PUT operation with the package content in the request body. ``` -PUT https://gitea.example.com/api/packages/{owner}/rpm/upload +PUT https://gitea.example.com/api/packages/{owner}/rpm/{group}/upload ``` | Parameter | Description | | --------- | ----------- | | `owner` | The owner of the package. | +| `group` | Everything, e.g. `el7`, `rocky/el9` , `test/fc38`.| Example request using HTTP Basic authentication: ```shell curl --user your_username:your_password_or_token \ --upload-file path/to/file.rpm \ - https://gitea.example.com/api/packages/testuser/rpm/upload + https://gitea.example.com/api/packages/testuser/rpm/centos/el7/upload ``` If you are using 2FA or OAuth use a [personal access token](development/api-usage.md#authentication) instead of the password. @@ -78,21 +80,22 @@ The server responds with the following HTTP Status codes. To delete an RPM package perform a HTTP DELETE operation. This will delete the package version too if there is no file left. ``` -DELETE https://gitea.example.com/api/packages/{owner}/rpm/{package_name}/{package_version}/{architecture} +DELETE https://gitea.example.com/api/packages/{owner}/rpm/{group}/package/{package_name}/{package_version}/{architecture} ``` -| Parameter | Description | -| ----------------- | ----------- | -| `owner` | The owner of the package. | -| `package_name` | The package name. | -| `package_version` | The package version. | -| `architecture` | The package architecture. | +| Parameter | Description | +|-------------------|----------------------------| +| `owner` | The owner of the package. | +| `group` | The package group . | +| `package_name` | The package name. | +| `package_version` | The package version. | +| `architecture` | The package architecture. | Example request using HTTP Basic authentication: ```shell curl --user your_username:your_token_or_password -X DELETE \ - https://gitea.example.com/api/packages/testuser/rpm/test-package/1.0.0/x86_64 + https://gitea.example.com/api/packages/testuser/rpm/centos/el7/package/test-package/1.0.0/x86_64 ``` The server responds with the following HTTP Status codes. diff --git a/docs/content/usage/packages/rpm.zh-cn.md b/docs/content/usage/packages/rpm.zh-cn.md index 3cc7dca8ff2cf..cbe74bfee230d 100644 --- a/docs/content/usage/packages/rpm.zh-cn.md +++ b/docs/content/usage/packages/rpm.zh-cn.md @@ -27,17 +27,18 @@ menu: 要注册RPM注册表,请将 URL 添加到已知 `apt` 源列表中: ```shell -dnf config-manager --add-repo https://gitea.example.com/api/packages/{owner}/rpm.repo +dnf config-manager --add-repo https://gitea.example.com/api/packages/{owner}/rpm/{group}.repo ``` -| 占位符 | 描述 | -| ------- | -------------- | -| `owner` | 软件包的所有者 | +| 占位符 | 描述 | +| ------- |--------------------------------------| +| `owner` | 软件包的所有者 | +| `group` | 任何名称,例如 `centos/7`、`el-7`、`fc38` | 如果注册表是私有的,请在URL中提供凭据。您可以使用密码或[个人访问令牌](development/api-usage.md#通过-api-认证): ```shell -dnf config-manager --add-repo https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/rpm.repo +dnf config-manager --add-repo https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/rpm/{group}.repo ``` 您还必须将凭据添加到 `/etc/yum.repos.d` 中的 `rpm.repo` 文件中的URL中。 @@ -47,19 +48,20 @@ dnf config-manager --add-repo https://{username}:{your_password_or_token}@gitea. 要发布RPM软件包(`*.rpm`),请执行带有软件包内容的 HTTP `PUT` 操作。 ``` -PUT https://gitea.example.com/api/packages/{owner}/rpm/upload +PUT https://gitea.example.com/api/packages/{owner}/rpm/{group}/upload ``` | 参数 | 描述 | -| ------- | -------------- | -| `owner` | 软件包的所有者 | +| ------- |--------------| +| `owner` | 软件包的所有者 | +| `group` | 软件包自定义分组名称 | 使用HTTP基本身份验证的示例请求: ```shell curl --user your_username:your_password_or_token \ --upload-file path/to/file.rpm \ - https://gitea.example.com/api/packages/testuser/rpm/upload + https://gitea.example.com/api/packages/testuser/rpm/centos/el7/version/upload ``` 如果您使用 2FA 或 OAuth,请使用[个人访问令牌](development/api-usage.md#通过-api-认证)替代密码。您无法将具有相同名称的文件两次发布到软件包中。您必须先删除现有的软件包版本。 @@ -77,12 +79,13 @@ curl --user your_username:your_password_or_token \ 要删除 RPM 软件包,请执行 HTTP `DELETE` 操作。如果没有文件剩余,这也将删除软件包版本。 ``` -DELETE https://gitea.example.com/api/packages/{owner}/rpm/{package_name}/{package_version}/{architecture} +DELETE https://gitea.example.com/api/packages/{owner}/rpm/{group}/package/{package_name}/{package_version}/{architecture} ``` | 参数 | 描述 | | ----------------- | -------------- | | `owner` | 软件包的所有者 | +| `group` | 软件包自定义分组 | | `package_name` | 软件包名称 | | `package_version` | 软件包版本 | | `architecture` | 软件包架构 | @@ -91,7 +94,7 @@ DELETE https://gitea.example.com/api/packages/{owner}/rpm/{package_name}/{packag ```shell curl --user your_username:your_token_or_password -X DELETE \ - https://gitea.example.com/api/packages/testuser/rpm/test-package/1.0.0/x86_64 + https://gitea.example.com/api/packages/testuser/rpm/centos/el7/package/test-package/1.0.0/x86_64 ``` 服务器将以以下HTTP状态码响应: diff --git a/go.mod b/go.mod index c08010a916c71..e1b37c65f6604 100644 --- a/go.mod +++ b/go.mod @@ -121,7 +121,7 @@ require ( mvdan.cc/xurls/v2 v2.5.0 strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 xorm.io/builder v0.3.13 - xorm.io/xorm v1.3.6 + xorm.io/xorm v1.3.7-0.20240101024435-4992cba040fe ) require ( @@ -165,7 +165,7 @@ require ( github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/cloudflare/circl v1.3.6 // indirect + github.com/cloudflare/circl v1.3.7 // indirect github.com/couchbase/go-couchbase v0.1.1 // indirect github.com/couchbase/gomemcached v0.3.0 // indirect github.com/couchbase/goutils v0.1.2 // indirect diff --git a/go.sum b/go.sum index 69b2556dd3d38..ae6c91f902f2d 100644 --- a/go.sum +++ b/go.sum @@ -65,7 +65,6 @@ gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHq gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= -gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE= github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121 h1:r3qt8PCHnfjOv9PN3H+XXKmDA1dfFMIN1AislhlA/ps= github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121/go.mod h1:Ock8XgA7pvULhIaHGAk/cDnRfNrF9Jey81nPcc403iU= github.com/6543/go-version v1.3.1 h1:HvOp+Telns7HWJ2Xo/05YXQSB2bE0WmVgbHqwMPZT4U= @@ -87,7 +86,6 @@ github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwS github.com/Julusian/godocdown v0.0.0-20170816220326-6d19f8ff2df8/go.mod h1:INZr5t32rG59/5xeltqoCJoNY7e5x/3xoY9WSWVWg74= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= @@ -213,22 +211,16 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chi-middleware/proxy v1.1.1 h1:4HaXUp8o2+bhHr1OhVy+VjN0+L7/07JDcn6v7YrTjrQ= github.com/chi-middleware/proxy v1.1.1/go.mod h1:jQwMEJct2tz9VmtCELxvnXoMfa+SOdikvbVJVHv/M+0= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= -github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg= -github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k= github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A= github.com/couchbase/go-couchbase v0.1.1 h1:ClFXELcKj/ojyoTYbsY34QUrrYCBi/1G749sXSCkdhk= @@ -243,7 +235,6 @@ github.com/couchbase/moss v0.1.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37g github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= @@ -276,7 +267,6 @@ github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5O github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dvyukov/go-fuzz v0.0.0-20210429054444-fca39067bc72/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= @@ -359,10 +349,8 @@ github.com/go-git/go-git/v5 v5.9.0/go.mod h1:RKIqga24sWdMGZF+1Ekv9kylsDz6LzdTSI2 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-openapi/analysis v0.21.5 h1:3tHfEBh6Ia8eKc4M7khOGjPOAlWKJ10d877Cr9teujI= github.com/go-openapi/analysis v0.21.5/go.mod h1:25YcZosX9Lwz2wBsrFrrsL8bmjjXdlyP6zsr2AMy29M= github.com/go-openapi/errors v0.21.0 h1:FhChC/duCnfoLj1gZ0BgaBmzhJC2SL/sJr8a2vAobSY= @@ -388,10 +376,8 @@ github.com/go-openapi/validate v0.22.4/go.mod h1:qm6O8ZIcPVdSY5219468Jv7kBdGvkiZ github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-redis/redis/v8 v8.4.0/go.mod h1:A1tbYoHSa1fXwN+//ljcCYYJeLmVrwL9hbQN45Jdy0M= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-swagger/go-swagger v0.30.5 h1:SQ2+xSonWjjoEMOV5tcOnZJVlfyUfCBhGQGArS1b9+U= github.com/go-swagger/go-swagger v0.30.5/go.mod h1:cWUhSyCNqV7J1wkkxfr5QmbcnCewetCdvEXqgPvbc/Q= github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013 h1:l9rI6sNaZgNC0LnF3MiE+qTmyBA/tZAg1rtyrGbUMK0= @@ -407,7 +393,6 @@ github.com/go-webauthn/x v0.1.5 h1:V2TCzDU2TGLd0kSZOXdrqDVV5JB9ILnKxA9S53CSBw0= github.com/go-webauthn/x v0.1.5/go.mod h1:qbzWwcFcv4rTwtCLOZd+icnr6B7oSsAGZJqlt8cukqY= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.5/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= @@ -476,7 +461,6 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v57 v57.0.0 h1:L+Y3UPTY8ALM8x+TV0lg+IEBI+upibemtBD8Q9u7zHs= @@ -499,12 +483,10 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/pprof v0.0.0-20231212022811-ec68065c825e h1:bwOy7hAFd0C91URzMIEBfr6BAz29yk7Qj0cy6S7DJlU= github.com/google/pprof v0.0.0-20231212022811-ec68065c825e/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= @@ -556,61 +538,26 @@ github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= -github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.0/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056 h1:iCHtR9CQyktQ5+f3dMVZfwD2KWJUgm7M0gdL9NGr8KA= github.com/jaytaylor/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= @@ -656,15 +603,12 @@ github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQ github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kljensen/snowball v0.6.0/go.mod h1:27N7E8fVU5H68RlUmnWwZCfxgt4POBJfENGMvNRhldw= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -674,11 +618,7 @@ github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++ github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= github.com/lestrrat-go/jwx v1.2.21/go.mod h1:9cfxnOH7G1gN75CaJP2hKGcxFEx5sPh1abRIA/ZJVh4= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libdns/libdns v0.2.1 h1:Wu59T7wSHRgtA0cfxC+n1c/e+O3upJGWytknkmFEDis= @@ -699,13 +639,8 @@ github.com/markbates/goth v1.78.0 h1:7VEIFDycJp9deyVv3YraGBPdD0ZYQW93Y3Aw1eVP3BY github.com/markbates/goth v1.78.0/go.mod h1:X6xdNgpapSENS0O35iTBBcMHoJDQDfI9bJl+APCkYMc= github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= @@ -713,8 +648,6 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= @@ -807,7 +740,6 @@ github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -845,11 +777,8 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -866,7 +795,6 @@ github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -876,8 +804,6 @@ github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBf github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d/go.mod h1:vq0tzqLRu6TS7Id0wMo2N5QzJoKedVeovOpHjnykSzY= github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg= github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -912,8 +838,6 @@ github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02n github.com/stephens2424/writerset v1.0.2/go.mod h1:aS2JhsMn6eA7e82oNmW4rfsgAOp9COBTTl8mzkwADnc= github.com/steveyen/gtreap v0.1.0/go.mod h1:kl/5J7XbrOmlIbYIXdRHDDE5QxHqpk0cmkT7Z4dM9/Y= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -1001,8 +925,6 @@ github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= @@ -1019,41 +941,25 @@ go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc= go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -1061,7 +967,6 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= @@ -1117,7 +1022,6 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1173,16 +1077,12 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1199,7 +1099,6 @@ golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1224,7 +1123,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1254,7 +1152,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= @@ -1278,18 +1175,14 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1298,7 +1191,6 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1322,15 +1214,12 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20200928182047-19e03678916f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= -golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1436,7 +1325,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= @@ -1463,43 +1351,26 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= -modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= -modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= -modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI= -modernc.org/ccgo/v3 v3.0.0-20220910160915-348f15de615a/go.mod h1:8p47QxPkdugex9J4n9P2tLZ9bK01yngIVp00g4nomW0= -modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g= modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= -modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= -modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= -modernc.org/libc v1.17.4/go.mod h1:WNg2ZH56rDEwdropAJeZPQkXmDwh+JCA1s/htl6r2fA= -modernc.org/libc v1.18.0/go.mod h1:vj6zehR5bfc98ipowQOM2nIDUZnVew/wNC/2tOGS+q0= -modernc.org/libc v1.19.0/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= -modernc.org/libc v1.20.3/go.mod h1:ZRfIaEkgrYgZDl6pa4W39HgN5G/yDW+NRmNKZBDFrk0= -modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0= modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= -modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk= modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE= modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= -modernc.org/tcl v1.15.0/go.mod h1:xRoGotBZ6dU+Zo2tca+2EqVEeMmOUBzHnhIwq4YrVnE= modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ= mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8= mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= @@ -1507,8 +1378,7 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251 h1:mUcz5b3FJbP5Cvdq7Khzn6J9OCUQJaBwgBkCR+MOwSs= strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:FJGmPh3vz9jSos1L/F91iAgnC/aejc0wIIrF2ZwJxdY= -xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo= xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= -xorm.io/xorm v1.3.6 h1:hfpWHkDIWWqUi8FRF2H2M9O8lO3Ov47rwFcS9gPzPkU= -xorm.io/xorm v1.3.6/go.mod h1:qFJGFoVYbbIdnz2vaL5OxSQ2raleMpyRRalnq3n9OJo= +xorm.io/xorm v1.3.7-0.20240101024435-4992cba040fe h1:c+IGxoesJV3s4QZb55feZIb1sqFEUluAYHpe5uJIO6U= +xorm.io/xorm v1.3.7-0.20240101024435-4992cba040fe/go.mod h1:/PjYRKEcJ67WtOnb6DXEMb2Y0uWFaZSoDlhJUebWbXw= diff --git a/models/actions/run.go b/models/actions/run.go index 4656aa22a2933..1a3701b0b0aec 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -168,13 +168,14 @@ func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) err } // CancelRunningJobs cancels all running and waiting jobs associated with a specific workflow. -func CancelRunningJobs(ctx context.Context, repoID int64, ref, workflowID string) error { +func CancelRunningJobs(ctx context.Context, repoID int64, ref, workflowID string, event webhook_module.HookEventType) error { // Find all runs in the specified repository, reference, and workflow with statuses 'Running' or 'Waiting'. runs, total, err := db.FindAndCount[ActionRun](ctx, FindRunOptions{ - RepoID: repoID, - Ref: ref, - WorkflowID: workflowID, - Status: []Status{StatusRunning, StatusWaiting}, + RepoID: repoID, + Ref: ref, + WorkflowID: workflowID, + TriggerEvent: event, + Status: []Status{StatusRunning, StatusWaiting}, }) if err != nil { return err diff --git a/models/actions/run_list.go b/models/actions/run_list.go index 375c46221b043..388bfc4f86f94 100644 --- a/models/actions/run_list.go +++ b/models/actions/run_list.go @@ -10,6 +10,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" + webhook_module "code.gitea.io/gitea/modules/webhook" "xorm.io/builder" ) @@ -71,6 +72,7 @@ type FindRunOptions struct { WorkflowID string Ref string // the commit/tag/… that caused this workflow TriggerUserID int64 + TriggerEvent webhook_module.HookEventType Approved bool // not util.OptionalBool, it works only when it's true Status []Status } @@ -98,6 +100,9 @@ func (opts FindRunOptions) ToConds() builder.Cond { if opts.Ref != "" { cond = cond.And(builder.Eq{"ref": opts.Ref}) } + if opts.TriggerEvent != "" { + cond = cond.And(builder.Eq{"trigger_event": opts.TriggerEvent}) + } return cond } diff --git a/models/actions/schedule.go b/models/actions/schedule.go index 34d23f1c0179c..d450e7aa07291 100644 --- a/models/actions/schedule.go +++ b/models/actions/schedule.go @@ -5,6 +5,7 @@ package actions import ( "context" + "fmt" "time" "code.gitea.io/gitea/models/db" @@ -118,3 +119,22 @@ func DeleteScheduleTaskByRepo(ctx context.Context, id int64) error { return committer.Commit() } + +func CleanRepoScheduleTasks(ctx context.Context, repo *repo_model.Repository) error { + // If actions disabled when there is schedule task, this will remove the outdated schedule tasks + // There is no other place we can do this because the app.ini will be changed manually + if err := DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil { + return fmt.Errorf("DeleteCronTaskByRepo: %v", err) + } + // cancel running cron jobs of this repository and delete old schedules + if err := CancelRunningJobs( + ctx, + repo.ID, + repo.DefaultBranch, + "", + webhook_module.HookEventSchedule, + ); err != nil { + return fmt.Errorf("CancelRunningJobs: %v", err) + } + return nil +} diff --git a/models/activities/action.go b/models/activities/action.go index 15bd9a52acc5c..c9745e4a8ac63 100644 --- a/models/activities/action.go +++ b/models/activities/action.go @@ -446,9 +446,12 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err return nil, 0, err } - sess := db.GetEngine(ctx).Where(cond). - Select("`action`.*"). // this line will avoid select other joined table's columns - Join("INNER", "repository", "`repository`.id = `action`.repo_id") + sess := db.GetEngine(ctx).Where(cond) + if setting.Database.Type.IsMySQL() { + sess = sess.IndexHint("USE", "JOIN", "IDX_action_c_u_d") + } + sess = sess.Select("`action`.*"). // this line will avoid select other joined table's columns + Join("INNER", "repository", "`repository`.id = `action`.repo_id") opts.SetDefaultValues() sess = db.SetSessionPagination(sess, &opts) diff --git a/models/asymkey/gpg_key.go b/models/asymkey/gpg_key.go index 421f24d4de939..5236b2d4500e4 100644 --- a/models/asymkey/gpg_key.go +++ b/models/asymkey/gpg_key.go @@ -11,21 +11,13 @@ import ( "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" "github.com/keybase/go-crypto/openpgp" "github.com/keybase/go-crypto/openpgp/packet" - "xorm.io/xorm" + "xorm.io/builder" ) -// __________________ ________ ____ __. -// / _____/\______ \/ _____/ | |/ _|____ ___.__. -// / \ ___ | ___/ \ ___ | <_/ __ < | | -// \ \_\ \| | \ \_\ \ | | \ ___/\___ | -// \______ /|____| \______ / |____|__ \___ > ____| -// \/ \/ \/ \/\/ - // GPGKey represents a GPG key. type GPGKey struct { ID int64 `xorm:"pk autoincr"` @@ -54,12 +46,11 @@ func (key *GPGKey) BeforeInsert() { key.AddedUnix = timeutil.TimeStampNow() } -// AfterLoad is invoked from XORM after setting the values of all fields of this object. -func (key *GPGKey) AfterLoad(session *xorm.Session) { - err := session.Where("primary_key_id=?", key.KeyID).Find(&key.SubsKey) - if err != nil { - log.Error("Find Sub GPGkeys[%s]: %v", key.KeyID, err) +func (key *GPGKey) LoadSubKeys(ctx context.Context) error { + if err := db.GetEngine(ctx).Where("primary_key_id=?", key.KeyID).Find(&key.SubsKey); err != nil { + return fmt.Errorf("find Sub GPGkeys[%s]: %v", key.KeyID, err) } + return nil } // PaddedKeyID show KeyID padded to 16 characters @@ -76,20 +67,26 @@ func PaddedKeyID(keyID string) string { return zeros[0:16-len(keyID)] + keyID } -// ListGPGKeys returns a list of public keys belongs to given user. -func ListGPGKeys(ctx context.Context, uid int64, listOptions db.ListOptions) ([]*GPGKey, error) { - sess := db.GetEngine(ctx).Table(&GPGKey{}).Where("owner_id=? AND primary_key_id=''", uid) - if listOptions.Page != 0 { - sess = db.SetSessionPagination(sess, &listOptions) - } - - keys := make([]*GPGKey, 0, 2) - return keys, sess.Find(&keys) +type FindGPGKeyOptions struct { + db.ListOptions + OwnerID int64 + KeyID string + IncludeSubKeys bool } -// CountUserGPGKeys return number of gpg keys a user own -func CountUserGPGKeys(ctx context.Context, userID int64) (int64, error) { - return db.GetEngine(ctx).Where("owner_id=? AND primary_key_id=''", userID).Count(&GPGKey{}) +func (opts FindGPGKeyOptions) ToConds() builder.Cond { + cond := builder.NewCond() + if !opts.IncludeSubKeys { + cond = cond.And(builder.Eq{"primary_key_id": ""}) + } + + if opts.OwnerID > 0 { + cond = cond.And(builder.Eq{"owner_id": opts.OwnerID}) + } + if opts.KeyID != "" { + cond = cond.And(builder.Eq{"key_id": opts.KeyID}) + } + return cond } func GetGPGKeyForUserByID(ctx context.Context, ownerID, keyID int64) (*GPGKey, error) { @@ -103,12 +100,6 @@ func GetGPGKeyForUserByID(ctx context.Context, ownerID, keyID int64) (*GPGKey, e return key, nil } -// GetGPGKeysByKeyID returns public key by given ID. -func GetGPGKeysByKeyID(ctx context.Context, keyID string) ([]*GPGKey, error) { - keys := make([]*GPGKey, 0, 1) - return keys, db.GetEngine(ctx).Where("key_id=?", keyID).Find(&keys) -} - // GPGKeyToEntity retrieve the imported key and the traducted entity func GPGKeyToEntity(ctx context.Context, k *GPGKey) (*openpgp.Entity, error) { impKey, err := GetGPGImportByKeyID(ctx, k.KeyID) diff --git a/models/asymkey/gpg_key_commit_verification.go b/models/asymkey/gpg_key_commit_verification.go index 8ac436440453f..83fbab5d36c8a 100644 --- a/models/asymkey/gpg_key_commit_verification.go +++ b/models/asymkey/gpg_key_commit_verification.go @@ -166,7 +166,9 @@ func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerific // Now try to associate the signature with the committer, if present if committer.ID != 0 { - keys, err := ListGPGKeys(ctx, committer.ID, db.ListOptions{}) + keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{ + OwnerID: committer.ID, + }) if err != nil { // Skipping failed to get gpg keys of user log.Error("ListGPGKeys: %v", err) return &CommitVerification{ @@ -176,6 +178,15 @@ func ParseCommitWithSignature(ctx context.Context, c *git.Commit) *CommitVerific } } + if err := GPGKeyList(keys).LoadSubKeys(ctx); err != nil { + log.Error("LoadSubKeys: %v", err) + return &CommitVerification{ + CommittingUser: committer, + Verified: false, + Reason: "gpg.error.failed_retrieval_gpg_keys", + } + } + committerEmailAddresses, _ := user_model.GetEmailAddresses(ctx, committer.ID) activated := false for _, e := range committerEmailAddresses { @@ -392,7 +403,10 @@ func hashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload s if keyID == "" { return nil } - keys, err := GetGPGKeysByKeyID(ctx, keyID) + keys, err := db.Find[GPGKey](ctx, FindGPGKeyOptions{ + KeyID: keyID, + IncludeSubKeys: true, + }) if err != nil { log.Error("GetGPGKeysByKeyID: %v", err) return &CommitVerification{ @@ -407,7 +421,10 @@ func hashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload s for _, key := range keys { var primaryKeys []*GPGKey if key.PrimaryKeyID != "" { - primaryKeys, err = GetGPGKeysByKeyID(ctx, key.PrimaryKeyID) + primaryKeys, err = db.Find[GPGKey](ctx, FindGPGKeyOptions{ + KeyID: key.PrimaryKeyID, + IncludeSubKeys: true, + }) if err != nil { log.Error("GetGPGKeysByKeyID: %v", err) return &CommitVerification{ diff --git a/models/asymkey/gpg_key_list.go b/models/asymkey/gpg_key_list.go new file mode 100644 index 0000000000000..89548e495ec3f --- /dev/null +++ b/models/asymkey/gpg_key_list.go @@ -0,0 +1,38 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package asymkey + +import ( + "context" + + "code.gitea.io/gitea/models/db" +) + +type GPGKeyList []*GPGKey + +func (keys GPGKeyList) keyIDs() []string { + ids := make([]string, len(keys)) + for i, key := range keys { + ids[i] = key.KeyID + } + return ids +} + +func (keys GPGKeyList) LoadSubKeys(ctx context.Context) error { + subKeys := make([]*GPGKey, 0, len(keys)) + if err := db.GetEngine(ctx).In("primary_key_id", keys.keyIDs()).Find(&subKeys); err != nil { + return err + } + subKeysMap := make(map[string][]*GPGKey, len(subKeys)) + for _, key := range subKeys { + subKeysMap[key.PrimaryKeyID] = append(subKeysMap[key.PrimaryKeyID], key) + } + + for _, key := range keys { + if subKeys, ok := subKeysMap[key.KeyID]; ok { + key.SubsKey = subKeys + } + } + return nil +} diff --git a/models/asymkey/ssh_key.go b/models/asymkey/ssh_key.go index 116d6351b0d76..a409d8e841926 100644 --- a/models/asymkey/ssh_key.go +++ b/models/asymkey/ssh_key.go @@ -197,10 +197,10 @@ func (opts FindPublicKeyOptions) ToConds() builder.Cond { cond = cond.And(builder.Eq{"fingerprint": opts.Fingerprint}) } if len(opts.KeyTypes) > 0 { - cond = cond.And(builder.In("type", opts.KeyTypes)) + cond = cond.And(builder.In("`type`", opts.KeyTypes)) } if opts.NotKeytype > 0 { - cond = cond.And(builder.Neq{"type": opts.NotKeytype}) + cond = cond.And(builder.Neq{"`type`": opts.NotKeytype}) } if opts.LoginSourceID > 0 { cond = cond.And(builder.Eq{"login_source_id": opts.LoginSourceID}) diff --git a/models/asymkey/ssh_key_principals.go b/models/asymkey/ssh_key_principals.go index 92789e26f8679..4e7dee2c91d06 100644 --- a/models/asymkey/ssh_key_principals.go +++ b/models/asymkey/ssh_key_principals.go @@ -15,15 +15,6 @@ import ( "code.gitea.io/gitea/modules/util" ) -// __________ .__ .__ .__ -// \______ _______|__| ____ ____ |_____________ | | ______ -// | ___\_ __ | |/ \_/ ___\| \____ \__ \ | | / ___/ -// | | | | \| | | \ \___| | |_> / __ \| |__\___ \ -// |____| |__| |__|___| /\___ |__| __(____ |____/____ > -// \/ \/ |__| \/ \/ -// -// This file contains functions related to principals - // AddPrincipalKey adds new principal to database and authorized_principals file. func AddPrincipalKey(ctx context.Context, ownerID int64, content string, authSourceID int64) (*PublicKey, error) { dbCtx, committer, err := db.TxContext(ctx) @@ -103,17 +94,3 @@ func CheckPrincipalKeyString(ctx context.Context, user *user_model.User, content return "", fmt.Errorf("didn't match allowed principals: %s", setting.SSH.AuthorizedPrincipalsAllow) } - -// ListPrincipalKeys returns a list of principals belongs to given user. -func ListPrincipalKeys(ctx context.Context, uid int64, listOptions db.ListOptions) ([]*PublicKey, error) { - sess := db.GetEngine(ctx).Where("owner_id = ? AND type = ?", uid, KeyTypePrincipal) - if listOptions.Page != 0 { - sess = db.SetSessionPagination(sess, &listOptions) - - keys := make([]*PublicKey, 0, listOptions.PageSize) - return keys, sess.Find(&keys) - } - - keys := make([]*PublicKey, 0, 5) - return keys, sess.Find(&keys) -} diff --git a/models/auth/webauthn.go b/models/auth/webauthn.go index d12713bd37c89..a65d2e1e343db 100644 --- a/models/auth/webauthn.go +++ b/models/auth/webauthn.go @@ -13,7 +13,6 @@ import ( "code.gitea.io/gitea/modules/util" "github.com/go-webauthn/webauthn/webauthn" - "xorm.io/xorm" ) // ErrWebAuthnCredentialNotExist represents a "ErrWebAuthnCRedentialNotExist" kind of error. @@ -83,7 +82,7 @@ func (cred *WebAuthnCredential) BeforeUpdate() { } // AfterLoad is invoked from XORM after setting the values of all fields of this object. -func (cred *WebAuthnCredential) AfterLoad(session *xorm.Session) { +func (cred *WebAuthnCredential) AfterLoad() { cred.LowerName = strings.ToLower(cred.Name) } diff --git a/models/db/collation.go b/models/db/collation.go new file mode 100644 index 0000000000000..2f5ff2bf05489 --- /dev/null +++ b/models/db/collation.go @@ -0,0 +1,191 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package db + +import ( + "errors" + "fmt" + "strings" + + "code.gitea.io/gitea/modules/container" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + + "xorm.io/xorm" + "xorm.io/xorm/schemas" +) + +type CheckCollationsResult struct { + ExpectedCollation string + AvailableCollation container.Set[string] + DatabaseCollation string + IsCollationCaseSensitive func(s string) bool + CollationEquals func(a, b string) bool + ExistingTableNumber int + + InconsistentCollationColumns []string +} + +func findAvailableCollationsMySQL(x *xorm.Engine) (ret container.Set[string], err error) { + var res []struct { + Collation string + } + if err = x.SQL("SHOW COLLATION WHERE (Collation = 'utf8mb4_bin') OR (Collation LIKE '%\\_as\\_cs%')").Find(&res); err != nil { + return nil, err + } + ret = make(container.Set[string], len(res)) + for _, r := range res { + ret.Add(r.Collation) + } + return ret, nil +} + +func findAvailableCollationsMSSQL(x *xorm.Engine) (ret container.Set[string], err error) { + var res []struct { + Name string + } + if err = x.SQL("SELECT * FROM sys.fn_helpcollations() WHERE name LIKE '%[_]CS[_]AS%'").Find(&res); err != nil { + return nil, err + } + ret = make(container.Set[string], len(res)) + for _, r := range res { + ret.Add(r.Name) + } + return ret, nil +} + +func CheckCollations(x *xorm.Engine) (*CheckCollationsResult, error) { + dbTables, err := x.DBMetas() + if err != nil { + return nil, err + } + + res := &CheckCollationsResult{ + ExistingTableNumber: len(dbTables), + CollationEquals: func(a, b string) bool { return a == b }, + } + + var candidateCollations []string + if x.Dialect().URI().DBType == schemas.MYSQL { + if _, err = x.SQL("SELECT @@collation_database").Get(&res.DatabaseCollation); err != nil { + return nil, err + } + res.IsCollationCaseSensitive = func(s string) bool { + return s == "utf8mb4_bin" || strings.HasSuffix(s, "_as_cs") + } + candidateCollations = []string{"utf8mb4_0900_as_cs", "uca1400_as_cs", "utf8mb4_bin"} + res.AvailableCollation, err = findAvailableCollationsMySQL(x) + if err != nil { + return nil, err + } + res.CollationEquals = func(a, b string) bool { + // MariaDB adds the "utf8mb4_" prefix, eg: "utf8mb4_uca1400_as_cs", but not the name "uca1400_as_cs" in "SHOW COLLATION" + // At the moment, it's safe to ignore the database difference, just trim the prefix and compare. It could be fixed easily if there is any problem in the future. + return a == b || strings.TrimPrefix(a, "utf8mb4_") == strings.TrimPrefix(b, "utf8mb4_") + } + } else if x.Dialect().URI().DBType == schemas.MSSQL { + if _, err = x.SQL("SELECT DATABASEPROPERTYEX(DB_NAME(), 'Collation')").Get(&res.DatabaseCollation); err != nil { + return nil, err + } + res.IsCollationCaseSensitive = func(s string) bool { + return strings.HasSuffix(s, "_CS_AS") + } + candidateCollations = []string{"Latin1_General_CS_AS"} + res.AvailableCollation, err = findAvailableCollationsMSSQL(x) + if err != nil { + return nil, err + } + } else { + return nil, nil + } + + if res.DatabaseCollation == "" { + return nil, errors.New("unable to get collation for current database") + } + + res.ExpectedCollation = setting.Database.CharsetCollation + if res.ExpectedCollation == "" { + for _, collation := range candidateCollations { + if res.AvailableCollation.Contains(collation) { + res.ExpectedCollation = collation + break + } + } + } + + if res.ExpectedCollation == "" { + return nil, errors.New("unable to find a suitable collation for current database") + } + + allColumnsMatchExpected := true + allColumnsMatchDatabase := true + for _, table := range dbTables { + for _, col := range table.Columns() { + if col.Collation != "" { + allColumnsMatchExpected = allColumnsMatchExpected && res.CollationEquals(col.Collation, res.ExpectedCollation) + allColumnsMatchDatabase = allColumnsMatchDatabase && res.CollationEquals(col.Collation, res.DatabaseCollation) + if !res.IsCollationCaseSensitive(col.Collation) || !res.CollationEquals(col.Collation, res.DatabaseCollation) { + res.InconsistentCollationColumns = append(res.InconsistentCollationColumns, fmt.Sprintf("%s.%s", table.Name, col.Name)) + } + } + } + } + // if all columns match expected collation or all match database collation, then it could also be considered as "consistent" + if allColumnsMatchExpected || allColumnsMatchDatabase { + res.InconsistentCollationColumns = nil + } + return res, nil +} + +func CheckCollationsDefaultEngine() (*CheckCollationsResult, error) { + return CheckCollations(x) +} + +func alterDatabaseCollation(x *xorm.Engine, collation string) error { + if x.Dialect().URI().DBType == schemas.MYSQL { + _, err := x.Exec("ALTER DATABASE CHARACTER SET utf8mb4 COLLATE " + collation) + return err + } else if x.Dialect().URI().DBType == schemas.MSSQL { + // TODO: MSSQL has many limitations on changing database collation, it could fail in many cases. + _, err := x.Exec("ALTER DATABASE CURRENT COLLATE " + collation) + return err + } + return errors.New("unsupported database type") +} + +// preprocessDatabaseCollation checks database & table column collation, and alter the database collation if needed +func preprocessDatabaseCollation(x *xorm.Engine) { + r, err := CheckCollations(x) + if err != nil { + log.Error("Failed to check database collation: %v", err) + } + if r == nil { + return // no check result means the database doesn't need to do such check/process (at the moment ....) + } + + // try to alter database collation to expected if the database is empty, it might fail in some cases (and it isn't necessary to succeed) + // at the moment, there is no "altering" solution for MSSQL, site admin should manually change the database collation + // and there is a bug https://github.com/go-testfixtures/testfixtures/pull/182 mssql: Invalid object name 'information_schema.tables'. + if !r.CollationEquals(r.DatabaseCollation, r.ExpectedCollation) && r.ExistingTableNumber == 0 && x.Dialect().URI().DBType == schemas.MYSQL { + if err = alterDatabaseCollation(x, r.ExpectedCollation); err != nil { + log.Error("Failed to change database collation to %q: %v", r.ExpectedCollation, err) + } else { + _, _ = x.Exec("SELECT 1") // after "altering", MSSQL's session becomes invalid, so make a simple query to "refresh" the session + if r, err = CheckCollations(x); err != nil { + log.Error("Failed to check database collation again after altering: %v", err) // impossible case + return + } + log.Warn("Current database has been altered to use collation %q", r.DatabaseCollation) + } + } + + // check column collation, and show warning/error to end users -- no need to fatal, do not block the startup + if !r.IsCollationCaseSensitive(r.DatabaseCollation) { + log.Warn("Current database is using a case-insensitive collation %q, although Gitea could work with it, there might be some rare cases which don't work as expected.", r.DatabaseCollation) + } + + if len(r.InconsistentCollationColumns) > 0 { + log.Error("There are %d table columns using inconsistent collation, they should use %q. Please go to admin panel Self Check page", len(r.InconsistentCollationColumns), r.DatabaseCollation) + } +} diff --git a/models/db/convert.go b/models/db/convert.go index 112c8575ca2c7..8c124471aba25 100644 --- a/models/db/convert.go +++ b/models/db/convert.go @@ -14,13 +14,18 @@ import ( "xorm.io/xorm/schemas" ) -// ConvertUtf8ToUtf8mb4 converts database and tables from utf8 to utf8mb4 if it's mysql and set ROW_FORMAT=dynamic -func ConvertUtf8ToUtf8mb4() error { +// ConvertDatabaseTable converts database and tables from utf8 to utf8mb4 if it's mysql and set ROW_FORMAT=dynamic +func ConvertDatabaseTable() error { if x.Dialect().URI().DBType != schemas.MYSQL { return nil } - _, err := x.Exec(fmt.Sprintf("ALTER DATABASE `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci", setting.Database.Name)) + r, err := CheckCollations(x) + if err != nil { + return err + } + + _, err = x.Exec(fmt.Sprintf("ALTER DATABASE `%s` CHARACTER SET utf8mb4 COLLATE %s", setting.Database.Name, r.ExpectedCollation)) if err != nil { return err } @@ -30,11 +35,11 @@ func ConvertUtf8ToUtf8mb4() error { return err } for _, table := range tables { - if _, err := x.Exec(fmt.Sprintf("ALTER TABLE `%s` ROW_FORMAT=dynamic;", table.Name)); err != nil { + if _, err := x.Exec(fmt.Sprintf("ALTER TABLE `%s` ROW_FORMAT=dynamic", table.Name)); err != nil { return err } - if _, err := x.Exec(fmt.Sprintf("ALTER TABLE `%s` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;", table.Name)); err != nil { + if _, err := x.Exec(fmt.Sprintf("ALTER TABLE `%s` CONVERT TO CHARACTER SET utf8mb4 COLLATE %s", table.Name, r.ExpectedCollation)); err != nil { return err } } diff --git a/models/db/engine.go b/models/db/engine.go index 99906813ca870..2cd1c36c580b8 100755 --- a/models/db/engine.go +++ b/models/db/engine.go @@ -182,6 +182,8 @@ func InitEngineWithMigration(ctx context.Context, migrateFunc func(*xorm.Engine) return err } + preprocessDatabaseCollation(x) + // We have to run migrateFunc here in case the user is re-running installation on a previously created DB. // If we do not then table schemas will be changed and there will be conflicts when the migrations run properly. // diff --git a/models/db/list.go b/models/db/list.go index b2f932e89bf8e..4aeaf3e084508 100644 --- a/models/db/list.go +++ b/models/db/list.go @@ -21,17 +21,9 @@ const ( // Paginator is the base for different ListOptions types type Paginator interface { GetSkipTake() (skip, take int) - GetStartEnd() (start, end int) IsListAll() bool } -// GetPaginatedSession creates a paginated database session -func GetPaginatedSession(p Paginator) *xorm.Session { - skip, take := p.GetSkipTake() - - return x.Limit(take, skip) -} - // SetSessionPagination sets pagination for a database session func SetSessionPagination(sess Engine, p Paginator) *xorm.Session { skip, take := p.GetSkipTake() @@ -39,13 +31,6 @@ func SetSessionPagination(sess Engine, p Paginator) *xorm.Session { return sess.Limit(take, skip) } -// SetEnginePagination sets pagination for a database engine -func SetEnginePagination(e Engine, p Paginator) Engine { - skip, take := p.GetSkipTake() - - return e.Limit(take, skip) -} - // ListOptions options to paginate results type ListOptions struct { PageSize int @@ -66,13 +51,6 @@ func (opts *ListOptions) GetSkipTake() (skip, take int) { return (opts.Page - 1) * opts.PageSize, opts.PageSize } -// GetStartEnd returns the start and end of the ListOptions -func (opts *ListOptions) GetStartEnd() (start, end int) { - start, take := opts.GetSkipTake() - end = start + take - return start, end -} - func (opts ListOptions) GetPage() int { return opts.Page } @@ -135,11 +113,6 @@ func (opts *AbsoluteListOptions) GetSkipTake() (skip, take int) { return opts.skip, opts.take } -// GetStartEnd returns the start and end values -func (opts *AbsoluteListOptions) GetStartEnd() (start, end int) { - return opts.skip, opts.skip + opts.take -} - // FindOptions represents a find options type FindOptions interface { GetPage() int @@ -148,15 +121,34 @@ type FindOptions interface { ToConds() builder.Cond } +type JoinFunc func(sess Engine) error + +type FindOptionsJoin interface { + ToJoins() []JoinFunc +} + type FindOptionsOrder interface { ToOrders() string } // Find represents a common find function which accept an options interface func Find[T any](ctx context.Context, opts FindOptions) ([]*T, error) { - sess := GetEngine(ctx).Where(opts.ToConds()) + sess := GetEngine(ctx) + + if joinOpt, ok := opts.(FindOptionsJoin); ok && len(joinOpt.ToJoins()) > 0 { + for _, joinFunc := range joinOpt.ToJoins() { + if err := joinFunc(sess); err != nil { + return nil, err + } + } + } + + sess = sess.Where(opts.ToConds()) page, pageSize := opts.GetPage(), opts.GetPageSize() - if !opts.IsListAll() && pageSize > 0 && page >= 1 { + if !opts.IsListAll() && pageSize > 0 { + if page == 0 { + page = 1 + } sess.Limit(pageSize, (page-1)*pageSize) } if newOpt, ok := opts.(FindOptionsOrder); ok && newOpt.ToOrders() != "" { @@ -176,8 +168,17 @@ func Find[T any](ctx context.Context, opts FindOptions) ([]*T, error) { // Count represents a common count function which accept an options interface func Count[T any](ctx context.Context, opts FindOptions) (int64, error) { + sess := GetEngine(ctx) + if joinOpt, ok := opts.(FindOptionsJoin); ok && len(joinOpt.ToJoins()) > 0 { + for _, joinFunc := range joinOpt.ToJoins() { + if err := joinFunc(sess); err != nil { + return 0, err + } + } + } + var object T - return GetEngine(ctx).Where(opts.ToConds()).Count(&object) + return sess.Where(opts.ToConds()).Count(&object) } // FindAndCount represents a common findandcount function which accept an options interface diff --git a/models/db/paginator/paginator_test.go b/models/db/paginator/paginator_test.go index a1117fc7a4455..20602212d9876 100644 --- a/models/db/paginator/paginator_test.go +++ b/models/db/paginator/paginator_test.go @@ -52,11 +52,8 @@ func TestPaginator(t *testing.T) { for _, c := range cases { skip, take := c.Paginator.GetSkipTake() - start, end := c.Paginator.GetStartEnd() assert.Equal(t, c.Skip, skip) assert.Equal(t, c.Take, take) - assert.Equal(t, c.Start, start) - assert.Equal(t, c.End, end) } } diff --git a/models/error.go b/models/error.go index b7bb967b739fb..83dfe29805774 100644 --- a/models/error.go +++ b/models/error.go @@ -57,6 +57,21 @@ func (err ErrUserOwnPackages) Error() string { return fmt.Sprintf("user still has ownership of packages [uid: %d]", err.UID) } +// ErrDeleteLastAdminUser represents a "DeleteLastAdminUser" kind of error. +type ErrDeleteLastAdminUser struct { + UID int64 +} + +// IsErrDeleteLastAdminUser checks if an error is a ErrDeleteLastAdminUser. +func IsErrDeleteLastAdminUser(err error) bool { + _, ok := err.(ErrDeleteLastAdminUser) + return ok +} + +func (err ErrDeleteLastAdminUser) Error() string { + return fmt.Sprintf("can not delete the last admin user [uid: %d]", err.UID) +} + // ErrNoPendingRepoTransfer is an error type for repositories without a pending // transfer request type ErrNoPendingRepoTransfer struct { diff --git a/models/git/branch.go b/models/git/branch.go index ffd1d7ed164a0..db02fc9b28bbd 100644 --- a/models/git/branch.go +++ b/models/git/branch.go @@ -283,7 +283,7 @@ func FindRenamedBranch(ctx context.Context, repoID int64, from string) (branch * } // RenameBranch rename a branch -func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(isDefault bool) error) (err error) { +func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to string, gitAction func(ctx context.Context, isDefault bool) error) (err error) { ctx, committer, err := db.TxContext(ctx) if err != nil { return err @@ -358,7 +358,7 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str } // 5. do git action - if err = gitAction(isDefault); err != nil { + if err = gitAction(ctx, isDefault); err != nil { return err } diff --git a/models/git/branch_test.go b/models/git/branch_test.go index d480e2ec30c22..fd5d6519e919d 100644 --- a/models/git/branch_test.go +++ b/models/git/branch_test.go @@ -4,6 +4,7 @@ package git_test import ( + "context" "testing" "code.gitea.io/gitea/models/db" @@ -132,7 +133,7 @@ func TestRenameBranch(t *testing.T) { }, git_model.WhitelistOptions{})) assert.NoError(t, committer.Commit()) - assert.NoError(t, git_model.RenameBranch(db.DefaultContext, repo1, "master", "main", func(isDefault bool) error { + assert.NoError(t, git_model.RenameBranch(db.DefaultContext, repo1, "master", "main", func(ctx context.Context, isDefault bool) error { _isDefault = isDefault return nil })) diff --git a/models/git/protected_branch.go b/models/git/protected_branch.go index 66a4b52b1790e..e0ff4d1542dec 100644 --- a/models/git/protected_branch.go +++ b/models/git/protected_branch.go @@ -54,6 +54,7 @@ type ProtectedBranch struct { BlockOnOfficialReviewRequests bool `xorm:"NOT NULL DEFAULT false"` BlockOnOutdatedBranch bool `xorm:"NOT NULL DEFAULT false"` DismissStaleApprovals bool `xorm:"NOT NULL DEFAULT false"` + IgnoreStaleApprovals bool `xorm:"NOT NULL DEFAULT false"` RequireSignedCommits bool `xorm:"NOT NULL DEFAULT false"` ProtectedFilePatterns string `xorm:"TEXT"` UnprotectedFilePatterns string `xorm:"TEXT"` diff --git a/models/issues/comment.go b/models/issues/comment.go index 7b068d49831ac..a1698d48246a0 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -28,7 +28,6 @@ import ( "code.gitea.io/gitea/modules/util" "xorm.io/builder" - "xorm.io/xorm" ) // ErrCommentNotExist represents a "CommentNotExist" kind of error. @@ -338,7 +337,7 @@ func (c *Comment) BeforeUpdate() { } // AfterLoad is invoked from XORM after setting the values of all fields of this object. -func (c *Comment) AfterLoad(session *xorm.Session) { +func (c *Comment) AfterLoad() { c.Patch = c.PatchQuoted if len(c.PatchQuoted) > 0 && c.PatchQuoted[0] == '"' { unquoted, err := strconv.Unquote(c.PatchQuoted) diff --git a/models/issues/comment_code.go b/models/issues/comment_code.go index 25e606f0924a6..384a595dd90dc 100644 --- a/models/issues/comment_code.go +++ b/models/issues/comment_code.go @@ -109,9 +109,11 @@ func findCodeComments(ctx context.Context, opts FindCommentsOptions, issue *Issu var err error if comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{ - Ctx: ctx, - URLPrefix: issue.Repo.Link(), - Metas: issue.Repo.ComposeMetas(ctx), + Ctx: ctx, + Links: markup.Links{ + Base: issue.Repo.Link(), + }, + Metas: issue.Repo.ComposeMetas(ctx), }, comment.Content); err != nil { return nil, err } diff --git a/models/issues/pull.go b/models/issues/pull.go index 34bea921a0d26..614ee9a87ceaf 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -801,7 +801,7 @@ func GetGrantedApprovalsCount(ctx context.Context, protectBranch *git_model.Prot And("type = ?", ReviewTypeApprove). And("official = ?", true). And("dismissed = ?", false) - if protectBranch.DismissStaleApprovals { + if protectBranch.IgnoreStaleApprovals { sess = sess.And("stale = ?", false) } approvals, err := sess.Count(new(Review)) diff --git a/models/issues/tracked_time.go b/models/issues/tracked_time.go index 795bddeb347b8..884a445d265cb 100644 --- a/models/issues/tracked_time.go +++ b/models/issues/tracked_time.go @@ -94,7 +94,7 @@ type FindTrackedTimesOptions struct { } // toCond will convert each condition into a xorm-Cond -func (opts *FindTrackedTimesOptions) toCond() builder.Cond { +func (opts *FindTrackedTimesOptions) ToConds() builder.Cond { cond := builder.NewCond().And(builder.Eq{"tracked_time.deleted": false}) if opts.IssueID != 0 { cond = cond.And(builder.Eq{"issue_id": opts.IssueID}) @@ -117,6 +117,18 @@ func (opts *FindTrackedTimesOptions) toCond() builder.Cond { return cond } +func (opts *FindTrackedTimesOptions) ToJoins() []db.JoinFunc { + if opts.RepositoryID > 0 || opts.MilestoneID > 0 { + return []db.JoinFunc{ + func(e db.Engine) error { + e.Join("INNER", "issue", "issue.id = tracked_time.issue_id") + return nil + }, + } + } + return nil +} + // toSession will convert the given options to a xorm Session by using the conditions from toCond and joining with issue table if required func (opts *FindTrackedTimesOptions) toSession(e db.Engine) db.Engine { sess := e @@ -124,10 +136,10 @@ func (opts *FindTrackedTimesOptions) toSession(e db.Engine) db.Engine { sess = e.Join("INNER", "issue", "issue.id = tracked_time.issue_id") } - sess = sess.Where(opts.toCond()) + sess = sess.Where(opts.ToConds()) if opts.Page != 0 { - sess = db.SetEnginePagination(sess, opts) + sess = db.SetSessionPagination(sess, opts) } return sess @@ -141,7 +153,7 @@ func GetTrackedTimes(ctx context.Context, options *FindTrackedTimesOptions) (tra // CountTrackedTimes returns count of tracked times that fit to the given options. func CountTrackedTimes(ctx context.Context, opts *FindTrackedTimesOptions) (int64, error) { - sess := db.GetEngine(ctx).Where(opts.toCond()) + sess := db.GetEngine(ctx).Where(opts.ToConds()) if opts.RepositoryID > 0 || opts.MilestoneID > 0 { sess = sess.Join("INNER", "issue", "issue.id = tracked_time.issue_id") } diff --git a/models/migrations/v1_22/v284.go b/models/migrations/v1_22/v284.go new file mode 100644 index 0000000000000..1a4c786964909 --- /dev/null +++ b/models/migrations/v1_22/v284.go @@ -0,0 +1,14 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_22 //nolint +import ( + "xorm.io/xorm" +) + +func AddIgnoreStaleApprovalsColumnToProtectedBranchTable(x *xorm.Engine) error { + type ProtectedBranch struct { + IgnoreStaleApprovals bool `xorm:"NOT NULL DEFAULT false"` + } + return x.Sync(new(ProtectedBranch)) +} diff --git a/models/repo/archiver.go b/models/repo/archiver.go index 1fccb29499461..d9520c670cb4f 100644 --- a/models/repo/archiver.go +++ b/models/repo/archiver.go @@ -111,7 +111,7 @@ type FindRepoArchiversOption struct { OlderThan time.Duration } -func (opts FindRepoArchiversOption) toConds() builder.Cond { +func (opts FindRepoArchiversOption) ToConds() builder.Cond { cond := builder.NewCond() if opts.OlderThan > 0 { cond = cond.And(builder.Lt{"created_unix": time.Now().Add(-opts.OlderThan).Unix()}) @@ -119,15 +119,8 @@ func (opts FindRepoArchiversOption) toConds() builder.Cond { return cond } -// FindRepoArchives find repo archivers -func FindRepoArchives(ctx context.Context, opts FindRepoArchiversOption) ([]*RepoArchiver, error) { - archivers := make([]*RepoArchiver, 0, opts.PageSize) - start, limit := opts.GetSkipTake() - err := db.GetEngine(ctx).Where(opts.toConds()). - Asc("created_unix"). - Limit(limit, start). - Find(&archivers) - return archivers, err +func (opts FindRepoArchiversOption) ToOrders() string { + return "created_unix ASC" } // SetArchiveRepoState sets if a repo is archived diff --git a/models/repo/collaboration.go b/models/repo/collaboration.go index 2018ae2a7db15..7288082614738 100644 --- a/models/repo/collaboration.go +++ b/models/repo/collaboration.go @@ -11,8 +11,9 @@ import ( "code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/timeutil" + + "xorm.io/builder" ) // Collaboration represent the relation between an individual and a repository. @@ -37,35 +38,38 @@ type Collaborator struct { // GetCollaborators returns the collaborators for a repository func GetCollaborators(ctx context.Context, repoID int64, listOptions db.ListOptions) ([]*Collaborator, error) { - collaborations, err := getCollaborations(ctx, repoID, listOptions) + collaborations, err := db.Find[Collaboration](ctx, FindCollaborationOptions{ + ListOptions: listOptions, + RepoID: repoID, + }) if err != nil { - return nil, fmt.Errorf("getCollaborations: %w", err) + return nil, fmt.Errorf("db.Find[Collaboration]: %w", err) } collaborators := make([]*Collaborator, 0, len(collaborations)) + userIDs := make([]int64, 0, len(collaborations)) for _, c := range collaborations { - user, err := user_model.GetUserByID(ctx, c.UserID) - if err != nil { - if user_model.IsErrUserNotExist(err) { - log.Warn("Inconsistent DB: User: %d is listed as collaborator of %-v but does not exist", c.UserID, repoID) - user = user_model.NewGhostUser() - } else { - return nil, err - } + userIDs = append(userIDs, c.UserID) + } + + usersMap := make(map[int64]*user_model.User) + if err := db.GetEngine(ctx).In("id", userIDs).Find(&usersMap); err != nil { + return nil, fmt.Errorf("Find users map by user ids: %w", err) + } + + for _, c := range collaborations { + u := usersMap[c.UserID] + if u == nil { + u = user_model.NewGhostUser() } collaborators = append(collaborators, &Collaborator{ - User: user, + User: u, Collaboration: c, }) } return collaborators, nil } -// CountCollaborators returns total number of collaborators for a repository -func CountCollaborators(ctx context.Context, repoID int64) (int64, error) { - return db.GetEngine(ctx).Where("repo_id = ? ", repoID).Count(&Collaboration{}) -} - // GetCollaboration get collaboration for a repository id with a user id func GetCollaboration(ctx context.Context, repoID, uid int64) (*Collaboration, error) { collaboration := &Collaboration{ @@ -84,18 +88,13 @@ func IsCollaborator(ctx context.Context, repoID, userID int64) (bool, error) { return db.GetEngine(ctx).Get(&Collaboration{RepoID: repoID, UserID: userID}) } -func getCollaborations(ctx context.Context, repoID int64, listOptions db.ListOptions) ([]*Collaboration, error) { - if listOptions.Page == 0 { - collaborations := make([]*Collaboration, 0, 8) - return collaborations, db.GetEngine(ctx).Find(&collaborations, &Collaboration{RepoID: repoID}) - } - - e := db.GetEngine(ctx) - - e = db.SetEnginePagination(e, &listOptions) +type FindCollaborationOptions struct { + db.ListOptions + RepoID int64 +} - collaborations := make([]*Collaboration, 0, listOptions.PageSize) - return collaborations, e.Find(&collaborations, &Collaboration{RepoID: repoID}) +func (opts FindCollaborationOptions) ToConds() builder.Cond { + return builder.And(builder.Eq{"repo_id": opts.RepoID}) } // ChangeCollaborationAccessMode sets new access mode for the collaboration. diff --git a/models/repo/collaboration_test.go b/models/repo/collaboration_test.go index 38114c307f6e1..21a99dd5573e6 100644 --- a/models/repo/collaboration_test.go +++ b/models/repo/collaboration_test.go @@ -89,17 +89,23 @@ func TestRepository_CountCollaborators(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - count, err := repo_model.CountCollaborators(db.DefaultContext, repo1.ID) + count, err := db.Count[repo_model.Collaboration](db.DefaultContext, repo_model.FindCollaborationOptions{ + RepoID: repo1.ID, + }) assert.NoError(t, err) assert.EqualValues(t, 2, count) repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 22}) - count, err = repo_model.CountCollaborators(db.DefaultContext, repo2.ID) + count, err = db.Count[repo_model.Collaboration](db.DefaultContext, repo_model.FindCollaborationOptions{ + RepoID: repo2.ID, + }) assert.NoError(t, err) assert.EqualValues(t, 2, count) // Non-existent repository. - count, err = repo_model.CountCollaborators(db.DefaultContext, unittest.NonexistentID) + count, err = db.Count[repo_model.Collaboration](db.DefaultContext, repo_model.FindCollaborationOptions{ + RepoID: unittest.NonexistentID, + }) assert.NoError(t, err) assert.EqualValues(t, 0, count) } diff --git a/models/repo/fork.go b/models/repo/fork.go index 6be6ebc3f523c..07cd31c2690a9 100644 --- a/models/repo/fork.go +++ b/models/repo/fork.go @@ -56,13 +56,16 @@ func GetUserFork(ctx context.Context, repoID, userID int64) (*Repository, error) // GetForks returns all the forks of the repository func GetForks(ctx context.Context, repo *Repository, listOptions db.ListOptions) ([]*Repository, error) { + sess := db.GetEngine(ctx) + + var forks []*Repository if listOptions.Page == 0 { - forks := make([]*Repository, 0, repo.NumForks) - return forks, db.GetEngine(ctx).Find(&forks, &Repository{ForkID: repo.ID}) + forks = make([]*Repository, 0, repo.NumForks) + } else { + forks = make([]*Repository, 0, listOptions.PageSize) + sess = db.SetSessionPagination(sess, &listOptions) } - sess := db.GetPaginatedSession(&listOptions) - forks := make([]*Repository, 0, listOptions.PageSize) return forks, sess.Find(&forks, &Repository{ForkID: repo.ID}) } diff --git a/models/repo/release.go b/models/repo/release.go index 4514a034ed9af..72a73f8e80929 100644 --- a/models/repo/release.go +++ b/models/repo/release.go @@ -225,6 +225,7 @@ func GetReleaseForRepoByID(ctx context.Context, repoID, id int64) (*Release, err // FindReleasesOptions describes the conditions to Find releases type FindReleasesOptions struct { db.ListOptions + RepoID int64 IncludeDrafts bool IncludeTags bool IsPreRelease util.OptionalBool @@ -233,9 +234,8 @@ type FindReleasesOptions struct { HasSha1 util.OptionalBool // useful to find draft releases which are created with existing tags } -func (opts *FindReleasesOptions) toConds(repoID int64) builder.Cond { - cond := builder.NewCond() - cond = cond.And(builder.Eq{"repo_id": repoID}) +func (opts FindReleasesOptions) ToConds() builder.Cond { + var cond builder.Cond = builder.Eq{"repo_id": opts.RepoID} if !opts.IncludeDrafts { cond = cond.And(builder.Eq{"is_draft": false}) @@ -262,18 +262,8 @@ func (opts *FindReleasesOptions) toConds(repoID int64) builder.Cond { return cond } -// GetReleasesByRepoID returns a list of releases of repository. -func GetReleasesByRepoID(ctx context.Context, repoID int64, opts FindReleasesOptions) ([]*Release, error) { - sess := db.GetEngine(ctx). - Desc("created_unix", "id"). - Where(opts.toConds(repoID)) - - if opts.PageSize != 0 { - sess = db.SetSessionPagination(sess, &opts.ListOptions) - } - - rels := make([]*Release, 0, opts.PageSize) - return rels, sess.Find(&rels) +func (opts FindReleasesOptions) ToOrders() string { + return "created_unix DESC, id DESC" } // GetTagNamesByRepoID returns a list of release tag names of repository. @@ -286,23 +276,19 @@ func GetTagNamesByRepoID(ctx context.Context, repoID int64) ([]string, error) { IncludeDrafts: true, IncludeTags: true, HasSha1: util.OptionalBoolTrue, + RepoID: repoID, } tags := make([]string, 0) sess := db.GetEngine(ctx). Table("release"). Desc("created_unix", "id"). - Where(opts.toConds(repoID)). + Where(opts.ToConds()). Cols("tag_name") return tags, sess.Find(&tags) } -// CountReleasesByRepoID returns a number of releases matching FindReleaseOptions and RepoID. -func CountReleasesByRepoID(ctx context.Context, repoID int64, opts FindReleasesOptions) (int64, error) { - return db.GetEngine(ctx).Where(opts.toConds(repoID)).Count(new(Release)) -} - // GetLatestReleaseByRepoID returns the latest release for a repository func GetLatestReleaseByRepoID(ctx context.Context, repoID int64) (*Release, error) { cond := builder.NewCond(). @@ -325,20 +311,6 @@ func GetLatestReleaseByRepoID(ctx context.Context, repoID int64) (*Release, erro return rel, nil } -// GetReleasesByRepoIDAndNames returns a list of releases of repository according repoID and tagNames. -func GetReleasesByRepoIDAndNames(ctx context.Context, repoID int64, tagNames []string) (rels []*Release, err error) { - err = db.GetEngine(ctx). - In("tag_name", tagNames). - Desc("created_unix"). - Find(&rels, Release{RepoID: repoID}) - return rels, err -} - -// GetReleaseCountByRepoID returns the count of releases of repository -func GetReleaseCountByRepoID(ctx context.Context, repoID int64, opts FindReleasesOptions) (int64, error) { - return db.GetEngine(ctx).Where(opts.toConds(repoID)).Count(&Release{}) -} - type releaseMetaSearch struct { ID []int64 Rel []*Release diff --git a/models/repo/repo.go b/models/repo/repo.go index fb1849a4bb7fa..3695e1f78b466 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -584,8 +584,7 @@ func (repo *Repository) CanEnableEditor() bool { // DescriptionHTML does special handles to description and return HTML string. func (repo *Repository) DescriptionHTML(ctx context.Context) template.HTML { desc, err := markup.RenderDescriptionHTML(&markup.RenderContext{ - Ctx: ctx, - URLPrefix: repo.HTMLURL(), + Ctx: ctx, // Don't use Metas to speedup requests }, repo.Description) if err != nil { diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go index 89f28b8fcaee4..8a3ba1ee89092 100644 --- a/models/repo/repo_unit.go +++ b/models/repo/repo_unit.go @@ -283,29 +283,3 @@ func UpdateRepoUnit(ctx context.Context, unit *RepoUnit) error { _, err := db.GetEngine(ctx).ID(unit.ID).Update(unit) return err } - -// UpdateRepositoryUnits updates a repository's units -func UpdateRepositoryUnits(ctx context.Context, repo *Repository, units []RepoUnit, deleteUnitTypes []unit.Type) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - // Delete existing settings of units before adding again - for _, u := range units { - deleteUnitTypes = append(deleteUnitTypes, u.Type) - } - - if _, err = db.GetEngine(ctx).Where("repo_id = ?", repo.ID).In("type", deleteUnitTypes).Delete(new(RepoUnit)); err != nil { - return err - } - - if len(units) > 0 { - if err = db.Insert(ctx, units); err != nil { - return err - } - } - - return committer.Commit() -} diff --git a/models/user/user.go b/models/user/user.go index d828f3d65dcc1..6d1b1aef18bdc 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -730,9 +730,18 @@ func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOve return committer.Commit() } +// IsLastAdminUser check whether user is the last admin +func IsLastAdminUser(ctx context.Context, user *User) bool { + if user.IsAdmin && CountUsers(ctx, &CountUserFilter{IsAdmin: util.OptionalBoolTrue}) <= 1 { + return true + } + return false +} + // CountUserFilter represent optional filters for CountUsers type CountUserFilter struct { LastLoginSince *int64 + IsAdmin util.OptionalBool } // CountUsers returns number of users. @@ -741,13 +750,25 @@ func CountUsers(ctx context.Context, opts *CountUserFilter) int64 { } func countUsers(ctx context.Context, opts *CountUserFilter) int64 { - sess := db.GetEngine(ctx).Where(builder.Eq{"type": "0"}) + sess := db.GetEngine(ctx) + cond := builder.NewCond() + cond = cond.And(builder.Eq{"type": UserTypeIndividual}) - if opts != nil && opts.LastLoginSince != nil { - sess = sess.Where(builder.Gte{"last_login_unix": *opts.LastLoginSince}) + if opts != nil { + if opts.LastLoginSince != nil { + cond = cond.And(builder.Gte{"last_login_unix": *opts.LastLoginSince}) + } + + if !opts.IsAdmin.IsNone() { + cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.IsTrue()}) + } + } + + count, err := sess.Where(cond).Count(new(User)) + if err != nil { + log.Error("user.countUsers: %v", err) } - count, _ := sess.Count(new(User)) return count } @@ -1230,6 +1251,8 @@ func isUserVisibleToViewerCond(viewer *User) builder.Cond { return builder.Neq{ "`user`.visibility": structs.VisibleTypePrivate, }.Or( + // viewer self + builder.Eq{"`user`.id": viewer.ID}, // viewer's following builder.In("`user`.id", builder. diff --git a/modules/actions/github.go b/modules/actions/github.go index 71f81a89034c5..fafea4e11a805 100644 --- a/modules/actions/github.go +++ b/modules/actions/github.go @@ -22,6 +22,7 @@ const ( GithubEventRelease = "release" GithubEventPullRequestComment = "pull_request_comment" GithubEventGollum = "gollum" + GithubEventSchedule = "schedule" ) // canGithubEventMatch check if the input Github event can match any Gitea event. @@ -69,6 +70,9 @@ func canGithubEventMatch(eventName string, triggedEvent webhook_module.HookEvent return false } + case GithubEventSchedule: + return triggedEvent == webhook_module.HookEventSchedule + default: return eventName == string(triggedEvent) } diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index 408fdb8f8ef22..cbc7e011d1d1b 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -22,7 +22,7 @@ import ( type DetectedWorkflow struct { EntryName string - TriggerEvent string + TriggerEvent *jobparser.Event Content []byte } @@ -100,6 +100,7 @@ func DetectWorkflows( commit *git.Commit, triggedEvent webhook_module.HookEventType, payload api.Payloader, + detectSchedule bool, ) ([]*DetectedWorkflow, []*DetectedWorkflow, error) { entries, err := ListWorkflows(commit) if err != nil { @@ -114,6 +115,7 @@ func DetectWorkflows( return nil, nil, err } + // one workflow may have multiple events events, err := GetEventsFromContent(content) if err != nil { log.Warn("ignore invalid workflow %q: %v", entry.Name(), err) @@ -122,17 +124,18 @@ func DetectWorkflows( for _, evt := range events { log.Trace("detect workflow %q for event %#v matching %q", entry.Name(), evt, triggedEvent) if evt.IsSchedule() { - dwf := &DetectedWorkflow{ - EntryName: entry.Name(), - TriggerEvent: evt.Name, - Content: content, + if detectSchedule { + dwf := &DetectedWorkflow{ + EntryName: entry.Name(), + TriggerEvent: evt, + Content: content, + } + schedules = append(schedules, dwf) } - schedules = append(schedules, dwf) - } - if detectMatched(gitRepo, commit, triggedEvent, payload, evt) { + } else if detectMatched(gitRepo, commit, triggedEvent, payload, evt) { dwf := &DetectedWorkflow{ EntryName: entry.Name(), - TriggerEvent: evt.Name, + TriggerEvent: evt, Content: content, } workflows = append(workflows, dwf) @@ -153,7 +156,8 @@ func detectMatched(gitRepo *git.Repository, commit *git.Commit, triggedEvent web webhook_module.HookEventCreate, webhook_module.HookEventDelete, webhook_module.HookEventFork, - webhook_module.HookEventWiki: + webhook_module.HookEventWiki, + webhook_module.HookEventSchedule: if len(evt.Acts()) != 0 { log.Warn("Ignore unsupported %s event arguments %v", triggedEvent, evt.Acts()) } diff --git a/modules/actions/workflows_test.go b/modules/actions/workflows_test.go index 2d57f19488e4d..c8e1e553fe94b 100644 --- a/modules/actions/workflows_test.go +++ b/modules/actions/workflows_test.go @@ -118,6 +118,13 @@ func TestDetectMatched(t *testing.T) { yamlOn: "on: gollum", expected: true, }, + { + desc: "HookEventSchedue(schedule) matches GithubEventSchedule(schedule)", + triggedEvent: webhook_module.HookEventSchedule, + payload: nil, + yamlOn: "on: schedule", + expected: true, + }, } for _, tc := range testCases { diff --git a/modules/context/repo.go b/modules/context/repo.go index 882a406731f54..8d82be1990da1 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -536,18 +536,20 @@ func RepoAssignment(ctx *Context) context.CancelFunc { ctx.Data["RepoExternalIssuesLink"] = unit.ExternalTrackerConfig().ExternalTrackerURL } - ctx.Data["NumTags"], err = repo_model.GetReleaseCountByRepoID(ctx, ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{ + ctx.Data["NumTags"], err = db.Count[repo_model.Release](ctx, repo_model.FindReleasesOptions{ IncludeDrafts: true, IncludeTags: true, HasSha1: util.OptionalBoolTrue, // only draft releases which are created with existing tags + RepoID: ctx.Repo.Repository.ID, }) if err != nil { ctx.ServerError("GetReleaseCountByRepoID", err) return nil } - ctx.Data["NumReleases"], err = repo_model.GetReleaseCountByRepoID(ctx, ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{ + ctx.Data["NumReleases"], err = db.Count[repo_model.Release](ctx, repo_model.FindReleasesOptions{ // only show draft releases for users who can write, read-only users shouldn't see draft releases. IncludeDrafts: ctx.Repo.CanWrite(unit_model.TypeReleases), + RepoID: ctx.Repo.Repository.ID, }) if err != nil { ctx.ServerError("GetReleaseCountByRepoID", err) diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index ccb3eb4adef0b..9c9ee7768febf 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -142,6 +142,9 @@ func (repo *Repository) searchCommits(id ObjectID, opts SearchCommitsOptions) ([ cmd.AddArguments("--all") } + // interpret search string keywords as string instead of regex + cmd.AddArguments("--fixed-strings") + // add remaining keywords from search string // note this is done only for command created above for _, v := range opts.Keywords { diff --git a/modules/graceful/manager_common.go b/modules/graceful/manager_common.go index aaf008670ce9d..27196e1531a84 100644 --- a/modules/graceful/manager_common.go +++ b/modules/graceful/manager_common.go @@ -10,6 +10,9 @@ import ( "time" ) +// FIXME: it seems that there is a bug when using systemd Type=notify: the "Install Page" (INSTALL_LOCK=false) doesn't notify properly. +// At the moment, no idea whether it also affects Windows Service, or whether it's a regression bug. It needs to be investigated later. + type systemdNotifyMsg string const ( diff --git a/modules/markup/external/external.go b/modules/markup/external/external.go index ffbb6da4daddc..122517ed11c0a 100644 --- a/modules/markup/external/external.go +++ b/modules/markup/external/external.go @@ -79,9 +79,10 @@ func envMark(envName string) string { // Render renders the data of the document to HTML via the external tool. func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { var ( - urlRawPrefix = strings.Replace(ctx.URLPrefix, "/src/", "/raw/", 1) - command = strings.NewReplacer(envMark("GITEA_PREFIX_SRC"), ctx.URLPrefix, - envMark("GITEA_PREFIX_RAW"), urlRawPrefix).Replace(p.Command) + command = strings.NewReplacer( + envMark("GITEA_PREFIX_SRC"), ctx.Links.SrcLink(), + envMark("GITEA_PREFIX_RAW"), ctx.Links.RawLink(), + ).Replace(p.Command) commands = strings.Fields(command) args = commands[1:] ) @@ -121,14 +122,14 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io. ctx.Ctx = graceful.GetManager().ShutdownContext() } - processCtx, _, finished := process.GetManager().AddContext(ctx.Ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.URLPrefix)) + processCtx, _, finished := process.GetManager().AddContext(ctx.Ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.Links.SrcLink())) defer finished() cmd := exec.CommandContext(processCtx, commands[0], args...) cmd.Env = append( os.Environ(), - "GITEA_PREFIX_SRC="+ctx.URLPrefix, - "GITEA_PREFIX_RAW="+urlRawPrefix, + "GITEA_PREFIX_SRC="+ctx.Links.SrcLink(), + "GITEA_PREFIX_RAW="+ctx.Links.RawLink(), ) if !p.IsInputFile { cmd.Stdin = input diff --git a/modules/markup/html.go b/modules/markup/html.go index 05b1c3ef72a70..a64e4c565d7ef 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -80,15 +80,10 @@ const keywordClass = "issue-keyword" // IsLink reports whether link fits valid format. func IsLink(link []byte) bool { - return isLink(link) -} - -// isLink reports whether link fits valid format. -func isLink(link []byte) bool { return validLinksPattern.Match(link) } -func isLinkStr(link string) bool { +func IsLinkStr(link string) bool { return validLinksPattern.MatchString(link) } @@ -344,7 +339,7 @@ func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output node = node.FirstChild } - visitNode(ctx, procs, procs, node) + visitNode(ctx, procs, node) newNodes := make([]*html.Node, 0, 5) @@ -375,7 +370,7 @@ func postProcess(ctx *RenderContext, procs []processor, input io.Reader, output return nil } -func visitNode(ctx *RenderContext, procs, textProcs []processor, node *html.Node) { +func visitNode(ctx *RenderContext, procs []processor, node *html.Node) { // Add user-content- to IDs and "#" links if they don't already have them for idx, attr := range node.Attr { val := strings.TrimPrefix(attr.Val, "#") @@ -390,35 +385,29 @@ func visitNode(ctx *RenderContext, procs, textProcs []processor, node *html.Node } if attr.Key == "class" && attr.Val == "emoji" { - textProcs = nil + procs = nil } } // We ignore code and pre. switch node.Type { case html.TextNode: - textNode(ctx, textProcs, node) + textNode(ctx, procs, node) case html.ElementNode: if node.Data == "img" { for i, attr := range node.Attr { if attr.Key != "src" { continue } - if len(attr.Val) > 0 && !isLinkStr(attr.Val) && !strings.HasPrefix(attr.Val, "data:image/") { - prefix := ctx.URLPrefix - if ctx.IsWiki { - prefix = util.URLJoin(prefix, "wiki", "raw") - } - prefix = strings.Replace(prefix, "/src/", "/media/", 1) - - attr.Val = util.URLJoin(prefix, attr.Val) + if len(attr.Val) > 0 && !IsLinkStr(attr.Val) && !strings.HasPrefix(attr.Val, "data:image/") { + attr.Val = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), attr.Val) } attr.Val = camoHandleLink(attr.Val) node.Attr[i] = attr } } else if node.Data == "a" { // Restrict text in links to emojis - textProcs = emojiProcessors + procs = emojiProcessors } else if node.Data == "code" || node.Data == "pre" { return } else if node.Data == "i" { @@ -444,7 +433,7 @@ func visitNode(ctx *RenderContext, procs, textProcs []processor, node *html.Node } } for n := node.FirstChild; n != nil; n = n.NextSibling { - visitNode(ctx, procs, textProcs, n) + visitNode(ctx, procs, n) } } // ignore everything else @@ -641,10 +630,6 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) { } func shortLinkProcessor(ctx *RenderContext, node *html.Node) { - shortLinkProcessorFull(ctx, node, false) -} - -func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) { next := node.NextSibling for node != nil && node != next { m := shortLinkPattern.FindStringSubmatchIndex(node.Data) @@ -665,7 +650,7 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) { if equalPos := strings.IndexByte(v, '='); equalPos == -1 { // There is no equal in this argument; this is a mandatory arg if props["name"] == "" { - if isLinkStr(v) { + if IsLinkStr(v) { // If we clearly see it is a link, we save it so // But first we need to ensure, that if both mandatory args provided @@ -740,7 +725,7 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) { DataAtom: atom.A, } childNode.Parent = linkNode - absoluteLink := isLinkStr(link) + absoluteLink := IsLinkStr(link) if !absoluteLink { if image { link = strings.ReplaceAll(link, " ", "+") @@ -751,16 +736,9 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) { link = url.PathEscape(link) } } - urlPrefix := ctx.URLPrefix if image { if !absoluteLink { - if IsSameDomain(urlPrefix) { - urlPrefix = strings.Replace(urlPrefix, "/src/", "/raw/", 1) - } - if ctx.IsWiki { - link = util.URLJoin("wiki", "raw", link) - } - link = util.URLJoin(urlPrefix, link) + link = util.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), link) } title := props["title"] if title == "" { @@ -789,18 +767,15 @@ func shortLinkProcessorFull(ctx *RenderContext, node *html.Node, noLink bool) { } else { if !absoluteLink { if ctx.IsWiki { - link = util.URLJoin("wiki", link) + link = util.URLJoin(ctx.Links.WikiLink(), link) + } else { + link = util.URLJoin(ctx.Links.SrcLink(), link) } - link = util.URLJoin(urlPrefix, link) } childNode.Type = html.TextNode childNode.Data = name } - if noLink { - linkNode = childNode - } else { - linkNode.Attr = []html.Attribute{{Key: "href", Val: link}} - } + linkNode.Attr = []html.Attribute{{Key: "href", Val: link}} replaceContent(node, m[0], m[1], linkNode) node = node.NextSibling.NextSibling } diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index 7b7f6df70106b..5ba956191561b 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -287,8 +287,8 @@ func TestRender_IssueIndexPattern_Document(t *testing.T) { } func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) { - if ctx.URLPrefix == "" { - ctx.URLPrefix = TestAppURL + if ctx.Links.Base == "" { + ctx.Links.Base = TestRepoURL } var buf strings.Builder @@ -303,19 +303,23 @@ func TestRender_AutoLink(t *testing.T) { test := func(input, expected string) { var buffer strings.Builder err := PostProcess(&RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: TestRepoURL, - Metas: localMetas, + Ctx: git.DefaultContext, + Links: Links{ + Base: TestRepoURL, + }, + Metas: localMetas, }, strings.NewReader(input), &buffer) assert.Equal(t, err, nil) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) buffer.Reset() err = PostProcess(&RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: TestRepoURL, - Metas: localMetas, - IsWiki: true, + Ctx: git.DefaultContext, + Links: Links{ + Base: TestRepoURL, + }, + Metas: localMetas, + IsWiki: true, }, strings.NewReader(input), &buffer) assert.Equal(t, err, nil) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) @@ -342,9 +346,11 @@ func TestRender_FullIssueURLs(t *testing.T) { test := func(input, expected string) { var result strings.Builder err := postProcess(&RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: TestRepoURL, - Metas: localMetas, + Ctx: git.DefaultContext, + Links: Links{ + Base: TestRepoURL, + }, + Metas: localMetas, }, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result) assert.NoError(t, err) assert.Equal(t, expected, result.String()) diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index 62fd0f5a85883..89ecfc036b587 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -42,8 +42,10 @@ func TestRender_Commits(t *testing.T) { buffer, err := markup.RenderString(&markup.RenderContext{ Ctx: git.DefaultContext, RelativePath: ".md", - URLPrefix: markup.TestRepoURL, - Metas: localMetas, + Links: markup.Links{ + Base: markup.TestRepoURL, + }, + Metas: localMetas, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -93,8 +95,10 @@ func TestRender_CrossReferences(t *testing.T) { buffer, err := markup.RenderString(&markup.RenderContext{ Ctx: git.DefaultContext, RelativePath: "a.md", - URLPrefix: setting.AppSubURL, - Metas: localMetas, + Links: markup.Links{ + Base: setting.AppSubURL, + }, + Metas: localMetas, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -138,7 +142,9 @@ func TestRender_links(t *testing.T) { buffer, err := markup.RenderString(&markup.RenderContext{ Ctx: git.DefaultContext, RelativePath: "a.md", - URLPrefix: markup.TestRepoURL, + Links: markup.Links{ + Base: markup.TestRepoURL, + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -238,7 +244,9 @@ func TestRender_email(t *testing.T) { res, err := markup.RenderString(&markup.RenderContext{ Ctx: git.DefaultContext, RelativePath: "a.md", - URLPrefix: markup.TestRepoURL, + Links: markup.Links{ + Base: markup.TestRepoURL, + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res)) @@ -309,7 +317,9 @@ func TestRender_emoji(t *testing.T) { buffer, err := markup.RenderString(&markup.RenderContext{ Ctx: git.DefaultContext, RelativePath: "a.md", - URLPrefix: markup.TestRepoURL, + Links: markup.Links{ + Base: markup.TestRepoURL, + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -371,29 +381,34 @@ func TestRender_ShortLinks(t *testing.T) { test := func(input, expected, expectedWiki string) { buffer, err := markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: tree, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: markup.TestRepoURL, + BranchPath: "master", + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) buffer, err = markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: markup.TestRepoURL, - Metas: localMetas, - IsWiki: true, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: markup.TestRepoURL, + }, + Metas: localMetas, + IsWiki: true, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer)) } - rawtree := util.URLJoin(markup.TestRepoURL, "raw", "master") + mediatree := util.URLJoin(markup.TestRepoURL, "media", "master") url := util.URLJoin(tree, "Link") otherURL := util.URLJoin(tree, "Other-Link") encodedURL := util.URLJoin(tree, "Link%3F") - imgurl := util.URLJoin(rawtree, "Link.jpg") - otherImgurl := util.URLJoin(rawtree, "Link+Other.jpg") - encodedImgurl := util.URLJoin(rawtree, "Link+%23.jpg") - notencodedImgurl := util.URLJoin(rawtree, "some", "path", "Link+#.jpg") + imgurl := util.URLJoin(mediatree, "Link.jpg") + otherImgurl := util.URLJoin(mediatree, "Link+Other.jpg") + encodedImgurl := util.URLJoin(mediatree, "Link+%23.jpg") + notencodedImgurl := util.URLJoin(mediatree, "some", "path", "Link+#.jpg") urlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "Link") otherURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "Other-Link") encodedURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "Link%3F") @@ -475,21 +490,25 @@ func TestRender_ShortLinks(t *testing.T) { func TestRender_RelativeImages(t *testing.T) { setting.AppURL = markup.TestAppURL - tree := util.URLJoin(markup.TestRepoURL, "src", "master") test := func(input, expected, expectedWiki string) { buffer, err := markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: tree, - Metas: localMetas, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: markup.TestRepoURL, + BranchPath: "master", + }, + Metas: localMetas, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) buffer, err = markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: markup.TestRepoURL, - Metas: localMetas, - IsWiki: true, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: markup.TestRepoURL, + }, + Metas: localMetas, + IsWiki: true, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer)) @@ -521,9 +540,11 @@ func Test_ParseClusterFuzz(t *testing.T) { var res strings.Builder err := markup.PostProcess(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: "https://example.com", - Metas: localMetas, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: "https://example.com", + }, + Metas: localMetas, }, strings.NewReader(data), &res) assert.NoError(t, err) assert.NotContains(t, res.String(), " 0 && !markup.IsLink(link) { - prefix := pc.Get(urlPrefixKey).(string) - if pc.Get(isWikiKey).(bool) { - prefix = giteautil.URLJoin(prefix, "wiki", "raw") - } - prefix = strings.Replace(prefix, "/src/", "/media/", 1) - - lnk := strings.TrimLeft(string(link), "/") - - lnk = giteautil.URLJoin(prefix, lnk) - link = []byte(lnk) + v.Destination = []byte(giteautil.URLJoin(ctx.Links.ResolveMediaLink(ctx.IsWiki), string(link))) } - v.Destination = link parent := n.Parent() // Create a link around image only if parent is not already a link @@ -107,7 +97,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa // Create a link wrapper wrap := ast.NewLink() - wrap.Destination = link + wrap.Destination = v.Destination wrap.Title = v.Title wrap.SetAttributeString("target", []byte("_blank")) @@ -143,11 +133,15 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa link[0] != '#' && !bytes.HasPrefix(link, byteMailto) { // special case: this is not a link, a hash link or a mailto:, so it's a // relative URL - lnk := string(link) - if pc.Get(isWikiKey).(bool) { - lnk = giteautil.URLJoin("wiki", lnk) + + var base string + if ctx.IsWiki { + base = ctx.Links.WikiLink() + } else { + base = ctx.Links.Base } - link = []byte(giteautil.URLJoin(pc.Get(urlPrefixKey).(string), lnk)) + + link = []byte(giteautil.URLJoin(base, string(link))) } if len(link) > 0 && link[0] == '#' { link = []byte("#user-content-" + string(link)[1:]) @@ -188,9 +182,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa applyElementDir(v) case *ast.Text: if v.SoftLineBreak() && !v.HardLineBreak() { - renderMetas := pc.Get(renderMetasKey).(map[string]string) - mode := renderMetas["mode"] - if mode != "document" { + if ctx.Metas["mode"] != "document" { v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInComments) } else { v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInDocuments) diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index 43885889d125d..771162b9a3f12 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -34,9 +34,6 @@ var ( ) var ( - urlPrefixKey = parser.NewContextKey() - isWikiKey = parser.NewContextKey() - renderMetasKey = parser.NewContextKey() renderContextKey = parser.NewContextKey() renderConfigKey = parser.NewContextKey() ) @@ -66,9 +63,6 @@ func (l *limitWriter) Write(data []byte) (int, error) { // newParserContext creates a parser.Context with the render context set func newParserContext(ctx *markup.RenderContext) parser.Context { pc := parser.NewContext(parser.WithIDs(newPrefixedIDs())) - pc.Set(urlPrefixKey, ctx.URLPrefix) - pc.Set(isWikiKey, ctx.IsWiki) - pc.Set(renderMetasKey, ctx.Metas) pc.Set(renderContextKey, ctx) return pc } diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index 8f855f1b130d0..957d773acde18 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -52,16 +52,20 @@ func TestRender_StandardLinks(t *testing.T) { test := func(input, expected, expectedWiki string) { buffer, err := markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: setting.AppSubURL, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: setting.AppSubURL, + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) buffer, err = markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: setting.AppSubURL, - IsWiki: true, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: setting.AppSubURL, + }, + IsWiki: true, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer)) @@ -83,8 +87,10 @@ func TestRender_Images(t *testing.T) { test := func(input, expected string) { buffer, err := markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: setting.AppSubURL, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: setting.AppSubURL, + }, }, input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) @@ -107,7 +113,6 @@ func TestRender_Images(t *testing.T) { "[!["+title+"]("+url+")]("+href+")", `
`) - url = "/../../.images/src/02/train.jpg" test( "!["+title+"]("+url+")", ``) @@ -286,14 +291,16 @@ func TestTotal_RenderWiki(t *testing.T) { setting.AppURL = AppURL setting.AppSubURL = AppSubURL - answers := testAnswers(util.URLJoin(AppSubURL, "wiki/"), util.URLJoin(AppSubURL, "wiki", "raw/")) + answers := testAnswers(util.URLJoin(AppSubURL, "wiki"), util.URLJoin(AppSubURL, "wiki", "raw")) for i := 0; i < len(sameCases); i++ { line, err := markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: AppSubURL, - Metas: localMetas, - IsWiki: true, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: setting.AppSubURL, + }, + Metas: localMetas, + IsWiki: true, }, sameCases[i]) assert.NoError(t, err) assert.Equal(t, answers[i], line) @@ -314,9 +321,11 @@ func TestTotal_RenderWiki(t *testing.T) { for i := 0; i < len(testCases); i += 2 { line, err := markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: AppSubURL, - IsWiki: true, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: setting.AppSubURL, + }, + IsWiki: true, }, testCases[i]) assert.NoError(t, err) assert.Equal(t, testCases[i+1], line) @@ -327,13 +336,16 @@ func TestTotal_RenderString(t *testing.T) { setting.AppURL = AppURL setting.AppSubURL = AppSubURL - answers := testAnswers(util.URLJoin(AppSubURL, "src", "master/"), util.URLJoin(AppSubURL, "raw", "master/")) + answers := testAnswers(util.URLJoin(AppSubURL, "src", "master"), util.URLJoin(AppSubURL, "media", "master")) for i := 0; i < len(sameCases); i++ { line, err := markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: util.URLJoin(AppSubURL, "src", "master/"), - Metas: localMetas, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: AppSubURL, + BranchPath: "master", + }, + Metas: localMetas, }, sameCases[i]) assert.NoError(t, err) assert.Equal(t, answers[i], line) @@ -343,8 +355,10 @@ func TestTotal_RenderString(t *testing.T) { for i := 0; i < len(testCases); i += 2 { line, err := markdown.RenderString(&markup.RenderContext{ - Ctx: git.DefaultContext, - URLPrefix: AppSubURL, + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: AppSubURL, + }, }, testCases[i]) assert.NoError(t, err) assert.Equal(t, testCases[i+1], line) @@ -556,3 +570,367 @@ foo: bar assert.Equal(t, test.expected, res, "Unexpected result in testcase %q", test.testcase) } } + +func TestRenderLinks(t *testing.T) { + input := ` space @mention-user +/just/a/path.bin +https://example.com/file.bin +[local link](file.bin) +[remote link](https://example.com) +[[local link|file.bin]] +[[remote link|https://example.com]] +![local image](image.jpg) +![remote image](https://example.com/image.jpg) +[[local image|image.jpg]] +[[remote link|https://example.com/image.jpg]] +https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare +https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb +com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit +:+1: +mail@domain.com +@mention-user test +#123 + space +` + cases := []struct { + Links markup.Links + IsWiki bool + Expected string + }{ + { // 0 + Links: markup.Links{}, + IsWiki: false, + Expected: `space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+
+
+
+
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space
space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+
+
+
+
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space
space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+
+
+
+
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space
space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+
+
+
+
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space
space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+
+
+
+
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space
space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+
+
+
+
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space
space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+
+
+
+
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space
space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+
+
+
+
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space
space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+
+
+
+
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space
space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+
+
+
+
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space
space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+
+
+
+
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space
space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+
+
+
+
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space
88fc37a3c0...12fc37a3c0 (hash)
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+88fc37a3c0
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+ space`
+
+ assert.EqualValues(t, expected, RenderCommitBody(context.Background(), testInput, testMetas))
+}
+
+func TestRenderCommitMessage(t *testing.T) {
+ expected := `space @mention-user `
+
+ assert.EqualValues(t, expected, RenderCommitMessage(context.Background(), testInput, testMetas))
+}
+
+func TestRenderCommitMessageLinkSubject(t *testing.T) {
+ expected := `space @mention-user`
+
+ assert.EqualValues(t, expected, RenderCommitMessageLinkSubject(context.Background(), testInput, "https://example.com/link", testMetas))
+}
+
+func TestRenderIssueTitle(t *testing.T) {
+ expected := ` space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+[local link](file.bin)
+[remote link](https://example.com)
+[[local link|file.bin]]
+[[remote link|https://example.com]]
+![local image](image.jpg)
+![remote image](https://example.com/image.jpg)
+[[local image|image.jpg]]
+[[remote link|https://example.com/image.jpg]]
+https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+ space
+`
+ assert.EqualValues(t, expected, RenderIssueTitle(context.Background(), testInput, testMetas))
+}
+
+func TestRenderMarkdownToHtml(t *testing.T) {
+ expected := `space @mention-user
+/just/a/path.bin
+https://example.com/file.bin
+local link
+remote link
+local link
+remote link
+
+
+
+
+88fc37a3c0...12fc37a3c0 (hash)
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
+88fc37a3c0
+com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
+👍
+mail@domain.com
+@mention-user test
+#123
+space
%[1]s
to %[3]s
diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini
index 898e351d42110..f3a264c1c8599 100644
--- a/options/locale/locale_fr-FR.ini
+++ b/options/locale/locale_fr-FR.ini
@@ -17,6 +17,7 @@ template=Modèle
language=Langue
notifications=Notifications
active_stopwatch=Suivi du temps actif
+tracked_time_summary=Résumé du pointage d’après les filtres de la liste des tickets
create_new=Créer…
user_profile_and_more=Profil et réglages…
signed_in_as=Connecté en tant que
@@ -887,6 +888,7 @@ webauthn_nickname=Pseudonyme
webauthn_delete_key=Retirer la clé de sécurité
webauthn_delete_key_desc=Si vous retirez une clé de sécurité, vous ne pourrez plus l'utiliser pour vous connecter. Continuer ?
webauthn_key_loss_warning=Si vous perdez vos clés de sécurité, vous perdrez l’accès à votre compte.
+webauthn_alternative_tip=Vous devriez configurer une méthode d’authentification supplémentaire.
manage_account_links=Gérer les comptes liés
manage_account_links_desc=Ces comptes externes sont liés à votre compte Gitea.
@@ -923,6 +925,7 @@ visibility.private=Privé
visibility.private_tooltip=Visible uniquement aux membres des organisations que vous avez rejointes
[repo]
+new_repo_helper=Un dépôt contient tous les fichiers d’un projet, ainsi que l’historique de leurs modifications. Vous avez déjà ça ailleurs ? Migrez-le ici.
owner=Propriétaire
owner_helper=Certaines organisations peuvent ne pas apparaître dans la liste déroulante en raison d'une limite maximale du nombre de dépôts.
repo_name=Nom du dépôt
@@ -1796,6 +1799,10 @@ pulls.close=Fermer la demande d’ajout
pulls.closed_at=`a fermé cette demande d'ajout %[2]s.`
pulls.reopened_at=`a rouvert cette demande d'ajout %[2]s.`
pulls.cmd_instruction_hint=`Voir les instructions en ligne de commande.`
+pulls.cmd_instruction_checkout_title=Basculer
+pulls.cmd_instruction_checkout_desc=Depuis votre dépôt, basculer sur une nouvelle branche et tester des modifications.
+pulls.cmd_instruction_merge_title=Fusionner
+pulls.cmd_instruction_merge_desc=Fusionner les modifications et mettre à jour sur Gitea.
pulls.clear_merge_message=Effacer le message de fusion
pulls.clear_merge_message_hint=Effacer le message de fusion ne supprimera que le message de la révision, mais pas les pieds de révision générés tels que "Co-Authored-By:".
@@ -2307,6 +2314,7 @@ settings.dismiss_stale_approvals_desc=Lorsque des nouvelles révisions changent
settings.require_signed_commits=Exiger des révisions signées
settings.require_signed_commits_desc=Rejeter les soumissions sur cette branche lorsqu'ils ne sont pas signés ou vérifiables.
settings.protect_branch_name_pattern=Motif de nom de branche protégé
+settings.protect_branch_name_pattern_desc=Motifs de nom de branche protégé. Consultez la documentation pour la syntaxe du motif. Exemples : main
, release/**
settings.protect_patterns=Motifs
settings.protect_protected_file_patterns=Liste des fichiers et motifs protégés
settings.protect_protected_file_patterns_desc=Liste de fichiers et de motifs, séparés par un point-virgule « ; », qui ne pourront pas être modifiés même si les utilisateurs disposent des droits sur la branche. Voir la syntaxe glob. Exemples : .drone.yml ; /docs/**/*.txt
.
@@ -2852,6 +2860,7 @@ emails.updated=Courriel mis à jour
emails.not_updated=Impossible de mettre à jour l’adresse courriel demandée : %v
emails.duplicate_active=Cette adresse courriel est déjà active pour un autre utilisateur.
emails.change_email_header=Mettre à jour les propriétés du courriel
+emails.change_email_text=Êtes-vous sûr de vouloir mettre à jour cette adresse courriel ?
orgs.org_manage_panel=Gestion des organisations
orgs.name=Nom
@@ -2876,6 +2885,7 @@ packages.package_manage_panel=Gestion des paquets
packages.total_size=Taille totale : %s
packages.unreferenced_size=Taille non référencée : %s
packages.cleanup=Purger les données expirées
+packages.cleanup.success=Les données expirées ont été nettoyées avec succès
packages.owner=Propriétaire
packages.creator=Créateur
packages.name=Nom
@@ -3520,6 +3530,9 @@ runs.status=Statut
runs.actors_no_select=Tous les acteurs
runs.status_no_select=Touts les statuts
runs.no_results=Aucun résultat correspondant.
+runs.no_workflows=Il n'y a pas encore de workflows.
+runs.no_workflows.quick_start=Vous ne savez pas comment commencer avec Gitea Action ? Consultez le guide de démarrage rapide.
+runs.no_workflows.documentation=Pour plus d’informations sur les Actions Gitea, voir la documentation.
runs.no_runs=Le flux de travail n'a pas encore d'exécution.
runs.empty_commit_message=(message de révision vide)
diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini
index e5fa9e59843ae..9216277955c7a 100644
--- a/options/locale/locale_ja-JP.ini
+++ b/options/locale/locale_ja-JP.ini
@@ -3525,6 +3525,7 @@ runs.commit=コミット
runs.scheduled=スケジュール済み
runs.pushed_by=pushed by
runs.invalid_workflow_helper=ワークフロー設定ファイルは無効です。あなたの設定ファイルを確認してください: %s
+runs.no_matching_online_runner_helper=ラベルに一致するオンラインのランナーが見つかりません: %s
runs.actor=アクター
runs.status=ステータス
runs.actors_no_select=すべてのアクター
diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go
index 76116d0751d7f..9026387129787 100644
--- a/routers/api/packages/api.go
+++ b/routers/api/packages/api.go
@@ -512,19 +512,7 @@ func CommonRoutes() *web.Route {
r.Get("/files/{id}/{version}/{filename}", pypi.DownloadPackageFile)
r.Get("/simple/{id}", pypi.PackageMetadata)
}, reqPackageAccess(perm.AccessModeRead))
- r.Group("/rpm", func() {
- r.Get(".repo", rpm.GetRepositoryConfig)
- r.Get("/repository.key", rpm.GetRepositoryKey)
- r.Put("/upload", reqPackageAccess(perm.AccessModeWrite), rpm.UploadPackageFile)
- r.Group("/package/{name}/{version}/{architecture}", func() {
- r.Get("", rpm.DownloadPackageFile)
- r.Delete("", reqPackageAccess(perm.AccessModeWrite), rpm.DeletePackageFile)
- })
- r.Group("/repodata/{filename}", func() {
- r.Head("", rpm.CheckRepositoryFileExistence)
- r.Get("", rpm.GetRepositoryFile)
- })
- }, reqPackageAccess(perm.AccessModeRead))
+ r.Group("/rpm", RpmRoutes(r), reqPackageAccess(perm.AccessModeRead))
r.Group("/rubygems", func() {
r.Get("/specs.4.8.gz", rubygems.EnumeratePackages)
r.Get("/latest_specs.4.8.gz", rubygems.EnumeratePackagesLatest)
@@ -589,6 +577,82 @@ func CommonRoutes() *web.Route {
return r
}
+// Support for uploading rpm packages with arbitrary depth paths
+func RpmRoutes(r *web.Route) func() {
+ var (
+ groupRepoInfo = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)\.repo\z`)
+ groupUpload = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)/upload\z`)
+ groupRpm = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)/package/([^/]+)/([^/]+)/([^/]+)(?:/([^/]+\.rpm)|)\z`)
+ groupMetadata = regexp.MustCompile(`\A((?:/(?:[^/]+))*|)/repodata/([^/]+)\z`)
+ )
+
+ return func() {
+ r.Methods("HEAD,GET,POST,PUT,PATCH,DELETE", "*", func(ctx *context.Context) {
+ path := ctx.Params("*")
+ isHead := ctx.Req.Method == "HEAD"
+ isGetHead := ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET"
+ isPut := ctx.Req.Method == "PUT"
+ isDelete := ctx.Req.Method == "DELETE"
+
+ if path == "/repository.key" && isGetHead {
+ rpm.GetRepositoryKey(ctx)
+ return
+ }
+
+ // get repo
+ m := groupRepoInfo.FindStringSubmatch(path)
+ if len(m) == 2 && isGetHead {
+ ctx.SetParams("group", strings.Trim(m[1], "/"))
+ rpm.GetRepositoryConfig(ctx)
+ return
+ }
+ // get meta
+ m = groupMetadata.FindStringSubmatch(path)
+ if len(m) == 3 && isGetHead {
+ ctx.SetParams("group", strings.Trim(m[1], "/"))
+ ctx.SetParams("filename", m[2])
+ if isHead {
+ rpm.CheckRepositoryFileExistence(ctx)
+ } else {
+ rpm.GetRepositoryFile(ctx)
+ }
+ return
+ }
+ // upload
+ m = groupUpload.FindStringSubmatch(path)
+ if len(m) == 2 && isPut {
+ reqPackageAccess(perm.AccessModeWrite)(ctx)
+ if ctx.Written() {
+ return
+ }
+ ctx.SetParams("group", strings.Trim(m[1], "/"))
+ rpm.UploadPackageFile(ctx)
+ return
+ }
+ // rpm down/delete
+ m = groupRpm.FindStringSubmatch(path)
+ if len(m) == 6 {
+ ctx.SetParams("group", strings.Trim(m[1], "/"))
+ ctx.SetParams("name", m[2])
+ ctx.SetParams("version", m[3])
+ ctx.SetParams("architecture", m[4])
+ if isGetHead {
+ rpm.DownloadPackageFile(ctx)
+ return
+ } else if isDelete {
+ reqPackageAccess(perm.AccessModeWrite)(ctx)
+ if ctx.Written() {
+ return
+ }
+ rpm.DeletePackageFile(ctx)
+ }
+ }
+ // default
+ ctx.Status(http.StatusNotFound)
+ })
+ }
+}
+
// ContainerRoutes provides endpoints that implement the OCI API to serve containers
// These have to be mounted on `/v2/...` to comply with the OCI spec:
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md
diff --git a/routers/api/packages/rpm/rpm.go b/routers/api/packages/rpm/rpm.go
index 2e161940b8ca5..75d19e2b436b8 100644
--- a/routers/api/packages/rpm/rpm.go
+++ b/routers/api/packages/rpm/rpm.go
@@ -33,11 +33,14 @@ func apiError(ctx *context.Context, status int, obj any) {
// https://dnf.readthedocs.io/en/latest/conf_ref.html
func GetRepositoryConfig(ctx *context.Context) {
+ group := ctx.Params("group")
+ if group != "" {
+ group = fmt.Sprintf("/%s", group)
+ }
url := fmt.Sprintf("%sapi/packages/%s/rpm", setting.AppURL, ctx.Package.Owner.Name)
-
- ctx.PlainText(http.StatusOK, `[gitea-`+ctx.Package.Owner.LowerName+`]
-name=`+ctx.Package.Owner.Name+` - `+setting.AppName+`
-baseurl=`+url+`
+ ctx.PlainText(http.StatusOK, `[gitea-`+ctx.Package.Owner.LowerName+strings.ReplaceAll(group, "/", "-")+`]
+name=`+ctx.Package.Owner.Name+` - `+setting.AppName+strings.ReplaceAll(group, "/", " - ")+`
+baseurl=`+url+group+`/
enabled=1
gpgcheck=1
gpgkey=`+url+`/repository.key`)
@@ -64,7 +67,7 @@ func CheckRepositoryFileExistence(ctx *context.Context) {
return
}
- pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, ctx.Params("filename"), packages_model.EmptyFileKey)
+ pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, ctx.Params("filename"), ctx.Params("group"))
if err != nil {
if errors.Is(err, util.ErrNotExist) {
ctx.Status(http.StatusNotFound)
@@ -93,7 +96,8 @@ func GetRepositoryFile(ctx *context.Context) {
ctx,
pv,
&packages_service.PackageFileInfo{
- Filename: ctx.Params("filename"),
+ Filename: ctx.Params("filename"),
+ CompositeKey: ctx.Params("group"),
},
)
if err != nil {
@@ -145,7 +149,7 @@ func UploadPackageFile(ctx *context.Context) {
apiError(ctx, http.StatusInternalServerError, err)
return
}
-
+ group := ctx.Params("group")
_, _, err = packages_service.CreatePackageOrAddFileToExisting(
ctx,
&packages_service.PackageCreationInfo{
@@ -153,14 +157,15 @@ func UploadPackageFile(ctx *context.Context) {
Owner: ctx.Package.Owner,
PackageType: packages_model.TypeRpm,
Name: pck.Name,
- Version: pck.Version,
+ Version: strings.Trim(fmt.Sprintf("%s/%s", group, pck.Version), "/"),
},
Creator: ctx.Doer,
Metadata: pck.VersionMetadata,
},
&packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{
- Filename: fmt.Sprintf("%s-%s.%s.rpm", pck.Name, pck.Version, pck.FileMetadata.Architecture),
+ Filename: fmt.Sprintf("%s-%s.%s.rpm", pck.Name, pck.Version, pck.FileMetadata.Architecture),
+ CompositeKey: group,
},
Creator: ctx.Doer,
Data: buf,
@@ -182,7 +187,7 @@ func UploadPackageFile(ctx *context.Context) {
return
}
- if err := rpm_service.BuildRepositoryFiles(ctx, ctx.Package.Owner.ID); err != nil {
+ if err := rpm_service.BuildRepositoryFiles(ctx, ctx.Package.Owner.ID, group); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
@@ -191,19 +196,20 @@ func UploadPackageFile(ctx *context.Context) {
}
func DownloadPackageFile(ctx *context.Context) {
+ group := ctx.Params("group")
name := ctx.Params("name")
version := ctx.Params("version")
-
s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
ctx,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
PackageType: packages_model.TypeRpm,
Name: name,
- Version: version,
+ Version: strings.Trim(fmt.Sprintf("%s/%s", group, version), "/"),
},
&packages_service.PackageFileInfo{
- Filename: fmt.Sprintf("%s-%s.%s.rpm", name, version, ctx.Params("architecture")),
+ Filename: fmt.Sprintf("%s-%s.%s.rpm", name, version, ctx.Params("architecture")),
+ CompositeKey: group,
},
)
if err != nil {
@@ -219,14 +225,19 @@ func DownloadPackageFile(ctx *context.Context) {
}
func DeletePackageFile(webctx *context.Context) {
+ group := webctx.Params("group")
name := webctx.Params("name")
version := webctx.Params("version")
architecture := webctx.Params("architecture")
-
var pd *packages_model.PackageDescriptor
err := db.WithTx(webctx, func(ctx stdctx.Context) error {
- pv, err := packages_model.GetVersionByNameAndVersion(ctx, webctx.Package.Owner.ID, packages_model.TypeRpm, name, version)
+ pv, err := packages_model.GetVersionByNameAndVersion(ctx,
+ webctx.Package.Owner.ID,
+ packages_model.TypeRpm,
+ name,
+ strings.Trim(fmt.Sprintf("%s/%s", group, version), "/"),
+ )
if err != nil {
return err
}
@@ -235,7 +246,7 @@ func DeletePackageFile(webctx *context.Context) {
ctx,
pv.ID,
fmt.Sprintf("%s-%s.%s.rpm", name, version, architecture),
- packages_model.EmptyFileKey,
+ group,
)
if err != nil {
return err
@@ -275,7 +286,7 @@ func DeletePackageFile(webctx *context.Context) {
notify_service.PackageDelete(webctx, webctx.Doer, pd)
}
- if err := rpm_service.BuildRepositoryFiles(webctx, webctx.Package.Owner.ID); err != nil {
+ if err := rpm_service.BuildRepositoryFiles(webctx, webctx.Package.Owner.ID, group); err != nil {
apiError(webctx, http.StatusInternalServerError, err)
return
}
diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go
index 91b5f3a1b0b64..b4cc42ea5d820 100644
--- a/routers/api/v1/admin/user.go
+++ b/routers/api/v1/admin/user.go
@@ -183,6 +183,8 @@ func EditUser(ctx *context.APIContext) {
// responses:
// "200":
// "$ref": "#/responses/User"
+ // "400":
+ // "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/forbidden"
// "422":
@@ -264,6 +266,10 @@ func EditUser(ctx *context.APIContext) {
ctx.ContextUser.Visibility = api.VisibilityModes[form.Visibility]
}
if form.Admin != nil {
+ if !*form.Admin && user_model.IsLastAdminUser(ctx, ctx.ContextUser) {
+ ctx.Error(http.StatusBadRequest, "LastAdmin", ctx.Tr("auth.last_admin"))
+ return
+ }
ctx.ContextUser.IsAdmin = *form.Admin
}
if form.AllowGitHook != nil {
@@ -341,7 +347,8 @@ func DeleteUser(ctx *context.APIContext) {
if err := user_service.DeleteUser(ctx, ctx.ContextUser, ctx.FormBool("purge")); err != nil {
if models.IsErrUserOwnRepos(err) ||
models.IsErrUserHasOrgs(err) ||
- models.IsErrUserOwnPackages(err) {
+ models.IsErrUserOwnPackages(err) ||
+ models.IsErrDeleteLastAdminUser(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err)
} else {
ctx.Error(http.StatusInternalServerError, "DeleteUser", err)
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 4fe4e20e79485..8d7669762b722 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -1156,9 +1156,9 @@ func Routes() *web.Route {
m.Get("/subscribers", repo.ListSubscribers)
m.Group("/subscription", func() {
m.Get("", user.IsWatching)
- m.Put("", reqToken(), user.Watch)
- m.Delete("", reqToken(), user.Unwatch)
- })
+ m.Put("", user.Watch)
+ m.Delete("", user.Unwatch)
+ }, reqToken())
m.Group("/releases", func() {
m.Combo("").Get(repo.ListReleases).
Post(reqToken(), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.CreateReleaseOption{}), repo.CreateRelease)
diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go
index 36c85c8a57034..edbfbcc568bad 100644
--- a/routers/api/v1/repo/branch.go
+++ b/routers/api/v1/repo/branch.go
@@ -615,6 +615,7 @@ func CreateBranchProtection(ctx *context.APIContext) {
BlockOnRejectedReviews: form.BlockOnRejectedReviews,
BlockOnOfficialReviewRequests: form.BlockOnOfficialReviewRequests,
DismissStaleApprovals: form.DismissStaleApprovals,
+ IgnoreStaleApprovals: form.IgnoreStaleApprovals,
RequireSignedCommits: form.RequireSignedCommits,
ProtectedFilePatterns: form.ProtectedFilePatterns,
UnprotectedFilePatterns: form.UnprotectedFilePatterns,
@@ -786,6 +787,10 @@ func EditBranchProtection(ctx *context.APIContext) {
protectBranch.DismissStaleApprovals = *form.DismissStaleApprovals
}
+ if form.IgnoreStaleApprovals != nil {
+ protectBranch.IgnoreStaleApprovals = *form.IgnoreStaleApprovals
+ }
+
if form.RequireSignedCommits != nil {
protectBranch.RequireSignedCommits = *form.RequireSignedCommits
}
diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go
index 2538bcdbc628d..a222e50a5e537 100644
--- a/routers/api/v1/repo/collaborators.go
+++ b/routers/api/v1/repo/collaborators.go
@@ -8,6 +8,7 @@ import (
"errors"
"net/http"
+ "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
@@ -53,7 +54,9 @@ func ListCollaborators(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
- count, err := repo_model.CountCollaborators(ctx, ctx.Repo.Repository.ID)
+ count, err := db.Count[repo_model.Collaboration](ctx, repo_model.FindCollaborationOptions{
+ RepoID: ctx.Repo.Repository.ID,
+ })
if err != nil {
ctx.InternalServerError(err)
return
diff --git a/routers/api/v1/repo/issue_dependency.go b/routers/api/v1/repo/issue_dependency.go
index 0e3bcd93ef4f1..62d1057cdf946 100644
--- a/routers/api/v1/repo/issue_dependency.go
+++ b/routers/api/v1/repo/issue_dependency.go
@@ -102,23 +102,24 @@ func GetIssueDependencies(ctx *context.APIContext) {
return
}
- var lastRepoID int64
- var lastPerm access_model.Permission
+ repoPerms := make(map[int64]access_model.Permission)
+ repoPerms[ctx.Repo.Repository.ID] = ctx.Repo.Permission
for _, blocker := range blockersInfo {
// Get the permissions for this repository
- perm := lastPerm
- if lastRepoID != blocker.Repository.ID {
- if blocker.Repository.ID == ctx.Repo.Repository.ID {
- perm = ctx.Repo.Permission
- } else {
- var err error
- perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
- if err != nil {
- ctx.ServerError("GetUserRepoPermission", err)
- return
- }
+ // If the repo ID exists in the map, return the exist permissions
+ // else get the permission and add it to the map
+ var perm access_model.Permission
+ existPerm, ok := repoPerms[blocker.RepoID]
+ if ok {
+ perm = existPerm
+ } else {
+ var err error
+ perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
+ if err != nil {
+ ctx.ServerError("GetUserRepoPermission", err)
+ return
}
- lastRepoID = blocker.Repository.ID
+ repoPerms[blocker.RepoID] = perm
}
// check permission
@@ -345,29 +346,31 @@ func GetIssueBlocks(ctx *context.APIContext) {
return
}
- var lastRepoID int64
- var lastPerm access_model.Permission
-
var issues []*issues_model.Issue
+
+ repoPerms := make(map[int64]access_model.Permission)
+ repoPerms[ctx.Repo.Repository.ID] = ctx.Repo.Permission
+
for i, depMeta := range deps {
if i < skip || i >= max {
continue
}
// Get the permissions for this repository
- perm := lastPerm
- if lastRepoID != depMeta.Repository.ID {
- if depMeta.Repository.ID == ctx.Repo.Repository.ID {
- perm = ctx.Repo.Permission
- } else {
- var err error
- perm, err = access_model.GetUserRepoPermission(ctx, &depMeta.Repository, ctx.Doer)
- if err != nil {
- ctx.ServerError("GetUserRepoPermission", err)
- return
- }
+ // If the repo ID exists in the map, return the exist permissions
+ // else get the permission and add it to the map
+ var perm access_model.Permission
+ existPerm, ok := repoPerms[depMeta.RepoID]
+ if ok {
+ perm = existPerm
+ } else {
+ var err error
+ perm, err = access_model.GetUserRepoPermission(ctx, &depMeta.Repository, ctx.Doer)
+ if err != nil {
+ ctx.ServerError("GetUserRepoPermission", err)
+ return
}
- lastRepoID = depMeta.Repository.ID
+ repoPerms[depMeta.RepoID] = perm
}
if !perm.CanReadIssuesOrPulls(depMeta.Issue.IsPull) {
diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go
index d6b9dddd9d7ba..34129ad5958c8 100644
--- a/routers/api/v1/repo/pull.go
+++ b/routers/api/v1/repo/pull.go
@@ -1329,17 +1329,16 @@ func GetPullRequestCommits(ctx *context.APIContext) {
userCache := make(map[string]*user_model.User)
- start, end := listOptions.GetStartEnd()
+ start, limit := listOptions.GetSkipTake()
- if end > totalNumberOfCommits {
- end = totalNumberOfCommits
- }
+ limit = min(limit, totalNumberOfCommits-start)
+ limit = max(limit, 0)
verification := ctx.FormString("verification") == "" || ctx.FormBool("verification")
files := ctx.FormString("files") == "" || ctx.FormBool("files")
- apiCommits := make([]*api.Commit, 0, end-start)
- for i := start; i < end; i++ {
+ apiCommits := make([]*api.Commit, 0, limit)
+ for i := start; i < start+limit; i++ {
apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, baseGitRepo, commits[i], userCache,
convert.ToCommitOptions{
Stat: true,
@@ -1477,19 +1476,14 @@ func GetPullRequestFiles(ctx *context.APIContext) {
totalNumberOfFiles := diff.NumFiles
totalNumberOfPages := int(math.Ceil(float64(totalNumberOfFiles) / float64(listOptions.PageSize)))
- start, end := listOptions.GetStartEnd()
+ start, limit := listOptions.GetSkipTake()
- if end > totalNumberOfFiles {
- end = totalNumberOfFiles
- }
+ limit = min(limit, totalNumberOfFiles-start)
- lenFiles := end - start
- if lenFiles < 0 {
- lenFiles = 0
- }
+ limit = max(limit, 0)
- apiFiles := make([]*api.ChangedFile, 0, lenFiles)
- for i := start; i < end; i++ {
+ apiFiles := make([]*api.ChangedFile, 0, limit)
+ for i := start; i < start+limit; i++ {
apiFiles = append(apiFiles, convert.ToChangedFile(diff.Files[i], pr.HeadRepo, endCommitID))
}
diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go
index b1d3b5f45717d..a41c5ba7d8550 100644
--- a/routers/api/v1/repo/release.go
+++ b/routers/api/v1/repo/release.go
@@ -7,6 +7,7 @@ import (
"net/http"
"code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
@@ -154,9 +155,10 @@ func ListReleases(ctx *context.APIContext) {
IncludeTags: false,
IsDraft: ctx.FormOptionalBool("draft"),
IsPreRelease: ctx.FormOptionalBool("pre-release"),
+ RepoID: ctx.Repo.Repository.ID,
}
- releases, err := repo_model.GetReleasesByRepoID(ctx, ctx.Repo.Repository.ID, opts)
+ releases, err := db.Find[repo_model.Release](ctx, opts)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetReleasesByRepoID", err)
return
@@ -170,7 +172,7 @@ func ListReleases(ctx *context.APIContext) {
rels[i] = convert.ToAPIRelease(ctx, ctx.Repo.Repository, release)
}
- filteredCount, err := repo_model.CountReleasesByRepoID(ctx, ctx.Repo.Repository.ID, opts)
+ filteredCount, err := db.Count[repo_model.Release](ctx, opts)
if err != nil {
ctx.InternalServerError(err)
return
diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go
index 6eb2cc4227429..8ce03cf29caa5 100644
--- a/routers/api/v1/repo/repo.go
+++ b/routers/api/v1/repo/repo.go
@@ -983,7 +983,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error {
}
if len(units)+len(deleteUnitTypes) > 0 {
- if err := repo_model.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
+ if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateRepositoryUnits", err)
return err
}
diff --git a/routers/api/v1/user/gpg_key.go b/routers/api/v1/user/gpg_key.go
index 4f8bcaca3e6d5..234da5dfdc490 100644
--- a/routers/api/v1/user/gpg_key.go
+++ b/routers/api/v1/user/gpg_key.go
@@ -18,23 +18,25 @@ import (
)
func listGPGKeys(ctx *context.APIContext, uid int64, listOptions db.ListOptions) {
- keys, err := asymkey_model.ListGPGKeys(ctx, uid, listOptions)
+ keys, total, err := db.FindAndCount[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
+ ListOptions: listOptions,
+ OwnerID: uid,
+ })
if err != nil {
ctx.Error(http.StatusInternalServerError, "ListGPGKeys", err)
return
}
+ if err := asymkey_model.GPGKeyList(keys).LoadSubKeys(ctx); err != nil {
+ ctx.Error(http.StatusInternalServerError, "ListGPGKeys", err)
+ return
+ }
+
apiKeys := make([]*api.GPGKey, len(keys))
for i := range keys {
apiKeys[i] = convert.ToGPGKey(keys[i])
}
- total, err := asymkey_model.CountUserGPGKeys(ctx, uid)
- if err != nil {
- ctx.InternalServerError(err)
- return
- }
-
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, &apiKeys)
}
@@ -121,6 +123,10 @@ func GetGPGKey(ctx *context.APIContext) {
}
return
}
+ if err := key.LoadSubKeys(ctx); err != nil {
+ ctx.Error(http.StatusInternalServerError, "LoadSubKeys", err)
+ return
+ }
ctx.JSON(http.StatusOK, convert.ToGPGKey(key))
}
@@ -198,7 +204,10 @@ func VerifyUserGPGKey(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "VerifyUserGPGKey", err)
}
- key, err := asymkey_model.GetGPGKeysByKeyID(ctx, form.KeyID)
+ keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
+ KeyID: form.KeyID,
+ IncludeSubKeys: true,
+ })
if err != nil {
if asymkey_model.IsErrGPGKeyNotExist(err) {
ctx.NotFound()
@@ -207,7 +216,7 @@ func VerifyUserGPGKey(ctx *context.APIContext) {
}
return
}
- ctx.JSON(http.StatusOK, convert.ToGPGKey(key[0]))
+ ctx.JSON(http.StatusOK, convert.ToGPGKey(keys[0]))
}
// swagger:parameters userCurrentPostGPGKey
diff --git a/routers/common/markup.go b/routers/common/markup.go
index aaedc13de9b70..a1c2c37ac07a4 100644
--- a/routers/common/markup.go
+++ b/routers/common/markup.go
@@ -32,8 +32,10 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
case "markdown":
// Raw markdown
if err := markdown.RenderRaw(&markup.RenderContext{
- Ctx: ctx,
- URLPrefix: urlPrefix,
+ Ctx: ctx,
+ Links: markup.Links{
+ Base: urlPrefix,
+ },
}, strings.NewReader(text), ctx.Resp); err != nil {
ctx.Error(http.StatusInternalServerError, err.Error())
}
@@ -75,8 +77,10 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
}
if err := markup.Render(&markup.RenderContext{
- Ctx: ctx,
- URLPrefix: urlPrefix,
+ Ctx: ctx,
+ Links: markup.Links{
+ Base: urlPrefix,
+ },
Metas: meta,
IsWiki: wiki,
Type: markupType,
diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go
index 1838fe190ca25..5559b6af88f2c 100644
--- a/routers/web/admin/admin.go
+++ b/routers/web/admin/admin.go
@@ -12,6 +12,7 @@ import (
"time"
activities_model "code.gitea.io/gitea/models/activities"
+ "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/graceful"
@@ -27,6 +28,7 @@ import (
const (
tplDashboard base.TplName = "admin/dashboard"
+ tplSelfCheck base.TplName = "admin/self_check"
tplCron base.TplName = "admin/cron"
tplQueue base.TplName = "admin/queue"
tplStacktrace base.TplName = "admin/stacktrace"
@@ -172,6 +174,33 @@ func DashboardPost(ctx *context.Context) {
}
}
+func SelfCheck(ctx *context.Context) {
+ ctx.Data["PageIsAdminSelfCheck"] = true
+ r, err := db.CheckCollationsDefaultEngine()
+ if err != nil {
+ ctx.Flash.Error(fmt.Sprintf("CheckCollationsDefaultEngine: %v", err), true)
+ }
+
+ if r != nil {
+ ctx.Data["DatabaseType"] = setting.Database.Type
+ ctx.Data["DatabaseCheckResult"] = r
+ hasProblem := false
+ if !r.CollationEquals(r.DatabaseCollation, r.ExpectedCollation) {
+ ctx.Data["DatabaseCheckCollationMismatch"] = true
+ hasProblem = true
+ }
+ if !r.IsCollationCaseSensitive(r.DatabaseCollation) {
+ ctx.Data["DatabaseCheckCollationCaseInsensitive"] = true
+ hasProblem = true
+ }
+ ctx.Data["DatabaseCheckInconsistentCollationColumns"] = r.InconsistentCollationColumns
+ hasProblem = hasProblem || len(r.InconsistentCollationColumns) > 0
+
+ ctx.Data["DatabaseCheckHasProblems"] = hasProblem
+ }
+ ctx.HTML(http.StatusOK, tplSelfCheck)
+}
+
func CronTasks(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.monitor.cron")
ctx.Data["PageIsAdminMonitorCron"] = true
diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go
index 44c4fa7512548..8f6995b96f464 100644
--- a/routers/web/admin/users.go
+++ b/routers/web/admin/users.go
@@ -436,6 +436,12 @@ func EditUserPost(ctx *context.Context) {
}
+ // Check whether user is the last admin
+ if !form.Admin && user_model.IsLastAdminUser(ctx, u) {
+ ctx.RenderWithErr(ctx.Tr("auth.last_admin"), tplUserEdit, &form)
+ return
+ }
+
u.LoginName = form.LoginName
u.FullName = form.FullName
emailChanged := !strings.EqualFold(u.Email, form.Email)
@@ -503,7 +509,10 @@ func DeleteUser(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
case models.IsErrUserOwnPackages(err):
ctx.Flash.Error(ctx.Tr("admin.users.still_own_packages"))
- ctx.Redirect(setting.AppSubURL + "/admin/users/" + ctx.Params(":userid"))
+ ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
+ case models.IsErrDeleteLastAdminUser(err):
+ ctx.Flash.Error(ctx.Tr("auth.last_admin"))
+ ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")))
default:
ctx.ServerError("DeleteUser", err)
}
diff --git a/routers/web/feed/convert.go b/routers/web/feed/convert.go
index 4d4918a8fd87d..95b1062253229 100644
--- a/routers/web/feed/convert.go
+++ b/routers/web/feed/convert.go
@@ -51,9 +51,11 @@ func toReleaseLink(ctx *context.Context, act *activities_model.Action) string {
// If rendering fails, the original markdown text is returned
func renderMarkdown(ctx *context.Context, act *activities_model.Action, content string) string {
markdownCtx := &markup.RenderContext{
- Ctx: ctx,
- URLPrefix: act.GetRepoLink(ctx),
- Type: markdown.MarkupName,
+ Ctx: ctx,
+ Links: markup.Links{
+ Base: act.GetRepoLink(ctx),
+ },
+ Type: markdown.MarkupName,
Metas: map[string]string{
"user": act.GetRepoUserName(ctx),
"repo": act.GetRepoName(ctx),
@@ -199,7 +201,6 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
switch act.OpType {
case activities_model.ActionCommitRepo, activities_model.ActionMirrorSyncPush:
push := templates.ActionContent2Commits(act)
- repoLink := act.GetRepoAbsoluteLink(ctx)
for _, commit := range push.Commits {
if len(desc) != 0 {
@@ -208,7 +209,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
desc += fmt.Sprintf("%s\n%s",
html.EscapeString(fmt.Sprintf("%s/commit/%s", act.GetRepoAbsoluteLink(ctx), commit.Sha1)),
commit.Sha1,
- templates.RenderCommitMessage(ctx, commit.Message, repoLink, nil),
+ templates.RenderCommitMessage(ctx, commit.Message, nil),
)
}
@@ -288,9 +289,11 @@ func releasesToFeedItems(ctx *context.Context, releases []*repo_model.Release, i
link := &feeds.Link{Href: rel.HTMLURL()}
content, err = markdown.RenderString(&markup.RenderContext{
- Ctx: ctx,
- URLPrefix: rel.Repo.Link(),
- Metas: rel.Repo.ComposeMetas(ctx),
+ Ctx: ctx,
+ Links: markup.Links{
+ Base: rel.Repo.Link(),
+ },
+ Metas: rel.Repo.ComposeMetas(ctx),
}, rel.Note)
if err != nil {
diff --git a/routers/web/feed/profile.go b/routers/web/feed/profile.go
index ce86727e24820..04f84c0c8d40c 100644
--- a/routers/web/feed/profile.go
+++ b/routers/web/feed/profile.go
@@ -42,8 +42,10 @@ func showUserFeed(ctx *context.Context, formatType string) {
}
ctxUserDescription, err := markdown.RenderString(&markup.RenderContext{
- Ctx: ctx,
- URLPrefix: ctx.ContextUser.HTMLURL(),
+ Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.ContextUser.HTMLURL(),
+ },
Metas: map[string]string{
"user": ctx.ContextUser.GetDisplayName(),
},
diff --git a/routers/web/feed/release.go b/routers/web/feed/release.go
index fbfa11c63ecff..57b0c927665d0 100644
--- a/routers/web/feed/release.go
+++ b/routers/web/feed/release.go
@@ -6,6 +6,7 @@ package feed
import (
"time"
+ "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/context"
@@ -14,8 +15,9 @@ import (
// shows tags and/or releases on the repo as RSS / Atom feed
func ShowReleaseFeed(ctx *context.Context, repo *repo_model.Repository, isReleasesOnly bool, formatType string) {
- releases, err := repo_model.GetReleasesByRepoID(ctx, ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{
+ releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
IncludeTags: !isReleasesOnly,
+ RepoID: ctx.Repo.Repository.ID,
})
if err != nil {
ctx.ServerError("GetReleasesByRepoID", err)
diff --git a/routers/web/org/home.go b/routers/web/org/home.go
index cdf280ed4acc3..8bf02b2c42a74 100644
--- a/routers/web/org/home.go
+++ b/routers/web/org/home.go
@@ -5,6 +5,7 @@ package org
import (
"net/http"
+ "path"
"strings"
"code.gitea.io/gitea/models/db"
@@ -47,10 +48,8 @@ func Home(ctx *context.Context) {
ctx.Data["Title"] = org.DisplayName()
if len(org.Description) != 0 {
desc, err := markdown.RenderString(&markup.RenderContext{
- Ctx: ctx,
- URLPrefix: ctx.Repo.RepoLink,
- Metas: map[string]string{"mode": "document"},
- GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
+ Metas: map[string]string{"mode": "document"},
}, org.Description)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -173,14 +172,16 @@ func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repositor
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
log.Error("failed to GetBlobContent: %v", err)
} else {
- // Pass URLPrefix to markdown render for the full link of media elements.
- // The profile of default branch would be shown.
- prefix := profileDbRepo.Link() + "/src/branch/" + util.PathEscapeSegments(profileDbRepo.DefaultBranch)
if profileContent, err := markdown.RenderString(&markup.RenderContext{
- Ctx: ctx,
- GitRepo: profileGitRepo,
- URLPrefix: prefix,
- Metas: map[string]string{"mode": "document"},
+ Ctx: ctx,
+ GitRepo: profileGitRepo,
+ Links: markup.Links{
+ // Pass repo link to markdown render for the full link of media elements.
+ // The profile of default branch would be shown.
+ Base: profileDbRepo.Link(),
+ BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
+ },
+ Metas: map[string]string{"mode": "document"},
}, bytes); err != nil {
log.Error("failed to RenderString: %v", err)
} else {
diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go
index bd393e7fb7f1c..abb39caa5753e 100644
--- a/routers/web/repo/commit.go
+++ b/routers/web/repo/commit.go
@@ -7,7 +7,9 @@ package repo
import (
"errors"
"fmt"
+ "html/template"
"net/http"
+ "path"
"strings"
asymkey_model "code.gitea.io/gitea/models/asymkey"
@@ -21,7 +23,9 @@ import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitgraph"
"code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/gitdiff"
git_service "code.gitea.io/gitea/services/repository"
)
@@ -370,9 +374,21 @@ func Diff(ctx *context.Context) {
note := &git.Note{}
err = git.GetNote(ctx, ctx.Repo.GitRepo, commitID, note)
if err == nil {
- ctx.Data["Note"] = string(charset.ToUTF8WithFallback(note.Message))
ctx.Data["NoteCommit"] = note.Commit
ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit)
+ ctx.Data["NoteRendered"], err = markup.RenderCommitMessage(&markup.RenderContext{
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ BranchPath: path.Join("commit", util.PathEscapeSegments(commitID)),
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
+ }, template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message))))
+ if err != nil {
+ ctx.ServerError("RenderCommitMessage", err)
+ return
+ }
}
ctx.Data["BranchName"], err = commit.GetBranchName()
diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go
index f3b95b68fe5a2..042b8ed692acf 100644
--- a/routers/web/repo/compare.go
+++ b/routers/web/repo/compare.go
@@ -845,6 +845,7 @@ func CompareDiff(ctx *context.Context) {
}
}
+ ctx.Data["IsProjectsEnabled"] = ctx.Repo.CanWrite(unit.TypeProjects)
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
upload.AddUploadContext(ctx, "comment")
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index 14781be822da0..0d660e3b89fee 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -1436,12 +1436,13 @@ func ViewIssue(ctx *context.Context) {
}
}
ctx.Data["IssueWatch"] = iw
-
issue.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.Repo.RepoLink,
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, issue.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -1601,10 +1602,12 @@ func ViewIssue(ctx *context.Context) {
}
comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.Repo.RepoLink,
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, comment.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -1678,10 +1681,12 @@ func ViewIssue(ctx *context.Context) {
}
} else if comment.Type.HasContentSupport() {
comment.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.Repo.RepoLink,
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, comment.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -1962,7 +1967,7 @@ func ViewIssue(ctx *context.Context) {
return
}
- ctx.Data["BlockingDependencies"], ctx.Data["BlockingByDependenciesNotPermitted"] = checkBlockedByIssues(ctx, blocking)
+ ctx.Data["BlockingDependencies"], ctx.Data["BlockingDependenciesNotPermitted"] = checkBlockedByIssues(ctx, blocking)
if ctx.Written() {
return
}
@@ -2023,38 +2028,34 @@ func ViewIssue(ctx *context.Context) {
// checkBlockedByIssues return canRead and notPermitted
func checkBlockedByIssues(ctx *context.Context, blockers []*issues_model.DependencyInfo) (canRead, notPermitted []*issues_model.DependencyInfo) {
- var (
- lastRepoID int64
- lastPerm access_model.Permission
- )
- for i, blocker := range blockers {
+ repoPerms := make(map[int64]access_model.Permission)
+ repoPerms[ctx.Repo.Repository.ID] = ctx.Repo.Permission
+ for _, blocker := range blockers {
// Get the permissions for this repository
- perm := lastPerm
- if lastRepoID != blocker.Repository.ID {
- if blocker.Repository.ID == ctx.Repo.Repository.ID {
- perm = ctx.Repo.Permission
- } else {
- var err error
- perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
- if err != nil {
- ctx.ServerError("GetUserRepoPermission", err)
- return nil, nil
- }
+ // If the repo ID exists in the map, return the exist permissions
+ // else get the permission and add it to the map
+ var perm access_model.Permission
+ existPerm, ok := repoPerms[blocker.RepoID]
+ if ok {
+ perm = existPerm
+ } else {
+ var err error
+ perm, err = access_model.GetUserRepoPermission(ctx, &blocker.Repository, ctx.Doer)
+ if err != nil {
+ ctx.ServerError("GetUserRepoPermission", err)
+ return nil, nil
}
- lastRepoID = blocker.Repository.ID
+ repoPerms[blocker.RepoID] = perm
}
-
- // check permission
- if !perm.CanReadIssuesOrPulls(blocker.Issue.IsPull) {
- blockers[len(notPermitted)], blockers[i] = blocker, blockers[len(notPermitted)]
- notPermitted = blockers[:len(notPermitted)+1]
+ if perm.CanReadIssuesOrPulls(blocker.Issue.IsPull) {
+ canRead = append(canRead, blocker)
+ } else {
+ notPermitted = append(notPermitted, blocker)
}
}
- blockers = blockers[len(notPermitted):]
- sortDependencyInfo(blockers)
+ sortDependencyInfo(canRead)
sortDependencyInfo(notPermitted)
-
- return blockers, notPermitted
+ return canRead, notPermitted
}
func sortDependencyInfo(blockers []*issues_model.DependencyInfo) {
@@ -2238,10 +2239,12 @@ func UpdateIssueContent(ctx *context.Context) {
}
content, err := markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, issue.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -3147,10 +3150,12 @@ func UpdateCommentContent(ctx *context.Context) {
}
content, err := markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, comment.Content)
if err != nil {
ctx.ServerError("RenderString", err)
diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go
index fbecabb2b1f0f..19db2abd68d1c 100644
--- a/routers/web/repo/milestone.go
+++ b/routers/web/repo/milestone.go
@@ -81,10 +81,12 @@ func Milestones(ctx *context.Context) {
}
for _, m := range miles {
m.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.Repo.RepoLink,
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, m.Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -275,10 +277,12 @@ func MilestoneIssuesAndPulls(ctx *context.Context) {
}
milestone.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.Repo.RepoLink,
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, milestone.Content)
if err != nil {
ctx.ServerError("RenderString", err)
diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go
index 5694575b468de..4908bb796d9dc 100644
--- a/routers/web/repo/projects.go
+++ b/routers/web/repo/projects.go
@@ -90,10 +90,12 @@ func Projects(ctx *context.Context) {
for i := range projects {
projects[i].RenderedContent, err = markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.Repo.RepoLink,
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, projects[i].Description)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -357,10 +359,12 @@ func ViewProject(ctx *context.Context) {
ctx.Data["LinkedPRs"] = linkedPrsMap
project.RenderedContent, err = markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.Repo.RepoLink,
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, project.Description)
if err != nil {
ctx.ServerError("RenderString", err)
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index 39f9cefa5c669..32d82b2a643cb 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/repo/pull.go
@@ -1149,30 +1149,28 @@ func MergePullRequest(ctx *context.Context) {
switch {
case errors.Is(err, pull_service.ErrIsClosed):
if issue.IsPull {
- ctx.Flash.Error(ctx.Tr("repo.pulls.is_closed"))
+ ctx.JSONError(ctx.Tr("repo.pulls.is_closed"))
} else {
- ctx.Flash.Error(ctx.Tr("repo.issues.closed_title"))
+ ctx.JSONError(ctx.Tr("repo.issues.closed_title"))
}
case errors.Is(err, pull_service.ErrUserNotAllowedToMerge):
- ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed"))
+ ctx.JSONError(ctx.Tr("repo.pulls.update_not_allowed"))
case errors.Is(err, pull_service.ErrHasMerged):
- ctx.Flash.Error(ctx.Tr("repo.pulls.has_merged"))
+ ctx.JSONError(ctx.Tr("repo.pulls.has_merged"))
case errors.Is(err, pull_service.ErrIsWorkInProgress):
- ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_wip"))
+ ctx.JSONError(ctx.Tr("repo.pulls.no_merge_wip"))
case errors.Is(err, pull_service.ErrNotMergableState):
- ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready"))
+ ctx.JSONError(ctx.Tr("repo.pulls.no_merge_not_ready"))
case models.IsErrDisallowedToMerge(err):
- ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready"))
+ ctx.JSONError(ctx.Tr("repo.pulls.no_merge_not_ready"))
case asymkey_service.IsErrWontSign(err):
- ctx.Flash.Error(err.Error()) // has no translation ...
+ ctx.JSONError(err.Error()) // has no translation ...
case errors.Is(err, pull_service.ErrDependenciesLeft):
- ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
+ ctx.JSONError(ctx.Tr("repo.issues.dependency.pr_close_blocked"))
default:
ctx.ServerError("WebCheck", err)
- return
}
- ctx.Redirect(issue.Link())
return
}
@@ -1180,18 +1178,18 @@ func MergePullRequest(ctx *context.Context) {
if manuallyMerged {
if err := pull_service.MergedManually(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, form.MergeCommitID); err != nil {
switch {
-
case models.IsErrInvalidMergeStyle(err):
- ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
+ ctx.JSONError(ctx.Tr("repo.pulls.invalid_merge_option"))
case strings.Contains(err.Error(), "Wrong commit ID"):
- ctx.Flash.Error(ctx.Tr("repo.pulls.wrong_commit_id"))
+ ctx.JSONError(ctx.Tr("repo.pulls.wrong_commit_id"))
default:
ctx.ServerError("MergedManually", err)
- return
}
+
+ return
}
- ctx.Redirect(issue.Link())
+ ctx.JSONRedirect(issue.Link())
return
}
@@ -1221,15 +1219,14 @@ func MergePullRequest(ctx *context.Context) {
} else if scheduled {
// nothing more to do ...
ctx.Flash.Success(ctx.Tr("repo.pulls.auto_merge_newly_scheduled"))
- ctx.Redirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, pr.Index))
+ ctx.JSONRedirect(fmt.Sprintf("%s/pulls/%d", ctx.Repo.RepoLink, pr.Index))
return
}
}
if err := pull_service.Merge(ctx, pr, ctx.Doer, ctx.Repo.GitRepo, repo_model.MergeStyle(form.Do), form.HeadCommitID, message, false); err != nil {
if models.IsErrInvalidMergeStyle(err) {
- ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option"))
- ctx.Redirect(issue.Link())
+ ctx.JSONError(ctx.Tr("repo.pulls.invalid_merge_option"))
} else if models.IsErrMergeConflicts(err) {
conflictError := err.(models.ErrMergeConflicts)
flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
@@ -1242,7 +1239,7 @@ func MergePullRequest(ctx *context.Context) {
return
}
ctx.Flash.Error(flashError)
- ctx.Redirect(issue.Link())
+ ctx.JSONRedirect(issue.Link())
} else if models.IsErrRebaseConflicts(err) {
conflictError := err.(models.ErrRebaseConflicts)
flashError, err := ctx.RenderToString(tplAlertDetails, map[string]any{
@@ -1286,7 +1283,7 @@ func MergePullRequest(ctx *context.Context) {
}
ctx.Flash.Error(flashError)
}
- ctx.Redirect(issue.Link())
+ ctx.JSONRedirect(issue.Link())
} else {
ctx.ServerError("Merge", err)
}
@@ -1295,7 +1292,7 @@ func MergePullRequest(ctx *context.Context) {
log.Trace("Pull request merged: %d", pr.ID)
if err := stopTimerIfAvailable(ctx, ctx.Doer, issue); err != nil {
- ctx.ServerError("CreateOrStopIssueStopwatch", err)
+ ctx.ServerError("stopTimerIfAvailable", err)
return
}
@@ -1309,7 +1306,7 @@ func MergePullRequest(ctx *context.Context) {
return
}
if exist {
- ctx.Redirect(issue.Link())
+ ctx.JSONRedirect(issue.Link())
return
}
@@ -1327,7 +1324,7 @@ func MergePullRequest(ctx *context.Context) {
deleteBranch(ctx, pr, headRepo)
}
- ctx.Redirect(issue.Link())
+ ctx.JSONRedirect(issue.Link())
}
// CancelAutoMergePullRequest cancels a scheduled pr
@@ -1387,7 +1384,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
return
}
- labelIDs, assigneeIDs, milestoneID, _ := ValidateRepoMetas(ctx, *form, true)
+ labelIDs, assigneeIDs, milestoneID, projectID := ValidateRepoMetas(ctx, *form, true)
if ctx.Written() {
return
}
@@ -1465,6 +1462,17 @@ func CompareAndPullRequestPost(ctx *context.Context) {
return
}
+ if projectID > 0 {
+ if !ctx.Repo.CanWrite(unit.TypeProjects) {
+ ctx.Error(http.StatusBadRequest, "user hasn't the permission to write to projects")
+ return
+ }
+ if err := issues_model.ChangeProjectAssign(ctx, pullIssue, ctx.Doer, projectID); err != nil {
+ ctx.ServerError("ChangeProjectAssign", err)
+ return
+ }
+ }
+
log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID)
ctx.JSONRedirect(pullIssue.Link())
}
diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go
index 595d599fe1312..fdb247d413835 100644
--- a/routers/web/repo/release.go
+++ b/routers/web/repo/release.go
@@ -95,9 +95,10 @@ func Releases(ctx *context.Context) {
ListOptions: listOptions,
// only show draft releases for users who can write, read-only users shouldn't see draft releases.
IncludeDrafts: writeAccess,
+ RepoID: ctx.Repo.Repository.ID,
}
- releases, err := repo_model.GetReleasesByRepoID(ctx, ctx.Repo.Repository.ID, opts)
+ releases, err := db.Find[repo_model.Release](ctx, opts)
if err != nil {
ctx.ServerError("GetReleasesByRepoID", err)
return
@@ -135,10 +136,12 @@ func Releases(ctx *context.Context) {
}
r.Note, err = markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.Repo.RepoLink,
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, r.Note)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -194,9 +197,10 @@ func TagsList(ctx *context.Context) {
IncludeDrafts: true,
IncludeTags: true,
HasSha1: util.OptionalBoolTrue,
+ RepoID: ctx.Repo.Repository.ID,
}
- releases, err := repo_model.GetReleasesByRepoID(ctx, ctx.Repo.Repository.ID, opts)
+ releases, err := db.Find[repo_model.Release](ctx, opts)
if err != nil {
ctx.ServerError("GetReleasesByRepoID", err)
return
@@ -285,10 +289,12 @@ func SingleRelease(ctx *context.Context) {
}
}
release.Note, err = markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.Repo.RepoLink,
- Metas: ctx.Repo.Repository.ComposeMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ },
+ Metas: ctx.Repo.Repository.ComposeMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
+ Ctx: ctx,
}, release.Note)
if err != nil {
ctx.ServerError("RenderString", err)
diff --git a/routers/web/repo/release_test.go b/routers/web/repo/release_test.go
index a5a923d464ab9..c4a2c1904e3e0 100644
--- a/routers/web/repo/release_test.go
+++ b/routers/web/repo/release_test.go
@@ -6,6 +6,7 @@ package repo
import (
"testing"
+ "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest"
@@ -74,8 +75,9 @@ func TestCalReleaseNumCommitsBehind(t *testing.T) {
contexttest.LoadGitRepo(t, ctx)
t.Cleanup(func() { ctx.Repo.GitRepo.Close() })
- releases, err := repo_model.GetReleasesByRepoID(ctx, ctx.Repo.Repository.ID, repo_model.FindReleasesOptions{
+ releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
IncludeDrafts: ctx.Repo.CanWrite(unit.TypeReleases),
+ RepoID: ctx.Repo.Repository.ID,
})
assert.NoError(t, err)
diff --git a/routers/web/repo/render.go b/routers/web/repo/render.go
index 33476c1d2c05e..f2c6ab3f8fff4 100644
--- a/routers/web/repo/render.go
+++ b/routers/web/repo/render.go
@@ -57,16 +57,15 @@ func RenderFile(ctx *context.Context) {
return
}
- treeLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
- if ctx.Repo.TreePath != "" {
- treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
- }
-
ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'; sandbox allow-scripts")
err = markup.Render(&markup.RenderContext{
- Ctx: ctx,
- RelativePath: ctx.Repo.TreePath,
- URLPrefix: path.Dir(treeLink),
+ Ctx: ctx,
+ RelativePath: ctx.Repo.TreePath,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ BranchPath: ctx.Repo.BranchNameSubURL(),
+ TreePath: path.Dir(ctx.Repo.TreePath),
+ },
Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
GitRepo: ctx.Repo.GitRepo,
InStandalonePage: true,
diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go
index b16e28383629e..cd5eac057e040 100644
--- a/routers/web/repo/repo.go
+++ b/routers/web/repo/repo.go
@@ -379,7 +379,10 @@ func RedirectDownload(ctx *context.Context) {
)
tagNames := []string{vTag}
curRepo := ctx.Repo.Repository
- releases, err := repo_model.GetReleasesByRepoIDAndNames(ctx, curRepo.ID, tagNames)
+ releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
+ RepoID: curRepo.ID,
+ TagNames: tagNames,
+ })
if err != nil {
ctx.ServerError("RedirectDownload", err)
return
diff --git a/routers/web/repo/setting/default_branch.go b/routers/web/repo/setting/default_branch.go
index 9bf54e706a37e..c8a576e57663a 100644
--- a/routers/web/repo/setting/default_branch.go
+++ b/routers/web/repo/setting/default_branch.go
@@ -6,13 +6,12 @@ package setting
import (
"net/http"
- repo_model "code.gitea.io/gitea/models/repo"
+ git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/modules/context"
- "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers/web/repo"
- notify_service "code.gitea.io/gitea/services/notify"
+ repo_service "code.gitea.io/gitea/services/repository"
)
// SetDefaultBranchPost set default branch
@@ -35,23 +34,14 @@ func SetDefaultBranchPost(ctx *context.Context) {
}
branch := ctx.FormString("branch")
- if !ctx.Repo.GitRepo.IsBranchExist(branch) {
- ctx.Status(http.StatusNotFound)
- return
- } else if repo.DefaultBranch != branch {
- repo.DefaultBranch = branch
- if err := ctx.Repo.GitRepo.SetDefaultBranch(branch); err != nil {
- if !git.IsErrUnsupportedVersion(err) {
- ctx.ServerError("SetDefaultBranch", err)
- return
- }
- }
- if err := repo_model.UpdateDefaultBranch(ctx, repo); err != nil {
+ if err := repo_service.SetRepoDefaultBranch(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, branch); err != nil {
+ switch {
+ case git_model.IsErrBranchNotExist(err):
+ ctx.Status(http.StatusNotFound)
+ default:
ctx.ServerError("SetDefaultBranch", err)
- return
}
-
- notify_service.ChangeDefaultBranch(ctx, repo)
+ return
}
log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name)
diff --git a/routers/web/repo/setting/protected_branch.go b/routers/web/repo/setting/protected_branch.go
index 73adfec95a0ee..98d6977b81f0d 100644
--- a/routers/web/repo/setting/protected_branch.go
+++ b/routers/web/repo/setting/protected_branch.go
@@ -228,6 +228,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
protectBranch.BlockOnRejectedReviews = f.BlockOnRejectedReviews
protectBranch.BlockOnOfficialReviewRequests = f.BlockOnOfficialReviewRequests
protectBranch.DismissStaleApprovals = f.DismissStaleApprovals
+ protectBranch.IgnoreStaleApprovals = f.IgnoreStaleApprovals
protectBranch.RequireSignedCommits = f.RequireSignedCommits
protectBranch.ProtectedFilePatterns = f.ProtectedFilePatterns
protectBranch.UnprotectedFilePatterns = f.UnprotectedFilePatterns
diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go
index 289cf5c9aea57..e721b7c739972 100644
--- a/routers/web/repo/setting/setting.go
+++ b/routers/web/repo/setting/setting.go
@@ -594,7 +594,7 @@ func SettingsPost(ctx *context.Context) {
return
}
- if err := repo_model.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
+ if err := repo_service.UpdateRepositoryUnits(ctx, repo, units, deleteUnitTypes); err != nil {
ctx.ServerError("UpdateRepositoryUnits", err)
return
}
diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go
index 9cf0dff5d8e2f..c2daa3e5e684b 100644
--- a/routers/web/repo/view.go
+++ b/routers/web/repo/view.go
@@ -158,7 +158,7 @@ func findReadmeFileInEntries(ctx *context.Context, entries []*git.TreeEntry, try
return "", readmeFile, nil
}
-func renderDirectory(ctx *context.Context, treeLink string) {
+func renderDirectory(ctx *context.Context) {
entries := renderDirectoryFiles(ctx, 1*time.Second)
if ctx.Written() {
return
@@ -175,7 +175,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
return
}
- renderReadmeFile(ctx, subfolder, readmeFile, treeLink)
+ renderReadmeFile(ctx, subfolder, readmeFile)
}
// localizedExtensions prepends the provided language code with and without a
@@ -259,7 +259,7 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte,
return buf, dataRc, &fileInfo{st.IsText(), true, meta.Size, &meta.Pointer, st}, nil
}
-func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry, readmeTreelink string) {
+func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry) {
target := readmeFile
if readmeFile != nil && readmeFile.IsLink() {
target, _ = readmeFile.FollowLinks()
@@ -312,9 +312,13 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{
Ctx: ctx,
RelativePath: path.Join(ctx.Repo.TreePath, readmeFile.Name()), // ctx.Repo.TreePath is the directory not the Readme so we must append the Readme filename (and path).
- URLPrefix: path.Join(readmeTreelink, subfolder),
- Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ BranchPath: ctx.Repo.BranchNameSubURL(),
+ TreePath: path.Join(ctx.Repo.TreePath, subfolder),
+ },
+ Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
}, rd)
if err != nil {
log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
@@ -337,7 +341,7 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr
}
}
-func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink string) {
+func renderFile(ctx *context.Context, entry *git.TreeEntry) {
ctx.Data["IsViewFile"] = true
ctx.Data["HideRepoInfo"] = true
blob := entry.Blob()
@@ -351,7 +355,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
ctx.Data["FileIsSymlink"] = entry.IsLink()
ctx.Data["FileName"] = blob.Name()
- ctx.Data["RawFileLink"] = rawLink + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
+ ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
if ctx.Repo.TreePath == ".editorconfig" {
_, editorconfigWarning, editorconfigErr := ctx.Repo.GetEditorconfig(ctx.Repo.Commit)
@@ -479,9 +483,13 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
Ctx: ctx,
Type: markupType,
RelativePath: ctx.Repo.TreePath,
- URLPrefix: path.Dir(treeLink),
- Metas: metas,
- GitRepo: ctx.Repo.GitRepo,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ BranchPath: ctx.Repo.BranchNameSubURL(),
+ TreePath: path.Dir(ctx.Repo.TreePath),
+ },
+ Metas: metas,
+ GitRepo: ctx.Repo.GitRepo,
}, rd)
if err != nil {
ctx.ServerError("Render", err)
@@ -585,9 +593,13 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{
Ctx: ctx,
RelativePath: ctx.Repo.TreePath,
- URLPrefix: path.Dir(treeLink),
- Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
- GitRepo: ctx.Repo.GitRepo,
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ BranchPath: ctx.Repo.BranchNameSubURL(),
+ TreePath: path.Dir(ctx.Repo.TreePath),
+ },
+ Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
+ GitRepo: ctx.Repo.GitRepo,
}, rd)
if err != nil {
ctx.ServerError("Render", err)
@@ -945,14 +957,6 @@ func renderCode(ctx *context.Context) {
}
ctx.Data["Title"] = title
- branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
- treeLink := branchLink
- rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL()
-
- if len(ctx.Repo.TreePath) > 0 {
- treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
- }
-
// Get Topics of this repo
renderRepoTopics(ctx)
if ctx.Written() {
@@ -977,9 +981,9 @@ func renderCode(ctx *context.Context) {
}
if entry.IsDir() {
- renderDirectory(ctx, treeLink)
+ renderDirectory(ctx)
} else {
- renderFile(ctx, entry, treeLink, rawLink)
+ renderFile(ctx, entry)
}
if ctx.Written() {
return
@@ -1020,6 +1024,12 @@ func renderCode(ctx *context.Context) {
}
ctx.Data["Paths"] = paths
+
+ branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
+ treeLink := branchLink
+ if len(ctx.Repo.TreePath) > 0 {
+ treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
+ }
ctx.Data["TreeLink"] = treeLink
ctx.Data["TreeNames"] = treeNames
ctx.Data["BranchLink"] = branchLink
diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go
index 8ea18a186c4cb..4e09f046cf856 100644
--- a/routers/web/repo/wiki.go
+++ b/routers/web/repo/wiki.go
@@ -238,10 +238,12 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
}
rctx := &markup.RenderContext{
- Ctx: ctx,
- URLPrefix: ctx.Repo.RepoLink,
- Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
- IsWiki: true,
+ Ctx: ctx,
+ Metas: ctx.Repo.Repository.ComposeDocumentMetas(ctx),
+ Links: markup.Links{
+ Base: ctx.Repo.RepoLink,
+ },
+ IsWiki: true,
}
buf := &strings.Builder{}
diff --git a/routers/web/shared/user/header.go b/routers/web/shared/user/header.go
index 919a080b42c65..0f8d64e7b24d3 100644
--- a/routers/web/shared/user/header.go
+++ b/routers/web/shared/user/header.go
@@ -47,10 +47,8 @@ func PrepareContextForProfileBigAvatar(ctx *context.Context) {
if len(ctx.ContextUser.Description) != 0 {
content, err := markdown.RenderString(&markup.RenderContext{
- URLPrefix: ctx.Repo.RepoLink,
- Metas: map[string]string{"mode": "document"},
- GitRepo: ctx.Repo.GitRepo,
- Ctx: ctx,
+ Metas: map[string]string{"mode": "document"},
+ Ctx: ctx,
}, ctx.ContextUser.Description)
if err != nil {
ctx.ServerError("RenderString", err)
diff --git a/routers/web/user/home.go b/routers/web/user/home.go
index 204d4adbd4900..44920817c98f9 100644
--- a/routers/web/user/home.go
+++ b/routers/web/user/home.go
@@ -258,9 +258,11 @@ func Milestones(ctx *context.Context) {
}
milestones[i].RenderedContent, err = markdown.RenderString(&markup.RenderContext{
- URLPrefix: milestones[i].Repo.Link(),
- Metas: milestones[i].Repo.ComposeMetas(ctx),
- Ctx: ctx,
+ Links: markup.Links{
+ Base: milestones[i].Repo.Link(),
+ },
+ Metas: milestones[i].Repo.ComposeMetas(ctx),
+ Ctx: ctx,
}, milestones[i].Content)
if err != nil {
ctx.ServerError("RenderString", err)
@@ -663,7 +665,10 @@ func ShowSSHKeys(ctx *context.Context) {
// ShowGPGKeys output all the public GPG keys of user by uid
func ShowGPGKeys(ctx *context.Context) {
- keys, err := asymkey_model.ListGPGKeys(ctx, ctx.ContextUser.ID, db.ListOptions{})
+ keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
+ ListOptions: db.ListOptionsAll,
+ OwnerID: ctx.ContextUser.ID,
+ })
if err != nil {
ctx.ServerError("ListGPGKeys", err)
return
diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go
index a8ab3dde81d01..c5305ebcd9f4b 100644
--- a/routers/web/user/profile.go
+++ b/routers/web/user/profile.go
@@ -7,6 +7,7 @@ package user
import (
"fmt"
"net/http"
+ "path"
"strings"
activities_model "code.gitea.io/gitea/models/activities"
@@ -233,18 +234,19 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
log.Error("failed to GetBlobContent: %v", err)
} else {
- // Give the URLPrefix to the markdown render for the full link of media element.
- // the media link usually be like /[user]/[repoName]/media/branch/[branchName],
- // Eg. /Tom/.profile/media/branch/main
- // The branch shown on the profile page is the default branch, this need to be in sync with doc, see:
- // https://docs.gitea.com/usage/profile-readme
-
- prefix := profileDbRepo.Link() + "/src/branch/" + util.PathEscapeSegments(profileDbRepo.DefaultBranch)
if profileContent, err := markdown.RenderString(&markup.RenderContext{
- Ctx: ctx,
- GitRepo: profileGitRepo,
- URLPrefix: prefix,
- Metas: map[string]string{"mode": "document"},
+ Ctx: ctx,
+ GitRepo: profileGitRepo,
+ Links: markup.Links{
+ // Give the repo link to the markdown render for the full link of media element.
+ // the media link usually be like /[user]/[repoName]/media/branch/[branchName],
+ // Eg. /Tom/.profile/media/branch/main
+ // The branch shown on the profile page is the default branch, this need to be in sync with doc, see:
+ // https://docs.gitea.com/usage/profile-readme
+ Base: profileDbRepo.Link(),
+ BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)),
+ },
+ Metas: map[string]string{"mode": "document"},
}, bytes); err != nil {
log.Error("failed to RenderString: %v", err)
} else {
diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go
index 6d61d8021cc30..7a306636e0a56 100644
--- a/routers/web/user/setting/account.go
+++ b/routers/web/user/setting/account.go
@@ -244,6 +244,13 @@ func DeleteAccount(ctx *context.Context) {
return
}
+ // admin should not delete themself
+ if ctx.Doer.IsAdmin {
+ ctx.Flash.Error(ctx.Tr("form.admin_cannot_delete_self"))
+ ctx.Redirect(setting.AppSubURL + "/user/settings/account")
+ return
+ }
+
if err := user.DeleteUser(ctx, ctx.Doer, false); err != nil {
switch {
case models.IsErrUserOwnRepos(err):
@@ -255,6 +262,9 @@ func DeleteAccount(ctx *context.Context) {
case models.IsErrUserOwnPackages(err):
ctx.Flash.Error(ctx.Tr("form.still_own_packages"))
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
+ case models.IsErrDeleteLastAdminUser(err):
+ ctx.Flash.Error(ctx.Tr("auth.last_admin"))
+ ctx.Redirect(setting.AppSubURL + "/user/settings/account")
default:
ctx.ServerError("DeleteUser", err)
}
diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go
index 0dfb506fa9676..16410d06ff02f 100644
--- a/routers/web/user/setting/keys.go
+++ b/routers/web/user/setting/keys.go
@@ -277,18 +277,29 @@ func loadKeysData(ctx *context.Context) {
}
ctx.Data["ExternalKeys"] = externalKeys
- gpgkeys, err := asymkey_model.ListGPGKeys(ctx, ctx.Doer.ID, db.ListOptions{})
+ gpgkeys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
+ ListOptions: db.ListOptionsAll,
+ OwnerID: ctx.Doer.ID,
+ })
if err != nil {
ctx.ServerError("ListGPGKeys", err)
return
}
+ if err := asymkey_model.GPGKeyList(gpgkeys).LoadSubKeys(ctx); err != nil {
+ ctx.ServerError("LoadSubKeys", err)
+ return
+ }
ctx.Data["GPGKeys"] = gpgkeys
tokenToSign := asymkey_model.VerificationToken(ctx.Doer, 1)
// generate a new aes cipher using the csrfToken
ctx.Data["TokenToSign"] = tokenToSign
- principals, err := asymkey_model.ListPrincipalKeys(ctx, ctx.Doer.ID, db.ListOptions{})
+ principals, err := db.Find[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{
+ ListOptions: db.ListOptionsAll,
+ OwnerID: ctx.Doer.ID,
+ KeyTypes: []asymkey_model.KeyType{asymkey_model.KeyTypePrincipal},
+ })
if err != nil {
ctx.ServerError("ListPrincipalKeys", err)
return
diff --git a/routers/web/web.go b/routers/web/web.go
index 164c137f2a3b6..22f98d95de27f 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -678,6 +678,8 @@ func registerRoutes(m *web.Route) {
m.Get("", admin.Dashboard)
m.Post("", web.Bind(forms.AdminDashboardForm{}), admin.DashboardPost)
+ m.Get("/self_check", admin.SelfCheck)
+
m.Group("/config", func() {
m.Get("", admin.Config)
m.Post("", admin.ChangeConfig)
diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go
index 705661ae7a775..0618f15602ee5 100644
--- a/services/actions/notifier_helper.go
+++ b/services/actions/notifier_helper.go
@@ -117,6 +117,9 @@ func notify(ctx context.Context, input *notifyInput) error {
return nil
}
if unit_model.TypeActions.UnitGlobalDisabled() {
+ if err := actions_model.CleanRepoScheduleTasks(ctx, input.Repo); err != nil {
+ log.Error("CleanRepoScheduleTasks: %v", err)
+ }
return nil
}
if err := input.Repo.LoadUnits(ctx); err != nil {
@@ -153,7 +156,11 @@ func notify(ctx context.Context, input *notifyInput) error {
var detectedWorkflows []*actions_module.DetectedWorkflow
actionsConfig := input.Repo.MustGetUnit(ctx, unit_model.TypeActions).ActionsConfig()
- workflows, schedules, err := actions_module.DetectWorkflows(gitRepo, commit, input.Event, input.Payload)
+ workflows, schedules, err := actions_module.DetectWorkflows(gitRepo, commit,
+ input.Event,
+ input.Payload,
+ input.Event == webhook_module.HookEventPush && input.Ref == input.Repo.DefaultBranch,
+ )
if err != nil {
return fmt.Errorf("DetectWorkflows: %w", err)
}
@@ -167,7 +174,7 @@ func notify(ctx context.Context, input *notifyInput) error {
continue
}
- if wf.TriggerEvent != actions_module.GithubEventPullRequestTarget {
+ if wf.TriggerEvent.Name != actions_module.GithubEventPullRequestTarget {
detectedWorkflows = append(detectedWorkflows, wf)
}
}
@@ -180,7 +187,7 @@ func notify(ctx context.Context, input *notifyInput) error {
if err != nil {
return fmt.Errorf("gitRepo.GetCommit: %w", err)
}
- baseWorkflows, _, err := actions_module.DetectWorkflows(gitRepo, baseCommit, input.Event, input.Payload)
+ baseWorkflows, _, err := actions_module.DetectWorkflows(gitRepo, baseCommit, input.Event, input.Payload, false)
if err != nil {
return fmt.Errorf("DetectWorkflows: %w", err)
}
@@ -188,7 +195,7 @@ func notify(ctx context.Context, input *notifyInput) error {
log.Trace("repo %s with commit %s couldn't find pull_request_target workflows", input.Repo.RepoPath(), baseCommit.ID)
} else {
for _, wf := range baseWorkflows {
- if wf.TriggerEvent == actions_module.GithubEventPullRequestTarget {
+ if wf.TriggerEvent.Name == actions_module.GithubEventPullRequestTarget {
detectedWorkflows = append(detectedWorkflows, wf)
}
}
@@ -265,7 +272,7 @@ func handleWorkflows(
IsForkPullRequest: isForkPullRequest,
Event: input.Event,
EventPayload: string(p),
- TriggerEvent: dwf.TriggerEvent,
+ TriggerEvent: dwf.TriggerEvent.Name,
Status: actions_model.StatusWaiting,
}
if need, err := ifNeedApproval(ctx, run, input.Repo, input.Doer); err != nil {
@@ -289,6 +296,7 @@ func handleWorkflows(
run.RepoID,
run.Ref,
run.WorkflowID,
+ run.Event,
); err != nil {
log.Error("CancelRunningJobs: %v", err)
}
@@ -414,8 +422,8 @@ func handleSchedules(
log.Error("CountSchedules: %v", err)
return err
} else if count > 0 {
- if err := actions_model.DeleteScheduleTaskByRepo(ctx, input.Repo.ID); err != nil {
- log.Error("DeleteCronTaskByRepo: %v", err)
+ if err := actions_model.CleanRepoScheduleTasks(ctx, input.Repo); err != nil {
+ log.Error("CleanRepoScheduleTasks: %v", err)
}
}
@@ -456,19 +464,6 @@ func handleSchedules(
Specs: schedules,
Content: dwf.Content,
}
-
- // cancel running jobs if the event is push
- if run.Event == webhook_module.HookEventPush {
- // cancel running jobs of the same workflow
- if err := actions_model.CancelRunningJobs(
- ctx,
- run.RepoID,
- run.Ref,
- run.WorkflowID,
- ); err != nil {
- log.Error("CancelRunningJobs: %v", err)
- }
- }
crons = append(crons, run)
}
diff --git a/services/actions/schedule_tasks.go b/services/actions/schedule_tasks.go
index 8eef2b67bdc7a..e7aa4a39accfe 100644
--- a/services/actions/schedule_tasks.go
+++ b/services/actions/schedule_tasks.go
@@ -59,6 +59,7 @@ func startTasks(ctx context.Context) error {
row.RepoID,
row.Schedule.Ref,
row.Schedule.WorkflowID,
+ webhook_module.HookEventSchedule,
); err != nil {
log.Error("CancelRunningJobs: %v", err)
}
@@ -113,6 +114,7 @@ func CreateScheduleTask(ctx context.Context, cron *actions_model.ActionSchedule)
CommitSHA: cron.CommitSHA,
Event: cron.Event,
EventPayload: cron.EventPayload,
+ TriggerEvent: string(webhook_module.HookEventSchedule),
ScheduleID: cron.ID,
Status: actions_model.StatusWaiting,
}
diff --git a/services/asymkey/sign.go b/services/asymkey/sign.go
index 1598f32165349..0c4aac8156934 100644
--- a/services/asymkey/sign.go
+++ b/services/asymkey/sign.go
@@ -143,7 +143,10 @@ Loop:
case always:
break Loop
case pubkey:
- keys, err := asymkey_model.ListGPGKeys(ctx, u.ID, db.ListOptions{})
+ keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
+ OwnerID: u.ID,
+ IncludeSubKeys: true,
+ })
if err != nil {
return false, "", nil, err
}
@@ -179,7 +182,10 @@ Loop:
case always:
break Loop
case pubkey:
- keys, err := asymkey_model.ListGPGKeys(ctx, u.ID, db.ListOptions{})
+ keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
+ OwnerID: u.ID,
+ IncludeSubKeys: true,
+ })
if err != nil {
return false, "", nil, err
}
@@ -232,7 +238,10 @@ Loop:
case always:
break Loop
case pubkey:
- keys, err := asymkey_model.ListGPGKeys(ctx, u.ID, db.ListOptions{})
+ keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
+ OwnerID: u.ID,
+ IncludeSubKeys: true,
+ })
if err != nil {
return false, "", nil, err
}
@@ -294,7 +303,10 @@ Loop:
case always:
break Loop
case pubkey:
- keys, err := asymkey_model.ListGPGKeys(ctx, u.ID, db.ListOptions{})
+ keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
+ OwnerID: u.ID,
+ IncludeSubKeys: true,
+ })
if err != nil {
return false, "", nil, err
}
diff --git a/services/convert/convert.go b/services/convert/convert.go
index 366782390a758..ca3ec32a4042f 100644
--- a/services/convert/convert.go
+++ b/services/convert/convert.go
@@ -158,6 +158,7 @@ func ToBranchProtection(ctx context.Context, bp *git_model.ProtectedBranch) *api
BlockOnOfficialReviewRequests: bp.BlockOnOfficialReviewRequests,
BlockOnOutdatedBranch: bp.BlockOnOutdatedBranch,
DismissStaleApprovals: bp.DismissStaleApprovals,
+ IgnoreStaleApprovals: bp.IgnoreStaleApprovals,
RequireSignedCommits: bp.RequireSignedCommits,
ProtectedFilePatterns: bp.ProtectedFilePatterns,
UnprotectedFilePatterns: bp.UnprotectedFilePatterns,
@@ -308,40 +309,38 @@ func ToTeam(ctx context.Context, team *organization.Team, loadOrg ...bool) (*api
// ToTeams convert models.Team list to api.Team list
func ToTeams(ctx context.Context, teams []*organization.Team, loadOrgs bool) ([]*api.Team, error) {
- if len(teams) == 0 || teams[0] == nil {
- return nil, nil
- }
-
cache := make(map[int64]*api.Organization)
- apiTeams := make([]*api.Team, len(teams))
- for i := range teams {
- if err := teams[i].LoadUnits(ctx); err != nil {
+ apiTeams := make([]*api.Team, 0, len(teams))
+ for _, t := range teams {
+ if err := t.LoadUnits(ctx); err != nil {
return nil, err
}
- apiTeams[i] = &api.Team{
- ID: teams[i].ID,
- Name: teams[i].Name,
- Description: teams[i].Description,
- IncludesAllRepositories: teams[i].IncludesAllRepositories,
- CanCreateOrgRepo: teams[i].CanCreateOrgRepo,
- Permission: teams[i].AccessMode.String(),
- Units: teams[i].GetUnitNames(),
- UnitsMap: teams[i].GetUnitsMap(),
+ apiTeam := &api.Team{
+ ID: t.ID,
+ Name: t.Name,
+ Description: t.Description,
+ IncludesAllRepositories: t.IncludesAllRepositories,
+ CanCreateOrgRepo: t.CanCreateOrgRepo,
+ Permission: t.AccessMode.String(),
+ Units: t.GetUnitNames(),
+ UnitsMap: t.GetUnitsMap(),
}
if loadOrgs {
- apiOrg, ok := cache[teams[i].OrgID]
+ apiOrg, ok := cache[t.OrgID]
if !ok {
- org, err := organization.GetOrgByID(ctx, teams[i].OrgID)
+ org, err := organization.GetOrgByID(ctx, t.OrgID)
if err != nil {
return nil, err
}
apiOrg = ToOrganization(ctx, org)
- cache[teams[i].OrgID] = apiOrg
+ cache[t.OrgID] = apiOrg
}
- apiTeams[i].Organization = apiOrg
+ apiTeam.Organization = apiOrg
}
+
+ apiTeams = append(apiTeams, apiTeam)
}
return apiTeams, nil
}
diff --git a/services/convert/pull_review.go b/services/convert/pull_review.go
index 0332606285d18..aa7ad68a47856 100644
--- a/services/convert/pull_review.go
+++ b/services/convert/pull_review.go
@@ -21,15 +21,9 @@ func ToPullReview(ctx context.Context, r *issues_model.Review, doer *user_model.
r.Reviewer = user_model.NewGhostUser()
}
- apiTeam, err := ToTeam(ctx, r.ReviewerTeam)
- if err != nil {
- return nil, err
- }
-
result := &api.PullReview{
ID: r.ID,
Reviewer: ToUser(ctx, r.Reviewer, doer),
- ReviewerTeam: apiTeam,
State: api.ReviewStateUnknown,
Body: r.Content,
CommitID: r.CommitID,
@@ -43,6 +37,14 @@ func ToPullReview(ctx context.Context, r *issues_model.Review, doer *user_model.
HTMLPullURL: r.Issue.HTMLURL(),
}
+ if r.ReviewerTeam != nil {
+ var err error
+ result.ReviewerTeam, err = ToTeam(ctx, r.ReviewerTeam)
+ if err != nil {
+ return nil, err
+ }
+ }
+
switch r.Type {
case issues_model.ReviewTypeApprove:
result.State = api.ReviewStateApproved
diff --git a/services/convert/repository.go b/services/convert/repository.go
index 71038cd0629cb..c16180c0afc30 100644
--- a/services/convert/repository.go
+++ b/services/convert/repository.go
@@ -8,6 +8,7 @@ import (
"time"
"code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
@@ -133,7 +134,11 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR
return nil
}
- numReleases, _ := repo_model.GetReleaseCountByRepoID(ctx, repo.ID, repo_model.FindReleasesOptions{IncludeDrafts: false, IncludeTags: false})
+ numReleases, _ := db.Count[repo_model.Release](ctx, repo_model.FindReleasesOptions{
+ IncludeDrafts: false,
+ IncludeTags: false,
+ RepoID: repo.ID,
+ })
mirrorInterval := ""
var mirrorUpdated time.Time
diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
index 86599000a5882..780fc8800098a 100644
--- a/services/forms/repo_form.go
+++ b/services/forms/repo_form.go
@@ -212,6 +212,7 @@ type ProtectBranchForm struct {
BlockOnOfficialReviewRequests bool
BlockOnOutdatedBranch bool
DismissStaleApprovals bool
+ IgnoreStaleApprovals bool
RequireSignedCommits bool
ProtectedFilePatterns string
UnprotectedFilePatterns string
diff --git a/services/mailer/mail.go b/services/mailer/mail.go
index b597dd0487cd9..cf80333608248 100644
--- a/services/mailer/mail.go
+++ b/services/mailer/mail.go
@@ -220,9 +220,11 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
// This is the body of the new issue or comment, not the mail body
body, err := markdown.RenderString(&markup.RenderContext{
- Ctx: ctx,
- URLPrefix: ctx.Issue.Repo.HTMLURL(),
- Metas: ctx.Issue.Repo.ComposeMetas(ctx),
+ Ctx: ctx,
+ Links: markup.Links{
+ Base: ctx.Issue.Repo.HTMLURL(),
+ },
+ Metas: ctx.Issue.Repo.ComposeMetas(ctx),
}, ctx.Content)
if err != nil {
return nil, err
diff --git a/services/mailer/mail_release.go b/services/mailer/mail_release.go
index 88973a6be2160..801c2476c2752 100644
--- a/services/mailer/mail_release.go
+++ b/services/mailer/mail_release.go
@@ -57,9 +57,11 @@ func mailNewRelease(ctx context.Context, lang string, tos []string, rel *repo_mo
var err error
rel.RenderedNote, err = markdown.RenderString(&markup.RenderContext{
- Ctx: ctx,
- URLPrefix: rel.Repo.Link(),
- Metas: rel.Repo.ComposeMetas(ctx),
+ Ctx: ctx,
+ Links: markup.Links{
+ Base: rel.Repo.HTMLURL(),
+ },
+ Metas: rel.Repo.ComposeMetas(ctx),
}, rel.Note)
if err != nil {
log.Error("markdown.RenderString(%d): %v", rel.RepoID, err)
diff --git a/services/migrations/gitea_uploader_test.go b/services/migrations/gitea_uploader_test.go
index 3dec3a26fc4a9..4ec0361dfb467 100644
--- a/services/migrations/gitea_uploader_test.go
+++ b/services/migrations/gitea_uploader_test.go
@@ -83,22 +83,24 @@ func TestGiteaUploadRepo(t *testing.T) {
assert.NoError(t, err)
assert.Len(t, labels, 12)
- releases, err := repo_model.GetReleasesByRepoID(db.DefaultContext, repo.ID, repo_model.FindReleasesOptions{
+ releases, err := db.Find[repo_model.Release](db.DefaultContext, repo_model.FindReleasesOptions{
ListOptions: db.ListOptions{
PageSize: 10,
Page: 0,
},
IncludeTags: true,
+ RepoID: repo.ID,
})
assert.NoError(t, err)
assert.Len(t, releases, 8)
- releases, err = repo_model.GetReleasesByRepoID(db.DefaultContext, repo.ID, repo_model.FindReleasesOptions{
+ releases, err = db.Find[repo_model.Release](db.DefaultContext, repo_model.FindReleasesOptions{
ListOptions: db.ListOptions{
PageSize: 10,
Page: 0,
},
IncludeTags: false,
+ RepoID: repo.ID,
})
assert.NoError(t, err)
assert.Len(t, releases, 1)
diff --git a/services/packages/rpm/repository.go b/services/packages/rpm/repository.go
index 7e718d321fd7f..7a49efde4f656 100644
--- a/services/packages/rpm/repository.go
+++ b/services/packages/rpm/repository.go
@@ -125,17 +125,18 @@ type packageData struct {
type packageCache = map[*packages_model.PackageFile]*packageData
-// BuildRepositoryFiles builds metadata files for the repository
-func BuildRepositoryFiles(ctx context.Context, ownerID int64) error {
+// BuildSpecificRepositoryFiles builds metadata files for the repository
+func BuildRepositoryFiles(ctx context.Context, ownerID int64, compositeKey string) error {
pv, err := GetOrCreateRepositoryVersion(ctx, ownerID)
if err != nil {
return err
}
pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
- OwnerID: ownerID,
- PackageType: packages_model.TypeRpm,
- Query: "%.rpm",
+ OwnerID: ownerID,
+ PackageType: packages_model.TypeRpm,
+ Query: "%.rpm",
+ CompositeKey: compositeKey,
})
if err != nil {
return err
@@ -194,15 +195,15 @@ func BuildRepositoryFiles(ctx context.Context, ownerID int64) error {
cache[pf] = pd
}
- primary, err := buildPrimary(ctx, pv, pfs, cache)
+ primary, err := buildPrimary(ctx, pv, pfs, cache, compositeKey)
if err != nil {
return err
}
- filelists, err := buildFilelists(ctx, pv, pfs, cache)
+ filelists, err := buildFilelists(ctx, pv, pfs, cache, compositeKey)
if err != nil {
return err
}
- other, err := buildOther(ctx, pv, pfs, cache)
+ other, err := buildOther(ctx, pv, pfs, cache, compositeKey)
if err != nil {
return err
}
@@ -216,11 +217,12 @@ func BuildRepositoryFiles(ctx context.Context, ownerID int64) error {
filelists,
other,
},
+ compositeKey,
)
}
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#repomd-xml
-func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID int64, data []*repoData) error {
+func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID int64, data []*repoData, compositeKey string) error {
type Repomd struct {
XMLName xml.Name `xml:"repomd"`
Xmlns string `xml:"xmlns,attr"`
@@ -275,7 +277,8 @@ func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID
pv,
&packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{
- Filename: file.Name,
+ Filename: file.Name,
+ CompositeKey: compositeKey,
},
Creator: user_model.NewGhostUser(),
Data: file.Data,
@@ -292,7 +295,7 @@ func buildRepomd(ctx context.Context, pv *packages_model.PackageVersion, ownerID
}
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#primary-xml
-func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache) (*repoData, error) {
+func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, compositeKey string) (*repoData, error) {
type Version struct {
Epoch string `xml:"epoch,attr"`
Version string `xml:"ver,attr"`
@@ -372,7 +375,7 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []
files = append(files, f)
}
}
-
+ packageVersion := fmt.Sprintf("%s-%s", pd.FileMetadata.Version, pd.FileMetadata.Release)
packages = append(packages, &Package{
Type: "rpm",
Name: pd.Package.Name,
@@ -401,7 +404,7 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []
Archive: pd.FileMetadata.ArchiveSize,
},
Location: Location{
- Href: fmt.Sprintf("package/%s/%s/%s", url.PathEscape(pd.Package.Name), url.PathEscape(pd.Version.Version), url.PathEscape(pd.FileMetadata.Architecture)),
+ Href: fmt.Sprintf("package/%s/%s/%s/%s", url.PathEscape(pd.Package.Name), url.PathEscape(packageVersion), url.PathEscape(pd.FileMetadata.Architecture), url.PathEscape(fmt.Sprintf("%s-%s.%s.rpm", pd.Package.Name, packageVersion, pd.FileMetadata.Architecture))),
},
Format: Format{
License: pd.VersionMetadata.License,
@@ -431,11 +434,11 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs []
XmlnsRpm: "http://linux.duke.edu/metadata/rpm",
PackageCount: len(pfs),
Packages: packages,
- })
+ }, compositeKey)
}
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#filelists-xml
-func buildFilelists(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache) (*repoData, error) { //nolint:dupl
+func buildFilelists(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, compositeKey string) (*repoData, error) { //nolint:dupl
type Version struct {
Epoch string `xml:"epoch,attr"`
Version string `xml:"ver,attr"`
@@ -478,11 +481,12 @@ func buildFilelists(ctx context.Context, pv *packages_model.PackageVersion, pfs
Xmlns: "http://linux.duke.edu/metadata/other",
PackageCount: len(pfs),
Packages: packages,
- })
+ },
+ compositeKey)
}
// https://docs.pulpproject.org/en/2.19/plugins/pulp_rpm/tech-reference/rpm.html#other-xml
-func buildOther(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache) (*repoData, error) { //nolint:dupl
+func buildOther(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, compositeKey string) (*repoData, error) { //nolint:dupl
type Version struct {
Epoch string `xml:"epoch,attr"`
Version string `xml:"ver,attr"`
@@ -525,7 +529,7 @@ func buildOther(ctx context.Context, pv *packages_model.PackageVersion, pfs []*p
Xmlns: "http://linux.duke.edu/metadata/other",
PackageCount: len(pfs),
Packages: packages,
- })
+ }, compositeKey)
}
// writtenCounter counts all written bytes
@@ -545,10 +549,8 @@ func (wc *writtenCounter) Written() int64 {
return wc.written
}
-func addDataAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion, filetype string, obj any) (*repoData, error) {
+func addDataAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion, filetype string, obj any, compositeKey string) (*repoData, error) {
content, _ := packages_module.NewHashedBuffer()
- defer content.Close()
-
gzw := gzip.NewWriter(content)
wc := &writtenCounter{}
h := sha256.New()
@@ -571,7 +573,8 @@ func addDataAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion,
pv,
&packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{
- Filename: filename,
+ Filename: filename,
+ CompositeKey: compositeKey,
},
Creator: user_model.NewGhostUser(),
Data: content,
diff --git a/services/pull/pull.go b/services/pull/pull.go
index 6094a4ed31b9e..d1630f3792c07 100644
--- a/services/pull/pull.go
+++ b/services/pull/pull.go
@@ -4,6 +4,7 @@
package pull
import (
+ "bytes"
"context"
"fmt"
"io"
@@ -422,9 +423,11 @@ func checkIfPRContentChanged(ctx context.Context, pr *issues_model.PullRequest,
return false, fmt.Errorf("unable to open pipe for to run diff: %w", err)
}
+ stderr := new(bytes.Buffer)
if err := cmd.Run(&git.RunOpts{
Dir: prCtx.tmpBasePath,
Stdout: stdoutWriter,
+ Stderr: stderr,
PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error {
_ = stdoutWriter.Close()
defer func() {
@@ -436,6 +439,7 @@ func checkIfPRContentChanged(ctx context.Context, pr *issues_model.PullRequest,
if err == util.ErrNotEmpty {
return true, nil
}
+ err = git.ConcatenateError(err, stderr.String())
log.Error("Unable to run diff on %s %s %s in tempRepo for PR[%d]%s/%s...%s/%s: Error: %v",
newCommitID, oldCommitID, base,
diff --git a/services/repository/archiver/archiver.go b/services/repository/archiver/archiver.go
index b73d0eed4809a..c2ad4d484a69e 100644
--- a/services/repository/archiver/archiver.go
+++ b/services/repository/archiver/archiver.go
@@ -324,7 +324,7 @@ func DeleteOldRepositoryArchives(ctx context.Context, olderThan time.Duration) e
log.Trace("Doing: ArchiveCleanup")
for {
- archivers, err := repo_model.FindRepoArchives(ctx, repo_model.FindRepoArchiversOption{
+ archivers, err := db.Find[repo_model.RepoArchiver](ctx, repo_model.FindRepoArchiversOption{
ListOptions: db.ListOptions{
PageSize: 100,
Page: 1,
diff --git a/services/repository/branch.go b/services/repository/branch.go
index 7254778763371..6ddc6badfa6e2 100644
--- a/services/repository/branch.go
+++ b/services/repository/branch.go
@@ -10,6 +10,7 @@ import (
"strings"
"code.gitea.io/gitea/models"
+ actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
issues_model "code.gitea.io/gitea/models/issues"
@@ -22,6 +23,7 @@ import (
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
+ webhook_module "code.gitea.io/gitea/modules/webhook"
notify_service "code.gitea.io/gitea/services/notify"
files_service "code.gitea.io/gitea/services/repository/files"
@@ -308,13 +310,28 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_m
return "from_not_exist", nil
}
- if err := git_model.RenameBranch(ctx, repo, from, to, func(isDefault bool) error {
+ if err := git_model.RenameBranch(ctx, repo, from, to, func(ctx context.Context, isDefault bool) error {
err2 := gitRepo.RenameBranch(from, to)
if err2 != nil {
return err2
}
if isDefault {
+ // if default branch changed, we need to delete all schedules and cron jobs
+ if err := actions_model.DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
+ log.Error("DeleteCronTaskByRepo: %v", err)
+ }
+ // cancel running cron jobs of this repository and delete old schedules
+ if err := actions_model.CancelRunningJobs(
+ ctx,
+ repo.ID,
+ from,
+ "",
+ webhook_module.HookEventSchedule,
+ ); err != nil {
+ log.Error("CancelRunningJobs: %v", err)
+ }
+
err2 = gitRepo.SetDefaultBranch(to)
if err2 != nil {
return err2
@@ -450,3 +467,50 @@ func AddAllRepoBranchesToSyncQueue(ctx context.Context, doerID int64) error {
}
return nil
}
+
+func SetRepoDefaultBranch(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, newBranchName string) error {
+ if repo.DefaultBranch == newBranchName {
+ return nil
+ }
+
+ if !gitRepo.IsBranchExist(newBranchName) {
+ return git_model.ErrBranchNotExist{
+ BranchName: newBranchName,
+ }
+ }
+
+ oldDefaultBranchName := repo.DefaultBranch
+ repo.DefaultBranch = newBranchName
+ if err := db.WithTx(ctx, func(ctx context.Context) error {
+ if err := repo_model.UpdateDefaultBranch(ctx, repo); err != nil {
+ return err
+ }
+
+ if err := actions_model.DeleteScheduleTaskByRepo(ctx, repo.ID); err != nil {
+ log.Error("DeleteCronTaskByRepo: %v", err)
+ }
+ // cancel running cron jobs of this repository and delete old schedules
+ if err := actions_model.CancelRunningJobs(
+ ctx,
+ repo.ID,
+ oldDefaultBranchName,
+ "",
+ webhook_module.HookEventSchedule,
+ ); err != nil {
+ log.Error("CancelRunningJobs: %v", err)
+ }
+
+ if err := gitRepo.SetDefaultBranch(newBranchName); err != nil {
+ if !git.IsErrUnsupportedVersion(err) {
+ return err
+ }
+ }
+ return nil
+ }); err != nil {
+ return err
+ }
+
+ notify_service.ChangeDefaultBranch(ctx, repo)
+
+ return nil
+}
diff --git a/services/repository/push.go b/services/repository/push.go
index 3bc7a78cb955d..e86eebde8110b 100644
--- a/services/repository/push.go
+++ b/services/repository/push.go
@@ -327,9 +327,12 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo
lowerTags = append(lowerTags, strings.ToLower(tag))
}
- releases, err := repo_model.GetReleasesByRepoIDAndNames(ctx, repo.ID, lowerTags)
+ releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
+ RepoID: repo.ID,
+ TagNames: lowerTags,
+ })
if err != nil {
- return fmt.Errorf("GetReleasesByRepoIDAndNames: %w", err)
+ return fmt.Errorf("db.Find[repo_model.Release]: %w", err)
}
relMap := make(map[string]*repo_model.Release)
for _, rel := range releases {
diff --git a/services/repository/setting.go b/services/repository/setting.go
new file mode 100644
index 0000000000000..6496ac4014c11
--- /dev/null
+++ b/services/repository/setting.go
@@ -0,0 +1,47 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repository
+
+import (
+ "context"
+ "slices"
+
+ actions_model "code.gitea.io/gitea/models/actions"
+ "code.gitea.io/gitea/models/db"
+ repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/models/unit"
+ "code.gitea.io/gitea/modules/log"
+)
+
+// UpdateRepositoryUnits updates a repository's units
+func UpdateRepositoryUnits(ctx context.Context, repo *repo_model.Repository, units []repo_model.RepoUnit, deleteUnitTypes []unit.Type) (err error) {
+ ctx, committer, err := db.TxContext(ctx)
+ if err != nil {
+ return err
+ }
+ defer committer.Close()
+
+ // Delete existing settings of units before adding again
+ for _, u := range units {
+ deleteUnitTypes = append(deleteUnitTypes, u.Type)
+ }
+
+ if slices.Contains(deleteUnitTypes, unit.TypeActions) {
+ if err := actions_model.CleanRepoScheduleTasks(ctx, repo); err != nil {
+ log.Error("CleanRepoScheduleTasks: %v", err)
+ }
+ }
+
+ if _, err = db.GetEngine(ctx).Where("repo_id = ?", repo.ID).In("type", deleteUnitTypes).Delete(new(repo_model.RepoUnit)); err != nil {
+ return err
+ }
+
+ if len(units) > 0 {
+ if err = db.Insert(ctx, units); err != nil {
+ return err
+ }
+ }
+
+ return committer.Commit()
+}
diff --git a/services/user/delete.go b/services/user/delete.go
index 748f5c2d1e9a8..0e9c86617146e 100644
--- a/services/user/delete.go
+++ b/services/user/delete.go
@@ -159,7 +159,9 @@ func deleteUser(ctx context.Context, u *user_model.User, purge bool) (err error)
// ***** END: PublicKey *****
// ***** START: GPGPublicKey *****
- keys, err := asymkey_model.ListGPGKeys(ctx, u.ID, db.ListOptions{})
+ keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
+ OwnerID: u.ID,
+ })
if err != nil {
return fmt.Errorf("ListGPGKeys: %w", err)
}
diff --git a/services/user/user.go b/services/user/user.go
index 932e359c9f997..8bf083192fa02 100644
--- a/services/user/user.go
+++ b/services/user/user.go
@@ -129,6 +129,10 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error {
return fmt.Errorf("%s is an organization not a user", u.Name)
}
+ if user_model.IsLastAdminUser(ctx, u) {
+ return models.ErrDeleteLastAdminUser{UID: u.ID}
+ }
+
if purge {
// Disable the user first
// NOTE: This is deliberately not within a transaction as it must disable the user immediately to prevent any further action by the user to be purged.
@@ -295,7 +299,8 @@ func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error {
}
if err := DeleteUser(ctx, u, false); err != nil {
// Ignore users that were set inactive by admin.
- if models.IsErrUserOwnRepos(err) || models.IsErrUserHasOrgs(err) || models.IsErrUserOwnPackages(err) {
+ if models.IsErrUserOwnRepos(err) || models.IsErrUserHasOrgs(err) ||
+ models.IsErrUserOwnPackages(err) || models.IsErrDeleteLastAdminUser(err) {
continue
}
return err
diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go
index f98854c8dd5f6..ce54a00da73b4 100644
--- a/services/wiki/wiki.go
+++ b/services/wiki/wiki.go
@@ -19,6 +19,7 @@ import (
repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/sync"
asymkey_service "code.gitea.io/gitea/services/asymkey"
+ repo_service "code.gitea.io/gitea/services/repository"
)
// TODO: use clustered lock (unique queue? or *abuse* cache)
@@ -350,7 +351,7 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model
// DeleteWiki removes the actual and local copy of repository wiki.
func DeleteWiki(ctx context.Context, repo *repo_model.Repository) error {
- if err := repo_model.UpdateRepositoryUnits(ctx, repo, nil, []unit.Type{unit.TypeWiki}); err != nil {
+ if err := repo_service.UpdateRepositoryUnits(ctx, repo, nil, []unit.Type{unit.TypeWiki}); err != nil {
return err
}
diff --git a/templates/admin/navbar.tmpl b/templates/admin/navbar.tmpl
index b22db1d1fc8f4..fa79f0f7598a8 100644
--- a/templates/admin/navbar.tmpl
+++ b/templates/admin/navbar.tmpl
@@ -4,6 +4,9 @@
{{ctx.Locale.Tr "admin.dashboard"}}
+
+ {{ctx.Locale.Tr "admin.self_check"}}
+