Compare commits

..

2 commits

Author SHA1 Message Date
71bd71e45d nulo: woodpecker CI
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-12-08 16:26:22 -03:00
26341a7fac Dockerfile: rename user to _gitea instead of git 2023-12-08 16:26:10 -03:00
97 changed files with 543 additions and 919 deletions

View file

@ -0,0 +1,45 @@
name: upgrade
on:
pull_request_review:
push:
branches:
- 'forgejo*'
- 'v*/forgejo*'
jobs:
upgrade:
runs-on: docker
container:
image: codeberg.org/forgejo/test_env:main
steps:
- run: apt-get install -y -qq zstd
- name: cache S3 binaries
id: S3
uses: https://code.forgejo.org/actions/cache@v3
with:
path: |
/usr/local/bin/minio
/usr/local/bin/mc
/usr/local/bin/garage
key: S3
- name: skip if S3 cache hit
if: steps.S3.outputs.cache-hit != 'true'
run: echo no hit
- uses: https://code.forgejo.org/actions/checkout@v3
- uses: https://code.forgejo.org/actions/setup-go@v4
with:
go-version: "1.21"
- run: |
git config --add safe.directory '*'
chown -R gitea:gitea . /go
- run: |
su gitea -c 'make deps-backend'
- run: |
script=$(pwd)/.forgejo/upgrades/test-upgrade.sh
$script run dependencies
$script clobber
su gitea -c "$script test_upgrades"

View file

@ -3,7 +3,7 @@ pipeline:
image: docker.io/woodpeckerci/plugin-docker-buildx image: docker.io/woodpeckerci/plugin-docker-buildx
settings: settings:
repo: gitea.nulo.in/nulo/forgejo repo: gitea.nulo.in/nulo/forgejo
tag: v1.21.3-0 tag: v1.21.2-0
registry: https://gitea.nulo.in registry: https://gitea.nulo.in
username: Nulo username: Nulo
password: password:

View file

