diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a656fa9ac97f..4e6f0a34fa79 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,7 +34,7 @@ - [Backport PRs](#backport-prs) - [Documentation](#documentation) - [API v1](#api-v1) - - [GitHub API compatability](#github-api-compatability) + - [GitHub API compatibility](#github-api-compatibility) - [Adding/Maintaining API routes](#addingmaintaining-api-routes) - [When to use what HTTP method](#when-to-use-what-http-method) - [Requirements for API routes](#requirements-for-api-routes) @@ -339,7 +339,7 @@ If you add a new feature or change an existing aspect of Gitea, the documentatio The API is documented by [swagger](http://try.gitea.io/api/swagger) and is based on [the GitHub API](https://docs.github.com/en/rest). -### GitHub API compatability +### GitHub API compatibility Gitea's API should use the same endpoints and fields as the GitHub API as far as possible, unless there are good reasons to deviate. \ If Gitea provides functionality that GitHub does not, a new endpoint can be created. \ diff --git a/Makefile b/Makefile index e75a2bcb944a..167f56c6b926 100644 --- a/Makefile +++ b/Makefile @@ -29,12 +29,12 @@ AIR_PACKAGE ?= github.com/cosmtrek/air@v1.44.0 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.5.0 GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.53.3 -GXZ_PAGAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 +GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 MISSPELL_PACKAGE ?= github.com/client9/misspell/cmd/misspell@v0.3.4 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.5 XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 -GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v0.2.0 +GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.0.0 ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.6.25 DOCKER_IMAGE ?= gitea/gitea @@ -864,7 +864,7 @@ release-check: | $(DIST_DIRS) .PHONY: release-compress release-compress: | $(DIST_DIRS) - cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && $(GO) run $(GXZ_PAGAGE) -k -9 $${file}; done; + cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && $(GO) run $(GXZ_PACKAGE) -k -9 $${file}; done; .PHONY: release-sources release-sources: | $(DIST_DIRS) @@ -903,7 +903,7 @@ deps-tools: $(GO) install $(EDITORCONFIG_CHECKER_PACKAGE) $(GO) install $(GOFUMPT_PACKAGE) $(GO) install $(GOLANGCI_LINT_PACKAGE) - $(GO) install $(GXZ_PAGAGE) + $(GO) install $(GXZ_PACKAGE) $(GO) install $(MISSPELL_PACKAGE) $(GO) install $(SWAGGER_PACKAGE) $(GO) install $(XGO_PACKAGE) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index db148c52ade4..e5f72d436c4b 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1420,8 +1420,8 @@ LEVEL = Info ;; Provides the suffix of the default redis/disk unique queue set name - specific queues can be overridden within in their [queue.name] sections. ;SET_NAME = "_unique" ;; -;; Dynamically scale the worker pool to at this many workers -;MAX_WORKERS = 10 +;; Maximum number of worker go-routines for the queue. Default value is "CpuNum/2" clipped to between 1 and 10. +;MAX_WORKERS = ; (dynamic) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/docs/content/administration/config-cheat-sheet.en-us.md b/docs/content/administration/config-cheat-sheet.en-us.md index 6d5789ff0dc5..f30e0e246a83 100644 --- a/docs/content/administration/config-cheat-sheet.en-us.md +++ b/docs/content/administration/config-cheat-sheet.en-us.md @@ -488,7 +488,7 @@ Configuration at `[queue]` will set defaults for queues with overrides for indiv - `CONN_STR`: **redis://127.0.0.1:6379/0**: Connection string for the redis queue type. For `redis-cluster` use `redis+cluster://127.0.0.1:6379/0`. Options can be set using query params. Similarly, LevelDB options can also be set using: **leveldb://relative/path?option=value** or **leveldb:///absolute/path?option=value**, and will override `DATADIR` - `QUEUE_NAME`: **_queue**: The suffix for default redis and disk queue name. Individual queues will default to **`name`**`QUEUE_NAME` but can be overridden in the specific `queue.name` section. - `SET_NAME`: **_unique**: The suffix that will be added to the default redis and disk queue `set` name for unique queues. Individual queues will default to **`name`**`QUEUE_NAME`_`SET_NAME`_ but can be overridden in the specific `queue.name` section. -- `MAX_WORKERS`: **10**: Maximum number of worker go-routines for the queue. +- `MAX_WORKERS`: **(dynamic)**: Maximum number of worker go-routines for the queue. Default value is "CpuNum/2" clipped to between 1 and 10. Gitea creates the following non-unique queues: diff --git a/docs/content/administration/email-setup.en-us.md b/docs/content/administration/email-setup.en-us.md index 2f92e20410de..10058d82849b 100644 --- a/docs/content/administration/email-setup.en-us.md +++ b/docs/content/administration/email-setup.en-us.md @@ -31,7 +31,7 @@ Note: For Internet-facing sites consult documentation of your MTA for instructio [mailer] ENABLED = true FROM = gitea@mydomain.com -MAILER_TYPE = sendmail +PROTOCOL = sendmail SENDMAIL_PATH = /usr/sbin/sendmail SENDMAIL_ARGS = "--" ; most "sendmail" programs take options, "--" will prevent an email address being interpreted as an option. ``` @@ -44,10 +44,9 @@ Directly use SMTP server as relay. This option is useful if you don't want to se [mailer] ENABLED = true FROM = gitea@mydomain.com -MAILER_TYPE = smtp +PROTOCOL = smtps SMTP_ADDR = mail.mydomain.com SMTP_PORT = 587 -IS_TLS_ENABLED = true USER = gitea@mydomain.com PASSWD = `password` ``` @@ -80,7 +79,7 @@ SMTP_PORT = 465 FROM = example.user@gmail.com USER = example.user PASSWD = `***` -MAILER_TYPE = smtp +PROTOCOL = smtp IS_TLS_ENABLED = true ``` diff --git a/docs/content/administration/email-setup.zh-cn.md b/docs/content/administration/email-setup.zh-cn.md index e526a9d4337c..0a7ac3378f98 100644 --- a/docs/content/administration/email-setup.zh-cn.md +++ b/docs/content/administration/email-setup.zh-cn.md @@ -31,7 +31,7 @@ Gitea 具有邮件功能,用于发送事务性邮件(例如注册确认邮 [mailer] ENABLED = true FROM = gitea@mydomain.com -MAILER_TYPE = sendmail +PROTOCOL = sendmail SENDMAIL_PATH = /usr/sbin/sendmail SENDMAIL_ARGS = "--" ; 大多数 "sendmail" 程序都接受选项,使用 "--" 将防止电子邮件地址被解释为选项。 ``` @@ -44,10 +44,9 @@ SENDMAIL_ARGS = "--" ; 大多数 "sendmail" 程序都接受选项,使用 "--" [mailer] ENABLED = true FROM = gitea@mydomain.com -MAILER_TYPE = smtp +PROTOCOL = smtps SMTP_ADDR = mail.mydomain.com SMTP_PORT = 587 -IS_TLS_ENABLED = true USER = gitea@mydomain.com PASSWD = `password` ``` @@ -80,8 +79,7 @@ SMTP_PORT = 465 FROM = example.user@gmail.com USER = example.user PASSWD = `***` -MAILER_TYPE = smtp -IS_TLS_ENABLED = true +PROTOCOL = smtps ``` 请注意,您需要创建并使用一个 [应用密码](https://support.google.com/accounts/answer/185833?hl=en) 并在您的 Google 帐户上启用 2FA。您将无法直接使用您的 Google 帐户密码。 diff --git a/docs/content/installation/from-source.zh-cn.md b/docs/content/installation/from-source.zh-cn.md index c1f00904d4fe..260ccbee3c62 100644 --- a/docs/content/installation/from-source.zh-cn.md +++ b/docs/content/installation/from-source.zh-cn.md @@ -17,94 +17,158 @@ menu: # 使用源代码安装 -首先你需要安装Golang,关于Golang的安装,参见[官方文档](https://golang.google.cn/doc/install)。 +你需要 [安装Go](https://golang.google.cn/doc/install) 并正确设置Go环境。特别的,建议设置`$GOPATH`环境变量,并将 Go 的二进制目录或目录`${GOPATH//://bin:}/bin`添加到`$PATH`中。请参阅 Go 百科上关于 [GOPATH](https://github.com/golang/go/wiki/GOPATH) 的词条。 -其次你需要[安装Node.js](https://nodejs.org/zh-cn/download/),Node.js 和 npm 将用于构建 Gitea 前端。 +接下来,[安装 Node.js 和 npm](https://nodejs.org/zh-cn/download/), 这是构建 JavaScript 和 CSS 文件所需的。最低支持的 Node.js 版本是 @minNodeVersion@,建议使用最新的 LTS 版本。 + +**注意**:当执行需要外部工具的 make 任务(如`make misspell-check`)时,Gitea 将根据需要自动下载和构建这些工具。为了能够实现这个目的,你必须将`"$GOPATH/bin"`目录添加到可执行路径中。如果没有将 Go 的二进制目录添加到可执行路径中,你需要自行解决产生的问题。 + +**注意2**:需要 Go 版本 @minGoVersion@ 或更高版本。不过,建议获取与我们的持续集成(continuous integration, CI)相同的版本,请参阅在 [Hacking on Gitea](development/hacking-on-gitea.md) 中给出的建议。 ## 下载 -你需要获取Gitea的源码,最方便的方式是使用 `git` 命令。执行以下命令: +首先,我们需要获取源码。由于引入了 Go 模块,最简单的方法是直接使用 Git,因为我们不再需要在 GOPATH 内构建 Gitea。 -``` +```bash git clone https://github.com/go-gitea/gitea -cd gitea ``` -然后你可以选择编译和安装的版本,当前你有多个选择。如果你想编译 `main` 版本,你可以直接跳到 [编译](#编译) 部分,这是我们的开发分支,虽然也很稳定但不建议您在正式产品中使用。 +(之前的版本中建议使用 `go get`,但现在不再需要。) + +你可以选择编译和安装的版本,当前有多个选择。`main` 分支代表当前的开发版本。如果你想编译 `main` 版本,你可以直接跳到 [构建](#构建) 部分。 -如果你想编译最新稳定分支,你可以执行以下命令签出源码: +如果你想编译带有标签的发行版本,可以使用以下命令签出: ```bash git branch -a git checkout v@version@ ``` -最后,你也可以直接使用标签版本如 `v@version@`。你可以执行以下命令列出可用的版本并选择某个版本签出: +要验证一个拉取请求(Pull Request, PR),要先启用新的分支(其中 `xyz` 是 PR 的 ID;例如,对于 [#2663](https://github.com/go-gitea/gitea/pull/2663),ID是 `2663 `): + +```bash +git fetch origin pull/xyz/head:pr-xyz +``` + +要以指定发行版本(如 v@version@ )的源代码来构建 Gitea,可执行以下命令列出可用的版本并选择某个版本签出。 +使用以下命令列出可用的版本: ```bash git tag -l -git checkout v@version@ +git checkout v@version@ # or git checkout pr-xyz ``` -## 编译 +## 构建 -要从源代码进行编译,以下依赖程序必须事先安装好: +要从源代码进行构建,系统必须预先安装以下程序: -- `go` @minGoVersion@ 或以上版本, 详见[这里](https://golang.google.cn/doc/install) -- `node` @minNodeVersion@ 或以上版本,并且安装 `npm`, 详见[这里](https://nodejs.org/zh-cn/download/) -- `make`, 详见[这里](/zh-cn/hacking-on-gitea/) +- `go` @minGoVersion@ 或更高版本,请参阅 [这里](https://golang.org/dl/) +- `node` @minNodeVersion@ 或更高版本,并且安装 `npm`, 请参阅 [这里](https://nodejs.org/zh-cn/download/) +- `make`, 请参阅 [这里](/zh-cn/hacking-on-gitea/) -各种可用的 [make 任务](https://github.com/go-gitea/gitea/blob/main/Makefile) -可以用来使编译过程更方便。 +为了尽可能简化编译过程,提供了各种 [make任务](https://github.com/go-gitea/gitea/blob/main/Makefile)。 -按照您的编译需求,以下 tags 可以使用: +根据你的构建需求,以下 tags 可以使用: -- `bindata`: 这个编译选项将会把运行Gitea所需的所有外部资源都打包到可执行文件中,这样部署将非常简单因为除了可执行程序将不再需要任何其他文件。 -- `sqlite sqlite_unlock_notify`: 这个编译选项将启用SQLite3数据库的支持,建议只在少数人使用时使用这个模式。 -- `pam`: 这个编译选项将会启用 PAM (Linux Pluggable Authentication Modules) 认证,如果你使用这一认证模式的话需要开启这个选项。 +- `bindata`: 构建一个单一的整体二进制文件,包含所有资源。适用于构建生产环境版本。 +- `sqlite sqlite_unlock_notify`: 启用对 [SQLite3](https://sqlite.org/) 数据库的支持。仅建议在少数人使用时使用这个模式。 +- `pam`: 启用对 PAM( Linux 可插拔认证模块)的支持。可用于对本地用户进行身份验证或扩展身份验证到 PAM 可用的方法。 +- `gogit`:(实验性功能)使用 go-git 变体的 Git 命令。 -使用 bindata 可以打包资源文件到二进制可以使开发和测试更容易,你可以根据自己的需求决定是否打包资源文件。 -要包含资源文件,请使用 `bindata` tag: +将所有资源(JS/CSS/模板等)打包到二进制文件中。在生产环境部署时,使用`bindata`构建标签是必需的。在开发/测试 Gitea 或能够明确分离资源时,可以不用`bindata`。 + +要包含所有资源,请使用 `bindata` 标签: ```bash TAGS="bindata" make build ``` -默认的发布版本中的编译选项是: `TAGS="bindata sqlite sqlite_unlock_notify"`。以下为推荐的编译方式: +在我们的持续集成系统的默认发行版中,构建标签为:`TAGS="bindata sqlite sqlite_unlock_notify"`。因此,从源码构建的最简单、推荐方式是: ```bash TAGS="bindata sqlite sqlite_unlock_notify" make build ``` +`build`目标分为两个子目标: + +- `make backend` 需要 [Go @minGoVersion@](https://golang.google.cn/doc/install) 或更高版本。 +- `make frontend` 需要 [Node.js @minNodeVersion@](https://nodejs.org/zh-cn/download/) 或更高版本。 + +如果存在预构建的前端文件,可以仅构建后端: + +```bash +TAGS="bindata" make backend +``` + +在开发构建中,默认启用 Webpack 源映射,在生产构建中禁用。可以通过设置`ENABLE_SOURCEMAP=true`环境变量来启用它们。 + ## 测试 -在执行了以上步骤之后,你将会获得 `gitea` 的二进制文件,在你复制到部署的机器之前可以先测试一下。在命令行执行完后,你可以 `Ctrl + C` 关掉程序。 +按照上述步骤完成后,工作目录中将会有一个`gitea`二进制文件。可以从该目录进行测试,或将其移动到带有测试数据的目录中。当手动从命令行启动 Gitea 时,可以通过按下`Ctrl + C`来停止程序。 ```bash ./gitea web ``` +## 更改默认路径 + +Gitea 将从`CustomPath`中查找许多信息。默认的,这会在运行 Gitea 时当前工作目录下的`custom/`目录中(译者案:即`$PATH_TO_YOUR_GITEA$/custom/`)。它还将在`$(CustomPath)/conf/app.ini`中查找其配置文件`CustomConf`,并将当前工作目录用作一些可配置值的相对基本路径`AppWorkPath`。最后,静态文件将从默认为 `AppWorkPath`的`StaticRootPath`提供。 + +尽管在开发时这些值很有用,但可能与下游用户的偏好冲突。 + +一种选择是使用脚本文件来隐藏`gitea`二进制文件,并在运行Gitea之前创建适当的环境。然而,在构建时,可以使用`make`的`LDFLAGS`环境变量来更改这些默认值。适当的设置如下: + +- 要设置`CustomPath`,请使用`LDFLAGS="-X \"code.gitea.io/gitea/modules/setting.CustomPath=custom-path\""` +- 对于`CustomConf`,应该使用`-X \"code.gitea.io/gitea/modules/setting.CustomConf=conf.ini\"` +- 对于`AppWorkPath`,应该使用`-X \"code.gitea.io/gitea/modules/setting.AppWorkPath=working-path\"` +- 对于`StaticRootPath`,应该使用`-X \"code.gitea.io/gitea/modules/setting.StaticRootPath=static-root-path\"` +- 要更改默认的 PID 文件位置,请使用`-X \"code.gitea.io/gitea/cmd.PIDFile=/run/gitea.pid\"` + +将这些字符串与其前导的`-X`添加到`LDFLAGS`变量中,并像上面那样使用适当的`TAGS`运行`make build`。 + +运行`gitea help`将允许您查看配置的`gitea`设置。 + ## 交叉编译 -Go 编译器支持交叉编译到不同的目标架构。有关 Go 支持的目标架构列表,请参见 [Optional environment variables](https://go.dev/doc/install/source#environment)。 +`go`编译器工具链支持将代码交叉编译到不同的目标架构上。请参考[`GOOS`和`GOARCH`环境变量](https://golang.org/doc/install/source#environment) 以获取支持的目标列表。如果您想为性能较弱的系统(如树莓派)构建 Gitea,交叉编译非常有用。 -交叉构建适用于 Linux ARM64 的 Gitea: +要使用构建标签(`TAGS`)进行交叉编译Gitea,您还需要一个 C 交叉编译器,该编译器的目标架构与`GOOS`和`GOARCH`变量选择的架构相同。例如,要为 Linux ARM64(`GOOS=linux`和`GOARCH=arm64`)进行交叉编译,您需要`aarch64-unknown-linux-gnu-gcc`交叉编译器。这是因为 Gitea 构建标签使用了`cgo`的外部函数接口(FFI)。 -```bash +在没有任何标签的情况下,交叉编译的 Gitea 为 Linux ARM64 版本: + +``` GOOS=linux GOARCH=arm64 make build ``` -交叉构建适用于 Linux ARM64 的 Gitea,并且带上 Gitea 发行版采用的编译选项: +要交叉编译 Linux ARM64 下的Gitea,这是推荐的构建标签: -```bash +``` CC=aarch64-unknown-linux-gnu-gcc GOOS=linux GOARCH=arm64 TAGS="bindata sqlite sqlite_unlock_notify" make build ``` -## 使用Linux与Zig编译或交叉编译 +根据您的目标架构,适当替换`CC`、`GOOS`和`GOARCH`。 + +有时您需要构建一个静态编译的镜像。为此,您需要添加以下内容: + +``` +LDFLAGS="-linkmode external -extldflags '-static' $LDFLAGS" TAGS="netgo osusergo $TAGS" make build +``` + +这可以与上述的`CC`、`GOOS`和`GOARCH`结合使用。 + +### 添加 bash/zsh 自动补全(从 1.19 版本起) + +在[`contrib/autocompletion/bash_autocomplete`](https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/autocompletion/bash_autocomplete)中可以找到一个启用 bash 自动补全的脚本。您可以根据需要进行修改,并在您的 `.bashrc` 中使用 `source` 命令加载该脚本,或者将其复制到 `/usr/share/bash-completion/completions/gitea`。 + +类似地,可以在[`contrib/autocompletion/zsh_autocomplete`](https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/autocompletion/zsh_autocomplete)中找到一个用于 zsh 自动补全的脚本。您可以将其复制到 `/usr/share/zsh/_gitea`,或者在您的 `.zshrc` 中使用 `source` 命令加载该脚本。 + +可能需要你根据具体情况进一步改进这些脚本。 + +## 在 Linux 上使用 Zig 进行编译或交叉编译 -按照[Getting Started of Zig](https://ziglang.org/learn/getting-started/#installing-zig)来安装zig。 +请按照 [Zig 的入门指南](https://ziglang.org/learn/getting-started/#installing-zig) 安装 Zig。 -- 编译(Linux ➝ Linux) +- 编译 (Linux ➝ Linux) ```sh CC="zig cc -target x86_64-linux-gnu" \ @@ -117,7 +181,7 @@ TAGS="bindata sqlite sqlite_unlock_notify" \ make build ``` -- 交叉编译(Linux ➝ Windows) +- 交叉编译 (Linux ➝ Windows) ```sh CC="zig cc -target x86_64-windows-gnu" \ @@ -129,11 +193,11 @@ TAGS="bindata sqlite sqlite_unlock_notify" \ make build ``` -## 使用Windows与Zig编译或交叉编译 +## 在 Windows 上使用 Zig 进行编译或交叉编译 使用`GIT BASH`编译。 -- 编译(Windows ➝ Windows) +- 编译 (Windows ➝ Windows) ```sh CC="zig cc -target x86_64-windows-gnu" \ @@ -145,7 +209,7 @@ TAGS="bindata sqlite sqlite_unlock_notify" \ make build ``` -- 交叉编译(Windows ➝ Linux) +- 交叉编译 (Windows ➝ Linux) ```sh CC="zig cc -target x86_64-linux-gnu" \ diff --git a/docs/content/installation/on-cloud-provider.zh-cn.md b/docs/content/installation/on-cloud-provider.zh-cn.md index 7a2f3709ca13..7b9a7bdf4047 100644 --- a/docs/content/installation/on-cloud-provider.zh-cn.md +++ b/docs/content/installation/on-cloud-provider.zh-cn.md @@ -51,3 +51,13 @@ Gitea 可以在 [Vultr](https://www.vultr.com) 的市场中被找到。 [alwaysdata](https://www.alwaysdata.com/) 将 Gitea 作为其市场中的一个 droplet. 要将 Gitea 部署到 alwaysdata, 请参考 [alwaysdata Marketplace](https://www.alwaysdata.com/en/marketplace/gitea/). + +## Exoscale + +[Exoscale](https://www.exoscale.com/) 在其市场中提供由 [Glasskube](https://glasskube.eu/) 管理的 Gitea。 + +Exoscale 是一家欧洲的云服务提供商。 + +该软件包通过开源的 [Glasskube Kubernetes Operator](https://github.com/glasskube/operator) 进行维护和更新。 + +要在 Exoscale 上部署 Gitea,请参考 [Exoscale Marketplace](https://www.exoscale.com/marketplace/listing/glasskube-gitea/)。 diff --git a/docs/content/installation/upgrade-from-gitea.zh-cn.md b/docs/content/installation/upgrade-from-gitea.zh-cn.md index 8c919e15f327..c0ad8d551a1a 100644 --- a/docs/content/installation/upgrade-from-gitea.zh-cn.md +++ b/docs/content/installation/upgrade-from-gitea.zh-cn.md @@ -39,7 +39,7 @@ Gitea 会保留首二位版本号相同的版本的兼容性 (`a.b.x` -> `a.b.y` | 1.4.0 | 1.4.1 | ✅ | | 1.4.1 | 1.4.0 | ⚠️ 不建议,后果自负!尽管数据库结构可能不会变更,让它可以正常工作。我们强烈建议降级前进行完全的备份。 | | 1.4.x | 1.5.y | ✅ 数据库会被自动升级。你可以直接从 1.4.x 升级到最新的 1.5.y。 | -| 1.5.y | 1.4.x | ❌ 数据库已被升级且不可被旧版 Gitea 利用,使用备份来进行降级。 | +| 1.5.y | 1.4.x | ❌ 数据库已被升级且无法用于旧版本Gitea,使用备份来进行降级。 | **因为你不能基于升级后的数据库运行旧版 Gitea,所以你应该在数据库升级前完成数据备份。** @@ -60,7 +60,7 @@ Gitea 会保留首二位版本号相同的版本的兼容性 (`a.b.x` -> `a.b.y` * `docker pull` 拉取 Gitea 的最新发布版。 * 停止运行中的实例,备份数据。 -* 使用 `docker` 或 `docker-compose` 启动更新的 Gitea Docker 容器. +* 使用 `docker` 或 `docker-compose` 启动较新的 Gitea Docker 容器. ## 从包升级 @@ -82,6 +82,6 @@ Gitea 会保留首二位版本号相同的版本的兼容性 (`a.b.x` -> `a.b.y` Gitea 的模板结构与变量可能会随着各个版本的发布发生变化,如果你使用了自定义模板, 你得注意你的模板与你使用的 Gitea 版本的兼容性。 -如果自定义模板与 Gitea 版本不兼容,你可能会得到: +如果自定义模板与 Gitea 版本不兼容,你可能会遇到: `50x` 服务器错误,页面元素丢失或故障,莫名其妙的页面布局,等等… 移除或更新不兼容的模板,Gitea Web 才可以正常工作。 diff --git a/docs/content/installation/with-docker-rootless.en-us.md b/docs/content/installation/with-docker-rootless.en-us.md index 3c81c8633c9c..fc99819d7f7c 100644 --- a/docs/content/installation/with-docker-rootless.en-us.md +++ b/docs/content/installation/with-docker-rootless.en-us.md @@ -306,7 +306,7 @@ services: environment: - GITEA__mailer__ENABLED=true - GITEA__mailer__FROM=${GITEA__mailer__FROM:?GITEA__mailer__FROM not set} - - GITEA__mailer__MAILER_TYPE=smtp + - GITEA__mailer__PROTOCOL=smtp - GITEA__mailer__HOST=${GITEA__mailer__HOST:?GITEA__mailer__HOST not set} - GITEA__mailer__IS_TLS_ENABLED=true - GITEA__mailer__USER=${GITEA__mailer__USER:-apikey} diff --git a/docs/content/installation/with-docker-rootless.zh-cn.md b/docs/content/installation/with-docker-rootless.zh-cn.md index 1910e0a88400..9d5013eaa091 100644 --- a/docs/content/installation/with-docker-rootless.zh-cn.md +++ b/docs/content/installation/with-docker-rootless.zh-cn.md @@ -274,7 +274,7 @@ services: environment: - GITEA__mailer__ENABLED=true - GITEA__mailer__FROM=${GITEA__mailer__FROM:?GITEA__mailer__FROM not set} - - GITEA__mailer__MAILER_TYPE=smtp + - GITEA__mailer__PROTOCOL=smtp - GITEA__mailer__HOST=${GITEA__mailer__HOST:?GITEA__mailer__HOST not set} - GITEA__mailer__IS_TLS_ENABLED=true - GITEA__mailer__USER=${GITEA__mailer__USER:-apikey} diff --git a/docs/content/installation/with-docker.en-us.md b/docs/content/installation/with-docker.en-us.md index 7b47046031d6..f5a09e9fc524 100644 --- a/docs/content/installation/with-docker.en-us.md +++ b/docs/content/installation/with-docker.en-us.md @@ -303,9 +303,8 @@ services: environment: - GITEA__mailer__ENABLED=true - GITEA__mailer__FROM=${GITEA__mailer__FROM:?GITEA__mailer__FROM not set} - - GITEA__mailer__MAILER_TYPE=smtp + - GITEA__mailer__PROTOCOL=smtps - GITEA__mailer__HOST=${GITEA__mailer__HOST:?GITEA__mailer__HOST not set} - - GITEA__mailer__IS_TLS_ENABLED=true - GITEA__mailer__USER=${GITEA__mailer__USER:-apikey} - GITEA__mailer__PASSWD="""${GITEA__mailer__PASSWD:?GITEA__mailer__PASSWD not set}""" ``` diff --git a/docs/content/installation/with-docker.zh-cn.md b/docs/content/installation/with-docker.zh-cn.md index e04d3158a1c1..d8cbc2f9509f 100644 --- a/docs/content/installation/with-docker.zh-cn.md +++ b/docs/content/installation/with-docker.zh-cn.md @@ -287,9 +287,8 @@ services: environment: - GITEA__mailer__ENABLED=true - GITEA__mailer__FROM=${GITEA__mailer__FROM:?GITEA__mailer__FROM not set} - - GITEA__mailer__MAILER_TYPE=smtp + - GITEA__mailer__PROTOCOL=smtps - GITEA__mailer__HOST=${GITEA__mailer__HOST:?GITEA__mailer__HOST not set} - - GITEA__mailer__IS_TLS_ENABLED=true - GITEA__mailer__USER=${GITEA__mailer__USER:-apikey} - GITEA__mailer__PASSWD="""${GITEA__mailer__PASSWD:?GITEA__mailer__PASSWD not set}""" ``` diff --git a/docs/static/authorize.png b/docs/static/authorize.png new file mode 100644 index 000000000000..7556b1220cd2 Binary files /dev/null and b/docs/static/authorize.png differ diff --git a/docs/static/cloudron.svg b/docs/static/cloudron.svg new file mode 100644 index 000000000000..716f67a165ea --- /dev/null +++ b/docs/static/cloudron.svg @@ -0,0 +1,53 @@ + + + \ No newline at end of file diff --git a/docs/static/gitea-push-hint.png b/docs/static/gitea-push-hint.png new file mode 100644 index 000000000000..6f3ab3c60682 Binary files /dev/null and b/docs/static/gitea-push-hint.png differ diff --git a/docs/static/open-in-gitpod.svg b/docs/static/open-in-gitpod.svg new file mode 100644 index 000000000000..b97cd2948794 --- /dev/null +++ b/docs/static/open-in-gitpod.svg @@ -0,0 +1,23 @@ + \ No newline at end of file diff --git a/models/git/lfs.go b/models/git/lfs.go index 7d3da72a9410..e8192f92c5c6 100644 --- a/models/git/lfs.go +++ b/models/git/lfs.go @@ -6,7 +6,6 @@ package git import ( "context" "fmt" - "time" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/perm" @@ -370,8 +369,8 @@ func IterateRepositoryIDsWithLFSMetaObjects(ctx context.Context, f func(ctx cont // IterateLFSMetaObjectsForRepoOptions provides options for IterateLFSMetaObjectsForRepo type IterateLFSMetaObjectsForRepoOptions struct { - OlderThan time.Time - UpdatedLessRecentlyThan time.Time + OlderThan timeutil.TimeStamp + UpdatedLessRecentlyThan timeutil.TimeStamp OrderByUpdated bool LoopFunctionAlwaysUpdates bool } @@ -382,8 +381,8 @@ func IterateLFSMetaObjectsForRepo(ctx context.Context, repoID int64, f func(cont batchSize := setting.Database.IterateBufferSize engine := db.GetEngine(ctx) type CountLFSMetaObject struct { - Count int64 - LFSMetaObject + Count int64 + LFSMetaObject `xorm:"extends"` } id := int64(0) diff --git a/models/repo/release.go b/models/repo/release.go index c63b32445767..a00585111e12 100644 --- a/models/repo/release.go +++ b/models/repo/release.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -336,6 +337,17 @@ func (s releaseMetaSearch) Less(i, j int) bool { return s.ID[i] < s.ID[j] } +func hasDuplicateName(attaches []*Attachment) bool { + attachSet := container.Set[string]{} + for _, attachment := range attaches { + if attachSet.Contains(attachment.Name) { + return true + } + attachSet.Add(attachment.Name) + } + return false +} + // GetReleaseAttachments retrieves the attachments for releases func GetReleaseAttachments(ctx context.Context, rels ...*Release) (err error) { if len(rels) == 0 { @@ -360,7 +372,7 @@ func GetReleaseAttachments(ctx context.Context, rels ...*Release) (err error) { err = db.GetEngine(ctx). Asc("release_id", "name"). In("release_id", sortedRels.ID). - Find(&attachments, Attachment{}) + Find(&attachments) if err != nil { return err } @@ -381,21 +393,8 @@ func GetReleaseAttachments(ctx context.Context, rels ...*Release) (err error) { continue } - // Check if there are two or more attachments with the same name - hasDuplicates := false - foundNames := make(map[string]bool) - for _, attachment := range release.Attachments { - _, found := foundNames[attachment.Name] - if found { - hasDuplicates = true - break - } else { - foundNames[attachment.Name] = true - } - } - // If the names unique, use the URL with the Name instead of the UUID - if !hasDuplicates { + if !hasDuplicateName(release.Attachments) { for _, attachment := range release.Attachments { attachment.CustomDownloadURL = release.Repo.HTMLURL() + "/releases/download/" + url.PathEscape(release.TagName) + "/" + url.PathEscape(attachment.Name) } diff --git a/modules/cache/cache_test.go b/modules/cache/cache_test.go index 1c8f7d55ba8c..cf464af392d5 100644 --- a/modules/cache/cache_test.go +++ b/modules/cache/cache_test.go @@ -18,6 +18,7 @@ func createTestCache() { Adapter: "memory", TTL: time.Minute, }) + setting.CacheService.TTL = 24 * time.Hour } func TestNewContext(t *testing.T) { @@ -54,12 +55,12 @@ func TestGetString(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "", data) - // data, err = GetString("key", func() (string, error) { - // return "some data", nil - // }) - // assert.NoError(t, err) - // assert.Equal(t, "", data) - // Remove("key") + data, err = GetString("key", func() (string, error) { + return "some data", nil + }) + assert.NoError(t, err) + assert.Equal(t, "", data) + Remove("key") data, err = GetString("key", func() (string, error) { return "some data", nil @@ -67,13 +68,12 @@ func TestGetString(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "some data", data) - // data, err = GetString("key", func() (string, error) { - // return "", fmt.Errorf("some error") - // }) - // assert.NoError(t, err) - // assert.Equal(t, "some data", data) - - // TODO: uncommented code works in IDE but not with go test + data, err = GetString("key", func() (string, error) { + return "", fmt.Errorf("some error") + }) + assert.NoError(t, err) + assert.Equal(t, "some data", data) + Remove("key") } func TestGetInt(t *testing.T) { @@ -91,12 +91,12 @@ func TestGetInt(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 0, data) - // data, err = GetInt("key", func() (int, error) { - // return 100, nil - // }) - // assert.NoError(t, err) - // assert.Equal(t, 0, data) - // Remove("key") + data, err = GetInt("key", func() (int, error) { + return 100, nil + }) + assert.NoError(t, err) + assert.Equal(t, 0, data) + Remove("key") data, err = GetInt("key", func() (int, error) { return 100, nil @@ -104,13 +104,12 @@ func TestGetInt(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 100, data) - // data, err = GetInt("key", func() (int, error) { - // return 0, fmt.Errorf("some error") - // }) - // assert.NoError(t, err) - // assert.Equal(t, 100, data) - - // TODO: uncommented code works in IDE but not with go test + data, err = GetInt("key", func() (int, error) { + return 0, fmt.Errorf("some error") + }) + assert.NoError(t, err) + assert.Equal(t, 100, data) + Remove("key") } func TestGetInt64(t *testing.T) { @@ -128,12 +127,12 @@ func TestGetInt64(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 0, data) - // data, err = GetInt64("key", func() (int64, error) { - // return 100, nil - // }) - // assert.NoError(t, err) - // assert.EqualValues(t, 0, data) - // Remove("key") + data, err = GetInt64("key", func() (int64, error) { + return 100, nil + }) + assert.NoError(t, err) + assert.EqualValues(t, 0, data) + Remove("key") data, err = GetInt64("key", func() (int64, error) { return 100, nil @@ -141,11 +140,10 @@ func TestGetInt64(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, 100, data) - // data, err = GetInt64("key", func() (int, error) { - // return 0, fmt.Errorf("some error") - // }) - // assert.NoError(t, err) - // assert.EqualValues(t, 100, data) - - // TODO: uncommented code works in IDE but not with go test + data, err = GetInt64("key", func() (int64, error) { + return 0, fmt.Errorf("some error") + }) + assert.NoError(t, err) + assert.EqualValues(t, 100, data) + Remove("key") } diff --git a/modules/context/base.go b/modules/context/base.go index 8566ef7861ba..83e5a214f8b3 100644 --- a/modules/context/base.go +++ b/modules/context/base.go @@ -136,18 +136,6 @@ func (b *Base) JSON(status int, content any) { } } -func (b *Base) JSONRedirect(redirect string) { - b.JSON(http.StatusOK, map[string]any{"redirect": redirect}) -} - -func (b *Base) JSONOK() { - b.JSON(http.StatusOK, map[string]any{"ok": true}) // this is only a dummy response, frontend seldom uses it -} - -func (b *Base) JSONError(msg string) { - b.JSON(http.StatusBadRequest, map[string]any{"errorMessage": msg}) -} - // RemoteAddr returns the client machine ip address func (b *Base) RemoteAddr() string { return b.Req.RemoteAddr diff --git a/modules/context/context.go b/modules/context/context.go index 47a04c989c81..de0518a1d27c 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -226,3 +226,15 @@ func (ctx *Context) GetErrMsg() string { } return msg } + +func (ctx *Context) JSONRedirect(redirect string) { + ctx.JSON(http.StatusOK, map[string]any{"redirect": redirect}) +} + +func (ctx *Context) JSONOK() { + ctx.JSON(http.StatusOK, map[string]any{"ok": true}) // this is only a dummy response, frontend seldom uses it +} + +func (ctx *Context) JSONError(msg string) { + ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": msg}) +} diff --git a/modules/context/context_response.go b/modules/context/context_response.go index bb3ccf69ce42..9dc6d1fc0ec5 100644 --- a/modules/context/context_response.go +++ b/modules/context/context_response.go @@ -166,6 +166,7 @@ func (ctx *Context) serverErrorInternal(logMsg string, logErr error) { // NotFoundOrServerError use error check function to determine if the error // is about not found. It responds with 404 status code for not found error, // or error context description for logging purpose of 500 server error. +// TODO: remove the "errCheck" and use util.ErrNotFound to check func (ctx *Context) NotFoundOrServerError(logMsg string, errCheck func(error) bool, logErr error) { if errCheck(logErr) { ctx.notFoundInternal(logMsg, logErr) diff --git a/modules/git/commit.go b/modules/git/commit.go index 729e3b4672a7..c44882d88617 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -20,7 +20,6 @@ import ( // Commit represents a git commit. type Commit struct { - Branch string // Branch this commit belongs to Tree ID SHA1 // The ID of this commit object Author *Signature @@ -432,31 +431,6 @@ func (c *Commit) GetBranchName() (string, error) { return strings.SplitN(strings.TrimSpace(data), "~", 2)[0], nil } -// LoadBranchName load branch name for commit -func (c *Commit) LoadBranchName() (err error) { - if len(c.Branch) != 0 { - return nil - } - - c.Branch, err = c.GetBranchName() - return err -} - -// GetTagName gets the current tag name for given commit -func (c *Commit) GetTagName() (string, error) { - data, _, err := NewCommand(c.repo.Ctx, "describe", "--exact-match", "--tags", "--always").AddDynamicArguments(c.ID.String()).RunStdString(&RunOpts{Dir: c.repo.Path}) - if err != nil { - // handle special case where there is no tag for this commit - if strings.Contains(err.Error(), "no tag exactly matches") { - return "", nil - } - - return "", err - } - - return strings.TrimSpace(data), nil -} - // CommitFileStatus represents status of files in a commit. type CommitFileStatus struct { Added []string diff --git a/modules/git/repo_ref.go b/modules/git/repo_ref.go index 54e424bb832a..8eaa17cb0418 100644 --- a/modules/git/repo_ref.go +++ b/modules/git/repo_ref.go @@ -3,7 +3,61 @@ package git +import ( + "context" + "strings" + + "code.gitea.io/gitea/modules/util" +) + // GetRefs returns all references of the repository. func (repo *Repository) GetRefs() ([]*Reference, error) { return repo.GetRefsFiltered("") } + +// ListOccurrences lists all refs of the given refType the given commit appears in sorted by creation date DESC +// refType should only be a literal "branch" or "tag" and nothing else +func (repo *Repository) ListOccurrences(ctx context.Context, refType, commitSHA string) ([]string, error) { + cmd := NewCommand(ctx) + if refType == "branch" { + cmd.AddArguments("branch") + } else if refType == "tag" { + cmd.AddArguments("tag") + } else { + return nil, util.NewInvalidArgumentErrorf(`can only use "branch" or "tag" for refType, but got %q`, refType) + } + stdout, _, err := cmd.AddArguments("--no-color", "--sort=-creatordate", "--contains").AddDynamicArguments(commitSHA).RunStdString(&RunOpts{Dir: repo.Path}) + if err != nil { + return nil, err + } + + refs := strings.Split(strings.TrimSpace(stdout), "\n") + if refType == "branch" { + return parseBranches(refs), nil + } + return parseTags(refs), nil +} + +func parseBranches(refs []string) []string { + results := make([]string, 0, len(refs)) + for _, ref := range refs { + if strings.HasPrefix(ref, "* ") { // current branch (main branch) + results = append(results, ref[len("* "):]) + } else if strings.HasPrefix(ref, " ") { // all other branches + results = append(results, ref[len(" "):]) + } else if ref != "" { + results = append(results, ref) + } + } + return results +} + +func parseTags(refs []string) []string { + results := make([]string, 0, len(refs)) + for _, ref := range refs { + if ref != "" { + results = append(results, ref) + } + } + return results +} diff --git a/modules/markup/sanitizer.go b/modules/markup/sanitizer.go index 9f97f1d5b13e..48c08831f166 100644 --- a/modules/markup/sanitizer.go +++ b/modules/markup/sanitizer.go @@ -96,6 +96,9 @@ func createDefaultPolicy() *bluemonday.Policy { // Allow classes for task lists policy.AllowAttrs("class").Matching(regexp.MustCompile(`task-list-item`)).OnElements("li") + // Allow classes for org mode list item status. + policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(unchecked|checked|indeterminate)$`)).OnElements("li") + // Allow icons policy.AllowAttrs("class").Matching(regexp.MustCompile(`^icon(\s+[\p{L}\p{N}_-]+)+$`)).OnElements("i") diff --git a/modules/markup/sanitizer_test.go b/modules/markup/sanitizer_test.go index 4d85cbf9f303..0bc63ff0a7cb 100644 --- a/modules/markup/sanitizer_test.go +++ b/modules/markup/sanitizer_test.go @@ -53,6 +53,11 @@ func Test_Sanitizer(t *testing.T) { `
Hello World
`, `Hello World
`, `Hello World
`, `Hello World
`,
+ // Org mode status of list items.
+ ``, ``,
+ ``, ``,
+ ``, ``,
+
// URLs
`my custom URL scheme`, `my custom URL scheme`,
`my custom URL scheme`, `my custom URL scheme`,
diff --git a/modules/queue/manager_test.go b/modules/queue/manager_test.go
index 1fd29f813f8a..9a5b616b1a57 100644
--- a/modules/queue/manager_test.go
+++ b/modules/queue/manager_test.go
@@ -51,7 +51,7 @@ CONN_STR = redis://
assert.Equal(t, "", q.baseConfig.ConnStr)
assert.Equal(t, "default_queue", q.baseConfig.QueueFullName)
assert.Equal(t, "default_queue_unique", q.baseConfig.SetFullName)
- assert.Equal(t, 10, q.GetWorkerMaxNumber())
+ assert.NotZero(t, q.GetWorkerMaxNumber())
assert.Equal(t, 0, q.GetWorkerNumber())
assert.Equal(t, 0, q.GetWorkerActiveNumber())
assert.Equal(t, 0, q.GetQueueItemNumber())
@@ -75,7 +75,7 @@ BATCH_LENGTH = 22
CONN_STR =
QUEUE_NAME = _q2
SET_NAME = _u2
-MAX_WORKERS = 2
+MAX_WORKERS = 123
`)
assert.NoError(t, err)
@@ -89,7 +89,7 @@ MAX_WORKERS = 2
assert.Equal(t, "addrs=127.0.0.1:6379 db=0", q1.baseConfig.ConnStr)
assert.Equal(t, "no-such_queue1", q1.baseConfig.QueueFullName)
assert.Equal(t, "no-such_queue1_unique", q1.baseConfig.SetFullName)
- assert.Equal(t, 10, q1.GetWorkerMaxNumber())
+ assert.NotZero(t, q1.GetWorkerMaxNumber())
assert.Equal(t, 0, q1.GetWorkerNumber())
assert.Equal(t, 0, q1.GetWorkerActiveNumber())
assert.Equal(t, 0, q1.GetQueueItemNumber())
@@ -105,7 +105,7 @@ MAX_WORKERS = 2
assert.Equal(t, "", q2.baseConfig.ConnStr)
assert.Equal(t, "sub_q2", q2.baseConfig.QueueFullName)
assert.Equal(t, "sub_q2_u2", q2.baseConfig.SetFullName)
- assert.Equal(t, 2, q2.GetWorkerMaxNumber())
+ assert.Equal(t, 123, q2.GetWorkerMaxNumber())
assert.Equal(t, 0, q2.GetWorkerNumber())
assert.Equal(t, 0, q2.GetWorkerActiveNumber())
assert.Equal(t, 0, q2.GetQueueItemNumber())
diff --git a/modules/setting/queue.go b/modules/setting/queue.go
index 8673537b5256..fc15bd07ed68 100644
--- a/modules/setting/queue.go
+++ b/modules/setting/queue.go
@@ -5,6 +5,7 @@ package setting
import (
"path/filepath"
+ "runtime"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
@@ -25,18 +26,24 @@ type QueueSettings struct {
MaxWorkers int
}
-var queueSettingsDefault = QueueSettings{
- Type: "level", // dummy, channel, level, redis
- Datadir: "queues/common", // relative to AppDataPath
- Length: 100, // queue length before a channel queue will block
-
- QueueName: "_queue",
- SetName: "_unique",
- BatchLength: 20,
- MaxWorkers: 10,
-}
-
func GetQueueSettings(rootCfg ConfigProvider, name string) (QueueSettings, error) {
+ queueSettingsDefault := QueueSettings{
+ Type: "level", // dummy, channel, level, redis
+ Datadir: "queues/common", // relative to AppDataPath
+ Length: 100, // queue length before a channel queue will block
+
+ QueueName: "_queue",
+ SetName: "_unique",
+ BatchLength: 20,
+ MaxWorkers: runtime.NumCPU() / 2,
+ }
+ if queueSettingsDefault.MaxWorkers < 1 {
+ queueSettingsDefault.MaxWorkers = 1
+ }
+ if queueSettingsDefault.MaxWorkers > 10 {
+ queueSettingsDefault.MaxWorkers = 10
+ }
+
// deep copy default settings
cfg := QueueSettings{}
if cfgBs, err := json.Marshal(queueSettingsDefault); err != nil {
diff --git a/modules/structs/commit_status.go b/modules/structs/commit_status.go
index ff31f2d2ac3d..de1d8fa566cd 100644
--- a/modules/structs/commit_status.go
+++ b/modules/structs/commit_status.go
@@ -4,7 +4,7 @@
package structs
// CommitStatusState holds the state of a CommitStatus
-// It can be "pending", "success", "error", "failure", and "warning"
+// It can be "pending", "success", "error" and "failure"
type CommitStatusState string
const (
@@ -18,14 +18,25 @@ const (
CommitStatusFailure CommitStatusState = "failure"
)
+var commitStatusPriorities = map[CommitStatusState]int{
+ CommitStatusError: 0,
+ CommitStatusFailure: 1,
+ CommitStatusPending: 2,
+ CommitStatusSuccess: 3,
+}
+
// NoBetterThan returns true if this State is no better than the given State
+// This function only handles the states defined in CommitStatusPriorities
func (css CommitStatusState) NoBetterThan(css2 CommitStatusState) bool {
- commitStatusPriorities := map[CommitStatusState]int{
- CommitStatusError: 0,
- CommitStatusFailure: 1,
- CommitStatusPending: 2,
- CommitStatusSuccess: 3,
+ // NoBetterThan only handles the 4 states above
+ if _, exist := commitStatusPriorities[css]; !exist {
+ return false
}
+
+ if _, exist := commitStatusPriorities[css2]; !exist {
+ return false
+ }
+
return commitStatusPriorities[css] <= commitStatusPriorities[css2]
}
diff --git a/modules/structs/commit_status_test.go b/modules/structs/commit_status_test.go
new file mode 100644
index 000000000000..f06808534c40
--- /dev/null
+++ b/modules/structs/commit_status_test.go
@@ -0,0 +1,174 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package structs
+
+import (
+ "testing"
+)
+
+func TestNoBetterThan(t *testing.T) {
+ type args struct {
+ css CommitStatusState
+ css2 CommitStatusState
+ }
+ var unExpectedState CommitStatusState
+ tests := []struct {
+ name string
+ args args
+ want bool
+ }{
+ {
+ name: "success is no better than success",
+ args: args{
+ css: CommitStatusSuccess,
+ css2: CommitStatusSuccess,
+ },
+ want: true,
+ },
+ {
+ name: "success is no better than pending",
+ args: args{
+ css: CommitStatusSuccess,
+ css2: CommitStatusPending,
+ },
+ want: false,
+ },
+ {
+ name: "success is no better than failure",
+ args: args{
+ css: CommitStatusSuccess,
+ css2: CommitStatusFailure,
+ },
+ want: false,
+ },
+ {
+ name: "success is no better than error",
+ args: args{
+ css: CommitStatusSuccess,
+ css2: CommitStatusError,
+ },
+ want: false,
+ },
+ {
+ name: "pending is no better than success",
+ args: args{
+ css: CommitStatusPending,
+ css2: CommitStatusSuccess,
+ },
+ want: true,
+ },
+ {
+ name: "pending is no better than pending",
+ args: args{
+ css: CommitStatusPending,
+ css2: CommitStatusPending,
+ },
+ want: true,
+ },
+ {
+ name: "pending is no better than failure",
+ args: args{
+ css: CommitStatusPending,
+ css2: CommitStatusFailure,
+ },
+ want: false,
+ },
+ {
+ name: "pending is no better than error",
+ args: args{
+ css: CommitStatusPending,
+ css2: CommitStatusError,
+ },
+ want: false,
+ },
+ {
+ name: "failure is no better than success",
+ args: args{
+ css: CommitStatusFailure,
+ css2: CommitStatusSuccess,
+ },
+ want: true,
+ },
+ {
+ name: "failure is no better than pending",
+ args: args{
+ css: CommitStatusFailure,
+ css2: CommitStatusPending,
+ },
+ want: true,
+ },
+ {
+ name: "failure is no better than failure",
+ args: args{
+ css: CommitStatusFailure,
+ css2: CommitStatusFailure,
+ },
+ want: true,
+ },
+ {
+ name: "failure is no better than error",
+ args: args{
+ css: CommitStatusFailure,
+ css2: CommitStatusError,
+ },
+ want: false,
+ },
+ {
+ name: "error is no better than success",
+ args: args{
+ css: CommitStatusError,
+ css2: CommitStatusSuccess,
+ },
+ want: true,
+ },
+ {
+ name: "error is no better than pending",
+ args: args{
+ css: CommitStatusError,
+ css2: CommitStatusPending,
+ },
+ want: true,
+ },
+ {
+ name: "error is no better than failure",
+ args: args{
+ css: CommitStatusError,
+ css2: CommitStatusFailure,
+ },
+ want: true,
+ },
+ {
+ name: "error is no better than error",
+ args: args{
+ css: CommitStatusError,
+ css2: CommitStatusError,
+ },
+ want: true,
+ },
+ {
+ name: "unExpectedState is no better than success",
+ args: args{
+ css: unExpectedState,
+ css2: CommitStatusSuccess,
+ },
+ want: false,
+ },
+ {
+ name: "unExpectedState is no better than unExpectedState",
+ args: args{
+ css: unExpectedState,
+ css2: unExpectedState,
+ },
+ want: false,
+ },
+ }
+ 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)
+ }
+ })
+ }
+}
diff --git a/modules/structs/org_team.go b/modules/structs/org_team.go
index 95db0debadca..78dc4abaefd3 100644
--- a/modules/structs/org_team.go
+++ b/modules/structs/org_team.go
@@ -29,10 +29,10 @@ type CreateTeamOption struct {
IncludesAllRepositories bool `json:"includes_all_repositories"`
// enum: read,write,admin
Permission string `json:"permission"`
- // example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"]
+ // example: ["repo.actions","repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.ext_wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"]
// Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions.
Units []string `json:"units"`
- // example: {"repo.code":"read","repo.issues":"write","repo.ext_issues":"none","repo.wiki":"admin","repo.pulls":"owner","repo.releases":"none","repo.projects":"none","repo.ext_wiki":"none"}
+ // example: {"repo.actions","repo.packages","repo.code":"read","repo.issues":"write","repo.ext_issues":"none","repo.wiki":"admin","repo.pulls":"owner","repo.releases":"none","repo.projects":"none","repo.ext_wiki":"none"}
UnitsMap map[string]string `json:"units_map"`
CanCreateOrgRepo bool `json:"can_create_org_repo"`
}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index a76750e44fbd..dc88c422b5ea 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1170,6 +1170,9 @@ commit_graph.select = Select branches
commit_graph.hide_pr_refs = Hide Pull Requests
commit_graph.monochrome = Mono
commit_graph.color = Color
+commit.contained_in = This commit is contained in:
+commit.contained_in_default_branch = This commit is part of the default branch
+commit.load_referencing_branches_and_tags = Load branches and tags referencing this commit
blame = Blame
download_file = Download file
normal_view = Normal View
@@ -1384,7 +1387,7 @@ issues.delete_branch_at = `deleted branch %s %s`
issues.filter_label = Label
issues.filter_label_exclude = `Use alt
+ click/enter
to exclude labels`
issues.filter_label_no_select = All labels
-issues.filter_label_select_no_label = No Label
+issues.filter_label_select_no_label = No label
issues.filter_milestone = Milestone
issues.filter_milestone_all = All milestones
issues.filter_milestone_none = No milestones
diff --git a/routers/api/packages/nuget/api_v2.go b/routers/api/packages/nuget/api_v2.go
index 7d0ac64a8adc..a726065ad087 100644
--- a/routers/api/packages/nuget/api_v2.go
+++ b/routers/api/packages/nuget/api_v2.go
@@ -289,7 +289,7 @@ type FeedResponse struct {
ID string `xml:"id"`
Title TypedValue[string] `xml:"title"`
Updated time.Time `xml:"updated"`
- Link FeedEntryLink `xml:"link"`
+ Links []FeedEntryLink `xml:"link"`
Entries []*FeedEntry `xml:"entry"`
Count int64 `xml:"m:count"`
}
@@ -300,6 +300,16 @@ func createFeedResponse(l *linkBuilder, totalEntries int64, pds []*packages_mode
entries = append(entries, createEntry(l, pd, false))
}
+ links := []FeedEntryLink{
+ {Rel: "self", Href: l.Base},
+ }
+ if l.Next != nil {
+ links = append(links, FeedEntryLink{
+ Rel: "next",
+ Href: l.GetNextURL(),
+ })
+ }
+
return &FeedResponse{
Xmlns: "http://www.w3.org/2005/Atom",
Base: l.Base,
@@ -307,7 +317,7 @@ func createFeedResponse(l *linkBuilder, totalEntries int64, pds []*packages_mode
XmlnsM: "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata",
ID: "http://schemas.datacontract.org/2004/07/",
Updated: time.Now(),
- Link: FeedEntryLink{Rel: "self", Href: l.Base},
+ Links: links,
Count: totalEntries,
Entries: entries,
}
diff --git a/routers/api/packages/nuget/api_v3.go b/routers/api/packages/nuget/api_v3.go
index 28626f9294c3..af52125e2e4d 100644
--- a/routers/api/packages/nuget/api_v3.go
+++ b/routers/api/packages/nuget/api_v3.go
@@ -166,10 +166,10 @@ type PackageVersionsResponse struct {
Versions []string `json:"versions"`
}
-func createPackageVersionsResponse(pds []*packages_model.PackageDescriptor) *PackageVersionsResponse {
- versions := make([]string, 0, len(pds))
- for _, pd := range pds {
- versions = append(versions, pd.Version.Version)
+func createPackageVersionsResponse(pvs []*packages_model.PackageVersion) *PackageVersionsResponse {
+ versions := make([]string, 0, len(pvs))
+ for _, pv := range pvs {
+ versions = append(versions, pv.Version)
}
return &PackageVersionsResponse{
diff --git a/routers/api/packages/nuget/links.go b/routers/api/packages/nuget/links.go
index 1b02e4618467..4c573fe3161c 100644
--- a/routers/api/packages/nuget/links.go
+++ b/routers/api/packages/nuget/links.go
@@ -5,10 +5,17 @@ package nuget
import (
"fmt"
+ "net/url"
)
+type nextOptions struct {
+ Path string
+ Query url.Values
+}
+
type linkBuilder struct {
Base string
+ Next *nextOptions
}
// GetRegistrationIndexURL builds the registration index url
@@ -30,3 +37,16 @@ func (l *linkBuilder) GetPackageDownloadURL(id, version string) string {
func (l *linkBuilder) GetPackageMetadataURL(id, version string) string {
return fmt.Sprintf("%s/Packages(Id='%s',Version='%s')", l.Base, id, version)
}
+
+func (l *linkBuilder) GetNextURL() string {
+ u, _ := url.Parse(l.Base)
+ u = u.JoinPath(l.Next.Path)
+ q := u.Query()
+ for k, vs := range l.Next.Query {
+ for _, v := range vs {
+ q.Add(k, v)
+ }
+ }
+ u.RawQuery = q.Encode()
+ return u.String()
+}
diff --git a/routers/api/packages/nuget/nuget.go b/routers/api/packages/nuget/nuget.go
index edeba19b3bbd..9c97ba8e776f 100644
--- a/routers/api/packages/nuget/nuget.go
+++ b/routers/api/packages/nuget/nuget.go
@@ -9,6 +9,7 @@ import (
"fmt"
"io"
"net/http"
+ "net/url"
"regexp"
"strconv"
"strings"
@@ -111,13 +112,8 @@ func getSearchTerm(ctx *context.Context) string {
// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs
func SearchServiceV2(ctx *context.Context) {
- skip, take := ctx.FormInt("skip"), ctx.FormInt("take")
- if skip == 0 {
- skip = ctx.FormInt("$skip")
- }
- if take == 0 {
- take = ctx.FormInt("$top")
- }
+ skip, take := ctx.FormInt("$skip"), ctx.FormInt("$top")
+ paginator := db.NewAbsoluteListOptions(skip, take)
pvs, total, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
@@ -126,10 +122,7 @@ func SearchServiceV2(ctx *context.Context) {
Value: getSearchTerm(ctx),
},
IsInternal: util.OptionalBoolFalse,
- Paginator: db.NewAbsoluteListOptions(
- skip,
- take,
- ),
+ Paginator: paginator,
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
@@ -142,8 +135,28 @@ func SearchServiceV2(ctx *context.Context) {
return
}
+ skip, take = paginator.GetSkipTake()
+
+ var next *nextOptions
+ if len(pvs) == take {
+ next = &nextOptions{
+ Path: "Search()",
+ Query: url.Values{},
+ }
+ searchTerm := ctx.FormTrim("searchTerm")
+ if searchTerm != "" {
+ next.Query.Set("searchTerm", searchTerm)
+ }
+ filter := ctx.FormTrim("$filter")
+ if filter != "" {
+ next.Query.Set("$filter", filter)
+ }
+ next.Query.Set("$skip", strconv.Itoa(skip+take))
+ next.Query.Set("$top", strconv.Itoa(take))
+ }
+
resp := createFeedResponse(
- &linkBuilder{setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
+ &linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget", Next: next},
total,
pds,
)
@@ -193,7 +206,7 @@ func SearchServiceV3(ctx *context.Context) {
}
resp := createSearchResultResponse(
- &linkBuilder{setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
+ &linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
count,
pds,
)
@@ -222,7 +235,7 @@ func RegistrationIndex(ctx *context.Context) {
}
resp := createRegistrationIndexResponse(
- &linkBuilder{setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
+ &linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
pds,
)
@@ -251,7 +264,7 @@ func RegistrationLeafV2(ctx *context.Context) {
}
resp := createEntryResponse(
- &linkBuilder{setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
+ &linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
pd,
)
@@ -280,7 +293,7 @@ func RegistrationLeafV3(ctx *context.Context) {
}
resp := createRegistrationLeafResponse(
- &linkBuilder{setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
+ &linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
pd,
)
@@ -291,7 +304,19 @@ func RegistrationLeafV3(ctx *context.Context) {
func EnumeratePackageVersionsV2(ctx *context.Context) {
packageName := strings.Trim(ctx.FormTrim("id"), "'")
- pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName)
+ skip, take := ctx.FormInt("$skip"), ctx.FormInt("$top")
+ paginator := db.NewAbsoluteListOptions(skip, take)
+
+ pvs, total, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
+ OwnerID: ctx.Package.Owner.ID,
+ Type: packages_model.TypeNuGet,
+ Name: packages_model.SearchValue{
+ ExactMatch: true,
+ Value: packageName,
+ },
+ IsInternal: util.OptionalBoolFalse,
+ Paginator: paginator,
+ })
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
@@ -303,9 +328,22 @@ func EnumeratePackageVersionsV2(ctx *context.Context) {
return
}
+ skip, take = paginator.GetSkipTake()
+
+ var next *nextOptions
+ if len(pvs) == take {
+ next = &nextOptions{
+ Path: "FindPackagesById()",
+ Query: url.Values{},
+ }
+ next.Query.Set("id", packageName)
+ next.Query.Set("$skip", strconv.Itoa(skip+take))
+ next.Query.Set("$top", strconv.Itoa(take))
+ }
+
resp := createFeedResponse(
- &linkBuilder{setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
- int64(len(pds)),
+ &linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget", Next: next},
+ total,
pds,
)
@@ -345,13 +383,7 @@ func EnumeratePackageVersionsV3(ctx *context.Context) {
return
}
- pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
- if err != nil {
- apiError(ctx, http.StatusInternalServerError, err)
- return
- }
-
- resp := createPackageVersionsResponse(pds)
+ resp := createPackageVersionsResponse(pvs)
ctx.JSON(http.StatusOK, resp)
}
diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go
index adde26f0b56e..b743d1b0a55b 100644
--- a/routers/web/admin/auths.go
+++ b/routers/web/admin/auths.go
@@ -454,15 +454,11 @@ func DeleteAuthSource(ctx *context.Context) {
} else {
ctx.Flash.Error(fmt.Sprintf("auth_service.DeleteSource: %v", err))
}
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": setting.AppSubURL + "/admin/auths/" + url.PathEscape(ctx.Params(":authid")),
- })
+ ctx.JSONRedirect(setting.AppSubURL + "/admin/auths/" + url.PathEscape(ctx.Params(":authid")))
return
}
log.Trace("Authentication deleted by admin(%s): %d", ctx.Doer.Name, source.ID)
ctx.Flash.Success(ctx.Tr("admin.auths.deletion_success"))
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": setting.AppSubURL + "/admin/auths",
- })
+ ctx.JSONRedirect(setting.AppSubURL + "/admin/auths")
}
diff --git a/routers/web/admin/config.go b/routers/web/admin/config.go
index ba0d86264519..c70a2d1c9512 100644
--- a/routers/web/admin/config.go
+++ b/routers/web/admin/config.go
@@ -179,9 +179,7 @@ func Config(ctx *context.Context) {
func ChangeConfig(ctx *context.Context) {
key := strings.TrimSpace(ctx.FormString("key"))
if key == "" {
- ctx.JSON(http.StatusOK, map[string]string{
- "redirect": ctx.Req.URL.String(),
- })
+ ctx.JSONRedirect(ctx.Req.URL.String())
return
}
value := ctx.FormString("value")
diff --git a/routers/web/admin/hooks.go b/routers/web/admin/hooks.go
index 2e4122c9048b..cd8cc29cdfbc 100644
--- a/routers/web/admin/hooks.go
+++ b/routers/web/admin/hooks.go
@@ -67,7 +67,5 @@ func DeleteDefaultOrSystemWebhook(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
}
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": setting.AppSubURL + "/admin/hooks",
- })
+ ctx.JSONRedirect(setting.AppSubURL + "/admin/hooks")
}
diff --git a/routers/web/admin/packages.go b/routers/web/admin/packages.go
index 731a1d6ac2f2..8e4b8a373ef5 100644
--- a/routers/web/admin/packages.go
+++ b/routers/web/admin/packages.go
@@ -97,7 +97,5 @@ func DeletePackageVersion(ctx *context.Context) {
}
ctx.Flash.Success(ctx.Tr("packages.settings.delete.success"))
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": setting.AppSubURL + "/admin/packages?page=" + url.QueryEscape(ctx.FormString("page")) + "&q=" + url.QueryEscape(ctx.FormString("q")) + "&type=" + url.QueryEscape(ctx.FormString("type")),
- })
+ ctx.JSONRedirect(setting.AppSubURL + "/admin/packages?page=" + url.QueryEscape(ctx.FormString("page")) + "&q=" + url.QueryEscape(ctx.FormString("q")) + "&type=" + url.QueryEscape(ctx.FormString("type")))
}
diff --git a/routers/web/admin/repos.go b/routers/web/admin/repos.go
index 2ea8a2ad3581..d1d0abca0254 100644
--- a/routers/web/admin/repos.go
+++ b/routers/web/admin/repos.go
@@ -58,9 +58,7 @@ func DeleteRepo(ctx *context.Context) {
log.Trace("Repository deleted: %s", repo.FullName())
ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success"))
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": setting.AppSubURL + "/admin/repos?page=" + url.QueryEscape(ctx.FormString("page")) + "&sort=" + url.QueryEscape(ctx.FormString("sort")),
- })
+ ctx.JSONRedirect(setting.AppSubURL + "/admin/repos?page=" + url.QueryEscape(ctx.FormString("page")) + "&sort=" + url.QueryEscape(ctx.FormString("sort")))
}
// UnadoptedRepos lists the unadopted repositories
diff --git a/routers/web/admin/stacktrace.go b/routers/web/admin/stacktrace.go
index f2d2be481a6a..b603fb59a26d 100644
--- a/routers/web/admin/stacktrace.go
+++ b/routers/web/admin/stacktrace.go
@@ -42,7 +42,5 @@ func Stacktrace(ctx *context.Context) {
func StacktraceCancel(ctx *context.Context) {
pid := ctx.Params("pid")
process.GetManager().Cancel(process.IDType(pid))
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": setting.AppSubURL + "/admin/monitor/stacktrace",
- })
+ ctx.JSONRedirect(setting.AppSubURL + "/admin/monitor/stacktrace")
}
diff --git a/routers/web/auth/webauthn.go b/routers/web/auth/webauthn.go
index e369f860811f..013e11eacce0 100644
--- a/routers/web/auth/webauthn.go
+++ b/routers/web/auth/webauthn.go
@@ -154,5 +154,5 @@ func WebAuthnLoginAssertionPost(ctx *context.Context) {
}
_ = ctx.Session.Delete("twofaUid")
- ctx.JSON(http.StatusOK, map[string]string{"redirect": redirect})
+ ctx.JSONRedirect(redirect)
}
diff --git a/routers/web/org/members.go b/routers/web/org/members.go
index 8da0f0b9fd01..fae8b48128be 100644
--- a/routers/web/org/members.go
+++ b/routers/web/org/members.go
@@ -101,9 +101,7 @@ func MembersAction(ctx *context.Context) {
err = models.RemoveOrgUser(org.ID, uid)
if organization.IsErrLastOrgOwner(err) {
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": ctx.Org.OrgLink + "/members",
- })
+ ctx.JSONRedirect(ctx.Org.OrgLink + "/members")
return
}
case "leave":
@@ -115,9 +113,7 @@ func MembersAction(ctx *context.Context) {
})
} else if organization.IsErrLastOrgOwner(err) {
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": ctx.Org.OrgLink + "/members",
- })
+ ctx.JSONRedirect(ctx.Org.OrgLink + "/members")
} else {
log.Error("RemoveOrgUser(%d,%d): %v", org.ID, ctx.Doer.ID, err)
}
@@ -138,7 +134,5 @@ func MembersAction(ctx *context.Context) {
redirect = setting.AppSubURL + "/"
}
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": redirect,
- })
+ ctx.JSONRedirect(redirect)
}
diff --git a/routers/web/org/org_labels.go b/routers/web/org/org_labels.go
index 08566637a878..a9f9e963d497 100644
--- a/routers/web/org/org_labels.go
+++ b/routers/web/org/org_labels.go
@@ -90,9 +90,7 @@ func DeleteLabel(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("repo.issues.label_deletion_success"))
}
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": ctx.Org.OrgLink + "/settings/labels",
- })
+ ctx.JSONRedirect(ctx.Org.OrgLink + "/settings/labels")
}
// InitializeLabels init labels for an organization
diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go
index 50bb5591e57a..ea6e7dff48ff 100644
--- a/routers/web/org/projects.go
+++ b/routers/web/org/projects.go
@@ -219,9 +219,7 @@ func DeleteProject(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("repo.projects.deletion_success"))
}
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": ctx.ContextUser.HomeLink() + "/-/projects",
- })
+ ctx.JSONRedirect(ctx.ContextUser.HomeLink() + "/-/projects")
}
// RenderEditProject allows a project to be edited
@@ -449,9 +447,7 @@ func UpdateIssueProject(ctx *context.Context) {
}
}
- ctx.JSON(http.StatusOK, map[string]any{
- "ok": true,
- })
+ ctx.JSONOK()
}
// DeleteProjectBoard allows for the deletion of a project board
@@ -497,9 +493,7 @@ func DeleteProjectBoard(ctx *context.Context) {
return
}
- ctx.JSON(http.StatusOK, map[string]any{
- "ok": true,
- })
+ ctx.JSONOK()
}
// AddBoardToProjectPost allows a new board to be added to a project.
@@ -526,9 +520,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
return
}
- ctx.JSON(http.StatusOK, map[string]any{
- "ok": true,
- })
+ ctx.JSONOK()
}
// CheckProjectBoardChangePermissions check permission
@@ -594,9 +586,7 @@ func EditProjectBoard(ctx *context.Context) {
return
}
- ctx.JSON(http.StatusOK, map[string]any{
- "ok": true,
- })
+ ctx.JSONOK()
}
// SetDefaultProjectBoard set default board for uncategorized issues/pulls
@@ -611,9 +601,7 @@ func SetDefaultProjectBoard(ctx *context.Context) {
return
}
- ctx.JSON(http.StatusOK, map[string]any{
- "ok": true,
- })
+ ctx.JSONOK()
}
// UnsetDefaultProjectBoard unset default board for uncategorized issues/pulls
@@ -628,9 +616,7 @@ func UnsetDefaultProjectBoard(ctx *context.Context) {
return
}
- ctx.JSON(http.StatusOK, map[string]any{
- "ok": true,
- })
+ ctx.JSONOK()
}
// MoveIssues moves or keeps issues in a column and sorts them inside that column
@@ -730,7 +716,5 @@ func MoveIssues(ctx *context.Context) {
return
}
- ctx.JSON(http.StatusOK, map[string]any{
- "ok": true,
- })
+ ctx.JSONOK()
}
diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go
index f63b2e9f064e..5ae61c79befe 100644
--- a/routers/web/org/setting.go
+++ b/routers/web/org/setting.go
@@ -219,9 +219,7 @@ func DeleteWebhook(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
}
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": ctx.Org.OrgLink + "/settings/hooks",
- })
+ ctx.JSONRedirect(ctx.Org.OrgLink + "/settings/hooks")
}
// Labels render organization labels page
diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go
index aefadaf809e8..196d3e9bf040 100644
--- a/routers/web/org/teams.go
+++ b/routers/web/org/teams.go
@@ -256,9 +256,7 @@ func TeamsRepoAction(ctx *context.Context) {
}
if action == "addall" || action == "removeall" {
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName) + "/repositories",
- })
+ ctx.JSONRedirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName) + "/repositories")
return
}
ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName) + "/repositories")
@@ -530,9 +528,7 @@ func DeleteTeam(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("org.teams.delete_team_success"))
}
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": ctx.Org.OrgLink + "/teams",
- })
+ ctx.JSONRedirect(ctx.Org.OrgLink + "/teams")
}
// TeamInvite renders the team invite page
diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go
index 999104d7872d..d71d555bc2b5 100644
--- a/routers/web/repo/branch.go
+++ b/routers/web/repo/branch.go
@@ -162,9 +162,7 @@ func RestoreBranchPost(ctx *context.Context) {
}
func redirect(ctx *context.Context) {
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": ctx.Repo.RepoLink + "/branches?page=" + url.QueryEscape(ctx.FormString("page")),
- })
+ ctx.JSONRedirect(ctx.Repo.RepoLink + "/branches?page=" + url.QueryEscape(ctx.FormString("page")))
}
// CreateBranch creates new branch in repository
diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go
index e88f1139f8b7..5b32591b8914 100644
--- a/routers/web/repo/commit.go
+++ b/routers/web/repo/commit.go
@@ -23,6 +23,7 @@ import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/gitdiff"
+ git_service "code.gitea.io/gitea/services/repository"
)
const (
@@ -255,6 +256,15 @@ func FileHistory(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplCommits)
}
+func LoadBranchesAndTags(ctx *context.Context) {
+ response, err := git_service.LoadBranchesAndTags(ctx, ctx.Repo, ctx.Params("sha"))
+ if err == nil {
+ ctx.JSON(http.StatusOK, response)
+ return
+ }
+ ctx.NotFoundOrServerError(fmt.Sprintf("could not load branches and tags the commit %s belongs to", ctx.Params("sha")), git.IsErrNotExist, err)
+}
+
// Diff show different from current commit to previous commit
func Diff(ctx *context.Context) {
ctx.Data["PageIsDiff"] = true
@@ -374,11 +384,6 @@ func Diff(ctx *context.Context) {
return
}
- ctx.Data["TagName"], err = commit.GetTagName()
- if err != nil {
- ctx.ServerError("commit.GetTagName", err)
- return
- }
ctx.HTML(http.StatusOK, tplCommitPage)
}
diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go
index b7d159d15808..f243507c3347 100644
--- a/routers/web/repo/issue.go
+++ b/routers/web/repo/issue.go
@@ -174,7 +174,11 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti
// 0 means issues with no label
// blank means labels will not be filtered for issues
selectLabels := ctx.FormString("labels")
- if len(selectLabels) > 0 {
+ if selectLabels == "" {
+ ctx.Data["AllLabels"] = true
+ } else if selectLabels == "0" {
+ ctx.Data["NoLabel"] = true
+ } else if len(selectLabels) > 0 {
labelIDs, err = base.StringsToInt64s(strings.Split(selectLabels, ","))
if err != nil {
ctx.ServerError("StringsToInt64s", err)
@@ -2221,9 +2225,7 @@ func UpdateIssueMilestone(ctx *context.Context) {
}
}
- ctx.JSON(http.StatusOK, map[string]any{
- "ok": true,
- })
+ ctx.JSONOK()
}
// UpdateIssueAssignee change issue's or pull's assignee
@@ -2267,9 +2269,7 @@ func UpdateIssueAssignee(ctx *context.Context) {
}
}
}
- ctx.JSON(http.StatusOK, map[string]any{
- "ok": true,
- })
+ ctx.JSONOK()
}
// UpdatePullReviewRequest add or remove review request
@@ -2392,9 +2392,7 @@ func UpdatePullReviewRequest(ctx *context.Context) {
}
}
- ctx.JSON(http.StatusOK, map[string]any{
- "ok": true,
- })
+ ctx.JSONOK()
}
// SearchIssues searches for issues across the repositories that the user has access to
diff --git a/routers/web/repo/issue_label.go b/routers/web/repo/issue_label.go
index af5db83bd5d2..5d326bab5808 100644
--- a/routers/web/repo/issue_label.go
+++ b/routers/web/repo/issue_label.go
@@ -157,9 +157,7 @@ func DeleteLabel(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("repo.issues.label_deletion_success"))
}
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": ctx.Repo.RepoLink + "/labels",
- })
+ ctx.JSONRedirect(ctx.Repo.RepoLink + "/labels")
}
// UpdateIssueLabel change issue's labels
@@ -226,7 +224,5 @@ func UpdateIssueLabel(ctx *context.Context) {
return
}
- ctx.JSON(http.StatusOK, map[string]any{
- "ok": true,
- })
+ ctx.JSONOK()
}
diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go
index 7b20cd98482b..ad355ce5d7d9 100644
--- a/routers/web/repo/milestone.go
+++ b/routers/web/repo/milestone.go
@@ -255,9 +255,7 @@ func DeleteMilestone(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("repo.milestones.deletion_success"))
}
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": ctx.Repo.RepoLink + "/milestones",
- })
+ ctx.JSONRedirect(ctx.Repo.RepoLink + "/milestones")
}
// MilestoneIssuesAndPulls lists all the issues and pull requests of the milestone
diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go
index 1574c90c02ed..b8662e060556 100644
--- a/routers/web/repo/projects.go
+++ b/routers/web/repo/projects.go
@@ -203,9 +203,7 @@ func DeleteProject(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("repo.projects.deletion_success"))
}
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": ctx.Repo.RepoLink + "/projects",
- })
+ ctx.JSONRedirect(ctx.Repo.RepoLink + "/projects")
}
// RenderEditProject allows a project to be edited
@@ -397,9 +395,7 @@ func UpdateIssueProject(ctx *context.Context) {
}
}
- ctx.JSON(http.StatusOK, map[string]any{
- "ok": true,
- })
+ ctx.JSONOK()
}
// DeleteProjectBoard allows for the deletion of a project board
@@ -452,9 +448,7 @@ func DeleteProjectBoard(ctx *context.Context) {
return
}
- ctx.JSON(http.StatusOK, map[string]any{
- "ok": true,
- })
+ ctx.JSONOK()
}
// AddBoardToProjectPost allows a new board to be added to a project.
@@ -487,9 +481,7 @@ func AddBoardToProjectPost(ctx *context.Context) {
return
}
- ctx.JSON(http.StatusOK, map[string]any{
- "ok": true,
- })
+ ctx.JSONOK()
}
func checkProjectBoardChangePermissions(ctx *context.Context) (*project_model.Project, *project_model.Board) {
@@ -561,9 +553,7 @@ func EditProjectBoard(ctx *context.Context) {
return
}
- ctx.JSON(http.StatusOK, map[string]any{
- "ok": true,
- })
+ ctx.JSONOK()
}
// SetDefaultProjectBoard set default board for uncategorized issues/pulls
@@ -578,9 +568,7 @@ func SetDefaultProjectBoard(ctx *context.Context) {
return
}
- ctx.JSON(http.StatusOK, map[string]any{
- "ok": true,
- })
+ ctx.JSONOK()
}
// UnSetDefaultProjectBoard unset default board for uncategorized issues/pulls
@@ -595,9 +583,7 @@ func UnSetDefaultProjectBoard(ctx *context.Context) {
return
}
- ctx.JSON(http.StatusOK, map[string]any{
- "ok": true,
- })
+ ctx.JSONOK()
}
// MoveIssues moves or keeps issues in a column and sorts them inside that column
@@ -699,7 +685,5 @@ func MoveIssues(ctx *context.Context) {
return
}
- ctx.JSON(http.StatusOK, map[string]any{
- "ok": true,
- })
+ ctx.JSONOK()
}
diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go
index 5290e25d463f..237e53413f76 100644
--- a/routers/web/repo/pull.go
+++ b/routers/web/repo/pull.go
@@ -1423,9 +1423,7 @@ func CleanUpPullRequest(ctx *context.Context) {
}
defer func() {
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": issue.Link(),
- })
+ ctx.JSONRedirect(issue.Link())
}()
// Check if branch has no new commits
diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go
index f7c962d1ae8f..c2271750c4d1 100644
--- a/routers/web/repo/pull_review.go
+++ b/routers/web/repo/pull_review.go
@@ -156,9 +156,7 @@ func UpdateResolveConversation(ctx *context.Context) {
renderConversation(ctx, comment)
return
}
- ctx.JSON(http.StatusOK, map[string]any{
- "ok": true,
- })
+ ctx.JSONOK()
}
func renderConversation(ctx *context.Context, comment *issues_model.Comment) {
diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go
index 3d991384e56a..957cf56972ce 100644
--- a/routers/web/repo/release.go
+++ b/routers/web/repo/release.go
@@ -628,13 +628,9 @@ func deleteReleaseOrTag(ctx *context.Context, isDelTag bool) {
}
if isDelTag {
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": ctx.Repo.RepoLink + "/tags",
- })
+ ctx.JSONRedirect(ctx.Repo.RepoLink + "/tags")
return
}
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": ctx.Repo.RepoLink + "/releases",
- })
+ ctx.JSONRedirect(ctx.Repo.RepoLink + "/releases")
}
diff --git a/routers/web/repo/setting/collaboration.go b/routers/web/repo/setting/collaboration.go
index 8f2d30686207..b708422cbd26 100644
--- a/routers/web/repo/setting/collaboration.go
+++ b/routers/web/repo/setting/collaboration.go
@@ -133,9 +133,7 @@ func DeleteCollaboration(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("repo.settings.remove_collaborator_success"))
}
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": ctx.Repo.RepoLink + "/settings/collaboration",
- })
+ ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/collaboration")
}
// AddTeamPost response for adding a team to a repository
@@ -204,7 +202,5 @@ func DeleteTeam(ctx *context.Context) {
}
ctx.Flash.Success(ctx.Tr("repo.settings.remove_team_success"))
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": ctx.Repo.RepoLink + "/settings/collaboration",
- })
+ ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/collaboration")
}
diff --git a/routers/web/repo/setting/deploy_key.go b/routers/web/repo/setting/deploy_key.go
index d08c51f5e5c3..577706d45441 100644
--- a/routers/web/repo/setting/deploy_key.go
+++ b/routers/web/repo/setting/deploy_key.go
@@ -105,7 +105,5 @@ func DeleteDeployKey(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("repo.settings.deploy_key_deletion_success"))
}
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": ctx.Repo.RepoLink + "/settings/keys",
- })
+ ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/keys")
}
diff --git a/routers/web/repo/setting/protected_branch.go b/routers/web/repo/setting/protected_branch.go
index cf59e747d84f..5bfdb8f515ba 100644
--- a/routers/web/repo/setting/protected_branch.go
+++ b/routers/web/repo/setting/protected_branch.go
@@ -318,41 +318,31 @@ func DeleteProtectedBranchRulePost(ctx *context.Context) {
ruleID := ctx.ParamsInt64("id")
if ruleID <= 0 {
ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID)))
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
- })
+ ctx.JSONRedirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
return
}
rule, err := git_model.GetProtectedBranchRuleByID(ctx, ctx.Repo.Repository.ID, ruleID)
if err != nil {
ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID)))
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
- })
+ ctx.JSONRedirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
return
}
if rule == nil {
ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID)))
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
- })
+ ctx.JSONRedirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
return
}
if err := git_model.DeleteProtectedBranch(ctx, ctx.Repo.Repository.ID, ruleID); err != nil {
ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", rule.RuleName))
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
- })
+ ctx.JSONRedirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
return
}
ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success", rule.RuleName))
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink),
- })
+ ctx.JSONRedirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink))
}
// RenameBranchPost responses for rename a branch
diff --git a/routers/web/repo/setting/webhook.go b/routers/web/repo/setting/webhook.go
index d85d5c8b07ff..5c4e1d47d09a 100644
--- a/routers/web/repo/setting/webhook.go
+++ b/routers/web/repo/setting/webhook.go
@@ -729,7 +729,5 @@ func DeleteWebhook(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
}
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": ctx.Repo.RepoLink + "/settings/hooks",
- })
+ ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/hooks")
}
diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go
index 4773e25c7066..e3c187c33bfd 100644
--- a/routers/web/repo/wiki.go
+++ b/routers/web/repo/wiki.go
@@ -790,7 +790,5 @@ func DeleteWikiPagePost(ctx *context.Context) {
notification.NotifyDeleteWikiPage(ctx, ctx.Doer, ctx.Repo.Repository, string(wikiName))
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": ctx.Repo.RepoLink + "/wiki/",
- })
+ ctx.JSONRedirect(ctx.Repo.RepoLink + "/wiki/")
}
diff --git a/routers/web/shared/actions/runners.go b/routers/web/shared/actions/runners.go
index 21e5a90d8f20..eb84cc4a2ef4 100644
--- a/routers/web/shared/actions/runners.go
+++ b/routers/web/shared/actions/runners.go
@@ -5,7 +5,6 @@ package actions
import (
"errors"
- "net/http"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
@@ -160,9 +159,7 @@ func RunnerDeletePost(ctx *context.Context, runnerID int64,
log.Warn("DeleteRunnerPost.UpdateRunner failed: %v, url: %s", err, ctx.Req.URL)
ctx.Flash.Warning(ctx.Tr("actions.runners.delete_runner_failed"))
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": failedRedirectTo,
- })
+ ctx.JSONRedirect(failedRedirectTo)
return
}
@@ -170,7 +167,5 @@ func RunnerDeletePost(ctx *context.Context, runnerID int64,
ctx.Flash.Success(ctx.Tr("actions.runners.delete_runner_success"))
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": successRedirectTo,
- })
+ ctx.JSONRedirect(successRedirectTo)
}
diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go
index 532f0d8e3912..ecb846e91bb3 100644
--- a/routers/web/user/setting/account.go
+++ b/routers/web/user/setting/account.go
@@ -227,9 +227,7 @@ func DeleteEmail(ctx *context.Context) {
log.Trace("Email address deleted: %s", ctx.Doer.Name)
ctx.Flash.Success(ctx.Tr("settings.email_deletion_success"))
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": setting.AppSubURL + "/user/settings/account",
- })
+ ctx.JSONRedirect(setting.AppSubURL + "/user/settings/account")
}
// DeleteAccount render user suicide page and response for delete user himself
diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go
index 81209376960d..088aba38b692 100644
--- a/routers/web/user/setting/applications.go
+++ b/routers/web/user/setting/applications.go
@@ -83,9 +83,7 @@ func DeleteApplication(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("settings.delete_token_success"))
}
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": setting.AppSubURL + "/user/settings/applications",
- })
+ ctx.JSONRedirect(setting.AppSubURL + "/user/settings/applications")
}
func loadApplicationsData(ctx *context.Context) {
diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go
index d9412cae7cae..2336c04bbed2 100644
--- a/routers/web/user/setting/keys.go
+++ b/routers/web/user/setting/keys.go
@@ -256,9 +256,7 @@ func DeleteKey(ctx *context.Context) {
ctx.Flash.Warning("Function not implemented")
ctx.Redirect(setting.AppSubURL + "/user/settings/keys")
}
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": setting.AppSubURL + "/user/settings/keys",
- })
+ ctx.JSONRedirect(setting.AppSubURL + "/user/settings/keys")
}
func loadKeysData(ctx *context.Context) {
diff --git a/routers/web/user/setting/oauth2_common.go b/routers/web/user/setting/oauth2_common.go
index 923ce4b4367b..641cc1fd9ffa 100644
--- a/routers/web/user/setting/oauth2_common.go
+++ b/routers/web/user/setting/oauth2_common.go
@@ -138,7 +138,7 @@ func (oa *OAuth2CommonHandlers) DeleteApp(ctx *context.Context) {
}
ctx.Flash.Success(ctx.Tr("settings.remove_oauth2_application_success"))
- ctx.JSON(http.StatusOK, map[string]any{"redirect": oa.BasePathList})
+ ctx.JSONRedirect(oa.BasePathList)
}
// RevokeGrant revokes the grant
@@ -149,5 +149,5 @@ func (oa *OAuth2CommonHandlers) RevokeGrant(ctx *context.Context) {
}
ctx.Flash.Success(ctx.Tr("settings.revoke_oauth2_grant_success"))
- ctx.JSON(http.StatusOK, map[string]any{"redirect": oa.BasePathList})
+ ctx.JSONRedirect(oa.BasePathList)
}
diff --git a/routers/web/user/setting/security/openid.go b/routers/web/user/setting/security/openid.go
index f4133f391602..b5509f570fd6 100644
--- a/routers/web/user/setting/security/openid.go
+++ b/routers/web/user/setting/security/openid.go
@@ -112,9 +112,7 @@ func DeleteOpenID(ctx *context.Context) {
log.Trace("OpenID address deleted: %s", ctx.Doer.Name)
ctx.Flash.Success(ctx.Tr("settings.openid_deletion_success"))
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": setting.AppSubURL + "/user/settings/security",
- })
+ ctx.JSONRedirect(setting.AppSubURL + "/user/settings/security")
}
// ToggleOpenIDVisibility response for toggle visibility of user's openid
diff --git a/routers/web/user/setting/security/security.go b/routers/web/user/setting/security/security.go
index cc5f817a9d42..dae9bf950dce 100644
--- a/routers/web/user/setting/security/security.go
+++ b/routers/web/user/setting/security/security.go
@@ -48,9 +48,7 @@ func DeleteAccountLink(ctx *context.Context) {
}
}
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": setting.AppSubURL + "/user/settings/security",
- })
+ ctx.JSONRedirect(setting.AppSubURL + "/user/settings/security")
}
func loadSecurityData(ctx *context.Context) {
diff --git a/routers/web/user/setting/security/webauthn.go b/routers/web/user/setting/security/webauthn.go
index 89ac184a47e7..990e506d6fdf 100644
--- a/routers/web/user/setting/security/webauthn.go
+++ b/routers/web/user/setting/security/webauthn.go
@@ -116,7 +116,5 @@ func WebauthnDelete(ctx *context.Context) {
ctx.ServerError("GetWebAuthnCredentialByID", err)
return
}
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": setting.AppSubURL + "/user/settings/security",
- })
+ ctx.JSONRedirect(setting.AppSubURL + "/user/settings/security")
}
diff --git a/routers/web/user/setting/webhooks.go b/routers/web/user/setting/webhooks.go
index db03d7b1ed20..04092461fde3 100644
--- a/routers/web/user/setting/webhooks.go
+++ b/routers/web/user/setting/webhooks.go
@@ -42,7 +42,5 @@ func DeleteWebhook(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
}
- ctx.JSON(http.StatusOK, map[string]any{
- "redirect": setting.AppSubURL + "/user/settings/hooks",
- })
+ ctx.JSONRedirect(setting.AppSubURL + "/user/settings/hooks")
}
diff --git a/routers/web/web.go b/routers/web/web.go
index 6d5ccad484fa..0b519614453a 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -374,8 +374,9 @@ func registerRoutes(m *web.Route) {
m.Get("/milestones", reqSignIn, reqMilestonesDashboardPageEnabled, user.Milestones)
// ***** START: User *****
+ // "user/login" doesn't need signOut, then logged-in users can still access this route for redirection purposes by "/user/login?redirec_to=..."
+ m.Get("/user/login", auth.SignIn)
m.Group("/user", func() {
- m.Get("/login", auth.SignIn)
m.Post("/login", web.Bind(forms.SignInForm{}), auth.SignInPost)
m.Group("", func() {
m.Combo("/login/openid").
@@ -1336,6 +1337,7 @@ func registerRoutes(m *web.Route) {
m.Group("", func() {
m.Get("/graph", repo.Graph)
m.Get("/commit/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
+ m.Get("/commit/{sha:([a-f0-9]{7,40})$}/load-branches-and-tags", repo.LoadBranchesAndTags)
m.Get("/cherry-pick/{sha:([a-f0-9]{7,40})$}", repo.SetEditorconfigIfExists, repo.CherryPick)
}, repo.MustBeNotEmpty, context.RepoRef(), reqRepoCodeReader)
diff --git a/services/convert/status.go b/services/convert/status.go
index c7b6450e272d..6cef63c1cdad 100644
--- a/services/convert/status.go
+++ b/services/convert/status.go
@@ -48,7 +48,7 @@ func ToCombinedStatus(ctx context.Context, statuses []*git_model.CommitStatus, r
retStatus.Statuses = make([]*api.CommitStatus, 0, len(statuses))
for _, status := range statuses {
retStatus.Statuses = append(retStatus.Statuses, ToCommitStatus(ctx, status))
- if status.State.NoBetterThan(retStatus.State) {
+ if retStatus.State == "" || status.State.NoBetterThan(retStatus.State) {
retStatus.State = status.State
}
}
diff --git a/services/repository/commit.go b/services/repository/commit.go
new file mode 100644
index 000000000000..2497910a838d
--- /dev/null
+++ b/services/repository/commit.go
@@ -0,0 +1,55 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repository
+
+import (
+ "context"
+ "fmt"
+
+ gitea_ctx "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/util"
+)
+
+type ContainedLinks struct { // TODO: better name?
+ Branches []*namedLink `json:"branches"`
+ Tags []*namedLink `json:"tags"`
+ DefaultBranch string `json:"default_branch"`
+}
+
+type namedLink struct { // TODO: better name?
+ Name string `json:"name"`
+ WebLink string `json:"web_link"`
+}
+
+// LoadBranchesAndTags creates a new repository branch
+func LoadBranchesAndTags(ctx context.Context, baseRepo *gitea_ctx.Repository, commitSHA string) (*ContainedLinks, error) {
+ containedTags, err := baseRepo.GitRepo.ListOccurrences(ctx, "tag", commitSHA)
+ if err != nil {
+ return nil, fmt.Errorf("encountered a problem while querying %s: %w", "tags", err)
+ }
+ containedBranches, err := baseRepo.GitRepo.ListOccurrences(ctx, "branch", commitSHA)
+ if err != nil {
+ return nil, fmt.Errorf("encountered a problem while querying %s: %w", "branches", err)
+ }
+
+ result := &ContainedLinks{
+ DefaultBranch: baseRepo.Repository.DefaultBranch,
+ Branches: make([]*namedLink, 0, len(containedBranches)),
+ Tags: make([]*namedLink, 0, len(containedTags)),
+ }
+ for _, tag := range containedTags {
+ // TODO: Use a common method to get the link to a branch/tag instead of hard-coding it here
+ result.Tags = append(result.Tags, &namedLink{
+ Name: tag,
+ WebLink: fmt.Sprintf("%s/src/tag/%s", baseRepo.RepoLink, util.PathEscapeSegments(tag)),
+ })
+ }
+ for _, branch := range containedBranches {
+ result.Branches = append(result.Branches, &namedLink{
+ Name: branch,
+ WebLink: fmt.Sprintf("%s/src/branch/%s", baseRepo.RepoLink, util.PathEscapeSegments(branch)),
+ })
+ }
+ return result, nil
+}
diff --git a/services/repository/lfs.go b/services/repository/lfs.go
index 0bd4d53a5c4d..8e654b6f13bc 100644
--- a/services/repository/lfs.go
+++ b/services/repository/lfs.go
@@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/timeutil"
)
// GarbageCollectLFSMetaObjectsOptions provides options for GarbageCollectLFSMetaObjects function
@@ -120,8 +121,8 @@ func GarbageCollectLFSMetaObjectsForRepo(ctx context.Context, repo *repo_model.R
//
// It is likely that a week is potentially excessive but it should definitely be enough that any
// unassociated LFS object is genuinely unassociated.
- OlderThan: opts.OlderThan,
- UpdatedLessRecentlyThan: opts.UpdatedLessRecentlyThan,
+ OlderThan: timeutil.TimeStamp(opts.OlderThan.Unix()),
+ UpdatedLessRecentlyThan: timeutil.TimeStamp(opts.UpdatedLessRecentlyThan.Unix()),
OrderByUpdated: true,
LoopFunctionAlwaysUpdates: true,
})
diff --git a/services/repository/lfs_test.go b/services/repository/lfs_test.go
new file mode 100644
index 000000000000..e88befdfefe9
--- /dev/null
+++ b/services/repository/lfs_test.go
@@ -0,0 +1,64 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repository
+
+import (
+ "bytes"
+ "context"
+ "testing"
+ "time"
+
+ "code.gitea.io/gitea/models/db"
+ git_model "code.gitea.io/gitea/models/git"
+ repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/models/unittest"
+ "code.gitea.io/gitea/modules/lfs"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/storage"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGarbageCollectLFSMetaObjects(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ setting.LFS.StartServer = true
+ err := storage.Init()
+ assert.NoError(t, err)
+
+ repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "repo1")
+ assert.NoError(t, err)
+
+ // add lfs object
+ lfsContent := []byte("gitea1")
+ lfsOid := storeObjectInRepo(t, repo.ID, &lfsContent)
+
+ // gc
+ err = GarbageCollectLFSMetaObjects(context.Background(), GarbageCollectLFSMetaObjectsOptions{
+ AutoFix: true,
+ OlderThan: time.Now().Add(7 * 24 * time.Hour).Add(5 * 24 * time.Hour),
+ UpdatedLessRecentlyThan: time.Now().Add(7 * 24 * time.Hour).Add(3 * 24 * time.Hour),
+ })
+ assert.NoError(t, err)
+
+ // lfs meta has been deleted
+ _, err = git_model.GetLFSMetaObjectByOid(db.DefaultContext, repo.ID, lfsOid)
+ assert.ErrorIs(t, err, git_model.ErrLFSObjectNotExist)
+}
+
+func storeObjectInRepo(t *testing.T, repositoryID int64, content *[]byte) string {
+ pointer, err := lfs.GeneratePointer(bytes.NewReader(*content))
+ assert.NoError(t, err)
+
+ _, err = git_model.NewLFSMetaObject(db.DefaultContext, &git_model.LFSMetaObject{Pointer: pointer, RepositoryID: repositoryID})
+ assert.NoError(t, err)
+ contentStore := lfs.NewContentStore()
+ exist, err := contentStore.Exists(pointer)
+ assert.NoError(t, err)
+ if !exist {
+ err := contentStore.Put(pointer, bytes.NewReader(*content))
+ assert.NoError(t, err)
+ }
+ return pointer.Oid
+}
diff --git a/templates/repo/branch/list.tmpl b/templates/repo/branch/list.tmpl
index 297427eeebd5..b32e8cace774 100644
--- a/templates/repo/branch/list.tmpl
+++ b/templates/repo/branch/list.tmpl
@@ -56,7 +56,7 @@