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 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 @@ +
    +
    +
    {{.locale.Tr "repo.commit.contained_in"}}
    +
    +
    {{svg "octicon-git-branch"}}
    +
    +
    +
    +
    {{svg "octicon-tag"}}
    +
    +
    +
    + diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl index e4aad30fa7cf..bd33a304435a 100644 --- a/templates/repo/commit_page.tmpl +++ b/templates/repo/commit_page.tmpl @@ -137,12 +137,7 @@ {{if IsMultilineCommitMessage .Commit.Message}}
    {{RenderCommitBody $.Context .Commit.Message $.RepoLink $.Repository.ComposeMetas}}
    {{end}} - {{if .BranchName}} - {{svg "octicon-git-branch" 16 "gt-mr-2"}}{{.BranchName}} - {{end}} - {{if .TagName}} - {{svg "octicon-tag" 16 "gt-mr-2"}}{{.TagName}} - {{end}} + {{template "repo/commit_load_branches_and_tags" .}}
    diff --git a/templates/repo/commits_list.tmpl b/templates/repo/commits_list.tmpl index 6768bcb5137f..ef9d0654566f 100644 --- a/templates/repo/commits_list.tmpl +++ b/templates/repo/commits_list.tmpl @@ -70,7 +70,7 @@ {{end}} {{if IsMultilineCommitMessage .Message}} - + {{end}} {{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses "root" $}} {{if IsMultilineCommitMessage .Message}} diff --git a/templates/repo/commits_list_small.tmpl b/templates/repo/commits_list_small.tmpl index 6bbc19529f94..57c9fd17ef77 100644 --- a/templates/repo/commits_list_small.tmpl +++ b/templates/repo/commits_list_small.tmpl @@ -40,7 +40,7 @@ {{RenderCommitMessageLinkSubject $.root.Context .Message ($.comment.Issue.PullRequest.BaseRepo.Link|Escape) $commitLink $.comment.Issue.PullRequest.BaseRepo.ComposeMetas}} {{if IsMultilineCommitMessage .Message}} - + {{end}} {{if IsMultilineCommitMessage .Message}}
    {{RenderCommitBody $.root.Context .Message ($.comment.Issue.PullRequest.BaseRepo.Link|Escape) $.comment.Issue.PullRequest.BaseRepo.ComposeMetas}}
    diff --git a/templates/repo/issue/filters.tmpl b/templates/repo/issue/filters.tmpl index edc483d7529d..b75d98ed6f39 100644 --- a/templates/repo/issue/filters.tmpl +++ b/templates/repo/issue/filters.tmpl @@ -20,8 +20,8 @@
    {{.locale.Tr "repo.issues.filter_label_exclude" | Safe}}
    - {{.locale.Tr "repo.issues.filter_label_select_no_label"}} - {{.locale.Tr "repo.issues.filter_label_no_select"}} + {{.locale.Tr "repo.issues.filter_label_no_select"}} + {{.locale.Tr "repo.issues.filter_label_select_no_label"}} {{$previousExclusiveScope := "_no_scope"}} {{range .Labels}} {{$exclusiveScope := .ExclusiveScope}} @@ -84,8 +84,8 @@ {{svg "octicon-search" 16}}
    - {{.locale.Tr "repo.issues.filter_project_all"}} - {{.locale.Tr "repo.issues.filter_project_none"}} + {{.locale.Tr "repo.issues.filter_project_all"}} + {{.locale.Tr "repo.issues.filter_project_none"}} {{if .OpenProjects}}
    @@ -141,8 +141,8 @@ {{svg "octicon-search" 16}}
    - {{.locale.Tr "repo.issues.filter_assginee_no_assignee"}} - {{.locale.Tr "repo.issues.filter_assginee_no_select"}} + {{.locale.Tr "repo.issues.filter_assginee_no_select"}} + {{.locale.Tr "repo.issues.filter_assginee_no_assignee"}}
    {{range .Assignees}} diff --git a/templates/repo/settings/lfs.tmpl b/templates/repo/settings/lfs.tmpl index 0d3a618e8d9e..c4c27dce6429 100644 --- a/templates/repo/settings/lfs.tmpl +++ b/templates/repo/settings/lfs.tmpl @@ -20,8 +20,8 @@ {{TimeSince .CreatedUnix.AsTime $.locale}} {{$.locale.Tr "repo.settings.lfs_findcommits"}} - diff --git a/templates/repo/settings/lfs_pointers.tmpl b/templates/repo/settings/lfs_pointers.tmpl index 0fffdadc3f36..b6b58a37720a 100644 --- a/templates/repo/settings/lfs_pointers.tmpl +++ b/templates/repo/settings/lfs_pointers.tmpl @@ -24,7 +24,7 @@ {{.locale.Tr "repo.settings.lfs_pointers.oid"}} {{.locale.Tr "repo.settings.lfs_pointers.inRepo"}} {{.locale.Tr "repo.settings.lfs_pointers.exists"}} - {{.locale.Tr "repo.settings.lfs_pointers.accessible"}} + {{.locale.Tr "repo.settings.lfs_pointers.accessible"}} diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl index 13b4d3d3d322..3eabf9f181e5 100644 --- a/templates/repo/view_list.tmpl +++ b/templates/repo/view_list.tmpl @@ -28,7 +28,7 @@ {{$commitLink:= printf "%s/commit/%s" .RepoLink (PathEscape .LatestCommit.ID.String)}} {{RenderCommitMessageLinkSubject $.Context .LatestCommit.Message $.RepoLink $commitLink $.Repository.ComposeMetas}} {{if IsMultilineCommitMessage .LatestCommit.Message}} - +
    {{RenderCommitBody $.Context .LatestCommit.Message $.RepoLink $.Repository.ComposeMetas}}
    {{end}}
    diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 7cb9bac95d38..8cf5332bafc4 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -16514,7 +16514,7 @@ "x-go-package": "code.gitea.io/gitea/modules/structs" }, "CommitStatusState": { - "description": "CommitStatusState holds the state of a CommitStatus\nIt can be \"pending\", \"success\", \"error\", \"failure\", and \"warning\"", + "description": "CommitStatusState holds the state of a CommitStatus\nIt can be \"pending\", \"success\", \"error\" and \"failure\"", "type": "string", "x-go-package": "code.gitea.io/gitea/modules/structs" }, @@ -17483,10 +17483,12 @@ }, "x-go-name": "Units", "example": [ + "repo.actions", "repo.code", "repo.issues", "repo.ext_issues", "repo.wiki", + "repo.ext_wiki", "repo.pulls", "repo.releases", "repo.projects", @@ -17499,16 +17501,7 @@ "type": "string" }, "x-go-name": "UnitsMap", - "example": { - "repo.code": "read", - "repo.ext_issues": "none", - "repo.ext_wiki": "none", - "repo.issues": "write", - "repo.projects": "none", - "repo.pulls": "owner", - "repo.releases": "none", - "repo.wiki": "admin" - } + "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\"}" } }, "x-go-package": "code.gitea.io/gitea/modules/structs" diff --git a/tests/integration/api_packages_nuget_test.go b/tests/integration/api_packages_nuget_test.go index 3592d64db24b..e4ea92ee1111 100644 --- a/tests/integration/api_packages_nuget_test.go +++ b/tests/integration/api_packages_nuget_test.go @@ -12,6 +12,7 @@ import ( "io" "net/http" "net/http/httptest" + neturl "net/url" "strconv" "testing" "time" @@ -68,10 +69,16 @@ func TestPackageNuGet(t *testing.T) { Content string `xml:",innerxml"` } + type FeedEntryLink struct { + Rel string `xml:"rel,attr"` + Href string `xml:"href,attr"` + } + type FeedResponse struct { - XMLName xml.Name `xml:"feed"` - Entries []*FeedEntry `xml:"entry"` - Count int64 `xml:"count"` + XMLName xml.Name `xml:"feed"` + Links []FeedEntryLink `xml:"link"` + Entries []*FeedEntry `xml:"entry"` + Count int64 `xml:"count"` } user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) @@ -373,6 +380,25 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) }) }) + containsOneNextLink := func(t *testing.T, links []FeedEntryLink) func() bool { + return func() bool { + found := 0 + for _, l := range links { + if l.Rel == "next" { + found++ + u, err := neturl.Parse(l.Href) + assert.NoError(t, err) + q := u.Query() + assert.Contains(t, q, "$skip") + assert.Contains(t, q, "$top") + assert.Equal(t, "1", q.Get("$skip")) + assert.Equal(t, "1", q.Get("$top")) + } + } + return found == 1 + } + } + t.Run("SearchService", func(t *testing.T) { cases := []struct { Query string @@ -393,7 +419,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) defer tests.PrintCurrentTest(t)() for i, c := range cases { - req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?searchTerm='%s'&skip=%d&take=%d", url, c.Query, c.Skip, c.Take)) + req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?searchTerm='%s'&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)) req = AddBasicAuthHeader(req, user.Name) resp := MakeRequest(t, req, http.StatusOK) @@ -403,7 +429,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i) assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i) - req = NewRequest(t, "GET", fmt.Sprintf("%s/Search()/$count?searchTerm='%s'&skip=%d&take=%d", url, c.Query, c.Skip, c.Take)) + req = NewRequest(t, "GET", fmt.Sprintf("%s/Search()/$count?searchTerm='%s'&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take)) req = AddBasicAuthHeader(req, user.Name) resp = MakeRequest(t, req, http.StatusOK) @@ -432,6 +458,17 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) assert.Equal(t, strconv.FormatInt(c.ExpectedTotal, 10), resp.Body.String(), "case %d: unexpected total hits", i) } }) + + t.Run("Next", func(t *testing.T) { + req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?searchTerm='test'&$skip=0&$top=1", url)) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusOK) + + var result FeedResponse + decodeXML(t, resp, &result) + + assert.Condition(t, containsOneNextLink(t, result.Links)) + }) }) t.Run("v3", func(t *testing.T) { @@ -558,7 +595,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) t.Run("v2", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/FindPackagesById()?id='%s'", url, packageName)) + req := NewRequest(t, "GET", fmt.Sprintf("%s/FindPackagesById()?id='%s'&$top=1", url, packageName)) req = AddBasicAuthHeader(req, user.Name) resp := MakeRequest(t, req, http.StatusOK) @@ -567,6 +604,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) assert.Len(t, result.Entries, 1) assert.Equal(t, packageVersion, result.Entries[0].Properties.Version) + assert.Condition(t, containsOneNextLink(t, result.Links)) req = NewRequest(t, "GET", fmt.Sprintf("%s/FindPackagesById()/$count?id='%s'", url, packageName)) req = AddBasicAuthHeader(req, user.Name) diff --git a/tests/mssql.ini.tmpl b/tests/mssql.ini.tmpl index 10e70d35fc81..93ade74805b1 100644 --- a/tests/mssql.ini.tmpl +++ b/tests/mssql.ini.tmpl @@ -58,7 +58,7 @@ PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-mssql/data/attachments [mailer] ENABLED = true -MAILER_TYPE = dummy +PROTOCOL = dummy FROM = mssql-{{TEST_TYPE}}-test@gitea.io [service] diff --git a/tests/mysql.ini.tmpl b/tests/mysql.ini.tmpl index 9d6bbd65e6e8..10aa3cabbee8 100644 --- a/tests/mysql.ini.tmpl +++ b/tests/mysql.ini.tmpl @@ -58,7 +58,7 @@ SSH_TRUSTED_USER_CA_KEYS = ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCb4DC1dMFnJ6pXW [mailer] ENABLED = true -MAILER_TYPE = dummy +PROTOCOL = dummy FROM = mysql-{{TEST_TYPE}}-test@gitea.io [service] diff --git a/tests/pgsql.ini.tmpl b/tests/pgsql.ini.tmpl index dd45ab717aae..2b54843c30a6 100644 --- a/tests/pgsql.ini.tmpl +++ b/tests/pgsql.ini.tmpl @@ -59,7 +59,7 @@ PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-pgsql/data/attachments [mailer] ENABLED = true -MAILER_TYPE = dummy +PROTOCOL = dummy FROM = pgsql-{{TEST_TYPE}}-test@gitea.io [service] diff --git a/tests/sqlite.ini.tmpl b/tests/sqlite.ini.tmpl index 969c15698daa..546b27c5f66d 100644 --- a/tests/sqlite.ini.tmpl +++ b/tests/sqlite.ini.tmpl @@ -55,7 +55,7 @@ PATH = tests/{{TEST_TYPE}}/gitea-{{TEST_TYPE}}-sqlite/data/attachments [mailer] ENABLED = true -MAILER_TYPE = dummy +PROTOCOL = dummy FROM = sqlite-{{TEST_TYPE}}-test@gitea.io [service] diff --git a/web_src/css/markup/asciicast.css b/web_src/css/markup/asciicast.css index a52b2ae12e9d..89696bc71058 100644 --- a/web_src/css/markup/asciicast.css +++ b/web_src/css/markup/asciicast.css @@ -3,6 +3,6 @@ height: auto; } -.asciinema-terminal { +.ap-terminal { overflow: hidden !important; } diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css index 2a645e522a17..d89e02c6404f 100644 --- a/web_src/css/markup/content.css +++ b/web_src/css/markup/content.css @@ -556,3 +556,26 @@ border-top-left-radius: 0 !important; border-top-right-radius: 0 !important; } + +.file-view.markup.orgmode li.unchecked::before { + content: "[ ] "; +} + +.file-view.markup.orgmode li.checked::before { + content: "[x] "; +} + +.file-view.markup.orgmode li.indeterminate::before { + content: "[-] "; +} + +/* This is only needed for

    because they are literally acting as paragraphs, + * and thus having an ::before on the same line would force the paragraph to + * move to the next line. This can be avoided by an inline-block display that + * avoids that property while still having the other properties of the block + * display. */ +.file-view.markup.orgmode li.unchecked > p, +.file-view.markup.orgmode li.checked > p, +.file-view.markup.orgmode li.indeterminate > p { + display: inline-block; +} diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index 08cead08c58e..2c4273f2f2ce 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -74,7 +74,7 @@