mirror of
https://github.com/go-gitea/gitea.git
synced 2025-02-20 11:43:57 +08:00
Merge branch 'main' into feat/actions-delete-workflow-run
This commit is contained in:
commit
5576b10604
@ -403,7 +403,7 @@ module.exports = {
|
||||
'github/a11y-svg-has-accessible-name': [0],
|
||||
'github/array-foreach': [0],
|
||||
'github/async-currenttarget': [2],
|
||||
'github/async-preventdefault': [2],
|
||||
'github/async-preventdefault': [0], // https://github.com/github/eslint-plugin-github/issues/599
|
||||
'github/authenticity-token': [0],
|
||||
'github/get-attribute': [0],
|
||||
'github/js-class-name': [0],
|
||||
|
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -13,5 +13,5 @@ contact_links:
|
||||
url: https://docs.gitea.com/help/faq
|
||||
about: Please check if your question isn't mentioned here.
|
||||
- name: Crowdin Translations
|
||||
url: https://crowdin.com/project/gitea
|
||||
url: https://translate.gitea.com
|
||||
about: Translations are managed here.
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@ _test
|
||||
|
||||
# IntelliJ
|
||||
.idea
|
||||
.run
|
||||
|
||||
# IntelliJ Gateway
|
||||
.uuid
|
||||
|
@ -182,7 +182,7 @@ Here's how to run the test suite:
|
||||
|
||||
## Translation
|
||||
|
||||
All translation work happens on [Crowdin](https://crowdin.com/project/gitea).
|
||||
All translation work happens on [Crowdin](https://translate.gitea.com).
|
||||
The only translation that is maintained in this repository is [the English translation](https://github.com/go-gitea/gitea/blob/main/options/locale/locale_en-US.ini).
|
||||
It is synced regularly with Crowdin. \
|
||||
Other locales on main branch **should not** be updated manually as they will be overwritten with each sync. \
|
||||
|
42
README.md
42
README.md
@ -9,7 +9,7 @@
|
||||
[](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
|
||||
[](https://opensource.org/licenses/MIT "License: MIT")
|
||||
[](https://gitpod.io/#https://github.com/go-gitea/gitea)
|
||||
[](https://crowdin.com/project/gitea "Crowdin")
|
||||
[](https://translate.gitea.com "Crowdin")
|
||||
|
||||
[View this document in Chinese](./README_ZH.md)
|
||||
|
||||
@ -31,6 +31,14 @@ For accessing free Gitea service (with a limited number of repositories), you ca
|
||||
|
||||
To quickly deploy your own dedicated Gitea instance on Gitea Cloud, you can start a free trial at [cloud.gitea.com](https://cloud.gitea.com).
|
||||
|
||||
## Documentation
|
||||
|
||||
You can find comprehensive documentation on our official [documentation website](https://docs.gitea.com/).
|
||||
|
||||
It includes installation, administration, usage, development, contributing guides, and more to help you get started and explore all features effectively.
|
||||
|
||||
If you have any suggestions or would like to contribute to it, you can visit the [documentation repository](https://gitea.com/gitea/docs)
|
||||
|
||||
## Building
|
||||
|
||||
From the root of the source tree, run:
|
||||
@ -52,6 +60,8 @@ More info: https://docs.gitea.com/installation/install-from-source
|
||||
|
||||
## Using
|
||||
|
||||
After building, a binary file named `gitea` will be generated in the root of the source tree by default. To run it, use:
|
||||
|
||||
./gitea web
|
||||
|
||||
> [!NOTE]
|
||||
@ -68,22 +78,25 @@ Expected workflow is: Fork -> Patch -> Push -> Pull Request
|
||||
|
||||
## Translating
|
||||
|
||||
Translations are done through Crowdin. If you want to translate to a new language ask one of the managers in the Crowdin project to add a new language there.
|
||||
[](https://translate.gitea.com)
|
||||
|
||||
Translations are done through [Crowdin](https://translate.gitea.com). If you want to translate to a new language ask one of the managers in the Crowdin project to add a new language there.
|
||||
|
||||
You can also just create an issue for adding a language or ask on discord on the #translation channel. If you need context or find some translation issues, you can leave a comment on the string or ask on Discord. For general translation questions there is a section in the docs. Currently a bit empty but we hope to fill it as questions pop up.
|
||||
|
||||
https://docs.gitea.com/contributing/localization
|
||||
Get more information from [documentation](https://docs.gitea.com/contributing/localization).
|
||||
|
||||
[](https://crowdin.com/project/gitea)
|
||||
## Official and Third-Party Projects
|
||||
|
||||
## Further information
|
||||
We provide an official [go-sdk](https://gitea.com/gitea/go-sdk), a CLI tool called [tea](https://gitea.com/gitea/tea) and an [action runner](https://gitea.com/gitea/act_runner) for Gitea Action.
|
||||
|
||||
For more information and instructions about how to install Gitea, please look at our [documentation](https://docs.gitea.com/).
|
||||
If you have questions that are not covered by the documentation, you can get in contact with us on our [Discord server](https://discord.gg/Gitea) or create a post in the [discourse forum](https://forum.gitea.com/).
|
||||
We maintain a list of Gitea-related projects at [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea), where you can discover more third-party projects, including SDKs, plugins, themes, and more.
|
||||
|
||||
We maintain a list of Gitea-related projects at [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea).
|
||||
## Communication
|
||||
|
||||
The official Gitea CLI is developed at [gitea/tea](https://gitea.com/gitea/tea).
|
||||
[](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea")
|
||||
|
||||
If you have questions that are not covered by the [documentation](https://docs.gitea.com/), you can get in contact with us on our [Discord server](https://discord.gg/Gitea) or create a post in the [discourse forum](https://forum.gitea.com/).
|
||||
|
||||
## Authors
|
||||
|
||||
@ -122,18 +135,25 @@ Gitea is pronounced [/ɡɪ’ti:/](https://youtu.be/EM71-2uDAoY) as in "gi-tea"
|
||||
|
||||
We're [working on it](https://github.com/go-gitea/gitea/issues/1029).
|
||||
|
||||
**Where can I find the security patches?**
|
||||
|
||||
In the [release log](https://github.com/go-gitea/gitea/releases) or the [change log](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md), search for the keyword `SECURITY` to find the security patches.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License.
|
||||
See the [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) file
|
||||
for the full license text.
|
||||
|
||||
## Screenshots
|
||||
## Further information
|
||||
|
||||
Looking for an overview of the interface? Check it out!
|
||||
<details>
|
||||
<summary>Looking for an overview of the interface? Check it out!</summary>
|
||||
|
||||
||||
|
||||
|:---:|:---:|:---:|
|
||||
||||
|
||||
||||
|
||||
||||
|
||||
|
||||
</details>
|
||||
|
73
README_ZH.md
73
README_ZH.md
@ -9,13 +9,13 @@
|
||||
[](https://opencollective.com/gitea "Become a backer/sponsor of gitea")
|
||||
[](https://opensource.org/licenses/MIT "License: MIT")
|
||||
[](https://gitpod.io/#https://github.com/go-gitea/gitea)
|
||||
[](https://crowdin.com/project/gitea "Crowdin")
|
||||
[](https://translate.gitea.com "Crowdin")
|
||||
|
||||
[View this document in English](./README.md)
|
||||
|
||||
## 目标
|
||||
|
||||
Gitea 的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。我们采用 Go 作为后端语言,这使我们只要生成一个可执行程序即可。并且他还支持跨平台,支持 Linux, macOS 和 Windows 以及各种架构,除了 x86,amd64,还包括 ARM 和 PowerPC。
|
||||
Gitea 的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。我们采用 Go 作为后端语言,这使我们只要生成一个可执行程序即可。并且他还支持跨平台,支持 Linux、macOS 和 Windows 以及各种架构,除了 x86 和 amd64,还包括 ARM 和 PowerPC。
|
||||
|
||||
如果你想试用在线演示和报告问题,请访问 [demo.gitea.com](https://demo.gitea.com/)。
|
||||
|
||||
@ -23,39 +23,80 @@ Gitea 的首要目标是创建一个极易安装,运行非常快速,安装
|
||||
|
||||
如果你想在 Gitea Cloud 上快速部署你自己独享的 Gitea 实例,请访问 [cloud.gitea.com](https://cloud.gitea.com) 开始免费试用。
|
||||
|
||||
## 提示
|
||||
|
||||
1. **开始贡献代码之前请确保你已经看过了 [贡献者向导(英文)](CONTRIBUTING.md)**.
|
||||
2. 所有的安全问题,请私下发送邮件给 **security@gitea.io**。谢谢!
|
||||
3. 如果你要使用API,请参见 [API 文档](https://godoc.org/code.gitea.io/sdk/gitea).
|
||||
|
||||
## 文档
|
||||
|
||||
关于如何安装请访问我们的 [文档站](https://docs.gitea.com/zh-cn/category/installation),如果没有找到对应的文档,你也可以通过 [Discord - 英文](https://discord.gg/gitea) 和 QQ群 328432459 来和我们交流。
|
||||
|
||||
## 贡献流程
|
||||
## 编译
|
||||
|
||||
Fork -> Patch -> Push -> Pull Request
|
||||
在源代码的根目录下执行:
|
||||
|
||||
TAGS="bindata" make build
|
||||
|
||||
或者如果需要SQLite支持:
|
||||
|
||||
TAGS="bindata sqlite sqlite_unlock_notify" make build
|
||||
|
||||
编译过程会分成2个子任务:
|
||||
|
||||
- `make backend`,需要 [Go Stable](https://go.dev/dl/),最低版本需求可查看 [go.mod](/go.mod)。
|
||||
- `make frontend`,需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本。
|
||||
|
||||
你需要连接网络来下载 go 和 npm modules。当从 tar 格式的源文件编译时,其中包含了预编译的前端文件,因此 `make frontend` 将不会被执行。这允许编译时不需要 Node.js。
|
||||
|
||||
更多信息: https://docs.gitea.com/installation/install-from-source
|
||||
|
||||
## 使用
|
||||
|
||||
编译之后,默认会在根目录下生成一个名为 `gitea` 的文件。你可以这样执行它:
|
||||
|
||||
./gitea web
|
||||
|
||||
> [!注意]
|
||||
> 如果你要使用API,请参见 [API 文档](https://godoc.org/code.gitea.io/sdk/gitea)。
|
||||
|
||||
## 贡献
|
||||
|
||||
贡献流程:Fork -> Patch -> Push -> Pull Request
|
||||
|
||||
> [!注意]
|
||||
>
|
||||
> 1. **开始贡献代码之前请确保你已经看过了 [贡献者向导(英文)](CONTRIBUTING.md)**。
|
||||
> 2. 所有的安全问题,请私下发送邮件给 **security@gitea.io**。 谢谢!
|
||||
|
||||
## 翻译
|
||||
|
||||
多语言翻译是基于Crowdin进行的.
|
||||
[](https://crowdin.com/project/gitea)
|
||||
[](https://translate.gitea.com)
|
||||
|
||||
多语言翻译是基于Crowdin进行的。
|
||||
|
||||
从 [文档](https://docs.gitea.com/contributing/localization) 中获取更多信息。
|
||||
|
||||
## 官方和第三方项目
|
||||
|
||||
Gitea 提供官方的 [go-sdk](https://gitea.com/gitea/go-sdk),以及名为 [tea](https://gitea.com/gitea/tea) 的 CLI 工具 和 用于 Gitea Action 的 [action runner](https://gitea.com/gitea/act_runner)。
|
||||
|
||||
[gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea) 是一个 Gitea 相关项目的列表,你可以在这里找到更多的第三方项目,包括 SDK、插件、主题等等。
|
||||
|
||||
## 作者
|
||||
|
||||
* [Maintainers](https://github.com/orgs/go-gitea/people)
|
||||
* [Contributors](https://github.com/go-gitea/gitea/graphs/contributors)
|
||||
* [Translators](options/locale/TRANSLATORS)
|
||||
- [Maintainers](https://github.com/orgs/go-gitea/people)
|
||||
- [Contributors](https://github.com/go-gitea/gitea/graphs/contributors)
|
||||
- [Translators](options/locale/TRANSLATORS)
|
||||
|
||||
## 授权许可
|
||||
|
||||
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) 文件中。
|
||||
|
||||
## 截图
|
||||
## 更多信息
|
||||
|
||||
<details>
|
||||
<summary>截图</summary>
|
||||
|
||||
||||
|
||||
|:---:|:---:|:---:|
|
||||
||||
|
||||
||||
|
||||
||||
|
||||
|
||||
</details>
|
||||
|
22
assets/go-licenses.json
generated
22
assets/go-licenses.json
generated
File diff suppressed because one or more lines are too long
@ -18,10 +18,12 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/gtprof"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
"code.gitea.io/gitea/modules/public"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/routers"
|
||||
"code.gitea.io/gitea/routers/install"
|
||||
|
||||
@ -218,6 +220,8 @@ func serveInstalled(ctx *cli.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
gtprof.EnableBuiltinTracer(util.Iif(setting.IsProd, 2000*time.Millisecond, 100*time.Millisecond))
|
||||
|
||||
// Set up Chi routes
|
||||
webRoutes := routers.NormalRoutes()
|
||||
err := listen(webRoutes, true)
|
||||
|
@ -54,8 +54,10 @@ func runACME(listenAddr string, m http.Handler) error {
|
||||
altTLSALPNPort = p
|
||||
}
|
||||
|
||||
magic := &certmagic.Default
|
||||
magic.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
|
||||
// FIXME: this path is not right, it uses "AppWorkPath" incorrectly, and writes the data into "AppWorkPath/https"
|
||||
// Ideally it should migrate to AppDataPath write to "AppDataPath/https"
|
||||
certmagic.Default.Storage = &certmagic.FileStorage{Path: setting.AcmeLiveDirectory}
|
||||
magic := certmagic.NewDefault()
|
||||
// Try to use private CA root if provided, otherwise defaults to system's trust
|
||||
var certPool *x509.CertPool
|
||||
if setting.AcmeCARoot != "" {
|
||||
|
@ -790,10 +790,13 @@ LEVEL = Info
|
||||
;; Please note that setting this to false will not disable OAuth Basic or Basic authentication using a token
|
||||
;ENABLE_BASIC_AUTHENTICATION = true
|
||||
;;
|
||||
;; Show the password sign-in form (for password-based login), otherwise, only show OAuth2 login methods.
|
||||
;; Show the password sign-in form (for password-based login), otherwise, only show OAuth2 or passkey login methods if they are enabled.
|
||||
;; If you set it to false, maybe it also needs to set ENABLE_BASIC_AUTHENTICATION to false to completely disable password-based authentication.
|
||||
;ENABLE_PASSWORD_SIGNIN_FORM = true
|
||||
;;
|
||||
;; Allow users to sign-in with a passkey
|
||||
;ENABLE_PASSKEY_AUTHENTICATION = true
|
||||
;;
|
||||
;; More detail: https://github.com/gogits/gogs/issues/165
|
||||
;ENABLE_REVERSE_PROXY_AUTHENTICATION = false
|
||||
; Enable this to allow reverse proxy authentication for API requests, the reverse proxy is responsible for ensuring that no CSRF is possible.
|
||||
|
@ -37,5 +37,5 @@ done
|
||||
if [ $# -gt 0 ]; then
|
||||
exec "$@"
|
||||
else
|
||||
exec /bin/s6-svscan /etc/s6
|
||||
exec /usr/bin/s6-svscan /etc/s6
|
||||
fi
|
||||
|
12
flake.lock
generated
12
flake.lock
generated
@ -5,11 +5,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1726560853,
|
||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -20,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1731139594,
|
||||
"narHash": "sha256-IigrKK3vYRpUu+HEjPL/phrfh7Ox881er1UEsZvw9Q4=",
|
||||
"lastModified": 1736798957,
|
||||
"narHash": "sha256-qwpCtZhSsSNQtK4xYGzMiyEDhkNzOCz/Vfu4oL2ETsQ=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "76612b17c0ce71689921ca12d9ffdc9c23ce40b2",
|
||||
"rev": "9abb87b552b7f55ac8916b6fc9e5cb486656a2f3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -29,9 +29,14 @@
|
||||
poetry
|
||||
|
||||
# backend
|
||||
go_1_23
|
||||
gofumpt
|
||||
sqlite
|
||||
];
|
||||
shellHook = ''
|
||||
export GO="${pkgs.go_1_23}/bin/go"
|
||||
export GOROOT="${pkgs.go_1_23}/share/go"
|
||||
'';
|
||||
};
|
||||
}
|
||||
);
|
||||
|
35
go.mod
35
go.mod
@ -24,10 +24,10 @@ require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
||||
github.com/ProtonMail/go-crypto v1.0.0
|
||||
github.com/ProtonMail/go-crypto v1.1.4
|
||||
github.com/PuerkitoBio/goquery v1.10.0
|
||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3
|
||||
github.com/alecthomas/chroma/v2 v2.14.0
|
||||
github.com/alecthomas/chroma/v2 v2.15.0
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.42
|
||||
github.com/aws/aws-sdk-go-v2/service/codecommit v1.27.3
|
||||
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
|
||||
@ -54,8 +54,8 @@ require (
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-co-op/gocron v1.37.0
|
||||
github.com/go-enry/go-enry/v2 v2.9.1
|
||||
github.com/go-git/go-billy/v5 v5.6.0
|
||||
github.com/go-git/go-git/v5 v5.12.0
|
||||
github.com/go-git/go-billy/v5 v5.6.1
|
||||
github.com/go-git/go-git/v5 v5.13.1
|
||||
github.com/go-ldap/ldap/v3 v3.4.8
|
||||
github.com/go-redsync/redsync/v4 v4.13.0
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
@ -106,28 +106,28 @@ require (
|
||||
github.com/sassoftware/go-rpmutils v0.4.0
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
|
||||
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/syndtr/goleveldb v1.0.0
|
||||
github.com/tstranex/u2f v1.0.0
|
||||
github.com/ulikunitz/xz v0.5.12
|
||||
github.com/urfave/cli/v2 v2.27.5
|
||||
github.com/wneessen/go-mail v0.5.2
|
||||
github.com/xanzy/go-gitlab v0.112.0
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
github.com/yohcop/openid-go v1.0.1
|
||||
github.com/yuin/goldmark v1.7.8
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
github.com/yuin/goldmark-meta v1.1.0
|
||||
golang.org/x/crypto v0.31.0
|
||||
gitlab.com/gitlab-org/api/client-go v0.119.0
|
||||
golang.org/x/crypto v0.32.0
|
||||
golang.org/x/image v0.21.0
|
||||
golang.org/x/net v0.33.0
|
||||
golang.org/x/oauth2 v0.23.0
|
||||
golang.org/x/net v0.34.0
|
||||
golang.org/x/oauth2 v0.24.0
|
||||
golang.org/x/sync v0.10.0
|
||||
golang.org/x/sys v0.28.0
|
||||
golang.org/x/sys v0.29.0
|
||||
golang.org/x/text v0.21.0
|
||||
golang.org/x/tools v0.26.0
|
||||
golang.org/x/tools v0.29.0
|
||||
google.golang.org/grpc v1.67.1
|
||||
google.golang.org/protobuf v1.35.1
|
||||
google.golang.org/protobuf v1.36.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
mvdan.cc/xurls/v2 v2.5.0
|
||||
@ -186,7 +186,7 @@ require (
|
||||
github.com/couchbase/gomemcached v0.3.2 // indirect
|
||||
github.com/couchbase/goutils v0.1.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.3.4 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.3.6 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
@ -219,7 +219,7 @@ require (
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
@ -253,6 +253,7 @@ require (
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/mmcloughlin/avo v0.6.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 // indirect
|
||||
@ -264,7 +265,7 @@ require (
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.60.1 // indirect
|
||||
@ -304,8 +305,8 @@ require (
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
|
||||
golang.org/x/mod v0.21.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/time v0.8.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
94
go.sum
94
go.sum
@ -71,8 +71,8 @@ github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSC
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/ProtonMail/go-crypto v1.1.4 h1:G5U5asvD5N/6/36oIw3k2bOfBn5XVcZrb7PBjzzKKoE=
|
||||
github.com/ProtonMail/go-crypto v1.1.4/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||
github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
|
||||
github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
|
||||
github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
|
||||
@ -81,11 +81,11 @@ github.com/RoaringBitmap/roaring v1.9.4 h1:yhEIoH4YezLYT04s1nHehNO64EKFTop/wBhxv
|
||||
github.com/RoaringBitmap/roaring v1.9.4/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90=
|
||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3 h1:BP0HiyNT3AQEYi+if3wkRcIdQFHtsw6xX3Kx0glckgA=
|
||||
github.com/SaveTheRbtz/zstd-seekable-format-go/pkg v0.7.3/go.mod h1:hMNtySovKkn2gdDuLqnqveP+mfhUSaBdoBcr2I7Zt0E=
|
||||
github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE=
|
||||
github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||
github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E=
|
||||
github.com/alecthomas/chroma/v2 v2.14.0/go.mod h1:QolEbTfmUHIMVpBqxeDnNBj2uoeI4EbYP4i6n68SG4I=
|
||||
github.com/alecthomas/chroma/v2 v2.15.0 h1:LxXTQHFoYrstG2nnV9y2X5O94sOBzf0CIUpSTbpxvMc=
|
||||
github.com/alecthomas/chroma/v2 v2.15.0/go.mod h1:gUhVLrPDXPtp/f+L1jo9xepo9gL4eLwRuGAunSZMkio=
|
||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
@ -188,7 +188,6 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/buildkite/terminal-to-html/v3 v3.16.3 h1:IGuJjboHjuMLWOGsKZKNxbbn41emOLiHzXPmQZk31fk=
|
||||
github.com/buildkite/terminal-to-html/v3 v3.16.3/go.mod h1:r/J7cC9c3EzBzP3/wDz0RJLPwv5PUAMp+KF2w+ntMc0=
|
||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/caddyserver/certmagic v0.21.4 h1:e7VobB8rffHv8ZZpSiZtEwnLDHUwLVYLWzWSa1FfKI0=
|
||||
github.com/caddyserver/certmagic v0.21.4/go.mod h1:swUXjQ1T9ZtMv95qj7/InJvWLXURU85r+CfG0T+ZbDE=
|
||||
github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=
|
||||
@ -205,7 +204,6 @@ github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moA
|
||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
||||
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
|
||||
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
@ -223,8 +221,8 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cyphar/filepath-securejoin v0.3.4 h1:VBWugsJh2ZxJmLFSM06/0qzQyiQX2Qs0ViKrUAcqdZ8=
|
||||
github.com/cyphar/filepath-securejoin v0.3.4/go.mod h1:8s/MCNJREmFK0H02MF6Ihv1nakJe4L/w3WZLHNkvlYM=
|
||||
github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
|
||||
github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
@ -254,8 +252,8 @@ github.com/dvyukov/go-fuzz v0.0.0-20210429054444-fca39067bc72/go.mod h1:11Gm+ccJ
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 h1:dKG8sc7n321deIVRcQtwlMNoBEra7j0qQ8RwxO8RN0w=
|
||||
github.com/editorconfig/editorconfig-core-go/v2 v2.6.2/go.mod h1:7dvD3GCm7eBw53xZ/lsiq72LqobdMg3ITbMBxnmJmqY=
|
||||
github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ=
|
||||
github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64=
|
||||
github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA=
|
||||
github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY=
|
||||
github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4=
|
||||
@ -313,12 +311,12 @@ github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e h1:oRq/fiirun5Hql
|
||||
github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8=
|
||||
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM=
|
||||
github.com/go-git/go-billy/v5 v5.6.1 h1:u+dcrgaguSSkbjzHwelEjc0Yj300NUevrrPphk/SoRA=
|
||||
github.com/go-git/go-billy/v5 v5.6.1/go.mod h1:0AsLr1z2+Uksi4NlElmMblP5rPcDZNRCD8ujZCRR2BE=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
|
||||
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
|
||||
github.com/go-git/go-git/v5 v5.13.1 h1:DAQ9APonnlvSWpvolXWIuV6Q6zXy2wHbN4cVlNR5Q+M=
|
||||
github.com/go-git/go-git/v5 v5.13.1/go.mod h1:qryJB4cSBoq3FRoBRf5A77joojuBcmPJ0qu3XXXVixc=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-ldap/ldap/v3 v3.4.8 h1:loKJyspcRezt2Q3ZRMq2p/0v8iOurlmeXDPw6fikSvQ=
|
||||
@ -385,8 +383,8 @@ github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei
|
||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I=
|
||||
github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
@ -581,6 +579,8 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY=
|
||||
github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@ -631,8 +631,8 @@ github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG
|
||||
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||
github.com/pjbgf/sha1cd v0.3.1 h1:Dh2GYdpJnO84lIw0LJwTFXjcNbasP/bklicSznyAaPI=
|
||||
github.com/pjbgf/sha1cd v0.3.1/go.mod h1:Y8t7jSB/dEI/lQE04A1HVKteqjj9bX5O4+Cex0TCu8s=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
@ -737,8 +737,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM=
|
||||
github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203/go.mod h1:oqN97ltKNihBbwlX8dLpwxCl3+HnXKV/R0e+sRLd9C8=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
@ -766,8 +766,6 @@ github.com/wneessen/go-mail v0.5.2 h1:MZKwgHJoRboLJ+EHMLuHpZc95wo+u1xViL/4XSswDT
|
||||
github.com/wneessen/go-mail v0.5.2/go.mod h1:kRroJvEq2hOSEPFRiKjN7Csrz0G1w+RpiGR3b6yo+Ck=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xanzy/go-gitlab v0.112.0 h1:6Z0cqEooCvBMfBIHw+CgO4AKGRV8na/9781xOb0+DKw=
|
||||
github.com/xanzy/go-gitlab v0.112.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
@ -802,6 +800,8 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
||||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
|
||||
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
|
||||
gitlab.com/gitlab-org/api/client-go v0.119.0 h1:YBZyx9XUTtEDBBYtY36cZWz6JmT7om/8HPSk37IS95g=
|
||||
gitlab.com/gitlab-org/api/client-go v0.119.0/go.mod h1:ygHmS3AU3TpvK+AC6DYO1QuAxLlv6yxYK+/Votr/WFQ=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
|
||||
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
|
||||
@ -823,16 +823,14 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
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.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY=
|
||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8=
|
||||
golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s=
|
||||
@ -846,8 +844,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@ -859,20 +857,18 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
||||
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -910,8 +906,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
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=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@ -921,14 +915,12 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
@ -936,15 +928,13 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
@ -952,8 +942,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
@ -964,8 +954,8 @@ 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.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
|
||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
|
||||
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=
|
||||
@ -980,8 +970,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ=
|
||||
google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -88,7 +88,7 @@ func (run *ActionRun) RefLink() string {
|
||||
if refName.IsPull() {
|
||||
return run.Repo.Link() + "/pulls/" + refName.ShortName()
|
||||
}
|
||||
return git.RefURL(run.Repo.Link(), run.Ref)
|
||||
return run.Repo.Link() + "/src/" + refName.RefWebLinkPath()
|
||||
}
|
||||
|
||||
// PrettyRef return #id for pull ref or ShortName for others
|
||||
|
@ -355,7 +355,7 @@ func (a *Action) GetBranch() string {
|
||||
|
||||
// GetRefLink returns the action's ref link.
|
||||
func (a *Action) GetRefLink(ctx context.Context) string {
|
||||
return git.RefURL(a.GetRepoLink(ctx), a.RefName)
|
||||
return a.GetRepoLink(ctx) + "/src/" + git.RefName(a.RefName).RefWebLinkPath()
|
||||
}
|
||||
|
||||
// GetTag returns the action's repository tag.
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
|
||||
"github.com/keybase/go-crypto/openpgp/packet"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCheckArmoredGPGKeyString(t *testing.T) {
|
||||
@ -107,9 +108,8 @@ MkM/fdpyc2hY7Dl/+qFmN5MG5yGmMpQcX+RNNR222ibNC1D3wg==
|
||||
=i9b7
|
||||
-----END PGP PUBLIC KEY BLOCK-----`
|
||||
keys, err := checkArmoredGPGKeyString(testGPGArmor)
|
||||
if !assert.NotEmpty(t, keys) {
|
||||
return
|
||||
}
|
||||
require.NotEmpty(t, keys)
|
||||
|
||||
ekey := keys[0]
|
||||
assert.NoError(t, err, "Could not parse a valid GPG armored key", ekey)
|
||||
|
||||
|
@ -7,23 +7,36 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/gtprof"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"xorm.io/xorm/contexts"
|
||||
)
|
||||
|
||||
type SlowQueryHook struct {
|
||||
type EngineHook struct {
|
||||
Threshold time.Duration
|
||||
Logger log.Logger
|
||||
}
|
||||
|
||||
var _ contexts.Hook = (*SlowQueryHook)(nil)
|
||||
var _ contexts.Hook = (*EngineHook)(nil)
|
||||
|
||||
func (*SlowQueryHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) {
|
||||
return c.Ctx, nil
|
||||
func (*EngineHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) {
|
||||
ctx, _ := gtprof.GetTracer().Start(c.Ctx, gtprof.TraceSpanDatabase)
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func (h *SlowQueryHook) AfterProcess(c *contexts.ContextHook) error {
|
||||
func (h *EngineHook) AfterProcess(c *contexts.ContextHook) error {
|
||||
span := gtprof.GetContextSpan(c.Ctx)
|
||||
if span != nil {
|
||||
// Do not record SQL parameters here:
|
||||
// * It shouldn't expose the parameters because they contain sensitive information, end users need to report the trace details safely.
|
||||
// * Some parameters contain quite long texts, waste memory and are difficult to display.
|
||||
span.SetAttributeString(gtprof.TraceAttrDbSQL, c.SQL)
|
||||
span.End()
|
||||
} else {
|
||||
setting.PanicInDevOrTesting("span in database engine hook is nil")
|
||||
}
|
||||
if c.ExecuteTime >= h.Threshold {
|
||||
// 8 is the amount of skips passed to runtime.Caller, so that in the log the correct function
|
||||
// is being displayed (the function that ultimately wants to execute the query in the code)
|
||||
|
@ -72,7 +72,7 @@ func InitEngine(ctx context.Context) error {
|
||||
xe.SetDefaultContext(ctx)
|
||||
|
||||
if setting.Database.SlowQueryThreshold > 0 {
|
||||
xe.AddHook(&SlowQueryHook{
|
||||
xe.AddHook(&EngineHook{
|
||||
Threshold: setting.Database.SlowQueryThreshold,
|
||||
Logger: log.GetLogger("xorm"),
|
||||
})
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
_ "code.gitea.io/gitea/cmd" // for TestPrimaryKeys
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDumpDatabase(t *testing.T) {
|
||||
@ -62,9 +63,7 @@ func TestPrimaryKeys(t *testing.T) {
|
||||
// Import "code.gitea.io/gitea/cmd" to make sure each db.RegisterModel in init functions has been called.
|
||||
|
||||
beans, err := db.NamesToBean()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
whitelist := map[string]string{
|
||||
"the_table_name_to_skip_checking": "Write a note here to explain why",
|
||||
@ -79,8 +78,6 @@ func TestPrimaryKeys(t *testing.T) {
|
||||
t.Logf("ignore %q because %q", table.Name, why)
|
||||
continue
|
||||
}
|
||||
if len(table.PrimaryKeys) == 0 {
|
||||
t.Errorf("table %q has no primary key", table.Name)
|
||||
}
|
||||
assert.NotEmpty(t, table.PrimaryKeys, "table %q has no primary key", table.Name)
|
||||
}
|
||||
}
|
||||
|
@ -171,3 +171,9 @@
|
||||
user_id: 40
|
||||
repo_id: 61
|
||||
mode: 4
|
||||
|
||||
-
|
||||
id: 30
|
||||
user_id: 40
|
||||
repo_id: 1
|
||||
mode: 2
|
||||
|
@ -22,6 +22,7 @@
|
||||
content_type: 1 # json
|
||||
events: '{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}'
|
||||
is_active: true
|
||||
|
||||
-
|
||||
id: 4
|
||||
repo_id: 2
|
||||
@ -29,3 +30,23 @@
|
||||
content_type: 1 # json
|
||||
events: '{"push_only":true,"branch_filter":"{master,feature*}"}'
|
||||
is_active: true
|
||||
|
||||
-
|
||||
id: 5
|
||||
repo_id: 0
|
||||
owner_id: 0
|
||||
url: www.example.com/url5
|
||||
content_type: 1 # json
|
||||
events: '{"push_only":true,"branch_filter":"{master,feature*}"}'
|
||||
is_active: true
|
||||
is_system_webhook: true
|
||||
|
||||
-
|
||||
id: 6
|
||||
repo_id: 0
|
||||
owner_id: 0
|
||||
url: www.example.com/url6
|
||||
content_type: 1 # json
|
||||
events: '{"push_only":true,"branch_filter":"{master,feature*}"}'
|
||||
is_active: true
|
||||
is_system_webhook: false
|
||||
|
@ -167,6 +167,9 @@ func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, e
|
||||
BranchName: branchName,
|
||||
}
|
||||
}
|
||||
// FIXME: this design is not right: it doesn't check `branch.IsDeleted`, it doesn't make sense to make callers to check IsDeleted again and again.
|
||||
// It causes inconsistency with `GetBranches` and `git.GetBranch`, and will lead to strange bugs
|
||||
// In the future, there should be 2 functions: `GetBranchExisting` and `GetBranchWithDeleted`
|
||||
return &branch, nil
|
||||
}
|
||||
|
||||
@ -440,6 +443,8 @@ type FindRecentlyPushedNewBranchesOptions struct {
|
||||
}
|
||||
|
||||
type RecentlyPushedNewBranch struct {
|
||||
BranchRepo *repo_model.Repository
|
||||
BranchName string
|
||||
BranchDisplayName string
|
||||
BranchLink string
|
||||
BranchCompareURL string
|
||||
@ -540,7 +545,9 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
|
||||
branchDisplayName = fmt.Sprintf("%s:%s", branch.Repo.FullName(), branchDisplayName)
|
||||
}
|
||||
newBranches = append(newBranches, &RecentlyPushedNewBranch{
|
||||
BranchRepo: branch.Repo,
|
||||
BranchDisplayName: branchDisplayName,
|
||||
BranchName: branch.Name,
|
||||
BranchLink: fmt.Sprintf("%s/src/branch/%s", branch.Repo.Link(), util.PathEscapeSegments(branch.Name)),
|
||||
BranchCompareURL: branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, branch.Name),
|
||||
CommitTime: branch.CommitTime,
|
||||
|
@ -112,8 +112,8 @@ const (
|
||||
CommentTypePRScheduledToAutoMerge // 34 pr was scheduled to auto merge when checks succeed
|
||||
CommentTypePRUnScheduledToAutoMerge // 35 pr was un scheduled to auto merge when checks succeed
|
||||
|
||||
CommentTypePin // 36 pin Issue
|
||||
CommentTypeUnpin // 37 unpin Issue
|
||||
CommentTypePin // 36 pin Issue/PullRequest
|
||||
CommentTypeUnpin // 37 unpin Issue/PullRequest
|
||||
|
||||
CommentTypeChangeTimeEstimate // 38 Change time estimate
|
||||
)
|
||||
|
@ -26,14 +26,14 @@ func (comments CommentList) LoadPosters(ctx context.Context) error {
|
||||
return c.PosterID, c.Poster == nil && c.PosterID > 0
|
||||
})
|
||||
|
||||
posterMaps, err := getPostersByIDs(ctx, posterIDs)
|
||||
posterMaps, err := user_model.GetUsersMapByIDs(ctx, posterIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, comment := range comments {
|
||||
if comment.Poster == nil {
|
||||
comment.Poster = getPoster(comment.PosterID, posterMaps)
|
||||
comment.Poster = user_model.GetPossibleUserFromMap(comment.PosterID, posterMaps)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -41,7 +41,7 @@ func (comments CommentList) LoadPosters(ctx context.Context) error {
|
||||
|
||||
func (comments CommentList) getLabelIDs() []int64 {
|
||||
return container.FilterSlice(comments, func(comment *Comment) (int64, bool) {
|
||||
return comment.LabelID, comment.LabelID > 0
|
||||
return comment.LabelID, comment.LabelID > 0 && comment.Label == nil
|
||||
})
|
||||
}
|
||||
|
||||
@ -51,6 +51,9 @@ func (comments CommentList) loadLabels(ctx context.Context) error {
|
||||
}
|
||||
|
||||
labelIDs := comments.getLabelIDs()
|
||||
if len(labelIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
commentLabels := make(map[int64]*Label, len(labelIDs))
|
||||
left := len(labelIDs)
|
||||
for left > 0 {
|
||||
@ -118,8 +121,8 @@ func (comments CommentList) loadMilestones(ctx context.Context) error {
|
||||
milestoneIDs = milestoneIDs[limit:]
|
||||
}
|
||||
|
||||
for _, issue := range comments {
|
||||
issue.Milestone = milestoneMaps[issue.MilestoneID]
|
||||
for _, comment := range comments {
|
||||
comment.Milestone = milestoneMaps[comment.MilestoneID]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -175,6 +178,9 @@ func (comments CommentList) loadAssignees(ctx context.Context) error {
|
||||
}
|
||||
|
||||
assigneeIDs := comments.getAssigneeIDs()
|
||||
if len(assigneeIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
assignees := make(map[int64]*user_model.User, len(assigneeIDs))
|
||||
left := len(assigneeIDs)
|
||||
for left > 0 {
|
||||
@ -301,6 +307,9 @@ func (comments CommentList) loadDependentIssues(ctx context.Context) error {
|
||||
|
||||
e := db.GetEngine(ctx)
|
||||
issueIDs := comments.getDependentIssueIDs()
|
||||
if len(issueIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
issues := make(map[int64]*Issue, len(issueIDs))
|
||||
left := len(issueIDs)
|
||||
for left > 0 {
|
||||
@ -427,6 +436,9 @@ func (comments CommentList) loadReviews(ctx context.Context) error {
|
||||
}
|
||||
|
||||
reviewIDs := comments.getReviewIDs()
|
||||
if len(reviewIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
reviews := make(map[int64]*Review, len(reviewIDs))
|
||||
if err := db.GetEngine(ctx).In("id", reviewIDs).Find(&reviews); err != nil {
|
||||
return err
|
||||
|
@ -46,23 +46,6 @@ func (err ErrIssueNotExist) Unwrap() error {
|
||||
return util.ErrNotExist
|
||||
}
|
||||
|
||||
// ErrIssueIsClosed represents a "IssueIsClosed" kind of error.
|
||||
type ErrIssueIsClosed struct {
|
||||
ID int64
|
||||
RepoID int64
|
||||
Index int64
|
||||
}
|
||||
|
||||
// IsErrIssueIsClosed checks if an error is a ErrIssueNotExist.
|
||||
func IsErrIssueIsClosed(err error) bool {
|
||||
_, ok := err.(ErrIssueIsClosed)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrIssueIsClosed) Error() string {
|
||||
return fmt.Sprintf("issue is closed [id: %d, repo_id: %d, index: %d]", err.ID, err.RepoID, err.Index)
|
||||
}
|
||||
|
||||
// ErrNewIssueInsert is used when the INSERT statement in newIssue fails
|
||||
type ErrNewIssueInsert struct {
|
||||
OriginalError error
|
||||
@ -78,22 +61,6 @@ func (err ErrNewIssueInsert) Error() string {
|
||||
return err.OriginalError.Error()
|
||||
}
|
||||
|
||||
// ErrIssueWasClosed is used when close a closed issue
|
||||
type ErrIssueWasClosed struct {
|
||||
ID int64
|
||||
Index int64
|
||||
}
|
||||
|
||||
// IsErrIssueWasClosed checks if an error is a ErrIssueWasClosed.
|
||||
func IsErrIssueWasClosed(err error) bool {
|
||||
_, ok := err.(ErrIssueWasClosed)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrIssueWasClosed) Error() string {
|
||||
return fmt.Sprintf("Issue [%d] %d was already closed", err.ID, err.Index)
|
||||
}
|
||||
|
||||
var ErrIssueAlreadyChanged = util.NewInvalidArgumentErrorf("the issue is already changed")
|
||||
|
||||
// Issue represents an issue or pull request of repository.
|
||||
@ -271,6 +238,9 @@ func (issue *Issue) loadCommentsByType(ctx context.Context, tp CommentType) (err
|
||||
IssueID: issue.ID,
|
||||
Type: tp,
|
||||
})
|
||||
for _, comment := range issue.Comments {
|
||||
comment.Issue = issue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -81,53 +81,19 @@ func (issues IssueList) LoadPosters(ctx context.Context) error {
|
||||
return issue.PosterID, issue.Poster == nil && issue.PosterID > 0
|
||||
})
|
||||
|
||||
posterMaps, err := getPostersByIDs(ctx, posterIDs)
|
||||
posterMaps, err := user_model.GetUsersMapByIDs(ctx, posterIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, issue := range issues {
|
||||
if issue.Poster == nil {
|
||||
issue.Poster = getPoster(issue.PosterID, posterMaps)
|
||||
issue.Poster = user_model.GetPossibleUserFromMap(issue.PosterID, posterMaps)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getPostersByIDs(ctx context.Context, posterIDs []int64) (map[int64]*user_model.User, error) {
|
||||
posterMaps := make(map[int64]*user_model.User, len(posterIDs))
|
||||
left := len(posterIDs)
|
||||
for left > 0 {
|
||||
limit := db.DefaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
err := db.GetEngine(ctx).
|
||||
In("id", posterIDs[:limit]).
|
||||
Find(&posterMaps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
left -= limit
|
||||
posterIDs = posterIDs[limit:]
|
||||
}
|
||||
return posterMaps, nil
|
||||
}
|
||||
|
||||
func getPoster(posterID int64, posterMaps map[int64]*user_model.User) *user_model.User {
|
||||
if posterID == user_model.ActionsUserID {
|
||||
return user_model.NewActionsUser()
|
||||
}
|
||||
if posterID <= 0 {
|
||||
return nil
|
||||
}
|
||||
poster, ok := posterMaps[posterID]
|
||||
if !ok {
|
||||
return user_model.NewGhostUser()
|
||||
}
|
||||
return poster
|
||||
}
|
||||
|
||||
func (issues IssueList) getIssueIDs() []int64 {
|
||||
ids := make([]int64, 0, len(issues))
|
||||
for _, issue := range issues {
|
||||
|
@ -28,38 +28,40 @@ import (
|
||||
|
||||
// UpdateIssueCols updates cols of issue
|
||||
func UpdateIssueCols(ctx context.Context, issue *Issue, cols ...string) error {
|
||||
if _, err := db.GetEngine(ctx).ID(issue.ID).Cols(cols...).Update(issue); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
_, err := db.GetEngine(ctx).ID(issue.ID).Cols(cols...).Update(issue)
|
||||
return err
|
||||
}
|
||||
|
||||
func ChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isClosed, isMergePull bool) (*Comment, error) {
|
||||
// Reload the issue
|
||||
currentIssue, err := GetIssueByID(ctx, issue.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// ErrIssueIsClosed is used when close a closed issue
|
||||
type ErrIssueIsClosed struct {
|
||||
ID int64
|
||||
RepoID int64
|
||||
Index int64
|
||||
IsPull bool
|
||||
}
|
||||
|
||||
// Nothing should be performed if current status is same as target status
|
||||
if currentIssue.IsClosed == isClosed {
|
||||
if !issue.IsPull {
|
||||
return nil, ErrIssueWasClosed{
|
||||
ID: issue.ID,
|
||||
}
|
||||
}
|
||||
return nil, ErrPullWasClosed{
|
||||
ID: issue.ID,
|
||||
// IsErrIssueIsClosed checks if an error is a ErrIssueIsClosed.
|
||||
func IsErrIssueIsClosed(err error) bool {
|
||||
_, ok := err.(ErrIssueIsClosed)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrIssueIsClosed) Error() string {
|
||||
return fmt.Sprintf("%s [id: %d, repo_id: %d, index: %d] is already closed", util.Iif(err.IsPull, "Pull Request", "Issue"), err.ID, err.RepoID, err.Index)
|
||||
}
|
||||
|
||||
func SetIssueAsClosed(ctx context.Context, issue *Issue, doer *user_model.User, isMergePull bool) (*Comment, error) {
|
||||
if issue.IsClosed {
|
||||
return nil, ErrIssueIsClosed{
|
||||
ID: issue.ID,
|
||||
RepoID: issue.RepoID,
|
||||
Index: issue.Index,
|
||||
IsPull: issue.IsPull,
|
||||
}
|
||||
}
|
||||
|
||||
issue.IsClosed = isClosed
|
||||
return doChangeIssueStatus(ctx, issue, doer, isMergePull)
|
||||
}
|
||||
|
||||
func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.User, isMergePull bool) (*Comment, error) {
|
||||
// Check for open dependencies
|
||||
if issue.IsClosed && issue.Repo.IsDependenciesEnabled(ctx) {
|
||||
if issue.Repo.IsDependenciesEnabled(ctx) {
|
||||
// only check if dependencies are enabled and we're about to close an issue, otherwise reopening an issue would fail when there are unsatisfied dependencies
|
||||
noDeps, err := IssueNoDependenciesLeft(ctx, issue)
|
||||
if err != nil {
|
||||
@ -71,16 +73,63 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
|
||||
}
|
||||
}
|
||||
|
||||
if issue.IsClosed {
|
||||
issue.ClosedUnix = timeutil.TimeStampNow()
|
||||
} else {
|
||||
issue.ClosedUnix = 0
|
||||
}
|
||||
issue.IsClosed = true
|
||||
issue.ClosedUnix = timeutil.TimeStampNow()
|
||||
|
||||
if err := UpdateIssueCols(ctx, issue, "is_closed", "closed_unix"); err != nil {
|
||||
if cnt, err := db.GetEngine(ctx).ID(issue.ID).Cols("is_closed", "closed_unix").
|
||||
Where("is_closed = ?", false).
|
||||
Update(issue); err != nil {
|
||||
return nil, err
|
||||
} else if cnt != 1 {
|
||||
return nil, ErrIssueAlreadyChanged
|
||||
}
|
||||
|
||||
return updateIssueNumbers(ctx, issue, doer, util.Iif(isMergePull, CommentTypeMergePull, CommentTypeClose))
|
||||
}
|
||||
|
||||
// ErrIssueIsOpen is used when reopen an opened issue
|
||||
type ErrIssueIsOpen struct {
|
||||
ID int64
|
||||
RepoID int64
|
||||
IsPull bool
|
||||
Index int64
|
||||
}
|
||||
|
||||
// IsErrIssueIsOpen checks if an error is a ErrIssueIsOpen.
|
||||
func IsErrIssueIsOpen(err error) bool {
|
||||
_, ok := err.(ErrIssueIsOpen)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrIssueIsOpen) Error() string {
|
||||
return fmt.Sprintf("%s [id: %d, repo_id: %d, index: %d] is already open", util.Iif(err.IsPull, "Pull Request", "Issue"), err.ID, err.RepoID, err.Index)
|
||||
}
|
||||
|
||||
func setIssueAsReopen(ctx context.Context, issue *Issue, doer *user_model.User) (*Comment, error) {
|
||||
if !issue.IsClosed {
|
||||
return nil, ErrIssueIsOpen{
|
||||
ID: issue.ID,
|
||||
RepoID: issue.RepoID,
|
||||
Index: issue.Index,
|
||||
IsPull: issue.IsPull,
|
||||
}
|
||||
}
|
||||
|
||||
issue.IsClosed = false
|
||||
issue.ClosedUnix = 0
|
||||
|
||||
if cnt, err := db.GetEngine(ctx).ID(issue.ID).Cols("is_closed", "closed_unix").
|
||||
Where("is_closed = ?", true).
|
||||
Update(issue); err != nil {
|
||||
return nil, err
|
||||
} else if cnt != 1 {
|
||||
return nil, ErrIssueAlreadyChanged
|
||||
}
|
||||
|
||||
return updateIssueNumbers(ctx, issue, doer, CommentTypeReopen)
|
||||
}
|
||||
|
||||
func updateIssueNumbers(ctx context.Context, issue *Issue, doer *user_model.User, cmtType CommentType) (*Comment, error) {
|
||||
// Update issue count of labels
|
||||
if err := issue.LoadLabels(ctx); err != nil {
|
||||
return nil, err
|
||||
@ -103,14 +152,6 @@ func doChangeIssueStatus(ctx context.Context, issue *Issue, doer *user_model.Use
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// New action comment
|
||||
cmtType := CommentTypeClose
|
||||
if !issue.IsClosed {
|
||||
cmtType = CommentTypeReopen
|
||||
} else if isMergePull {
|
||||
cmtType = CommentTypeMergePull
|
||||
}
|
||||
|
||||
return CreateComment(ctx, &CreateCommentOptions{
|
||||
Type: cmtType,
|
||||
Doer: doer,
|
||||
@ -134,7 +175,7 @@ func CloseIssue(ctx context.Context, issue *Issue, doer *user_model.User) (*Comm
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
comment, err := ChangeIssueStatus(ctx, issue, doer, true, false)
|
||||
comment, err := SetIssueAsClosed(ctx, issue, doer, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -159,7 +200,7 @@ func ReopenIssue(ctx context.Context, issue *Issue, doer *user_model.User) (*Com
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
comment, err := ChangeIssueStatus(ctx, issue, doer, false, false)
|
||||
comment, err := setIssueAsReopen(ctx, issue, doer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -80,22 +80,6 @@ func (err ErrPullRequestAlreadyExists) Unwrap() error {
|
||||
return util.ErrAlreadyExist
|
||||
}
|
||||
|
||||
// ErrPullWasClosed is used close a closed pull request
|
||||
type ErrPullWasClosed struct {
|
||||
ID int64
|
||||
Index int64
|
||||
}
|
||||
|
||||
// IsErrPullWasClosed checks if an error is a ErrErrPullWasClosed.
|
||||
func IsErrPullWasClosed(err error) bool {
|
||||
_, ok := err.(ErrPullWasClosed)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrPullWasClosed) Error() string {
|
||||
return fmt.Sprintf("Pull request [%d] %d was already closed", err.ID, err.Index)
|
||||
}
|
||||
|
||||
// PullRequestType defines pull request type
|
||||
type PullRequestType int
|
||||
|
||||
|
@ -166,6 +166,23 @@ func (prs PullRequestList) getRepositoryIDs() []int64 {
|
||||
return repoIDs.Values()
|
||||
}
|
||||
|
||||
func (prs PullRequestList) SetBaseRepo(baseRepo *repo_model.Repository) {
|
||||
for _, pr := range prs {
|
||||
if pr.BaseRepo == nil {
|
||||
pr.BaseRepo = baseRepo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (prs PullRequestList) SetHeadRepo(headRepo *repo_model.Repository) {
|
||||
for _, pr := range prs {
|
||||
if pr.HeadRepo == nil {
|
||||
pr.HeadRepo = headRepo
|
||||
pr.isHeadRepoLoaded = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (prs PullRequestList) LoadRepositories(ctx context.Context) error {
|
||||
repoIDs := prs.getRepositoryIDs()
|
||||
reposMap := make(map[int64]*repo_model.Repository, len(repoIDs))
|
||||
|
@ -46,11 +46,6 @@ func (s Stopwatch) Seconds() int64 {
|
||||
return int64(timeutil.TimeStampNow() - s.CreatedUnix)
|
||||
}
|
||||
|
||||
// Duration returns a human-readable duration string based on local server time
|
||||
func (s Stopwatch) Duration() string {
|
||||
return util.SecToTime(s.Seconds())
|
||||
}
|
||||
|
||||
func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, exists bool, err error) {
|
||||
sw = new(Stopwatch)
|
||||
exists, err = db.GetEngine(ctx).
|
||||
@ -201,7 +196,7 @@ func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Iss
|
||||
Doer: user,
|
||||
Issue: issue,
|
||||
Repo: issue.Repo,
|
||||
Content: util.SecToTime(timediff),
|
||||
Content: util.SecToHours(timediff),
|
||||
Type: CommentTypeStopTracking,
|
||||
TimeID: tt.ID,
|
||||
}); err != nil {
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"code.gitea.io/gitea/models/migrations/v1_21"
|
||||
"code.gitea.io/gitea/models/migrations/v1_22"
|
||||
"code.gitea.io/gitea/models/migrations/v1_23"
|
||||
"code.gitea.io/gitea/models/migrations/v1_24"
|
||||
"code.gitea.io/gitea/models/migrations/v1_6"
|
||||
"code.gitea.io/gitea/models/migrations/v1_7"
|
||||
"code.gitea.io/gitea/models/migrations/v1_8"
|
||||
@ -369,6 +370,9 @@ func prepareMigrationTasks() []*migration {
|
||||
newMigration(309, "Improve Notification table indices", v1_23.ImproveNotificationTableIndices),
|
||||
newMigration(310, "Add Priority to ProtectedBranch", v1_23.AddPriorityToProtectedBranch),
|
||||
newMigration(311, "Add TimeEstimate to Issue table", v1_23.AddTimeEstimateColumnToIssueTable),
|
||||
|
||||
// Gitea 1.23.0-rc0 ends at migration ID number 311 (database version 312)
|
||||
newMigration(312, "Add DeleteBranchAfterMerge to AutoMerge", v1_24.AddDeleteBranchAfterMergeForAutoMerge),
|
||||
}
|
||||
return preparedMigrations
|
||||
}
|
||||
|
21
models/migrations/v1_24/v312.go
Normal file
21
models/migrations/v1_24/v312.go
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package v1_24 //nolint
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type pullAutoMerge struct {
|
||||
DeleteBranchAfterMerge bool
|
||||
}
|
||||
|
||||
// TableName return database table name for xorm
|
||||
func (pullAutoMerge) TableName() string {
|
||||
return "pull_auto_merge"
|
||||
}
|
||||
|
||||
func AddDeleteBranchAfterMergeForAutoMerge(x *xorm.Engine) error {
|
||||
return x.Sync(new(pullAutoMerge))
|
||||
}
|
@ -16,6 +16,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUser_IsOwnedBy(t *testing.T) {
|
||||
@ -180,9 +181,8 @@ func TestRestrictedUserOrgMembers(t *testing.T) {
|
||||
ID: 29,
|
||||
IsRestricted: true,
|
||||
})
|
||||
if !assert.True(t, restrictedUser.IsRestricted) {
|
||||
return // ensure fixtures return restricted user
|
||||
}
|
||||
// ensure fixtures return restricted user
|
||||
require.True(t, restrictedUser.IsRestricted)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
@ -175,10 +175,14 @@ func (p *Permission) LogString() string {
|
||||
return fmt.Sprintf(format, args...)
|
||||
}
|
||||
|
||||
func applyEveryoneRepoPermission(user *user_model.User, perm *Permission) {
|
||||
func finalProcessRepoUnitPermission(user *user_model.User, perm *Permission) {
|
||||
if user == nil || user.ID <= 0 {
|
||||
// for anonymous access, it could be:
|
||||
// AccessMode is None or Read, units has repo units, unitModes is nil
|
||||
return
|
||||
}
|
||||
|
||||
// apply everyone access permissions
|
||||
for _, u := range perm.units {
|
||||
if u.EveryoneAccessMode >= perm_model.AccessModeRead && u.EveryoneAccessMode > perm.everyoneAccessMode[u.Type] {
|
||||
if perm.everyoneAccessMode == nil {
|
||||
@ -187,17 +191,40 @@ func applyEveryoneRepoPermission(user *user_model.User, perm *Permission) {
|
||||
perm.everyoneAccessMode[u.Type] = u.EveryoneAccessMode
|
||||
}
|
||||
}
|
||||
|
||||
if perm.unitsMode == nil {
|
||||
// if unitsMode is not set, then it means that the default p.AccessMode applies to all units
|
||||
return
|
||||
}
|
||||
|
||||
// remove no permission units
|
||||
origPermUnits := perm.units
|
||||
perm.units = make([]*repo_model.RepoUnit, 0, len(perm.units))
|
||||
for _, u := range origPermUnits {
|
||||
shouldKeep := false
|
||||
for t := range perm.unitsMode {
|
||||
if shouldKeep = u.Type == t; shouldKeep {
|
||||
break
|
||||
}
|
||||
}
|
||||
for t := range perm.everyoneAccessMode {
|
||||
if shouldKeep = shouldKeep || u.Type == t; shouldKeep {
|
||||
break
|
||||
}
|
||||
}
|
||||
if shouldKeep {
|
||||
perm.units = append(perm.units, u)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserRepoPermission returns the user permissions to the repository
|
||||
func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
applyEveryoneRepoPermission(user, &perm)
|
||||
}
|
||||
if log.IsTrace() {
|
||||
log.Trace("Permission Loaded for user %-v in repo %-v, permissions: %-+v", user, repo, perm)
|
||||
finalProcessRepoUnitPermission(user, &perm)
|
||||
}
|
||||
log.Trace("Permission Loaded for user %-v in repo %-v, permissions: %-+v", user, repo, perm)
|
||||
}()
|
||||
|
||||
if err = repo.LoadUnits(ctx); err != nil {
|
||||
@ -294,16 +321,6 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
|
||||
}
|
||||
}
|
||||
|
||||
// remove no permission units
|
||||
perm.units = make([]*repo_model.RepoUnit, 0, len(repo.Units))
|
||||
for t := range perm.unitsMode {
|
||||
for _, u := range repo.Units {
|
||||
if u.Type == t {
|
||||
perm.units = append(perm.units, u)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return perm, err
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ func TestApplyEveryoneRepoPermission(t *testing.T) {
|
||||
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
|
||||
},
|
||||
}
|
||||
applyEveryoneRepoPermission(nil, &perm)
|
||||
finalProcessRepoUnitPermission(nil, &perm)
|
||||
assert.False(t, perm.CanRead(unit.TypeWiki))
|
||||
|
||||
perm = Permission{
|
||||
@ -59,7 +59,7 @@ func TestApplyEveryoneRepoPermission(t *testing.T) {
|
||||
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
|
||||
},
|
||||
}
|
||||
applyEveryoneRepoPermission(&user_model.User{ID: 0}, &perm)
|
||||
finalProcessRepoUnitPermission(&user_model.User{ID: 0}, &perm)
|
||||
assert.False(t, perm.CanRead(unit.TypeWiki))
|
||||
|
||||
perm = Permission{
|
||||
@ -68,7 +68,7 @@ func TestApplyEveryoneRepoPermission(t *testing.T) {
|
||||
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
|
||||
},
|
||||
}
|
||||
applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm)
|
||||
finalProcessRepoUnitPermission(&user_model.User{ID: 1}, &perm)
|
||||
assert.True(t, perm.CanRead(unit.TypeWiki))
|
||||
|
||||
perm = Permission{
|
||||
@ -77,20 +77,22 @@ func TestApplyEveryoneRepoPermission(t *testing.T) {
|
||||
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
|
||||
},
|
||||
}
|
||||
applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm)
|
||||
finalProcessRepoUnitPermission(&user_model.User{ID: 1}, &perm)
|
||||
// it should work the same as "EveryoneAccessMode: none" because the default AccessMode should be applied to units
|
||||
assert.True(t, perm.CanWrite(unit.TypeWiki))
|
||||
|
||||
perm = Permission{
|
||||
units: []*repo_model.RepoUnit{
|
||||
{Type: unit.TypeCode}, // will be removed
|
||||
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
|
||||
},
|
||||
unitsMode: map[unit.Type]perm_model.AccessMode{
|
||||
unit.TypeWiki: perm_model.AccessModeWrite,
|
||||
},
|
||||
}
|
||||
applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm)
|
||||
finalProcessRepoUnitPermission(&user_model.User{ID: 1}, &perm)
|
||||
assert.True(t, perm.CanWrite(unit.TypeWiki))
|
||||
assert.Len(t, perm.units, 1)
|
||||
}
|
||||
|
||||
func TestUnitAccessMode(t *testing.T) {
|
||||
|
@ -15,13 +15,14 @@ import (
|
||||
|
||||
// AutoMerge represents a pull request scheduled for merging when checks succeed
|
||||
type AutoMerge struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
PullID int64 `xorm:"UNIQUE"`
|
||||
DoerID int64 `xorm:"INDEX NOT NULL"`
|
||||
Doer *user_model.User `xorm:"-"`
|
||||
MergeStyle repo_model.MergeStyle `xorm:"varchar(30)"`
|
||||
Message string `xorm:"LONGTEXT"`
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
PullID int64 `xorm:"UNIQUE"`
|
||||
DoerID int64 `xorm:"INDEX NOT NULL"`
|
||||
Doer *user_model.User `xorm:"-"`
|
||||
MergeStyle repo_model.MergeStyle `xorm:"varchar(30)"`
|
||||
Message string `xorm:"LONGTEXT"`
|
||||
DeleteBranchAfterMerge bool
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created"`
|
||||
}
|
||||
|
||||
// TableName return database table name for xorm
|
||||
@ -49,7 +50,7 @@ func IsErrAlreadyScheduledToAutoMerge(err error) bool {
|
||||
}
|
||||
|
||||
// ScheduleAutoMerge schedules a pull request to be merged when all checks succeed
|
||||
func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pullID int64, style repo_model.MergeStyle, message string) error {
|
||||
func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pullID int64, style repo_model.MergeStyle, message string, deleteBranchAfterMerge bool) error {
|
||||
// Check if we already have a merge scheduled for that pull request
|
||||
if exists, _, err := GetScheduledMergeByPullID(ctx, pullID); err != nil {
|
||||
return err
|
||||
@ -58,10 +59,11 @@ func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pullID int64,
|
||||
}
|
||||
|
||||
_, err := db.GetEngine(ctx).Insert(&AutoMerge{
|
||||
DoerID: doer.ID,
|
||||
PullID: pullID,
|
||||
MergeStyle: style,
|
||||
Message: message,
|
||||
DoerID: doer.ID,
|
||||
PullID: pullID,
|
||||
MergeStyle: style,
|
||||
Message: message,
|
||||
DeleteBranchAfterMerge: deleteBranchAfterMerge,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
@ -56,16 +56,11 @@ func repoArchiverForRelativePath(relativePath string) (*RepoArchiver, error) {
|
||||
if err != nil {
|
||||
return nil, util.SilentWrap{Message: fmt.Sprintf("invalid storage path: %s", relativePath), Err: util.ErrInvalidArgument}
|
||||
}
|
||||
nameExts := strings.SplitN(parts[2], ".", 2)
|
||||
if len(nameExts) != 2 {
|
||||
commitID, archiveType := git.SplitArchiveNameType(parts[2])
|
||||
if archiveType == git.ArchiveUnknown {
|
||||
return nil, util.SilentWrap{Message: fmt.Sprintf("invalid storage path: %s", relativePath), Err: util.ErrInvalidArgument}
|
||||
}
|
||||
|
||||
return &RepoArchiver{
|
||||
RepoID: repoID,
|
||||
CommitID: parts[1] + nameExts[0],
|
||||
Type: git.ToArchiveType(nameExts[1]),
|
||||
}, nil
|
||||
return &RepoArchiver{RepoID: repoID, CommitID: commitID, Type: archiveType}, nil
|
||||
}
|
||||
|
||||
// GetRepoArchiver get an archiver
|
||||
|
@ -54,6 +54,7 @@ func UpdateRepoLicenses(ctx context.Context, repo *Repository, commitID string,
|
||||
for _, o := range oldLicenses {
|
||||
// Update already existing license
|
||||
if o.License == license {
|
||||
o.CommitID = commitID
|
||||
if _, err := db.GetEngine(ctx).ID(o.ID).Cols("`commit_id`").Update(o); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -784,60 +784,10 @@ func GetRepositoryByName(ctx context.Context, ownerID int64, name string) (*Repo
|
||||
return &repo, err
|
||||
}
|
||||
|
||||
func parseRepositoryURL(ctx context.Context, repoURL string) (ret struct {
|
||||
OwnerName, RepoName, RemainingPath string
|
||||
},
|
||||
) {
|
||||
// possible urls for git:
|
||||
// https://my.domain/sub-path/<owner>/<repo>[.git]
|
||||
// git+ssh://user@my.domain/<owner>/<repo>[.git]
|
||||
// ssh://user@my.domain/<owner>/<repo>[.git]
|
||||
// user@my.domain:<owner>/<repo>[.git]
|
||||
|
||||
fillPathParts := func(s string) {
|
||||
s = strings.TrimPrefix(s, "/")
|
||||
fields := strings.SplitN(s, "/", 3)
|
||||
if len(fields) >= 2 {
|
||||
ret.OwnerName = fields[0]
|
||||
ret.RepoName = strings.TrimSuffix(fields[1], ".git")
|
||||
if len(fields) == 3 {
|
||||
ret.RemainingPath = "/" + fields[2]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parsed, err := giturl.ParseGitURL(repoURL)
|
||||
if err != nil {
|
||||
return ret
|
||||
}
|
||||
if parsed.URL.Scheme == "http" || parsed.URL.Scheme == "https" {
|
||||
if !httplib.IsCurrentGiteaSiteURL(ctx, repoURL) {
|
||||
return ret
|
||||
}
|
||||
fillPathParts(strings.TrimPrefix(parsed.URL.Path, setting.AppSubURL))
|
||||
} else if parsed.URL.Scheme == "ssh" || parsed.URL.Scheme == "git+ssh" {
|
||||
domainSSH := setting.SSH.Domain
|
||||
domainCur := httplib.GuessCurrentHostDomain(ctx)
|
||||
urlDomain, _, _ := net.SplitHostPort(parsed.URL.Host)
|
||||
urlDomain = util.IfZero(urlDomain, parsed.URL.Host)
|
||||
if urlDomain == "" {
|
||||
return ret
|
||||
}
|
||||
// check whether URL domain is the App domain
|
||||
domainMatches := domainSSH == urlDomain
|
||||
// check whether URL domain is current domain from context
|
||||
domainMatches = domainMatches || (domainCur != "" && domainCur == urlDomain)
|
||||
if domainMatches {
|
||||
fillPathParts(parsed.URL.Path)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// GetRepositoryByURL returns the repository by given url
|
||||
func GetRepositoryByURL(ctx context.Context, repoURL string) (*Repository, error) {
|
||||
ret := parseRepositoryURL(ctx, repoURL)
|
||||
if ret.OwnerName == "" {
|
||||
ret, err := giturl.ParseRepositoryURL(ctx, repoURL)
|
||||
if err != nil || ret.OwnerName == "" {
|
||||
return nil, fmt.Errorf("unknown or malformed repository URL")
|
||||
}
|
||||
return GetRepositoryByOwnerAndName(ctx, ret.OwnerName, ret.RepoName)
|
||||
|
@ -4,16 +4,12 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@ -132,79 +128,6 @@ func TestMetas(t *testing.T) {
|
||||
assert.Equal(t, ",owners,team1,", metas["teams"])
|
||||
}
|
||||
|
||||
func TestParseRepositoryURLPathSegments(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000")()
|
||||
|
||||
ctxURL, _ := url.Parse("https://gitea")
|
||||
ctxReq := &http.Request{URL: ctxURL, Header: http.Header{}}
|
||||
ctxReq.Host = ctxURL.Host
|
||||
ctxReq.Header.Add("X-Forwarded-Proto", ctxURL.Scheme)
|
||||
ctx := context.WithValue(context.Background(), httplib.RequestContextKey, ctxReq)
|
||||
cases := []struct {
|
||||
input string
|
||||
ownerName, repoName, remaining string
|
||||
}{
|
||||
{input: "/user/repo"},
|
||||
|
||||
{input: "https://localhost:3000/user/repo", ownerName: "user", repoName: "repo"},
|
||||
{input: "https://external:3000/user/repo"},
|
||||
|
||||
{input: "https://localhost:3000/user/repo.git/other", ownerName: "user", repoName: "repo", remaining: "/other"},
|
||||
|
||||
{input: "https://gitea/user/repo", ownerName: "user", repoName: "repo"},
|
||||
{input: "https://gitea:3333/user/repo"},
|
||||
|
||||
{input: "ssh://try.gitea.io:2222/user/repo", ownerName: "user", repoName: "repo"},
|
||||
{input: "ssh://external:2222/user/repo"},
|
||||
|
||||
{input: "git+ssh://user@try.gitea.io/user/repo.git", ownerName: "user", repoName: "repo"},
|
||||
{input: "git+ssh://user@external/user/repo.git"},
|
||||
|
||||
{input: "root@try.gitea.io:user/repo.git", ownerName: "user", repoName: "repo"},
|
||||
{input: "root@gitea:user/repo.git", ownerName: "user", repoName: "repo"},
|
||||
{input: "root@external:user/repo.git"},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.input, func(t *testing.T) {
|
||||
ret := parseRepositoryURL(ctx, c.input)
|
||||
assert.Equal(t, c.ownerName, ret.OwnerName)
|
||||
assert.Equal(t, c.repoName, ret.RepoName)
|
||||
assert.Equal(t, c.remaining, ret.RemainingPath)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("WithSubpath", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000/subpath")()
|
||||
defer test.MockVariableValue(&setting.AppSubURL, "/subpath")()
|
||||
cases = []struct {
|
||||
input string
|
||||
ownerName, repoName, remaining string
|
||||
}{
|
||||
{input: "https://localhost:3000/user/repo"},
|
||||
{input: "https://localhost:3000/subpath/user/repo.git/other", ownerName: "user", repoName: "repo", remaining: "/other"},
|
||||
|
||||
{input: "ssh://try.gitea.io:2222/user/repo", ownerName: "user", repoName: "repo"},
|
||||
{input: "ssh://external:2222/user/repo"},
|
||||
|
||||
{input: "git+ssh://user@try.gitea.io/user/repo.git", ownerName: "user", repoName: "repo"},
|
||||
{input: "git+ssh://user@external/user/repo.git"},
|
||||
|
||||
{input: "root@try.gitea.io:user/repo.git", ownerName: "user", repoName: "repo"},
|
||||
{input: "root@external:user/repo.git"},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.input, func(t *testing.T) {
|
||||
ret := parseRepositoryURL(ctx, c.input)
|
||||
assert.Equal(t, c.ownerName, ret.OwnerName)
|
||||
assert.Equal(t, c.repoName, ret.RepoName)
|
||||
assert.Equal(t, c.remaining, ret.RemainingPath)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetRepositoryByURL(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
|
@ -11,35 +11,13 @@ import (
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// Copy copies file from source to target path.
|
||||
func Copy(src, dest string) error {
|
||||
// Gather file information to set back later.
|
||||
si, err := os.Lstat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Handle symbolic link.
|
||||
if si.Mode()&os.ModeSymlink != 0 {
|
||||
target, err := os.Readlink(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// NOTE: os.Chmod and os.Chtimes don't recognize symbolic link,
|
||||
// which will lead "no such file or directory" error.
|
||||
return os.Symlink(target, dest)
|
||||
}
|
||||
|
||||
return util.CopyFile(src, dest)
|
||||
}
|
||||
|
||||
// Sync synchronizes the two files. This is skipped if both files
|
||||
// SyncFile synchronizes the two files. This is skipped if both files
|
||||
// exist and the size, modtime, and mode match.
|
||||
func Sync(srcPath, destPath string) error {
|
||||
func SyncFile(srcPath, destPath string) error {
|
||||
dest, err := os.Stat(destPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return Copy(srcPath, destPath)
|
||||
return util.CopyFile(srcPath, destPath)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -55,7 +33,7 @@ func Sync(srcPath, destPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Copy(srcPath, destPath)
|
||||
return util.CopyFile(srcPath, destPath)
|
||||
}
|
||||
|
||||
// SyncDirs synchronizes files recursively from source to target directory.
|
||||
@ -66,6 +44,10 @@ func SyncDirs(srcPath, destPath string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// the keep file is used to keep the directory in a git repository, it doesn't need to be synced
|
||||
// and go-git doesn't work with the ".keep" file (it would report errors like "ref is empty")
|
||||
const keepFile = ".keep"
|
||||
|
||||
// find and delete all untracked files
|
||||
destFiles, err := util.ListDirRecursively(destPath, &util.ListDirOptions{IncludeDir: true})
|
||||
if err != nil {
|
||||
@ -73,16 +55,20 @@ func SyncDirs(srcPath, destPath string) error {
|
||||
}
|
||||
for _, destFile := range destFiles {
|
||||
destFilePath := filepath.Join(destPath, destFile)
|
||||
shouldRemove := filepath.Base(destFilePath) == keepFile
|
||||
if _, err = os.Stat(filepath.Join(srcPath, destFile)); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// if src file does not exist, remove dest file
|
||||
if err = os.RemoveAll(destFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
shouldRemove = true
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// if src file does not exist, remove dest file
|
||||
if shouldRemove {
|
||||
if err = os.RemoveAll(destFilePath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sync src files to dest
|
||||
@ -95,8 +81,8 @@ func SyncDirs(srcPath, destPath string) error {
|
||||
// util.ListDirRecursively appends a slash to the directory name
|
||||
if strings.HasSuffix(srcFile, "/") {
|
||||
err = os.MkdirAll(destFilePath, os.ModePerm)
|
||||
} else {
|
||||
err = Sync(filepath.Join(srcPath, srcFile), destFilePath)
|
||||
} else if filepath.Base(destFilePath) != keepFile {
|
||||
err = SyncFile(filepath.Join(srcPath, srcFile), destFilePath)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetUserOpenIDs(t *testing.T) {
|
||||
@ -34,30 +35,23 @@ func TestGetUserOpenIDs(t *testing.T) {
|
||||
func TestToggleUserOpenIDVisibility(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
oids, err := user_model.GetUserOpenIDs(db.DefaultContext, int64(2))
|
||||
if !assert.NoError(t, err) || !assert.Len(t, oids, 1) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Len(t, oids, 1)
|
||||
assert.True(t, oids[0].Show)
|
||||
|
||||
err = user_model.ToggleUserOpenIDVisibility(db.DefaultContext, oids[0].ID)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
oids, err = user_model.GetUserOpenIDs(db.DefaultContext, int64(2))
|
||||
if !assert.NoError(t, err) || !assert.Len(t, oids, 1) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Len(t, oids, 1)
|
||||
|
||||
assert.False(t, oids[0].Show)
|
||||
err = user_model.ToggleUserOpenIDVisibility(db.DefaultContext, oids[0].ID)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
oids, err = user_model.GetUserOpenIDs(db.DefaultContext, int64(2))
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
if assert.Len(t, oids, 1) {
|
||||
assert.True(t, oids[0].Show)
|
||||
}
|
||||
|
47
models/user/user_list.go
Normal file
47
models/user/user_list.go
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
)
|
||||
|
||||
func GetUsersMapByIDs(ctx context.Context, userIDs []int64) (map[int64]*User, error) {
|
||||
userMaps := make(map[int64]*User, len(userIDs))
|
||||
left := len(userIDs)
|
||||
for left > 0 {
|
||||
limit := db.DefaultMaxInSize
|
||||
if left < limit {
|
||||
limit = left
|
||||
}
|
||||
err := db.GetEngine(ctx).
|
||||
In("id", userIDs[:limit]).
|
||||
Find(&userMaps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
left -= limit
|
||||
userIDs = userIDs[limit:]
|
||||
}
|
||||
return userMaps, nil
|
||||
}
|
||||
|
||||
func GetPossibleUserFromMap(userID int64, usererMaps map[int64]*User) *User {
|
||||
switch userID {
|
||||
case GhostUserID:
|
||||
return NewGhostUser()
|
||||
case ActionsUserID:
|
||||
return NewActionsUser()
|
||||
case 0:
|
||||
return nil
|
||||
default:
|
||||
user, ok := usererMaps[userID]
|
||||
if !ok {
|
||||
return NewGhostUser()
|
||||
}
|
||||
return user
|
||||
}
|
||||
}
|
@ -11,6 +11,19 @@ import (
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
)
|
||||
|
||||
// GetSystemOrDefaultWebhooks returns webhooks by given argument or all if argument is missing.
|
||||
func GetSystemOrDefaultWebhooks(ctx context.Context, isSystemWebhook optional.Option[bool]) ([]*Webhook, error) {
|
||||
webhooks := make([]*Webhook, 0, 5)
|
||||
if !isSystemWebhook.Has() {
|
||||
return webhooks, db.GetEngine(ctx).Where("repo_id=? AND owner_id=?", 0, 0).
|
||||
Find(&webhooks)
|
||||
}
|
||||
|
||||
return webhooks, db.GetEngine(ctx).
|
||||
Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, isSystemWebhook.Value()).
|
||||
Find(&webhooks)
|
||||
}
|
||||
|
||||
// GetDefaultWebhooks returns all admin-default webhooks.
|
||||
func GetDefaultWebhooks(ctx context.Context) ([]*Webhook, error) {
|
||||
webhooks := make([]*Webhook, 0, 5)
|
||||
|
37
models/webhook/webhook_system_test.go
Normal file
37
models/webhook/webhook_system_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetSystemOrDefaultWebhooks(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
hooks, err := GetSystemOrDefaultWebhooks(db.DefaultContext, optional.None[bool]())
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, hooks, 2) {
|
||||
assert.Equal(t, int64(5), hooks[0].ID)
|
||||
assert.Equal(t, int64(6), hooks[1].ID)
|
||||
}
|
||||
|
||||
hooks, err = GetSystemOrDefaultWebhooks(db.DefaultContext, optional.Some(true))
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, hooks, 1) {
|
||||
assert.Equal(t, int64(5), hooks[0].ID)
|
||||
}
|
||||
|
||||
hooks, err = GetSystemOrDefaultWebhooks(db.DefaultContext, optional.Some(false))
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, hooks, 1) {
|
||||
assert.Equal(t, int64(6), hooks[0].ID)
|
||||
}
|
||||
}
|
@ -3,7 +3,11 @@
|
||||
|
||||
package analyze
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsVendor(t *testing.T) {
|
||||
tests := []struct {
|
||||
@ -33,9 +37,8 @@ func TestIsVendor(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.path, func(t *testing.T) {
|
||||
if got := IsVendor(tt.path); got != tt.want {
|
||||
t.Errorf("IsVendor() = %v, want %v", got, tt.want)
|
||||
}
|
||||
got := IsVendor(tt.path)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,9 @@ package openid
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testDiscoveredInfo struct{}
|
||||
@ -29,21 +32,17 @@ func TestTimedDiscoveryCache(t *testing.T) {
|
||||
dc.Put("foo", &testDiscoveredInfo{}) // openid.opEndpoint: "a", openid.opLocalID: "b", openid.claimedID: "c"})
|
||||
|
||||
// Make sure we can retrieve them
|
||||
if di := dc.Get("foo"); di == nil {
|
||||
t.Errorf("Expected a result, got nil")
|
||||
} else if di.OpEndpoint() != "opEndpoint" || di.OpLocalID() != "opLocalID" || di.ClaimedID() != "claimedID" {
|
||||
t.Errorf("Expected opEndpoint opLocalID claimedID, got %v %v %v", di.OpEndpoint(), di.OpLocalID(), di.ClaimedID())
|
||||
}
|
||||
di := dc.Get("foo")
|
||||
require.NotNil(t, di)
|
||||
assert.Equal(t, "opEndpoint", di.OpEndpoint())
|
||||
assert.Equal(t, "opLocalID", di.OpLocalID())
|
||||
assert.Equal(t, "claimedID", di.ClaimedID())
|
||||
|
||||
// Attempt to get a non-existent value
|
||||
if di := dc.Get("bar"); di != nil {
|
||||
t.Errorf("Expected nil, got %v", di)
|
||||
}
|
||||
assert.Nil(t, dc.Get("bar"))
|
||||
|
||||
// Sleep one second and try retrieve again
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
if di := dc.Get("foo"); di != nil {
|
||||
t.Errorf("Expected a nil, got a result")
|
||||
}
|
||||
assert.Nil(t, dc.Get("foo"))
|
||||
}
|
||||
|
@ -15,5 +15,5 @@ func TestPamAuth(t *testing.T) {
|
||||
result, err := Auth("gitea", "user1", "false-pwd")
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, err, "Authentication failure")
|
||||
assert.Len(t, result)
|
||||
assert.Empty(t, result)
|
||||
}
|
||||
|
9
modules/cache/cache.go
vendored
9
modules/cache/cache.go
vendored
@ -37,10 +37,15 @@ func Init() error {
|
||||
}
|
||||
|
||||
const (
|
||||
testCacheKey = "DefaultCache.TestKey"
|
||||
SlowCacheThreshold = 100 * time.Microsecond
|
||||
testCacheKey = "DefaultCache.TestKey"
|
||||
// SlowCacheThreshold marks cache tests as slow
|
||||
// set to 30ms per discussion: https://github.com/go-gitea/gitea/issues/33190
|
||||
// TODO: Replace with metrics histogram
|
||||
SlowCacheThreshold = 30 * time.Millisecond
|
||||
)
|
||||
|
||||
// Test performs delete, put and get operations on a predefined key
|
||||
// returns
|
||||
func Test() (time.Duration, error) {
|
||||
if defaultCache == nil {
|
||||
return 0, fmt.Errorf("default cache not initialized")
|
||||
|
3
modules/cache/cache_test.go
vendored
3
modules/cache/cache_test.go
vendored
@ -43,7 +43,8 @@ func TestTest(t *testing.T) {
|
||||
elapsed, err := Test()
|
||||
assert.NoError(t, err)
|
||||
// mem cache should take from 300ns up to 1ms on modern hardware ...
|
||||
assert.Less(t, elapsed, time.Millisecond)
|
||||
assert.Positive(t, elapsed)
|
||||
assert.Less(t, elapsed, SlowCacheThreshold)
|
||||
}
|
||||
|
||||
func TestGetCache(t *testing.T) {
|
||||
|
@ -5,7 +5,6 @@
|
||||
package emoji
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -22,32 +21,18 @@ func TestLookup(t *testing.T) {
|
||||
c := FromAlias(":beer:")
|
||||
d := FromAlias("beer")
|
||||
|
||||
if !reflect.DeepEqual(a, b) {
|
||||
t.Errorf("a and b should equal")
|
||||
}
|
||||
if !reflect.DeepEqual(b, c) {
|
||||
t.Errorf("b and c should equal")
|
||||
}
|
||||
if !reflect.DeepEqual(c, d) {
|
||||
t.Errorf("c and d should equal")
|
||||
}
|
||||
if !reflect.DeepEqual(a, d) {
|
||||
t.Errorf("a and d should equal")
|
||||
}
|
||||
assert.Equal(t, a, b)
|
||||
assert.Equal(t, b, c)
|
||||
assert.Equal(t, c, d)
|
||||
assert.Equal(t, a, d)
|
||||
|
||||
m := FromCode("\U0001f44d")
|
||||
n := FromAlias(":thumbsup:")
|
||||
o := FromAlias("+1")
|
||||
|
||||
if !reflect.DeepEqual(m, n) {
|
||||
t.Errorf("m and n should equal")
|
||||
}
|
||||
if !reflect.DeepEqual(n, o) {
|
||||
t.Errorf("n and o should equal")
|
||||
}
|
||||
if !reflect.DeepEqual(m, o) {
|
||||
t.Errorf("m and o should equal")
|
||||
}
|
||||
assert.Equal(t, m, n)
|
||||
assert.Equal(t, m, o)
|
||||
assert.Equal(t, n, o)
|
||||
}
|
||||
|
||||
func TestReplacers(t *testing.T) {
|
||||
@ -61,9 +46,7 @@ func TestReplacers(t *testing.T) {
|
||||
|
||||
for i, x := range tests {
|
||||
s := x.f(x.v)
|
||||
if s != x.exp {
|
||||
t.Errorf("test %d `%s` expected `%s`, got: `%s`", i, x.v, x.exp, s)
|
||||
}
|
||||
assert.Equalf(t, x.exp, s, "test %d `%s` expected `%s`, got: `%s`", i, x.v, x.exp, s)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,9 @@ package eventsource
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_wrapNewlines(t *testing.T) {
|
||||
@ -38,16 +41,10 @@ func Test_wrapNewlines(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
w := &bytes.Buffer{}
|
||||
gotSum, err := wrapNewlines(w, []byte(tt.prefix), []byte(tt.value))
|
||||
if err != nil {
|
||||
t.Errorf("wrapNewlines() error = %v", err)
|
||||
return
|
||||
}
|
||||
if gotSum != int64(len(tt.output)) {
|
||||
t.Errorf("wrapNewlines() = %v, want %v", gotSum, int64(len(tt.output)))
|
||||
}
|
||||
if gotW := w.String(); gotW != tt.output {
|
||||
t.Errorf("wrapNewlines() = %v, want %v", gotW, tt.output)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, len(tt.output), gotSum)
|
||||
assert.Equal(t, tt.output, w.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,7 @@ func TestBlob_Data(t *testing.T) {
|
||||
output := "file2\n"
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
repo, err := openRepositoryWithDefaultContext(bareRepo1Path)
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fatal()
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer repo.Close()
|
||||
|
||||
testBlob, err := repo.GetBlob("6c493ff740f9380390d5c9ddef4af18697ac9375")
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/git/internal" //nolint:depguard // only this file can use the internal type CmdArg, other files and packages should use AddXxx functions
|
||||
"code.gitea.io/gitea/modules/gtprof"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@ -54,7 +55,7 @@ func logArgSanitize(arg string) string {
|
||||
} else if filepath.IsAbs(arg) {
|
||||
base := filepath.Base(arg)
|
||||
dir := filepath.Dir(arg)
|
||||
return filepath.Join(filepath.Base(dir), base)
|
||||
return ".../" + filepath.Join(filepath.Base(dir), base)
|
||||
}
|
||||
return arg
|
||||
}
|
||||
@ -295,15 +296,20 @@ func (c *Command) run(skip int, opts *RunOpts) error {
|
||||
timeout = defaultCommandExecutionTimeout
|
||||
}
|
||||
|
||||
var desc string
|
||||
cmdLogString := c.LogString()
|
||||
callerInfo := util.CallerFuncName(1 /* util */ + 1 /* this */ + skip /* parent */)
|
||||
if pos := strings.LastIndex(callerInfo, "/"); pos >= 0 {
|
||||
callerInfo = callerInfo[pos+1:]
|
||||
}
|
||||
// these logs are for debugging purposes only, so no guarantee of correctness or stability
|
||||
desc = fmt.Sprintf("git.Run(by:%s, repo:%s): %s", callerInfo, logArgSanitize(opts.Dir), c.LogString())
|
||||
desc := fmt.Sprintf("git.Run(by:%s, repo:%s): %s", callerInfo, logArgSanitize(opts.Dir), cmdLogString)
|
||||
log.Debug("git.Command: %s", desc)
|
||||
|
||||
_, span := gtprof.GetTracer().Start(c.parentContext, gtprof.TraceSpanGitRun)
|
||||
defer span.End()
|
||||
span.SetAttributeString(gtprof.TraceAttrFuncCaller, callerInfo)
|
||||
span.SetAttributeString(gtprof.TraceAttrGitCommand, cmdLogString)
|
||||
|
||||
var ctx context.Context
|
||||
var cancel context.CancelFunc
|
||||
var finished context.CancelFunc
|
||||
|
@ -58,5 +58,5 @@ func TestCommandString(t *testing.T) {
|
||||
assert.EqualValues(t, cmd.prog+` a "-m msg" "it's a test" "say \"hello\""`, cmd.LogString())
|
||||
|
||||
cmd = NewCommandContextNoGlobals(context.Background(), "url: https://a:b@c/", "/root/dir-a/dir-b")
|
||||
assert.EqualValues(t, cmd.prog+` "url: https://sanitized-credential@c/" dir-a/dir-b`, cmd.LogString())
|
||||
assert.EqualValues(t, cmd.prog+` "url: https://sanitized-credential@c/" .../dir-a/dir-b`, cmd.LogString())
|
||||
}
|
||||
|
@ -476,8 +476,12 @@ func (c *Commit) GetRepositoryDefaultPublicGPGKey(forceUpdate bool) (*GPGSetting
|
||||
}
|
||||
|
||||
func IsStringLikelyCommitID(objFmt ObjectFormat, s string, minLength ...int) bool {
|
||||
minLen := util.OptionalArg(minLength, objFmt.FullLength())
|
||||
if len(s) < minLen || len(s) > objFmt.FullLength() {
|
||||
maxLen := 64 // sha256
|
||||
if objFmt != nil {
|
||||
maxLen = objFmt.FullLength()
|
||||
}
|
||||
minLen := util.OptionalArg(minLength, maxLen)
|
||||
if len(s) < minLen || len(s) > maxLen {
|
||||
return false
|
||||
}
|
||||
for _, c := range s {
|
||||
|
@ -7,5 +7,5 @@ package git
|
||||
type CommitInfo struct {
|
||||
Entry *TreeEntry
|
||||
Commit *Commit
|
||||
SubModuleFile *CommitSubModuleFile
|
||||
SubmoduleFile *CommitSubmoduleFile
|
||||
}
|
||||
|
@ -85,8 +85,8 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
|
||||
} else if subModule != nil {
|
||||
subModuleURL = subModule.URL
|
||||
}
|
||||
subModuleFile := NewCommitSubModuleFile(subModuleURL, entry.ID.String())
|
||||
commitsInfo[i].SubModuleFile = subModuleFile
|
||||
subModuleFile := NewCommitSubmoduleFile(subModuleURL, entry.ID.String())
|
||||
commitsInfo[i].SubmoduleFile = subModuleFile
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,8 +79,8 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
|
||||
} else if subModule != nil {
|
||||
subModuleURL = subModule.URL
|
||||
}
|
||||
subModuleFile := NewCommitSubModuleFile(subModuleURL, entry.ID.String())
|
||||
commitsInfo[i].SubModuleFile = subModuleFile
|
||||
subModuleFile := NewCommitSubmoduleFile(subModuleURL, entry.ID.String())
|
||||
commitsInfo[i].SubmoduleFile = subModuleFile
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCommitsCountSha256(t *testing.T) {
|
||||
@ -94,9 +95,7 @@ signed commit`
|
||||
|
||||
commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString))
|
||||
assert.NoError(t, err)
|
||||
if !assert.NotNil(t, commitFromReader) {
|
||||
return
|
||||
}
|
||||
require.NotNil(t, commitFromReader)
|
||||
assert.EqualValues(t, sha, commitFromReader.ID)
|
||||
assert.EqualValues(t, `-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
|
@ -3,6 +3,10 @@
|
||||
|
||||
package git
|
||||
|
||||
type SubmoduleWebLink struct {
|
||||
RepoWebLink, CommitWebLink string
|
||||
}
|
||||
|
||||
// GetSubModules get all the submodules of current revision git tree
|
||||
func (c *Commit) GetSubModules() (*ObjectCache[*SubModule], error) {
|
||||
if c.submoduleCache != nil {
|
||||
|
@ -5,107 +5,50 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"context"
|
||||
|
||||
giturl "code.gitea.io/gitea/modules/git/url"
|
||||
)
|
||||
|
||||
var scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9_]+@)?([a-zA-Z0-9._-]+):(.*)$`)
|
||||
|
||||
// CommitSubModuleFile represents a file with submodule type.
|
||||
type CommitSubModuleFile struct {
|
||||
refURL string
|
||||
refID string
|
||||
// CommitSubmoduleFile represents a file with submodule type.
|
||||
type CommitSubmoduleFile struct {
|
||||
refURL string
|
||||
parsedURL *giturl.RepositoryURL
|
||||
parsed bool
|
||||
refID string
|
||||
repoLink string
|
||||
}
|
||||
|
||||
// NewCommitSubModuleFile create a new submodule file
|
||||
func NewCommitSubModuleFile(refURL, refID string) *CommitSubModuleFile {
|
||||
return &CommitSubModuleFile{
|
||||
refURL: refURL,
|
||||
refID: refID,
|
||||
}
|
||||
// NewCommitSubmoduleFile create a new submodule file
|
||||
func NewCommitSubmoduleFile(refURL, refID string) *CommitSubmoduleFile {
|
||||
return &CommitSubmoduleFile{refURL: refURL, refID: refID}
|
||||
}
|
||||
|
||||
func getRefURL(refURL, urlPrefix, repoFullName, sshDomain string) string {
|
||||
if refURL == "" {
|
||||
return ""
|
||||
func (sf *CommitSubmoduleFile) RefID() string {
|
||||
return sf.refID // this function is only used in templates
|
||||
}
|
||||
|
||||
// SubmoduleWebLink tries to make some web links for a submodule, it also works on "nil" receiver
|
||||
func (sf *CommitSubmoduleFile) SubmoduleWebLink(ctx context.Context, optCommitID ...string) *SubmoduleWebLink {
|
||||
if sf == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
refURI := strings.TrimSuffix(refURL, ".git")
|
||||
|
||||
prefixURL, _ := url.Parse(urlPrefix)
|
||||
urlPrefixHostname, _, err := net.SplitHostPort(prefixURL.Host)
|
||||
if err != nil {
|
||||
urlPrefixHostname = prefixURL.Host
|
||||
}
|
||||
|
||||
urlPrefix = strings.TrimSuffix(urlPrefix, "/")
|
||||
|
||||
// FIXME: Need to consider branch - which will require changes in modules/git/commit.go:GetSubModules
|
||||
// Relative url prefix check (according to git submodule documentation)
|
||||
if strings.HasPrefix(refURI, "./") || strings.HasPrefix(refURI, "../") {
|
||||
return urlPrefix + path.Clean(path.Join("/", repoFullName, refURI))
|
||||
}
|
||||
|
||||
if !strings.Contains(refURI, "://") {
|
||||
// scp style syntax which contains *no* port number after the : (and is not parsed by net/url)
|
||||
// ex: git@try.gitea.io:go-gitea/gitea
|
||||
match := scpSyntax.FindAllStringSubmatch(refURI, -1)
|
||||
if len(match) > 0 {
|
||||
m := match[0]
|
||||
refHostname := m[2]
|
||||
pth := m[3]
|
||||
|
||||
if !strings.HasPrefix(pth, "/") {
|
||||
pth = "/" + pth
|
||||
}
|
||||
|
||||
if urlPrefixHostname == refHostname || refHostname == sshDomain {
|
||||
return urlPrefix + path.Clean(path.Join("/", pth))
|
||||
}
|
||||
return "http://" + refHostname + pth
|
||||
if !sf.parsed {
|
||||
sf.parsed = true
|
||||
parsedURL, err := giturl.ParseRepositoryURL(ctx, sf.refURL)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
sf.parsedURL = parsedURL
|
||||
sf.repoLink = giturl.MakeRepositoryWebLink(sf.parsedURL)
|
||||
}
|
||||
|
||||
ref, err := url.Parse(refURI)
|
||||
if err != nil {
|
||||
return ""
|
||||
var commitLink string
|
||||
if len(optCommitID) == 2 {
|
||||
commitLink = sf.repoLink + "/compare/" + optCommitID[0] + "..." + optCommitID[1]
|
||||
} else if len(optCommitID) == 1 {
|
||||
commitLink = sf.repoLink + "/commit/" + optCommitID[0]
|
||||
} else {
|
||||
commitLink = sf.repoLink + "/commit/" + sf.refID
|
||||
}
|
||||
|
||||
refHostname, _, err := net.SplitHostPort(ref.Host)
|
||||
if err != nil {
|
||||
refHostname = ref.Host
|
||||
}
|
||||
|
||||
supportedSchemes := []string{"http", "https", "git", "ssh", "git+ssh"}
|
||||
|
||||
for _, scheme := range supportedSchemes {
|
||||
if ref.Scheme == scheme {
|
||||
if ref.Scheme == "http" || ref.Scheme == "https" {
|
||||
if len(ref.User.Username()) > 0 {
|
||||
return ref.Scheme + "://" + fmt.Sprintf("%v", ref.User) + "@" + ref.Host + ref.Path
|
||||
}
|
||||
return ref.Scheme + "://" + ref.Host + ref.Path
|
||||
} else if urlPrefixHostname == refHostname || refHostname == sshDomain {
|
||||
return urlPrefix + path.Clean(path.Join("/", ref.Path))
|
||||
}
|
||||
return "http://" + refHostname + ref.Path
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// RefURL guesses and returns reference URL.
|
||||
// FIXME: template passes AppURL as urlPrefix, it needs to figure out the correct approach (no hard-coded AppURL anymore)
|
||||
func (sf *CommitSubModuleFile) RefURL(urlPrefix, repoFullName, sshDomain string) string {
|
||||
return getRefURL(sf.refURL, urlPrefix, repoFullName, sshDomain)
|
||||
}
|
||||
|
||||
// RefID returns reference ID.
|
||||
func (sf *CommitSubModuleFile) RefID() string {
|
||||
return sf.refID
|
||||
return &SubmoduleWebLink{RepoWebLink: sf.repoLink, CommitWebLink: commitLink}
|
||||
}
|
||||
|
@ -1,42 +1,30 @@
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCommitSubModuleFileGetRefURL(t *testing.T) {
|
||||
kases := []struct {
|
||||
refURL string
|
||||
prefixURL string
|
||||
parentPath string
|
||||
SSHDomain string
|
||||
expect string
|
||||
}{
|
||||
{"git://github.com/user1/repo1", "/", "user1/repo2", "", "http://github.com/user1/repo1"},
|
||||
{"https://localhost/user1/repo1.git", "/", "user1/repo2", "", "https://localhost/user1/repo1"},
|
||||
{"http://localhost/user1/repo1.git", "/", "owner/reponame", "", "http://localhost/user1/repo1"},
|
||||
{"git@github.com:user1/repo1.git", "/", "owner/reponame", "", "http://github.com/user1/repo1"},
|
||||
{"ssh://git@git.zefie.net:2222/zefie/lge_g6_kernel_scripts.git", "/", "zefie/lge_g6_kernel", "", "http://git.zefie.net/zefie/lge_g6_kernel_scripts"},
|
||||
{"git@git.zefie.net:2222/zefie/lge_g6_kernel_scripts.git", "/", "zefie/lge_g6_kernel", "", "http://git.zefie.net/2222/zefie/lge_g6_kernel_scripts"},
|
||||
{"git@try.gitea.io:go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"},
|
||||
{"ssh://git@try.gitea.io:9999/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"},
|
||||
{"git://git@try.gitea.io:9999/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "", "https://try.gitea.io/go-gitea/gitea"},
|
||||
{"ssh://git@127.0.0.1:9999/go-gitea/gitea", "https://127.0.0.1:3000/", "go-gitea/sdk", "", "https://127.0.0.1:3000/go-gitea/gitea"},
|
||||
{"https://gitea.com:3000/user1/repo1.git", "https://127.0.0.1:3000/", "user/repo2", "", "https://gitea.com:3000/user1/repo1"},
|
||||
{"https://example.gitea.com/gitea/user1/repo1.git", "https://example.gitea.com/gitea/", "", "user/repo2", "https://example.gitea.com/gitea/user1/repo1"},
|
||||
{"https://username:password@github.com/username/repository.git", "/", "username/repository2", "", "https://username:password@github.com/username/repository"},
|
||||
{"somethingbad", "https://127.0.0.1:3000/go-gitea/gitea", "/", "", ""},
|
||||
{"git@localhost:user/repo", "https://localhost/", "user2/repo1", "", "https://localhost/user/repo"},
|
||||
{"../path/to/repo.git/", "https://localhost/", "user/repo2", "", "https://localhost/user/path/to/repo.git"},
|
||||
{"ssh://git@ssh.gitea.io:2222/go-gitea/gitea", "https://try.gitea.io/", "go-gitea/sdk", "ssh.gitea.io", "https://try.gitea.io/go-gitea/gitea"},
|
||||
}
|
||||
func TestCommitSubmoduleLink(t *testing.T) {
|
||||
sf := NewCommitSubmoduleFile("git@github.com:user/repo.git", "aaaa")
|
||||
|
||||
for _, kase := range kases {
|
||||
assert.EqualValues(t, kase.expect, getRefURL(kase.refURL, kase.prefixURL, kase.parentPath, kase.SSHDomain))
|
||||
}
|
||||
wl := sf.SubmoduleWebLink(context.Background())
|
||||
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
|
||||
assert.Equal(t, "https://github.com/user/repo/commit/aaaa", wl.CommitWebLink)
|
||||
|
||||
wl = sf.SubmoduleWebLink(context.Background(), "1111")
|
||||
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
|
||||
assert.Equal(t, "https://github.com/user/repo/commit/1111", wl.CommitWebLink)
|
||||
|
||||
wl = sf.SubmoduleWebLink(context.Background(), "1111", "2222")
|
||||
assert.Equal(t, "https://github.com/user/repo", wl.RepoWebLink)
|
||||
assert.Equal(t, "https://github.com/user/repo/compare/1111...2222", wl.CommitWebLink)
|
||||
|
||||
wl = (*CommitSubmoduleFile)(nil).SubmoduleWebLink(context.Background())
|
||||
assert.Nil(t, wl)
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCommitsCount(t *testing.T) {
|
||||
@ -91,9 +92,7 @@ empty commit`
|
||||
|
||||
commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString))
|
||||
assert.NoError(t, err)
|
||||
if !assert.NotNil(t, commitFromReader) {
|
||||
return
|
||||
}
|
||||
require.NotNil(t, commitFromReader)
|
||||
assert.EqualValues(t, sha, commitFromReader.ID)
|
||||
assert.EqualValues(t, `-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
@ -159,9 +158,7 @@ ISO-8859-1`
|
||||
|
||||
commitFromReader, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString))
|
||||
assert.NoError(t, err)
|
||||
if !assert.NotNil(t, commitFromReader) {
|
||||
return
|
||||
}
|
||||
require.NotNil(t, commitFromReader)
|
||||
assert.EqualValues(t, sha, commitFromReader.ID)
|
||||
assert.EqualValues(t, `-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
|
@ -64,7 +64,10 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
|
||||
} else if commit.ParentCount() == 0 {
|
||||
cmd.AddArguments("show").AddDynamicArguments(endCommit).AddDashesAndList(files...)
|
||||
} else {
|
||||
c, _ := commit.Parent(0)
|
||||
c, err := commit.Parent(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.AddArguments("diff", "-M").AddDynamicArguments(c.ID.String(), endCommit).AddDashesAndList(files...)
|
||||
}
|
||||
case RawDiffPatch:
|
||||
@ -74,7 +77,10 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
|
||||
} else if commit.ParentCount() == 0 {
|
||||
cmd.AddArguments("format-patch", "--no-signature", "--stdout", "--root").AddDynamicArguments(endCommit).AddDashesAndList(files...)
|
||||
} else {
|
||||
c, _ := commit.Parent(0)
|
||||
c, err := commit.Parent(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
query := fmt.Sprintf("%s...%s", endCommit, c.ID.String())
|
||||
cmd.AddArguments("format-patch", "--no-signature", "--stdout").AddDynamicArguments(query).AddDashesAndList(files...)
|
||||
}
|
||||
|
@ -80,6 +80,10 @@ func RefNameFromTag(shortName string) RefName {
|
||||
return RefName(TagPrefix + shortName)
|
||||
}
|
||||
|
||||
func RefNameFromCommit(shortName string) RefName {
|
||||
return RefName(shortName)
|
||||
}
|
||||
|
||||
func (ref RefName) String() string {
|
||||
return string(ref)
|
||||
}
|
||||
@ -181,32 +185,38 @@ func (ref RefName) RefGroup() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// RefType is a simple ref type of the reference, it is used for UI and webhooks
|
||||
type RefType string
|
||||
|
||||
const (
|
||||
RefTypeBranch RefType = "branch"
|
||||
RefTypeTag RefType = "tag"
|
||||
RefTypeCommit RefType = "commit"
|
||||
)
|
||||
|
||||
// RefType returns the simple ref type of the reference, e.g. branch, tag
|
||||
// It's different from RefGroup, which is using the name of the directory under .git/refs
|
||||
// Here we using branch but not heads, using tag but not tags
|
||||
func (ref RefName) RefType() string {
|
||||
var refType string
|
||||
if ref.IsBranch() {
|
||||
refType = "branch"
|
||||
} else if ref.IsTag() {
|
||||
refType = "tag"
|
||||
func (ref RefName) RefType() RefType {
|
||||
switch {
|
||||
case ref.IsBranch():
|
||||
return RefTypeBranch
|
||||
case ref.IsTag():
|
||||
return RefTypeTag
|
||||
case IsStringLikelyCommitID(nil, string(ref), 6):
|
||||
return RefTypeCommit
|
||||
}
|
||||
return refType
|
||||
return ""
|
||||
}
|
||||
|
||||
// RefURL returns the absolute URL for a ref in a repository
|
||||
func RefURL(repoURL, ref string) string {
|
||||
refFullName := RefName(ref)
|
||||
refName := util.PathEscapeSegments(refFullName.ShortName())
|
||||
switch {
|
||||
case refFullName.IsBranch():
|
||||
return repoURL + "/src/branch/" + refName
|
||||
case refFullName.IsTag():
|
||||
return repoURL + "/src/tag/" + refName
|
||||
case !Sha1ObjectFormat.IsValid(ref):
|
||||
// assume they mean a branch
|
||||
return repoURL + "/src/branch/" + refName
|
||||
default:
|
||||
return repoURL + "/src/commit/" + refName
|
||||
// RefWebLinkPath returns a path for the reference that can be used in a web link:
|
||||
// * "branch/<branch_name>"
|
||||
// * "tag/<tag_name>"
|
||||
// * "commit/<commit_id>"
|
||||
// It returns an empty string if the reference is not a branch, tag or commit.
|
||||
func (ref RefName) RefWebLinkPath() string {
|
||||
refType := ref.RefType()
|
||||
if refType == "" {
|
||||
return ""
|
||||
}
|
||||
return string(refType) + "/" + util.PathEscapeSegments(ref.ShortName())
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ func TestRefName(t *testing.T) {
|
||||
|
||||
// Test pull names
|
||||
assert.Equal(t, "1", RefName("refs/pull/1/head").PullName())
|
||||
assert.True(t, RefName("refs/pull/1/head").IsPull())
|
||||
assert.True(t, RefName("refs/pull/1/merge").IsPull())
|
||||
assert.Equal(t, "my/pull", RefName("refs/pull/my/pull/head").PullName())
|
||||
|
||||
// Test for branch names
|
||||
@ -30,9 +32,8 @@ func TestRefName(t *testing.T) {
|
||||
assert.Equal(t, "c0ffee", RefName("c0ffee").ShortName())
|
||||
}
|
||||
|
||||
func TestRefURL(t *testing.T) {
|
||||
repoURL := "/user/repo"
|
||||
assert.Equal(t, repoURL+"/src/branch/foo", RefURL(repoURL, "refs/heads/foo"))
|
||||
assert.Equal(t, repoURL+"/src/tag/foo", RefURL(repoURL, "refs/tags/foo"))
|
||||
assert.Equal(t, repoURL+"/src/commit/c0ffee", RefURL(repoURL, "c0ffee"))
|
||||
func TestRefWebLinkPath(t *testing.T) {
|
||||
assert.Equal(t, "branch/foo", RefName("refs/heads/foo").RefWebLinkPath())
|
||||
assert.Equal(t, "tag/foo", RefName("refs/tags/foo").RefWebLinkPath())
|
||||
assert.Equal(t, "commit/c0ffee", RefName("c0ffee").RefWebLinkPath())
|
||||
}
|
||||
|
@ -16,37 +16,35 @@ import (
|
||||
type ArchiveType int
|
||||
|
||||
const (
|
||||
// ZIP zip archive type
|
||||
ZIP ArchiveType = iota + 1
|
||||
// TARGZ tar gz archive type
|
||||
TARGZ
|
||||
// BUNDLE bundle archive type
|
||||
BUNDLE
|
||||
ArchiveUnknown ArchiveType = iota
|
||||
ArchiveZip // 1
|
||||
ArchiveTarGz // 2
|
||||
ArchiveBundle // 3
|
||||
)
|
||||
|
||||
// String converts an ArchiveType to string
|
||||
// String converts an ArchiveType to string: the extension of the archive file without prefix dot
|
||||
func (a ArchiveType) String() string {
|
||||
switch a {
|
||||
case ZIP:
|
||||
case ArchiveZip:
|
||||
return "zip"
|
||||
case TARGZ:
|
||||
case ArchiveTarGz:
|
||||
return "tar.gz"
|
||||
case BUNDLE:
|
||||
case ArchiveBundle:
|
||||
return "bundle"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func ToArchiveType(s string) ArchiveType {
|
||||
switch s {
|
||||
case "zip":
|
||||
return ZIP
|
||||
case "tar.gz":
|
||||
return TARGZ
|
||||
case "bundle":
|
||||
return BUNDLE
|
||||
func SplitArchiveNameType(s string) (string, ArchiveType) {
|
||||
switch {
|
||||
case strings.HasSuffix(s, ".zip"):
|
||||
return strings.TrimSuffix(s, ".zip"), ArchiveZip
|
||||
case strings.HasSuffix(s, ".tar.gz"):
|
||||
return strings.TrimSuffix(s, ".tar.gz"), ArchiveTarGz
|
||||
case strings.HasSuffix(s, ".bundle"):
|
||||
return strings.TrimSuffix(s, ".bundle"), ArchiveBundle
|
||||
}
|
||||
return 0
|
||||
return s, ArchiveUnknown
|
||||
}
|
||||
|
||||
// CreateArchive create archive content to the target path
|
||||
|
32
modules/git/repo_archive_test.go
Normal file
32
modules/git/repo_archive_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestArchiveType(t *testing.T) {
|
||||
name, archiveType := SplitArchiveNameType("test.tar.gz")
|
||||
assert.Equal(t, "test", name)
|
||||
assert.Equal(t, "tar.gz", archiveType.String())
|
||||
|
||||
name, archiveType = SplitArchiveNameType("a/b/test.zip")
|
||||
assert.Equal(t, "a/b/test", name)
|
||||
assert.Equal(t, "zip", archiveType.String())
|
||||
|
||||
name, archiveType = SplitArchiveNameType("1234.bundle")
|
||||
assert.Equal(t, "1234", name)
|
||||
assert.Equal(t, "bundle", archiveType.String())
|
||||
|
||||
name, archiveType = SplitArchiveNameType("test")
|
||||
assert.Equal(t, "test", name)
|
||||
assert.Equal(t, "unknown", archiveType.String())
|
||||
|
||||
name, archiveType = SplitArchiveNameType("test.xz")
|
||||
assert.Equal(t, "test.xz", name)
|
||||
assert.Equal(t, "unknown", archiveType.String())
|
||||
}
|
@ -57,7 +57,7 @@ func (repo *Repository) IsBranchExist(name string) bool {
|
||||
|
||||
// GetBranches returns branches from the repository, skipping "skip" initial branches and
|
||||
// returning at most "limit" branches, or all branches if "limit" is 0.
|
||||
// Branches are returned with sort of `-commiterdate` as the nogogit
|
||||
// Branches are returned with sort of `-committerdate` as the nogogit
|
||||
// implementation. This requires full fetch, sort and then the
|
||||
// skip/limit applies later as gogit returns in undefined order.
|
||||
func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) {
|
||||
|
@ -10,20 +10,18 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRepository_GetLanguageStats(t *testing.T) {
|
||||
repoPath := filepath.Join(testReposDir, "language_stats_repo")
|
||||
gitRepo, err := openRepositoryWithDefaultContext(repoPath)
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fatal()
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
defer gitRepo.Close()
|
||||
|
||||
stats, err := gitRepo.GetLanguageStats("8fee858da5796dfb37704761701bb8e800ad9ef3")
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fatal()
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, map[string]int64{
|
||||
"Python": 134,
|
||||
|
@ -182,7 +182,6 @@ func TestRepository_GetAnnotatedTag(t *testing.T) {
|
||||
|
||||
// Annotated tag's name should fail
|
||||
tag3, err := bareRepo1.GetAnnotatedTag(aTagName)
|
||||
assert.Error(t, err)
|
||||
assert.Errorf(t, err, "Length must be 40: %d", len(aTagName))
|
||||
assert.Nil(t, tag3)
|
||||
|
||||
|
@ -4,9 +4,15 @@
|
||||
package url
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
stdurl "net/url"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
// ErrWrongURLFormat represents an error with wrong url format
|
||||
@ -90,3 +96,86 @@ func ParseGitURL(remote string) (*GitURL, error) {
|
||||
extraMark: 2,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type RepositoryURL struct {
|
||||
GitURL *GitURL
|
||||
|
||||
// if the URL belongs to current Gitea instance, then the below fields have values
|
||||
OwnerName string
|
||||
RepoName string
|
||||
RemainingPath string
|
||||
}
|
||||
|
||||
// ParseRepositoryURL tries to parse a Git URL and extract the owner/repository name if it belongs to current Gitea instance.
|
||||
func ParseRepositoryURL(ctx context.Context, repoURL string) (*RepositoryURL, error) {
|
||||
// possible urls for git:
|
||||
// https://my.domain/sub-path/<owner>/<repo>[.git]
|
||||
// git+ssh://user@my.domain/<owner>/<repo>[.git]
|
||||
// ssh://user@my.domain/<owner>/<repo>[.git]
|
||||
// user@my.domain:<owner>/<repo>[.git]
|
||||
parsed, err := ParseGitURL(repoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := &RepositoryURL{}
|
||||
ret.GitURL = parsed
|
||||
|
||||
fillPathParts := func(s string) {
|
||||
s = strings.TrimPrefix(s, "/")
|
||||
fields := strings.SplitN(s, "/", 3)
|
||||
if len(fields) >= 2 {
|
||||
ret.OwnerName = fields[0]
|
||||
ret.RepoName = strings.TrimSuffix(fields[1], ".git")
|
||||
if len(fields) == 3 {
|
||||
ret.RemainingPath = "/" + fields[2]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if parsed.URL.Scheme == "http" || parsed.URL.Scheme == "https" {
|
||||
if !httplib.IsCurrentGiteaSiteURL(ctx, repoURL) {
|
||||
return ret, nil
|
||||
}
|
||||
fillPathParts(strings.TrimPrefix(parsed.URL.Path, setting.AppSubURL))
|
||||
} else if parsed.URL.Scheme == "ssh" || parsed.URL.Scheme == "git+ssh" {
|
||||
domainSSH := setting.SSH.Domain
|
||||
domainCur := httplib.GuessCurrentHostDomain(ctx)
|
||||
urlDomain, _, _ := net.SplitHostPort(parsed.URL.Host)
|
||||
urlDomain = util.IfZero(urlDomain, parsed.URL.Host)
|
||||
if urlDomain == "" {
|
||||
return ret, nil
|
||||
}
|
||||
// check whether URL domain is the App domain
|
||||
domainMatches := domainSSH == urlDomain
|
||||
// check whether URL domain is current domain from context
|
||||
domainMatches = domainMatches || (domainCur != "" && domainCur == urlDomain)
|
||||
if domainMatches {
|
||||
fillPathParts(parsed.URL.Path)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// MakeRepositoryWebLink generates a web link (http/https) for a git repository (by guessing sometimes)
|
||||
func MakeRepositoryWebLink(repoURL *RepositoryURL) string {
|
||||
if repoURL.OwnerName != "" {
|
||||
return setting.AppSubURL + "/" + repoURL.OwnerName + "/" + repoURL.RepoName
|
||||
}
|
||||
|
||||
// now, let's guess, for example:
|
||||
// * git@github.com:owner/submodule.git
|
||||
// * https://github.com/example/submodule1.git
|
||||
if repoURL.GitURL.Scheme == "http" || repoURL.GitURL.Scheme == "https" {
|
||||
return strings.TrimSuffix(repoURL.GitURL.String(), ".git")
|
||||
} else if repoURL.GitURL.Scheme == "ssh" || repoURL.GitURL.Scheme == "git+ssh" {
|
||||
hostname, _, _ := net.SplitHostPort(repoURL.GitURL.Host)
|
||||
hostname = util.IfZero(hostname, repoURL.GitURL.Host)
|
||||
urlPath := strings.TrimSuffix(repoURL.GitURL.Path, ".git")
|
||||
urlPath = strings.TrimPrefix(urlPath, "/")
|
||||
urlFull := fmt.Sprintf("https://%s/%s", hostname, urlPath)
|
||||
urlFull = strings.TrimSuffix(urlFull, "/")
|
||||
return urlFull
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
@ -4,9 +4,15 @@
|
||||
package url
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -164,3 +170,98 @@ func TestParseGitURLs(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRepositoryURL(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000")()
|
||||
defer test.MockVariableValue(&setting.SSH.Domain, "try.gitea.io")()
|
||||
|
||||
ctxURL, _ := url.Parse("https://gitea")
|
||||
ctxReq := &http.Request{URL: ctxURL, Header: http.Header{}}
|
||||
ctxReq.Host = ctxURL.Host
|
||||
ctxReq.Header.Add("X-Forwarded-Proto", ctxURL.Scheme)
|
||||
ctx := context.WithValue(context.Background(), httplib.RequestContextKey, ctxReq)
|
||||
cases := []struct {
|
||||
input string
|
||||
ownerName, repoName, remaining string
|
||||
}{
|
||||
{input: "/user/repo"},
|
||||
|
||||
{input: "https://localhost:3000/user/repo", ownerName: "user", repoName: "repo"},
|
||||
{input: "https://external:3000/user/repo"},
|
||||
|
||||
{input: "https://localhost:3000/user/repo.git/other", ownerName: "user", repoName: "repo", remaining: "/other"},
|
||||
|
||||
{input: "https://gitea/user/repo", ownerName: "user", repoName: "repo"},
|
||||
{input: "https://gitea:3333/user/repo"},
|
||||
|
||||
{input: "ssh://try.gitea.io:2222/user/repo", ownerName: "user", repoName: "repo"},
|
||||
{input: "ssh://external:2222/user/repo"},
|
||||
|
||||
{input: "git+ssh://user@try.gitea.io/user/repo.git", ownerName: "user", repoName: "repo"},
|
||||
{input: "git+ssh://user@external/user/repo.git"},
|
||||
|
||||
{input: "root@try.gitea.io:user/repo.git", ownerName: "user", repoName: "repo"},
|
||||
{input: "root@gitea:user/repo.git", ownerName: "user", repoName: "repo"},
|
||||
{input: "root@external:user/repo.git"},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.input, func(t *testing.T) {
|
||||
ret, _ := ParseRepositoryURL(ctx, c.input)
|
||||
assert.Equal(t, c.ownerName, ret.OwnerName)
|
||||
assert.Equal(t, c.repoName, ret.RepoName)
|
||||
assert.Equal(t, c.remaining, ret.RemainingPath)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("WithSubpath", func(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000/subpath")()
|
||||
defer test.MockVariableValue(&setting.AppSubURL, "/subpath")()
|
||||
cases = []struct {
|
||||
input string
|
||||
ownerName, repoName, remaining string
|
||||
}{
|
||||
{input: "https://localhost:3000/user/repo"},
|
||||
{input: "https://localhost:3000/subpath/user/repo.git/other", ownerName: "user", repoName: "repo", remaining: "/other"},
|
||||
|
||||
{input: "ssh://try.gitea.io:2222/user/repo", ownerName: "user", repoName: "repo"},
|
||||
{input: "ssh://external:2222/user/repo"},
|
||||
|
||||
{input: "git+ssh://user@try.gitea.io/user/repo.git", ownerName: "user", repoName: "repo"},
|
||||
{input: "git+ssh://user@external/user/repo.git"},
|
||||
|
||||
{input: "root@try.gitea.io:user/repo.git", ownerName: "user", repoName: "repo"},
|
||||
{input: "root@external:user/repo.git"},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.input, func(t *testing.T) {
|
||||
ret, _ := ParseRepositoryURL(ctx, c.input)
|
||||
assert.Equal(t, c.ownerName, ret.OwnerName)
|
||||
assert.Equal(t, c.repoName, ret.RepoName)
|
||||
assert.Equal(t, c.remaining, ret.RemainingPath)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestMakeRepositoryBaseLink(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.AppURL, "https://localhost:3000/subpath")()
|
||||
defer test.MockVariableValue(&setting.AppSubURL, "/subpath")()
|
||||
|
||||
u, err := ParseRepositoryURL(context.Background(), "https://localhost:3000/subpath/user/repo.git")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "/subpath/user/repo", MakeRepositoryWebLink(u))
|
||||
|
||||
u, err = ParseRepositoryURL(context.Background(), "https://github.com/owner/repo.git")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://github.com/owner/repo", MakeRepositoryWebLink(u))
|
||||
|
||||
u, err = ParseRepositoryURL(context.Background(), "git@github.com:owner/repo.git")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://github.com/owner/repo", MakeRepositoryWebLink(u))
|
||||
|
||||
u, err = ParseRepositoryURL(context.Background(), "git+ssh://other:123/owner/repo.git")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://other/owner/repo", MakeRepositoryWebLink(u))
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func BenchmarkGetCommitGraph(b *testing.B) {
|
||||
@ -235,9 +237,7 @@ func TestParseGlyphs(t *testing.T) {
|
||||
}
|
||||
row++
|
||||
}
|
||||
if len(parser.availableColors) != 9 {
|
||||
t.Errorf("Expected 9 colors but have %d", len(parser.availableColors))
|
||||
}
|
||||
assert.Len(t, parser.availableColors, 9)
|
||||
}
|
||||
|
||||
func TestCommitStringParsing(t *testing.T) {
|
||||
@ -262,9 +262,7 @@ func TestCommitStringParsing(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
if test.commitMessage != commit.Subject {
|
||||
t.Errorf("%s does not match %s", test.commitMessage, commit.Subject)
|
||||
}
|
||||
assert.Equal(t, test.commitMessage, commit.Subject)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
32
modules/gtprof/event.go
Normal file
32
modules/gtprof/event.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package gtprof
|
||||
|
||||
type EventConfig struct {
|
||||
attributes []*TraceAttribute
|
||||
}
|
||||
|
||||
type EventOption interface {
|
||||
applyEvent(*EventConfig)
|
||||
}
|
||||
|
||||
type applyEventFunc func(*EventConfig)
|
||||
|
||||
func (f applyEventFunc) applyEvent(cfg *EventConfig) {
|
||||
f(cfg)
|
||||
}
|
||||
|
||||
func WithAttributes(attrs ...*TraceAttribute) EventOption {
|
||||
return applyEventFunc(func(cfg *EventConfig) {
|
||||
cfg.attributes = append(cfg.attributes, attrs...)
|
||||
})
|
||||
}
|
||||
|
||||
func eventConfigFromOptions(options ...EventOption) *EventConfig {
|
||||
cfg := &EventConfig{}
|
||||
for _, opt := range options {
|
||||
opt.applyEvent(cfg)
|
||||
}
|
||||
return cfg
|
||||
}
|
175
modules/gtprof/trace.go
Normal file
175
modules/gtprof/trace.go
Normal file
@ -0,0 +1,175 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package gtprof
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
|
||||
type contextKey struct {
|
||||
name string
|
||||
}
|
||||
|
||||
var contextKeySpan = &contextKey{"span"}
|
||||
|
||||
type traceStarter interface {
|
||||
start(ctx context.Context, traceSpan *TraceSpan, internalSpanIdx int) (context.Context, traceSpanInternal)
|
||||
}
|
||||
|
||||
type traceSpanInternal interface {
|
||||
addEvent(name string, cfg *EventConfig)
|
||||
recordError(err error, cfg *EventConfig)
|
||||
end()
|
||||
}
|
||||
|
||||
type TraceSpan struct {
|
||||
// immutable
|
||||
parent *TraceSpan
|
||||
internalSpans []traceSpanInternal
|
||||
internalContexts []context.Context
|
||||
|
||||
// mutable, must be protected by mutex
|
||||
mu sync.RWMutex
|
||||
name string
|
||||
statusCode uint32
|
||||
statusDesc string
|
||||
startTime time.Time
|
||||
endTime time.Time
|
||||
attributes []*TraceAttribute
|
||||
children []*TraceSpan
|
||||
}
|
||||
|
||||
type TraceAttribute struct {
|
||||
Key string
|
||||
Value TraceValue
|
||||
}
|
||||
|
||||
type TraceValue struct {
|
||||
v any
|
||||
}
|
||||
|
||||
func (t *TraceValue) AsString() string {
|
||||
return fmt.Sprint(t.v)
|
||||
}
|
||||
|
||||
func (t *TraceValue) AsInt64() int64 {
|
||||
v, _ := util.ToInt64(t.v)
|
||||
return v
|
||||
}
|
||||
|
||||
func (t *TraceValue) AsFloat64() float64 {
|
||||
v, _ := util.ToFloat64(t.v)
|
||||
return v
|
||||
}
|
||||
|
||||
var globalTraceStarters []traceStarter
|
||||
|
||||
type Tracer struct {
|
||||
starters []traceStarter
|
||||
}
|
||||
|
||||
func (s *TraceSpan) SetName(name string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.name = name
|
||||
}
|
||||
|
||||
func (s *TraceSpan) SetStatus(code uint32, desc string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.statusCode, s.statusDesc = code, desc
|
||||
}
|
||||
|
||||
func (s *TraceSpan) AddEvent(name string, options ...EventOption) {
|
||||
cfg := eventConfigFromOptions(options...)
|
||||
for _, tsp := range s.internalSpans {
|
||||
tsp.addEvent(name, cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TraceSpan) RecordError(err error, options ...EventOption) {
|
||||
cfg := eventConfigFromOptions(options...)
|
||||
for _, tsp := range s.internalSpans {
|
||||
tsp.recordError(err, cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TraceSpan) SetAttributeString(key, value string) *TraceSpan {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
s.attributes = append(s.attributes, &TraceAttribute{Key: key, Value: TraceValue{v: value}})
|
||||
return s
|
||||
}
|
||||
|
||||
func (t *Tracer) Start(ctx context.Context, spanName string) (context.Context, *TraceSpan) {
|
||||
starters := t.starters
|
||||
if starters == nil {
|
||||
starters = globalTraceStarters
|
||||
}
|
||||
ts := &TraceSpan{name: spanName, startTime: time.Now()}
|
||||
parentSpan := GetContextSpan(ctx)
|
||||
if parentSpan != nil {
|
||||
parentSpan.mu.Lock()
|
||||
parentSpan.children = append(parentSpan.children, ts)
|
||||
parentSpan.mu.Unlock()
|
||||
ts.parent = parentSpan
|
||||
}
|
||||
|
||||
parentCtx := ctx
|
||||
for internalSpanIdx, tsp := range starters {
|
||||
var internalSpan traceSpanInternal
|
||||
if parentSpan != nil {
|
||||
parentCtx = parentSpan.internalContexts[internalSpanIdx]
|
||||
}
|
||||
ctx, internalSpan = tsp.start(parentCtx, ts, internalSpanIdx)
|
||||
ts.internalContexts = append(ts.internalContexts, ctx)
|
||||
ts.internalSpans = append(ts.internalSpans, internalSpan)
|
||||
}
|
||||
ctx = context.WithValue(ctx, contextKeySpan, ts)
|
||||
return ctx, ts
|
||||
}
|
||||
|
||||
type mutableContext interface {
|
||||
context.Context
|
||||
SetContextValue(key, value any)
|
||||
GetContextValue(key any) any
|
||||
}
|
||||
|
||||
// StartInContext starts a trace span in Gitea's mutable context (usually the web request context).
|
||||
// Due to the design limitation of Gitea's web framework, it can't use `context.WithValue` to bind a new span into a new context.
|
||||
// So here we use our "reqctx" framework to achieve the same result: web request context could always see the latest "span".
|
||||
func (t *Tracer) StartInContext(ctx mutableContext, spanName string) (*TraceSpan, func()) {
|
||||
curTraceSpan := GetContextSpan(ctx)
|
||||
_, newTraceSpan := GetTracer().Start(ctx, spanName)
|
||||
ctx.SetContextValue(contextKeySpan, newTraceSpan)
|
||||
return newTraceSpan, func() {
|
||||
newTraceSpan.End()
|
||||
ctx.SetContextValue(contextKeySpan, curTraceSpan)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TraceSpan) End() {
|
||||
s.mu.Lock()
|
||||
s.endTime = time.Now()
|
||||
s.mu.Unlock()
|
||||
|
||||
for _, tsp := range s.internalSpans {
|
||||
tsp.end()
|
||||
}
|
||||
}
|
||||
|
||||
func GetTracer() *Tracer {
|
||||
return &Tracer{}
|
||||
}
|
||||
|
||||
func GetContextSpan(ctx context.Context) *TraceSpan {
|
||||
ts, _ := ctx.Value(contextKeySpan).(*TraceSpan)
|
||||
return ts
|
||||
}
|
96
modules/gtprof/trace_builtin.go
Normal file
96
modules/gtprof/trace_builtin.go
Normal file
@ -0,0 +1,96 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package gtprof
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/tailmsg"
|
||||
)
|
||||
|
||||
type traceBuiltinStarter struct{}
|
||||
|
||||
type traceBuiltinSpan struct {
|
||||
ts *TraceSpan
|
||||
|
||||
internalSpanIdx int
|
||||
}
|
||||
|
||||
func (t *traceBuiltinSpan) addEvent(name string, cfg *EventConfig) {
|
||||
// No-op because builtin tracer doesn't need it.
|
||||
// In the future we might use it to mark the time point between backend logic and network response.
|
||||
}
|
||||
|
||||
func (t *traceBuiltinSpan) recordError(err error, cfg *EventConfig) {
|
||||
// No-op because builtin tracer doesn't need it.
|
||||
// Actually Gitea doesn't handle err this way in most cases
|
||||
}
|
||||
|
||||
func (t *traceBuiltinSpan) toString(out *strings.Builder, indent int) {
|
||||
t.ts.mu.RLock()
|
||||
defer t.ts.mu.RUnlock()
|
||||
|
||||
out.WriteString(strings.Repeat(" ", indent))
|
||||
out.WriteString(t.ts.name)
|
||||
if t.ts.endTime.IsZero() {
|
||||
out.WriteString(" duration: (not ended)")
|
||||
} else {
|
||||
out.WriteString(fmt.Sprintf(" duration=%.4fs", t.ts.endTime.Sub(t.ts.startTime).Seconds()))
|
||||
}
|
||||
for _, a := range t.ts.attributes {
|
||||
out.WriteString(" ")
|
||||
out.WriteString(a.Key)
|
||||
out.WriteString("=")
|
||||
value := a.Value.AsString()
|
||||
if strings.ContainsAny(value, " \t\r\n") {
|
||||
quoted := false
|
||||
for _, c := range "\"'`" {
|
||||
if quoted = !strings.Contains(value, string(c)); quoted {
|
||||
value = string(c) + value + string(c)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !quoted {
|
||||
value = fmt.Sprintf("%q", value)
|
||||
}
|
||||
}
|
||||
out.WriteString(value)
|
||||
}
|
||||
out.WriteString("\n")
|
||||
for _, c := range t.ts.children {
|
||||
span := c.internalSpans[t.internalSpanIdx].(*traceBuiltinSpan)
|
||||
span.toString(out, indent+2)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *traceBuiltinSpan) end() {
|
||||
if t.ts.parent == nil {
|
||||
// TODO: debug purpose only
|
||||
// TODO: it should distinguish between http response network lag and actual processing time
|
||||
threshold := time.Duration(traceBuiltinThreshold.Load())
|
||||
if threshold != 0 && t.ts.endTime.Sub(t.ts.startTime) > threshold {
|
||||
sb := &strings.Builder{}
|
||||
t.toString(sb, 0)
|
||||
tailmsg.GetManager().GetTraceRecorder().Record(sb.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *traceBuiltinStarter) start(ctx context.Context, traceSpan *TraceSpan, internalSpanIdx int) (context.Context, traceSpanInternal) {
|
||||
return ctx, &traceBuiltinSpan{ts: traceSpan, internalSpanIdx: internalSpanIdx}
|
||||
}
|
||||
|
||||
func init() {
|
||||
globalTraceStarters = append(globalTraceStarters, &traceBuiltinStarter{})
|
||||
}
|
||||
|
||||
var traceBuiltinThreshold atomic.Int64
|
||||
|
||||
func EnableBuiltinTracer(threshold time.Duration) {
|
||||
traceBuiltinThreshold.Store(int64(threshold))
|
||||
}
|
19
modules/gtprof/trace_const.go
Normal file
19
modules/gtprof/trace_const.go
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package gtprof
|
||||
|
||||
// Some interesting names could be found in https://github.com/open-telemetry/opentelemetry-go/tree/main/semconv
|
||||
|
||||
const (
|
||||
TraceSpanHTTP = "http"
|
||||
TraceSpanGitRun = "git-run"
|
||||
TraceSpanDatabase = "database"
|
||||
)
|
||||
|
||||
const (
|
||||
TraceAttrFuncCaller = "func.caller"
|
||||
TraceAttrDbSQL = "db.sql"
|
||||
TraceAttrGitCommand = "git.command"
|
||||
TraceAttrHTTPRoute = "http.route"
|
||||
)
|
93
modules/gtprof/trace_test.go
Normal file
93
modules/gtprof/trace_test.go
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package gtprof
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// "vendor span" is a simple demo for a span from a vendor library
|
||||
|
||||
var vendorContextKey any = "vendorContextKey"
|
||||
|
||||
type vendorSpan struct {
|
||||
name string
|
||||
children []*vendorSpan
|
||||
}
|
||||
|
||||
func vendorTraceStart(ctx context.Context, name string) (context.Context, *vendorSpan) {
|
||||
span := &vendorSpan{name: name}
|
||||
parentSpan, ok := ctx.Value(vendorContextKey).(*vendorSpan)
|
||||
if ok {
|
||||
parentSpan.children = append(parentSpan.children, span)
|
||||
}
|
||||
ctx = context.WithValue(ctx, vendorContextKey, span)
|
||||
return ctx, span
|
||||
}
|
||||
|
||||
// below "testTrace*" integrate the vendor span into our trace system
|
||||
|
||||
type testTraceSpan struct {
|
||||
vendorSpan *vendorSpan
|
||||
}
|
||||
|
||||
func (t *testTraceSpan) addEvent(name string, cfg *EventConfig) {}
|
||||
|
||||
func (t *testTraceSpan) recordError(err error, cfg *EventConfig) {}
|
||||
|
||||
func (t *testTraceSpan) end() {}
|
||||
|
||||
type testTraceStarter struct{}
|
||||
|
||||
func (t *testTraceStarter) start(ctx context.Context, traceSpan *TraceSpan, internalSpanIdx int) (context.Context, traceSpanInternal) {
|
||||
ctx, span := vendorTraceStart(ctx, traceSpan.name)
|
||||
return ctx, &testTraceSpan{span}
|
||||
}
|
||||
|
||||
func TestTraceStarter(t *testing.T) {
|
||||
globalTraceStarters = []traceStarter{&testTraceStarter{}}
|
||||
|
||||
ctx := context.Background()
|
||||
ctx, span := GetTracer().Start(ctx, "root")
|
||||
defer span.End()
|
||||
|
||||
func(ctx context.Context) {
|
||||
ctx, span := GetTracer().Start(ctx, "span1")
|
||||
defer span.End()
|
||||
func(ctx context.Context) {
|
||||
_, span := GetTracer().Start(ctx, "spanA")
|
||||
defer span.End()
|
||||
}(ctx)
|
||||
func(ctx context.Context) {
|
||||
_, span := GetTracer().Start(ctx, "spanB")
|
||||
defer span.End()
|
||||
}(ctx)
|
||||
}(ctx)
|
||||
|
||||
func(ctx context.Context) {
|
||||
_, span := GetTracer().Start(ctx, "span2")
|
||||
defer span.End()
|
||||
}(ctx)
|
||||
|
||||
var spanFullNames []string
|
||||
var collectSpanNames func(parentFullName string, s *vendorSpan)
|
||||
collectSpanNames = func(parentFullName string, s *vendorSpan) {
|
||||
fullName := parentFullName + "/" + s.name
|
||||
spanFullNames = append(spanFullNames, fullName)
|
||||
for _, c := range s.children {
|
||||
collectSpanNames(fullName, c)
|
||||
}
|
||||
}
|
||||
collectSpanNames("", span.internalSpans[0].(*testTraceSpan).vendorSpan)
|
||||
assert.Equal(t, []string{
|
||||
"/root",
|
||||
"/root/span1",
|
||||
"/root/span1/spanA",
|
||||
"/root/span1/spanB",
|
||||
"/root/span2",
|
||||
}, spanFullNames)
|
||||
}
|
@ -30,7 +30,7 @@ func ParseSizeAndClass(defaultSize int, defaultClass string, others ...any) (int
|
||||
return size, class
|
||||
}
|
||||
|
||||
func HTMLFormat(s string, rawArgs ...any) template.HTML {
|
||||
func HTMLFormat(s template.HTML, rawArgs ...any) template.HTML {
|
||||
args := slices.Clone(rawArgs)
|
||||
for i, v := range args {
|
||||
switch v := v.(type) {
|
||||
@ -44,5 +44,5 @@ func HTMLFormat(s string, rawArgs ...any) template.HTML {
|
||||
args[i] = template.HTMLEscapeString(fmt.Sprint(v))
|
||||
}
|
||||
}
|
||||
return template.HTML(fmt.Sprintf(s, args...))
|
||||
return template.HTML(fmt.Sprintf(string(s), args...))
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestServeContentByReader(t *testing.T) {
|
||||
@ -71,9 +72,7 @@ func TestServeContentByReadSeeker(t *testing.T) {
|
||||
}
|
||||
|
||||
seekReader, err := os.OpenFile(tmpFile, os.O_RDONLY, 0o644)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer seekReader.Close()
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
@ -11,6 +11,8 @@ import (
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/indexer/issues/internal/tests"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestElasticsearchIndexer(t *testing.T) {
|
||||
@ -26,20 +28,10 @@ func TestElasticsearchIndexer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
ok := false
|
||||
for i := 0; i < 60; i++ {
|
||||
require.Eventually(t, func() bool {
|
||||
resp, err := http.Get(url)
|
||||
if err == nil && resp.StatusCode == http.StatusOK {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
t.Logf("Waiting for elasticsearch to be up: %v", err)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("Failed to wait for elasticsearch to be up")
|
||||
return
|
||||
}
|
||||
return err == nil && resp.StatusCode == http.StatusOK
|
||||
}, time.Minute, time.Second, "Expected elasticsearch to be up")
|
||||
|
||||
indexer := NewIndexer(url, fmt.Sprintf("test_elasticsearch_indexer_%d", time.Now().Unix()))
|
||||
defer indexer.Close()
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
_ "code.gitea.io/gitea/models/activities"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@ -26,7 +27,7 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
func TestDBSearchIssues(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
require.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
setting.Indexer.IssueType = "db"
|
||||
InitIssueIndexer(true)
|
||||
@ -83,9 +84,7 @@ func searchIssueWithKeyword(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedIDs, issueIDs)
|
||||
}
|
||||
}
|
||||
@ -120,9 +119,7 @@ func searchIssueByIndex(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedIDs, issueIDs)
|
||||
}
|
||||
}
|
||||
@ -166,9 +163,7 @@ func searchIssueInRepo(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedIDs, issueIDs)
|
||||
}
|
||||
}
|
||||
@ -238,9 +233,7 @@ func searchIssueByID(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedIDs, issueIDs)
|
||||
}
|
||||
}
|
||||
@ -265,9 +258,7 @@ func searchIssueIsPull(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedIDs, issueIDs)
|
||||
}
|
||||
}
|
||||
@ -292,9 +283,7 @@ func searchIssueIsClosed(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedIDs, issueIDs)
|
||||
}
|
||||
}
|
||||
@ -319,9 +308,7 @@ func searchIssueIsArchived(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedIDs, issueIDs)
|
||||
}
|
||||
}
|
||||
@ -346,9 +333,7 @@ func searchIssueByMilestoneID(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedIDs, issueIDs)
|
||||
}
|
||||
}
|
||||
@ -379,9 +364,7 @@ func searchIssueByLabelID(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedIDs, issueIDs)
|
||||
}
|
||||
}
|
||||
@ -400,9 +383,7 @@ func searchIssueByTime(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedIDs, issueIDs)
|
||||
}
|
||||
}
|
||||
@ -421,9 +402,7 @@ func searchIssueWithOrder(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedIDs, issueIDs)
|
||||
}
|
||||
}
|
||||
@ -454,9 +433,7 @@ func searchIssueInProject(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
issueIDs, _, err := SearchIssues(context.TODO(), &test.opts)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedIDs, issueIDs)
|
||||
}
|
||||
}
|
||||
@ -479,9 +456,7 @@ func searchIssueWithPaginator(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
issueIDs, total, err := SearchIssues(context.TODO(), &test.opts)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expectedIDs, issueIDs)
|
||||
assert.Equal(t, test.expectedTotal, total)
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
|
||||
"github.com/meilisearch/meilisearch-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMeilisearchIndexer(t *testing.T) {
|
||||
@ -32,20 +33,10 @@ func TestMeilisearchIndexer(t *testing.T) {
|
||||
key = os.Getenv("TEST_MEILISEARCH_KEY")
|
||||
}
|
||||
|
||||
ok := false
|
||||
for i := 0; i < 60; i++ {
|
||||
require.Eventually(t, func() bool {
|
||||
resp, err := http.Get(url)
|
||||
if err == nil && resp.StatusCode == http.StatusOK {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
t.Logf("Waiting for meilisearch to be up: %v", err)
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("Failed to wait for meilisearch to be up")
|
||||
return
|
||||
}
|
||||
return err == nil && resp.StatusCode == http.StatusOK
|
||||
}, time.Minute, time.Second, "Expected meilisearch to be up")
|
||||
|
||||
indexer := NewIndexer(url, key, fmt.Sprintf("test_meilisearch_indexer_%d", time.Now().Unix()))
|
||||
defer indexer.Close()
|
||||
|
@ -957,9 +957,8 @@ func Test_minQuotes(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := minQuotes(tt.args.value); got != tt.want {
|
||||
t.Errorf("minQuotes() = %v, want %v", got, tt.want)
|
||||
}
|
||||
got := minQuotes(tt.args.value)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer)
|
||||
setting.AppSubURL,
|
||||
url.PathEscape(ctx.RenderOptions.Metas["user"]),
|
||||
url.PathEscape(ctx.RenderOptions.Metas["repo"]),
|
||||
ctx.RenderOptions.Metas["BranchNameSubURL"],
|
||||
ctx.RenderOptions.Metas["RefTypeNameSubURL"],
|
||||
url.PathEscape(ctx.RenderOptions.RelativePath),
|
||||
)
|
||||
return ctx.RenderInternal.FormatWithSafeAttrs(output, `<div class="%s" %s="%s"></div>`, playerClassName, playerSrcAttr, rawURL)
|
||||
|
@ -76,7 +76,7 @@ func (r *RenderInternal) ProtectSafeAttrs(content template.HTML) template.HTML {
|
||||
return template.HTML(reAttrClass().ReplaceAllString(string(content), `$1 data-attr-class="`+r.secureIDPrefix+`$2"$3`))
|
||||
}
|
||||
|
||||
func (r *RenderInternal) FormatWithSafeAttrs(w io.Writer, fmt string, a ...any) error {
|
||||
func (r *RenderInternal) FormatWithSafeAttrs(w io.Writer, fmt template.HTML, a ...any) error {
|
||||
_, err := w.Write([]byte(r.ProtectSafeAttrs(htmlutil.HTMLFormat(fmt, a...))))
|
||||
return err
|
||||
}
|
||||
|
@ -4,6 +4,8 @@
|
||||
package math
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
|
||||
"code.gitea.io/gitea/modules/markup/internal"
|
||||
giteaUtil "code.gitea.io/gitea/modules/util"
|
||||
|
||||
@ -50,7 +52,7 @@ func (r *BlockRenderer) renderBlock(w util.BufWriter, source []byte, node gast.N
|
||||
n := node.(*Block)
|
||||
if entering {
|
||||
code := giteaUtil.Iif(n.Inline, "", `<pre class="code-block is-loading">`) + `<code class="language-math display">`
|
||||
_ = r.renderInternal.FormatWithSafeAttrs(w, code)
|
||||
_ = r.renderInternal.FormatWithSafeAttrs(w, template.HTML(code))
|
||||
r.writeLines(w, source, n)
|
||||
} else {
|
||||
_, _ = w.WriteString(`</code>` + giteaUtil.Iif(n.Inline, "", `</pre>`) + "\n")
|
||||
|
@ -7,6 +7,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@ -140,23 +142,13 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) {
|
||||
Icon: "table",
|
||||
Lang: "",
|
||||
}
|
||||
if err := yaml.Unmarshal([]byte(strings.ReplaceAll(tt.args, "\t", " ")), got); err != nil {
|
||||
t.Errorf("RenderConfig.UnmarshalYAML() error = %v\n%q", err, tt.args)
|
||||
return
|
||||
}
|
||||
err := yaml.Unmarshal([]byte(strings.ReplaceAll(tt.args, "\t", " ")), got)
|
||||
require.NoError(t, err)
|
||||
|
||||
if got.Meta != tt.expected.Meta {
|
||||
t.Errorf("Meta Expected %s Got %s", tt.expected.Meta, got.Meta)
|
||||
}
|
||||
if got.Icon != tt.expected.Icon {
|
||||
t.Errorf("Icon Expected %s Got %s", tt.expected.Icon, got.Icon)
|
||||
}
|
||||
if got.Lang != tt.expected.Lang {
|
||||
t.Errorf("Lang Expected %s Got %s", tt.expected.Lang, got.Lang)
|
||||
}
|
||||
if got.TOC != tt.expected.TOC {
|
||||
t.Errorf("TOC Expected %q Got %q", tt.expected.TOC, got.TOC)
|
||||
}
|
||||
assert.Equal(t, tt.expected.Meta, got.Meta)
|
||||
assert.Equal(t, tt.expected.Icon, got.Icon)
|
||||
assert.Equal(t, tt.expected.Lang, got.Lang)
|
||||
assert.Equal(t, tt.expected.TOC, got.TOC)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ func (r *orgWriter) resolveLink(kind, link string) string {
|
||||
func (r *orgWriter) WriteRegularLink(l org.RegularLink) {
|
||||
link := r.resolveLink(l.Kind(), l.URL)
|
||||
|
||||
printHTML := func(html string, a ...any) {
|
||||
printHTML := func(html template.HTML, a ...any) {
|
||||
_, _ = fmt.Fprint(r, htmlutil.HTMLFormat(html, a...))
|
||||
}
|
||||
// Inspired by https://github.com/niklasfasching/go-org/blob/6eb20dbda93cb88c3503f7508dc78cbbc639378f/org/html_writer.go#L406-L427
|
||||
|
@ -103,8 +103,8 @@ func HelloWorld() {
|
||||
}
|
||||
#+end_src
|
||||
`, `<div class="src src-go">
|
||||
<pre><code class="chroma language-go"><span class="c1">// HelloWorld prints "Hello World"
|
||||
</span><span class="c1"></span><span class="kd">func</span> <span class="nf">HelloWorld</span><span class="p">()</span> <span class="p">{</span>
|
||||
<pre><code class="chroma language-go"><span class="c1">// HelloWorld prints "Hello World"</span>
|
||||
<span class="kd">func</span> <span class="nf">HelloWorld</span><span class="p">()</span> <span class="p">{</span>
|
||||
<span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">"Hello World"</span><span class="p">)</span>
|
||||
<span class="p">}</span></code></pre>
|
||||
</div>`)
|
||||
|
@ -44,7 +44,7 @@ type RenderOptions struct {
|
||||
MarkupType string
|
||||
|
||||
// user&repo, format&style®exp (for external issue pattern), teams&org (for mention)
|
||||
// BranchNameSubURL (for iframe&asciicast)
|
||||
// RefTypeNameSubURL (for iframe&asciicast)
|
||||
// markupAllowShortIssuePattern
|
||||
// markdownLineBreakStyle (comment, document)
|
||||
Metas map[string]string
|
||||
@ -170,7 +170,7 @@ sandbox="allow-scripts"
|
||||
setting.AppSubURL,
|
||||
url.PathEscape(ctx.RenderOptions.Metas["user"]),
|
||||
url.PathEscape(ctx.RenderOptions.Metas["repo"]),
|
||||
ctx.RenderOptions.Metas["BranchNameSubURL"],
|
||||
ctx.RenderOptions.Metas["RefTypeNameSubURL"],
|
||||
url.PathEscape(ctx.RenderOptions.RelativePath),
|
||||
))
|
||||
return err
|
||||
|
@ -5,6 +5,9 @@ package nosql
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestToRedisURI(t *testing.T) {
|
||||
@ -26,9 +29,9 @@ func TestToRedisURI(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ToRedisURI(tt.connection); got == nil || got.String() != tt.want {
|
||||
t.Errorf(`ToRedisURI(%q) = %s, want %s`, tt.connection, got.String(), tt.want)
|
||||
}
|
||||
got := ToRedisURI(tt.connection)
|
||||
require.NotNil(t, got)
|
||||
assert.Equal(t, tt.want, got.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
|
||||
"gitea.com/lunny/levelqueue"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
)
|
||||
|
||||
@ -29,9 +30,7 @@ func TestCorruptedLevelQueue(t *testing.T) {
|
||||
// sometimes the levelqueue could be in a corrupted state, this test is to make sure it can recover from it
|
||||
dbDir := t.TempDir() + "/levelqueue-test"
|
||||
db, err := leveldb.OpenFile(dbDir, nil)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
assert.NoError(t, db.Put([]byte("other-key"), []byte("other-value"), nil))
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func waitRedisReady(conn string, dur time.Duration) (ready bool) {
|
||||
@ -61,9 +62,7 @@ func TestBaseRedis(t *testing.T) {
|
||||
return
|
||||
}
|
||||
assert.NoError(t, redisServer.Start())
|
||||
if !assert.True(t, waitRedisReady("redis://127.0.0.1:6379/0", 5*time.Second), "start redis-server") {
|
||||
return
|
||||
}
|
||||
require.True(t, waitRedisReady("redis://127.0.0.1:6379/0", 5*time.Second), "start redis-server")
|
||||
}
|
||||
|
||||
testQueueBasic(t, newBaseRedisSimple, toBaseConfig("baseRedis", setting.QueueSettings{Length: 10}), false)
|
||||
|
@ -46,6 +46,7 @@ var Service = struct {
|
||||
RequireSignInView bool
|
||||
EnableNotifyMail bool
|
||||
EnableBasicAuth bool
|
||||
EnablePasskeyAuth bool
|
||||
EnableReverseProxyAuth bool
|
||||
EnableReverseProxyAuthAPI bool
|
||||
EnableReverseProxyAutoRegister bool
|
||||
@ -161,6 +162,7 @@ func loadServiceFrom(rootCfg ConfigProvider) {
|
||||
Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool()
|
||||
Service.EnableBasicAuth = sec.Key("ENABLE_BASIC_AUTHENTICATION").MustBool(true)
|
||||
Service.EnablePasswordSignInForm = sec.Key("ENABLE_PASSWORD_SIGNIN_FORM").MustBool(true)
|
||||
Service.EnablePasskeyAuth = sec.Key("ENABLE_PASSKEY_AUTHENTICATION").MustBool(true)
|
||||
Service.EnableReverseProxyAuth = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool()
|
||||
Service.EnableReverseProxyAuthAPI = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION_API").MustBool()
|
||||
Service.EnableReverseProxyAutoRegister = sec.Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").MustBool()
|
||||
|
@ -5,6 +5,8 @@ package structs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNoBetterThan(t *testing.T) {
|
||||
@ -166,9 +168,7 @@ func TestNoBetterThan(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.args.css.NoBetterThan(tt.args.css2)
|
||||
if result != tt.want {
|
||||
t.Errorf("NoBetterThan() = %v, want %v", result, tt.want)
|
||||
}
|
||||
assert.Equal(t, tt.want, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user