@ -4,73 +4,6 @@ This changelog goes through all the changes that have been made in each release
without substantial changes to our git log; to see the highlights of what has without substantial changes to our git log; to see the highlights of what has
been added to each release, please refer to the [blog](https://blog.gitea.com). been added to each release, please refer to the [blog](https://blog.gitea.com).
## [1.21.3](https://github.com/go-gitea/gitea/releases/tag/1.21.3) - 2023-12-21
* SECURITY
* Update golang.org/x/crypto (#28519)
* API
* chore(api): support ignore password if login source type is LDAP for creating user API (#28491) (#28525)
* Add endpoint for not implemented Docker auth (#28457) (#28462)
* ENHANCEMENTS
* Add option to disable ambiguous unicode characters detection (#28454) (#28499)
* Refactor SSH clone URL generation code (#28421) (#28480)
* Polyfill SubmitEvent for PaleMoon (#28441) (#28478)
* BUGFIXES
* Fix the issue ref rendering for wiki (#28556) (#28559)
* Fix duplicate ID when deleting repo (#28520) (#28528)
* Only check online runner when detecting matching runners in workflows (#28286) (#28512)
* Initalize stroage for orphaned repository doctor (#28487) (#28490)
* Fix possible nil pointer access (#28428) (#28440)
* Don't show unnecessary citation JS error on UI (#28433) (#28437)
* DOCS
* Update actions document about comparsion as Github Actions (#28560) (#28564)
* Fix documents for "custom/public/assets/" (#28465) (#28467)
* MISC
* Fix inperformant query on retrifing review from database. (#28552) (#28562)
* Improve the prompt for "ssh-keygen sign" (#28509) (#28510)
* Update docs for DISABLE_QUERY_AUTH_TOKEN (#28485) (#28488)
* Fix Chinese translation of config cheat sheet[API] (#28472) (#28473)
* Retry SSH key verification with additional CRLF if it failed (#28392) (#28464)
## [1.21.2](https://github.com/go-gitea/gitea/releases/tag/1.21.2) - 2023-12-12
* SECURITY
* Rebuild with recently released golang version
* Fix missing check (#28406) (#28411)
* Do some missing checks (#28423) (#28432)
* BUGFIXES
* Fix margin in server signed signature verification view (#28379) (#28381)
* Fix object does not exist error when checking citation file (#28314) (#28369)
* Use `filepath` instead of `path` to create SQLite3 database file (#28374) (#28378)
* Fix the runs will not be displayed bug when the main branch have no workflows but other branches have (#28359) (#28365)
* Handle repository.size column being NULL in migration v263 (#28336) (#28363)
* Convert git commit summary to valid UTF8. (#28356) (#28358)
* Fix migration panic due to an empty review comment diff (#28334) (#28362)
* Add `HEAD` support for rpm repo files (#28309) (#28360)
* Fix RPM/Debian signature key creation (#28352) (#28353)
* Keep profile tab when clicking on Language (#28320) (#28331)
* Fix missing issue search index update when changing status (#28325) (#28330)
* Fix wrong link in `protect_branch_name_pattern_desc` (#28313) (#28315)
* Read `previous` info from git blame (#28306) (#28310)
* Ignore "non-existing" errors when getDirectorySize calculates the size (#28276) (#28285)
* Use appSubUrl for OAuth2 callback URL tip (#28266) (#28275)
* Meilisearch: require all query terms to be matched (#28293) (#28296)
* Fix required error for token name (#28267) (#28284)
* Fix issue will be detected as pull request when checking `First-time contributor` (#28237) (#28271)
* Use full width for project boards (#28225) (#28245)
* Increase "version" when update the setting value to a same value as before (#28243) (#28244)
* Also sync DB branches on push if necessary (#28361) (#28403)
* Make gogit Repository.GetBranchNames consistent (#28348) (#28386)
* Recover from panic in cron task (#28409) (#28425)
* Deprecate query string auth tokens (#28390) (#28430)
* ENHANCEMENTS
* Improve doctor cli behavior (#28422) (#28424)
* Fix margin in server signed signature verification view (#28379) (#28381)
* Refactor template empty checks (#28351) (#28354)
* Read `previous` info from git blame (#28306) (#28310)
* Use full width for project boards (#28225) (#28245)
* Enable system users search via the API (#28013) (#28018)
## [1.21.1](https://github.com/go-gitea/gitea/releases/tag/1.21.1) - 2023-11-26 ## [1.21.1](https://github.com/go-gitea/gitea/releases/tag/1.21.1) - 2023-11-26
* SECURITY * SECURITY

View file

@ -89,7 +89,7 @@ endif
VERSION = ${GITEA_VERSION} VERSION = ${GITEA_VERSION}
# SemVer # SemVer
FORGEJO_VERSION := 6.0.3+0-gitea-1.21.3 FORGEJO_VERSION := 6.0.1+0-gitea-1.21.2
LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" -X "code.gitea.io/gitea/routers/api/forgejo/v1.ForgejoVersion=$(FORGEJO_VERSION)" -X "main.ForgejoVersion=$(FORGEJO_VERSION)" LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(GITEA_VERSION)" -X "main.Tags=$(TAGS)" -X "code.gitea.io/gitea/routers/api/forgejo/v1.ForgejoVersion=$(FORGEJO_VERSION)" -X "main.ForgejoVersion=$(FORGEJO_VERSION)"

View file

@ -1214,9 +1214,6 @@ LEVEL = Info
;; Max size of files to be displayed (default is 8MiB) ;; Max size of files to be displayed (default is 8MiB)
;MAX_DISPLAY_FILE_SIZE = 8388608 ;MAX_DISPLAY_FILE_SIZE = 8388608
;; ;;
;; Detect ambiguous unicode characters in file contents and show warnings on the UI
;AMBIGUOUS_UNICODE_DETECTION = true
;;
;; Whether the email of the user should be shown in the Explore Users page ;; Whether the email of the user should be shown in the Explore Users page
;SHOW_USER_EMAIL = true ;SHOW_USER_EMAIL = true
;; ;;

View file

@ -19,10 +19,10 @@ Some jurisdictions (such as EU), requires certain legal pages (e.g. Privacy Poli
## Getting Pages ## Getting Pages
Gitea source code ships with sample pages, available in `contrib/legal` directory. Copy them to `custom/public/assets/`. For example, to add Privacy Policy: Gitea source code ships with sample pages, available in `contrib/legal` directory. Copy them to `custom/public/`. For example, to add Privacy Policy:
``` ```
wget -O /path/to/custom/public/assets/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample wget -O /path/to/custom/public/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample
``` ```
Now you need to edit the page to meet your requirements. In particular you must change the email addresses, web addresses and references to "Your Gitea Instance" to match your situation. Now you need to edit the page to meet your requirements. In particular you must change the email addresses, web addresses and references to "Your Gitea Instance" to match your situation.

View file

@ -19,10 +19,10 @@ menu:
## 获取页面 ## 获取页面
Gitea 源代码附带了示例页面,位于 `contrib/legal` 目录中。将它们复制到 `custom/public/assets/` 目录下。例如,如果要添加隐私政策: Gitea 源代码附带了示例页面,位于 `contrib/legal` 目录中。将它们复制到 `custom/public/` 目录下。例如,如果要添加隐私政策:
``` ```
wget -O /path/to/custom/public/assets/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample wget -O /path/to/custom/public/privacy.html https://raw.githubusercontent.com/go-gitea/gitea/main/contrib/legal/privacy.html.sample
``` ```
现在,你需要编辑该页面以满足你的需求。特别是,你必须更改电子邮件地址、网址以及与 "Your Gitea Instance" 相关的引用,以匹配你的情况。 现在,你需要编辑该页面以满足你的需求。特别是,你必须更改电子邮件地址、网址以及与 "Your Gitea Instance" 相关的引用,以匹配你的情况。

View file

@ -220,7 +220,6 @@ The following configuration set `Content-Type: application/vnd.android.package-a
- `THEMES`: **auto,gitea,arc-green**: All available themes. Allow users select personalized themes. - `THEMES`: **auto,gitea,arc-green**: All available themes. Allow users select personalized themes.
regardless of the value of `DEFAULT_THEME`. regardless of the value of `DEFAULT_THEME`.
- `MAX_DISPLAY_FILE_SIZE`: **8388608**: Max size of files to be displayed (default is 8MiB) - `MAX_DISPLAY_FILE_SIZE`: **8388608**: Max size of files to be displayed (default is 8MiB)
- `AMBIGUOUS_UNICODE_DETECTION`: **true**: Detect ambiguous unicode characters in file contents and show warnings on the UI
- `REACTIONS`: All available reactions users can choose on issues/prs and comments - `REACTIONS`: All available reactions users can choose on issues/prs and comments
Values can be emoji alias (:smile:) or a unicode emoji. Values can be emoji alias (:smile:) or a unicode emoji.
For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png
@ -574,7 +573,6 @@ And the following unique queues:
- off - do not check password complexity - off - do not check password complexity
- `PASSWORD_CHECK_PWN`: **false**: Check [HaveIBeenPwned](https://haveibeenpwned.com/Passwords) to see if a password has been exposed. - `PASSWORD_CHECK_PWN`: **false**: Check [HaveIBeenPwned](https://haveibeenpwned.com/Passwords) to see if a password has been exposed.
- `SUCCESSFUL_TOKENS_CACHE_SIZE`: **20**: Cache successful token hashes. API tokens are stored in the DB as pbkdf2 hashes however, this means that there is a potentially significant hashing load when there are multiple API operations. This cache will store the successfully hashed tokens in a LRU cache as a balance between performance and security. - `SUCCESSFUL_TOKENS_CACHE_SIZE`: **20**: Cache successful token hashes. API tokens are stored in the DB as pbkdf2 hashes however, this means that there is a potentially significant hashing load when there are multiple API operations. This cache will store the successfully hashed tokens in a LRU cache as a balance between performance and security.
- `DISABLE_QUERY_AUTH_TOKEN`: **false**: Reject API tokens sent in URL query string (Accept Header-based API tokens only). This setting will default to `true` in Gitea 1.23 and be deprecated in Gitea 1.24.
## Camo (`camo`) ## Camo (`camo`)

View file

@ -1040,11 +1040,10 @@ Gitea 创建以下非唯一队列:
## API (`api`) ## API (`api`)
- `ENABLE_SWAGGER`: **true**: 启用API文档接口 (`/api/swagger`, `/api/v1/swagger`, …). True or false。 - `ENABLE_SWAGGER`: **true**: 是否启用swagger路由 (`/api/swagger`, `/api/v1/swagger`, …)。
- `MAX_RESPONSE_ITEMS`: **50**: API分页的最大单页项目数。 - `MAX_RESPONSE_ITEMS`: **50**: 单个页面的最大 Feed.
- `DEFAULT_PAGING_NUM`: **30**: API分页的默认分页数。 - `ENABLE_OPENID_SIGNIN`: **false**: 允许使用OpenID登录当设置为`true`时可以通过 `/user/login` 页面进行OpenID登录。
- `DEFAULT_GIT_TREES_PER_PAGE`: **1000**: Git trees API的默认单页项目数。 - `DISABLE_REGISTRATION`: **false**: 关闭用户注册。
- `DEFAULT_MAX_BLOB_SIZE`: **10485760** (10MiB): blobs API的默认最大文件大小。
## OAuth2 (`oauth2`) ## OAuth2 (`oauth2`)

View file

@ -42,11 +42,11 @@ Gitea 引用 `custom` 目录中的自定义配置文件来覆盖配置、模板
将自定义的公共文件(比如页面和图片)作为 webroot 放在 `custom/public/` 中来让 Gitea 提供这些自定义内容(符号链接将被追踪)。 将自定义的公共文件(比如页面和图片)作为 webroot 放在 `custom/public/` 中来让 Gitea 提供这些自定义内容(符号链接将被追踪)。
举例说明:`image.png` 存放在 `custom/public/assets/`中,那么它可以通过链接 http://gitea.domain.tld/assets/image.png 访问。 举例说明:`image.png` 存放在 `custom/public/`中,那么它可以通过链接 http://gitea.domain.tld/assets/image.png 访问。
## 修改默认头像 ## 修改默认头像
替换以下目录中的 png 图片: `custom/public/assets/img/avatar\_default.png` 替换以下目录中的 png 图片: `custom/public/img/avatar\_default.png`
## 自定义 Gitea 页面 ## 自定义 Gitea 页面

View file

@ -194,7 +194,7 @@ ALLOW_DATA_URI_IMAGES = true
} }
``` ```
将您的样式表添加到自定义目录中,例如 `custom/public/assets/css/my-style-XXXXX.css`,并使用自定义的头文件 `custom/templates/custom/header.tmpl` 进行导入: 将您的样式表添加到自定义目录中,例如 `custom/public/css/my-style-XXXXX.css`,并使用自定义的头文件 `custom/templates/custom/header.tmpl` 进行导入:
```html ```html
<link rel="stylesheet" href="{{AppSubUrl}}/assets/css/my-style-XXXXX.css" /> <link rel="stylesheet" href="{{AppSubUrl}}/assets/css/my-style-XXXXX.css" />

View file

@ -33,7 +33,7 @@ CERT_FILE = cert.pem
KEY_FILE = key.pem KEY_FILE = key.pem
``` ```
请注意,如果您的证书由第三方证书颁发机构签名(即不是自签名的),则 cert.pem 应包含证书链。服务器证书必须是 cert.pem 中的第一个条目,后跟中介(如果有)。不必包含根证书,因为连接客户端必须已经拥有根证书才能建立信任关系。要了解有关配置值的更多信息,请查看 [配置备忘单](administration/config-cheat-sheet#server-server)。 请注意,如果您的证书由第三方证书颁发机构签名(即不是自签名的),则 cert.pem 应包含证书链。服务器证书必须是 cert.pem 中的第一个条目,后跟中介(如果有)。不必包含根证书,因为连接客户端必须已经拥有根证书才能建立信任关系。要了解有关配置值的更多信息,请查看 [配置备忘单](../config-cheat-sheet#server-server)。
对于“CERT_FILE”或“KEY_FILE”字段当文件路径是相对路径时文件路径相对于“GITEA_CUSTOM”环境变量。它也可以是绝对路径。 对于“CERT_FILE”或“KEY_FILE”字段当文件路径是相对路径时文件路径相对于“GITEA_CUSTOM”环境变量。它也可以是绝对路径。

View file

@ -19,7 +19,10 @@ menu:
## Enabling/configuring API access ## Enabling/configuring API access
By default, `ENABLE_SWAGGER` is true, and `MAX_RESPONSE_ITEMS` is set to 50. See [Config Cheat Sheet](administration/config-cheat-sheet.md) for more information. By default, `ENABLE_SWAGGER` is true, and
`MAX_RESPONSE_ITEMS` is set to 50. See [Config Cheat
Sheet](administration/config-cheat-sheet.md) for more
information.
## Authentication ## Authentication

View file

@ -19,7 +19,8 @@ menu:
## 开启/配置 API 访问 ## 开启/配置 API 访问
通常情况下, `ENABLE_SWAGGER` 默认开启并且参数 `MAX_RESPONSE_ITEMS` 默认为 50。您可以从 [Config Cheat Sheet](administration/config-cheat-sheet.md) 中获取更多配置相关信息。 通常情况下, `ENABLE_SWAGGER` 默认开启并且参数 `MAX_RESPONSE_ITEMS` 默认为 50。您可以从 [Config Cheat
Sheet](administration/config-cheat-sheet.md) 中获取更多配置相关信息。
## 通过 API 认证 ## 通过 API 认证

View file

@ -190,7 +190,7 @@ Gitea 目前支持三个官方主题,分别是 `gitea`(亮色)、`arc-gree
假设我们的主题是 `arc-blue`(这是一个真实的主题,可以在[此问题](https://github.com/go-gitea/gitea/issues/6011)中找到) 假设我们的主题是 `arc-blue`(这是一个真实的主题,可以在[此问题](https://github.com/go-gitea/gitea/issues/6011)中找到)
将`.css`文件命名为`theme-arc-blue.css`并将其添加到`custom/public/assets/css`文件夹中 将`.css`文件命名为`theme-arc-blue.css`并将其添加到`custom/public/css`文件夹中
通过将`arc-blue`添加到`app.ini`中的`THEMES`列表中,允许用户使用该主题 通过将`arc-blue`添加到`app.ini`中的`THEMES`列表中,允许用户使用该主题

View file

@ -117,7 +117,7 @@ chmod 770 /etc/gitea
- 使用 `gitea generate secret` 创建 `SECRET_KEY``INTERNAL_TOKEN` - 使用 `gitea generate secret` 创建 `SECRET_KEY``INTERNAL_TOKEN`
- 提供所有必要的密钥 - 提供所有必要的密钥
详情参考 [命令行文档](administration/command-line.md) 中有关 `gitea generate secret` 的内容。 详情参考 [命令行文档](/zh-cn/command-line/) 中有关 `gitea generate secret` 的内容。
### 配置 Gitea 工作路径 ### 配置 Gitea 工作路径
@ -209,6 +209,6 @@ remote: ./hooks/pre-receive.d/gitea: line 2: [...]: No such file or directory
如果您没有使用 Gitea 内置的 SSH 服务器,您还需要通过在管理选项中运行任务 `Update the '.ssh/authorized_keys' file with Gitea SSH keys.` 来重新编写授权密钥文件。 如果您没有使用 Gitea 内置的 SSH 服务器,您还需要通过在管理选项中运行任务 `Update the '.ssh/authorized_keys' file with Gitea SSH keys.` 来重新编写授权密钥文件。
> 更多经验总结,请参考英文版 [Troubleshooting](https://docs.gitea.com/installation/install-from-binary#troubleshooting) > 更多经验总结,请参考英文版 [Troubleshooting](/en-us/install-from-binary/#troubleshooting)
如果从本页中没有找到你需要的内容,请访问 [帮助页面](help/support.md) 如果从本页中没有找到你需要的内容,请访问 [帮助页面](help/support.md)

View file

@ -64,7 +64,7 @@ git checkout v@version@ # or git checkout pr-xyz
- `go` @minGoVersion@ 或更高版本,请参阅 [这里](https://golang.org/dl/) - `go` @minGoVersion@ 或更高版本,请参阅 [这里](https://golang.org/dl/)
- `node` @minNodeVersion@ 或更高版本,并且安装 `npm`, 请参阅 [这里](https://nodejs.org/zh-cn/download/) - `node` @minNodeVersion@ 或更高版本,并且安装 `npm`, 请参阅 [这里](https://nodejs.org/zh-cn/download/)
- `make`, 请参阅 [这里](development/hacking-on-gitea.md) - `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)。

View file

@ -29,10 +29,6 @@ Like `uses: https://github.com/actions/checkout@v3` or `uses: http://your_gitea.
Gitea Actions supports writing actions in Go. Gitea Actions supports writing actions in Go.
See [Creating Go Actions](https://blog.gitea.com/creating-go-actions/). See [Creating Go Actions](https://blog.gitea.com/creating-go-actions/).
### Support the non-standard syntax @yearly, @monthly, @weekly, @daily, @hourly on schedule
Github Actions doesn't support that. https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
## Unsupported workflows syntax ## Unsupported workflows syntax
### `concurrency` ### `concurrency`
@ -114,10 +110,6 @@ It's ignored by Gitea Actions now.
Pre and Post steps don't have their own section in the job log user interface. Pre and Post steps don't have their own section in the job log user interface.
### Services steps
Services steps don't have their own section in the job log user interface.
## Different behavior ## Different behavior
### Downloading actions ### Downloading actions

View file

@ -29,10 +29,6 @@ Gitea Actions支持通过URL绝对路径定义actions这意味着您可以使
Gitea Actions支持使用Go编写Actions。 Gitea Actions支持使用Go编写Actions。
请参阅[创建Go Actions](https://blog.gitea.com/creating-go-actions/)。 请参阅[创建Go Actions](https://blog.gitea.com/creating-go-actions/)。
### 支持非标准的调度语法 @yearly, @monthly, @weekly, @daily, @hourly
Github Actions 不支持这些语法,详见: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule
## 不支持的工作流语法 ## 不支持的工作流语法
### `concurrency` ### `concurrency`
@ -120,10 +116,6 @@ Gitea Actions目前不支持此功能。
预处理和后处理步骤在Job日志用户界面中没有自己的用户界面。 预处理和后处理步骤在Job日志用户界面中没有自己的用户界面。
### 服务步骤
服务步骤在Job日志用户界面中没有自己的用户界面。
## 不一样的行为 ## 不一样的行为
### 下载Actions ### 下载Actions

6
go.mod
View file

@ -103,12 +103,12 @@ require (
github.com/yuin/goldmark v1.5.6 github.com/yuin/goldmark v1.5.6
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
github.com/yuin/goldmark-meta v1.1.0 github.com/yuin/goldmark-meta v1.1.0
golang.org/x/crypto v0.17.0 golang.org/x/crypto v0.14.0
golang.org/x/image v0.13.0 golang.org/x/image v0.13.0
golang.org/x/net v0.17.0 golang.org/x/net v0.17.0
golang.org/x/oauth2 v0.13.0 golang.org/x/oauth2 v0.13.0
golang.org/x/sys v0.15.0 golang.org/x/sys v0.13.0
golang.org/x/text v0.14.0 golang.org/x/text v0.13.0
golang.org/x/tools v0.14.0 golang.org/x/tools v0.14.0
google.golang.org/grpc v1.58.3 google.golang.org/grpc v1.58.3
google.golang.org/protobuf v1.31.0 google.golang.org/protobuf v1.31.0

15
go.sum
View file

@ -1143,8 +1143,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -1336,8 +1336,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -1347,8 +1347,8 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1363,9 +1363,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View file

@ -51,11 +51,6 @@ type ActionRunner struct {
Deleted timeutil.TimeStamp `xorm:"deleted"` Deleted timeutil.TimeStamp `xorm:"deleted"`
} }
const (
RunnerOfflineTime = time.Minute
RunnerIdleTime = 10 * time.Second
)
// BelongsToOwnerName before calling, should guarantee that all attributes are loaded // BelongsToOwnerName before calling, should guarantee that all attributes are loaded
func (r *ActionRunner) BelongsToOwnerName() string { func (r *ActionRunner) BelongsToOwnerName() string {
if r.RepoID != 0 { if r.RepoID != 0 {
@ -81,12 +76,11 @@ func (r *ActionRunner) BelongsToOwnerType() types.OwnerType {
return types.OwnerTypeSystemGlobal return types.OwnerTypeSystemGlobal
} }
// if the logic here changed, you should also modify FindRunnerOptions.ToCond
func (r *ActionRunner) Status() runnerv1.RunnerStatus { func (r *ActionRunner) Status() runnerv1.RunnerStatus {
if time.Since(r.LastOnline.AsTime()) > RunnerOfflineTime { if time.Since(r.LastOnline.AsTime()) > time.Minute {
return runnerv1.RunnerStatus_RUNNER_STATUS_OFFLINE return runnerv1.RunnerStatus_RUNNER_STATUS_OFFLINE
} }
if time.Since(r.LastActive.AsTime()) > RunnerIdleTime { if time.Since(r.LastActive.AsTime()) > 10*time.Second {
return runnerv1.RunnerStatus_RUNNER_STATUS_IDLE return runnerv1.RunnerStatus_RUNNER_STATUS_IDLE
} }
return runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE return runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE
@ -159,7 +153,6 @@ type FindRunnerOptions struct {
OwnerID int64 OwnerID int64
Sort string Sort string
Filter string Filter string
IsOnline util.OptionalBool
WithAvailable bool // not only runners belong to, but also runners can be used WithAvailable bool // not only runners belong to, but also runners can be used
} }
@ -185,12 +178,6 @@ func (opts FindRunnerOptions) toCond() builder.Cond {
if opts.Filter != "" { if opts.Filter != "" {
cond = cond.And(builder.Like{"name", opts.Filter}) cond = cond.And(builder.Like{"name", opts.Filter})
} }
if opts.IsOnline.IsTrue() {
cond = cond.And(builder.Gt{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
} else if opts.IsOnline.IsFalse() {
cond = cond.And(builder.Lte{"last_online": time.Now().Add(-RunnerOfflineTime).Unix()})
}
return cond return cond
} }

View file

@ -29,17 +29,12 @@ func VerifySSHKey(ownerID int64, fingerprint, token, signature string) (string,
return "", ErrKeyNotExist{} return "", ErrKeyNotExist{}
} }
err = sshsig.Verify(bytes.NewBuffer([]byte(token)), []byte(signature), []byte(key.Content), "gitea") if err := sshsig.Verify(bytes.NewBuffer([]byte(token)), []byte(signature), []byte(key.Content), "gitea"); err != nil {
if err != nil {
// edge case for Windows based shells that will add CR LF if piped to ssh-keygen command
// see https://github.com/PowerShell/PowerShell/issues/5974
if sshsig.Verify(bytes.NewBuffer([]byte(token+"\r\n")), []byte(signature), []byte(key.Content), "gitea") != nil {
log.Error("Unable to validate token signature. Error: %v", err) log.Error("Unable to validate token signature. Error: %v", err)
return "", ErrSSHInvalidTokenSignature{ return "", ErrSSHInvalidTokenSignature{
Fingerprint: key.Fingerprint, Fingerprint: key.Fingerprint,
} }
} }
}
key.Verified = true key.Verified = true
if _, err := db.GetEngine(ctx).ID(key.ID).Cols("verified").Update(key); err != nil { if _, err := db.GetEngine(ctx).ID(key.ID).Cols("verified").Update(key); err != nil {

View file

@ -178,15 +178,6 @@ func GetByBean(ctx context.Context, bean any) (bool, error) {
return GetEngine(ctx).Get(bean) return GetEngine(ctx).Get(bean)
} }
func Exist[T any](ctx context.Context, cond builder.Cond) (bool, error) {
if !cond.IsValid() {
return false, ErrConditionRequired{}
}
var bean T
return GetEngine(ctx).Where(cond).NoAutoCondition().Exist(&bean)
}
// DeleteByBean deletes all records according non-empty fields of the bean as conditions. // DeleteByBean deletes all records according non-empty fields of the bean as conditions.
func DeleteByBean(ctx context.Context, bean any) (int64, error) { func DeleteByBean(ctx context.Context, bean any) (int64, error) {
return GetEngine(ctx).Delete(bean) return GetEngine(ctx).Delete(bean)

View file

@ -72,21 +72,3 @@ func (err ErrNotExist) Error() string {
func (err ErrNotExist) Unwrap() error { func (err ErrNotExist) Unwrap() error {
return util.ErrNotExist return util.ErrNotExist
} }
// ErrConditionRequired represents an error which require condition.
type ErrConditionRequired struct{}
// IsErrConditionRequired checks if an error is an ErrConditionRequired
func IsErrConditionRequired(err error) bool {
_, ok := err.(ErrConditionRequired)
return ok
}
func (err ErrConditionRequired) Error() string {
return "condition is required"
}
// Unwrap unwraps this as a ErrNotExist err
func (err ErrConditionRequired) Unwrap() error {
return util.ErrInvalidArgument
}

View file

@ -205,9 +205,10 @@ func DeleteBranches(ctx context.Context, repoID, doerID int64, branchIDs []int64
}) })
} }
// UpdateBranch updates the branch information in the database. // UpdateBranch updates the branch information in the database. If the branch exist, it will update latest commit of this branch information
func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string, commit *git.Commit) (int64, error) { // If it doest not exist, insert a new record into database
return db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branchName). func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string, commit *git.Commit) error {
cnt, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repoID, branchName).
Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted, updated_unix"). Cols("commit_id, commit_message, pusher_id, commit_time, is_deleted, updated_unix").
Update(&Branch{ Update(&Branch{
CommitID: commit.ID.String(), CommitID: commit.ID.String(),
@ -216,6 +217,21 @@ func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string
CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()), CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()),
IsDeleted: false, IsDeleted: false,
}) })
if err != nil {
return err
}
if cnt > 0 {
return nil
}
return db.Insert(ctx, &Branch{
RepoID: repoID,
Name: branchName,
CommitID: commit.ID.String(),
CommitMessage: commit.Summary(),
PusherID: pusherID,
CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()),
})
} }
// AddDeletedBranch adds a deleted branch to the database // AddDeletedBranch adds a deleted branch to the database
@ -292,17 +308,6 @@ func RenameBranch(ctx context.Context, repo *repo_model.Repository, from, to str
sess := db.GetEngine(ctx) sess := db.GetEngine(ctx)
var branch Branch
exist, err := db.GetEngine(ctx).Where("repo_id=? AND name=?", repo.ID, from).Get(&branch)
if err != nil {
return err
} else if !exist || branch.IsDeleted {
return ErrBranchNotExist{
RepoID: repo.ID,
BranchName: from,
}
}
// 1. update branch in database // 1. update branch in database
if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{ if n, err := sess.Where("repo_id=? AND name=?", repo.ID, from).Update(&Branch{
Name: to, Name: to,

View file

@ -73,7 +73,7 @@ type FindBranchOptions struct {
Keyword string Keyword string
} }
func (opts FindBranchOptions) ToConds() builder.Cond { func (opts *FindBranchOptions) Cond() builder.Cond {
cond := builder.NewCond() cond := builder.NewCond()
if opts.RepoID > 0 { if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"repo_id": opts.RepoID}) cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
@ -92,7 +92,7 @@ func (opts FindBranchOptions) ToConds() builder.Cond {
} }
func CountBranches(ctx context.Context, opts FindBranchOptions) (int64, error) { func CountBranches(ctx context.Context, opts FindBranchOptions) (int64, error) {
return db.GetEngine(ctx).Where(opts.ToConds()).Count(&Branch{}) return db.GetEngine(ctx).Where(opts.Cond()).Count(&Branch{})
} }
func orderByBranches(sess *xorm.Session, opts FindBranchOptions) *xorm.Session { func orderByBranches(sess *xorm.Session, opts FindBranchOptions) *xorm.Session {
@ -108,7 +108,7 @@ func orderByBranches(sess *xorm.Session, opts FindBranchOptions) *xorm.Session {
} }
func FindBranches(ctx context.Context, opts FindBranchOptions) (BranchList, error) { func FindBranches(ctx context.Context, opts FindBranchOptions) (BranchList, error) {
sess := db.GetEngine(ctx).Where(opts.ToConds()) sess := db.GetEngine(ctx).Where(opts.Cond())
if opts.PageSize > 0 && !opts.IsListAll() { if opts.PageSize > 0 && !opts.IsListAll() {
sess = db.SetSessionPagination(sess, &opts.ListOptions) sess = db.SetSessionPagination(sess, &opts.ListOptions)
} }
@ -119,7 +119,7 @@ func FindBranches(ctx context.Context, opts FindBranchOptions) (BranchList, erro
} }
func FindBranchNames(ctx context.Context, opts FindBranchOptions) ([]string, error) { func FindBranchNames(ctx context.Context, opts FindBranchOptions) ([]string, error) {
sess := db.GetEngine(ctx).Select("name").Where(opts.ToConds()) sess := db.GetEngine(ctx).Select("name").Where(opts.Cond())
if opts.PageSize > 0 && !opts.IsListAll() { if opts.PageSize > 0 && !opts.IsListAll() {
sess = db.SetSessionPagination(sess, &opts.ListOptions) sess = db.SetSessionPagination(sess, &opts.ListOptions)
} }

View file

@ -37,7 +37,7 @@ func TestAddDeletedBranch(t *testing.T) {
}, },
} }
_, err := git_model.UpdateBranch(db.DefaultContext, repo.ID, secondBranch.PusherID, secondBranch.Name, commit) err := git_model.UpdateBranch(db.DefaultContext, repo.ID, secondBranch.PusherID, secondBranch.Name, commit)
assert.NoError(t, err) assert.NoError(t, err)
} }

View file

@ -461,10 +461,8 @@ func SubmitReview(ctx context.Context, doer *user_model.User, issue *Issue, revi
func GetReviewByIssueIDAndUserID(ctx context.Context, issueID, userID int64) (*Review, error) { func GetReviewByIssueIDAndUserID(ctx context.Context, issueID, userID int64) (*Review, error) {
review := new(Review) review := new(Review)
has, err := db.GetEngine(ctx).Where( has, err := db.GetEngine(ctx).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_id = ? AND original_author_id = 0 AND type in (?, ?, ?))",
builder.In("type", ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest). issueID, userID, ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest).
And(builder.Eq{"issue_id": issueID, "reviewer_id": userID, "original_author_id": 0})).
Desc("id").
Get(review) Get(review)
if err != nil { if err != nil {
return nil, err return nil, err
@ -478,13 +476,13 @@ func GetReviewByIssueIDAndUserID(ctx context.Context, issueID, userID int64) (*R
} }
// GetTeamReviewerByIssueIDAndTeamID get the latest review request of reviewer team for a pull request // GetTeamReviewerByIssueIDAndTeamID get the latest review request of reviewer team for a pull request
func GetTeamReviewerByIssueIDAndTeamID(ctx context.Context, issueID, teamID int64) (*Review, error) { func GetTeamReviewerByIssueIDAndTeamID(ctx context.Context, issueID, teamID int64) (review *Review, err error) {
review := new(Review) review = new(Review)
has, err := db.GetEngine(ctx).Where(builder.Eq{"issue_id": issueID, "reviewer_team_id": teamID}). var has bool
Desc("id"). if has, err = db.GetEngine(ctx).SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id = ? AND reviewer_team_id = ?)",
Get(review) issueID, teamID).
if err != nil { Get(review); err != nil {
return nil, err return nil, err
} }

View file

@ -74,7 +74,7 @@ func GetScheduledMergeByPullID(ctx context.Context, pullID int64) (bool, *AutoMe
return false, nil, err return false, nil, err
} }
doer, err := user_model.GetPossibleUserByID(ctx, scheduledPRM.DoerID) doer, err := user_model.GetUserByID(ctx, scheduledPRM.DoerID)
if err != nil { if err != nil {
return false, nil, err return false, nil, err
} }

View file

@ -47,14 +47,6 @@ func (err ErrUserDoesNotHaveAccessToRepo) Unwrap() error {
return util.ErrPermissionDenied return util.ErrPermissionDenied
} }
type ErrRepoIsArchived struct {
Repo *Repository
}
func (err ErrRepoIsArchived) Error() string {
return fmt.Sprintf("%s is archived", err.Repo.LogString())
}
var ( var (
reservedRepoNames = []string{".", "..", "-"} reservedRepoNames = []string{".", "..", "-"}
reservedRepoPatterns = []string{"*.git", "*.wiki", "*.rss", "*.atom"} reservedRepoPatterns = []string{"*.git", "*.wiki", "*.rss", "*.atom"}
@ -602,23 +594,25 @@ func ComposeHTTPSCloneURL(owner, repo string) string {
func ComposeSSHCloneURL(ownerName, repoName string) string { func ComposeSSHCloneURL(ownerName, repoName string) string {
sshUser := setting.SSH.User sshUser := setting.SSH.User
sshDomain := setting.SSH.Domain
// non-standard port, it must use full URI // if we have a ipv6 literal we need to put brackets around it
if setting.SSH.Port != 22 { // for the git cloning to work.
sshHost := net.JoinHostPort(sshDomain, strconv.Itoa(setting.SSH.Port)) sshDomain := setting.SSH.Domain
return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName)) ip := net.ParseIP(setting.SSH.Domain)
if ip != nil && ip.To4() == nil {
sshDomain = "[" + setting.SSH.Domain + "]"
} }
// for standard port, it can use a shorter URI (without the port) if setting.SSH.Port != 22 {
sshHost := sshDomain return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser,
if ip := net.ParseIP(sshHost); ip != nil && ip.To4() == nil { net.JoinHostPort(setting.SSH.Domain, strconv.Itoa(setting.SSH.Port)),
sshHost = "[" + sshHost + "]" // for IPv6 address, wrap it with brackets url.PathEscape(ownerName),
url.PathEscape(repoName))
} }
if setting.Repository.UseCompatSSHURI { if setting.Repository.UseCompatSSHURI {
return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName)) return fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshDomain, url.PathEscape(ownerName), url.PathEscape(repoName))
} }
return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshHost, url.PathEscape(ownerName), url.PathEscape(repoName)) return fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshDomain, url.PathEscape(ownerName), url.PathEscape(repoName))
} }
func (repo *Repository) cloneLink(isWiki bool) *CloneLink { func (repo *Repository) cloneLink(isWiki bool) *CloneLink {
@ -660,14 +654,6 @@ func (repo *Repository) GetTrustModel() TrustModelType {
return trustModel return trustModel
} }
// MustNotBeArchived returns ErrRepoIsArchived if the repo is archived
func (repo *Repository) MustNotBeArchived() error {
if repo.IsArchived {
return ErrRepoIsArchived{Repo: repo}
}
return nil
}
// __________ .__ __ // __________ .__ __
// \______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__. // \______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__.
// | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | | // | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | |

View file

@ -12,8 +12,6 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -188,32 +186,3 @@ func TestGetRepositoryByURL(t *testing.T) {
test(t, "try.gitea.io:user2/repo2.git") test(t, "try.gitea.io:user2/repo2.git")
}) })
} }
func TestComposeSSHCloneURL(t *testing.T) {
defer test.MockVariableValue(&setting.SSH, setting.SSH)()
defer test.MockVariableValue(&setting.Repository, setting.Repository)()
setting.SSH.User = "git"
// test SSH_DOMAIN
setting.SSH.Domain = "domain"
setting.SSH.Port = 22
setting.Repository.UseCompatSSHURI = false
assert.Equal(t, "git@domain:user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
setting.Repository.UseCompatSSHURI = true
assert.Equal(t, "ssh://git@domain/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
// test SSH_DOMAIN while use non-standard SSH port
setting.SSH.Port = 123
setting.Repository.UseCompatSSHURI = false
assert.Equal(t, "ssh://git@domain:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
setting.Repository.UseCompatSSHURI = true
assert.Equal(t, "ssh://git@domain:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
// test IPv6 SSH_DOMAIN
setting.Repository.UseCompatSSHURI = false
setting.SSH.Domain = "::1"
setting.SSH.Port = 22
assert.Equal(t, "git@[::1]:user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
setting.SSH.Port = 123
assert.Equal(t, "ssh://git@[::1]:123/user/repo.git", repo_model.ComposeSSHCloneURL("user", "repo"))
}

View file

@ -8,12 +8,11 @@
package charset package charset
import ( import (
"html/template" "bufio"
"io" "io"
"strings" "strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/translation"
) )
@ -21,18 +20,20 @@ import (
const RuneNBSP = 0xa0 const RuneNBSP = 0xa0
// EscapeControlHTML escapes the unicode control sequences in a provided html document // EscapeControlHTML escapes the unicode control sequences in a provided html document
func EscapeControlHTML(html template.HTML, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output template.HTML) { func EscapeControlHTML(text string, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output string) {
sb := &strings.Builder{} sb := &strings.Builder{}
escaped, _ = EscapeControlReader(strings.NewReader(string(html)), sb, locale, allowed...) // err has been handled in EscapeControlReader outputStream := &HTMLStreamerWriter{Writer: sb}
return escaped, template.HTML(sb.String()) streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
if err := StreamHTML(strings.NewReader(text), streamer); err != nil {
streamer.escaped.HasError = true
log.Error("Error whilst escaping: %v", err)
}
return streamer.escaped, sb.String()
} }
// EscapeControlReader escapes the unicode control sequences in a provided reader of HTML content and writer in a locale and returns the findings as an EscapeStatus // EscapeControlReaders escapes the unicode control sequences in a provided reader of HTML content and writer in a locale and returns the findings as an EscapeStatus and the escaped []byte
func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, err error) { func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, err error) {
if !setting.UI.AmbiguousUnicodeDetection {
_, err = io.Copy(writer, reader)
return &EscapeStatus{}, err
}
outputStream := &HTMLStreamerWriter{Writer: writer} outputStream := &HTMLStreamerWriter{Writer: writer}
streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer) streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
@ -42,3 +43,41 @@ func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.
} }
return streamer.escaped, err return streamer.escaped, err
} }
// EscapeControlStringReader escapes the unicode control sequences in a provided reader of string content and writer in a locale and returns the findings as an EscapeStatus and the escaped []byte. HTML line breaks are not inserted after every newline by this method.
func EscapeControlStringReader(reader io.Reader, writer io.Writer, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, err error) {
bufRd := bufio.NewReader(reader)
outputStream := &HTMLStreamerWriter{Writer: writer}
streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
for {
line, rdErr := bufRd.ReadString('\n')
if len(line) > 0 {
if err := streamer.Text(line); err != nil {
streamer.escaped.HasError = true
log.Error("Error whilst escaping: %v", err)
return streamer.escaped, err
}
}
if rdErr != nil {
if rdErr != io.EOF {
err = rdErr
}
break
}
}
return streamer.escaped, err
}
// EscapeControlString escapes the unicode control sequences in a provided string and returns the findings as an EscapeStatus and the escaped string
func EscapeControlString(text string, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, output string) {
sb := &strings.Builder{}
outputStream := &HTMLStreamerWriter{Writer: sb}
streamer := NewEscapeStreamer(locale, outputStream, allowed...).(*escapeStreamer)
if err := streamer.Text(text); err != nil {
streamer.escaped.HasError = true
log.Error("Error whilst escaping: %v", err)
}
return streamer.escaped, sb.String()
}

View file

@ -64,7 +64,7 @@ func (e *escapeStreamer) Text(data string) error {
until, next = nextIdxs[0]+pos, nextIdxs[1]+pos until, next = nextIdxs[0]+pos, nextIdxs[1]+pos
} }
// from pos until we know that the runes are not \r\t\n or even ' ' // from pos until until we know that the runes are not \r\t\n or even ' '
runes := make([]rune, 0, next-until) runes := make([]rune, 0, next-until)
positions := make([]int, 0, next-until+1) positions := make([]int, 0, next-until+1)

View file

@ -4,14 +4,11 @@
package charset package charset
import ( import (
"reflect"
"strings" "strings"
"testing" "testing"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/translation"
"github.com/stretchr/testify/assert"
) )
type escapeControlTest struct { type escapeControlTest struct {
@ -135,8 +132,22 @@ then resh (ר), and finally heh (ה) (which should appear leftmost).`,
}, },
} }
func TestEscapeControlString(t *testing.T) {
for _, tt := range escapeControlTests {
t.Run(tt.name, func(t *testing.T) {
status, result := EscapeControlString(tt.text, &translation.MockLocale{})
if !reflect.DeepEqual(*status, tt.status) {
t.Errorf("EscapeControlString() status = %v, wanted= %v", status, tt.status)
}
if result != tt.result {
t.Errorf("EscapeControlString()\nresult= %v,\nwanted= %v", result, tt.result)
}
})
}
}
func TestEscapeControlReader(t *testing.T) { func TestEscapeControlReader(t *testing.T) {
// add some control characters to the tests // lets add some control characters to the tests
tests := make([]escapeControlTest, 0, len(escapeControlTests)*3) tests := make([]escapeControlTest, 0, len(escapeControlTests)*3)
copy(tests, escapeControlTests) copy(tests, escapeControlTests)
@ -158,20 +169,29 @@ func TestEscapeControlReader(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
input := strings.NewReader(tt.text)
output := &strings.Builder{} output := &strings.Builder{}
status, err := EscapeControlReader(strings.NewReader(tt.text), output, &translation.MockLocale{}) status, err := EscapeControlReader(input, output, &translation.MockLocale{})
assert.NoError(t, err) result := output.String()
assert.Equal(t, tt.status, *status) if err != nil {
assert.Equal(t, tt.result, output.String()) t.Errorf("EscapeControlReader(): err = %v", err)
}
if !reflect.DeepEqual(*status, tt.status) {
t.Errorf("EscapeControlReader() status = %v, wanted= %v", status, tt.status)
}
if result != tt.result {
t.Errorf("EscapeControlReader()\nresult= %v,\nwanted= %v", result, tt.result)
}
}) })
} }
} }
func TestSettingAmbiguousUnicodeDetection(t *testing.T) { func TestEscapeControlReader_panic(t *testing.T) {
defer test.MockVariableValue(&setting.UI.AmbiguousUnicodeDetection, true)() bs := make([]byte, 0, 20479)
_, out := EscapeControlHTML("a test", &translation.MockLocale{}) bs = append(bs, 'A')
assert.EqualValues(t, `a<span class="escaped-code-point" data-escaped="[U+00A0]"><span class="char"> </span></span>test`, out) for i := 0; i < 6826; i++ {
setting.UI.AmbiguousUnicodeDetection = false bs = append(bs, []byte("—")...)
_, out = EscapeControlHTML("a test", &translation.MockLocale{}) }
assert.EqualValues(t, `a test`, out) _, _ = EscapeControlString(string(bs), &translation.MockLocale{})
} }

View file

@ -9,7 +9,6 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/storage"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
"xorm.io/builder" "xorm.io/builder"
@ -32,10 +31,6 @@ func countOrphanedRepos(ctx context.Context) (int64, error) {
// deleteOrphanedRepos delete repository where user of owner_id do not exist // deleteOrphanedRepos delete repository where user of owner_id do not exist
func deleteOrphanedRepos(ctx context.Context) (int64, error) { func deleteOrphanedRepos(ctx context.Context) (int64, error) {
if err := storage.Init(); err != nil {
return 0, err
}
batchSize := db.MaxBatchInsertSize("repository") batchSize := db.MaxBatchInsertSize("repository")
e := db.GetEngine(ctx) e := db.GetEngine(ctx)
var deleted int64 var deleted int64

View file

@ -14,6 +14,7 @@ import (
"os/exec" "os/exec"
"strings" "strings"
"time" "time"
"unsafe"
"code.gitea.io/gitea/modules/git/internal" //nolint:depguard // only this file can use the internal type CmdArg, other files and packages should use AddXxx functions "code.gitea.io/gitea/modules/git/internal" //nolint:depguard // only this file can use the internal type CmdArg, other files and packages should use AddXxx functions
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -388,11 +389,15 @@ func (r *runStdError) IsExitCode(code int) bool {
return false return false
} }
func bytesToString(b []byte) string {
return *(*string)(unsafe.Pointer(&b)) // that's what Golang's strings.Builder.String() does (go/src/strings/builder.go)
}
// RunStdString runs the command with options and returns stdout/stderr as string. and store stderr to returned error (err combined with stderr). // RunStdString runs the command with options and returns stdout/stderr as string. and store stderr to returned error (err combined with stderr).
func (c *Command) RunStdString(opts *RunOpts) (stdout, stderr string, runErr RunStdError) { func (c *Command) RunStdString(opts *RunOpts) (stdout, stderr string, runErr RunStdError) {
stdoutBytes, stderrBytes, err := c.RunStdBytes(opts) stdoutBytes, stderrBytes, err := c.RunStdBytes(opts)
stdout = util.UnsafeBytesToString(stdoutBytes) stdout = bytesToString(stdoutBytes)
stderr = util.UnsafeBytesToString(stderrBytes) stderr = bytesToString(stderrBytes)
if err != nil { if err != nil {
return stdout, stderr, &runStdError{err: err, stderr: stderr} return stdout, stderr, &runStdError{err: err, stderr: stderr}
} }
@ -427,7 +432,7 @@ func (c *Command) RunStdBytes(opts *RunOpts) (stdout, stderr []byte, runErr RunS
err := c.Run(newOpts) err := c.Run(newOpts)
stderr = stderrBuf.Bytes() stderr = stderrBuf.Bytes()
if err != nil { if err != nil {
return nil, stderr, &runStdError{err: err, stderr: util.UnsafeBytesToString(stderr)} return nil, stderr, &runStdError{err: err, stderr: bytesToString(stderr)}
} }
// even if there is no err, there could still be some stderr output // even if there is no err, there could still be some stderr output
return stdoutBuf.Bytes(), stderr, nil return stdoutBuf.Bytes(), stderr, nil

View file

@ -9,7 +9,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
gohtml "html" gohtml "html"
"html/template"
"io" "io"
"path/filepath" "path/filepath"
"strings" "strings"
@ -56,7 +55,7 @@ func NewContext() {
} }
// Code returns a HTML version of code string with chroma syntax highlighting classes and the matched lexer name // Code returns a HTML version of code string with chroma syntax highlighting classes and the matched lexer name
func Code(fileName, language, code string) (output template.HTML, lexerName string) { func Code(fileName, language, code string) (string, string) {
NewContext() NewContext()
// diff view newline will be passed as empty, change to literal '\n' so it can be copied // diff view newline will be passed as empty, change to literal '\n' so it can be copied
@ -66,7 +65,7 @@ func Code(fileName, language, code string) (output template.HTML, lexerName stri
} }
if len(code) > sizeLimit { if len(code) > sizeLimit {
return template.HTML(template.HTMLEscapeString(code)), "" return code, ""
} }
var lexer chroma.Lexer var lexer chroma.Lexer
@ -103,11 +102,13 @@ func Code(fileName, language, code string) (output template.HTML, lexerName stri
cache.Add(fileName, lexer) cache.Add(fileName, lexer)
} }
return CodeFromLexer(lexer, code), formatLexerName(lexer.Config().Name) lexerName := formatLexerName(lexer.Config().Name)
return CodeFromLexer(lexer, code), lexerName
} }
// CodeFromLexer returns a HTML version of code string with chroma syntax highlighting classes // CodeFromLexer returns a HTML version of code string with chroma syntax highlighting classes
func CodeFromLexer(lexer chroma.Lexer, code string) template.HTML { func CodeFromLexer(lexer chroma.Lexer, code string) string {
formatter := html.New(html.WithClasses(true), formatter := html.New(html.WithClasses(true),
html.WithLineNumbers(false), html.WithLineNumbers(false),
html.PreventSurroundingPre(true), html.PreventSurroundingPre(true),
@ -119,23 +120,23 @@ func CodeFromLexer(lexer chroma.Lexer, code string) template.HTML {
iterator, err := lexer.Tokenise(nil, code) iterator, err := lexer.Tokenise(nil, code)
if err != nil { if err != nil {
log.Error("Can't tokenize code: %v", err) log.Error("Can't tokenize code: %v", err)
return template.HTML(template.HTMLEscapeString(code)) return code
} }
// style not used for live site but need to pass something // style not used for live site but need to pass something
err = formatter.Format(htmlw, githubStyles, iterator) err = formatter.Format(htmlw, githubStyles, iterator)
if err != nil { if err != nil {
log.Error("Can't format code: %v", err) log.Error("Can't format code: %v", err)
return template.HTML(template.HTMLEscapeString(code)) return code
} }
_ = htmlw.Flush() _ = htmlw.Flush()
// Chroma will add newlines for certain lexers in order to highlight them properly // Chroma will add newlines for certain lexers in order to highlight them properly
// Once highlighted, strip them here, so they don't cause copy/paste trouble in HTML output // Once highlighted, strip them here, so they don't cause copy/paste trouble in HTML output
return template.HTML(strings.TrimSuffix(htmlbuf.String(), "\n")) return strings.TrimSuffix(htmlbuf.String(), "\n")
} }
// File returns a slice of chroma syntax highlighted HTML lines of code and the matched lexer name // File returns a slice of chroma syntax highlighted HTML lines of code and the matched lexer name
func File(fileName, language string, code []byte) ([]template.HTML, string, error) { func File(fileName, language string, code []byte) ([]string, string, error) {
NewContext() NewContext()
if len(code) > sizeLimit { if len(code) > sizeLimit {
@ -182,14 +183,14 @@ func File(fileName, language string, code []byte) ([]template.HTML, string, erro
tokensLines := chroma.SplitTokensIntoLines(iterator.Tokens()) tokensLines := chroma.SplitTokensIntoLines(iterator.Tokens())
htmlBuf := &bytes.Buffer{} htmlBuf := &bytes.Buffer{}
lines := make([]template.HTML, 0, len(tokensLines)) lines := make([]string, 0, len(tokensLines))
for _, tokens := range tokensLines { for _, tokens := range tokensLines {
iterator = chroma.Literator(tokens...) iterator = chroma.Literator(tokens...)
err = formatter.Format(htmlBuf, githubStyles, iterator) err = formatter.Format(htmlBuf, githubStyles, iterator)
if err != nil { if err != nil {
return nil, "", fmt.Errorf("can't format code: %w", err) return nil, "", fmt.Errorf("can't format code: %w", err)
} }
lines = append(lines, template.HTML(htmlBuf.String())) lines = append(lines, htmlBuf.String())
htmlBuf.Reset() htmlBuf.Reset()
} }
@ -197,9 +198,9 @@ func File(fileName, language string, code []byte) ([]template.HTML, string, erro
} }
// PlainText returns non-highlighted HTML for code // PlainText returns non-highlighted HTML for code
func PlainText(code []byte) []template.HTML { func PlainText(code []byte) []string {
r := bufio.NewReader(bytes.NewReader(code)) r := bufio.NewReader(bytes.NewReader(code))
m := make([]template.HTML, 0, bytes.Count(code, []byte{'\n'})+1) m := make([]string, 0, bytes.Count(code, []byte{'\n'})+1)
for { for {
content, err := r.ReadString('\n') content, err := r.ReadString('\n')
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
@ -209,7 +210,7 @@ func PlainText(code []byte) []template.HTML {
if content == "" && err == io.EOF { if content == "" && err == io.EOF {
break break
} }
s := template.HTML(gohtml.EscapeString(content)) s := gohtml.EscapeString(content)
m = append(m, s) m = append(m, s)
} }
return m return m

View file

@ -4,36 +4,21 @@
package highlight package highlight
import ( import (
"html/template"
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func lines(s string) (out []template.HTML) { func lines(s string) []string {
// "" => [], "a" => ["a"], "a\n" => ["a\n"], "a\nb" => ["a\n", "b"] (each line always includes EOL "\n" if it exists) return strings.Split(strings.ReplaceAll(strings.TrimSpace(s), `\n`, "\n"), "\n")
out = make([]template.HTML, 0)
s = strings.ReplaceAll(strings.ReplaceAll(strings.TrimSpace(s), "\n", ""), `\n`, "\n")
for {
if p := strings.IndexByte(s, '\n'); p != -1 {
out = append(out, template.HTML(s[:p+1]))
s = s[p+1:]
} else {
break
}
}
if s != "" {
out = append(out, template.HTML(s))
}
return out
} }
func TestFile(t *testing.T) { func TestFile(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
code string code string
want []template.HTML want []string
lexerName string lexerName string
}{ }{
{ {
@ -114,7 +99,10 @@ c=2
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
out, lexerName, err := File(tt.name, "", []byte(tt.code)) out, lexerName, err := File(tt.name, "", []byte(tt.code))
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, tt.want, out) expected := strings.Join(tt.want, "\n")
actual := strings.Join(out, "\n")
assert.Equal(t, strings.Count(actual, "<span"), strings.Count(actual, "</span>"))
assert.EqualValues(t, expected, actual)
assert.Equal(t, tt.lexerName, lexerName) assert.Equal(t, tt.lexerName, lexerName)
}) })
} }
@ -124,7 +112,7 @@ func TestPlainText(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
code string code string
want []template.HTML want []string
}{ }{
{ {
name: "empty.py", name: "empty.py",
@ -177,7 +165,9 @@ c=2`),
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
out := PlainText([]byte(tt.code)) out := PlainText([]byte(tt.code))
assert.EqualValues(t, tt.want, out) expected := strings.Join(tt.want, "\n")
actual := strings.Join(out, "\n")
assert.EqualValues(t, expected, actual)
}) })
} }
} }

View file

@ -6,7 +6,6 @@ package code
import ( import (
"bytes" "bytes"
"context" "context"
"html/template"
"strings" "strings"
"code.gitea.io/gitea/modules/highlight" "code.gitea.io/gitea/modules/highlight"
@ -23,7 +22,7 @@ type Result struct {
Language string Language string
Color string Color string
LineNumbers []int LineNumbers []int
FormattedLines template.HTML FormattedLines string
} }
type SearchResultLanguages = internal.SearchResultLanguages type SearchResultLanguages = internal.SearchResultLanguages

View file

@ -29,17 +29,12 @@ func CleanValue(value []byte) []byte {
value = bytes.TrimSpace(value) value = bytes.TrimSpace(value)
rs := bytes.Runes(value) rs := bytes.Runes(value)
result := make([]rune, 0, len(rs)) result := make([]rune, 0, len(rs))
needsDash := false
for _, r := range rs { for _, r := range rs {
switch { if unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_' || r == '-' {
case unicode.IsLetter(r) || unicode.IsNumber(r) || r == '_':
if needsDash && len(result) > 0 {
result = append(result, '-')
}
needsDash = false
result = append(result, unicode.ToLower(r)) result = append(result, unicode.ToLower(r))
default: }
needsDash = true if unicode.IsSpace(r) {
result = append(result, '-')
} }
} }
return []byte(string(result)) return []byte(string(result))

View file

@ -1,5 +1,4 @@
// Copyright 2023 The Gitea Authors. All rights reserved. // Copyright 2023 The Gitea Authors. All rights reserved.
// Copyright 2023 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package common package common
@ -16,45 +15,44 @@ func TestCleanValue(t *testing.T) {
}{ }{
// Github behavior test cases // Github behavior test cases
{"", ""}, {"", ""},
{"test.0.1", "test-0-1"}, {"test(0)", "test0"},
{"test(0)", "test-0"}, {"test!1", "test1"},
{"test!1", "test-1"}, {"test:2", "test2"},
{"test:2", "test-2"}, {"test*3", "test3"},
{"test*3", "test-3"}, {"test4", "test4"},
{"test4", "test-4"}, {"test5", "test5"},
{"test5", "test-5"}, {"test*6", "test6"},
{"test*6", "test-6"}, {"test6 a", "test6-a"},
{"test6 a", "test-6-a"}, {"test6 !b", "test6-b"},
{"test6 !b", "test-6-b"}, {"testad # df", "testad--df"},
{"testad # df", "test-ad-df"}, {"testad #23 df 2*/*", "testad-23-df-2"},
{"testad #23 df 2*/*", "test-ad-23-df-2"}, {"testad 23 df 2*/*", "testad-23-df-2"},
{"testad 23 df 2*/*", "test-ad-23-df-2"}, {"testad # 23 df 2*/*", "testad--23-df-2"},
{"testad # 23 df 2*/*", "test-ad-23-df-2"},
{"Anchors in Markdown", "anchors-in-markdown"}, {"Anchors in Markdown", "anchors-in-markdown"},
{"a_b_c", "a_b_c"}, {"a_b_c", "a_b_c"},
{"a-b-c", "a-b-c"}, {"a-b-c", "a-b-c"},
{"a-b-c----", "a-b-c"}, {"a-b-c----", "a-b-c----"},
{"test6a", "test-6a"}, {"test6a", "test6a"},
{"testa6", "test-a6"}, {"testa6", "testa6"},
{"tes a a a a", "tes-a-a-a-a"}, {"tes a a a a", "tes-a-a---a--a"},
{" tes a a a a ", "tes-a-a-a-a"}, {" tes a a a a ", "tes-a-a---a--a"},
{"Header with \"double quotes\"", "header-with-double-quotes"}, {"Header with \"double quotes\"", "header-with-double-quotes"},
{"Placeholder to force scrolling on link's click", "placeholder-to-force-scrolling-on-link-s-click"}, {"Placeholder to force scrolling on link's click", "placeholder-to-force-scrolling-on-links-click"},
{"tes", "tes"}, {"tes", "tes"},
{"tes0", "tes-0"}, {"tes0", "tes0"},
{"tes{0}", "tes-0"}, {"tes{0}", "tes0"},
{"tes[0]", "tes-0"}, {"tes[0]", "tes0"},
{"test【0】", "test-0"}, {"test【0】", "test0"},
{"tes…@a", "tes-a"}, {"tes…@a", "tesa"},
{"tes¥& a", "tes-a"}, {"tes¥& a", "tes-a"},
{"tes= a", "tes-a"}, {"tes= a", "tes-a"},
{"tes|a", "tes-a"}, {"tes|a", "tesa"},
{"tes\\a", "tes-a"}, {"tes\\a", "tesa"},
{"tes/a", "tes-a"}, {"tes/a", "tesa"},
{"a啊啊b", "a啊啊b"}, {"a啊啊b", "a啊啊b"},
{"c🤔🤔d", "c-d"}, {"c🤔🤔d", "cd"},
{"a⚡a", "a-a"}, {"a⚡a", "aa"},
{"e.~f", "e-f"}, {"e.~f", "ef"},
} }
for _, test := range tests { for _, test := range tests {
assert.Equal(t, []byte(test.expect), CleanValue([]byte(test.param)), test.param) assert.Equal(t, []byte(test.expect), CleanValue([]byte(test.param)), test.param)

View file

@ -852,9 +852,7 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
} }
func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
// FIXME: the use of "mode" is quite dirty and hacky, for example: what is a "document"? how should it be rendered? if ctx.Metas == nil || ctx.Metas["mode"] == "document" {
// The "mode" approach should be refactored to some other more clear&reliable way.
if ctx.Metas == nil || (ctx.Metas["mode"] == "document" && !ctx.IsWiki) {
return return
} }
var ( var (

View file

@ -87,7 +87,7 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
} }
lexer = chroma.Coalesce(lexer) lexer = chroma.Coalesce(lexer)
if _, err := w.WriteString(string(highlight.CodeFromLexer(lexer, source))); err != nil { if _, err := w.WriteString(highlight.CodeFromLexer(lexer, source)); err != nil {
return "" return ""
} }
} }

View file

@ -150,7 +150,7 @@ var (
DefaultPrivate: RepoCreatingLastUserVisibility, DefaultPrivate: RepoCreatingLastUserVisibility,
DefaultPushCreatePrivate: true, DefaultPushCreatePrivate: true,
MaxCreationLimit: -1, MaxCreationLimit: -1,
PreferredLicenses: []string{"Apache-2.0", "MIT"}, PreferredLicenses: []string{"Apache License 2.0", "MIT License"},
DisableHTTPGit: false, DisableHTTPGit: false,
AccessControlAllowOrigin: "", AccessControlAllowOrigin: "",
UseCompatSSHURI: false, UseCompatSSHURI: false,

View file

@ -34,8 +34,6 @@ var UI = struct {
SearchRepoDescription bool SearchRepoDescription bool
OnlyShowRelevantRepos bool OnlyShowRelevantRepos bool
AmbiguousUnicodeDetection bool
Notification struct { Notification struct {
MinTimeout time.Duration MinTimeout time.Duration
TimeoutStep time.Duration TimeoutStep time.Duration
@ -83,9 +81,6 @@ var UI = struct {
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`},
CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`, `forgejo`}, CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`, `forgejo`},
CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:", "forgejo": ":forgejo:"}, CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:", "forgejo": ":forgejo:"},
AmbiguousUnicodeDetection: true,
Notification: struct { Notification: struct {
MinTimeout time.Duration MinTimeout time.Duration
TimeoutStep time.Duration TimeoutStep time.Duration

View file

@ -16,7 +16,8 @@ type CreateUserOption struct {
// required: true // required: true
// swagger:strfmt email // swagger:strfmt email
Email string `json:"email" binding:"Required;Email;MaxSize(254)"` Email string `json:"email" binding:"Required;Email;MaxSize(254)"`
Password string `json:"password" binding:"MaxSize(255)"` // required: true
Password string `json:"password" binding:"Required;MaxSize(255)"`
MustChangePassword *bool `json:"must_change_password"` MustChangePassword *bool `json:"must_change_password"`
SendNotify bool `json:"send_notify"` SendNotify bool `json:"send_notify"`
Restricted *bool `json:"restricted"` Restricted *bool `json:"restricted"`

View file

@ -3,7 +3,7 @@
package util package util
import "unsafe" import "github.com/yuin/goldmark/util"
func isSnakeCaseUpper(c byte) bool { func isSnakeCaseUpper(c byte) bool {
return 'A' <= c && c <= 'Z' return 'A' <= c && c <= 'Z'
@ -83,15 +83,5 @@ func ToSnakeCase(input string) string {
} }
} }
} }
return UnsafeBytesToString(res) return util.BytesToReadOnlyString(res)
}
// UnsafeBytesToString uses Go's unsafe package to convert a byte slice to a string.
// TODO: replace all "goldmark/util.BytesToReadOnlyString" with this official approach
func UnsafeBytesToString(b []byte) string {
return unsafe.String(unsafe.SliceData(b), len(b))
}
func UnsafeStringToBytes(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
} }

View file

@ -3551,7 +3551,7 @@ runs.commit = Commit
runs.scheduled = Scheduled runs.scheduled = Scheduled
runs.pushed_by = pushed by runs.pushed_by = pushed by
runs.invalid_workflow_helper = Workflow config file is invalid. Please check your config file: %s runs.invalid_workflow_helper = Workflow config file is invalid. Please check your config file: %s
runs.no_matching_online_runner_helper = No matching online runner with label: %s runs.no_matching_runner_helper = No matching runner: %s
runs.actor = Actor runs.actor = Actor
runs.status = Status runs.status = Status
runs.actors_no_select = All actors runs.actors_no_select = All actors

18
package-lock.json generated
View file

@ -30,7 +30,7 @@
"katex": "0.16.9", "katex": "0.16.9",
"license-checker-webpack-plugin": "0.2.1", "license-checker-webpack-plugin": "0.2.1",
"lightningcss-loader": "2.1.0", "lightningcss-loader": "2.1.0",
"mermaid": "10.6.1", "mermaid": "10.5.0",
"mini-css-extract-plugin": "2.7.6", "mini-css-extract-plugin": "2.7.6",
"minimatch": "9.0.3", "minimatch": "9.0.3",
"monaco-editor": "0.44.0", "monaco-editor": "0.44.0",
@ -999,6 +999,11 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
} }
}, },
"node_modules/@github/combobox-nav": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.3.0.tgz",
"integrity": "sha512-5CX03DbsLZ41dX5hKHyQKtg133U6lruX4TD9G0Zs4W8BpWy7lN8DJ6TYaeZN/V7x8K34coaqNYk/Y5ic7stfkg=="
},
"node_modules/@github/markdown-toolbar-element": { "node_modules/@github/markdown-toolbar-element": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/@github/markdown-toolbar-element/-/markdown-toolbar-element-2.2.1.tgz", "resolved": "https://registry.npmjs.org/@github/markdown-toolbar-element/-/markdown-toolbar-element-2.2.1.tgz",
@ -1017,11 +1022,6 @@
"@github/combobox-nav": "^2.0.2" "@github/combobox-nav": "^2.0.2"
} }
}, },
"node_modules/@github/text-expander-element/node_modules/@github/combobox-nav": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.2.0.tgz",
"integrity": "sha512-28kJUfzzPDYNyYsFCP/be8aXvEpjcEuciVENZlopcaUynS/4Pt9ll88Kl9l1D1Vy6a9+k+Km/YGJQ1e+gzG7SQ=="
},
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.11.11", "version": "0.11.11",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz",
@ -7530,9 +7530,9 @@
} }
}, },
"node_modules/mermaid": { "node_modules/mermaid": {
"version": "10.6.1", "version": "10.5.0",
"resolved": "https://registry.npmmirror.com/mermaid/-/mermaid-10.6.1.tgz", "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-10.5.0.tgz",
"integrity": "sha512-Hky0/RpOw/1il9X8AvzOEChfJtVvmXm+y7JML5C//ePYMy0/9jCEmW1E1g86x9oDfW9+iVEdTV/i+M6KWRNs4A==", "integrity": "sha512-9l0o1uUod78D3/FVYPGSsgV+Z0tSnzLBDiC9rVzvelPxuO80HbN1oDr9ofpPETQy9XpypPQa26fr09VzEPfvWA==",
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^6.0.1", "@braintree/sanitize-url": "^6.0.1",
"@types/d3-scale": "^4.0.3", "@types/d3-scale": "^4.0.3",

View file

@ -29,7 +29,7 @@
"katex": "0.16.9", "katex": "0.16.9",
"license-checker-webpack-plugin": "0.2.1", "license-checker-webpack-plugin": "0.2.1",
"lightningcss-loader": "2.1.0", "lightningcss-loader": "2.1.0",
"mermaid": "10.6.1", "mermaid": "10.5.0",
"mini-css-extract-plugin": "2.7.6", "mini-css-extract-plugin": "2.7.6",
"minimatch": "9.0.3", "minimatch": "9.0.3",
"monaco-editor": "0.44.0", "monaco-editor": "0.44.0",
@ -82,9 +82,6 @@
"vite-string-plugin": "1.1.2", "vite-string-plugin": "1.1.2",
"vitest": "0.34.6" "vitest": "0.34.6"
}, },
"overrides": {
"@github/combobox-nav": "2.2.0"
},
"browserslist": [ "browserslist": [
"defaults", "defaults",
"not ie > 0", "not ie > 0",

View file

@ -25,11 +25,10 @@ func saveUploadChunk(st storage.ObjectStorage, ctx *ArtifactContext,
contentRange := ctx.Req.Header.Get("Content-Range") contentRange := ctx.Req.Header.Get("Content-Range")
start, end, length := int64(0), int64(0), int64(0) start, end, length := int64(0), int64(0), int64(0)
if _, err := fmt.Sscanf(contentRange, "bytes %d-%d/%d", &start, &end, &length); err != nil { if _, err := fmt.Sscanf(contentRange, "bytes %d-%d/%d", &start, &end, &length); err != nil {
log.Warn("parse content range error: %v, content-range: %s", err, contentRange)
return -1, fmt.Errorf("parse content range error: %v", err) return -1, fmt.Errorf("parse content range error: %v", err)
} }
// build chunk store path // build chunk store path
storagePath := fmt.Sprintf("tmp%d/%d-%d-%d-%d.chunk", runID, runID, artifact.ID, start, end) storagePath := fmt.Sprintf("tmp%d/%d-%d-%d.chunk", runID, artifact.ID, start, end)
// use io.TeeReader to avoid reading all body to md5 sum. // use io.TeeReader to avoid reading all body to md5 sum.
// it writes data to hasher after reading end // it writes data to hasher after reading end
// if hash is not matched, delete the read-end result // if hash is not matched, delete the read-end result
@ -58,7 +57,6 @@ func saveUploadChunk(st storage.ObjectStorage, ctx *ArtifactContext,
} }
type chunkFileItem struct { type chunkFileItem struct {
RunID int64
ArtifactID int64 ArtifactID int64
Start int64 Start int64
End int64 End int64
@ -68,12 +66,9 @@ type chunkFileItem struct {
func listChunksByRunID(st storage.ObjectStorage, runID int64) (map[int64][]*chunkFileItem, error) { func listChunksByRunID(st storage.ObjectStorage, runID int64) (map[int64][]*chunkFileItem, error) {
storageDir := fmt.Sprintf("tmp%d", runID) storageDir := fmt.Sprintf("tmp%d", runID)
var chunks []*chunkFileItem var chunks []*chunkFileItem
if err := st.IterateObjects(storageDir, func(fpath string, obj storage.Object) error { if err := st.IterateObjects(storageDir, func(path string, obj storage.Object) error {
baseName := filepath.Base(fpath) item := chunkFileItem{Path: path}
// when read chunks from storage, it only contains storage dir and basename, if _, err := fmt.Sscanf(path, filepath.Join(storageDir, "%d-%d-%d.chunk"), &item.ArtifactID, &item.Start, &item.End); err != nil {
// no matter the subdirectory setting in storage config
item := chunkFileItem{Path: storageDir + "/" + baseName}
if _, err := fmt.Sscanf(baseName, "%d-%d-%d-%d.chunk", &item.RunID, &item.ArtifactID, &item.Start, &item.End); err != nil {
return fmt.Errorf("parse content range error: %v", err) return fmt.Errorf("parse content range error: %v", err)
} }
chunks = append(chunks, &item) chunks = append(chunks, &item)

View file

@ -603,10 +603,7 @@ func ContainerRoutes() *web.Route {
}) })
r.Get("", container.ReqContainerAccess, container.DetermineSupport) r.Get("", container.ReqContainerAccess, container.DetermineSupport)
r.Group("/token", func() { r.Get("/token", container.Authenticate)
r.Get("", container.Authenticate)
r.Post("", container.AuthenticateNotImplemented)
})
r.Get("/_catalog", container.ReqContainerAccess, container.GetRepositoryList) r.Get("/_catalog", container.ReqContainerAccess, container.GetRepositoryList)
r.Group("/{username}", func() { r.Group("/{username}", func() {
r.Group("/{image}", func() { r.Group("/{image}", func() {

View file

@ -156,17 +156,6 @@ func Authenticate(ctx *context.Context) {
}) })
} }
// https://distribution.github.io/distribution/spec/auth/oauth/
func AuthenticateNotImplemented(ctx *context.Context) {
// This optional endpoint can be used to authenticate a client.
// It must implement the specification described in:
// https://datatracker.ietf.org/doc/html/rfc6749
// https://distribution.github.io/distribution/spec/auth/oauth/
// Purpose of this stub is to respond with 404 Not Found instead of 405 Method Not Allowed.
ctx.Status(http.StatusNotFound)
}
// https://docs.docker.com/registry/spec/api/#listing-repositories // https://docs.docker.com/registry/spec/api/#listing-repositories
func GetRepositoryList(ctx *context.Context) { func GetRepositoryList(ctx *context.Context) {
n := ctx.FormInt("n") n := ctx.FormInt("n")

View file

@ -93,20 +93,11 @@ func CreateUser(ctx *context.APIContext) {
if ctx.Written() { if ctx.Written() {
return return
} }
if u.LoginType == auth.Plain {
if len(form.Password) < setting.MinPasswordLength {
err := errors.New("PasswordIsRequired")
ctx.Error(http.StatusBadRequest, "PasswordIsRequired", err)
return
}
if !password.IsComplexEnough(form.Password) { if !password.IsComplexEnough(form.Password) {
err := errors.New("PasswordComplexity") err := errors.New("PasswordComplexity")
ctx.Error(http.StatusBadRequest, "PasswordComplexity", err) ctx.Error(http.StatusBadRequest, "PasswordComplexity", err)
return return
} }
pwned, err := password.IsPwned(ctx, form.Password) pwned, err := password.IsPwned(ctx, form.Password)
if pwned { if pwned {
if err != nil { if err != nil {
@ -115,7 +106,6 @@ func CreateUser(ctx *context.APIContext) {
ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned")) ctx.Error(http.StatusBadRequest, "PasswordPwned", errors.New("PasswordPwned"))
return return
} }
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{ overwriteDefault := &user_model.CreateUserOverwriteOptions{
IsActive: util.OptionalBoolTrue, IsActive: util.OptionalBoolTrue,

View file

@ -787,24 +787,6 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIC
} }
} }
func individualPermsChecker(ctx *context.APIContext) {
// org permissions have been checked in context.OrgAssignment(), but individual permissions haven't been checked.
if ctx.ContextUser.IsIndividual() {
switch {
case ctx.ContextUser.Visibility == api.VisibleTypePrivate:
if ctx.Doer == nil || (ctx.ContextUser.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin) {
ctx.NotFound("Visit Project", nil)
return
}
case ctx.ContextUser.Visibility == api.VisibleTypeLimited:
if ctx.Doer == nil {
ctx.NotFound("Visit Project", nil)
return
}
}
}
}
// Routes registers all v1 APIs routes to web application. // Routes registers all v1 APIs routes to web application.
func Routes() *web.Route { func Routes() *web.Route {
m := web.NewRoute() m := web.NewRoute()
@ -905,7 +887,7 @@ func Routes() *web.Route {
}, reqSelfOrAdmin(), reqBasicOrRevProxyAuth()) }, reqSelfOrAdmin(), reqBasicOrRevProxyAuth())
m.Get("/activities/feeds", user.ListUserActivityFeeds) m.Get("/activities/feeds", user.ListUserActivityFeeds)
}, context_service.UserAssignmentAPI(), individualPermsChecker) }, context_service.UserAssignmentAPI())
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser)) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
// Users (requires user scope) // Users (requires user scope)

View file

@ -262,11 +262,12 @@ func CreateBranch(ctx *context.APIContext) {
} }
} }
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, oldCommit.ID.String(), opt.BranchName) err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, oldCommit.ID.String(), opt.BranchName)
if err != nil { if err != nil {
if git_model.IsErrBranchNotExist(err) { if git_model.IsErrBranchNotExist(err) {
ctx.Error(http.StatusNotFound, "", "The old branch does not exist") ctx.Error(http.StatusNotFound, "", "The old branch does not exist")
} else if models.IsErrTagAlreadyExists(err) { }
if models.IsErrTagAlreadyExists(err) {
ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.") ctx.Error(http.StatusConflict, "", "The branch with the same tag already exists.")
} else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) { } else if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
ctx.Error(http.StatusConflict, "", "The branch already exists.") ctx.Error(http.StatusConflict, "", "The branch already exists.")

View file

@ -18,7 +18,6 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/web/repo" "code.gitea.io/gitea/routers/web/repo"
"code.gitea.io/gitea/services/convert" "code.gitea.io/gitea/services/convert"
@ -78,7 +77,6 @@ func List(ctx *context.Context) {
// Get all runner labels // Get all runner labels
opts := actions_model.FindRunnerOptions{ opts := actions_model.FindRunnerOptions{
RepoID: ctx.Repo.Repository.ID, RepoID: ctx.Repo.Repository.ID,
IsOnline: util.OptionalBoolTrue,
WithAvailable: true, WithAvailable: true,
} }
runners, err := actions_model.FindRunners(ctx, opts) runners, err := actions_model.FindRunners(ctx, opts)
@ -116,7 +114,7 @@ func List(ctx *context.Context) {
continue continue
} }
if !allRunnerLabels.Contains(ro) { if !allRunnerLabels.Contains(ro) {
workflow.ErrMsg = ctx.Locale.Tr("actions.runs.no_matching_online_runner_helper", ro) workflow.ErrMsg = ctx.Locale.Tr("actions.runs.no_matching_runner_helper", ro)
break break
} }
} }

View file

@ -125,7 +125,7 @@ func RefBlame(ctx *context.Context) {
} }
type blameResult struct { type blameResult struct {
Parts []*git.BlamePart Parts []git.BlamePart
UsesIgnoreRevs bool UsesIgnoreRevs bool
FaultyIgnoreRevsFile bool FaultyIgnoreRevsFile bool
} }
@ -170,9 +170,7 @@ func performBlame(ctx *context.Context, repoPath string, commit *git.Commit, fil
func fillBlameResult(br *git.BlameReader, r *blameResult) error { func fillBlameResult(br *git.BlameReader, r *blameResult) error {
r.UsesIgnoreRevs = br.UsesIgnoreRevs() r.UsesIgnoreRevs = br.UsesIgnoreRevs()
previousHelper := make(map[string]*git.BlamePart) r.Parts = make([]git.BlamePart, 0, 5)
r.Parts = make([]*git.BlamePart, 0, 5)
for { for {
blamePart, err := br.NextPart() blamePart, err := br.NextPart()
if err != nil { if err != nil {
@ -181,23 +179,13 @@ func fillBlameResult(br *git.BlameReader, r *blameResult) error {
if blamePart == nil { if blamePart == nil {
break break
} }
r.Parts = append(r.Parts, *blamePart)
if prev, ok := previousHelper[blamePart.Sha]; ok {
if blamePart.PreviousSha == "" {
blamePart.PreviousSha = prev.PreviousSha
blamePart.PreviousPath = prev.PreviousPath
}
} else {
previousHelper[blamePart.Sha] = blamePart
}
r.Parts = append(r.Parts, blamePart)
} }
return nil return nil
} }
func processBlameParts(ctx *context.Context, blameParts []*git.BlamePart) map[string]*user_model.UserCommit { func processBlameParts(ctx *context.Context, blameParts []git.BlamePart) map[string]*user_model.UserCommit {
// store commit data by SHA to look up avatar info etc // store commit data by SHA to look up avatar info etc
commitNames := make(map[string]*user_model.UserCommit) commitNames := make(map[string]*user_model.UserCommit)
// and as blameParts can reference the same commits multiple // and as blameParts can reference the same commits multiple
@ -239,7 +227,7 @@ func processBlameParts(ctx *context.Context, blameParts []*git.BlamePart) map[st
return commitNames return commitNames
} }
func renderBlame(ctx *context.Context, blameParts []*git.BlamePart, commitNames map[string]*user_model.UserCommit) { func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames map[string]*user_model.UserCommit) {
repoLink := ctx.Repo.RepoLink repoLink := ctx.Repo.RepoLink
language := "" language := ""
@ -322,7 +310,8 @@ func renderBlame(ctx *context.Context, blameParts []*git.BlamePart, commitNames
lexerName = lexerNameForLine lexerName = lexerNameForLine
} }
br.EscapeStatus, br.Code = charset.EscapeControlHTML(line, ctx.Locale) br.EscapeStatus, line = charset.EscapeControlHTML(line, ctx.Locale)
br.Code = gotemplate.HTML(line)
rows = append(rows, br) rows = append(rows, br)
escapeStatus = escapeStatus.Or(br.EscapeStatus) escapeStatus = escapeStatus.Or(br.EscapeStatus)
} }

View file

@ -191,9 +191,9 @@ func CreateBranch(ctx *context.Context) {
} }
err = release_service.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, target, form.NewBranchName, "") err = release_service.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, target, form.NewBranchName, "")
} else if ctx.Repo.IsViewBranch { } else if ctx.Repo.IsViewBranch {
err = repo_service.CreateNewBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.BranchName, form.NewBranchName) err = repo_service.CreateNewBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.BranchName, form.NewBranchName)
} else { } else {
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.CommitID, form.NewBranchName) err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.CommitID, form.NewBranchName)
} }
if err != nil { if err != nil {
if models.IsErrProtectedTagName(err) { if models.IsErrProtectedTagName(err) {

View file

@ -193,7 +193,16 @@ func SoftDeleteContentHistory(ctx *context.Context) {
var comment *issues_model.Comment var comment *issues_model.Comment
var history *issues_model.ContentHistory var history *issues_model.ContentHistory
var err error var err error
if commentID != 0 {
if comment, err = issues_model.GetCommentByID(ctx, commentID); err != nil {
log.Error("can not get comment for issue content history %v. err=%v", historyID, err)
return
}
if comment.IssueID != issue.ID {
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
return
}
}
if history, err = issues_model.GetIssueContentHistoryByID(ctx, historyID); err != nil { if history, err = issues_model.GetIssueContentHistoryByID(ctx, historyID); err != nil {
log.Error("can not get issue content history %v. err=%v", historyID, err) log.Error("can not get issue content history %v. err=%v", historyID, err)
return return
@ -202,21 +211,6 @@ func SoftDeleteContentHistory(ctx *context.Context) {
ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{}) ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
return return
} }
if commentID != 0 {
if history.CommentID != commentID {
ctx.NotFound("CompareCommentID", issues_model.ErrCommentNotExist{})
return
}
if comment, err = issues_model.GetCommentByID(ctx, commentID); err != nil {
log.Error("can not get comment for issue content history %v. err=%v", historyID, err)
return
}
if comment.IssueID != issue.ID {
ctx.NotFound("CompareIssueID", issues_model.ErrCommentNotExist{})
return
}
}
canSoftDelete := canSoftDeleteContentHistory(ctx, issue, comment, history) canSoftDelete := canSoftDeleteContentHistory(ctx, issue, comment, history)
if !canSoftDelete { if !canSoftDelete {

View file

@ -89,10 +89,8 @@ func IssuePinMove(ctx *context.Context) {
log.Error(err.Error()) log.Error(err.Error())
return return
} }
if issue.RepoID != ctx.Repo.Repository.ID { if issue.RepoID != ctx.Repo.Repository.ID {
ctx.Status(http.StatusNotFound) ctx.NotFound("CompareRepoID", issues_model.ErrCommentNotExist{})
log.Error("Issue does not belong to this repository")
return return
} }

View file

@ -9,7 +9,6 @@ import (
gocontext "context" gocontext "context"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"html/template"
"image" "image"
"io" "io"
"net/http" "net/http"
@ -318,18 +317,19 @@ func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.Tr
}, rd) }, rd)
if err != nil { if err != nil {
log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err) log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
delete(ctx.Data, "IsMarkup") buf := &bytes.Buffer{}
ctx.Data["EscapeStatus"], _ = charset.EscapeControlStringReader(rd, buf, ctx.Locale)
ctx.Data["FileContent"] = buf.String()
} }
} else {
ctx.Data["IsPlainText"] = true
buf := &bytes.Buffer{}
ctx.Data["EscapeStatus"], err = charset.EscapeControlStringReader(rd, buf, ctx.Locale)
if err != nil {
log.Error("Read failed: %v", err)
} }
if ctx.Data["IsMarkup"] != true { ctx.Data["FileContent"] = buf.String()
ctx.Data["IsPlainText"] = true
content, err := io.ReadAll(rd)
if err != nil {
log.Error("Read readme content failed: %v", err)
}
contentEscaped := template.HTMLEscapeString(util.UnsafeBytesToString(content))
ctx.Data["EscapeStatus"], ctx.Data["FileContent"] = charset.EscapeControlHTML(template.HTML(contentEscaped), ctx.Locale)
} }
} }
@ -611,7 +611,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
} }
} }
func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input io.Reader) (escaped *charset.EscapeStatus, output template.HTML, err error) { func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input io.Reader) (escaped *charset.EscapeStatus, output string, err error) {
markupRd, markupWr := io.Pipe() markupRd, markupWr := io.Pipe()
defer markupWr.Close() defer markupWr.Close()
done := make(chan struct{}) done := make(chan struct{})
@ -619,7 +619,7 @@ func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input i
sb := &strings.Builder{} sb := &strings.Builder{}
// We allow NBSP here this is rendered // We allow NBSP here this is rendered
escaped, _ = charset.EscapeControlReader(markupRd, sb, ctx.Locale, charset.RuneNBSP) escaped, _ = charset.EscapeControlReader(markupRd, sb, ctx.Locale, charset.RuneNBSP)
output = template.HTML(sb.String()) output = sb.String()
close(done) close(done)
}() }()
err = markup.Render(renderCtx, input, markupWr) err = markup.Render(renderCtx, input, markupWr)

View file

@ -798,24 +798,6 @@ func registerRoutes(m *web.Route) {
} }
} }
individualPermsChecker := func(ctx *context.Context) {
// org permissions have been checked in context.OrgAssignment(), but individual permissions haven't been checked.
if ctx.ContextUser.IsIndividual() {
switch {
case ctx.ContextUser.Visibility == structs.VisibleTypePrivate:
if ctx.Doer == nil || (ctx.ContextUser.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin) {
ctx.NotFound("Visit Project", nil)
return
}
case ctx.ContextUser.Visibility == structs.VisibleTypeLimited:
if ctx.Doer == nil {
ctx.NotFound("Visit Project", nil)
return
}
}
}
}
// ***** START: Organization ***** // ***** START: Organization *****
m.Group("/org", func() { m.Group("/org", func() {
m.Group("/{org}", func() { m.Group("/{org}", func() {
@ -1002,11 +984,11 @@ func registerRoutes(m *web.Route) {
return return
} }
}) })
}, reqUnitAccess(unit.TypeProjects, perm.AccessModeRead, true), individualPermsChecker) })
m.Group("", func() { m.Group("", func() {
m.Get("/code", user.CodeSearch) m.Get("/code", user.CodeSearch)
}, reqUnitAccess(unit.TypeCode, perm.AccessModeRead, false), individualPermsChecker) }, reqUnitAccess(unit.TypeCode, perm.AccessModeRead, false))
}, ignSignIn, context_service.UserAssignmentWeb(), context.OrgAssignment()) // for "/{username}/-" (packages, projects, code) }, ignSignIn, context_service.UserAssignmentWeb(), context.OrgAssignment()) // for "/{username}/-" (packages, projects, code)
m.Group("/{username}/{reponame}", func() { m.Group("/{username}/{reponame}", func() {

View file

@ -422,7 +422,7 @@ func handleSchedules(
OwnerID: input.Repo.OwnerID, OwnerID: input.Repo.OwnerID,
WorkflowID: dwf.EntryName, WorkflowID: dwf.EntryName,
TriggerUserID: input.Doer.ID, TriggerUserID: input.Doer.ID,
Ref: input.Repo.DefaultBranch, Ref: input.Ref,
CommitSHA: commit.ID.String(), CommitSHA: commit.ID.String(),
Event: input.Event, Event: input.Event,
EventPayload: string(p), EventPayload: string(p),

View file

@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: Copyright the Forgejo contributors
// SPDX-License-Identifier: MIT
package oauth2
import (
"net/http"
)
var HTTPClient *http.Client

View file

@ -63,7 +63,9 @@ func init() {
if setting.OAuth2Client.EnableAutoRegistration { if setting.OAuth2Client.EnableAutoRegistration {
scopes = append(scopes, "user:email") scopes = append(scopes, "user:email")
} }
return github.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, custom.EmailURL, scopes...), nil provider := github.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, custom.EmailURL, scopes...)
provider.HTTPClient = HTTPClient
return provider, nil
})) }))
RegisterGothProvider(NewCustomProvider( RegisterGothProvider(NewCustomProvider(
@ -73,7 +75,9 @@ func init() {
ProfileURL: availableAttribute(gitlab.ProfileURL), ProfileURL: availableAttribute(gitlab.ProfileURL),
}, func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) { }, func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) {
scopes = append(scopes, "read_user") scopes = append(scopes, "read_user")
return gitlab.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, scopes...), nil provider := gitlab.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, scopes...)
provider.HTTPClient = HTTPClient
return provider, nil
})) }))
RegisterGothProvider(NewCustomProvider( RegisterGothProvider(NewCustomProvider(
@ -83,7 +87,9 @@ func init() {
ProfileURL: requiredAttribute(gitea.ProfileURL), ProfileURL: requiredAttribute(gitea.ProfileURL),
}, },
func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) { func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) {
return gitea.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, scopes...), nil provider := gitea.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, scopes...)
provider.HTTPClient = HTTPClient
return provider, nil
})) }))
RegisterGothProvider(NewCustomProvider( RegisterGothProvider(NewCustomProvider(
@ -93,7 +99,9 @@ func init() {
ProfileURL: requiredAttribute(nextcloud.ProfileURL), ProfileURL: requiredAttribute(nextcloud.ProfileURL),
}, },
func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) { func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) {
return nextcloud.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, scopes...), nil provider := nextcloud.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, custom.TokenURL, custom.ProfileURL, scopes...)
provider.HTTPClient = HTTPClient
return provider, nil
})) }))
RegisterGothProvider(NewCustomProvider( RegisterGothProvider(NewCustomProvider(
@ -101,7 +109,9 @@ func init() {
AuthURL: requiredAttribute(mastodon.InstanceURL), AuthURL: requiredAttribute(mastodon.InstanceURL),
}, },
func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) { func(clientID, secret, callbackURL string, custom *CustomURLMapping, scopes []string) (goth.Provider, error) {
return mastodon.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, scopes...), nil provider := mastodon.NewCustomisedURL(clientID, secret, callbackURL, custom.AuthURL, scopes...)
provider.HTTPClient = HTTPClient
return provider, nil
})) }))
RegisterGothProvider(NewCustomProvider( RegisterGothProvider(NewCustomProvider(
@ -114,10 +124,12 @@ func init() {
azureScopes[i] = azureadv2.ScopeType(scope) azureScopes[i] = azureadv2.ScopeType(scope)
} }
return azureadv2.New(clientID, secret, callbackURL, azureadv2.ProviderOptions{ provider := azureadv2.New(clientID, secret, callbackURL, azureadv2.ProviderOptions{
Tenant: azureadv2.TenantType(custom.Tenant), Tenant: azureadv2.TenantType(custom.Tenant),
Scopes: azureScopes, Scopes: azureScopes,
}), nil })
provider.HTTPClient = HTTPClient
return provider, nil
}, },
)) ))
} }

View file

@ -43,6 +43,7 @@ func (o *OpenIDProvider) CreateGothProvider(providerName, callbackURL string, so
if err != nil { if err != nil {
log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, source.OpenIDConnectAutoDiscoveryURL, err) log.Warn("Failed to create OpenID Connect Provider with name '%s' with url '%s': %v", providerName, source.OpenIDConnectAutoDiscoveryURL, err)
} }
provider.HTTPClient = HTTPClient
return provider, err return provider, err
} }

View file

@ -84,15 +84,13 @@ func (t *Task) RunWithUser(doer *user_model.User, config Config) {
t.lock.Unlock() t.lock.Unlock()
defer func() { defer func() {
taskStatusTable.Stop(t.Name) taskStatusTable.Stop(t.Name)
}()
graceful.GetManager().RunWithShutdownContext(func(baseCtx context.Context) {
defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
// Recover a panic within the execution of the task. // Recover a panic within the
combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2)) combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
log.Error("PANIC whilst running task: %s Value: %v", t.Name, combinedErr) log.Error("PANIC whilst running task: %s Value: %v", t.Name, combinedErr)
} }
}() }()
graceful.GetManager().RunWithShutdownContext(func(baseCtx context.Context) {
// Store the time of this run, before the function is executed, so it // Store the time of this run, before the function is executed, so it
// matches the behavior of what the cron library does. // matches the behavior of what the cron library does.
t.lock.Lock() t.lock.Lock()

View file

@ -285,15 +285,15 @@ type DiffInline struct {
// DiffInlineWithUnicodeEscape makes a DiffInline with hidden unicode characters escaped // DiffInlineWithUnicodeEscape makes a DiffInline with hidden unicode characters escaped
func DiffInlineWithUnicodeEscape(s template.HTML, locale translation.Locale) DiffInline { func DiffInlineWithUnicodeEscape(s template.HTML, locale translation.Locale) DiffInline {
status, content := charset.EscapeControlHTML(s, locale) status, content := charset.EscapeControlHTML(string(s), locale)
return DiffInline{EscapeStatus: status, Content: content} return DiffInline{EscapeStatus: status, Content: template.HTML(content)}
} }
// DiffInlineWithHighlightCode makes a DiffInline with code highlight and hidden unicode characters escaped // DiffInlineWithHighlightCode makes a DiffInline with code highlight and hidden unicode characters escaped
func DiffInlineWithHighlightCode(fileName, language, code string, locale translation.Locale) DiffInline { func DiffInlineWithHighlightCode(fileName, language, code string, locale translation.Locale) DiffInline {
highlighted, _ := highlight.Code(fileName, language, code) highlighted, _ := highlight.Code(fileName, language, code)
status, content := charset.EscapeControlHTML(highlighted, locale) status, content := charset.EscapeControlHTML(highlighted, locale)
return DiffInline{EscapeStatus: status, Content: content} return DiffInline{EscapeStatus: status, Content: template.HTML(content)}
} }
// GetComputedInlineDiffFor computes inline diff for the given line. // GetComputedInlineDiffFor computes inline diff for the given line.

View file

@ -93,10 +93,10 @@ func (hcd *highlightCodeDiff) diffWithHighlight(filename, language, codeA, codeB
highlightCodeA, _ := highlight.Code(filename, language, codeA) highlightCodeA, _ := highlight.Code(filename, language, codeA)
highlightCodeB, _ := highlight.Code(filename, language, codeB) highlightCodeB, _ := highlight.Code(filename, language, codeB)
convertedCodeA := hcd.convertToPlaceholders(string(highlightCodeA)) highlightCodeA = hcd.convertToPlaceholders(highlightCodeA)
convertedCodeB := hcd.convertToPlaceholders(string(highlightCodeB)) highlightCodeB = hcd.convertToPlaceholders(highlightCodeB)
diffs := diffMatchPatch.DiffMain(convertedCodeA, convertedCodeB, true) diffs := diffMatchPatch.DiffMain(highlightCodeA, highlightCodeB, true)
diffs = diffMatchPatch.DiffCleanupEfficiency(diffs) diffs = diffMatchPatch.DiffCleanupEfficiency(diffs)
for i := range diffs { for i := range diffs {

View file

@ -82,7 +82,10 @@ func BuildAllRepositoryFiles(ctx context.Context, ownerID int64) error {
} }
for _, pf := range pfs { for _, pf := range pfs {
if err := packages_service.DeletePackageFile(ctx, pf); err != nil { if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
return err return err
} }
} }
@ -154,11 +157,12 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package
pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, IndexFilename, fmt.Sprintf("%s|%s|%s", branch, repository, architecture)) pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, IndexFilename, fmt.Sprintf("%s|%s|%s", branch, repository, architecture))
if err != nil && !errors.Is(err, util.ErrNotExist) { if err != nil && !errors.Is(err, util.ErrNotExist) {
return err return err
} else if pf == nil {
return nil
} }
return packages_service.DeletePackageFile(ctx, pf) if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
return packages_model.DeleteFileByID(ctx, pf.ID)
} }
// Cache data needed for all repository files // Cache data needed for all repository files

View file

@ -11,7 +11,6 @@ import (
container_model "code.gitea.io/gitea/models/packages/container" container_model "code.gitea.io/gitea/models/packages/container"
container_module "code.gitea.io/gitea/modules/packages/container" container_module "code.gitea.io/gitea/modules/packages/container"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
packages_service "code.gitea.io/gitea/services/packages"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
) )
@ -48,7 +47,10 @@ func cleanupExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) e
} }
for _, pf := range pfs { for _, pf := range pfs {
if err := packages_service.DeletePackageFile(ctx, pf); err != nil { if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
return err return err
} }
} }

View file

@ -110,7 +110,10 @@ func BuildAllRepositoryFiles(ctx context.Context, ownerID int64) error {
} }
for _, pf := range pfs { for _, pf := range pfs {
if err := packages_service.DeletePackageFile(ctx, pf); err != nil { if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
return err return err
} }
} }
@ -179,11 +182,12 @@ func buildPackagesIndices(ctx context.Context, ownerID int64, repoVersion *packa
pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, filename, key) pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, filename, key)
if err != nil && !errors.Is(err, util.ErrNotExist) { if err != nil && !errors.Is(err, util.ErrNotExist) {
return err return err
} else if pf == nil {
continue
} }
if err := packages_service.DeletePackageFile(ctx, pf); err != nil { if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
return err return err
} }
} }
@ -281,11 +285,12 @@ func buildReleaseFiles(ctx context.Context, ownerID int64, repoVersion *packages
pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, filename, distribution) pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, filename, distribution)
if err != nil && !errors.Is(err, util.ErrNotExist) { if err != nil && !errors.Is(err, util.ErrNotExist) {
return err return err
} else if pf == nil {
continue
} }
if err := packages_service.DeletePackageFile(ctx, pf); err != nil { if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
return err return err
} }
} }

View file

@ -148,7 +148,10 @@ func BuildRepositoryFiles(ctx context.Context, ownerID int64) error {
return err return err
} }
for _, pf := range pfs { for _, pf := range pfs {
if err := packages_service.DeletePackageFile(ctx, pf); err != nil { if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
return err
}
if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
return err return err
} }
} }

View file

@ -20,7 +20,6 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/queue"
repo_module "code.gitea.io/gitea/modules/repository" repo_module "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
notify_service "code.gitea.io/gitea/services/notify" notify_service "code.gitea.io/gitea/services/notify"
files_service "code.gitea.io/gitea/services/repository/files" files_service "code.gitea.io/gitea/services/repository/files"
@ -29,13 +28,30 @@ import (
) )
// CreateNewBranch creates a new repository branch // CreateNewBranch creates a new repository branch
func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, oldBranchName, branchName string) (err error) { func CreateNewBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldBranchName, branchName string) (err error) {
branch, err := git_model.GetBranch(ctx, repo.ID, oldBranchName) // Check if branch name can be used
if err != nil { if err := checkBranchName(ctx, repo, branchName); err != nil {
return err return err
} }
return CreateNewBranchFromCommit(ctx, doer, repo, gitRepo, branch.CommitID, branchName) if !git.IsBranchExist(ctx, repo.RepoPath(), oldBranchName) {
return git_model.ErrBranchNotExist{
BranchName: oldBranchName,
}
}
if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{
Remote: repo.RepoPath(),
Branch: fmt.Sprintf("%s%s:%s%s", git.BranchPrefix, oldBranchName, git.BranchPrefix, branchName),
Env: repo_module.PushingEnvironment(doer, repo),
}); err != nil {
if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {
return err
}
return fmt.Errorf("push: %w", err)
}
return nil
} }
// Branch contains the branch information // Branch contains the branch information
@ -228,54 +244,8 @@ func checkBranchName(ctx context.Context, repo *repo_model.Repository, name stri
return err return err
} }
// syncBranchToDB sync the branch information in the database. It will try to update the branch first,
// if updated success with affect records > 0, then all are done. Because that means the branch has been in the database.
// If no record is affected, that means the branch does not exist in database. So there are two possibilities.
// One is this is a new branch, then we just need to insert the record. Another is the branches haven't been synced,
// then we need to sync all the branches into database.
func syncBranchToDB(ctx context.Context, repoID, pusherID int64, branchName string, commit *git.Commit) error {
cnt, err := git_model.UpdateBranch(ctx, repoID, pusherID, branchName, commit)
if err != nil {
return fmt.Errorf("git_model.UpdateBranch %d:%s failed: %v", repoID, branchName, err)
}
if cnt > 0 { // This means branch does exist, so it's a normal update. It also means the branch has been synced.
return nil
}
// if user haven't visit UI but directly push to a branch after upgrading from 1.20 -> 1.21,
// we cannot simply insert the branch but need to check we have branches or not
hasBranch, err := db.Exist[git_model.Branch](ctx, git_model.FindBranchOptions{
RepoID: repoID,
IsDeletedBranch: util.OptionalBoolFalse,
}.ToConds())
if err != nil {
return err
}
if !hasBranch {
if _, err = repo_module.SyncRepoBranches(ctx, repoID, pusherID); err != nil {
return fmt.Errorf("repo_module.SyncRepoBranches %d:%s failed: %v", repoID, branchName, err)
}
return nil
}
// if database have branches but not this branch, it means this is a new branch
return db.Insert(ctx, &git_model.Branch{
RepoID: repoID,
Name: branchName,
CommitID: commit.ID.String(),
CommitMessage: commit.Summary(),
PusherID: pusherID,
CommitTime: timeutil.TimeStamp(commit.Committer.When.Unix()),
})
}
// CreateNewBranchFromCommit creates a new repository branch // CreateNewBranchFromCommit creates a new repository branch
func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, commitID, branchName string) (err error) { func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, commit, branchName string) (err error) {
err = repo.MustNotBeArchived()
if err != nil {
return err
}
// Check if branch name can be used // Check if branch name can be used
if err := checkBranchName(ctx, repo, branchName); err != nil { if err := checkBranchName(ctx, repo, branchName); err != nil {
return err return err
@ -283,7 +253,7 @@ func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo
if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{ if err := git.Push(ctx, repo.RepoPath(), git.PushOptions{
Remote: repo.RepoPath(), Remote: repo.RepoPath(),
Branch: fmt.Sprintf("%s:%s%s", commitID, git.BranchPrefix, branchName), Branch: fmt.Sprintf("%s:%s%s", commit, git.BranchPrefix, branchName),
Env: repo_module.PushingEnvironment(doer, repo), Env: repo_module.PushingEnvironment(doer, repo),
}); err != nil { }); err != nil {
if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) {

View file

@ -257,7 +257,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
commits.Commits = commits.Commits[:setting.UI.FeedMaxCommitNum] commits.Commits = commits.Commits[:setting.UI.FeedMaxCommitNum]
} }
if err = syncBranchToDB(ctx, repo.ID, opts.PusherID, branch, newCommit); err != nil { if err = git_model.UpdateBranch(ctx, repo.ID, opts.PusherID, branch, newCommit); err != nil {
return fmt.Errorf("git_model.UpdateBranch %s:%s failed: %v", repo.FullName(), branch, err) return fmt.Errorf("git_model.UpdateBranch %s:%s failed: %v", repo.FullName(), branch, err)
} }

View file

@ -31,7 +31,7 @@
<a href="{{$repo.Link}}/src/commit/{{$result.CommitID | PathEscape}}/{{$result.Filename | PathEscapeSegments}}#L{{.}}"><span>{{.}}</span></a> <a href="{{$repo.Link}}/src/commit/{{$result.CommitID | PathEscape}}/{{$result.Filename | PathEscapeSegments}}#L{{.}}"><span>{{.}}</span></a>
{{end}} {{end}}
</td> </td>
<td class="lines-code chroma"><code class="code-inner">{{.FormattedLines}}</code></td> <td class="lines-code chroma"><code class="code-inner">{{.FormattedLines | Safe}}</code></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View file

@ -73,8 +73,8 @@
</label> </label>
</div> </div>
<div class="required field"> <div class="required field">
<label for="repo_name_to_delete">{{ctx.Locale.Tr "repo.repo_name"}}</label> <label for="repo_name">{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input id="repo_name_to_delete" name="repo_name" required> <input id="repo_name" name="repo_name" required>
</div> </div>
<div class="text right actions"> <div class="text right actions">

View file

@ -53,7 +53,7 @@
<a href="{{$.SourcePath}}/src/commit/{{PathEscape $result.CommitID}}/{{PathEscapeSegments $result.Filename}}#L{{.}}"><span>{{.}}</span></a> <a href="{{$.SourcePath}}/src/commit/{{PathEscape $result.CommitID}}/{{PathEscapeSegments $result.Filename}}#L{{.}}"><span>{{.}}</span></a>
{{end}} {{end}}
</td> </td>
<td class="lines-code chroma"><code class="code-inner">{{.FormattedLines}}</code></td> <td class="lines-code chroma"><code class="code-inner">{{.FormattedLines | Safe}}</code></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View file

@ -921,8 +921,8 @@
</label> </label>
</div> </div>
<div class="required field"> <div class="required field">
<label for="repo_name_to_delete">{{ctx.Locale.Tr "repo.repo_name"}}</label> <label for="repo_name">{{ctx.Locale.Tr "repo.repo_name"}}</label>
<input id="repo_name_to_delete" name="repo_name" required> <input id="repo_name" name="repo_name" required>
</div> </div>
<div class="text right actions"> <div class="text right actions">

View file

@ -69,9 +69,9 @@
{{end}} {{end}}
<div class="file-view{{if .IsMarkup}} markup {{.MarkupType}}{{else if .IsPlainText}} plain-text{{else if .IsTextSource}} code-view{{end}}"> <div class="file-view{{if .IsMarkup}} markup {{.MarkupType}}{{else if .IsPlainText}} plain-text{{else if .IsTextSource}} code-view{{end}}">
{{if .IsMarkup}} {{if .IsMarkup}}
{{if .FileContent}}{{.FileContent}}{{end}} {{if .FileContent}}{{.FileContent | Safe}}{{end}}
{{else if .IsPlainText}} {{else if .IsPlainText}}
<pre>{{if .FileContent}}{{.FileContent}}{{end}}</pre> <pre>{{if .FileContent}}{{.FileContent | Safe}}{{end}}</pre>
{{else if not .IsTextSource}} {{else if not .IsTextSource}}
<div class="view-raw"> <div class="view-raw">
{{if .IsImageFile}} {{if .IsImageFile}}
@ -109,7 +109,7 @@
{{if $.EscapeStatus.Escaped}} {{if $.EscapeStatus.Escaped}}
<td class="lines-escape">{{if (index $.LineEscapeStatus $idx).Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{if (index $.LineEscapeStatus $idx).HasInvisible}}{{ctx.Locale.Tr "repo.invisible_runes_line"}} {{end}}{{if (index $.LineEscapeStatus $idx).HasAmbiguous}}{{ctx.Locale.Tr "repo.ambiguous_runes_line"}}{{end}}"></button>{{end}}</td> <td class="lines-escape">{{if (index $.LineEscapeStatus $idx).Escaped}}<button class="toggle-escape-button btn interact-bg" title="{{if (index $.LineEscapeStatus $idx).HasInvisible}}{{ctx.Locale.Tr "repo.invisible_runes_line"}} {{end}}{{if (index $.LineEscapeStatus $idx).HasAmbiguous}}{{ctx.Locale.Tr "repo.ambiguous_runes_line"}}{{end}}"></button>{{end}}</td>
{{end}} {{end}}
<td rel="L{{$line}}" class="lines-code chroma"><code class="code-inner">{{$code}}</code></td> <td rel="L{{$line}}" class="lines-code chroma"><code class="code-inner">{{$code | Safe}}</code></td>
</tr> </tr>
{{end}} {{end}}
</tbody> </tbody>

View file

@ -18596,7 +18596,8 @@
"type": "object", "type": "object",
"required": [ "required": [
"username", "username",
"email" "email",
"password"
], ],
"properties": { "properties": {
"created_at": { "created_at": {

View file

@ -78,7 +78,7 @@
<input readonly="" value="{{$.TokenToSign}}"> <input readonly="" value="{{$.TokenToSign}}">
<div class="help"> <div class="help">
<p>{{ctx.Locale.Tr "settings.ssh_token_help"}}</p> <p>{{ctx.Locale.Tr "settings.ssh_token_help"}}</p>
<p><code>{{printf "echo -n '%s' | ssh-keygen -Y sign -n gitea -f /path_to_PrivateKey_or_RelatedPublicKey" $.TokenToSign}}</code></p> <p><code>{{printf "echo -n '%s' | ssh-keygen -Y sign -n gitea -f /path_to_your_privkey" $.TokenToSign}}</code></p>
</div> </div>
<br> <br>
</div> </div>

View file

@ -70,16 +70,15 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) {
session = loginUser(t, user4.Name) session = loginUser(t, user4.Name)
token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
// Make a new branch in repo1
newBranch := "test_branch"
err := repo_service.CreateNewBranch(git.DefaultContext, user2, repo1, repo1.DefaultBranch, newBranch)
assert.NoError(t, err)
// Get the commit ID of the default branch // Get the commit ID of the default branch
gitRepo, err := git.OpenRepository(git.DefaultContext, repo1.RepoPath()) gitRepo, err := git.OpenRepository(git.DefaultContext, repo1.RepoPath())
assert.NoError(t, err) assert.NoError(t, err)
defer gitRepo.Close() defer gitRepo.Close()
// Make a new branch in repo1
newBranch := "test_branch"
err = repo_service.CreateNewBranch(git.DefaultContext, user2, repo1, gitRepo, repo1.DefaultBranch, newBranch)
assert.NoError(t, err)
commitID, _ := gitRepo.GetBranchCommitID(repo1.DefaultBranch) commitID, _ := gitRepo.GetBranchCommitID(repo1.DefaultBranch)
// Make a new tag in repo1 // Make a new tag in repo1
newTag := "test_tag" newTag := "test_tag"

View file

@ -72,16 +72,15 @@ func testAPIGetContents(t *testing.T, u *url.URL) {
session = loginUser(t, user4.Name) session = loginUser(t, user4.Name)
token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
// Make a new branch in repo1
newBranch := "test_branch"
err := repo_service.CreateNewBranch(git.DefaultContext, user2, repo1, repo1.DefaultBranch, newBranch)
assert.NoError(t, err)
// Get the commit ID of the default branch // Get the commit ID of the default branch
gitRepo, err := git.OpenRepository(git.DefaultContext, repo1.RepoPath()) gitRepo, err := git.OpenRepository(git.DefaultContext, repo1.RepoPath())
assert.NoError(t, err) assert.NoError(t, err)
defer gitRepo.Close() defer gitRepo.Close()
// Make a new branch in repo1
newBranch := "test_branch"
err = repo_service.CreateNewBranch(git.DefaultContext, user2, repo1, gitRepo, repo1.DefaultBranch, newBranch)
assert.NoError(t, err)
commitID, err := gitRepo.GetBranchCommitID(repo1.DefaultBranch) commitID, err := gitRepo.GetBranchCommitID(repo1.DefaultBranch)
assert.NoError(t, err) assert.NoError(t, err)
// Make a new tag in repo1 // Make a new tag in repo1

View file

@ -4,55 +4,72 @@
package integration package integration
import ( import (
"fmt"
"net/http" "net/http"
"net/url" "net/url"
"testing" "testing"
git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/modules/translation"
repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/tests"
"code.gitea.io/gitea/models/unittest"
gitea_context "code.gitea.io/gitea/modules/context"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestBranchActions(t *testing.T) { func TestViewBranches(t *testing.T) {
defer tests.PrepareTestEnv(t)()
req := NewRequest(t, "GET", "/user2/repo1/branches")
resp := MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
_, exists := htmlDoc.doc.Find(".delete-branch-button").Attr("data-url")
assert.False(t, exists, "The template has changed")
}
func TestDeleteBranch(t *testing.T) {
defer tests.PrepareTestEnv(t)()
deleteBranch(t)
}
func TestUndoDeleteBranch(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) { onGiteaRun(t, func(t *testing.T, u *url.URL) {
session := loginUser(t, "user2") deleteBranch(t)
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) htmlDoc, name := branchAction(t, ".restore-branch-button")
branch3 := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 3, RepoID: repo1.ID}) assert.Contains(t,
branchesLink := repo1.FullName() + "/branches" htmlDoc.doc.Find(".ui.positive.message").Text(),
translation.NewLocale("en-US").Tr("repo.branch.restore_success", name),
t.Run("View", func(t *testing.T) { )
req := NewRequest(t, "GET", branchesLink)
MakeRequest(t, req, http.StatusOK)
})
t.Run("Delete branch", func(t *testing.T) {
link := fmt.Sprintf("/%s/branches/delete?name=%s", repo1.FullName(), branch3.Name)
req := NewRequestWithValues(t, "POST", link, map[string]string{
"_csrf": GetCSRF(t, session, branchesLink),
})
session.MakeRequest(t, req, http.StatusOK)
flashCookie := session.GetCookie(gitea_context.CookieNameFlash)
assert.NotNil(t, flashCookie)
assert.Contains(t, flashCookie.Value, "success%3DBranch%2B%2522branch2%2522%2Bhas%2Bbeen%2Bdeleted.")
assert.True(t, unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 3, RepoID: repo1.ID}).IsDeleted)
})
t.Run("Restore branch", func(t *testing.T) {
link := fmt.Sprintf("/%s/branches/restore?branch_id=%d&name=%s", repo1.FullName(), branch3.ID, branch3.Name)
req := NewRequestWithValues(t, "POST", link, map[string]string{
"_csrf": GetCSRF(t, session, branchesLink),
})
session.MakeRequest(t, req, http.StatusOK)
flashCookie := session.GetCookie(gitea_context.CookieNameFlash)
assert.NotNil(t, flashCookie)
assert.Contains(t, flashCookie.Value, "success%3DBranch%2B%2522branch2%2522%2Bhas%2Bbeen%2Brestored")
assert.False(t, unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 3, RepoID: repo1.ID}).IsDeleted)
})
}) })
} }
func deleteBranch(t *testing.T) {
htmlDoc, name := branchAction(t, ".delete-branch-button")
assert.Contains(t,
htmlDoc.doc.Find(".ui.positive.message").Text(),
translation.NewLocale("en-US").Tr("repo.branch.deletion_success", name),
)
}
func branchAction(t *testing.T, button string) (*HTMLDoc, string) {
session := loginUser(t, "user2")
req := NewRequest(t, "GET", "/user2/repo1/branches")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
link, exists := htmlDoc.doc.Find(button).Attr("data-url")
if !assert.True(t, exists, "The template has changed") {
t.Skip()
}
req = NewRequestWithValues(t, "POST", link, map[string]string{
"_csrf": htmlDoc.GetCSRF(),
})
session.MakeRequest(t, req, http.StatusOK)
url, err := url.Parse(link)
assert.NoError(t, err)
req = NewRequest(t, "GET", "/user2/repo1/branches")
resp = session.MakeRequest(t, req, http.StatusOK)
return NewHTMLParser(t, resp.Body), url.Query().Get("name")
}

View file

@ -1,139 +0,0 @@
// Copyright Earl Warren <contact@earl-warren.org>
// SPDX-License-Identifier: MIT
package integration
import (
"net/http"
"net/url"
"os"
"path"
"testing"
"time"
actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
func TestActionsUserGit(t *testing.T) {
onGiteaRun(t, testActionsUserGit)
}
func NewActionsUserTestContext(t *testing.T, username, reponame string) APITestContext {
t.Helper()
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame})
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: username})
task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 47})
task.RepoID = repo.ID
task.OwnerID = repoOwner.ID
task.GenerateToken()
actions_model.UpdateTask(db.DefaultContext, task)
return APITestContext{
Session: emptyTestSession(t),
Token: task.Token,
Username: username,
Reponame: reponame,
}
}
func testActionsUserGit(t *testing.T, u *url.URL) {
username := "user2"
reponame := "repo1"
httpContext := NewAPITestContext(t, username, reponame, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser)
for _, testCase := range []struct {
name string
head string
ctx APITestContext
}{
{
name: "UserTypeIndividual",
head: "individualhead",
ctx: httpContext,
},
{
name: "ActionsUser",
head: "actionsuserhead",
ctx: NewActionsUserTestContext(t, username, reponame),
},
} {
t.Run("CreatePR "+testCase.name, func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
dstPath := t.TempDir()
u.Path = httpContext.GitPath()
u.User = url.UserPassword(httpContext.Username, userPassword)
t.Run("Clone", doGitClone(dstPath, u))
t.Run("PopulateBranch", doActionsUserPopulateBranch(dstPath, &httpContext, "master", testCase.head))
t.Run("CreatePR", doActionsUserPR(httpContext, testCase.ctx, "master", testCase.head))
})
}
}
func doActionsUserPopulateBranch(dstPath string, ctx *APITestContext, baseBranch, headBranch string) func(t *testing.T) {
return func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
t.Run("CreateHeadBranch", doGitCreateBranch(dstPath, headBranch))
t.Run("AddCommit", func(t *testing.T) {
err := os.WriteFile(path.Join(dstPath, "test_file"), []byte("## test content"), 0o666)
if !assert.NoError(t, err) {
return
}
err = git.AddChanges(dstPath, true)
assert.NoError(t, err)
err = git.CommitChanges(dstPath, git.CommitChangesOptions{
Committer: &git.Signature{
Email: "user2@example.com",
Name: "user2",
When: time.Now(),
},
Author: &git.Signature{
Email: "user2@example.com",
Name: "user2",
When: time.Now(),
},
Message: "Testing commit 1",
})
assert.NoError(t, err)
})
t.Run("Push", func(t *testing.T) {
err := git.NewCommand(git.DefaultContext, "push", "origin").AddDynamicArguments("HEAD:refs/heads/" + headBranch).Run(&git.RunOpts{Dir: dstPath})
assert.NoError(t, err)
})
}
}
func doActionsUserPR(ctx, doerCtx APITestContext, baseBranch, headBranch string) func(t *testing.T) {
return func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
var pr api.PullRequest
var err error
// Create a test pullrequest
t.Run("CreatePullRequest", func(t *testing.T) {
pr, err = doAPICreatePullRequest(doerCtx, ctx.Username, ctx.Reponame, baseBranch, headBranch)(t)
assert.NoError(t, err)
})
doerCtx.ExpectedCode = http.StatusCreated
t.Run("AutoMergePR", doAPIAutoMergePullRequest(doerCtx, ctx.Username, ctx.Reponame, pr.Index))
// Ensure the PR page works
t.Run("EnsureCanSeePull", doEnsureCanSeePull(ctx, pr))
}
}

View file

@ -1,23 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"net/http"
"testing"
"code.gitea.io/gitea/tests"
)
func TestPrivateRepoProject(t *testing.T) {
defer tests.PrepareTestEnv(t)()
// not logged in user
req := NewRequest(t, "GET", "/user31/-/projects")
MakeRequest(t, req, http.StatusNotFound)
sess := loginUser(t, "user1")
req = NewRequest(t, "GET", "/user31/-/projects")
sess.MakeRequest(t, req, http.StatusOK)
}

View file

@ -10,8 +10,6 @@ import (
"strings" "strings"
"testing" "testing"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/translation"
@ -49,14 +47,12 @@ func testCreateBranches(t *testing.T, giteaURL *url.URL) {
CreateRelease string CreateRelease string
FlashMessage string FlashMessage string
ExpectedStatus int ExpectedStatus int
CheckBranch bool
}{ }{
{ {
OldRefSubURL: "branch/master", OldRefSubURL: "branch/master",
NewBranch: "feature/test1", NewBranch: "feature/test1",
ExpectedStatus: http.StatusSeeOther, ExpectedStatus: http.StatusSeeOther,
FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature/test1"), FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature/test1"),
CheckBranch: true,
}, },
{ {
OldRefSubURL: "branch/master", OldRefSubURL: "branch/master",
@ -69,7 +65,6 @@ func testCreateBranches(t *testing.T, giteaURL *url.URL) {
NewBranch: "feature=test1", NewBranch: "feature=test1",
ExpectedStatus: http.StatusSeeOther, ExpectedStatus: http.StatusSeeOther,
FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature=test1"), FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature=test1"),
CheckBranch: true,
}, },
{ {
OldRefSubURL: "branch/master", OldRefSubURL: "branch/master",
@ -99,7 +94,6 @@ func testCreateBranches(t *testing.T, giteaURL *url.URL) {
NewBranch: "feature/test3", NewBranch: "feature/test3",
ExpectedStatus: http.StatusSeeOther, ExpectedStatus: http.StatusSeeOther,
FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature/test3"), FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature/test3"),
CheckBranch: true,
}, },
{ {
OldRefSubURL: "branch/master", OldRefSubURL: "branch/master",
@ -114,15 +108,10 @@ func testCreateBranches(t *testing.T, giteaURL *url.URL) {
CreateRelease: "v1.0.1", CreateRelease: "v1.0.1",
ExpectedStatus: http.StatusSeeOther, ExpectedStatus: http.StatusSeeOther,
FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature/test4"), FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature/test4"),
CheckBranch: true,
}, },
} }
session := loginUser(t, "user2")
for _, test := range tests { for _, test := range tests {
if test.CheckBranch { session := loginUser(t, "user2")
unittest.AssertNotExistsBean(t, &git_model.Branch{RepoID: 1, Name: test.NewBranch})
}
if test.CreateRelease != "" { if test.CreateRelease != "" {
createNewRelease(t, session, "/user2/repo1", test.CreateRelease, test.CreateRelease, false, false) createNewRelease(t, session, "/user2/repo1", test.CreateRelease, test.CreateRelease, false, false)
} }
@ -136,9 +125,6 @@ func testCreateBranches(t *testing.T, giteaURL *url.URL) {
test.FlashMessage, test.FlashMessage,
) )
} }
if test.CheckBranch {
unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: 1, Name: test.NewBranch})
}
} }
} }

View file

@ -2,7 +2,7 @@ import $ from 'jquery';
const {pageData} = window.config; const {pageData} = window.config;
async function initInputCitationValue($citationCopyApa, $citationCopyBibtex) { const initInputCitationValue = async ($citationCopyApa, $citationCopyBibtex) => {
const [{Cite, plugins}] = await Promise.all([ const [{Cite, plugins}] = await Promise.all([
import(/* webpackChunkName: "citation-js-core" */'@citation-js/core'), import(/* webpackChunkName: "citation-js-core" */'@citation-js/core'),
import(/* webpackChunkName: "citation-js-formats" */'@citation-js/plugin-software-formats'), import(/* webpackChunkName: "citation-js-formats" */'@citation-js/plugin-software-formats'),
@ -19,9 +19,9 @@ async function initInputCitationValue($citationCopyApa, $citationCopyBibtex) {
const bibtexOutput = citationFormatter.format('bibtex', {lang}); const bibtexOutput = citationFormatter.format('bibtex', {lang});
$citationCopyBibtex.attr('data-text', bibtexOutput); $citationCopyBibtex.attr('data-text', bibtexOutput);
$citationCopyApa.attr('data-text', apaOutput); $citationCopyApa.attr('data-text', apaOutput);
} };
export async function initCitationFileCopyContent() { export function initCitationFileCopyContent() {
const defaultCitationFormat = 'apa'; // apa or bibtex const defaultCitationFormat = 'apa'; // apa or bibtex
if (!pageData.citationFileContent) return; if (!pageData.citationFileContent) return;
@ -39,14 +39,7 @@ export async function initCitationFileCopyContent() {
$citationCopyBibtex.toggleClass('primary', isBibtex); $citationCopyBibtex.toggleClass('primary', isBibtex);
$citationCopyApa.toggleClass('primary', !isBibtex); $citationCopyApa.toggleClass('primary', !isBibtex);
}; };
initInputCitationValue($citationCopyApa, $citationCopyBibtex).then(updateUi);
try {
await initInputCitationValue($citationCopyApa, $citationCopyBibtex);
} catch (e) {
console.error(`initCitationFileCopyContent error: ${e}`, e);
return;
}
updateUi();
$citationCopyApa.on('click', () => { $citationCopyApa.on('click', () => {
localStorage.setItem('citation-copy-format', 'apa'); localStorage.setItem('citation-copy-format', 'apa');

View file

@ -6,7 +6,7 @@ import {initCompColorPicker} from './comp/ColorPicker.js';
import {showGlobalErrorMessage} from '../bootstrap.js'; import {showGlobalErrorMessage} from '../bootstrap.js';
import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js'; import {handleGlobalEnterQuickSubmit} from './comp/QuickSubmit.js';
import {svg} from '../svg.js'; import {svg} from '../svg.js';
import {hideElem, showElem, toggleElem, initSubmitEventPolyfill, submitEventSubmitter} from '../utils/dom.js'; import {hideElem, showElem, toggleElem} from '../utils/dom.js';
import {htmlEscape} from 'escape-goat'; import {htmlEscape} from 'escape-goat';
import {showTemporaryTooltip} from '../modules/tippy.js'; import {showTemporaryTooltip} from '../modules/tippy.js';
import {confirmModal} from './comp/ConfirmModal.js'; import {confirmModal} from './comp/ConfirmModal.js';
@ -122,8 +122,7 @@ async function formFetchAction(e) {
const formMethod = formEl.getAttribute('method') || 'get'; const formMethod = formEl.getAttribute('method') || 'get';
const formActionUrl = formEl.getAttribute('action'); const formActionUrl = formEl.getAttribute('action');
const formData = new FormData(formEl); const formData = new FormData(formEl);
const formSubmitter = submitEventSubmitter(e); const [submitterName, submitterValue] = [e.submitter?.getAttribute('name'), e.submitter?.getAttribute('value')];
const [submitterName, submitterValue] = [formSubmitter?.getAttribute('name'), formSubmitter?.getAttribute('value')];
if (submitterName) { if (submitterName) {
formData.append(submitterName, submitterValue || ''); formData.append(submitterName, submitterValue || '');
} }
@ -194,7 +193,6 @@ export function initGlobalCommon() {
$('.tabular.menu .item').tab(); $('.tabular.menu .item').tab();
initSubmitEventPolyfill();
document.addEventListener('submit', formFetchAction); document.addEventListener('submit', formFetchAction);
document.addEventListener('click', linkAction); document.addEventListener('click', linkAction);
} }

View file

@ -1,5 +1,5 @@
import $ from 'jquery'; import $ from 'jquery';
import {isElemHidden, onInputDebounce, submitEventSubmitter, toggleElem} from '../utils/dom.js'; import {isElemHidden, onInputDebounce, toggleElem} from '../utils/dom.js';
import {GET} from '../modules/fetch.js'; import {GET} from '../modules/fetch.js';
const {appSubUrl} = window.config; const {appSubUrl} = window.config;
@ -40,7 +40,7 @@ export function initCommonIssueListQuickGoto() {
$form.on('submit', (e) => { $form.on('submit', (e) => {
// if there is no goto button, or the form is submitted by non-quick-goto elements, submit the form directly // if there is no goto button, or the form is submitted by non-quick-goto elements, submit the form directly
let doQuickGoto = !isElemHidden($goto); let doQuickGoto = !isElemHidden($goto);
const submitter = submitEventSubmitter(e.originalEvent); const submitter = e.originalEvent.submitter;
if (submitter !== $form[0] && submitter !== $input[0] && submitter !== $goto[0]) doQuickGoto = false; if (submitter !== $form[0] && submitter !== $input[0] && submitter !== $goto[0]) doQuickGoto = false;
if (!doQuickGoto) return; if (!doQuickGoto) return;

View file

@ -7,7 +7,6 @@ import {validateTextareaNonEmpty} from './comp/ComboMarkdownEditor.js';
import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.js'; import {initViewedCheckboxListenerFor, countAndUpdateViewedFiles, initExpandAndCollapseFilesButton} from './pull-view-file.js';
import {initImageDiff} from './imagediff.js'; import {initImageDiff} from './imagediff.js';
import {showErrorToast} from '../modules/toast.js'; import {showErrorToast} from '../modules/toast.js';
import {submitEventSubmitter} from '../utils/dom.js';
const {csrfToken, pageData, i18n} = window.config; const {csrfToken, pageData, i18n} = window.config;
@ -58,7 +57,7 @@ function initRepoDiffConversationForm() {
const formData = new FormData($form[0]); const formData = new FormData($form[0]);
// if the form is submitted by a button, append the button's name and value to the form data // if the form is submitted by a button, append the button's name and value to the form data
const submitter = submitEventSubmitter(e.originalEvent); const submitter = e.originalEvent?.submitter;
const isSubmittedByButton = (submitter?.nodeName === 'BUTTON') || (submitter?.nodeName === 'INPUT' && submitter.type === 'submit'); const isSubmittedByButton = (submitter?.nodeName === 'BUTTON') || (submitter?.nodeName === 'INPUT' && submitter.type === 'submit');
if (isSubmittedByButton && submitter.name) { if (isSubmittedByButton && submitter.name) {
formData.append(submitter.name, submitter.value); formData.append(submitter.name, submitter.value);

View file

@ -106,7 +106,7 @@ function switchTitleToTooltip(target) {
/** /**
* Creating tooltip tippy instance is expensive, so we only create it when the user hovers over the element * Creating tooltip tippy instance is expensive, so we only create it when the user hovers over the element
* According to https://www.w3.org/TR/DOM-Level-3-Events/#events-mouseevent-event-order , mouseover event is fired before mouseenter event * According to https://www.w3.org/TR/DOM-Level-3-Events/#events-mouseevent-event-order , mouseover event is fired before mouseenter event
* Some browsers like PaleMoon don't support "addEventListener('mouseenter', capture)" * Some old browsers like Pale Moon doesn't support "mouseenter(capture)"
* The tippy by default uses "mouseenter" event to show, so we use "mouseover" event to switch to tippy * The tippy by default uses "mouseenter" event to show, so we use "mouseover" event to switch to tippy
* @param e {Event} * @param e {Event}
*/ */

View file

@ -194,24 +194,3 @@ export function loadElem(el, src) {
el.src = src; el.src = src;
}); });
} }
// some browsers like PaleMoon don't have "SubmitEvent" support, so polyfill it by a tricky method: use the last clicked button as submitter
// it can't use other transparent polyfill patches because PaleMoon also doesn't support "addEventListener(capture)"
const needSubmitEventPolyfill = typeof SubmitEvent === 'undefined';
export function submitEventSubmitter(e) {
return needSubmitEventPolyfill ? (e.target._submitter || null) : e.submitter;
}
function submitEventPolyfillListener(e) {
const form = e.target.closest('form');
if (!form) return;
form._submitter = e.target.closest('button:not([type]), button[type="submit"], input[type="submit"]');
}
export function initSubmitEventPolyfill() {
if (!needSubmitEventPolyfill) return;
console.warn(`This browser doesn't have "SubmitEvent" support, use a tricky method to polyfill`);
document.body.addEventListener('click', submitEventPolyfillListener);
document.body.addEventListener('focus', submitEventPolyfillListener);
}

View file

@ -1,4 +1,4 @@
import '@webcomponents/custom-elements'; // polyfill for some browsers like PaleMoon import '@webcomponents/custom-elements'; // polyfill for some browsers like Pale Moon
import './polyfill.js'; import './polyfill.js';
import '@github/relative-time-element'; import '@github/relative-time-element';