diff --git a/.gitattributes b/.gitattributes index 52695f70c2..e218bbe25d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,6 +4,7 @@ /assets/*.json linguist-generated /public/assets/img/svg/*.svg linguist-generated /templates/swagger/v1_json.tmpl linguist-generated +/options/fileicon/** linguist-generated /vendor/** -text -eol linguist-vendored /web_src/js/vendor/** -text -eol linguist-vendored Dockerfile.* linguist-language=Dockerfile diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index 2264c9e822..f459e3910d 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -59,6 +59,8 @@ jobs: aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress nightly-docker-rootful: runs-on: namespace-profile-gitea-release-docker + permissions: + packages: write # to publish to ghcr.io steps: - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions @@ -85,6 +87,12 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to GHCR using PAT + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} - name: fetch go modules run: make vendor - name: build rootful docker image @@ -93,9 +101,13 @@ jobs: context: . platforms: linux/amd64,linux/arm64 push: true - tags: gitea/gitea:${{ steps.clean_name.outputs.branch }} + tags: |- + gitea/gitea:${{ steps.clean_name.outputs.branch }} + ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }} nightly-docker-rootless: runs-on: namespace-profile-gitea-release-docker + permissions: + packages: write # to publish to ghcr.io steps: - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions @@ -122,6 +134,12 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to GHCR using PAT + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} - name: fetch go modules run: make vendor - name: build rootless docker image @@ -131,4 +149,6 @@ jobs: platforms: linux/amd64,linux/arm64 push: true file: Dockerfile.rootless - tags: gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless + tags: |- + gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless + ghcr.io/go-gitea/gitea:${{ steps.clean_name.outputs.branch }}-rootless diff --git a/.github/workflows/release-tag-rc.yml b/.github/workflows/release-tag-rc.yml index a406602dc0..02da6d1eab 100644 --- a/.github/workflows/release-tag-rc.yml +++ b/.github/workflows/release-tag-rc.yml @@ -69,6 +69,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} docker-rootful: runs-on: namespace-profile-gitea-release-docker + permissions: + packages: write # to publish to ghcr.io steps: - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions @@ -79,7 +81,9 @@ jobs: - uses: docker/metadata-action@v5 id: meta with: - images: gitea/gitea + images: |- + gitea/gitea + ghcr.io/go-gitea/gitea flavor: | latest=false # 1.2.3-rc0 @@ -90,6 +94,12 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to GHCR using PAT + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} - name: build rootful docker image uses: docker/build-push-action@v5 with: @@ -100,6 +110,8 @@ jobs: labels: ${{ steps.meta.outputs.labels }} docker-rootless: runs-on: namespace-profile-gitea-release-docker + permissions: + packages: write # to publish to ghcr.io steps: - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions @@ -110,7 +122,9 @@ jobs: - uses: docker/metadata-action@v5 id: meta with: - images: gitea/gitea + images: |- + gitea/gitea + ghcr.io/go-gitea/gitea # each tag below will have the suffix of -rootless flavor: | latest=false @@ -123,6 +137,12 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to GHCR using PAT + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} - name: build rootless docker image uses: docker/build-push-action@v5 with: diff --git a/.github/workflows/release-tag-version.yml b/.github/workflows/release-tag-version.yml index 08bb9baecf..158945b615 100644 --- a/.github/workflows/release-tag-version.yml +++ b/.github/workflows/release-tag-version.yml @@ -14,6 +14,8 @@ concurrency: jobs: binary: runs-on: namespace-profile-gitea-release-binary + permissions: + packages: write # to publish to ghcr.io steps: - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions @@ -71,6 +73,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} docker-rootful: runs-on: namespace-profile-gitea-release-docker + permissions: + packages: write # to publish to ghcr.io steps: - uses: actions/checkout@v4 # fetch all commits instead of only the last as some branches are long lived and could have many between versions @@ -81,7 +85,9 @@ jobs: - uses: docker/metadata-action@v5 id: meta with: - images: gitea/gitea + images: |- + gitea/gitea + ghcr.io/go-gitea/gitea # this will generate tags in the following format: # latest # 1 @@ -96,6 +102,12 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to GHCR using PAT + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} - name: build rootful docker image uses: docker/build-push-action@v5 with: @@ -116,7 +128,9 @@ jobs: - uses: docker/metadata-action@v5 id: meta with: - images: gitea/gitea + images: |- + gitea/gitea + ghcr.io/go-gitea/gitea # each tag below will have the suffix of -rootless flavor: | suffix=-rootless,onlatest=true @@ -134,6 +148,12 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Login to GHCR using PAT + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} - name: build rootless docker image uses: docker/build-push-action@v5 with: diff --git a/.golangci.yml b/.golangci.yml index cf7a6f1a1f..c176d2115c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,7 +1,9 @@ +version: "2" +output: + sort-order: + - file linters: - enable-all: false - disable-all: true - fast: false + default: none enable: - bidichk - depguard @@ -9,141 +11,162 @@ linters: - errcheck - forbidigo - gocritic - - gofmt - - gofumpt - - gosimple - govet - ineffassign + - mirror - nakedret - nolintlint + - perfsprint - revive - staticcheck - - stylecheck - testifylint - - typecheck - unconvert - - unused - unparam + - unused + - usestdlibvars - usetesting - wastedassign - -run: - timeout: 10m - -output: - sort-results: true - sort-order: [file] - show-stats: true - -linters-settings: - testifylint: - disable: - - go-require - - require-error - stylecheck: - checks: ["all", "-ST1005", "-ST1003"] - nakedret: - max-func-lines: 0 - gocritic: - disabled-checks: - - ifElseChain - - singleCaseSwitch # Every time this occurred in the code, there was no other way. - revive: - severity: error + settings: + depguard: + rules: + main: + deny: + - pkg: encoding/json + desc: use gitea's modules/json instead of encoding/json + - pkg: github.com/unknwon/com + desc: use gitea's util and replacements + - pkg: io/ioutil + desc: use os or io instead + - pkg: golang.org/x/exp + desc: it's experimental and unreliable + - pkg: code.gitea.io/gitea/modules/git/internal + desc: do not use the internal package, use AddXxx function instead + - pkg: gopkg.in/ini.v1 + desc: do not use the ini package, use gitea's config system instead + - pkg: gitea.com/go-chi/cache + desc: do not use the go-chi cache package, use gitea's cache system + gocritic: + disabled-checks: + - ifElseChain + - singleCaseSwitch # Every time this occurred in the code, there was no other way. + revive: + severity: error + rules: + - name: atomic + - name: bare-return + - name: blank-imports + - name: constant-logical-expr + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + - name: duplicated-imports + - name: empty-lines + - name: error-naming + - name: error-return + - name: error-strings + - name: errorf + - name: exported + - name: identical-branches + - name: if-return + - name: increment-decrement + - name: indent-error-flow + - name: modifies-value-receiver + - name: package-comments + - name: range + - name: receiver-naming + - name: redefines-builtin-id + - name: string-of-int + - name: superfluous-else + - name: time-naming + - name: unconditional-recursion + - name: unexported-return + - name: unreachable-code + - name: var-declaration + - name: var-naming + staticcheck: + checks: + - all + - -ST1003 + - -ST1005 + - -QF1001 + - -QF1006 + - -QF1008 + testifylint: + disable: + - go-require + - require-error + usetesting: + os-temp-dir: true + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling rules: - - name: atomic - - name: bare-return - - name: blank-imports - - name: constant-logical-expr - - name: context-as-argument - - name: context-keys-type - - name: dot-imports - - name: duplicated-imports - - name: empty-lines - - name: error-naming - - name: error-return - - name: error-strings - - name: errorf - - name: exported - - name: identical-branches - - name: if-return - - name: increment-decrement - - name: indent-error-flow - - name: modifies-value-receiver - - name: package-comments - - name: range - - name: receiver-naming - - name: redefines-builtin-id - - name: string-of-int - - name: superfluous-else - - name: time-naming - - name: unconditional-recursion - - name: unexported-return - - name: unreachable-code - - name: var-declaration - - name: var-naming - gofumpt: - extra-rules: true - depguard: - rules: - main: - deny: - - pkg: encoding/json - desc: use gitea's modules/json instead of encoding/json - - pkg: github.com/unknwon/com - desc: use gitea's util and replacements - - pkg: io/ioutil - desc: use os or io instead - - pkg: golang.org/x/exp - desc: it's experimental and unreliable - - pkg: code.gitea.io/gitea/modules/git/internal - desc: do not use the internal package, use AddXxx function instead - - pkg: gopkg.in/ini.v1 - desc: do not use the ini package, use gitea's config system instead - - pkg: gitea.com/go-chi/cache - desc: do not use the go-chi cache package, use gitea's cache system - usetesting: - os-temp-dir: true - + - linters: + - dupl + - errcheck + - gocyclo + - gosec + - staticcheck + - unparam + path: _test\.go + - linters: + - dupl + - errcheck + - gocyclo + - gosec + path: models/migrations/v + - linters: + - forbidigo + path: cmd + - linters: + - dupl + text: (?i)webhook + - linters: + - gocritic + text: (?i)`ID' should not be capitalized + - linters: + - deadcode + - unused + text: (?i)swagger + - linters: + - staticcheck + text: (?i)argument x is overwritten before first use + - linters: + - gocritic + text: '(?i)commentFormatting: put a space between `//` and comment text' + - linters: + - gocritic + text: '(?i)exitAfterDefer:' + paths: + - node_modules + - public + - web_src + - third_party$ + - builtin$ + - examples$ issues: max-issues-per-linter: 0 max-same-issues: 0 - exclude-dirs: [node_modules, public, web_src] - exclude-case-sensitive: true - exclude-rules: - - path: _test\.go - linters: - - gocyclo - - errcheck - - dupl - - gosec - - unparam - - staticcheck - - path: models/migrations/v - linters: - - gocyclo - - errcheck - - dupl - - gosec - - path: cmd - linters: - - forbidigo - - text: "webhook" - linters: - - dupl - - text: "`ID' should not be capitalized" - linters: - - gocritic - - text: "swagger" - linters: - - unused - - deadcode - - text: "argument x is overwritten before first use" - linters: - - staticcheck - - text: "commentFormatting: put a space between `//` and comment text" - linters: - - gocritic - - text: "exitAfterDefer:" - linters: - - gocritic +formatters: + enable: + - gofmt + - gofumpt + settings: + gofumpt: + extra-rules: true + exclusions: + generated: lax + paths: + - node_modules + - public + - web_src + - third_party$ + - builtin$ + - examples$ + +run: + timeout: 10m diff --git a/Makefile b/Makefile index aefd38bba5..685cbf6589 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ XGO_VERSION := go-1.24.x AIR_PACKAGE ?= github.com/air-verse/air@v1 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.2.1 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0 -GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.7 +GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.0.2 GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.12 MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.6.0 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.31.0 diff --git a/README.md b/README.md index 5ae65cd2ac..017ca629d0 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ [](https://gitpod.io/#https://github.com/go-gitea/gitea) [](https://translate.gitea.com "Crowdin") -[View this document in Chinese](./README_ZH.md) +[繁體中文](./README.zh-tw.md) | [简体中文](./README.zh-cn.md) ## Purpose diff --git a/README.zh-cn.md b/README.zh-cn.md new file mode 100644 index 0000000000..f34b25b945 --- /dev/null +++ b/README.zh-cn.md @@ -0,0 +1,206 @@ +# Gitea + +[](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml?query=branch%3Amain "Release Nightly") +[](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea") +[](https://goreportcard.com/report/code.gitea.io/gitea "Go Report Card") +[](https://pkg.go.dev/code.gitea.io/gitea "GoDoc") +[](https://github.com/go-gitea/gitea/releases/latest "GitHub release") +[](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source") +[](https://opencollective.com/gitea "Become a backer/sponsor of gitea") +[](https://opensource.org/licenses/MIT "License: MIT") +[](https://gitpod.io/#https://github.com/go-gitea/gitea) +[](https://translate.gitea.com "Crowdin") + +[English](./README.md) | [繁體中文](./README.zh-tw.md) + +## 目的 + +这个项目的目标是提供最简单、最快速、最无痛的方式来设置自托管的 Git 服务。 + +由于 Gitea 是用 Go 语言编写的,它可以在 Go 支持的所有平台和架构上运行,包括 Linux、macOS 和 Windows 的 x86、amd64、ARM 和 PowerPC 架构。这个项目自 2016 年 11 月从 [Gogs](https://gogs.io) [分叉](https://blog.gitea.com/welcome-to-gitea/) 而来,但已经有了很多变化。 + +在线演示可以访问 [demo.gitea.com](https://demo.gitea.com)。 + +要访问免费的 Gitea 服务(有一定数量的仓库限制),可以访问 [gitea.com](https://gitea.com/user/login)。 + +要快速部署您自己的专用 Gitea 实例,可以在 [cloud.gitea.com](https://cloud.gitea.com) 开始免费试用。 + +## 文件 + +您可以在我们的官方 [文件网站](https://docs.gitea.com/) 上找到全面的文件。 + +它包括安装、管理、使用、开发、贡献指南等,帮助您快速入门并有效地探索所有功能。 + +如果您有任何建议或想要贡献,可以访问 [文件仓库](https://gitea.com/gitea/docs) + +## 构建 + +从源代码树的根目录运行: + + TAGS="bindata" make build + +如果需要 SQLite 支持: + + TAGS="bindata sqlite sqlite_unlock_notify" make build + +`build` 目标分为两个子目标: + +- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定义。 +- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本。 + +需要互联网连接来下载 go 和 npm 模块。从包含预构建前端文件的官方源代码压缩包构建时,不会触发 `frontend` 目标,因此可以在没有 Node.js 的情况下构建。 + +更多信息:https://docs.gitea.com/installation/install-from-source + +## 使用 + +构建后,默认情况下会在源代码树的根目录生成一个名为 `gitea` 的二进制文件。要运行它,请使用: + + ./gitea web + +> [!注意] +> 如果您对使用我们的 API 感兴趣,我们提供了实验性支持,并附有 [文件](https://docs.gitea.com/api)。 + +## 贡献 + +预期的工作流程是:Fork -> Patch -> Push -> Pull Request + +> [!注意] +> +> 1. **在开始进行 Pull Request 之前,您必须阅读 [贡献者指南](CONTRIBUTING.md)。** +> 2. 如果您在项目中发现了漏洞,请私下写信给 **security@gitea.io**。谢谢! + +## 翻译 + +[](https://translate.gitea.com) + +翻译通过 [Crowdin](https://translate.gitea.com) 进行。如果您想翻译成新的语言,请在 Crowdin 项目中请求管理员添加新语言。 + +您也可以创建一个 issue 来添加语言,或者在 discord 的 #translation 频道上询问。如果您需要上下文或发现一些翻译问题,可以在字符串上留言或在 Discord 上询问。对于一般的翻译问题,文档中有一个部分。目前有点空,但我们希望随着问题的出现而填充它。 + +更多信息请参阅 [文件](https://docs.gitea.com/contributing/localization)。 + +## 官方和第三方项目 + +我们提供了一个官方的 [go-sdk](https://gitea.com/gitea/go-sdk),一个名为 [tea](https://gitea.com/gitea/tea) 的 CLI 工具和一个 Gitea Action 的 [action runner](https://gitea.com/gitea/act_runner)。 + +我们在 [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea) 维护了一个 Gitea 相关项目的列表,您可以在那里发现更多的第三方项目,包括 SDK、插件、主题等。 + +## 通讯 + +[](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea") + +如果您有任何文件未涵盖的问题,可以在我们的 [Discord 服务器](https://discord.gg/Gitea) 上与我们联系,或者在 [discourse 论坛](https://forum.gitea.com/) 上创建帖子。 + +## 作者 + +- [维护者](https://github.com/orgs/go-gitea/people) +- [贡献者](https://github.com/go-gitea/gitea/graphs/contributors) +- [翻译者](options/locale/TRANSLATORS) + +## 支持者 + +感谢所有支持者! 🙏 [[成为支持者](https://opencollective.com/gitea#backer)] + + + +## 赞助商 + +通过成为赞助商来支持这个项目。您的标志将显示在这里,并带有链接到您的网站。 [[成为赞助商](https://opencollective.com/gitea#sponsor)] + + + + + + + + + + + + +## 常见问题 + +**Gitea 怎么发音?** + +Gitea 的发音是 [/ɡɪ’ti:/](https://youtu.be/EM71-2uDAoY),就像 "gi-tea" 一样,g 是硬音。 + +**为什么这个项目没有托管在 Gitea 实例上?** + +我们正在 [努力](https://github.com/go-gitea/gitea/issues/1029)。 + +**在哪里可以找到安全补丁?** + +在 [发布日志](https://github.com/go-gitea/gitea/releases) 或 [变更日志](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md) 中,搜索关键词 `SECURITY` 以找到安全补丁。 + +## 许可证 + +这个项目是根据 MIT 许可证授权的。 +请参阅 [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) 文件以获取完整的许可证文本。 + +## 进一步信息 + + +寻找界面概述?查看这里! + +### 登录/注册页面 + + + + +### 用户仪表板 + + + + + + +### 用户资料 + + + +### 探索 + + + + + +### 仓库 + + + + + + + + + +#### 仓库问题 + + + + +#### 仓库拉取请求 + + + + + + +#### 仓库操作 + + + + +#### 仓库活动 + + + + + + +### 组织 + + + + diff --git a/README.zh-tw.md b/README.zh-tw.md new file mode 100644 index 0000000000..9de3f85dd5 --- /dev/null +++ b/README.zh-tw.md @@ -0,0 +1,206 @@ +# Gitea + +[](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml?query=branch%3Amain "Release Nightly") +[](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea") +[](https://goreportcard.com/report/code.gitea.io/gitea "Go Report Card") +[](https://pkg.go.dev/code.gitea.io/gitea "GoDoc") +[](https://github.com/go-gitea/gitea/releases/latest "GitHub release") +[](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source") +[](https://opencollective.com/gitea "Become a backer/sponsor of gitea") +[](https://opensource.org/licenses/MIT "License: MIT") +[](https://gitpod.io/#https://github.com/go-gitea/gitea) +[](https://translate.gitea.com "Crowdin") + +[English](./README.md) | [简体中文](./README.zh-cn.md) + +## 目的 + +這個項目的目標是提供最簡單、最快速、最無痛的方式來設置自託管的 Git 服務。 + +由於 Gitea 是用 Go 語言編寫的,它可以在 Go 支援的所有平台和架構上運行,包括 Linux、macOS 和 Windows 的 x86、amd64、ARM 和 PowerPC 架構。這個項目自 2016 年 11 月從 [Gogs](https://gogs.io) [分叉](https://blog.gitea.com/welcome-to-gitea/) 而來,但已經有了很多變化。 + +在線演示可以訪問 [demo.gitea.com](https://demo.gitea.com)。 + +要訪問免費的 Gitea 服務(有一定數量的倉庫限制),可以訪問 [gitea.com](https://gitea.com/user/login)。 + +要快速部署您自己的專用 Gitea 實例,可以在 [cloud.gitea.com](https://cloud.gitea.com) 開始免費試用。 + +## 文件 + +您可以在我們的官方 [文件網站](https://docs.gitea.com/) 上找到全面的文件。 + +它包括安裝、管理、使用、開發、貢獻指南等,幫助您快速入門並有效地探索所有功能。 + +如果您有任何建議或想要貢獻,可以訪問 [文件倉庫](https://gitea.com/gitea/docs) + +## 構建 + +從源代碼樹的根目錄運行: + + TAGS="bindata" make build + +如果需要 SQLite 支援: + + TAGS="bindata sqlite sqlite_unlock_notify" make build + +`build` 目標分為兩個子目標: + +- `make backend` 需要 [Go Stable](https://go.dev/dl/),所需版本在 [go.mod](/go.mod) 中定義。 +- `make frontend` 需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本。 + +需要互聯網連接來下載 go 和 npm 模塊。從包含預構建前端文件的官方源代碼壓縮包構建時,不會觸發 `frontend` 目標,因此可以在沒有 Node.js 的情況下構建。 + +更多信息:https://docs.gitea.com/installation/install-from-source + +## 使用 + +構建後,默認情況下會在源代碼樹的根目錄生成一個名為 `gitea` 的二進制文件。要運行它,請使用: + + ./gitea web + +> [!注意] +> 如果您對使用我們的 API 感興趣,我們提供了實驗性支援,並附有 [文件](https://docs.gitea.com/api)。 + +## 貢獻 + +預期的工作流程是:Fork -> Patch -> Push -> Pull Request + +> [!注意] +> +> 1. **在開始進行 Pull Request 之前,您必須閱讀 [貢獻者指南](CONTRIBUTING.md)。** +> 2. 如果您在項目中發現了漏洞,請私下寫信給 **security@gitea.io**。謝謝! + +## 翻譯 + +[](https://translate.gitea.com) + +翻譯通過 [Crowdin](https://translate.gitea.com) 進行。如果您想翻譯成新的語言,請在 Crowdin 項目中請求管理員添加新語言。 + +您也可以創建一個 issue 來添加語言,或者在 discord 的 #translation 頻道上詢問。如果您需要上下文或發現一些翻譯問題,可以在字符串上留言或在 Discord 上詢問。對於一般的翻譯問題,文檔中有一個部分。目前有點空,但我們希望隨著問題的出現而填充它。 + +更多信息請參閱 [文件](https://docs.gitea.com/contributing/localization)。 + +## 官方和第三方項目 + +我們提供了一個官方的 [go-sdk](https://gitea.com/gitea/go-sdk),一個名為 [tea](https://gitea.com/gitea/tea) 的 CLI 工具和一個 Gitea Action 的 [action runner](https://gitea.com/gitea/act_runner)。 + +我們在 [gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea) 維護了一個 Gitea 相關項目的列表,您可以在那裡發現更多的第三方項目,包括 SDK、插件、主題等。 + +## 通訊 + +[](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea") + +如果您有任何文件未涵蓋的問題,可以在我們的 [Discord 服務器](https://discord.gg/Gitea) 上與我們聯繫,或者在 [discourse 論壇](https://forum.gitea.com/) 上創建帖子。 + +## 作者 + +- [維護者](https://github.com/orgs/go-gitea/people) +- [貢獻者](https://github.com/go-gitea/gitea/graphs/contributors) +- [翻譯者](options/locale/TRANSLATORS) + +## 支持者 + +感謝所有支持者! 🙏 [[成為支持者](https://opencollective.com/gitea#backer)] + + + +## 贊助商 + +通過成為贊助商來支持這個項目。您的標誌將顯示在這裡,並帶有鏈接到您的網站。 [[成為贊助商](https://opencollective.com/gitea#sponsor)] + + + + + + + + + + + + +## 常見問題 + +**Gitea 怎麼發音?** + +Gitea 的發音是 [/ɡɪ’ti:/](https://youtu.be/EM71-2uDAoY),就像 "gi-tea" 一樣,g 是硬音。 + +**為什麼這個項目沒有託管在 Gitea 實例上?** + +我們正在 [努力](https://github.com/go-gitea/gitea/issues/1029)。 + +**在哪裡可以找到安全補丁?** + +在 [發佈日誌](https://github.com/go-gitea/gitea/releases) 或 [變更日誌](https://github.com/go-gitea/gitea/blob/main/CHANGELOG.md) 中,搜索關鍵詞 `SECURITY` 以找到安全補丁。 + +## 許可證 + +這個項目是根據 MIT 許可證授權的。 +請參閱 [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) 文件以獲取完整的許可證文本。 + +## 進一步信息 + + +尋找界面概述?查看這裡! + +### 登錄/註冊頁面 + + + + +### 用戶儀表板 + + + + + + +### 用戶資料 + + + +### 探索 + + + + + +### 倉庫 + + + + + + + + + +#### 倉庫問題 + + + + +#### 倉庫拉取請求 + + + + + + +#### 倉庫操作 + + + + +#### 倉庫活動 + + + + + + +### 組織 + + + + diff --git a/README_ZH.md b/README_ZH.md deleted file mode 100644 index 89c34f6b63..0000000000 --- a/README_ZH.md +++ /dev/null @@ -1,156 +0,0 @@ -# Gitea - -[](https://github.com/go-gitea/gitea/actions/workflows/release-nightly.yml?query=branch%3Amain "Release Nightly") -[](https://discord.gg/Gitea "Join the Discord chat at https://discord.gg/Gitea") -[](https://goreportcard.com/report/code.gitea.io/gitea "Go Report Card") -[](https://pkg.go.dev/code.gitea.io/gitea "GoDoc") -[](https://github.com/go-gitea/gitea/releases/latest "GitHub release") -[](https://www.codetriage.com/go-gitea/gitea "Help Contribute to Open Source") -[](https://opencollective.com/gitea "Become a backer/sponsor of gitea") -[](https://opensource.org/licenses/MIT "License: MIT") -[](https://gitpod.io/#https://github.com/go-gitea/gitea) -[](https://translate.gitea.com "Crowdin") - -[View this document in English](./README.md) - -## 目标 - -Gitea 的首要目标是创建一个极易安装,运行非常快速,安装和使用体验良好的自建 Git 服务。我们采用 Go 作为后端语言,这使我们只要生成一个可执行程序即可。并且他还支持跨平台,支持 Linux、macOS 和 Windows 以及各种架构,除了 x86 和 amd64,还包括 ARM 和 PowerPC。 - -如果你想试用在线演示和报告问题,请访问 [demo.gitea.com](https://demo.gitea.com/)。 - -如果你想使用免费的 Gitea 服务(有仓库数量限制),请访问 [gitea.com](https://gitea.com/user/login)。 - -如果你想在 Gitea Cloud 上快速部署你自己独享的 Gitea 实例,请访问 [cloud.gitea.com](https://cloud.gitea.com) 开始免费试用。 - -## 文档 - -关于如何安装请访问我们的 [文档站](https://docs.gitea.com/zh-cn/category/installation),如果没有找到对应的文档,你也可以通过 [Discord - 英文](https://discord.gg/gitea) 和 QQ群 328432459 来和我们交流。 - -## 编译 - -在源代码的根目录下执行: - - TAGS="bindata" make build - -或者如果需要SQLite支持: - - TAGS="bindata sqlite sqlite_unlock_notify" make build - -编译过程会分成2个子任务: - -- `make backend`,需要 [Go Stable](https://go.dev/dl/),最低版本需求可查看 [go.mod](/go.mod)。 -- `make frontend`,需要 [Node.js LTS](https://nodejs.org/en/download/) 或更高版本。 - -你需要连接网络来下载 go 和 npm modules。当从 tar 格式的源文件编译时,其中包含了预编译的前端文件,因此 `make frontend` 将不会被执行。这允许编译时不需要 Node.js。 - -更多信息: https://docs.gitea.com/installation/install-from-source - -## 使用 - -编译之后,默认会在根目录下生成一个名为 `gitea` 的文件。你可以这样执行它: - - ./gitea web - -> [!注意] -> 如果你要使用API,请参见 [API 文档](https://godoc.org/code.gitea.io/sdk/gitea)。 - -## 贡献 - -贡献流程:Fork -> Patch -> Push -> Pull Request - -> [!注意] -> -> 1. **开始贡献代码之前请确保你已经看过了 [贡献者向导(英文)](CONTRIBUTING.md)**。 -> 2. 所有的安全问题,请私下发送邮件给 **security@gitea.io**。 谢谢! - -## 翻译 - -[](https://translate.gitea.com) - -多语言翻译是基于Crowdin进行的。 - -从 [文档](https://docs.gitea.com/contributing/localization) 中获取更多信息。 - -## 官方和第三方项目 - -Gitea 提供官方的 [go-sdk](https://gitea.com/gitea/go-sdk),以及名为 [tea](https://gitea.com/gitea/tea) 的 CLI 工具 和 用于 Gitea Action 的 [action runner](https://gitea.com/gitea/act_runner)。 - -[gitea/awesome-gitea](https://gitea.com/gitea/awesome-gitea) 是一个 Gitea 相关项目的列表,你可以在这里找到更多的第三方项目,包括 SDK、插件、主题等等。 - -## 作者 - -- [Maintainers](https://github.com/orgs/go-gitea/people) -- [Contributors](https://github.com/go-gitea/gitea/graphs/contributors) -- [Translators](options/locale/TRANSLATORS) - -## 授权许可 - -本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/go-gitea/gitea/blob/main/LICENSE) 文件中。 - -## 更多信息 - - -截图 - -### 登录界面 - - - - -### 用户首页 - - - - - - -### 用户资料 - - - -### 探索 - - - - - -### 仓库 - - - - - - - - - -#### 仓库工单 - - - - -#### 仓库合并请求 - - - - - - -#### 仓库 Actions - - - - -#### 仓库动态 - - - - - - -### 组织 - - - - diff --git a/cmd/admin_auth_ldap_test.go b/cmd/admin_auth_ldap_test.go index bab42226ae..ea9a83ef76 100644 --- a/cmd/admin_auth_ldap_test.go +++ b/cmd/admin_auth_ldap_test.go @@ -229,11 +229,11 @@ func TestAddLdapBindDn(t *testing.T) { return nil }, updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { - assert.FailNow(t, "case %d: should not call updateAuthSource", n) + assert.FailNow(t, "updateAuthSource called", "case %d: should not call updateAuthSource", n) return nil }, getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) { - assert.FailNow(t, "case %d: should not call getAuthSourceByID", n) + assert.FailNow(t, "getAuthSourceByID called", "case %d: should not call getAuthSourceByID", n) return nil, nil }, } @@ -460,11 +460,11 @@ func TestAddLdapSimpleAuth(t *testing.T) { return nil }, updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { - assert.FailNow(t, "case %d: should not call updateAuthSource", n) + assert.FailNow(t, "updateAuthSource called", "case %d: should not call updateAuthSource", n) return nil }, getAuthSourceByID: func(ctx context.Context, id int64) (*auth.Source, error) { - assert.FailNow(t, "case %d: should not call getAuthSourceByID", n) + assert.FailNow(t, "getAuthSourceById called", "case %d: should not call getAuthSourceByID", n) return nil, nil }, } @@ -925,7 +925,7 @@ func TestUpdateLdapBindDn(t *testing.T) { return nil }, createAuthSource: func(ctx context.Context, authSource *auth.Source) error { - assert.FailNow(t, "case %d: should not call createAuthSource", n) + assert.FailNow(t, "createAuthSource called", "case %d: should not call createAuthSource", n) return nil }, updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { @@ -1315,7 +1315,7 @@ func TestUpdateLdapSimpleAuth(t *testing.T) { return nil }, createAuthSource: func(ctx context.Context, authSource *auth.Source) error { - assert.FailNow(t, "case %d: should not call createAuthSource", n) + assert.FailNow(t, "createAuthSource called", "case %d: should not call createAuthSource", n) return nil }, updateAuthSource: func(ctx context.Context, authSource *auth.Source) error { diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go index 5e03d6ca3f..ebe0266d1f 100644 --- a/cmd/admin_user_create.go +++ b/cmd/admin_user_create.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "strings" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" @@ -66,6 +67,16 @@ var microcmdUserCreate = &cli.Command{ Name: "access-token", Usage: "Generate access token for the user", }, + &cli.StringFlag{ + Name: "access-token-name", + Usage: `Name of the generated access token`, + Value: "gitea-admin", + }, + &cli.StringFlag{ + Name: "access-token-scopes", + Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`, + Value: "all", + }, &cli.BoolFlag{ Name: "restricted", Usage: "Make a restricted user account", @@ -187,23 +198,40 @@ func runCreateUser(c *cli.Context) error { IsRestricted: restricted, } + var accessTokenName string + var accessTokenScope auth_model.AccessTokenScope + if c.IsSet("access-token") { + accessTokenName = strings.TrimSpace(c.String("access-token-name")) + if accessTokenName == "" { + return errors.New("access-token-name cannot be empty") + } + var err error + accessTokenScope, err = auth_model.AccessTokenScope(c.String("access-token-scopes")).Normalize() + if err != nil { + return fmt.Errorf("invalid access token scope provided: %w", err) + } + if !accessTokenScope.HasPermissionScope() { + return errors.New("access token does not have any permission") + } + } else if c.IsSet("access-token-name") || c.IsSet("access-token-scopes") { + return errors.New("access-token-name and access-token-scopes flags are only valid when access-token flag is set") + } + + // arguments should be prepared before creating the user & access token, in case there is anything wrong + + // create the user if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil { return fmt.Errorf("CreateUser: %w", err) } + fmt.Printf("New user '%s' has been successfully created!\n", username) - if c.Bool("access-token") { - t := &auth_model.AccessToken{ - Name: "gitea-admin", - UID: u.ID, - } - + // create the access token + if accessTokenScope != "" { + t := &auth_model.AccessToken{Name: accessTokenName, UID: u.ID, Scope: accessTokenScope} if err := auth_model.NewAccessToken(ctx, t); err != nil { return err } - fmt.Printf("Access token was successfully created... %s\n", t.Token) } - - fmt.Printf("New user '%s' has been successfully created!\n", username) return nil } diff --git a/cmd/admin_user_create_test.go b/cmd/admin_user_create_test.go index d8044e8de7..6fd6f8c226 100644 --- a/cmd/admin_user_create_test.go +++ b/cmd/admin_user_create_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" @@ -22,6 +23,7 @@ func TestAdminUserCreate(t *testing.T) { reset := func() { require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{})) require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{})) + require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{})) } t.Run("MustChangePassword", func(t *testing.T) { @@ -48,11 +50,11 @@ func TestAdminUserCreate(t *testing.T) { assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u5", "--must-change-password=false")) }) - t.Run("UserType", func(t *testing.T) { - createUser := func(name, args string) error { - return app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s", name, name, args))) - } + createUser := func(name, args string) error { + return app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s", name, name, args))) + } + t.Run("UserType", func(t *testing.T) { reset() assert.ErrorContains(t, createUser("u", "--user-type invalid"), "invalid user type") assert.ErrorContains(t, createUser("u", "--user-type bot --password 123"), "can only be set for individual users") @@ -61,6 +63,58 @@ func TestAdminUserCreate(t *testing.T) { assert.NoError(t, createUser("u", "--user-type bot")) u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "u"}) assert.Equal(t, user_model.UserTypeBot, u.Type) - assert.Equal(t, "", u.Passwd) + assert.Empty(t, u.Passwd) + }) + + t.Run("AccessToken", func(t *testing.T) { + // no generated access token + reset() + assert.NoError(t, createUser("u", "--random-password")) + assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{})) + assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{})) + + // using "--access-token" only means "all" access + reset() + assert.NoError(t, createUser("u", "--random-password --access-token")) + assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{})) + assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{})) + accessToken := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"}) + hasScopes, err := accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository) + assert.NoError(t, err) + assert.True(t, hasScopes) + + // using "--access-token" with name & scopes + reset() + assert.NoError(t, createUser("u", "--random-password --access-token --access-token-name new-token-name --access-token-scopes read:issue,read:user")) + assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{})) + assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{})) + accessToken = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "new-token-name"}) + hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadUser) + assert.NoError(t, err) + assert.True(t, hasScopes) + hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository) + assert.NoError(t, err) + assert.False(t, hasScopes) + + // using "--access-token-name" without "--access-token" + reset() + err = createUser("u", "--random-password --access-token-name new-token-name") + assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{})) + assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{})) + assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set") + + // using "--access-token-scopes" without "--access-token" + reset() + err = createUser("u", "--random-password --access-token-scopes read:issue") + assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{})) + assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{})) + assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set") + + // empty permission + reset() + err = createUser("u", "--random-password --access-token --access-token-scopes public-only") + assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{})) + assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{})) + assert.ErrorContains(t, err, "access token does not have any permission") }) } diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go index 6c2c10494e..f6db7a74bd 100644 --- a/cmd/admin_user_generate_access_token.go +++ b/cmd/admin_user_generate_access_token.go @@ -34,8 +34,8 @@ var microcmdUserGenerateAccessToken = &cli.Command{ }, &cli.StringFlag{ Name: "scopes", - Value: "", - Usage: "Comma separated list of scopes to apply to access token", + Value: "all", + Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`, }, }, Action: runGenerateAccessToken, @@ -43,7 +43,7 @@ var microcmdUserGenerateAccessToken = &cli.Command{ func runGenerateAccessToken(c *cli.Context) error { if !c.IsSet("username") { - return errors.New("You must provide a username to generate a token for") + return errors.New("you must provide a username to generate a token for") } ctx, cancel := installSignals() @@ -77,6 +77,9 @@ func runGenerateAccessToken(c *cli.Context) error { if err != nil { return fmt.Errorf("invalid access token scope provided: %w", err) } + if !accessTokenScope.HasPermissionScope() { + return errors.New("access token does not have any permission") + } t.Scope = accessTokenScope // create the token diff --git a/cmd/doctor.go b/cmd/doctor.go index 52699cc4dd..4a12b957f5 100644 --- a/cmd/doctor.go +++ b/cmd/doctor.go @@ -144,11 +144,12 @@ func setupDoctorDefaultLogger(ctx *cli.Context, colorize bool) { setupConsoleLogger(log.FATAL, log.CanColorStderr, os.Stderr) logFile := ctx.String("log-file") - if logFile == "" { + switch logFile { + case "": return // if no doctor log-file is set, do not show any log from default logger - } else if logFile == "-" { + case "-": setupConsoleLogger(log.TRACE, colorize, os.Stdout) - } else { + default: logFile, _ = filepath.Abs(logFile) writeMode := log.WriterMode{Level: log.TRACE, WriterOption: log.WriterFileOption{FileName: logFile}} writer, err := log.NewEventWriter("console-to-file", "file", writeMode) diff --git a/cmd/dump.go b/cmd/dump.go index ececc80f72..7d640b78fd 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -5,7 +5,6 @@ package cmd import ( - "fmt" "os" "path" "path/filepath" @@ -93,7 +92,7 @@ var CmdDump = &cli.Command{ }, &cli.StringFlag{ Name: "type", - Usage: fmt.Sprintf(`Dump output format, default to "zip", supported types: %s`, strings.Join(dump.SupportedOutputTypes, ", ")), + Usage: `Dump output format, default to "zip", supported types: ` + strings.Join(dump.SupportedOutputTypes, ", "), }, }, } diff --git a/cmd/main_test.go b/cmd/main_test.go index 3ec584d323..9573cacbd4 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -4,6 +4,7 @@ package cmd import ( + "errors" "fmt" "io" "path/filepath" @@ -127,18 +128,18 @@ func TestCliCmd(t *testing.T) { } func TestCliCmdError(t *testing.T) { - app := newTestApp(func(ctx *cli.Context) error { return fmt.Errorf("normal error") }) + app := newTestApp(func(ctx *cli.Context) error { return errors.New("normal error") }) r, err := runTestApp(app, "./gitea", "test-cmd") assert.Error(t, err) assert.Equal(t, 1, r.ExitCode) - assert.Equal(t, "", r.Stdout) + assert.Empty(t, r.Stdout) assert.Equal(t, "Command error: normal error\n", r.Stderr) app = newTestApp(func(ctx *cli.Context) error { return cli.Exit("exit error", 2) }) r, err = runTestApp(app, "./gitea", "test-cmd") assert.Error(t, err) assert.Equal(t, 2, r.ExitCode) - assert.Equal(t, "", r.Stdout) + assert.Empty(t, r.Stdout) assert.Equal(t, "exit error\n", r.Stderr) app = newTestApp(func(ctx *cli.Context) error { return nil }) @@ -146,12 +147,12 @@ func TestCliCmdError(t *testing.T) { assert.Error(t, err) assert.Equal(t, 1, r.ExitCode) assert.Equal(t, "Incorrect Usage: flag provided but not defined: -no-such\n\n", r.Stdout) - assert.Equal(t, "", r.Stderr) // the cli package's strange behavior, the error message is not in stderr .... + assert.Empty(t, r.Stderr) // the cli package's strange behavior, the error message is not in stderr .... app = newTestApp(func(ctx *cli.Context) error { return nil }) r, err = runTestApp(app, "./gitea", "test-cmd") assert.NoError(t, err) assert.Equal(t, -1, r.ExitCode) // the cli.OsExiter is not called - assert.Equal(t, "", r.Stdout) - assert.Equal(t, "", r.Stderr) + assert.Empty(t, r.Stdout) + assert.Empty(t, r.Stderr) } diff --git a/cmd/migrate_storage_test.go b/cmd/migrate_storage_test.go index f8fa95a927..6817867e28 100644 --- a/cmd/migrate_storage_test.go +++ b/cmd/migrate_storage_test.go @@ -69,6 +69,6 @@ func TestMigratePackages(t *testing.T) { entries, err := os.ReadDir(p) assert.NoError(t, err) assert.Len(t, entries, 2) - assert.EqualValues(t, "01", entries[0].Name()) - assert.EqualValues(t, "tmp", entries[1].Name()) + assert.Equal(t, "01", entries[0].Name()) + assert.Equal(t, "tmp", entries[1].Name()) } diff --git a/cmd/serv.go b/cmd/serv.go index 476fd9a2ea..b18508459f 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -173,7 +173,7 @@ func getLFSAuthToken(ctx context.Context, lfsVerb string, results *private.ServC if err != nil { return "", fail(ctx, "Failed to sign JWT Token", "Failed to sign JWT token: %v", err) } - return fmt.Sprintf("Bearer %s", tokenString), nil + return "Bearer " + tokenString, nil } func runServ(c *cli.Context) error { @@ -372,9 +372,9 @@ func runServ(c *cli.Context) error { repo_module.EnvPusherEmail+"="+results.UserEmail, repo_module.EnvPusherID+"="+strconv.FormatInt(results.UserID, 10), repo_module.EnvRepoID+"="+strconv.FormatInt(results.RepoID, 10), - repo_module.EnvPRID+"="+fmt.Sprintf("%d", 0), - repo_module.EnvDeployKeyID+"="+fmt.Sprintf("%d", results.DeployKeyID), - repo_module.EnvKeyID+"="+fmt.Sprintf("%d", results.KeyID), + repo_module.EnvPRID+"="+strconv.Itoa(0), + repo_module.EnvDeployKeyID+"="+strconv.FormatInt(results.DeployKeyID, 10), + repo_module.EnvKeyID+"="+strconv.FormatInt(results.KeyID, 10), repo_module.EnvAppURL+"="+setting.AppURL, ) // to avoid breaking, here only use the minimal environment variables for the "gitea serv" command. diff --git a/cmd/web.go b/cmd/web.go index dc5c6de48a..e47b171455 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -213,6 +213,10 @@ func serveInstalled(ctx *cli.Context) error { log.Fatal("Can not find APP_DATA_PATH %q", setting.AppDataPath) } + // the AppDataTempDir is fully managed by us with a safe sub-path + // so it's safe to automatically remove the outdated files + setting.AppDataTempDir("").RemoveOutdated(3 * 24 * time.Hour) + // Override the provided port number within the configuration if ctx.IsSet("port") { if err := setPort(ctx.String("port")); err != nil { diff --git a/cmd/web_acme.go b/cmd/web_acme.go index 172dde913b..5f7a308334 100644 --- a/cmd/web_acme.go +++ b/cmd/web_acme.go @@ -136,7 +136,7 @@ func runACME(listenAddr string, m http.Handler) error { } func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != "GET" && r.Method != "HEAD" { + if r.Method != http.MethodGet && r.Method != http.MethodHead { http.Error(w, "Use HTTPS", http.StatusBadRequest) return } diff --git a/contrib/backport/backport.go b/contrib/backport/backport.go index eb19437445..9b30480300 100644 --- a/contrib/backport/backport.go +++ b/contrib/backport/backport.go @@ -6,6 +6,7 @@ package main import ( "context" + "errors" "fmt" "log" "net/http" @@ -158,7 +159,7 @@ func runBackport(c *cli.Context) error { args := c.Args().Slice() if len(args) == 0 && pr == "" { - return fmt.Errorf("no PR number provided\nProvide a PR number to backport") + return errors.New("no PR number provided\nProvide a PR number to backport") } else if len(args) != 1 && pr == "" { return fmt.Errorf("multiple PRs provided %v\nOnly a single PR can be backported at a time", args) } diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 0fc49accef..8d39551168 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -197,13 +197,6 @@ RUN_USER = ; git ;; relative paths are made absolute relative to the APP_DATA_PATH ;SSH_SERVER_HOST_KEYS=ssh/gitea.rsa, ssh/gogs.rsa ;; -;; Directory to create temporary files in when testing public keys using ssh-keygen, -;; default is the system temporary directory. -;SSH_KEY_TEST_PATH = -;; -;; Use `ssh-keygen` to parse public SSH keys. The value is passed to the shell. By default, Gitea does the parsing itself. -;SSH_KEYGEN_PATH = -;; ;; Enable SSH Authorized Key Backup when rewriting all keys, default is false ;SSH_AUTHORIZED_KEYS_BACKUP = false ;; @@ -294,6 +287,9 @@ RUN_USER = ; git ;; Default path for App data ;APP_DATA_PATH = data ; relative paths will be made absolute with _`AppWorkPath`_ ;; +;; Base path for App's temp files, leave empty to use the managed tmp directory in APP_DATA_PATH +;APP_TEMP_PATH = +;; ;; Enable gzip compression for runtime-generated content, static resources excluded ;ENABLE_GZIP = false ;; @@ -780,6 +776,9 @@ LEVEL = Info ;ALLOW_ONLY_EXTERNAL_REGISTRATION = false ;; ;; User must sign in to view anything. +;; It could be set to "expensive" to block anonymous users accessing some pages which consume a lot of resources, +;; for example: block anonymous AI crawlers from accessing repo code pages. +;; The "expensive" mode is experimental and subject to change. ;REQUIRE_SIGNIN_VIEW = false ;; ;; Mail notification @@ -1066,15 +1065,6 @@ LEVEL = Info ;; Separate extensions with a comma. To line wrap files without an extension, just put a comma ;LINE_WRAP_EXTENSIONS = .txt,.md,.markdown,.mdown,.mkd,.livemd, -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;[repository.local] -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; Path for local repository copy. Defaults to `tmp/local-repo` (content gets deleted on gitea restart) -;LOCAL_COPY_PATH = tmp/local-repo - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;[repository.upload] @@ -1084,9 +1074,6 @@ LEVEL = Info ;; Whether repository file uploads are enabled. Defaults to `true` ;ENABLED = true ;; -;; Path for uploads. Defaults to `data/tmp/uploads` (content gets deleted on gitea restart) -;TEMP_PATH = data/tmp/uploads -;; ;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. ;ALLOWED_TYPES = ;; @@ -1410,14 +1397,14 @@ LEVEL = Info ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; -;; Render soft line breaks as hard line breaks, which means a single newline character between -;; paragraphs will cause a line break and adding trailing whitespace to paragraphs is not -;; necessary to force a line break. -;; Render soft line breaks as hard line breaks for comments -;ENABLE_HARD_LINE_BREAK_IN_COMMENTS = true -;; -;; Render soft line breaks as hard line breaks for markdown documents -;ENABLE_HARD_LINE_BREAK_IN_DOCUMENTS = false +;; Customize render options for different contexts. Set to "none" to disable the defaults, or use comma separated list: +;; * short-issue-pattern: recognized "#123" issue reference and render it as a link to the issue +;; * new-line-hard-break: render soft line breaks as hard line breaks, which means a single newline character between +;; paragraphs will cause a line break and adding trailing whitespace to paragraphs is not +;; necessary to force a line break. +;RENDER_OPTIONS_COMMENT = short-issue-pattern, new-line-hard-break +;RENDER_OPTIONS_WIKI = short-issue-pattern +;RENDER_OPTIONS_REPO_FILE = ;; ;; Comma separated list of custom URL-Schemes that are allowed as links when rendering Markdown ;; for example git,magnet,ftp (more at https://en.wikipedia.org/wiki/List_of_URI_schemes) @@ -1431,6 +1418,12 @@ LEVEL = Info ;; ;; Enables math inline and block detection ;ENABLE_MATH = true +;; +;; Enable delimiters for math code block detection. Set to "none" to disable all, +;; or use comma separated list: inline-dollar, inline-parentheses, block-dollar, block-square-brackets +;; Defaults to "inline-dollar,block-dollar" to follow GitHub's behavior. +;MATH_CODE_BLOCK_DETECTION = +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -2464,7 +2457,7 @@ LEVEL = Info ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Set the maximum number of characters in a mermaid source. (Set to -1 to disable limits) -;MERMAID_MAX_SOURCE_CHARACTERS = 5000 +;MERMAID_MAX_SOURCE_CHARACTERS = 50000 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -2585,9 +2578,6 @@ LEVEL = Info ;; Currently, only `minio` and `azureblob` is supported. ;SERVE_DIRECT = false ;; -;; Path for chunked uploads. Defaults to APP_DATA_PATH + `tmp/package-upload` -;CHUNKED_UPLOAD_PATH = tmp/package-upload -;; ;; Maximum count of package versions a single owner can have (`-1` means no limits) ;LIMIT_TOTAL_OWNER_COUNT = -1 ;; Maximum size of packages a single owner can use (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) diff --git a/docker/root/etc/s6/openssh/setup b/docker/root/etc/s6/openssh/setup index dbb3bafd35..48e7d4b211 100755 --- a/docker/root/etc/s6/openssh/setup +++ b/docker/root/etc/s6/openssh/setup @@ -31,6 +31,21 @@ if [ -e /data/ssh/ssh_host_ecdsa_cert ]; then SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_cert"} fi +# In case someone wants to sign the `{keyname}.pub` key by `ssh-keygen -s ca -I identity ...` to +# make use of the ssh-key certificate authority feature (see ssh-keygen CERTIFICATES section), +# the generated key file name is `{keyname}-cert.pub` +if [ -e /data/ssh/ssh_host_ed25519_key-cert.pub ]; then + SSH_ED25519_CERT=${SSH_ED25519_CERT:-"/data/ssh/ssh_host_ed25519_key-cert.pub"} +fi + +if [ -e /data/ssh/ssh_host_rsa_key-cert.pub ]; then + SSH_RSA_CERT=${SSH_RSA_CERT:-"/data/ssh/ssh_host_rsa_key-cert.pub"} +fi + +if [ -e /data/ssh/ssh_host_ecdsa_key-cert.pub ]; then + SSH_ECDSA_CERT=${SSH_ECDSA_CERT:-"/data/ssh/ssh_host_ecdsa_key-cert.pub"} +fi + if [ -d /etc/ssh ]; then SSH_PORT=${SSH_PORT:-"22"} \ SSH_LISTEN_PORT=${SSH_LISTEN_PORT:-"${SSH_PORT}"} \ diff --git a/models/actions/run.go b/models/actions/run.go index 89f7f3e640..5f077940c5 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -5,6 +5,7 @@ package actions import ( "context" + "errors" "fmt" "slices" "strings" @@ -245,7 +246,7 @@ func CancelPreviousJobs(ctx context.Context, repoID int64, ref, workflowID strin // If the update affected 0 rows, it means the job has changed in the meantime, so we need to try again. if n == 0 { - return cancelledJobs, fmt.Errorf("job has changed, try again") + return cancelledJobs, errors.New("job has changed, try again") } cancelledJobs = append(cancelledJobs, job) @@ -412,7 +413,7 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error { return err } if affected == 0 { - return fmt.Errorf("run has changed") + return errors.New("run has changed") // It's impossible that the run is not found, since Gitea never deletes runs. } diff --git a/models/actions/runner.go b/models/actions/runner.go index 9ddf346aa6..0411a48393 100644 --- a/models/actions/runner.go +++ b/models/actions/runner.go @@ -86,9 +86,10 @@ func (r *ActionRunner) BelongsToOwnerType() types.OwnerType { return types.OwnerTypeRepository } if r.OwnerID != 0 { - if r.Owner.Type == user_model.UserTypeOrganization { + switch r.Owner.Type { + case user_model.UserTypeOrganization: return types.OwnerTypeOrganization - } else if r.Owner.Type == user_model.UserTypeIndividual { + case user_model.UserTypeIndividual: return types.OwnerTypeIndividual } } diff --git a/models/actions/runner_token_test.go b/models/actions/runner_token_test.go index 159805e5f7..21614b7086 100644 --- a/models/actions/runner_token_test.go +++ b/models/actions/runner_token_test.go @@ -17,7 +17,7 @@ func TestGetLatestRunnerToken(t *testing.T) { token := unittest.AssertExistsAndLoadBean(t, &ActionRunnerToken{ID: 3}) expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0) assert.NoError(t, err) - assert.EqualValues(t, expectedToken, token) + assert.Equal(t, expectedToken, token) } func TestNewRunnerToken(t *testing.T) { @@ -26,7 +26,7 @@ func TestNewRunnerToken(t *testing.T) { assert.NoError(t, err) expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0) assert.NoError(t, err) - assert.EqualValues(t, expectedToken, token) + assert.Equal(t, expectedToken, token) } func TestUpdateRunnerToken(t *testing.T) { @@ -36,5 +36,5 @@ func TestUpdateRunnerToken(t *testing.T) { assert.NoError(t, UpdateRunnerToken(db.DefaultContext, token)) expectedToken, err := GetLatestRunnerToken(db.DefaultContext, 1, 0) assert.NoError(t, err) - assert.EqualValues(t, expectedToken, token) + assert.Equal(t, expectedToken, token) } diff --git a/models/actions/task.go b/models/actions/task.go index 9f13ff94c9..43f11b2730 100644 --- a/models/actions/task.go +++ b/models/actions/task.go @@ -6,6 +6,7 @@ package actions import ( "context" "crypto/subtle" + "errors" "fmt" "time" @@ -361,7 +362,7 @@ func UpdateTaskByState(ctx context.Context, runnerID int64, state *runnerv1.Task } else if !has { return nil, util.ErrNotExist } else if runnerID != task.RunnerID { - return nil, fmt.Errorf("invalid runner for task") + return nil, errors.New("invalid runner for task") } if task.Status.IsDone() { diff --git a/models/activities/action_list.go b/models/activities/action_list.go index 6789ebcb99..b52cf7ee49 100644 --- a/models/activities/action_list.go +++ b/models/activities/action_list.go @@ -5,6 +5,7 @@ package activities import ( "context" + "errors" "fmt" "strconv" @@ -205,7 +206,7 @@ func (actions ActionList) LoadIssues(ctx context.Context) error { // GetFeeds returns actions according to the provided options func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, error) { if opts.RequestedUser == nil && opts.RequestedTeam == nil && opts.RequestedRepo == nil { - return nil, 0, fmt.Errorf("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo") + return nil, 0, errors.New("need at least one of these filters: RequestedUser, RequestedTeam, RequestedRepo") } var err error diff --git a/models/activities/action_test.go b/models/activities/action_test.go index ee2a225a3e..ff311ac891 100644 --- a/models/activities/action_test.go +++ b/models/activities/action_test.go @@ -130,7 +130,7 @@ func TestDeleteIssueActions(t *testing.T) { // load an issue issue := unittest.AssertExistsAndLoadBean(t, &issue_model.Issue{ID: 4}) - assert.NotEqualValues(t, issue.ID, issue.Index) // it needs to use different ID/Index to test the DeleteIssueActions to delete some actions by IssueIndex + assert.NotEqual(t, issue.ID, issue.Index) // it needs to use different ID/Index to test the DeleteIssueActions to delete some actions by IssueIndex // insert a comment err := db.Insert(db.DefaultContext, &issue_model.Comment{Type: issue_model.CommentTypeComment, IssueID: issue.ID}) diff --git a/models/activities/notification_test.go b/models/activities/notification_test.go index 52f0eacba1..5d2a29bc36 100644 --- a/models/activities/notification_test.go +++ b/models/activities/notification_test.go @@ -44,11 +44,11 @@ func TestNotificationsForUser(t *testing.T) { assert.NoError(t, err) if assert.Len(t, notfs, 3) { assert.EqualValues(t, 5, notfs[0].ID) - assert.EqualValues(t, user.ID, notfs[0].UserID) + assert.Equal(t, user.ID, notfs[0].UserID) assert.EqualValues(t, 4, notfs[1].ID) - assert.EqualValues(t, user.ID, notfs[1].UserID) + assert.Equal(t, user.ID, notfs[1].UserID) assert.EqualValues(t, 2, notfs[2].ID) - assert.EqualValues(t, user.ID, notfs[2].UserID) + assert.Equal(t, user.ID, notfs[2].UserID) } } @@ -58,7 +58,7 @@ func TestNotification_GetRepo(t *testing.T) { repo, err := notf.GetRepo(db.DefaultContext) assert.NoError(t, err) assert.Equal(t, repo, notf.Repository) - assert.EqualValues(t, notf.RepoID, repo.ID) + assert.Equal(t, notf.RepoID, repo.ID) } func TestNotification_GetIssue(t *testing.T) { @@ -67,7 +67,7 @@ func TestNotification_GetIssue(t *testing.T) { issue, err := notf.GetIssue(db.DefaultContext) assert.NoError(t, err) assert.Equal(t, issue, notf.Issue) - assert.EqualValues(t, notf.IssueID, issue.ID) + assert.Equal(t, notf.IssueID, issue.ID) } func TestGetNotificationCount(t *testing.T) { @@ -136,5 +136,5 @@ func TestSetIssueReadBy(t *testing.T) { nt, err := activities_model.GetIssueNotification(db.DefaultContext, user.ID, issue.ID) assert.NoError(t, err) - assert.EqualValues(t, activities_model.NotificationStatusRead, nt.Status) + assert.Equal(t, activities_model.NotificationStatusRead, nt.Status) } diff --git a/models/asymkey/error.go b/models/asymkey/error.go index 1ed6edd71a..b765624579 100644 --- a/models/asymkey/error.go +++ b/models/asymkey/error.go @@ -132,7 +132,7 @@ func IsErrGPGKeyParsing(err error) bool { } func (err ErrGPGKeyParsing) Error() string { - return fmt.Sprintf("failed to parse gpg key %s", err.ParseError.Error()) + return "failed to parse gpg key " + err.ParseError.Error() } // ErrGPGKeyNotExist represents a "GPGKeyNotExist" kind of error. diff --git a/models/asymkey/gpg_key.go b/models/asymkey/gpg_key.go index 7f35a96a59..220f46ad1d 100644 --- a/models/asymkey/gpg_key.go +++ b/models/asymkey/gpg_key.go @@ -5,6 +5,7 @@ package asymkey import ( "context" + "errors" "fmt" "strings" "time" @@ -207,7 +208,7 @@ func parseGPGKey(ctx context.Context, ownerID int64, e *openpgp.Entity, verified // deleteGPGKey does the actual key deletion func deleteGPGKey(ctx context.Context, keyID string) (int64, error) { if keyID == "" { - return 0, fmt.Errorf("empty KeyId forbidden") // Should never happen but just to be sure + return 0, errors.New("empty KeyId forbidden") // Should never happen but just to be sure } // Delete imported key n, err := db.GetEngine(ctx).Where("key_id=?", keyID).Delete(new(GPGKeyImport)) @@ -239,3 +240,10 @@ func DeleteGPGKey(ctx context.Context, doer *user_model.User, id int64) (err err return committer.Commit() } + +func FindGPGKeyWithSubKeys(ctx context.Context, keyID string) ([]*GPGKey, error) { + return db.Find[GPGKey](ctx, FindGPGKeyOptions{ + KeyID: keyID, + IncludeSubKeys: true, + }) +} diff --git a/models/asymkey/gpg_key_commit_verification.go b/models/asymkey/gpg_key_commit_verification.go index 1591cd5068..39ec893606 100644 --- a/models/asymkey/gpg_key_commit_verification.go +++ b/models/asymkey/gpg_key_commit_verification.go @@ -4,6 +4,7 @@ package asymkey import ( + "errors" "fmt" "hash" @@ -68,7 +69,7 @@ const ( func verifySign(s *packet.Signature, h hash.Hash, k *GPGKey) error { // Check if key can sign if !k.CanSign { - return fmt.Errorf("key can not sign") + return errors.New("key can not sign") } // Decode key pkey, err := base64DecPubKey(k.Content) diff --git a/models/asymkey/gpg_key_common.go b/models/asymkey/gpg_key_common.go index 1291cbc542..76f52a3ca4 100644 --- a/models/asymkey/gpg_key_common.go +++ b/models/asymkey/gpg_key_common.go @@ -7,6 +7,7 @@ import ( "bytes" "crypto" "encoding/base64" + "errors" "fmt" "hash" "io" @@ -75,7 +76,7 @@ func base64DecPubKey(content string) (*packet.PublicKey, error) { // Check type pkey, ok := p.(*packet.PublicKey) if !ok { - return nil, fmt.Errorf("key is not a public key") + return nil, errors.New("key is not a public key") } return pkey, nil } @@ -122,15 +123,15 @@ func readArmoredSign(r io.Reader) (body io.Reader, err error) { func ExtractSignature(s string) (*packet.Signature, error) { r, err := readArmoredSign(strings.NewReader(s)) if err != nil { - return nil, fmt.Errorf("Failed to read signature armor") + return nil, errors.New("Failed to read signature armor") } p, err := packet.Read(r) if err != nil { - return nil, fmt.Errorf("Failed to read signature packet") + return nil, errors.New("Failed to read signature packet") } sig, ok := p.(*packet.Signature) if !ok { - return nil, fmt.Errorf("Packet is not a signature") + return nil, errors.New("Packet is not a signature") } return sig, nil } diff --git a/models/asymkey/ssh_key_commit_verification.go b/models/asymkey/ssh_key_commit_verification.go deleted file mode 100644 index 27c6df3578..0000000000 --- a/models/asymkey/ssh_key_commit_verification.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2021 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package asymkey - -import ( - "bytes" - "context" - "fmt" - "strings" - - "code.gitea.io/gitea/models/db" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/log" - - "github.com/42wim/sshsig" -) - -// ParseCommitWithSSHSignature check if signature is good against keystore. -func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *user_model.User) *CommitVerification { - // Now try to associate the signature with the committer, if present - if committer.ID != 0 { - keys, err := db.Find[PublicKey](ctx, FindPublicKeyOptions{ - OwnerID: committer.ID, - NotKeytype: KeyTypePrincipal, - }) - if err != nil { // Skipping failed to get ssh keys of user - log.Error("ListPublicKeys: %v", err) - return &CommitVerification{ - CommittingUser: committer, - Verified: false, - Reason: "gpg.error.failed_retrieval_gpg_keys", - } - } - - committerEmailAddresses, err := user_model.GetEmailAddresses(ctx, committer.ID) - if err != nil { - log.Error("GetEmailAddresses: %v", err) - } - - activated := false - for _, e := range committerEmailAddresses { - if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) { - activated = true - break - } - } - - for _, k := range keys { - if k.Verified && activated { - commitVerification := verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, k, committer, committer, c.Committer.Email) - if commitVerification != nil { - return commitVerification - } - } - } - } - - return &CommitVerification{ - CommittingUser: committer, - Verified: false, - Reason: NoKeyFound, - } -} - -func verifySSHCommitVerification(sig, payload string, k *PublicKey, committer, signer *user_model.User, email string) *CommitVerification { - if err := sshsig.Verify(bytes.NewBuffer([]byte(payload)), []byte(sig), []byte(k.Content), "git"); err != nil { - return nil - } - - return &CommitVerification{ // Everything is ok - CommittingUser: committer, - Verified: true, - Reason: fmt.Sprintf("%s / %s", signer.Name, k.Fingerprint), - SigningUser: signer, - SigningSSHKey: k, - SigningEmail: email, - } -} diff --git a/models/asymkey/ssh_key_fingerprint.go b/models/asymkey/ssh_key_fingerprint.go index 1ed3b5df2a..4dcfe1f279 100644 --- a/models/asymkey/ssh_key_fingerprint.go +++ b/models/asymkey/ssh_key_fingerprint.go @@ -6,27 +6,13 @@ package asymkey import ( "context" "fmt" - "strings" "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/process" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" "golang.org/x/crypto/ssh" "xorm.io/builder" ) -// ___________.__ .__ __ -// \_ _____/|__| ____ ____ ________________________|__| _____/ |_ -// | __) | |/ \ / ___\_/ __ \_ __ \____ \_ __ \ |/ \ __\ -// | \ | | | \/ /_/ > ___/| | \/ |_> > | \/ | | \ | -// \___ / |__|___| /\___ / \___ >__| | __/|__| |__|___| /__| -// \/ \//_____/ \/ |__| \/ -// -// This file contains functions for fingerprinting SSH keys -// // The database is used in checkKeyFingerprint however most of these functions probably belong in a module // checkKeyFingerprint only checks if key fingerprint has been used as public key, @@ -41,29 +27,6 @@ func checkKeyFingerprint(ctx context.Context, fingerprint string) error { return nil } -func calcFingerprintSSHKeygen(publicKeyContent string) (string, error) { - // Calculate fingerprint. - tmpPath, err := writeTmpKeyFile(publicKeyContent) - if err != nil { - return "", err - } - defer func() { - if err := util.Remove(tmpPath); err != nil { - log.Warn("Unable to remove temporary key file: %s: Error: %v", tmpPath, err) - } - }() - stdout, stderr, err := process.GetManager().Exec("AddPublicKey", "ssh-keygen", "-lf", tmpPath) - if err != nil { - if strings.Contains(stderr, "is not a public key file") { - return "", ErrKeyUnableVerify{stderr} - } - return "", util.NewInvalidArgumentErrorf("'ssh-keygen -lf %s' failed with error '%s': %s", tmpPath, err, stderr) - } else if len(stdout) < 2 { - return "", util.NewInvalidArgumentErrorf("not enough output for calculating fingerprint: %s", stdout) - } - return strings.Split(stdout, " ")[1], nil -} - func calcFingerprintNative(publicKeyContent string) (string, error) { // Calculate fingerprint. pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publicKeyContent)) @@ -75,15 +38,12 @@ func calcFingerprintNative(publicKeyContent string) (string, error) { // CalcFingerprint calculate public key's fingerprint func CalcFingerprint(publicKeyContent string) (string, error) { - // Call the method based on configuration - useNative := setting.SSH.KeygenPath == "" - calcFn := util.Iif(useNative, calcFingerprintNative, calcFingerprintSSHKeygen) - fp, err := calcFn(publicKeyContent) + fp, err := calcFingerprintNative(publicKeyContent) if err != nil { if IsErrKeyUnableVerify(err) { return "", err } - return "", fmt.Errorf("CalcFingerprint(%s): %w", util.Iif(useNative, "native", "ssh-keygen"), err) + return "", fmt.Errorf("CalcFingerprint: %w", err) } return fp, nil } diff --git a/models/asymkey/ssh_key_parse.go b/models/asymkey/ssh_key_parse.go index 94b1cf112b..46dcf4d894 100644 --- a/models/asymkey/ssh_key_parse.go +++ b/models/asymkey/ssh_key_parse.go @@ -10,14 +10,12 @@ import ( "encoding/base64" "encoding/binary" "encoding/pem" + "errors" "fmt" "math/big" - "os" - "strconv" "strings" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -93,7 +91,7 @@ func parseKeyString(content string) (string, error) { block, _ := pem.Decode([]byte(content)) if block == nil { - return "", fmt.Errorf("failed to parse PEM block containing the public key") + return "", errors.New("failed to parse PEM block containing the public key") } if strings.Contains(block.Type, "PRIVATE") { return "", ErrKeyIsPrivate @@ -174,20 +172,9 @@ func CheckPublicKeyString(content string) (_ string, err error) { return content, nil } - var ( - fnName string - keyType string - length int - ) - if len(setting.SSH.KeygenPath) == 0 { - fnName = "SSHNativeParsePublicKey" - keyType, length, err = SSHNativeParsePublicKey(content) - } else { - fnName = "SSHKeyGenParsePublicKey" - keyType, length, err = SSHKeyGenParsePublicKey(content) - } + keyType, length, err := SSHNativeParsePublicKey(content) if err != nil { - return "", fmt.Errorf("%s: %w", fnName, err) + return "", fmt.Errorf("SSHNativeParsePublicKey: %w", err) } log.Trace("Key info [native: %v]: %s-%d", setting.SSH.StartBuiltinServer, keyType, length) @@ -257,56 +244,3 @@ func SSHNativeParsePublicKey(keyLine string) (string, int, error) { } return "", 0, fmt.Errorf("unsupported key length detection for type: %s", pkey.Type()) } - -// writeTmpKeyFile writes key content to a temporary file -// and returns the name of that file, along with any possible errors. -func writeTmpKeyFile(content string) (string, error) { - tmpFile, err := os.CreateTemp(setting.SSH.KeyTestPath, "gitea_keytest") - if err != nil { - return "", fmt.Errorf("TempFile: %w", err) - } - defer tmpFile.Close() - - if _, err = tmpFile.WriteString(content); err != nil { - return "", fmt.Errorf("WriteString: %w", err) - } - return tmpFile.Name(), nil -} - -// SSHKeyGenParsePublicKey extracts key type and length using ssh-keygen. -func SSHKeyGenParsePublicKey(key string) (string, int, error) { - tmpName, err := writeTmpKeyFile(key) - if err != nil { - return "", 0, fmt.Errorf("writeTmpKeyFile: %w", err) - } - defer func() { - if err := util.Remove(tmpName); err != nil { - log.Warn("Unable to remove temporary key file: %s: Error: %v", tmpName, err) - } - }() - - keygenPath := setting.SSH.KeygenPath - if len(keygenPath) == 0 { - keygenPath = "ssh-keygen" - } - - stdout, stderr, err := process.GetManager().Exec("SSHKeyGenParsePublicKey", keygenPath, "-lf", tmpName) - if err != nil { - return "", 0, fmt.Errorf("fail to parse public key: %s - %s", err, stderr) - } - if strings.Contains(stdout, "is not a public key file") { - return "", 0, ErrKeyUnableVerify{stdout} - } - - fields := strings.Split(stdout, " ") - if len(fields) < 4 { - return "", 0, fmt.Errorf("invalid public key line: %s", stdout) - } - - keyType := strings.Trim(fields[len(fields)-1], "()\r\n") - length, err := strconv.ParseInt(fields[0], 10, 32) - if err != nil { - return "", 0, err - } - return strings.ToLower(keyType), int(length), nil -} diff --git a/models/asymkey/ssh_key_test.go b/models/asymkey/ssh_key_test.go index 3650f1892f..21e4ddf62e 100644 --- a/models/asymkey/ssh_key_test.go +++ b/models/asymkey/ssh_key_test.go @@ -42,29 +42,7 @@ func Test_SSHParsePublicKey(t *testing.T) { keyTypeN, lengthN, err := SSHNativeParsePublicKey(tc.content) assert.NoError(t, err) assert.Equal(t, tc.keyType, keyTypeN) - assert.EqualValues(t, tc.length, lengthN) - }) - if tc.skipSSHKeygen { - return - } - t.Run("SSHKeygen", func(t *testing.T) { - keyTypeK, lengthK, err := SSHKeyGenParsePublicKey(tc.content) - if err != nil { - // Some servers do not support ecdsa format. - if !strings.Contains(err.Error(), "line 1 too long:") { - assert.FailNow(t, "%v", err) - } - } - assert.Equal(t, tc.keyType, keyTypeK) - assert.EqualValues(t, tc.length, lengthK) - }) - t.Run("SSHParseKeyNative", func(t *testing.T) { - keyTypeK, lengthK, err := SSHNativeParsePublicKey(tc.content) - if err != nil { - assert.FailNow(t, "%v", err) - } - assert.Equal(t, tc.keyType, keyTypeK) - assert.EqualValues(t, tc.length, lengthK) + assert.Equal(t, tc.length, lengthN) }) }) } @@ -186,14 +164,6 @@ func Test_calcFingerprint(t *testing.T) { assert.NoError(t, err) assert.Equal(t, tc.fp, fpN) }) - if tc.skipSSHKeygen { - return - } - t.Run("SSHKeygen", func(t *testing.T) { - fpK, err := calcFingerprintSSHKeygen(tc.content) - assert.NoError(t, err) - assert.Equal(t, tc.fp, fpK) - }) }) } } diff --git a/models/asymkey/ssh_key_verify.go b/models/asymkey/ssh_key_verify.go index 208288c77b..605ffe9096 100644 --- a/models/asymkey/ssh_key_verify.go +++ b/models/asymkey/ssh_key_verify.go @@ -4,8 +4,8 @@ package asymkey import ( - "bytes" "context" + "strings" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" @@ -30,11 +30,11 @@ func VerifySSHKey(ctx context.Context, ownerID int64, fingerprint, token, signat return "", ErrKeyNotExist{} } - err = sshsig.Verify(bytes.NewBuffer([]byte(token)), []byte(signature), []byte(key.Content), "gitea") + err = sshsig.Verify(strings.NewReader(token), []byte(signature), []byte(key.Content), "gitea") 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 { + if sshsig.Verify(strings.NewReader(token+"\r\n"), []byte(signature), []byte(key.Content), "gitea") != nil { log.Error("Unable to validate token signature. Error: %v", err) return "", ErrSSHInvalidTokenSignature{ Fingerprint: key.Fingerprint, diff --git a/models/auth/access_token_scope.go b/models/auth/access_token_scope.go index 0e5b2e96e6..2293fd89a0 100644 --- a/models/auth/access_token_scope.go +++ b/models/auth/access_token_scope.go @@ -295,6 +295,10 @@ func (s AccessTokenScope) Normalize() (AccessTokenScope, error) { return bitmap.toScope(), nil } +func (s AccessTokenScope) HasPermissionScope() bool { + return s != "" && s != AccessTokenScopePublicOnly +} + // PublicOnly checks if this token scope is limited to public resources func (s AccessTokenScope) PublicOnly() (bool, error) { bitmap, err := s.parse() diff --git a/models/auth/access_token_scope_test.go b/models/auth/access_token_scope_test.go index 9e4aa83633..b93c25528f 100644 --- a/models/auth/access_token_scope_test.go +++ b/models/auth/access_token_scope_test.go @@ -28,11 +28,11 @@ func TestAccessTokenScope_Normalize(t *testing.T) { for _, scope := range GetAccessTokenCategories() { tests = append(tests, - scopeTestNormalize{AccessTokenScope(fmt.Sprintf("read:%s", scope)), AccessTokenScope(fmt.Sprintf("read:%s", scope)), nil}, - scopeTestNormalize{AccessTokenScope(fmt.Sprintf("write:%s", scope)), AccessTokenScope(fmt.Sprintf("write:%s", scope)), nil}, - scopeTestNormalize{AccessTokenScope(fmt.Sprintf("write:%[1]s,read:%[1]s", scope)), AccessTokenScope(fmt.Sprintf("write:%s", scope)), nil}, - scopeTestNormalize{AccessTokenScope(fmt.Sprintf("read:%[1]s,write:%[1]s", scope)), AccessTokenScope(fmt.Sprintf("write:%s", scope)), nil}, - scopeTestNormalize{AccessTokenScope(fmt.Sprintf("read:%[1]s,write:%[1]s,write:%[1]s", scope)), AccessTokenScope(fmt.Sprintf("write:%s", scope)), nil}, + scopeTestNormalize{AccessTokenScope("read:" + scope), AccessTokenScope("read:" + scope), nil}, + scopeTestNormalize{AccessTokenScope("write:" + scope), AccessTokenScope("write:" + scope), nil}, + scopeTestNormalize{AccessTokenScope(fmt.Sprintf("write:%[1]s,read:%[1]s", scope)), AccessTokenScope("write:" + scope), nil}, + scopeTestNormalize{AccessTokenScope(fmt.Sprintf("read:%[1]s,write:%[1]s", scope)), AccessTokenScope("write:" + scope), nil}, + scopeTestNormalize{AccessTokenScope(fmt.Sprintf("read:%[1]s,write:%[1]s,write:%[1]s", scope)), AccessTokenScope("write:" + scope), nil}, ) } @@ -63,20 +63,20 @@ func TestAccessTokenScope_HasScope(t *testing.T) { for _, scope := range GetAccessTokenCategories() { tests = append(tests, scopeTestHasScope{ - AccessTokenScope(fmt.Sprintf("read:%s", scope)), - AccessTokenScope(fmt.Sprintf("read:%s", scope)), true, nil, + AccessTokenScope("read:" + scope), + AccessTokenScope("read:" + scope), true, nil, }, scopeTestHasScope{ - AccessTokenScope(fmt.Sprintf("write:%s", scope)), - AccessTokenScope(fmt.Sprintf("write:%s", scope)), true, nil, + AccessTokenScope("write:" + scope), + AccessTokenScope("write:" + scope), true, nil, }, scopeTestHasScope{ - AccessTokenScope(fmt.Sprintf("write:%s", scope)), - AccessTokenScope(fmt.Sprintf("read:%s", scope)), true, nil, + AccessTokenScope("write:" + scope), + AccessTokenScope("read:" + scope), true, nil, }, scopeTestHasScope{ - AccessTokenScope(fmt.Sprintf("read:%s", scope)), - AccessTokenScope(fmt.Sprintf("write:%s", scope)), false, nil, + AccessTokenScope("read:" + scope), + AccessTokenScope("write:" + scope), false, nil, }, ) } diff --git a/models/auth/oauth2_test.go b/models/auth/oauth2_test.go index fa89a58b14..c6626b283e 100644 --- a/models/auth/oauth2_test.go +++ b/models/auth/oauth2_test.go @@ -126,7 +126,7 @@ func TestOAuth2Application_CreateGrant(t *testing.T) { assert.NotNil(t, grant) assert.Equal(t, int64(2), grant.UserID) assert.Equal(t, int64(1), grant.ApplicationID) - assert.Equal(t, "", grant.Scope) + assert.Empty(t, grant.Scope) } //////////////////// Grant diff --git a/models/db/context_test.go b/models/db/context_test.go index e8c6b74d93..a6bd11d2ae 100644 --- a/models/db/context_test.go +++ b/models/db/context_test.go @@ -118,7 +118,7 @@ func TestContextSafety(t *testing.T) { }) return nil }) - assert.EqualValues(t, testCount, actualCount) + assert.Equal(t, testCount, actualCount) // deny the bad usages assert.PanicsWithError(t, "using database context in an iterator would cause corrupted results", func() { diff --git a/models/db/engine.go b/models/db/engine.go index 91015f7038..ba287d58f0 100755 --- a/models/db/engine.go +++ b/models/db/engine.go @@ -127,7 +127,7 @@ func IsTableNotEmpty(beanOrTableName any) (bool, error) { // DeleteAllRecords will delete all the records of this table func DeleteAllRecords(tableName string) error { - _, err := xormEngine.Exec(fmt.Sprintf("DELETE FROM %s", tableName)) + _, err := xormEngine.Exec("DELETE FROM " + tableName) return err } diff --git a/models/db/engine_init.go b/models/db/engine_init.go index 7a071fa29b..bb02aff274 100644 --- a/models/db/engine_init.go +++ b/models/db/engine_init.go @@ -42,9 +42,10 @@ func newXORMEngine() (*xorm.Engine, error) { if err != nil { return nil, err } - if setting.Database.Type == "mysql" { + switch setting.Database.Type { + case "mysql": engine.Dialect().SetParams(map[string]string{"rowFormat": "DYNAMIC"}) - } else if setting.Database.Type == "mssql" { + case "mssql": engine.Dialect().SetParams(map[string]string{"DEFAULT_VARCHAR": "nvarchar"}) } engine.SetSchema(setting.Database.Schema) diff --git a/models/db/engine_test.go b/models/db/engine_test.go index 10a1a33ff0..a236f83735 100644 --- a/models/db/engine_test.go +++ b/models/db/engine_test.go @@ -52,7 +52,7 @@ func TestDeleteOrphanedObjects(t *testing.T) { countAfter, err := db.GetEngine(db.DefaultContext).Count(&issues_model.PullRequest{}) assert.NoError(t, err) - assert.EqualValues(t, countBefore, countAfter) + assert.Equal(t, countBefore, countAfter) } func TestPrimaryKeys(t *testing.T) { diff --git a/models/db/error.go b/models/db/error.go index 665e970e17..d47c7adac4 100644 --- a/models/db/error.go +++ b/models/db/error.go @@ -65,7 +65,7 @@ func (err ErrNotExist) Error() string { if err.ID != 0 { return fmt.Sprintf("%s does not exist [id: %d]", name, err.ID) } - return fmt.Sprintf("%s does not exist", name) + return name + " does not exist" } // Unwrap unwraps this as a ErrNotExist err diff --git a/models/db/list_test.go b/models/db/list_test.go index 45194611f8..170473a968 100644 --- a/models/db/list_test.go +++ b/models/db/list_test.go @@ -47,6 +47,6 @@ func TestFind(t *testing.T) { repoUnits, newCnt, err := db.FindAndCount[repo_model.RepoUnit](db.DefaultContext, opts) assert.NoError(t, err) - assert.EqualValues(t, cnt, newCnt) + assert.Equal(t, cnt, newCnt) assert.Len(t, repoUnits, repoUnitCount) } diff --git a/models/dbfs/dbfs_test.go b/models/dbfs/dbfs_test.go index 96cb1014c7..0257d2bd15 100644 --- a/models/dbfs/dbfs_test.go +++ b/models/dbfs/dbfs_test.go @@ -31,15 +31,15 @@ func TestDbfsBasic(t *testing.T) { n, err := f.Write([]byte("0123456789")) // blocks: 0123 4567 89 assert.NoError(t, err) - assert.EqualValues(t, 10, n) + assert.Equal(t, 10, n) _, err = f.Seek(0, io.SeekStart) assert.NoError(t, err) buf, err := io.ReadAll(f) assert.NoError(t, err) - assert.EqualValues(t, 10, n) - assert.EqualValues(t, "0123456789", string(buf)) + assert.Equal(t, 10, n) + assert.Equal(t, "0123456789", string(buf)) // write some new data _, err = f.Seek(1, io.SeekStart) @@ -50,14 +50,14 @@ func TestDbfsBasic(t *testing.T) { // read from offset buf, err = io.ReadAll(f) assert.NoError(t, err) - assert.EqualValues(t, "9", string(buf)) + assert.Equal(t, "9", string(buf)) // read all _, err = f.Seek(0, io.SeekStart) assert.NoError(t, err) buf, err = io.ReadAll(f) assert.NoError(t, err) - assert.EqualValues(t, "0bcdefghi9", string(buf)) + assert.Equal(t, "0bcdefghi9", string(buf)) // write to new size _, err = f.Seek(-1, io.SeekEnd) @@ -68,7 +68,7 @@ func TestDbfsBasic(t *testing.T) { assert.NoError(t, err) buf, err = io.ReadAll(f) assert.NoError(t, err) - assert.EqualValues(t, "0bcdefghiJKLMNOP", string(buf)) + assert.Equal(t, "0bcdefghiJKLMNOP", string(buf)) // write beyond EOF and fill with zero _, err = f.Seek(5, io.SeekCurrent) @@ -79,7 +79,7 @@ func TestDbfsBasic(t *testing.T) { assert.NoError(t, err) buf, err = io.ReadAll(f) assert.NoError(t, err) - assert.EqualValues(t, "0bcdefghiJKLMNOP\x00\x00\x00\x00\x00xyzu", string(buf)) + assert.Equal(t, "0bcdefghiJKLMNOP\x00\x00\x00\x00\x00xyzu", string(buf)) // write to the block with zeros _, err = f.Seek(-6, io.SeekCurrent) @@ -90,7 +90,7 @@ func TestDbfsBasic(t *testing.T) { assert.NoError(t, err) buf, err = io.ReadAll(f) assert.NoError(t, err) - assert.EqualValues(t, "0bcdefghiJKLMNOP\x00\x00\x00ABCDzu", string(buf)) + assert.Equal(t, "0bcdefghiJKLMNOP\x00\x00\x00ABCDzu", string(buf)) assert.NoError(t, f.Close()) @@ -117,7 +117,7 @@ func TestDbfsBasic(t *testing.T) { assert.NoError(t, err) stat, err := f.Stat() assert.NoError(t, err) - assert.EqualValues(t, "test.txt", stat.Name()) + assert.Equal(t, "test.txt", stat.Name()) assert.EqualValues(t, 0, stat.Size()) _, err = f.Write([]byte("0123456789")) assert.NoError(t, err) @@ -144,7 +144,7 @@ func TestDbfsReadWrite(t *testing.T) { line, err := f2r.ReadString('\n') assert.NoError(t, err) - assert.EqualValues(t, "line 1\n", line) + assert.Equal(t, "line 1\n", line) _, err = f2r.ReadString('\n') assert.ErrorIs(t, err, io.EOF) @@ -153,7 +153,7 @@ func TestDbfsReadWrite(t *testing.T) { line, err = f2r.ReadString('\n') assert.NoError(t, err) - assert.EqualValues(t, "line 2\n", line) + assert.Equal(t, "line 2\n", line) _, err = f2r.ReadString('\n') assert.ErrorIs(t, err, io.EOF) } @@ -186,5 +186,5 @@ func TestDbfsSeekWrite(t *testing.T) { buf, err := io.ReadAll(fr) assert.NoError(t, err) - assert.EqualValues(t, "111333", string(buf)) + assert.Equal(t, "111333", string(buf)) } diff --git a/models/fixtures/branch.yml b/models/fixtures/branch.yml index 17b1869ab6..6536e1dda7 100644 --- a/models/fixtures/branch.yml +++ b/models/fixtures/branch.yml @@ -93,3 +93,111 @@ is_deleted: false deleted_by_id: 0 deleted_unix: 0 + +- + id: 16 + repo_id: 16 + name: 'master' + commit_id: '69554a64c1e6030f051e5c3f94bfbd773cd6a324' + commit_message: 'not signed commit' + commit_time: 1502042309 + pusher_id: 2 + is_deleted: false + deleted_by_id: 0 + deleted_unix: 0 + +- + id: 17 + repo_id: 16 + name: 'not-signed' + commit_id: '69554a64c1e6030f051e5c3f94bfbd773cd6a324' + commit_message: 'not signed commit' + commit_time: 1502042309 + pusher_id: 2 + is_deleted: false + deleted_by_id: 0 + deleted_unix: 0 + +- + id: 18 + repo_id: 16 + name: 'good-sign-not-yet-validated' + commit_id: '27566bd5738fc8b4e3fef3c5e72cce608537bd95' + commit_message: 'good signed commit (with not yet validated email)' + commit_time: 1502042234 + pusher_id: 2 + is_deleted: false + deleted_by_id: 0 + deleted_unix: 0 + +- + id: 19 + repo_id: 16 + name: 'good-sign' + commit_id: 'f27c2b2b03dcab38beaf89b0ab4ff61f6de63441' + commit_message: 'good signed commit' + commit_time: 1502042101 + pusher_id: 2 + is_deleted: false + deleted_by_id: 0 + deleted_unix: 0 + +- + id: 20 + repo_id: 1 + name: 'feature/1' + commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d' + commit_message: 'Initial commit' + commit_time: 1489950479 + pusher_id: 2 + is_deleted: false + deleted_by_id: 0 + deleted_unix: 0 + +- + id: 21 + repo_id: 49 + name: 'master' + commit_id: 'aacbdfe9e1c4b47f60abe81849045fa4e96f1d75' + commit_message: "Add 'test/test.txt'" + commit_time: 1572535577 + pusher_id: 2 + is_deleted: false + deleted_by_id: 0 + deleted_unix: 0 + +- + id: 22 + repo_id: 1 + name: 'develop' + commit_id: '65f1bf27bc3bf70f64657658635e66094edbcb4d' + commit_message: "Initial commit" + commit_time: 1489927679 + pusher_id: 1 + is_deleted: false + deleted_by_id: 0 + deleted_unix: 0 + +- + id: 23 + repo_id: 3 + name: 'master' + commit_id: '2a47ca4b614a9f5a43abbd5ad851a54a616ffee6' + commit_message: "init project" + commit_time: 1497448461 + pusher_id: 1 + is_deleted: false + deleted_by_id: 0 + deleted_unix: 0 + +- + id: 24 + repo_id: 3 + name: 'test_branch' + commit_id: 'd22b4d4daa5be07329fcef6ed458f00cf3392da0' + commit_message: "test commit" + commit_time: 1602935385 + pusher_id: 1 + is_deleted: false + deleted_by_id: 0 + deleted_unix: 0 diff --git a/models/fixtures/repo_transfer.yml b/models/fixtures/repo_transfer.yml index db92c95248..b12e6b207f 100644 --- a/models/fixtures/repo_transfer.yml +++ b/models/fixtures/repo_transfer.yml @@ -21,3 +21,11 @@ repo_id: 32 created_unix: 1553610671 updated_unix: 1553610671 + +- + id: 4 + doer_id: 3 + recipient_id: 1 + repo_id: 5 + created_unix: 1553610671 + updated_unix: 1553610671 diff --git a/models/fixtures/webhook.yml b/models/fixtures/webhook.yml index ebc4062b60..ec282914b8 100644 --- a/models/fixtures/webhook.yml +++ b/models/fixtures/webhook.yml @@ -1,7 +1,7 @@ - id: 1 repo_id: 1 - url: www.example.com/url1 + url: https://www.example.com/url1 content_type: 1 # json events: '{"push_only":true,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":false}}' is_active: true @@ -9,7 +9,7 @@ - id: 2 repo_id: 1 - url: www.example.com/url2 + url: https://www.example.com/url2 content_type: 1 # json events: '{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}' is_active: false @@ -18,7 +18,7 @@ id: 3 owner_id: 3 repo_id: 3 - url: www.example.com/url3 + url: https://www.example.com/url3 content_type: 1 # json events: '{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}' is_active: true @@ -26,7 +26,7 @@ - id: 4 repo_id: 2 - url: www.example.com/url4 + url: https://www.example.com/url4 content_type: 1 # json events: '{"push_only":true,"branch_filter":"{master,feature*}"}' is_active: true @@ -35,7 +35,7 @@ id: 5 repo_id: 0 owner_id: 0 - url: www.example.com/url5 + url: https://www.example.com/url5 content_type: 1 # json events: '{"push_only":true,"branch_filter":"{master,feature*}"}' is_active: true @@ -45,7 +45,7 @@ id: 6 repo_id: 0 owner_id: 0 - url: www.example.com/url6 + url: https://www.example.com/url6 content_type: 1 # json events: '{"push_only":true,"branch_filter":"{master,feature*}"}' is_active: true diff --git a/models/git/branch.go b/models/git/branch.go index d1caa35947..beeb7c0689 100644 --- a/models/git/branch.go +++ b/models/git/branch.go @@ -173,6 +173,18 @@ func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, e return &branch, nil } +// IsBranchExist returns true if the branch exists in the repository. +func IsBranchExist(ctx context.Context, repoID int64, branchName string) (bool, error) { + var branch Branch + has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).And("name=?", branchName).Get(&branch) + if err != nil { + return false, err + } else if !has { + return false, nil + } + return !branch.IsDeleted, nil +} + func GetBranches(ctx context.Context, repoID int64, branchNames []string, includeDeleted bool) ([]*Branch, error) { branches := make([]*Branch, 0, len(branchNames)) @@ -223,6 +235,11 @@ func GetDeletedBranchByID(ctx context.Context, repoID, branchID int64) (*Branch, return &branch, nil } +func DeleteRepoBranches(ctx context.Context, repoID int64) error { + _, err := db.GetEngine(ctx).Where("repo_id=?", repoID).Delete(new(Branch)) + return err +} + func DeleteBranches(ctx context.Context, repoID, doerID int64, branchIDs []int64) error { return db.WithTx(ctx, func(ctx context.Context) error { branches := make([]*Branch, 0, len(branchIDs)) diff --git a/models/git/branch_test.go b/models/git/branch_test.go index b8ea663e81..252dcc5690 100644 --- a/models/git/branch_test.go +++ b/models/git/branch_test.go @@ -21,7 +21,7 @@ import ( func TestAddDeletedBranch(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) - assert.EqualValues(t, git.Sha1ObjectFormat.Name(), repo.ObjectFormatName) + assert.Equal(t, git.Sha1ObjectFormat.Name(), repo.ObjectFormatName) firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1}) assert.True(t, firstBranch.IsDeleted) diff --git a/models/git/commit_status.go b/models/git/commit_status.go index d1c94aa023..b978476c4b 100644 --- a/models/git/commit_status.go +++ b/models/git/commit_status.go @@ -222,7 +222,7 @@ func (status *CommitStatus) HideActionsURL(ctx context.Context) { } } - prefix := fmt.Sprintf("%s/actions", status.Repo.Link()) + prefix := status.Repo.Link() + "/actions" if strings.HasPrefix(status.TargetURL, prefix) { status.TargetURL = "" } diff --git a/models/git/protected_branch_list_test.go b/models/git/protected_branch_list_test.go index a46402c543..298c2fa074 100644 --- a/models/git/protected_branch_list_test.go +++ b/models/git/protected_branch_list_test.go @@ -70,7 +70,7 @@ func TestBranchRuleMatchPriority(t *testing.T) { assert.Error(t, fmt.Errorf("no matched rules but expected %s[%d]", kase.Rules[kase.ExpectedMatchIdx], kase.ExpectedMatchIdx)) } } else { - assert.EqualValues(t, kase.Rules[kase.ExpectedMatchIdx], matchedPB.RuleName) + assert.Equal(t, kase.Rules[kase.ExpectedMatchIdx], matchedPB.RuleName) } } } diff --git a/models/git/protected_branch_test.go b/models/git/protected_branch_test.go index e1c91d927d..367992081d 100644 --- a/models/git/protected_branch_test.go +++ b/models/git/protected_branch_test.go @@ -74,7 +74,7 @@ func TestBranchRuleMatch(t *testing.T) { } else { infact = " not" } - assert.EqualValues(t, kase.ExpectedMatch, pb.Match(kase.BranchName), + assert.Equal(t, kase.ExpectedMatch, pb.Match(kase.BranchName), "%s should%s match %s but it is%s", kase.BranchName, should, kase.Rule, infact, ) } diff --git a/models/issues/comment_test.go b/models/issues/comment_test.go index ae0bc3ce17..c08e3b970d 100644 --- a/models/issues/comment_test.go +++ b/models/issues/comment_test.go @@ -34,10 +34,10 @@ func TestCreateComment(t *testing.T) { assert.NoError(t, err) then := time.Now().Unix() - assert.EqualValues(t, issues_model.CommentTypeComment, comment.Type) - assert.EqualValues(t, "Hello", comment.Content) - assert.EqualValues(t, issue.ID, comment.IssueID) - assert.EqualValues(t, doer.ID, comment.PosterID) + assert.Equal(t, issues_model.CommentTypeComment, comment.Type) + assert.Equal(t, "Hello", comment.Content) + assert.Equal(t, issue.ID, comment.IssueID) + assert.Equal(t, doer.ID, comment.PosterID) unittest.AssertInt64InRange(t, now, then, int64(comment.CreatedUnix)) unittest.AssertExistsAndLoadBean(t, comment) // assert actually added to DB @@ -58,9 +58,9 @@ func Test_UpdateCommentAttachment(t *testing.T) { assert.NoError(t, err) attachment2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Attachment{ID: attachment.ID}) - assert.EqualValues(t, attachment.Name, attachment2.Name) - assert.EqualValues(t, comment.ID, attachment2.CommentID) - assert.EqualValues(t, comment.IssueID, attachment2.IssueID) + assert.Equal(t, attachment.Name, attachment2.Name) + assert.Equal(t, comment.ID, attachment2.CommentID) + assert.Equal(t, comment.IssueID, attachment2.IssueID) } func TestFetchCodeComments(t *testing.T) { @@ -111,7 +111,7 @@ func TestMigrate_InsertIssueComments(t *testing.T) { assert.NoError(t, err) issueModified := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) - assert.EqualValues(t, issue.NumComments+1, issueModified.NumComments) + assert.Equal(t, issue.NumComments+1, issueModified.NumComments) unittest.CheckConsistencyFor(t, &issues_model.Issue{}) } @@ -122,5 +122,5 @@ func Test_UpdateIssueNumComments(t *testing.T) { assert.NoError(t, issues_model.UpdateIssueNumComments(db.DefaultContext, issue2.ID)) issue2 = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) - assert.EqualValues(t, 1, issue2.NumComments) + assert.Equal(t, 1, issue2.NumComments) } diff --git a/models/issues/issue.go b/models/issues/issue.go index 5204f27faf..a86d50ca9d 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -10,6 +10,7 @@ import ( "html/template" "regexp" "slices" + "strconv" "code.gitea.io/gitea/models/db" project_model "code.gitea.io/gitea/models/project" @@ -815,7 +816,7 @@ func ChangeIssueTimeEstimate(ctx context.Context, issue *Issue, doer *user_model Doer: doer, Repo: issue.Repo, Issue: issue, - Content: fmt.Sprintf("%d", timeEstimate), + Content: strconv.FormatInt(timeEstimate, 10), } if _, err := CreateComment(ctx, opts); err != nil { return fmt.Errorf("createComment: %w", err) diff --git a/models/issues/issue_list_test.go b/models/issues/issue_list_test.go index 9069e1012d..5b4d2ca5ab 100644 --- a/models/issues/issue_list_test.go +++ b/models/issues/issue_list_test.go @@ -27,7 +27,7 @@ func TestIssueList_LoadRepositories(t *testing.T) { assert.NoError(t, err) assert.Len(t, repos, 2) for _, issue := range issueList { - assert.EqualValues(t, issue.RepoID, issue.Repo.ID) + assert.Equal(t, issue.RepoID, issue.Repo.ID) } } @@ -41,28 +41,28 @@ func TestIssueList_LoadAttributes(t *testing.T) { assert.NoError(t, issueList.LoadAttributes(db.DefaultContext)) for _, issue := range issueList { - assert.EqualValues(t, issue.RepoID, issue.Repo.ID) + assert.Equal(t, issue.RepoID, issue.Repo.ID) for _, label := range issue.Labels { - assert.EqualValues(t, issue.RepoID, label.RepoID) + assert.Equal(t, issue.RepoID, label.RepoID) unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: label.ID}) } if issue.PosterID > 0 { - assert.EqualValues(t, issue.PosterID, issue.Poster.ID) + assert.Equal(t, issue.PosterID, issue.Poster.ID) } if issue.AssigneeID > 0 { - assert.EqualValues(t, issue.AssigneeID, issue.Assignee.ID) + assert.Equal(t, issue.AssigneeID, issue.Assignee.ID) } if issue.MilestoneID > 0 { - assert.EqualValues(t, issue.MilestoneID, issue.Milestone.ID) + assert.Equal(t, issue.MilestoneID, issue.Milestone.ID) } if issue.IsPull { - assert.EqualValues(t, issue.ID, issue.PullRequest.IssueID) + assert.Equal(t, issue.ID, issue.PullRequest.IssueID) } for _, attachment := range issue.Attachments { - assert.EqualValues(t, issue.ID, attachment.IssueID) + assert.Equal(t, issue.ID, attachment.IssueID) } for _, comment := range issue.Comments { - assert.EqualValues(t, issue.ID, comment.IssueID) + assert.Equal(t, issue.ID, comment.IssueID) } if issue.ID == int64(1) { assert.Equal(t, int64(400), issue.TotalTrackedTime) diff --git a/models/issues/issue_search.go b/models/issues/issue_search.go index 737b69f154..f9e1fbeb14 100644 --- a/models/issues/issue_search.go +++ b/models/issues/issue_search.go @@ -21,6 +21,8 @@ import ( "xorm.io/xorm" ) +const ScopeSortPrefix = "scope-" + // IssuesOptions represents options of an issue. type IssuesOptions struct { //nolint Paginator *db.ListOptions @@ -70,6 +72,17 @@ func (o *IssuesOptions) Copy(edit ...func(options *IssuesOptions)) *IssuesOption // applySorts sort an issues-related session based on the provided // sortType string func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) { + // Since this sortType is dynamically created, it has to be treated specially. + if strings.HasPrefix(sortType, ScopeSortPrefix) { + scope := strings.TrimPrefix(sortType, ScopeSortPrefix) + sess.Join("LEFT", "issue_label", "issue.id = issue_label.issue_id") + // "exclusive_order=0" means "no order is set", so exclude it from the JOIN criteria and then "LEFT JOIN" result is also null + sess.Join("LEFT", "label", "label.id = issue_label.label_id AND label.exclusive_order <> 0 AND label.name LIKE ?", scope+"/%") + // Use COALESCE to make sure we sort NULL last regardless of backend DB (2147483647 == max int) + sess.OrderBy("COALESCE(label.exclusive_order, 2147483647) ASC").Desc("issue.id") + return + } + switch sortType { case "oldest": sess.Asc("issue.created_unix").Asc("issue.id") diff --git a/models/issues/issue_test.go b/models/issues/issue_test.go index c32aa26b2b..18571e3aaa 100644 --- a/models/issues/issue_test.go +++ b/models/issues/issue_test.go @@ -141,8 +141,8 @@ func TestUpdateIssueCols(t *testing.T) { then := time.Now().Unix() updatedIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: issue.ID}) - assert.EqualValues(t, newTitle, updatedIssue.Title) - assert.EqualValues(t, prevContent, updatedIssue.Content) + assert.Equal(t, newTitle, updatedIssue.Title) + assert.Equal(t, prevContent, updatedIssue.Content) unittest.AssertInt64InRange(t, now, then, int64(updatedIssue.UpdatedUnix)) } @@ -201,7 +201,7 @@ func TestIssues(t *testing.T) { assert.NoError(t, err) if assert.Len(t, issues, len(test.ExpectedIssueIDs)) { for i, issue := range issues { - assert.EqualValues(t, test.ExpectedIssueIDs[i], issue.ID) + assert.Equal(t, test.ExpectedIssueIDs[i], issue.ID) } } } @@ -234,10 +234,10 @@ func testInsertIssue(t *testing.T, title, content string, expectIndex int64) *is has, err := db.GetEngine(db.DefaultContext).ID(issue.ID).Get(&newIssue) assert.NoError(t, err) assert.True(t, has) - assert.EqualValues(t, issue.Title, newIssue.Title) - assert.EqualValues(t, issue.Content, newIssue.Content) + assert.Equal(t, issue.Title, newIssue.Title) + assert.Equal(t, issue.Content, newIssue.Content) if expectIndex > 0 { - assert.EqualValues(t, expectIndex, newIssue.Index) + assert.Equal(t, expectIndex, newIssue.Index) } }) return &newIssue @@ -271,7 +271,7 @@ func TestIssue_ResolveMentions(t *testing.T) { ids[i] = user.ID } sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] }) - assert.EqualValues(t, expected, ids) + assert.Equal(t, expected, ids) } // Public repo, existing user @@ -392,28 +392,28 @@ func TestIssueLoadAttributes(t *testing.T) { for _, issue := range issueList { assert.NoError(t, issue.LoadAttributes(db.DefaultContext)) - assert.EqualValues(t, issue.RepoID, issue.Repo.ID) + assert.Equal(t, issue.RepoID, issue.Repo.ID) for _, label := range issue.Labels { - assert.EqualValues(t, issue.RepoID, label.RepoID) + assert.Equal(t, issue.RepoID, label.RepoID) unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: label.ID}) } if issue.PosterID > 0 { - assert.EqualValues(t, issue.PosterID, issue.Poster.ID) + assert.Equal(t, issue.PosterID, issue.Poster.ID) } if issue.AssigneeID > 0 { - assert.EqualValues(t, issue.AssigneeID, issue.Assignee.ID) + assert.Equal(t, issue.AssigneeID, issue.Assignee.ID) } if issue.MilestoneID > 0 { - assert.EqualValues(t, issue.MilestoneID, issue.Milestone.ID) + assert.Equal(t, issue.MilestoneID, issue.Milestone.ID) } if issue.IsPull { - assert.EqualValues(t, issue.ID, issue.PullRequest.IssueID) + assert.Equal(t, issue.ID, issue.PullRequest.IssueID) } for _, attachment := range issue.Attachments { - assert.EqualValues(t, issue.ID, attachment.IssueID) + assert.Equal(t, issue.ID, attachment.IssueID) } for _, comment := range issue.Comments { - assert.EqualValues(t, issue.ID, comment.IssueID) + assert.Equal(t, issue.ID, comment.IssueID) } if issue.ID == int64(1) { assert.Equal(t, int64(400), issue.TotalTrackedTime) diff --git a/models/issues/issue_update.go b/models/issues/issue_update.go index 7b3fe04aa5..7ddf7ee901 100644 --- a/models/issues/issue_update.go +++ b/models/issues/issue_update.go @@ -5,12 +5,12 @@ package issues import ( "context" + "errors" "fmt" "strings" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" - "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" project_model "code.gitea.io/gitea/models/project" repo_model "code.gitea.io/gitea/models/repo" @@ -386,10 +386,10 @@ func NewIssueWithIndex(ctx context.Context, doer *user_model.User, opts NewIssue } if opts.Issue.Index <= 0 { - return fmt.Errorf("no issue index provided") + return errors.New("no issue index provided") } if opts.Issue.ID > 0 { - return fmt.Errorf("issue exist") + return errors.New("issue exist") } if _, err := e.Insert(opts.Issue); err != nil { @@ -611,7 +611,7 @@ func ResolveIssueMentionsByVisibility(ctx context.Context, issue *Issue, doer *u unittype = unit.TypePullRequests } for _, team := range teams { - if team.AccessMode >= perm.AccessModeAdmin { + if team.HasAdminAccess() { checked = append(checked, team.ID) resolved[issue.Repo.Owner.LowerName+"/"+team.LowerName] = true continue @@ -845,6 +845,7 @@ func DeleteOrphanedIssues(ctx context.Context) error { // Remove issue attachment files. for i := range attachmentPaths { + // FIXME: it's not right, because the attachment might not be on local filesystem system_model.RemoveAllWithNotice(ctx, "Delete issue attachment", attachmentPaths[i]) } return nil diff --git a/models/issues/label.go b/models/issues/label.go index 8a5d9321cc..cfbe100926 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -87,6 +87,7 @@ type Label struct { OrgID int64 `xorm:"INDEX"` Name string Exclusive bool + ExclusiveOrder int `xorm:"DEFAULT 0"` // 0 means no exclusive order Description string Color string `xorm:"VARCHAR(7)"` NumIssues int @@ -236,7 +237,7 @@ func UpdateLabel(ctx context.Context, l *Label) error { } l.Color = color - return updateLabelCols(ctx, l, "name", "description", "color", "exclusive", "archived_unix") + return updateLabelCols(ctx, l, "name", "description", "color", "exclusive", "exclusive_order", "archived_unix") } // DeleteLabel delete a label diff --git a/models/issues/label_test.go b/models/issues/label_test.go index 185fa11bbc..226036d543 100644 --- a/models/issues/label_test.go +++ b/models/issues/label_test.go @@ -20,7 +20,7 @@ func TestLabel_CalOpenIssues(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) label.CalOpenIssues() - assert.EqualValues(t, 2, label.NumOpenIssues) + assert.Equal(t, 2, label.NumOpenIssues) } func TestLabel_LoadSelectedLabelsAfterClick(t *testing.T) { @@ -154,7 +154,7 @@ func TestGetLabelsByRepoID(t *testing.T) { assert.NoError(t, err) assert.Len(t, labels, len(expectedIssueIDs)) for i, label := range labels { - assert.EqualValues(t, expectedIssueIDs[i], label.ID) + assert.Equal(t, expectedIssueIDs[i], label.ID) } } testSuccess(1, "leastissues", []int64{2, 1}) @@ -221,7 +221,7 @@ func TestGetLabelsByOrgID(t *testing.T) { assert.NoError(t, err) assert.Len(t, labels, len(expectedIssueIDs)) for i, label := range labels { - assert.EqualValues(t, expectedIssueIDs[i], label.ID) + assert.Equal(t, expectedIssueIDs[i], label.ID) } } testSuccess(3, "leastissues", []int64{3, 4}) @@ -267,10 +267,10 @@ func TestUpdateLabel(t *testing.T) { label.Name = update.Name assert.NoError(t, issues_model.UpdateLabel(db.DefaultContext, update)) newLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) - assert.EqualValues(t, label.ID, newLabel.ID) - assert.EqualValues(t, label.Color, newLabel.Color) - assert.EqualValues(t, label.Name, newLabel.Name) - assert.EqualValues(t, label.Description, newLabel.Description) + assert.Equal(t, label.ID, newLabel.ID) + assert.Equal(t, label.Color, newLabel.Color) + assert.Equal(t, label.Name, newLabel.Name) + assert.Equal(t, label.Description, newLabel.Description) assert.EqualValues(t, 0, newLabel.ArchivedUnix) unittest.CheckConsistencyFor(t, &issues_model.Label{}, &repo_model.Repository{}) } @@ -313,7 +313,7 @@ func TestNewIssueLabel(t *testing.T) { Content: "1", }) label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2}) - assert.EqualValues(t, prevNumIssues+1, label.NumIssues) + assert.Equal(t, prevNumIssues+1, label.NumIssues) // re-add existing IssueLabel assert.NoError(t, issues_model.NewIssueLabel(db.DefaultContext, issue, label, doer)) @@ -366,11 +366,11 @@ func TestNewIssueLabels(t *testing.T) { }) unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issue.ID, LabelID: label1.ID}) label1 = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) - assert.EqualValues(t, 3, label1.NumIssues) - assert.EqualValues(t, 1, label1.NumClosedIssues) + assert.Equal(t, 3, label1.NumIssues) + assert.Equal(t, 1, label1.NumClosedIssues) label2 = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 2}) - assert.EqualValues(t, 1, label2.NumIssues) - assert.EqualValues(t, 1, label2.NumClosedIssues) + assert.Equal(t, 1, label2.NumIssues) + assert.Equal(t, 1, label2.NumClosedIssues) // corner case: test empty slice assert.NoError(t, issues_model.NewIssueLabels(db.DefaultContext, issue, []*issues_model.Label{}, doer)) @@ -408,8 +408,8 @@ func TestDeleteIssueLabel(t *testing.T) { LabelID: labelID, }, `content=''`) label = unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: labelID}) - assert.EqualValues(t, expectedNumIssues, label.NumIssues) - assert.EqualValues(t, expectedNumClosedIssues, label.NumClosedIssues) + assert.Equal(t, expectedNumIssues, label.NumIssues) + assert.Equal(t, expectedNumClosedIssues, label.NumClosedIssues) } testSuccess(1, 1, 2) testSuccess(2, 5, 2) diff --git a/models/issues/milestone_test.go b/models/issues/milestone_test.go index 28cd0c028b..f73355c27d 100644 --- a/models/issues/milestone_test.go +++ b/models/issues/milestone_test.go @@ -69,7 +69,7 @@ func TestGetMilestonesByRepoID(t *testing.T) { assert.Len(t, milestones, n) for _, milestone := range milestones { - assert.EqualValues(t, repoID, milestone.RepoID) + assert.Equal(t, repoID, milestone.RepoID) } } test(1, api.StateOpen) @@ -327,7 +327,7 @@ func TestUpdateMilestone(t *testing.T) { milestone.Content = "newMilestoneContent" assert.NoError(t, issues_model.UpdateMilestone(db.DefaultContext, milestone, milestone.IsClosed)) milestone = unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}) - assert.EqualValues(t, "newMilestoneName", milestone.Name) + assert.Equal(t, "newMilestoneName", milestone.Name) unittest.CheckConsistencyFor(t, &issues_model.Milestone{}) } @@ -364,7 +364,7 @@ func TestMigrate_InsertMilestones(t *testing.T) { assert.NoError(t, err) unittest.AssertExistsAndLoadBean(t, ms) repoModified := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: repo.ID}) - assert.EqualValues(t, repo.NumMilestones+1, repoModified.NumMilestones) + assert.Equal(t, repo.NumMilestones+1, repoModified.NumMilestones) unittest.CheckConsistencyFor(t, &issues_model.Milestone{}) } diff --git a/models/issues/pull.go b/models/issues/pull.go index e3af00224d..016db9f75c 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -6,6 +6,7 @@ package issues import ( "context" + "errors" "fmt" "io" "regexp" @@ -732,7 +733,7 @@ func (pr *PullRequest) GetWorkInProgressPrefix(ctx context.Context) string { // UpdateCommitDivergence update Divergence of a pull request func (pr *PullRequest) UpdateCommitDivergence(ctx context.Context, ahead, behind int) error { if pr.ID == 0 { - return fmt.Errorf("pull ID is 0") + return errors.New("pull ID is 0") } pr.CommitsAhead = ahead pr.CommitsBehind = behind @@ -925,7 +926,7 @@ func ParseCodeOwnersLine(ctx context.Context, tokens []string) (*CodeOwnerRule, if strings.Contains(user, "/") { s := strings.Split(user, "/") if len(s) != 2 { - warnings = append(warnings, fmt.Sprintf("incorrect codeowner group: %s", user)) + warnings = append(warnings, "incorrect codeowner group: "+user) continue } orgName := s[0] @@ -933,12 +934,12 @@ func ParseCodeOwnersLine(ctx context.Context, tokens []string) (*CodeOwnerRule, org, err := org_model.GetOrgByName(ctx, orgName) if err != nil { - warnings = append(warnings, fmt.Sprintf("incorrect codeowner organization: %s", user)) + warnings = append(warnings, "incorrect codeowner organization: "+user) continue } teams, err := org.LoadTeams(ctx) if err != nil { - warnings = append(warnings, fmt.Sprintf("incorrect codeowner team: %s", user)) + warnings = append(warnings, "incorrect codeowner team: "+user) continue } @@ -950,7 +951,7 @@ func ParseCodeOwnersLine(ctx context.Context, tokens []string) (*CodeOwnerRule, } else { u, err := user_model.GetUserByName(ctx, user) if err != nil { - warnings = append(warnings, fmt.Sprintf("incorrect codeowner user: %s", user)) + warnings = append(warnings, "incorrect codeowner user: "+user) continue } rule.Users = append(rule.Users, u) diff --git a/models/issues/pull_list_test.go b/models/issues/pull_list_test.go index f5553e7885..eb2de006d6 100644 --- a/models/issues/pull_list_test.go +++ b/models/issues/pull_list_test.go @@ -40,7 +40,7 @@ func TestPullRequestList_LoadReviewCommentsCounts(t *testing.T) { assert.NoError(t, err) assert.Len(t, reviewComments, 2) for _, pr := range prs { - assert.EqualValues(t, 1, reviewComments[pr.IssueID]) + assert.Equal(t, 1, reviewComments[pr.IssueID]) } } diff --git a/models/issues/pull_test.go b/models/issues/pull_test.go index 090659864a..8e09030215 100644 --- a/models/issues/pull_test.go +++ b/models/issues/pull_test.go @@ -285,7 +285,7 @@ func TestDeleteOrphanedObjects(t *testing.T) { countAfter, err := db.GetEngine(db.DefaultContext).Count(&issues_model.PullRequest{}) assert.NoError(t, err) - assert.EqualValues(t, countBefore, countAfter) + assert.Equal(t, countBefore, countAfter) } func TestParseCodeOwnersLine(t *testing.T) { @@ -318,7 +318,7 @@ func TestGetApprovers(t *testing.T) { setting.Repository.PullRequest.DefaultMergeMessageOfficialApproversOnly = false approvers := pr.GetApprovers(db.DefaultContext) expected := "Reviewed-by: User Five \nReviewed-by: Org Six \n" - assert.EqualValues(t, expected, approvers) + assert.Equal(t, expected, approvers) } func TestGetPullRequestByMergedCommit(t *testing.T) { diff --git a/models/issues/review.go b/models/issues/review.go index 1c5c2ee30a..2667bdaa7b 100644 --- a/models/issues/review.go +++ b/models/issues/review.go @@ -5,6 +5,7 @@ package issues import ( "context" + "errors" "fmt" "slices" "strings" @@ -374,7 +375,7 @@ func CreateReview(ctx context.Context, opts CreateReviewOptions) (*Review, error review.Type = ReviewTypeRequest review.ReviewerTeamID = opts.ReviewerTeam.ID } else { - return nil, fmt.Errorf("provide either reviewer or reviewer team") + return nil, errors.New("provide either reviewer or reviewer team") } if _, err := sess.Insert(review); err != nil { @@ -933,7 +934,7 @@ func MarkConversation(ctx context.Context, comment *Comment, doer *user_model.Us // the PR writer , official reviewer and poster can do it func CanMarkConversation(ctx context.Context, issue *Issue, doer *user_model.User) (permResult bool, err error) { if doer == nil || issue == nil { - return false, fmt.Errorf("issue or doer is nil") + return false, errors.New("issue or doer is nil") } if err = issue.LoadRepo(ctx); err != nil { @@ -972,11 +973,11 @@ func DeleteReview(ctx context.Context, r *Review) error { defer committer.Close() if r.ID == 0 { - return fmt.Errorf("review is not allowed to be 0") + return errors.New("review is not allowed to be 0") } if r.Type == ReviewTypeRequest { - return fmt.Errorf("review request can not be deleted using this method") + return errors.New("review request can not be deleted using this method") } opts := FindCommentsOptions{ diff --git a/models/migrations/base/db.go b/models/migrations/base/db.go index eb1c44a79e..4ecc930f10 100644 --- a/models/migrations/base/db.go +++ b/models/migrations/base/db.go @@ -52,7 +52,7 @@ func RecreateTable(sess *xorm.Session, bean any) error { // TODO: This will not work if there are foreign keys tableName := sess.Engine().TableName(bean) - tempTableName := fmt.Sprintf("tmp_recreate__%s", tableName) + tempTableName := "tmp_recreate__" + tableName // We need to move the old table away and create a new one with the correct columns // We will need to do this in stages to prevent data loss @@ -82,7 +82,7 @@ func RecreateTable(sess *xorm.Session, bean any) error { } newTableColumns := table.Columns() if len(newTableColumns) == 0 { - return fmt.Errorf("no columns in new table") + return errors.New("no columns in new table") } hasID := false for _, column := range newTableColumns { @@ -552,11 +552,11 @@ func deleteDB() error { } defer db.Close() - if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)); err != nil { + if _, err = db.Exec("DROP DATABASE IF EXISTS " + setting.Database.Name); err != nil { return err } - if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", setting.Database.Name)); err != nil { + if _, err = db.Exec("CREATE DATABASE IF NOT EXISTS " + setting.Database.Name); err != nil { return err } return nil @@ -568,11 +568,11 @@ func deleteDB() error { } defer db.Close() - if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)); err != nil { + if _, err = db.Exec("DROP DATABASE IF EXISTS " + setting.Database.Name); err != nil { return err } - if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", setting.Database.Name)); err != nil { + if _, err = db.Exec("CREATE DATABASE " + setting.Database.Name); err != nil { return err } db.Close() @@ -594,7 +594,7 @@ func deleteDB() error { if !schrows.Next() { // Create and setup a DB schema - _, err = db.Exec(fmt.Sprintf("CREATE SCHEMA %s", setting.Database.Schema)) + _, err = db.Exec("CREATE SCHEMA " + setting.Database.Schema) if err != nil { return err } diff --git a/models/migrations/base/tests.go b/models/migrations/base/tests.go index fe6de9c517..7da426fef0 100644 --- a/models/migrations/base/tests.go +++ b/models/migrations/base/tests.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/tempdir" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/testlogger" @@ -114,15 +115,16 @@ func MainTest(m *testing.M) { setting.CustomConf = giteaConf } - tmpDataPath, err := os.MkdirTemp("", "data") + tmpDataPath, cleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("data") if err != nil { testlogger.Fatalf("Unable to create temporary data path %v\n", err) } + defer cleanup() setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom") setting.AppDataPath = tmpDataPath - unittest.InitSettings() + unittest.InitSettingsForTesting() if err = git.InitFull(context.Background()); err != nil { testlogger.Fatalf("Unable to InitFull: %v\n", err) } @@ -134,8 +136,5 @@ func MainTest(m *testing.M) { if err := removeAllWithRetry(setting.RepoRootPath); err != nil { fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err) } - if err := removeAllWithRetry(tmpDataPath); err != nil { - fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err) - } os.Exit(exitStatus) } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 6e631f98c7..31b035eb31 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -6,6 +6,7 @@ package migrations import ( "context" + "errors" "fmt" "code.gitea.io/gitea/models/migrations/v1_10" @@ -379,6 +380,7 @@ func prepareMigrationTasks() []*migration { newMigration(316, "Add description for secrets and variables", v1_24.AddDescriptionForSecretsAndVariables), newMigration(317, "Add new index for action for heatmap", v1_24.AddNewIndexForUserDashboard), newMigration(318, "Add anonymous_access_mode for repo_unit", v1_24.AddRepoUnitAnonymousAccessMode), + newMigration(319, "Add ExclusiveOrder to Label table", v1_24.AddExclusiveOrderColumnToLabelTable), } return preparedMigrations } @@ -424,7 +426,7 @@ func EnsureUpToDate(ctx context.Context, x *xorm.Engine) error { } if currentDB < 0 { - return fmt.Errorf("database has not been initialized") + return errors.New("database has not been initialized") } if minDBVersion > currentDB { diff --git a/models/migrations/migrations_test.go b/models/migrations/migrations_test.go index e66b015b3d..8649d116f5 100644 --- a/models/migrations/migrations_test.go +++ b/models/migrations/migrations_test.go @@ -22,7 +22,7 @@ func TestMigrations(t *testing.T) { assert.EqualValues(t, 71, migrationIDNumberToDBVersion(70)) - assert.EqualValues(t, []*migration{{idNumber: 70}, {idNumber: 71}}, getPendingMigrations(70, preparedMigrations)) - assert.EqualValues(t, []*migration{{idNumber: 71}}, getPendingMigrations(71, preparedMigrations)) - assert.EqualValues(t, []*migration{}, getPendingMigrations(72, preparedMigrations)) + assert.Equal(t, []*migration{{idNumber: 70}, {idNumber: 71}}, getPendingMigrations(70, preparedMigrations)) + assert.Equal(t, []*migration{{idNumber: 71}}, getPendingMigrations(71, preparedMigrations)) + assert.Equal(t, []*migration{}, getPendingMigrations(72, preparedMigrations)) } diff --git a/models/migrations/v1_13/v140.go b/models/migrations/v1_13/v140.go index 2d3337012d..f3719e16f6 100644 --- a/models/migrations/v1_13/v140.go +++ b/models/migrations/v1_13/v140.go @@ -46,7 +46,7 @@ func FixLanguageStatsToSaveSize(x *xorm.Engine) error { } // Delete language stats - if _, err := x.Exec(fmt.Sprintf("%s language_stat", truncExpr)); err != nil { + if _, err := x.Exec(truncExpr + " language_stat"); err != nil { return err } diff --git a/models/migrations/v1_13/v145.go b/models/migrations/v1_13/v145.go index 8acb29bf33..bb1f40baa7 100644 --- a/models/migrations/v1_13/v145.go +++ b/models/migrations/v1_13/v145.go @@ -42,7 +42,7 @@ func IncreaseLanguageField(x *xorm.Engine) error { switch { case setting.Database.Type.IsMySQL(): - if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE language_stat MODIFY COLUMN language %s", sqlType)); err != nil { + if _, err := sess.Exec("ALTER TABLE language_stat MODIFY COLUMN language " + sqlType); err != nil { return err } case setting.Database.Type.IsMSSQL(): @@ -64,7 +64,7 @@ func IncreaseLanguageField(x *xorm.Engine) error { return fmt.Errorf("Drop table `language_stat` constraint `%s`: %w", constraint, err) } } - if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE language_stat ALTER COLUMN language %s", sqlType)); err != nil { + if _, err := sess.Exec("ALTER TABLE language_stat ALTER COLUMN language " + sqlType); err != nil { return err } // Finally restore the constraint @@ -72,7 +72,7 @@ func IncreaseLanguageField(x *xorm.Engine) error { return err } case setting.Database.Type.IsPostgreSQL(): - if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE language_stat ALTER COLUMN language TYPE %s", sqlType)); err != nil { + if _, err := sess.Exec("ALTER TABLE language_stat ALTER COLUMN language TYPE " + sqlType); err != nil { return err } } diff --git a/models/migrations/v1_13/v151.go b/models/migrations/v1_13/v151.go index 25af1d03ec..1865d58f04 100644 --- a/models/migrations/v1_13/v151.go +++ b/models/migrations/v1_13/v151.go @@ -5,6 +5,7 @@ package v1_13 //nolint import ( "context" + "errors" "fmt" "strings" @@ -113,7 +114,7 @@ func SetDefaultPasswordToArgon2(x *xorm.Engine) error { newTableColumns := table.Columns() if len(newTableColumns) == 0 { - return fmt.Errorf("no columns in new table") + return errors.New("no columns in new table") } hasID := false for _, column := range newTableColumns { diff --git a/models/migrations/v1_14/v158.go b/models/migrations/v1_14/v158.go index 1094d8abf7..a849ddf27e 100644 --- a/models/migrations/v1_14/v158.go +++ b/models/migrations/v1_14/v158.go @@ -4,7 +4,7 @@ package v1_14 //nolint import ( - "fmt" + "errors" "strconv" "code.gitea.io/gitea/modules/log" @@ -82,7 +82,7 @@ func UpdateCodeCommentReplies(x *xorm.Engine) error { sqlCmd = "SELECT TOP " + strconv.Itoa(batchSize) + " * FROM #temp_comments WHERE " + "(id NOT IN ( SELECT TOP " + strconv.Itoa(start) + " id FROM #temp_comments ORDER BY id )) ORDER BY id" default: - return fmt.Errorf("Unsupported database type") + return errors.New("Unsupported database type") } if err := sess.SQL(sqlCmd).Find(&comments); err != nil { diff --git a/models/migrations/v1_15/v181_test.go b/models/migrations/v1_15/v181_test.go index 1b075be7a0..7295aa4180 100644 --- a/models/migrations/v1_15/v181_test.go +++ b/models/migrations/v1_15/v181_test.go @@ -49,7 +49,7 @@ func Test_AddPrimaryEmail2EmailAddress(t *testing.T) { assert.NoError(t, err) assert.True(t, has) assert.True(t, emailAddress.IsPrimary) - assert.EqualValues(t, user.IsActive, emailAddress.IsActivated) - assert.EqualValues(t, user.ID, emailAddress.UID) + assert.Equal(t, user.IsActive, emailAddress.IsActivated) + assert.Equal(t, user.ID, emailAddress.UID) } } diff --git a/models/migrations/v1_16/v189_test.go b/models/migrations/v1_16/v189_test.go index 32ef821d27..2a73bfae03 100644 --- a/models/migrations/v1_16/v189_test.go +++ b/models/migrations/v1_16/v189_test.go @@ -75,8 +75,8 @@ func Test_UnwrapLDAPSourceCfg(t *testing.T) { return } - assert.EqualValues(t, expected, converted, "UnwrapLDAPSourceCfg failed for %d", source.ID) - assert.EqualValues(t, source.ID%2 == 0, source.IsActive, "UnwrapLDAPSourceCfg failed for %d", source.ID) + assert.Equal(t, expected, converted, "UnwrapLDAPSourceCfg failed for %d", source.ID) + assert.Equal(t, source.ID%2 == 0, source.IsActive, "UnwrapLDAPSourceCfg failed for %d", source.ID) } } } diff --git a/models/migrations/v1_16/v193_test.go b/models/migrations/v1_16/v193_test.go index b279967a2c..7f43846bc3 100644 --- a/models/migrations/v1_16/v193_test.go +++ b/models/migrations/v1_16/v193_test.go @@ -62,7 +62,7 @@ func Test_AddRepoIDForAttachment(t *testing.T) { has, err := x.ID(attach.IssueID).Get(&issue) assert.NoError(t, err) assert.True(t, has) - assert.EqualValues(t, attach.RepoID, issue.RepoID) + assert.Equal(t, attach.RepoID, issue.RepoID) } var releaseAttachments []*NewAttachment @@ -75,6 +75,6 @@ func Test_AddRepoIDForAttachment(t *testing.T) { has, err := x.ID(attach.ReleaseID).Get(&release) assert.NoError(t, err) assert.True(t, has) - assert.EqualValues(t, attach.RepoID, release.RepoID) + assert.Equal(t, attach.RepoID, release.RepoID) } } diff --git a/models/migrations/v1_16/v210_test.go b/models/migrations/v1_16/v210_test.go index d43fb03106..7917301c98 100644 --- a/models/migrations/v1_16/v210_test.go +++ b/models/migrations/v1_16/v210_test.go @@ -71,5 +71,5 @@ func Test_RemigrateU2FCredentials(t *testing.T) { return } - assert.EqualValues(t, expected, got) + assert.Equal(t, expected, got) } diff --git a/models/migrations/v1_17/v222.go b/models/migrations/v1_17/v222.go index 2ffb94eb1c..6c28f8102b 100644 --- a/models/migrations/v1_17/v222.go +++ b/models/migrations/v1_17/v222.go @@ -5,6 +5,7 @@ package v1_17 //nolint import ( "context" + "errors" "fmt" "code.gitea.io/gitea/models/migrations/base" @@ -29,7 +30,7 @@ func DropOldCredentialIDColumn(x *xorm.Engine) error { } if !credentialIDBytesExists { // looks like 221 hasn't properly run - return fmt.Errorf("webauthn_credential does not have a credential_id_bytes column... it is not safe to run this migration") + return errors.New("webauthn_credential does not have a credential_id_bytes column... it is not safe to run this migration") } // Create webauthnCredential table diff --git a/models/migrations/v1_19/v233_test.go b/models/migrations/v1_19/v233_test.go index 32c10ab0f4..5d445d5132 100644 --- a/models/migrations/v1_19/v233_test.go +++ b/models/migrations/v1_19/v233_test.go @@ -64,7 +64,7 @@ func Test_AddHeaderAuthorizationEncryptedColWebhook(t *testing.T) { assert.Equal(t, e.Meta, got[i].Meta) if e.HeaderAuthorization == "" { - assert.Equal(t, "", got[i].HeaderAuthorizationEncrypted) + assert.Empty(t, got[i].HeaderAuthorizationEncrypted) } else { cipherhex := got[i].HeaderAuthorizationEncrypted cleartext, err := secret.DecryptSecret(setting.SecretKey, cipherhex) diff --git a/models/migrations/v1_20/v245.go b/models/migrations/v1_20/v245.go index ab58d12880..5a195d2ccd 100644 --- a/models/migrations/v1_20/v245.go +++ b/models/migrations/v1_20/v245.go @@ -5,7 +5,6 @@ package v1_20 //nolint import ( "context" - "fmt" "code.gitea.io/gitea/models/migrations/base" "code.gitea.io/gitea/modules/setting" @@ -57,7 +56,7 @@ func RenameWebhookOrgToOwner(x *xorm.Engine) error { return err } sqlType := x.Dialect().SQLType(inferredTable.GetColumn("org_id")) - if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `webhook` CHANGE org_id owner_id %s", sqlType)); err != nil { + if _, err := sess.Exec("ALTER TABLE `webhook` CHANGE org_id owner_id " + sqlType); err != nil { return err } case setting.Database.Type.IsMSSQL(): diff --git a/models/migrations/v1_20/v259_test.go b/models/migrations/v1_20/v259_test.go index 5bc9a71391..a1aeb53d5d 100644 --- a/models/migrations/v1_20/v259_test.go +++ b/models/migrations/v1_20/v259_test.go @@ -96,7 +96,7 @@ func Test_ConvertScopedAccessTokens(t *testing.T) { tokens := make([]AccessToken, 0) err = x.Find(&tokens) assert.NoError(t, err) - assert.Equal(t, len(tests), len(tokens)) + assert.Len(t, tokens, len(tests)) // sort the tokens (insertion order by auto-incrementing primary key) sort.Slice(tokens, func(i, j int) bool { diff --git a/models/migrations/v1_21/v264.go b/models/migrations/v1_21/v264.go index e81a17ad6d..d737ef03b3 100644 --- a/models/migrations/v1_21/v264.go +++ b/models/migrations/v1_21/v264.go @@ -5,7 +5,7 @@ package v1_21 //nolint import ( "context" - "fmt" + "errors" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" @@ -57,7 +57,7 @@ func AddBranchTable(x *xorm.Engine) error { if err != nil { return err } else if !has { - return fmt.Errorf("no admin user found") + return errors.New("no admin user found") } branches := make([]Branch, 0, 100) diff --git a/models/migrations/v1_22/v286_test.go b/models/migrations/v1_22/v286_test.go index 1f213ddb6e..4702e4c37c 100644 --- a/models/migrations/v1_22/v286_test.go +++ b/models/migrations/v1_22/v286_test.go @@ -108,11 +108,11 @@ func Test_RepositoryFormat(t *testing.T) { ok, err := x.ID(2).Get(repo) assert.NoError(t, err) assert.True(t, ok) - assert.EqualValues(t, "sha1", repo.ObjectFormatName) + assert.Equal(t, "sha1", repo.ObjectFormatName) repo = new(Repository) ok, err = x.ID(id).Get(repo) assert.NoError(t, err) assert.True(t, ok) - assert.EqualValues(t, "sha256", repo.ObjectFormatName) + assert.Equal(t, "sha256", repo.ObjectFormatName) } diff --git a/models/migrations/v1_22/v287_test.go b/models/migrations/v1_22/v287_test.go index 9c7b10947d..58c3152ac3 100644 --- a/models/migrations/v1_22/v287_test.go +++ b/models/migrations/v1_22/v287_test.go @@ -4,7 +4,7 @@ package v1_22 //nolint import ( - "fmt" + "strconv" "testing" "code.gitea.io/gitea/models/migrations/base" @@ -50,7 +50,7 @@ func Test_UpdateBadgeColName(t *testing.T) { for i, e := range oldBadges { got := got[i+1] // 1 is in the badge.yml assert.Equal(t, e.ID, got.ID) - assert.Equal(t, fmt.Sprintf("%d", e.ID), got.Slug) + assert.Equal(t, strconv.FormatInt(e.ID, 10), got.Slug) } // TODO: check if badges have been updated diff --git a/models/migrations/v1_24/v319.go b/models/migrations/v1_24/v319.go new file mode 100644 index 0000000000..6983c38605 --- /dev/null +++ b/models/migrations/v1_24/v319.go @@ -0,0 +1,16 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_24 //nolint + +import ( + "xorm.io/xorm" +) + +func AddExclusiveOrderColumnToLabelTable(x *xorm.Engine) error { + type Label struct { + ExclusiveOrder int `xorm:"DEFAULT 0"` + } + + return x.Sync(new(Label)) +} diff --git a/models/organization/org.go b/models/organization/org.go index 3e55a36758..dc889ea17f 100644 --- a/models/organization/org.go +++ b/models/organization/org.go @@ -178,12 +178,6 @@ func (org *Organization) HomeLink() string { return org.AsUser().HomeLink() } -// CanCreateRepo returns if user login can create a repository -// NOTE: functions calling this assume a failure due to repository count limit; if new checks are added, those functions should be revised -func (org *Organization) CanCreateRepo() bool { - return org.AsUser().CanCreateRepo() -} - // FindOrgMembersOpts represensts find org members conditions type FindOrgMembersOpts struct { db.ListOptions diff --git a/models/organization/org_list_test.go b/models/organization/org_list_test.go index 0f0f8a4bcd..e859d87c84 100644 --- a/models/organization/org_list_test.go +++ b/models/organization/org_list_test.go @@ -57,7 +57,7 @@ func TestGetUserOrgsList(t *testing.T) { if assert.Len(t, orgs, 1) { assert.EqualValues(t, 3, orgs[0].ID) // repo_id: 3 is in the team, 32 is public, 5 is private with no team - assert.EqualValues(t, 2, orgs[0].NumRepos) + assert.Equal(t, 2, orgs[0].NumRepos) } } diff --git a/models/organization/org_test.go b/models/organization/org_test.go index 6638abd8e0..666a6c44d4 100644 --- a/models/organization/org_test.go +++ b/models/organization/org_test.go @@ -135,7 +135,7 @@ func TestIsOrganizationOwner(t *testing.T) { test := func(orgID, userID int64, expected bool) { isOwner, err := organization.IsOrganizationOwner(db.DefaultContext, orgID, userID) assert.NoError(t, err) - assert.EqualValues(t, expected, isOwner) + assert.Equal(t, expected, isOwner) } test(3, 2, true) test(3, 3, false) @@ -149,7 +149,7 @@ func TestIsOrganizationMember(t *testing.T) { test := func(orgID, userID int64, expected bool) { isMember, err := organization.IsOrganizationMember(db.DefaultContext, orgID, userID) assert.NoError(t, err) - assert.EqualValues(t, expected, isMember) + assert.Equal(t, expected, isMember) } test(3, 2, true) test(3, 3, false) @@ -164,7 +164,7 @@ func TestIsPublicMembership(t *testing.T) { test := func(orgID, userID int64, expected bool) { isMember, err := organization.IsPublicMembership(db.DefaultContext, orgID, userID) assert.NoError(t, err) - assert.EqualValues(t, expected, isMember) + assert.Equal(t, expected, isMember) } test(3, 2, true) test(3, 3, false) @@ -237,7 +237,7 @@ func TestRestrictedUserOrgMembers(t *testing.T) { memberUIDs = append(memberUIDs, member.UID) } slices.Sort(memberUIDs) - assert.EqualValues(t, tc.expectedUIDs, memberUIDs) + assert.Equal(t, tc.expectedUIDs, memberUIDs) }) } } @@ -255,7 +255,7 @@ func TestGetOrgUsersByOrgID(t *testing.T) { sort.Slice(orgUsers, func(i, j int) bool { return orgUsers[i].ID < orgUsers[j].ID }) - assert.EqualValues(t, []*organization.OrgUser{{ + assert.Equal(t, []*organization.OrgUser{{ ID: 1, OrgID: 3, UID: 2, @@ -322,7 +322,7 @@ func TestAccessibleReposEnv_CountRepos(t *testing.T) { assert.NoError(t, err) count, err := env.CountRepos(db.DefaultContext) assert.NoError(t, err) - assert.EqualValues(t, expectedCount, count) + assert.Equal(t, expectedCount, count) } testSuccess(2, 3) testSuccess(4, 2) diff --git a/models/organization/org_user.go b/models/organization/org_user.go index 08d936d922..4d7527c15f 100644 --- a/models/organization/org_user.go +++ b/models/organization/org_user.go @@ -78,7 +78,7 @@ func IsOrganizationAdmin(ctx context.Context, orgID, uid int64) (bool, error) { return false, err } for _, t := range teams { - if t.AccessMode >= perm.AccessModeAdmin { + if t.HasAdminAccess() { return true, nil } } diff --git a/models/organization/org_user_test.go b/models/organization/org_user_test.go index c5110b2a34..689544430d 100644 --- a/models/organization/org_user_test.go +++ b/models/organization/org_user_test.go @@ -139,7 +139,7 @@ func TestAddOrgUser(t *testing.T) { unittest.AssertExistsAndLoadBean(t, ou) assert.Equal(t, isPublic, ou.IsPublic) org = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: orgID}) - assert.EqualValues(t, expectedNumMembers, org.NumMembers) + assert.Equal(t, expectedNumMembers, org.NumMembers) } setting.Service.DefaultOrgMemberVisible = false diff --git a/models/organization/team.go b/models/organization/team.go index 96666da39a..7f3a9b3829 100644 --- a/models/organization/team.go +++ b/models/organization/team.go @@ -113,7 +113,7 @@ func (t *Team) LoadUnits(ctx context.Context) (err error) { // GetUnitNames returns the team units names func (t *Team) GetUnitNames() (res []string) { - if t.AccessMode >= perm.AccessModeAdmin { + if t.HasAdminAccess() { return unit.AllUnitKeyNames() } @@ -126,7 +126,7 @@ func (t *Team) GetUnitNames() (res []string) { // GetUnitsMap returns the team units permissions func (t *Team) GetUnitsMap() map[string]string { m := make(map[string]string) - if t.AccessMode >= perm.AccessModeAdmin { + if t.HasAdminAccess() { for _, u := range unit.Units { m[u.NameKey] = t.AccessMode.ToString() } @@ -153,6 +153,10 @@ func (t *Team) IsMember(ctx context.Context, userID int64) bool { return isMember } +func (t *Team) HasAdminAccess() bool { + return t.AccessMode >= perm.AccessModeAdmin +} + // LoadMembers returns paginated members in team of organization. func (t *Team) LoadMembers(ctx context.Context) (err error) { t.Members, err = GetTeamMembers(ctx, &SearchMembersOptions{ @@ -238,22 +242,6 @@ func GetTeamByID(ctx context.Context, teamID int64) (*Team, error) { return t, nil } -// GetTeamNamesByID returns team's lower name from a list of team ids. -func GetTeamNamesByID(ctx context.Context, teamIDs []int64) ([]string, error) { - if len(teamIDs) == 0 { - return []string{}, nil - } - - var teamNames []string - err := db.GetEngine(ctx).Table("team"). - Select("lower_name"). - In("id", teamIDs). - Asc("name"). - Find(&teamNames) - - return teamNames, err -} - // IncrTeamRepoNum increases the number of repos for the given team by 1 func IncrTeamRepoNum(ctx context.Context, teamID int64) error { _, err := db.GetEngine(ctx).Incr("num_repos").ID(teamID).Update(new(Team)) diff --git a/models/organization/team_test.go b/models/organization/team_test.go index deaabbfa2c..b0bf842584 100644 --- a/models/organization/team_test.go +++ b/models/organization/team_test.go @@ -77,7 +77,7 @@ func TestGetTeam(t *testing.T) { testSuccess := func(orgID int64, name string) { team, err := organization.GetTeam(db.DefaultContext, orgID, name) assert.NoError(t, err) - assert.EqualValues(t, orgID, team.OrgID) + assert.Equal(t, orgID, team.OrgID) assert.Equal(t, name, team.Name) } testSuccess(3, "Owners") @@ -95,7 +95,7 @@ func TestGetTeamByID(t *testing.T) { testSuccess := func(teamID int64) { team, err := organization.GetTeamByID(db.DefaultContext, teamID) assert.NoError(t, err) - assert.EqualValues(t, teamID, team.ID) + assert.Equal(t, teamID, team.ID) } testSuccess(1) testSuccess(2) @@ -163,7 +163,7 @@ func TestGetUserOrgTeams(t *testing.T) { teams, err := organization.GetUserOrgTeams(db.DefaultContext, orgID, userID) assert.NoError(t, err) for _, team := range teams { - assert.EqualValues(t, orgID, team.OrgID) + assert.Equal(t, orgID, team.OrgID) unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{TeamID: team.ID, UID: userID}) } } diff --git a/models/packages/descriptor.go b/models/packages/descriptor.go index d251fcc4a9..1ea181c723 100644 --- a/models/packages/descriptor.go +++ b/models/packages/descriptor.go @@ -11,6 +11,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/packages/alpine" "code.gitea.io/gitea/modules/packages/arch" @@ -102,22 +103,26 @@ func (pd *PackageDescriptor) CalculateBlobSize() int64 { // GetPackageDescriptor gets the package description for a version func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDescriptor, error) { - p, err := GetPackageByID(ctx, pv.PackageID) + return getPackageDescriptor(ctx, pv, cache.NewEphemeralCache()) +} + +func getPackageDescriptor(ctx context.Context, pv *PackageVersion, c *cache.EphemeralCache) (*PackageDescriptor, error) { + p, err := cache.GetWithEphemeralCache(ctx, c, "package", pv.PackageID, GetPackageByID) if err != nil { return nil, err } - o, err := user_model.GetUserByID(ctx, p.OwnerID) + o, err := cache.GetWithEphemeralCache(ctx, c, "user", p.OwnerID, user_model.GetUserByID) if err != nil { return nil, err } var repository *repo_model.Repository if p.RepoID > 0 { - repository, err = repo_model.GetRepositoryByID(ctx, p.RepoID) + repository, err = cache.GetWithEphemeralCache(ctx, c, "repo", p.RepoID, repo_model.GetRepositoryByID) if err != nil && !repo_model.IsErrRepoNotExist(err) { return nil, err } } - creator, err := user_model.GetUserByID(ctx, pv.CreatorID) + creator, err := cache.GetWithEphemeralCache(ctx, c, "user", pv.CreatorID, user_model.GetUserByID) if err != nil { if errors.Is(err, util.ErrNotExist) { creator = user_model.NewGhostUser() @@ -145,9 +150,13 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc return nil, err } - pfds, err := GetPackageFileDescriptors(ctx, pfs) - if err != nil { - return nil, err + pfds := make([]*PackageFileDescriptor, 0, len(pfs)) + for _, pf := range pfs { + pfd, err := getPackageFileDescriptor(ctx, pf, c) + if err != nil { + return nil, err + } + pfds = append(pfds, pfd) } var metadata any @@ -197,7 +206,7 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc case TypeVagrant: metadata = &vagrant.Metadata{} default: - panic(fmt.Sprintf("unknown package type: %s", string(p.Type))) + panic("unknown package type: " + string(p.Type)) } if metadata != nil { if err := json.Unmarshal([]byte(pv.MetadataJSON), &metadata); err != nil { @@ -221,7 +230,11 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc // GetPackageFileDescriptor gets a package file descriptor for a package file func GetPackageFileDescriptor(ctx context.Context, pf *PackageFile) (*PackageFileDescriptor, error) { - pb, err := GetBlobByID(ctx, pf.BlobID) + return getPackageFileDescriptor(ctx, pf, cache.NewEphemeralCache()) +} + +func getPackageFileDescriptor(ctx context.Context, pf *PackageFile, c *cache.EphemeralCache) (*PackageFileDescriptor, error) { + pb, err := cache.GetWithEphemeralCache(ctx, c, "package_file_blob", pf.BlobID, GetBlobByID) if err != nil { return nil, err } @@ -251,9 +264,13 @@ func GetPackageFileDescriptors(ctx context.Context, pfs []*PackageFile) ([]*Pack // GetPackageDescriptors gets the package descriptions for the versions func GetPackageDescriptors(ctx context.Context, pvs []*PackageVersion) ([]*PackageDescriptor, error) { + return getPackageDescriptors(ctx, pvs, cache.NewEphemeralCache()) +} + +func getPackageDescriptors(ctx context.Context, pvs []*PackageVersion, c *cache.EphemeralCache) ([]*PackageDescriptor, error) { pds := make([]*PackageDescriptor, 0, len(pvs)) for _, pv := range pvs { - pd, err := GetPackageDescriptor(ctx, pv) + pd, err := getPackageDescriptor(ctx, pv, c) if err != nil { return nil, err } diff --git a/models/packages/package.go b/models/packages/package.go index 8935dbaa1c..38d1cdcf66 100644 --- a/models/packages/package.go +++ b/models/packages/package.go @@ -127,7 +127,7 @@ func (pt Type) Name() string { case TypeVagrant: return "Vagrant" } - panic(fmt.Sprintf("unknown package type: %s", string(pt))) + panic("unknown package type: " + string(pt)) } // SVGName gets the name of the package type svg image @@ -178,7 +178,7 @@ func (pt Type) SVGName() string { case TypeVagrant: return "gitea-vagrant" } - panic(fmt.Sprintf("unknown package type: %s", string(pt))) + panic("unknown package type: " + string(pt)) } // Package represents a package diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go index 20ede7fee2..9d0c9f0077 100644 --- a/models/perm/access/repo_permission.go +++ b/models/perm/access/repo_permission.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" ) @@ -50,7 +51,7 @@ func (p *Permission) HasAnyUnitAccess() bool { return p.AccessMode >= perm_model.AccessModeRead } -func (p *Permission) HasAnyUnitAccessOrPublicAccess() bool { +func (p *Permission) HasAnyUnitPublicAccess() bool { for _, v := range p.anonymousAccessMode { if v >= perm_model.AccessModeRead { return true @@ -61,7 +62,11 @@ func (p *Permission) HasAnyUnitAccessOrPublicAccess() bool { return true } } - return p.HasAnyUnitAccess() + return false +} + +func (p *Permission) HasAnyUnitAccessOrPublicAccess() bool { + return p.HasAnyUnitPublicAccess() || p.HasAnyUnitAccess() } // HasUnits returns true if the permission contains attached units @@ -188,6 +193,9 @@ func (p *Permission) LogString() string { } func applyPublicAccessPermission(unitType unit.Type, accessMode perm_model.AccessMode, modeMap *map[unit.Type]perm_model.AccessMode) { + if setting.Repository.ForcePrivate { + return + } if accessMode >= perm_model.AccessModeRead && accessMode > (*modeMap)[unitType] { if *modeMap == nil { *modeMap = make(map[unit.Type]perm_model.AccessMode) @@ -323,7 +331,7 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use // if user in an owner team for _, team := range teams { - if team.AccessMode >= perm_model.AccessModeAdmin { + if team.HasAdminAccess() { perm.AccessMode = perm_model.AccessModeOwner perm.unitsMode = nil return perm, nil @@ -391,7 +399,7 @@ func IsUserRepoAdmin(ctx context.Context, repo *repo_model.Repository, user *use } for _, team := range teams { - if team.AccessMode >= perm_model.AccessModeAdmin { + if team.HasAdminAccess() { return true, nil } } diff --git a/models/project/column.go b/models/project/column.go index 77ff5ef83e..9b9d874997 100644 --- a/models/project/column.go +++ b/models/project/column.go @@ -147,7 +147,7 @@ func NewColumn(ctx context.Context, column *Column) error { return err } if res.ColumnCount >= maxProjectColumns { - return fmt.Errorf("NewBoard: maximum number of columns reached") + return errors.New("NewBoard: maximum number of columns reached") } column.Sorting = int8(util.Iif(res.ColumnCount > 0, res.MaxSorting+1, 0)) _, err := db.GetEngine(ctx).Insert(column) @@ -172,7 +172,7 @@ func deleteColumnByID(ctx context.Context, columnID int64) error { } if column.Default { - return fmt.Errorf("deleteColumnByID: cannot delete default column") + return errors.New("deleteColumnByID: cannot delete default column") } // move all issues to the default column diff --git a/models/project/column_test.go b/models/project/column_test.go index 66db23a3e4..5b93e7760f 100644 --- a/models/project/column_test.go +++ b/models/project/column_test.go @@ -97,9 +97,9 @@ func Test_MoveColumnsOnProject(t *testing.T) { columnsAfter, err := project1.GetColumns(db.DefaultContext) assert.NoError(t, err) assert.Len(t, columnsAfter, 3) - assert.EqualValues(t, columns[1].ID, columnsAfter[0].ID) - assert.EqualValues(t, columns[2].ID, columnsAfter[1].ID) - assert.EqualValues(t, columns[0].ID, columnsAfter[2].ID) + assert.Equal(t, columns[1].ID, columnsAfter[0].ID) + assert.Equal(t, columns[2].ID, columnsAfter[1].ID) + assert.Equal(t, columns[0].ID, columnsAfter[2].ID) } func Test_NewColumn(t *testing.T) { diff --git a/models/project/issue.go b/models/project/issue.go index 98eed2a213..47d1537ec7 100644 --- a/models/project/issue.go +++ b/models/project/issue.go @@ -5,7 +5,7 @@ package project import ( "context" - "fmt" + "errors" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/util" @@ -35,7 +35,7 @@ func deleteProjectIssuesByProjectID(ctx context.Context, projectID int64) error func (c *Column) moveIssuesToAnotherColumn(ctx context.Context, newColumn *Column) error { if c.ProjectID != newColumn.ProjectID { - return fmt.Errorf("columns have to be in the same project") + return errors.New("columns have to be in the same project") } if c.ID == newColumn.ID { diff --git a/models/project/project_test.go b/models/project/project_test.go index dd421b4659..c2e924e8ae 100644 --- a/models/project/project_test.go +++ b/models/project/project_test.go @@ -113,10 +113,10 @@ func TestProjectsSort(t *testing.T) { OrderBy: GetSearchOrderByBySortType(tt.sortType), }) assert.NoError(t, err) - assert.EqualValues(t, int64(6), count) + assert.Equal(t, int64(6), count) if assert.Len(t, projects, 6) { for i := range projects { - assert.EqualValues(t, tt.wants[i], projects[i].ID) + assert.Equal(t, tt.wants[i], projects[i].ID) } } } diff --git a/models/renderhelper/repo_comment.go b/models/renderhelper/repo_comment.go index 6bd5e91ad1..7c40eded44 100644 --- a/models/renderhelper/repo_comment.go +++ b/models/renderhelper/repo_comment.go @@ -28,14 +28,14 @@ func (r *RepoComment) IsCommitIDExisting(commitID string) bool { return r.commitChecker.IsCommitIDExisting(commitID) } -func (r *RepoComment) ResolveLink(link string, likeType markup.LinkType) (finalLink string) { - switch likeType { - case markup.LinkTypeApp: - finalLink = r.ctx.ResolveLinkApp(link) +func (r *RepoComment) ResolveLink(link, preferLinkType string) string { + linkType, link := markup.ParseRenderedLink(link, preferLinkType) + switch linkType { + case markup.LinkTypeRoot: + return r.ctx.ResolveLinkRoot(link) default: - finalLink = r.ctx.ResolveLinkRelative(r.repoLink, r.opts.CurrentRefPath, link) + return r.ctx.ResolveLinkRelative(r.repoLink, r.opts.CurrentRefPath, link) } - return finalLink } var _ markup.RenderHelper = (*RepoComment)(nil) @@ -56,7 +56,7 @@ func NewRenderContextRepoComment(ctx context.Context, repo *repo_model.Repositor if repo != nil { helper.repoLink = repo.Link() helper.commitChecker = newCommitChecker(ctx, repo) - rctx = rctx.WithMetas(repo.ComposeMetas(ctx)) + rctx = rctx.WithMetas(repo.ComposeCommentMetas(ctx)) } else { // this is almost dead code, only to pass the incorrect tests helper.repoLink = fmt.Sprintf("%s/%s", helper.opts.DeprecatedOwnerName, helper.opts.DeprecatedRepoName) @@ -64,7 +64,7 @@ func NewRenderContextRepoComment(ctx context.Context, repo *repo_model.Repositor "user": helper.opts.DeprecatedOwnerName, "repo": helper.opts.DeprecatedRepoName, - "markdownLineBreakStyle": "comment", + "markdownNewLineHardBreak": "true", "markupAllowShortIssuePattern": "true", }) } diff --git a/models/renderhelper/repo_file.go b/models/renderhelper/repo_file.go index 794828c617..e0375ed280 100644 --- a/models/renderhelper/repo_file.go +++ b/models/renderhelper/repo_file.go @@ -29,17 +29,17 @@ func (r *RepoFile) IsCommitIDExisting(commitID string) bool { return r.commitChecker.IsCommitIDExisting(commitID) } -func (r *RepoFile) ResolveLink(link string, likeType markup.LinkType) string { - finalLink := link - switch likeType { - case markup.LinkTypeApp: - finalLink = r.ctx.ResolveLinkApp(link) - case markup.LinkTypeDefault: - finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "src", r.opts.CurrentRefPath), r.opts.CurrentTreePath, link) +func (r *RepoFile) ResolveLink(link, preferLinkType string) (finalLink string) { + linkType, link := markup.ParseRenderedLink(link, preferLinkType) + switch linkType { + case markup.LinkTypeRoot: + finalLink = r.ctx.ResolveLinkRoot(link) case markup.LinkTypeRaw: finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "raw", r.opts.CurrentRefPath), r.opts.CurrentTreePath, link) case markup.LinkTypeMedia: finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "media", r.opts.CurrentRefPath), r.opts.CurrentTreePath, link) + default: + finalLink = r.ctx.ResolveLinkRelative(path.Join(r.repoLink, "src", r.opts.CurrentRefPath), r.opts.CurrentTreePath, link) } return finalLink } @@ -61,15 +61,13 @@ func NewRenderContextRepoFile(ctx context.Context, repo *repo_model.Repository, if repo != nil { helper.repoLink = repo.Link() helper.commitChecker = newCommitChecker(ctx, repo) - rctx = rctx.WithMetas(repo.ComposeDocumentMetas(ctx)) + rctx = rctx.WithMetas(repo.ComposeRepoFileMetas(ctx)) } else { // this is almost dead code, only to pass the incorrect tests helper.repoLink = fmt.Sprintf("%s/%s", helper.opts.DeprecatedOwnerName, helper.opts.DeprecatedRepoName) rctx = rctx.WithMetas(map[string]string{ "user": helper.opts.DeprecatedOwnerName, "repo": helper.opts.DeprecatedRepoName, - - "markdownLineBreakStyle": "document", }) } rctx = rctx.WithHelper(helper) diff --git a/models/renderhelper/repo_file_test.go b/models/renderhelper/repo_file_test.go index 29cb45f6f7..3b48efba3a 100644 --- a/models/renderhelper/repo_file_test.go +++ b/models/renderhelper/repo_file_test.go @@ -48,8 +48,8 @@ func TestRepoFile(t *testing.T) { assert.Equal(t, `/test ./test - - + +
/test ./test - -
/test -
https://google.com/ -The Image Desc
65f1bf27bc3bf70f64657658635e66094edbcb4d #1 -@user2
/test ./test diff --git a/models/repo/attachment.go b/models/repo/attachment.go index fa4f6c47e6..835bee5402 100644 --- a/models/repo/attachment.go +++ b/models/repo/attachment.go @@ -224,7 +224,7 @@ func DeleteAttachmentsByComment(ctx context.Context, commentID int64, remove boo // UpdateAttachmentByUUID Updates attachment via uuid func UpdateAttachmentByUUID(ctx context.Context, attach *Attachment, cols ...string) error { if attach.UUID == "" { - return fmt.Errorf("attachment uuid should be not blank") + return errors.New("attachment uuid should be not blank") } _, err := db.GetEngine(ctx).Where("uuid=?", attach.UUID).Cols(cols...).Update(attach) return err diff --git a/models/repo/avatar.go b/models/repo/avatar.go index ccfac12cad..eff64bd239 100644 --- a/models/repo/avatar.go +++ b/models/repo/avatar.go @@ -9,6 +9,7 @@ import ( "image/png" "io" "net/url" + "strconv" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/avatar" @@ -37,7 +38,7 @@ func (repo *Repository) RelAvatarLink(ctx context.Context) string { // generateRandomAvatar generates a random avatar for repository. func generateRandomAvatar(ctx context.Context, repo *Repository) error { - idToString := fmt.Sprintf("%d", repo.ID) + idToString := strconv.FormatInt(repo.ID, 10) seed := idToString img, err := avatar.RandomImage([]byte(seed)) diff --git a/models/repo/collaboration_test.go b/models/repo/collaboration_test.go index 639050f5fd..7b07dbffdf 100644 --- a/models/repo/collaboration_test.go +++ b/models/repo/collaboration_test.go @@ -25,8 +25,8 @@ func TestRepository_GetCollaborators(t *testing.T) { assert.NoError(t, err) assert.Len(t, collaborators, int(expectedLen)) for _, collaborator := range collaborators { - assert.EqualValues(t, collaborator.User.ID, collaborator.Collaboration.UserID) - assert.EqualValues(t, repoID, collaborator.Collaboration.RepoID) + assert.Equal(t, collaborator.User.ID, collaborator.Collaboration.UserID) + assert.Equal(t, repoID, collaborator.Collaboration.RepoID) } } test(1) @@ -51,7 +51,7 @@ func TestRepository_GetCollaborators(t *testing.T) { assert.NoError(t, err) assert.Len(t, collaborators2, 1) - assert.NotEqualValues(t, collaborators1[0].ID, collaborators2[0].ID) + assert.NotEqual(t, collaborators1[0].ID, collaborators2[0].ID) } func TestRepository_IsCollaborator(t *testing.T) { @@ -76,10 +76,10 @@ func TestRepository_ChangeCollaborationAccessMode(t *testing.T) { assert.NoError(t, repo_model.ChangeCollaborationAccessMode(db.DefaultContext, repo, 4, perm.AccessModeAdmin)) collaboration := unittest.AssertExistsAndLoadBean(t, &repo_model.Collaboration{RepoID: repo.ID, UserID: 4}) - assert.EqualValues(t, perm.AccessModeAdmin, collaboration.Mode) + assert.Equal(t, perm.AccessModeAdmin, collaboration.Mode) access := unittest.AssertExistsAndLoadBean(t, &access_model.Access{UserID: 4, RepoID: repo.ID}) - assert.EqualValues(t, perm.AccessModeAdmin, access.Mode) + assert.Equal(t, perm.AccessModeAdmin, access.Mode) assert.NoError(t, repo_model.ChangeCollaborationAccessMode(db.DefaultContext, repo, 4, perm.AccessModeAdmin)) diff --git a/models/repo/release.go b/models/repo/release.go index 1c2e4a48e3..663d310bc0 100644 --- a/models/repo/release.go +++ b/models/repo/release.go @@ -558,3 +558,8 @@ func FindTagsByCommitIDs(ctx context.Context, repoID int64, commitIDs ...string) } return res, nil } + +func DeleteRepoReleases(ctx context.Context, repoID int64) error { + _, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Delete(new(Release)) + return err +} diff --git a/models/repo/repo.go b/models/repo/repo.go index a8732f60bf..2977dfb9f1 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -5,6 +5,7 @@ package repo import ( "context" + "errors" "fmt" "html/template" "maps" @@ -59,7 +60,7 @@ type ErrRepoIsArchived struct { } func (err ErrRepoIsArchived) Error() string { - return fmt.Sprintf("%s is archived", err.Repo.LogString()) + return err.Repo.LogString() + " is archived" } type globalVarsStruct struct { @@ -425,32 +426,33 @@ func (repo *Repository) MustGetUnit(ctx context.Context, tp unit.Type) *RepoUnit return ru } - if tp == unit.TypeExternalWiki { + switch tp { + case unit.TypeExternalWiki: return &RepoUnit{ Type: tp, Config: new(ExternalWikiConfig), } - } else if tp == unit.TypeExternalTracker { + case unit.TypeExternalTracker: return &RepoUnit{ Type: tp, Config: new(ExternalTrackerConfig), } - } else if tp == unit.TypePullRequests { + case unit.TypePullRequests: return &RepoUnit{ Type: tp, Config: new(PullRequestsConfig), } - } else if tp == unit.TypeIssues { + case unit.TypeIssues: return &RepoUnit{ Type: tp, Config: new(IssuesConfig), } - } else if tp == unit.TypeActions { + case unit.TypeActions: return &RepoUnit{ Type: tp, Config: new(ActionsConfig), } - } else if tp == unit.TypeProjects { + case unit.TypeProjects: cfg := new(ProjectsConfig) cfg.ProjectsMode = ProjectsModeNone return &RepoUnit{ @@ -510,15 +512,15 @@ func (repo *Repository) composeCommonMetas(ctx context.Context) map[string]strin "repo": repo.Name, } - unit, err := repo.GetUnit(ctx, unit.TypeExternalTracker) + unitExternalTracker, err := repo.GetUnit(ctx, unit.TypeExternalTracker) if err == nil { - metas["format"] = unit.ExternalTrackerConfig().ExternalTrackerFormat - switch unit.ExternalTrackerConfig().ExternalTrackerStyle { + metas["format"] = unitExternalTracker.ExternalTrackerConfig().ExternalTrackerFormat + switch unitExternalTracker.ExternalTrackerConfig().ExternalTrackerStyle { case markup.IssueNameStyleAlphanumeric: metas["style"] = markup.IssueNameStyleAlphanumeric case markup.IssueNameStyleRegexp: metas["style"] = markup.IssueNameStyleRegexp - metas["regexp"] = unit.ExternalTrackerConfig().ExternalTrackerRegexpPattern + metas["regexp"] = unitExternalTracker.ExternalTrackerConfig().ExternalTrackerRegexpPattern default: metas["style"] = markup.IssueNameStyleNumeric } @@ -542,11 +544,11 @@ func (repo *Repository) composeCommonMetas(ctx context.Context) map[string]strin return repo.commonRenderingMetas } -// ComposeMetas composes a map of metas for properly rendering comments or comment-like contents (commit message) -func (repo *Repository) ComposeMetas(ctx context.Context) map[string]string { +// ComposeCommentMetas composes a map of metas for properly rendering comments or comment-like contents (commit message) +func (repo *Repository) ComposeCommentMetas(ctx context.Context) map[string]string { metas := maps.Clone(repo.composeCommonMetas(ctx)) - metas["markdownLineBreakStyle"] = "comment" - metas["markupAllowShortIssuePattern"] = "true" + metas["markdownNewLineHardBreak"] = strconv.FormatBool(setting.Markdown.RenderOptionsComment.NewLineHardBreak) + metas["markupAllowShortIssuePattern"] = strconv.FormatBool(setting.Markdown.RenderOptionsComment.ShortIssuePattern) return metas } @@ -554,16 +556,17 @@ func (repo *Repository) ComposeMetas(ctx context.Context) map[string]string { func (repo *Repository) ComposeWikiMetas(ctx context.Context) map[string]string { // does wiki need the "teams" and "org" from common metas? metas := maps.Clone(repo.composeCommonMetas(ctx)) - metas["markdownLineBreakStyle"] = "document" - metas["markupAllowShortIssuePattern"] = "true" + metas["markdownNewLineHardBreak"] = strconv.FormatBool(setting.Markdown.RenderOptionsWiki.NewLineHardBreak) + metas["markupAllowShortIssuePattern"] = strconv.FormatBool(setting.Markdown.RenderOptionsWiki.ShortIssuePattern) return metas } -// ComposeDocumentMetas composes a map of metas for properly rendering documents (repo files) -func (repo *Repository) ComposeDocumentMetas(ctx context.Context) map[string]string { +// ComposeRepoFileMetas composes a map of metas for properly rendering documents (repo files) +func (repo *Repository) ComposeRepoFileMetas(ctx context.Context) map[string]string { // does document(file) need the "teams" and "org" from common metas? metas := maps.Clone(repo.composeCommonMetas(ctx)) - metas["markdownLineBreakStyle"] = "document" + metas["markdownNewLineHardBreak"] = strconv.FormatBool(setting.Markdown.RenderOptionsRepoFile.NewLineHardBreak) + metas["markupAllowShortIssuePattern"] = strconv.FormatBool(setting.Markdown.RenderOptionsRepoFile.ShortIssuePattern) return metas } @@ -819,7 +822,7 @@ func GetRepositoryByName(ctx context.Context, ownerID int64, name string) (*Repo func GetRepositoryByURL(ctx context.Context, repoURL string) (*Repository, error) { ret, err := giturl.ParseRepositoryURL(ctx, repoURL) if err != nil || ret.OwnerName == "" { - return nil, fmt.Errorf("unknown or malformed repository URL") + return nil, errors.New("unknown or malformed repository URL") } return GetRepositoryByOwnerAndName(ctx, ret.OwnerName, ret.RepoName) } diff --git a/models/repo/repo_test.go b/models/repo/repo_test.go index dffbd18261..b2604ab575 100644 --- a/models/repo/repo_test.go +++ b/models/repo/repo_test.go @@ -86,7 +86,7 @@ func TestMetas(t *testing.T) { repo.Units = nil - metas := repo.ComposeMetas(db.DefaultContext) + metas := repo.ComposeCommentMetas(db.DefaultContext) assert.Equal(t, "testRepo", metas["repo"]) assert.Equal(t, "testOwner", metas["user"]) @@ -100,7 +100,7 @@ func TestMetas(t *testing.T) { testSuccess := func(expectedStyle string) { repo.Units = []*RepoUnit{&externalTracker} repo.commonRenderingMetas = nil - metas := repo.ComposeMetas(db.DefaultContext) + metas := repo.ComposeCommentMetas(db.DefaultContext) assert.Equal(t, expectedStyle, metas["style"]) assert.Equal(t, "testRepo", metas["repo"]) assert.Equal(t, "testOwner", metas["user"]) @@ -121,7 +121,7 @@ func TestMetas(t *testing.T) { repo, err := GetRepositoryByID(db.DefaultContext, 3) assert.NoError(t, err) - metas = repo.ComposeMetas(db.DefaultContext) + metas = repo.ComposeCommentMetas(db.DefaultContext) assert.Contains(t, metas, "org") assert.Contains(t, metas, "teams") assert.Equal(t, "org3", metas["org"]) diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go index 93b0dcab31..8a7dbfe340 100644 --- a/models/repo/repo_unit.go +++ b/models/repo/repo_unit.go @@ -5,7 +5,6 @@ package repo import ( "context" - "fmt" "slices" "strings" @@ -33,7 +32,7 @@ func IsErrUnitTypeNotExist(err error) bool { } func (err ErrUnitTypeNotExist) Error() string { - return fmt.Sprintf("Unit type does not exist: %s", err.UT.LogString()) + return "Unit type does not exist: " + err.UT.LogString() } func (err ErrUnitTypeNotExist) Unwrap() error { @@ -342,3 +341,9 @@ func UpdateRepoUnit(ctx context.Context, unit *RepoUnit) error { _, err := db.GetEngine(ctx).ID(unit.ID).Update(unit) return err } + +func UpdateRepoUnitPublicAccess(ctx context.Context, unit *RepoUnit) error { + _, err := db.GetEngine(ctx).Where("repo_id=? AND `type`=?", unit.RepoID, unit.Type). + Cols("anonymous_access_mode", "everyone_access_mode").Update(unit) + return err +} diff --git a/models/repo/repo_unit_test.go b/models/repo/repo_unit_test.go index a760594013..56dda5672d 100644 --- a/models/repo/repo_unit_test.go +++ b/models/repo/repo_unit_test.go @@ -12,19 +12,19 @@ import ( func TestActionsConfig(t *testing.T) { cfg := &ActionsConfig{} cfg.DisableWorkflow("test1.yaml") - assert.EqualValues(t, []string{"test1.yaml"}, cfg.DisabledWorkflows) + assert.Equal(t, []string{"test1.yaml"}, cfg.DisabledWorkflows) cfg.DisableWorkflow("test1.yaml") - assert.EqualValues(t, []string{"test1.yaml"}, cfg.DisabledWorkflows) + assert.Equal(t, []string{"test1.yaml"}, cfg.DisabledWorkflows) cfg.EnableWorkflow("test1.yaml") - assert.EqualValues(t, []string{}, cfg.DisabledWorkflows) + assert.Equal(t, []string{}, cfg.DisabledWorkflows) cfg.EnableWorkflow("test1.yaml") - assert.EqualValues(t, []string{}, cfg.DisabledWorkflows) + assert.Equal(t, []string{}, cfg.DisabledWorkflows) cfg.DisableWorkflow("test1.yaml") cfg.DisableWorkflow("test2.yaml") cfg.DisableWorkflow("test3.yaml") - assert.EqualValues(t, "test1.yaml,test2.yaml,test3.yaml", cfg.ToString()) + assert.Equal(t, "test1.yaml,test2.yaml,test3.yaml", cfg.ToString()) } diff --git a/models/repo/topic_test.go b/models/repo/topic_test.go index 1600896b6e..b6a7aed7b1 100644 --- a/models/repo/topic_test.go +++ b/models/repo/topic_test.go @@ -53,7 +53,7 @@ func TestAddTopic(t *testing.T) { totalNrOfTopics++ topic, err := repo_model.GetTopicByName(db.DefaultContext, "gitea") assert.NoError(t, err) - assert.EqualValues(t, 1, topic.RepoCount) + assert.Equal(t, 1, topic.RepoCount) topics, err = db.Find[repo_model.Topic](db.DefaultContext, &repo_model.FindTopicOptions{}) assert.NoError(t, err) diff --git a/models/repo/update.go b/models/repo/update.go index fce357a1ac..15c8c48d5b 100644 --- a/models/repo/update.go +++ b/models/repo/update.go @@ -111,31 +111,31 @@ func (err ErrRepoFilesAlreadyExist) Unwrap() error { return util.ErrAlreadyExist } -// CheckCreateRepository check if could created a repository -func CheckCreateRepository(ctx context.Context, doer, u *user_model.User, name string, overwriteOrAdopt bool) error { - if !doer.CanCreateRepo() { - return ErrReachLimitOfRepo{u.MaxRepoCreation} +// CheckCreateRepository check if doer could create a repository in new owner +func CheckCreateRepository(ctx context.Context, doer, owner *user_model.User, name string, overwriteOrAdopt bool) error { + if !doer.CanCreateRepoIn(owner) { + return ErrReachLimitOfRepo{owner.MaxRepoCreation} } if err := IsUsableRepoName(name); err != nil { return err } - has, err := IsRepositoryModelOrDirExist(ctx, u, name) + has, err := IsRepositoryModelOrDirExist(ctx, owner, name) if err != nil { return fmt.Errorf("IsRepositoryExist: %w", err) } else if has { - return ErrRepoAlreadyExist{u.Name, name} + return ErrRepoAlreadyExist{owner.Name, name} } - repoPath := RepoPath(u.Name, name) + repoPath := RepoPath(owner.Name, name) isExist, err := util.IsExist(repoPath) if err != nil { log.Error("Unable to check if %s exists. Error: %v", repoPath, err) return err } if !overwriteOrAdopt && isExist { - return ErrRepoFilesAlreadyExist{u.Name, name} + return ErrRepoFilesAlreadyExist{owner.Name, name} } return nil } diff --git a/models/repo/upload.go b/models/repo/upload.go index cae11df96a..fb57fb6c51 100644 --- a/models/repo/upload.go +++ b/models/repo/upload.go @@ -51,14 +51,10 @@ func init() { db.RegisterModel(new(Upload)) } -// UploadLocalPath returns where uploads is stored in local file system based on given UUID. -func UploadLocalPath(uuid string) string { - return filepath.Join(setting.Repository.Upload.TempPath, uuid[0:1], uuid[1:2], uuid) -} - -// LocalPath returns where uploads are temporarily stored in local file system. +// LocalPath returns where uploads are temporarily stored in local file system based on given UUID. func (upload *Upload) LocalPath() string { - return UploadLocalPath(upload.UUID) + uuid := upload.UUID + return setting.AppDataTempDir("repo-uploads").JoinPath(uuid[0:1], uuid[1:2], uuid) } // NewUpload creates a new upload object. diff --git a/models/repo/watch_test.go b/models/repo/watch_test.go index c39ef607e8..7ed72386c9 100644 --- a/models/repo/watch_test.go +++ b/models/repo/watch_test.go @@ -36,7 +36,7 @@ func TestGetWatchers(t *testing.T) { // One watchers are inactive, thus minus 1 assert.Len(t, watches, repo.NumWatches-1) for _, watch := range watches { - assert.EqualValues(t, repo.ID, watch.RepoID) + assert.Equal(t, repo.ID, watch.RepoID) } watches, err = repo_model.GetWatchers(db.DefaultContext, unittest.NonexistentID) diff --git a/models/repo/wiki.go b/models/repo/wiki.go index 4239a815b2..832e15ae0d 100644 --- a/models/repo/wiki.go +++ b/models/repo/wiki.go @@ -46,7 +46,7 @@ func IsErrWikiReservedName(err error) bool { } func (err ErrWikiReservedName) Error() string { - return fmt.Sprintf("wiki title is reserved: %s", err.Title) + return "wiki title is reserved: " + err.Title } func (err ErrWikiReservedName) Unwrap() error { @@ -65,7 +65,7 @@ func IsErrWikiInvalidFileName(err error) bool { } func (err ErrWikiInvalidFileName) Error() string { - return fmt.Sprintf("Invalid wiki filename: %s", err.FileName) + return "Invalid wiki filename: " + err.FileName } func (err ErrWikiInvalidFileName) Unwrap() error { diff --git a/models/repo_test.go b/models/repo_test.go index bcf62237f0..b6c53fd197 100644 --- a/models/repo_test.go +++ b/models/repo_test.go @@ -29,10 +29,10 @@ func Test_repoStatsCorrectIssueNumComments(t *testing.T) { issue2 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) assert.NotNil(t, issue2) - assert.EqualValues(t, 0, issue2.NumComments) // the fixture data is wrong, but we don't fix it here + assert.Equal(t, 0, issue2.NumComments) // the fixture data is wrong, but we don't fix it here assert.NoError(t, repoStatsCorrectIssueNumComments(db.DefaultContext, 2)) // reload the issue issue2 = unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 2}) - assert.EqualValues(t, 1, issue2.NumComments) + assert.Equal(t, 1, issue2.NumComments) } diff --git a/models/system/setting_test.go b/models/system/setting_test.go index 8f04412fb4..7e7e0c8fca 100644 --- a/models/system/setting_test.go +++ b/models/system/setting_test.go @@ -21,24 +21,24 @@ func TestSettings(t *testing.T) { rev, settings, err := system.GetAllSettings(db.DefaultContext) assert.NoError(t, err) - assert.EqualValues(t, 1, rev) + assert.Equal(t, 1, rev) assert.Len(t, settings, 1) // there is only one "revision" key err = system.SetSettings(db.DefaultContext, map[string]string{keyName: "true"}) assert.NoError(t, err) rev, settings, err = system.GetAllSettings(db.DefaultContext) assert.NoError(t, err) - assert.EqualValues(t, 2, rev) + assert.Equal(t, 2, rev) assert.Len(t, settings, 2) - assert.EqualValues(t, "true", settings[keyName]) + assert.Equal(t, "true", settings[keyName]) err = system.SetSettings(db.DefaultContext, map[string]string{keyName: "false"}) assert.NoError(t, err) rev, settings, err = system.GetAllSettings(db.DefaultContext) assert.NoError(t, err) - assert.EqualValues(t, 3, rev) + assert.Equal(t, 3, rev) assert.Len(t, settings, 2) - assert.EqualValues(t, "false", settings[keyName]) + assert.Equal(t, "false", settings[keyName]) // setting the same value should not trigger DuplicateKey error, and the "version" should be increased err = system.SetSettings(db.DefaultContext, map[string]string{keyName: "false"}) @@ -47,5 +47,5 @@ func TestSettings(t *testing.T) { rev, settings, err = system.GetAllSettings(db.DefaultContext) assert.NoError(t, err) assert.Len(t, settings, 2) - assert.EqualValues(t, 4, rev) + assert.Equal(t, 4, rev) } diff --git a/models/unit/unit.go b/models/unit/unit.go index c816fc6c68..4ca676802f 100644 --- a/models/unit/unit.go +++ b/models/unit/unit.go @@ -20,17 +20,21 @@ type Type int // Enumerate all the unit types const ( - TypeInvalid Type = iota // 0 invalid - TypeCode // 1 code - TypeIssues // 2 issues - TypePullRequests // 3 PRs - TypeReleases // 4 Releases - TypeWiki // 5 Wiki - TypeExternalWiki // 6 ExternalWiki - TypeExternalTracker // 7 ExternalTracker - TypeProjects // 8 Projects - TypePackages // 9 Packages - TypeActions // 10 Actions + TypeInvalid Type = iota // 0 invalid + + TypeCode // 1 code + TypeIssues // 2 issues + TypePullRequests // 3 PRs + TypeReleases // 4 Releases + TypeWiki // 5 Wiki + TypeExternalWiki // 6 ExternalWiki + TypeExternalTracker // 7 ExternalTracker + TypeProjects // 8 Projects + TypePackages // 9 Packages + TypeActions // 10 Actions + + // FIXME: TEAM-UNIT-PERMISSION: the team unit "admin" permission's design is not right, when a new unit is added in the future, + // admin team won't inherit the correct admin permission for the new unit, need to have a complete fix before adding any new unit. ) // Value returns integer value for unit type (used by template) @@ -380,20 +384,3 @@ func AllUnitKeyNames() []string { } return res } - -// MinUnitAccessMode returns the minial permission of the permission map -func MinUnitAccessMode(unitsMap map[Type]perm.AccessMode) perm.AccessMode { - res := perm.AccessModeNone - for t, mode := range unitsMap { - // Don't allow `TypeExternal{Tracker,Wiki}` to influence this as they can only be set to READ perms. - if t == TypeExternalTracker || t == TypeExternalWiki { - continue - } - - // get the minial permission great than AccessModeNone except all are AccessModeNone - if mode > perm.AccessModeNone && (res == perm.AccessModeNone || mode < res) { - res = mode - } - } - return res -} diff --git a/models/unittest/consistency.go b/models/unittest/consistency.go index 71839001be..364afb5c52 100644 --- a/models/unittest/consistency.go +++ b/models/unittest/consistency.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models/db" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "xorm.io/builder" ) @@ -24,7 +25,7 @@ const ( var consistencyCheckMap = make(map[string]func(t assert.TestingT, bean any)) // CheckConsistencyFor test that all matching database entries are consistent -func CheckConsistencyFor(t assert.TestingT, beansToCheck ...any) { +func CheckConsistencyFor(t require.TestingT, beansToCheck ...any) { for _, bean := range beansToCheck { sliceType := reflect.SliceOf(reflect.TypeOf(bean)) sliceValue := reflect.MakeSlice(sliceType, 0, 10) @@ -42,13 +43,11 @@ func CheckConsistencyFor(t assert.TestingT, beansToCheck ...any) { } } -func checkForConsistency(t assert.TestingT, bean any) { +func checkForConsistency(t require.TestingT, bean any) { tb, err := db.TableInfo(bean) assert.NoError(t, err) f := consistencyCheckMap[tb.Name] - if f == nil { - assert.FailNow(t, "unknown bean type: %#v", bean) - } + require.NotNil(t, f, "unknown bean type: %#v", bean) f(t, bean) } @@ -71,8 +70,8 @@ func init() { AssertCountByCond(t, "follow", builder.Eq{"user_id": user.int("ID")}, user.int("NumFollowing")) AssertCountByCond(t, "follow", builder.Eq{"follow_id": user.int("ID")}, user.int("NumFollowers")) if user.int("Type") != modelsUserTypeOrganization { - assert.EqualValues(t, 0, user.int("NumMembers"), "Unexpected number of members for user id: %d", user.int("ID")) - assert.EqualValues(t, 0, user.int("NumTeams"), "Unexpected number of teams for user id: %d", user.int("ID")) + assert.Equal(t, 0, user.int("NumMembers"), "Unexpected number of members for user id: %d", user.int("ID")) + assert.Equal(t, 0, user.int("NumTeams"), "Unexpected number of teams for user id: %d", user.int("ID")) } } @@ -119,7 +118,7 @@ func init() { assert.EqualValues(t, issue.int("NumComments"), actual, "Unexpected number of comments for issue id: %d", issue.int("ID")) if issue.bool("IsPull") { prRow := AssertExistsAndLoadMap(t, "pull_request", builder.Eq{"issue_id": issue.int("ID")}) - assert.EqualValues(t, parseInt(prRow["index"]), issue.int("Index"), "Unexpected index for issue id: %d", issue.int("ID")) + assert.Equal(t, parseInt(prRow["index"]), issue.int("Index"), "Unexpected index for issue id: %d", issue.int("ID")) } } @@ -127,7 +126,7 @@ func init() { pr := reflectionWrap(bean) issueRow := AssertExistsAndLoadMap(t, "issue", builder.Eq{"id": pr.int("IssueID")}) assert.True(t, parseBool(issueRow["is_pull"])) - assert.EqualValues(t, parseInt(issueRow["index"]), pr.int("Index"), "Unexpected index for pull request id: %d", pr.int("ID")) + assert.Equal(t, parseInt(issueRow["index"]), pr.int("Index"), "Unexpected index for pull request id: %d", pr.int("ID")) } checkForMilestoneConsistency := func(t assert.TestingT, bean any) { diff --git a/models/unittest/fixtures_loader.go b/models/unittest/fixtures_loader.go index 674f5cbe54..0560da8349 100644 --- a/models/unittest/fixtures_loader.go +++ b/models/unittest/fixtures_loader.go @@ -120,7 +120,7 @@ func (f *fixturesLoaderInternal) loadFixtures(tx *sql.Tx, fixture *FixtureItem) } } - _, err = tx.Exec(fmt.Sprintf("DELETE FROM %s", fixture.tableNameQuoted)) // sqlite3 doesn't support truncate + _, err = tx.Exec("DELETE FROM " + fixture.tableNameQuoted) // sqlite3 doesn't support truncate if err != nil { return err } diff --git a/models/unittest/fscopy.go b/models/unittest/fscopy.go index b7ba6b7ef5..98b01815bd 100644 --- a/models/unittest/fscopy.go +++ b/models/unittest/fscopy.go @@ -28,7 +28,7 @@ func SyncFile(srcPath, destPath string) error { } if src.Size() == dest.Size() && - src.ModTime() == dest.ModTime() && + src.ModTime().Equal(dest.ModTime()) && src.Mode() == dest.Mode() { return nil } diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go index 7a9ca9698d..cb60cf5f85 100644 --- a/models/unittest/testdb.go +++ b/models/unittest/testdb.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting/config" "code.gitea.io/gitea/modules/storage" + "code.gitea.io/gitea/modules/tempdir" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/util" @@ -35,8 +36,8 @@ func fatalTestError(fmtStr string, args ...any) { os.Exit(1) } -// InitSettings initializes config provider and load common settings for tests -func InitSettings() { +// InitSettingsForTesting initializes config provider and load common settings for tests +func InitSettingsForTesting() { setting.IsInTesting = true log.OsExiter = func(code int) { if code != 0 { @@ -75,7 +76,7 @@ func MainTest(m *testing.M, testOptsArg ...*TestOptions) { testOpts := util.OptionalArg(testOptsArg, &TestOptions{}) giteaRoot = test.SetupGiteaRoot() setting.CustomPath = filepath.Join(giteaRoot, "custom") - InitSettings() + InitSettingsForTesting() fixturesOpts := FixturesOptions{Dir: filepath.Join(giteaRoot, "models", "fixtures"), Files: testOpts.FixtureFiles} if err := CreateTestEngine(fixturesOpts); err != nil { @@ -92,15 +93,19 @@ func MainTest(m *testing.M, testOptsArg ...*TestOptions) { setting.SSH.Domain = "try.gitea.io" setting.Database.Type = "sqlite3" setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master" - repoRootPath, err := os.MkdirTemp(os.TempDir(), "repos") + repoRootPath, cleanup1, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("repos") if err != nil { fatalTestError("TempDir: %v\n", err) } + defer cleanup1() + setting.RepoRootPath = repoRootPath - appDataPath, err := os.MkdirTemp(os.TempDir(), "appdata") + appDataPath, cleanup2, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("appdata") if err != nil { fatalTestError("TempDir: %v\n", err) } + defer cleanup2() + setting.AppDataPath = appDataPath setting.AppWorkPath = giteaRoot setting.StaticRootPath = giteaRoot @@ -153,13 +158,6 @@ func MainTest(m *testing.M, testOptsArg ...*TestOptions) { fatalTestError("tear down failed: %v\n", err) } } - - if err = util.RemoveAll(repoRootPath); err != nil { - fatalTestError("util.RemoveAll: %v\n", err) - } - if err = util.RemoveAll(appDataPath); err != nil { - fatalTestError("util.RemoveAll: %v\n", err) - } os.Exit(exitStatus) } diff --git a/models/unittest/unit_tests.go b/models/unittest/unit_tests.go index 1c5595aef8..4a4cec40ae 100644 --- a/models/unittest/unit_tests.go +++ b/models/unittest/unit_tests.go @@ -153,9 +153,9 @@ func DumpQueryResult(t require.TestingT, sqlOrBean any, sqlArgs ...any) { goDB := x.DB().DB sql, ok := sqlOrBean.(string) if !ok { - sql = fmt.Sprintf("SELECT * FROM %s", db.TableName(sqlOrBean)) + sql = "SELECT * FROM " + db.TableName(sqlOrBean) } else if !strings.Contains(sql, " ") { - sql = fmt.Sprintf("SELECT * FROM %s", sql) + sql = "SELECT * FROM " + sql } rows, err := goDB.Query(sql, sqlArgs...) require.NoError(t, err) diff --git a/models/user/avatar.go b/models/user/avatar.go index 2a41b99129..3d9fc4452f 100644 --- a/models/user/avatar.go +++ b/models/user/avatar.go @@ -61,7 +61,9 @@ func GenerateRandomAvatar(ctx context.Context, u *User) error { // AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string { - if u.IsGhost() || u.IsGiteaActions() { + // ghost user was deleted, Gitea actions is a bot user, 0 means the user should be a virtual user + // which comes from git configure information + if u.IsGhost() || u.IsGiteaActions() || u.ID <= 0 { return avatars.DefaultAvatarLink() } diff --git a/models/user/email_address_test.go b/models/user/email_address_test.go index d72d873de2..0e52950cfd 100644 --- a/models/user/email_address_test.go +++ b/models/user/email_address_test.go @@ -205,7 +205,7 @@ func TestEmailAddressValidate(t *testing.T) { } for kase, err := range kases { t.Run(kase, func(t *testing.T) { - assert.EqualValues(t, err, user_model.ValidateEmail(kase)) + assert.Equal(t, err, user_model.ValidateEmail(kase)) }) } } diff --git a/models/user/search.go b/models/user/search.go index 85915f4020..f4436be09a 100644 --- a/models/user/search.go +++ b/models/user/search.go @@ -45,13 +45,14 @@ func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Sess var cond builder.Cond cond = builder.Eq{"type": opts.Type} if opts.IncludeReserved { - if opts.Type == UserTypeIndividual { + switch opts.Type { + case UserTypeIndividual: cond = cond.Or(builder.Eq{"type": UserTypeUserReserved}).Or( builder.Eq{"type": UserTypeBot}, ).Or( builder.Eq{"type": UserTypeRemoteUser}, ) - } else if opts.Type == UserTypeOrganization { + case UserTypeOrganization: cond = cond.Or(builder.Eq{"type": UserTypeOrganizationReserved}) } } diff --git a/models/user/setting.go b/models/user/setting.go index b4af0e5ccd..c65afae76c 100644 --- a/models/user/setting.go +++ b/models/user/setting.go @@ -5,6 +5,7 @@ package user import ( "context" + "errors" "fmt" "strings" @@ -114,10 +115,10 @@ func GetUserAllSettings(ctx context.Context, uid int64) (map[string]*Setting, er func validateUserSettingKey(key string) error { if len(key) == 0 { - return fmt.Errorf("setting key must be set") + return errors.New("setting key must be set") } if strings.ToLower(key) != key { - return fmt.Errorf("setting key should be lowercase") + return errors.New("setting key should be lowercase") } return nil } diff --git a/models/user/setting_test.go b/models/user/setting_test.go index c607d9fd00..3c199013f3 100644 --- a/models/user/setting_test.go +++ b/models/user/setting_test.go @@ -30,15 +30,15 @@ func TestSettings(t *testing.T) { settings, err := user_model.GetSettings(db.DefaultContext, 99, []string{keyName}) assert.NoError(t, err) assert.Len(t, settings, 1) - assert.EqualValues(t, newSetting.SettingValue, settings[keyName].SettingValue) + assert.Equal(t, newSetting.SettingValue, settings[keyName].SettingValue) settingValue, err := user_model.GetUserSetting(db.DefaultContext, 99, keyName) assert.NoError(t, err) - assert.EqualValues(t, newSetting.SettingValue, settingValue) + assert.Equal(t, newSetting.SettingValue, settingValue) settingValue, err = user_model.GetUserSetting(db.DefaultContext, 99, "no_such") assert.NoError(t, err) - assert.EqualValues(t, "", settingValue) + assert.Empty(t, settingValue) // updated setting updatedSetting := &user_model.Setting{UserID: 99, SettingKey: keyName, SettingValue: "Updated"} @@ -49,7 +49,7 @@ func TestSettings(t *testing.T) { settings, err = user_model.GetUserAllSettings(db.DefaultContext, 99) assert.NoError(t, err) assert.Len(t, settings, 1) - assert.EqualValues(t, updatedSetting.SettingValue, settings[updatedSetting.SettingKey].SettingValue) + assert.Equal(t, updatedSetting.SettingValue, settings[updatedSetting.SettingKey].SettingValue) // delete setting err = user_model.DeleteUserSetting(db.DefaultContext, 99, keyName) diff --git a/models/user/user.go b/models/user/user.go index 3c72aa7cc4..100f924cc6 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -247,19 +247,20 @@ func (u *User) MaxCreationLimit() int { return u.MaxRepoCreation } -// CanCreateRepo returns if user login can create a repository -// NOTE: functions calling this assume a failure due to repository count limit; if new checks are added, those functions should be revised -func (u *User) CanCreateRepo() bool { +// CanCreateRepoIn checks whether the doer(u) can create a repository in the owner +// NOTE: functions calling this assume a failure due to repository count limit; it ONLY checks the repo number LIMIT, if new checks are added, those functions should be revised +func (u *User) CanCreateRepoIn(owner *User) bool { if u.IsAdmin { return true } - if u.MaxRepoCreation <= -1 { - if setting.Repository.MaxCreationLimit <= -1 { + const noLimit = -1 + if owner.MaxRepoCreation == noLimit { + if setting.Repository.MaxCreationLimit == noLimit { return true } - return u.NumRepos < setting.Repository.MaxCreationLimit + return owner.NumRepos < setting.Repository.MaxCreationLimit } - return u.NumRepos < u.MaxRepoCreation + return owner.NumRepos < owner.MaxRepoCreation } // CanCreateOrganization returns true if user can create organisation. @@ -272,13 +273,12 @@ func (u *User) CanEditGitHook() bool { return !setting.DisableGitHooks && (u.IsAdmin || u.AllowGitHook) } -// CanForkRepo returns if user login can fork a repository -// It checks especially that the user can create repos, and potentially more -func (u *User) CanForkRepo() bool { +// CanForkRepoIn ONLY checks repository count limit +func (u *User) CanForkRepoIn(owner *User) bool { if setting.Repository.AllowForkWithoutMaximumLimit { return true } - return u.CanCreateRepo() + return u.CanCreateRepoIn(owner) } // CanImportLocal returns true if user can migrate repository by local path. @@ -1169,8 +1169,8 @@ func GetUsersByEmails(ctx context.Context, emails []string) (map[string]*User, e needCheckEmails := make(container.Set[string]) needCheckUserNames := make(container.Set[string]) for _, email := range emails { - if strings.HasSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress)) { - username := strings.TrimSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress)) + if strings.HasSuffix(email, "@"+setting.Service.NoReplyAddress) { + username := strings.TrimSuffix(email, "@"+setting.Service.NoReplyAddress) needCheckUserNames.Add(username) } else { needCheckEmails.Add(strings.ToLower(email)) @@ -1187,29 +1187,28 @@ func GetUsersByEmails(ctx context.Context, emails []string) (map[string]*User, e for _, email := range emailAddresses { userIDs.Add(email.UID) } - users, err := GetUsersMapByIDs(ctx, userIDs.Values()) - if err != nil { - return nil, err - } - results := make(map[string]*User, len(emails)) - for _, email := range emailAddresses { - user := users[email.UID] - if user != nil { - if user.KeepEmailPrivate { - results[user.LowerName+"@"+setting.Service.NoReplyAddress] = user - } else { - results[email.Email] = user + + if len(userIDs) > 0 { + users, err := GetUsersMapByIDs(ctx, userIDs.Values()) + if err != nil { + return nil, err + } + + for _, email := range emailAddresses { + user := users[email.UID] + if user != nil { + results[user.GetEmail()] = user } } } - users = make(map[int64]*User, len(needCheckUserNames)) + users := make(map[int64]*User, len(needCheckUserNames)) if err := db.GetEngine(ctx).In("lower_name", needCheckUserNames.Values()).Find(&users); err != nil { return nil, err } for _, user := range users { - results[user.LowerName+"@"+setting.Service.NoReplyAddress] = user + results[user.GetPlaceholderEmail()] = user } return results, nil } @@ -1232,8 +1231,8 @@ func GetUserByEmail(ctx context.Context, email string) (*User, error) { } // Finally, if email address is the protected email address: - if strings.HasSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress)) { - username := strings.TrimSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress)) + if strings.HasSuffix(email, "@"+setting.Service.NoReplyAddress) { + username := strings.TrimSuffix(email, "@"+setting.Service.NoReplyAddress) user := &User{} has, err := db.GetEngine(ctx).Where("lower_name=?", username).Get(user) if err != nil { diff --git a/models/user/user_test.go b/models/user/user_test.go index 1132c02f28..90e8bf13a8 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -19,6 +19,7 @@ import ( "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/timeutil" "github.com/stretchr/testify/assert" @@ -83,7 +84,7 @@ func TestSearchUsers(t *testing.T) { cassText := fmt.Sprintf("ids: %v, opts: %v", expectedUserOrOrgIDs, opts) if assert.Len(t, users, len(expectedUserOrOrgIDs), "case: %s", cassText) { for i, expectedID := range expectedUserOrOrgIDs { - assert.EqualValues(t, expectedID, users[i].ID, "case: %s", cassText) + assert.Equal(t, expectedID, users[i].ID, "case: %s", cassText) } } } @@ -513,7 +514,7 @@ func Test_ValidateUser(t *testing.T) { {ID: 2, Visibility: structs.VisibleTypePrivate}: true, } for kase, expected := range kases { - assert.EqualValues(t, expected, nil == user_model.ValidateUser(kase), "case: %+v", kase) + assert.Equal(t, expected, nil == user_model.ValidateUser(kase), "case: %+v", kase) } } @@ -537,7 +538,7 @@ func Test_NormalizeUserFromEmail(t *testing.T) { for _, testCase := range testCases { normalizedName, err := user_model.NormalizeUserName(testCase.Input) assert.NoError(t, err) - assert.EqualValues(t, testCase.Expected, normalizedName) + assert.Equal(t, testCase.Expected, normalizedName) if testCase.IsNormalizedValid { assert.NoError(t, user_model.IsUsableUsername(normalizedName)) } else { @@ -564,7 +565,7 @@ func TestEmailTo(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.result, func(t *testing.T) { testUser := &user_model.User{FullName: testCase.fullName, Email: testCase.mail} - assert.EqualValues(t, testCase.result, testUser.EmailTo()) + assert.Equal(t, testCase.result, testUser.EmailTo()) }) } } @@ -616,3 +617,37 @@ func TestGetInactiveUsers(t *testing.T) { assert.NoError(t, err) assert.Empty(t, users) } + +func TestCanCreateRepo(t *testing.T) { + defer test.MockVariableValue(&setting.Repository.MaxCreationLimit)() + const noLimit = -1 + doerNormal := &user_model.User{} + doerAdmin := &user_model.User{IsAdmin: true} + t.Run("NoGlobalLimit", func(t *testing.T) { + setting.Repository.MaxCreationLimit = noLimit + + assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: noLimit})) + assert.False(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 0})) + assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 100})) + + assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: noLimit})) + assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 0})) + assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 100})) + }) + + t.Run("GlobalLimit50", func(t *testing.T) { + setting.Repository.MaxCreationLimit = 50 + + assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: noLimit})) + assert.False(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 60, MaxRepoCreation: noLimit})) // limited by global limit + assert.False(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 0})) + assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 100})) + assert.True(t, doerNormal.CanCreateRepoIn(&user_model.User{NumRepos: 60, MaxRepoCreation: 100})) + + assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: noLimit})) + assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 60, MaxRepoCreation: noLimit})) + assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 0})) + assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 10, MaxRepoCreation: 100})) + assert.True(t, doerAdmin.CanCreateRepoIn(&user_model.User{NumRepos: 60, MaxRepoCreation: 100})) + }) +} diff --git a/models/webhook/hooktask.go b/models/webhook/hooktask.go index ff3fdbadb2..96ec11e43f 100644 --- a/models/webhook/hooktask.go +++ b/models/webhook/hooktask.go @@ -198,7 +198,8 @@ func MarkTaskDelivered(ctx context.Context, task *HookTask) (bool, error) { func CleanupHookTaskTable(ctx context.Context, cleanupType HookTaskCleanupType, olderThan time.Duration, numberToKeep int) error { log.Trace("Doing: CleanupHookTaskTable") - if cleanupType == OlderThan { + switch cleanupType { + case OlderThan: deleteOlderThan := time.Now().Add(-olderThan).UnixNano() deletes, err := db.GetEngine(ctx). Where("is_delivered = ? and delivered < ?", true, deleteOlderThan). @@ -207,7 +208,7 @@ func CleanupHookTaskTable(ctx context.Context, cleanupType HookTaskCleanupType, return err } log.Trace("Deleted %d rows from hook_task", deletes) - } else if cleanupType == PerWebhook { + case PerWebhook: hookIDs := make([]int64, 0, 10) err := db.GetEngine(ctx). Table("webhook"). diff --git a/models/webhook/webhook_test.go b/models/webhook/webhook_test.go index 6ff77a380d..e8a2547c65 100644 --- a/models/webhook/webhook_test.go +++ b/models/webhook/webhook_test.go @@ -67,7 +67,7 @@ func TestWebhook_UpdateEvent(t *testing.T) { } func TestWebhook_EventsArray(t *testing.T) { - assert.EqualValues(t, []string{ + assert.Equal(t, []string{ "create", "delete", "fork", "push", "issues", "issue_assign", "issue_label", "issue_milestone", "issue_comment", "pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone", @@ -90,7 +90,7 @@ func TestWebhook_EventsArray(t *testing.T) { func TestCreateWebhook(t *testing.T) { hook := &Webhook{ RepoID: 3, - URL: "www.example.com/unit_test", + URL: "https://www.example.com/unit_test", ContentType: ContentTypeJSON, Events: `{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}`, } diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index 0d2b0dd919..a538b6e290 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -463,7 +463,7 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa matchTimes++ } case "paths": - filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.Base.Ref) + filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.MergeBase) if err != nil { log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err) } else { @@ -476,7 +476,7 @@ func matchPullRequestEvent(gitRepo *git.Repository, commit *git.Commit, prPayloa } } case "paths-ignore": - filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.Base.Ref) + filesChanged, err := headCommit.GetFilesChangedSinceCommit(prPayload.PullRequest.MergeBase) if err != nil { log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", headCommit.ID.String(), err) } else { diff --git a/modules/assetfs/layered_test.go b/modules/assetfs/layered_test.go index 03a3ae0d7c..b549815ea5 100644 --- a/modules/assetfs/layered_test.go +++ b/modules/assetfs/layered_test.go @@ -52,7 +52,7 @@ func TestLayered(t *testing.T) { assert.NoError(t, err) bs, err := io.ReadAll(f) assert.NoError(t, err) - assert.EqualValues(t, "f1", string(bs)) + assert.Equal(t, "f1", string(bs)) _ = f.Close() assertRead := func(expected string, expectedErr error, elems ...string) { @@ -76,27 +76,27 @@ func TestLayered(t *testing.T) { files, err := assets.ListFiles(".", true) assert.NoError(t, err) - assert.EqualValues(t, []string{"f1", "f2", "fa"}, files) + assert.Equal(t, []string{"f1", "f2", "fa"}, files) files, err = assets.ListFiles(".", false) assert.NoError(t, err) - assert.EqualValues(t, []string{"d1", "d2", "da"}, files) + assert.Equal(t, []string{"d1", "d2", "da"}, files) files, err = assets.ListFiles(".") assert.NoError(t, err) - assert.EqualValues(t, []string{"d1", "d2", "da", "f1", "f2", "fa"}, files) + assert.Equal(t, []string{"d1", "d2", "da", "f1", "f2", "fa"}, files) files, err = assets.ListAllFiles(".", true) assert.NoError(t, err) - assert.EqualValues(t, []string{"d1/f", "d2/f", "da/f", "f1", "f2", "fa"}, files) + assert.Equal(t, []string{"d1/f", "d2/f", "da/f", "f1", "f2", "fa"}, files) files, err = assets.ListAllFiles(".", false) assert.NoError(t, err) - assert.EqualValues(t, []string{"d1", "d2", "da", "da/sub1", "da/sub2"}, files) + assert.Equal(t, []string{"d1", "d2", "da", "da/sub1", "da/sub2"}, files) files, err = assets.ListAllFiles(".") assert.NoError(t, err) - assert.EqualValues(t, []string{ + assert.Equal(t, []string{ "d1", "d1/f", "d2", "d2/f", "da", "da/f", "da/sub1", "da/sub2", @@ -104,6 +104,6 @@ func TestLayered(t *testing.T) { }, files) assert.Empty(t, assets.GetFileLayerName("no-such")) - assert.EqualValues(t, "l1", assets.GetFileLayerName("f1")) - assert.EqualValues(t, "l2", assets.GetFileLayerName("f2")) + assert.Equal(t, "l1", assets.GetFileLayerName("f1")) + assert.Equal(t, "l2", assets.GetFileLayerName("f2")) } diff --git a/modules/avatar/avatar_test.go b/modules/avatar/avatar_test.go index a721c77868..a5a1a7c1b0 100644 --- a/modules/avatar/avatar_test.go +++ b/modules/avatar/avatar_test.go @@ -94,8 +94,8 @@ func Test_ProcessAvatarImage(t *testing.T) { assert.NotEqual(t, origin, result) decoded, err := png.Decode(bytes.NewReader(result)) assert.NoError(t, err) - assert.EqualValues(t, scaledSize, decoded.Bounds().Max.X) - assert.EqualValues(t, scaledSize, decoded.Bounds().Max.Y) + assert.Equal(t, scaledSize, decoded.Bounds().Max.X) + assert.Equal(t, scaledSize, decoded.Bounds().Max.Y) // if origin image is smaller than the default size, use the origin image origin = newImgData(1) diff --git a/modules/avatar/hash_test.go b/modules/avatar/hash_test.go index 1b8249c696..c518144b47 100644 --- a/modules/avatar/hash_test.go +++ b/modules/avatar/hash_test.go @@ -19,8 +19,8 @@ func Test_HashAvatar(t *testing.T) { var buff bytes.Buffer png.Encode(&buff, myImage) - assert.EqualValues(t, "9ddb5bac41d57e72aa876321d0c09d71090c05f94bc625303801be2f3240d2cb", avatar.HashAvatar(1, buff.Bytes())) - assert.EqualValues(t, "9a5d44e5d637b9582a976676e8f3de1dccd877c2fe3e66ca3fab1629f2f47609", avatar.HashAvatar(8, buff.Bytes())) - assert.EqualValues(t, "ed7399158672088770de6f5211ce15528ebd675e92fc4fc060c025f4b2794ccb", avatar.HashAvatar(1024, buff.Bytes())) - assert.EqualValues(t, "161178642c7d59eb25a61dddced5e6b66eae1c70880d5f148b1b497b767e72d9", avatar.HashAvatar(1024, []byte{})) + assert.Equal(t, "9ddb5bac41d57e72aa876321d0c09d71090c05f94bc625303801be2f3240d2cb", avatar.HashAvatar(1, buff.Bytes())) + assert.Equal(t, "9a5d44e5d637b9582a976676e8f3de1dccd877c2fe3e66ca3fab1629f2f47609", avatar.HashAvatar(8, buff.Bytes())) + assert.Equal(t, "ed7399158672088770de6f5211ce15528ebd675e92fc4fc060c025f4b2794ccb", avatar.HashAvatar(1024, buff.Bytes())) + assert.Equal(t, "161178642c7d59eb25a61dddced5e6b66eae1c70880d5f148b1b497b767e72d9", avatar.HashAvatar(1024, []byte{})) } diff --git a/modules/avatar/identicon/identicon.go b/modules/avatar/identicon/identicon.go index 63926d5f19..87bd87796e 100644 --- a/modules/avatar/identicon/identicon.go +++ b/modules/avatar/identicon/identicon.go @@ -8,6 +8,7 @@ package identicon import ( "crypto/sha256" + "errors" "fmt" "image" "image/color" @@ -29,7 +30,7 @@ type Identicon struct { // fore all possible foreground colors. only one foreground color will be picked randomly for one image func New(size int, back color.Color, fore ...color.Color) (*Identicon, error) { if len(fore) == 0 { - return nil, fmt.Errorf("foreground is not set") + return nil, errors.New("foreground is not set") } if size < minImageSize { diff --git a/modules/badge/badge.go b/modules/badge/badge.go index fdf9866f60..d2e9bd9d1b 100644 --- a/modules/badge/badge.go +++ b/modules/badge/badge.go @@ -5,6 +5,7 @@ package badge import ( "strings" + "sync" "unicode" actions_model "code.gitea.io/gitea/models/actions" @@ -49,23 +50,40 @@ func (b Badge) Width() int { return b.Label.width + b.Message.width } +// Style follows https://shields.io/badges +const ( + StyleFlat = "flat" + StyleFlatSquare = "flat-square" +) + const ( defaultOffset = 10 defaultFontSize = 11 DefaultColor = "#9f9f9f" // Grey DefaultFontFamily = "DejaVu Sans,Verdana,Geneva,sans-serif" + DefaultStyle = StyleFlat ) -var StatusColorMap = map[actions_model.Status]string{ - actions_model.StatusSuccess: "#4c1", // Green - actions_model.StatusSkipped: "#dfb317", // Yellow - actions_model.StatusUnknown: "#97ca00", // Light Green - actions_model.StatusFailure: "#e05d44", // Red - actions_model.StatusCancelled: "#fe7d37", // Orange - actions_model.StatusWaiting: "#dfb317", // Yellow - actions_model.StatusRunning: "#dfb317", // Yellow - actions_model.StatusBlocked: "#dfb317", // Yellow -} +var GlobalVars = sync.OnceValue(func() (ret struct { + StatusColorMap map[actions_model.Status]string + DejaVuGlyphWidthData map[rune]uint8 + AllStyles []string +}, +) { + ret.StatusColorMap = map[actions_model.Status]string{ + actions_model.StatusSuccess: "#4c1", // Green + actions_model.StatusSkipped: "#dfb317", // Yellow + actions_model.StatusUnknown: "#97ca00", // Light Green + actions_model.StatusFailure: "#e05d44", // Red + actions_model.StatusCancelled: "#fe7d37", // Orange + actions_model.StatusWaiting: "#dfb317", // Yellow + actions_model.StatusRunning: "#dfb317", // Yellow + actions_model.StatusBlocked: "#dfb317", // Yellow + } + ret.DejaVuGlyphWidthData = dejaVuGlyphWidthDataFunc() + ret.AllStyles = []string{StyleFlat, StyleFlatSquare} + return ret +}) // GenerateBadge generates badge with given template func GenerateBadge(label, message, color string) Badge { @@ -93,7 +111,7 @@ func GenerateBadge(label, message, color string) Badge { func calculateTextWidth(text string) int { width := 0 - widthData := DejaVuGlyphWidthData() + widthData := GlobalVars().DejaVuGlyphWidthData for _, char := range strings.TrimSpace(text) { charWidth, ok := widthData[char] if !ok { diff --git a/modules/badge/badge_glyph_width.go b/modules/badge/badge_glyph_width.go index e8e43ec9cb..0d950c5a70 100644 --- a/modules/badge/badge_glyph_width.go +++ b/modules/badge/badge_glyph_width.go @@ -3,8 +3,6 @@ package badge -import "sync" - // DejaVuGlyphWidthData is generated by `sfnt.Face.GlyphAdvance(nil, , 11, font.HintingNone)` with DejaVu Sans // v2.37 (https://github.com/dejavu-fonts/dejavu-fonts/releases/download/version_2_37/dejavu-sans-ttf-2.37.zip). // @@ -13,7 +11,7 @@ import "sync" // // A devtest page "/devtest/badge-actions-svg" could be used to check the rendered images. -var DejaVuGlyphWidthData = sync.OnceValue(func() map[rune]uint8 { +func dejaVuGlyphWidthDataFunc() map[rune]uint8 { return map[rune]uint8{ 32: 3, 33: 4, @@ -205,4 +203,4 @@ var DejaVuGlyphWidthData = sync.OnceValue(func() map[rune]uint8 { 254: 7, 255: 7, } -}) +} diff --git a/modules/cache/cache.go b/modules/cache/cache.go index f7828e3cae..a434c13b67 100644 --- a/modules/cache/cache.go +++ b/modules/cache/cache.go @@ -4,6 +4,8 @@ package cache import ( + "encoding/hex" + "errors" "fmt" "strconv" "time" @@ -48,10 +50,10 @@ const ( // returns func Test() (time.Duration, error) { if defaultCache == nil { - return 0, fmt.Errorf("default cache not initialized") + return 0, errors.New("default cache not initialized") } - testData := fmt.Sprintf("%x", make([]byte, 500)) + testData := hex.EncodeToString(make([]byte, 500)) start := time.Now() @@ -63,10 +65,10 @@ func Test() (time.Duration, error) { } testVal, hit := defaultCache.Get(testCacheKey) if !hit { - return 0, fmt.Errorf("expect cache hit but got none") + return 0, errors.New("expect cache hit but got none") } if testVal != testData { - return 0, fmt.Errorf("expect cache to return same value as stored but got other") + return 0, errors.New("expect cache to return same value as stored but got other") } return time.Since(start), nil diff --git a/modules/cache/cache_test.go b/modules/cache/cache_test.go index 5408020306..d6ea2032ee 100644 --- a/modules/cache/cache_test.go +++ b/modules/cache/cache_test.go @@ -4,7 +4,7 @@ package cache import ( - "fmt" + "errors" "testing" "time" @@ -57,22 +57,22 @@ func TestGetString(t *testing.T) { createTestCache() data, err := GetString("key", func() (string, error) { - return "", fmt.Errorf("some error") + return "", errors.New("some error") }) assert.Error(t, err) - assert.Equal(t, "", data) + assert.Empty(t, data) data, err = GetString("key", func() (string, error) { return "", nil }) assert.NoError(t, err) - assert.Equal(t, "", data) + assert.Empty(t, data) data, err = GetString("key", func() (string, error) { return "some data", nil }) assert.NoError(t, err) - assert.Equal(t, "", data) + assert.Empty(t, data) Remove("key") data, err = GetString("key", func() (string, error) { @@ -82,7 +82,7 @@ func TestGetString(t *testing.T) { assert.Equal(t, "some data", data) data, err = GetString("key", func() (string, error) { - return "", fmt.Errorf("some error") + return "", errors.New("some error") }) assert.NoError(t, err) assert.Equal(t, "some data", data) @@ -93,7 +93,7 @@ func TestGetInt64(t *testing.T) { createTestCache() data, err := GetInt64("key", func() (int64, error) { - return 0, fmt.Errorf("some error") + return 0, errors.New("some error") }) assert.Error(t, err) assert.EqualValues(t, 0, data) @@ -118,7 +118,7 @@ func TestGetInt64(t *testing.T) { assert.EqualValues(t, 100, data) data, err = GetInt64("key", func() (int64, error) { - return 0, fmt.Errorf("some error") + return 0, errors.New("some error") }) assert.NoError(t, err) assert.EqualValues(t, 100, data) diff --git a/modules/cache/context.go b/modules/cache/context.go index 484cee659a..23f7c23a52 100644 --- a/modules/cache/context.go +++ b/modules/cache/context.go @@ -5,176 +5,39 @@ package cache import ( "context" - "sync" "time" - - "code.gitea.io/gitea/modules/log" ) -// cacheContext is a context that can be used to cache data in a request level context -// This is useful for caching data that is expensive to calculate and is likely to be -// used multiple times in a request. -type cacheContext struct { - data map[any]map[any]any - lock sync.RWMutex - created time.Time - discard bool -} +type cacheContextKeyType struct{} -func (cc *cacheContext) Get(tp, key any) any { - cc.lock.RLock() - defer cc.lock.RUnlock() - return cc.data[tp][key] -} +var cacheContextKey = cacheContextKeyType{} -func (cc *cacheContext) Put(tp, key, value any) { - cc.lock.Lock() - defer cc.lock.Unlock() - - if cc.discard { - return - } - - d := cc.data[tp] - if d == nil { - d = make(map[any]any) - cc.data[tp] = d - } - d[key] = value -} - -func (cc *cacheContext) Delete(tp, key any) { - cc.lock.Lock() - defer cc.lock.Unlock() - delete(cc.data[tp], key) -} - -func (cc *cacheContext) Discard() { - cc.lock.Lock() - defer cc.lock.Unlock() - cc.data = nil - cc.discard = true -} - -func (cc *cacheContext) isDiscard() bool { - cc.lock.RLock() - defer cc.lock.RUnlock() - return cc.discard -} - -// cacheContextLifetime is the max lifetime of cacheContext. -// Since cacheContext is used to cache data in a request level context, 5 minutes is enough. -// If a cacheContext is used more than 5 minutes, it's probably misuse. -const cacheContextLifetime = 5 * time.Minute - -var timeNow = time.Now - -func (cc *cacheContext) Expired() bool { - return timeNow().Sub(cc.created) > cacheContextLifetime -} - -var cacheContextKey = struct{}{} - -/* -Since there are both WithCacheContext and WithNoCacheContext, -it may be confusing when there is nesting. - -Some cases to explain the design: - -When: -- A, B or C means a cache context. -- A', B' or C' means a discard cache context. -- ctx means context.Backgrand(). -- A(ctx) means a cache context with ctx as the parent context. -- B(A(ctx)) means a cache context with A(ctx) as the parent context. -- With is alias of WithCacheContext. -- WithNo is alias of WithNoCacheContext. - -So: -- With(ctx) -> A(ctx) -- With(With(ctx)) -> A(ctx), not B(A(ctx)), always reuse parent cache context if possible. -- With(With(With(ctx))) -> A(ctx), not C(B(A(ctx))), ditto. -- WithNo(ctx) -> ctx, not A'(ctx), don't create new cache context if we don't have to. -- WithNo(With(ctx)) -> A'(ctx) -- WithNo(WithNo(With(ctx))) -> A'(ctx), not B'(A'(ctx)), don't create new cache context if we don't have to. -- With(WithNo(With(ctx))) -> B(A'(ctx)), not A(ctx), never reuse a discard cache context. -- WithNo(With(WithNo(With(ctx)))) -> B'(A'(ctx)) -- With(WithNo(With(WithNo(With(ctx))))) -> C(B'(A'(ctx))), so there's always only one not-discard cache context. -*/ +// contextCacheLifetime is the max lifetime of context cache. +// Since context cache is used to cache data in a request level context, 5 minutes is enough. +// If a context cache is used more than 5 minutes, it's probably abused. +const contextCacheLifetime = 5 * time.Minute func WithCacheContext(ctx context.Context) context.Context { - if c, ok := ctx.Value(cacheContextKey).(*cacheContext); ok { - if !c.isDiscard() { - // reuse parent context - return ctx - } - } - // FIXME: review the use of this nolint directive - return context.WithValue(ctx, cacheContextKey, &cacheContext{ //nolint:staticcheck - data: make(map[any]map[any]any), - created: timeNow(), - }) -} - -func WithNoCacheContext(ctx context.Context) context.Context { - if c, ok := ctx.Value(cacheContextKey).(*cacheContext); ok { - // The caller want to run long-life tasks, but the parent context is a cache context. - // So we should disable and clean the cache data, or it will be kept in memory for a long time. - c.Discard() + if c := GetContextCache(ctx); c != nil { return ctx } - - return ctx + return context.WithValue(ctx, cacheContextKey, NewEphemeralCache(contextCacheLifetime)) } -func GetContextData(ctx context.Context, tp, key any) any { - if c, ok := ctx.Value(cacheContextKey).(*cacheContext); ok { - if c.Expired() { - // The warning means that the cache context is misused for long-life task, - // it can be resolved with WithNoCacheContext(ctx). - log.Warn("cache context is expired, is highly likely to be misused for long-life tasks: %v", c) - return nil - } - return c.Get(tp, key) - } - return nil -} - -func SetContextData(ctx context.Context, tp, key, value any) { - if c, ok := ctx.Value(cacheContextKey).(*cacheContext); ok { - if c.Expired() { - // The warning means that the cache context is misused for long-life task, - // it can be resolved with WithNoCacheContext(ctx). - log.Warn("cache context is expired, is highly likely to be misused for long-life tasks: %v", c) - return - } - c.Put(tp, key, value) - return - } -} - -func RemoveContextData(ctx context.Context, tp, key any) { - if c, ok := ctx.Value(cacheContextKey).(*cacheContext); ok { - if c.Expired() { - // The warning means that the cache context is misused for long-life task, - // it can be resolved with WithNoCacheContext(ctx). - log.Warn("cache context is expired, is highly likely to be misused for long-life tasks: %v", c) - return - } - c.Delete(tp, key) - } +func GetContextCache(ctx context.Context) *EphemeralCache { + c, _ := ctx.Value(cacheContextKey).(*EphemeralCache) + return c } // GetWithContextCache returns the cache value of the given key in the given context. -func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) { - v := GetContextData(ctx, cacheGroupKey, cacheTargetID) - if vv, ok := v.(T); ok { - return vv, nil +// FIXME: in some cases, the "context cache" should not be used, because it has uncontrollable behaviors +// For example, these calls: +// * GetWithContextCache(TargetID) -> OtherCodeCreateModel(TargetID) -> GetWithContextCache(TargetID) +// Will cause the second call is not able to get the correct created target. +// UNLESS it is certain that the target won't be changed during the request, DO NOT use it. +func GetWithContextCache[T, K any](ctx context.Context, groupKey string, targetKey K, f func(context.Context, K) (T, error)) (T, error) { + if c := GetContextCache(ctx); c != nil { + return GetWithEphemeralCache(ctx, c, groupKey, targetKey, f) } - t, err := f() - if err != nil { - return t, err - } - SetContextData(ctx, cacheGroupKey, cacheTargetID, t) - return t, nil + return f(ctx, targetKey) } diff --git a/modules/cache/context_test.go b/modules/cache/context_test.go index cfc186a7bd..8371c2b908 100644 --- a/modules/cache/context_test.go +++ b/modules/cache/context_test.go @@ -4,74 +4,47 @@ package cache import ( + "context" "testing" "time" + "code.gitea.io/gitea/modules/test" + "github.com/stretchr/testify/assert" ) func TestWithCacheContext(t *testing.T) { ctx := WithCacheContext(t.Context()) - - v := GetContextData(ctx, "empty_field", "my_config1") + c := GetContextCache(ctx) + v, _ := c.Get("empty_field", "my_config1") assert.Nil(t, v) const field = "system_setting" - v = GetContextData(ctx, field, "my_config1") + v, _ = c.Get(field, "my_config1") assert.Nil(t, v) - SetContextData(ctx, field, "my_config1", 1) - v = GetContextData(ctx, field, "my_config1") + c.Put(field, "my_config1", 1) + v, _ = c.Get(field, "my_config1") assert.NotNil(t, v) - assert.EqualValues(t, 1, v.(int)) + assert.Equal(t, 1, v.(int)) - RemoveContextData(ctx, field, "my_config1") - RemoveContextData(ctx, field, "my_config2") // remove a non-exist key + c.Delete(field, "my_config1") + c.Delete(field, "my_config2") // remove a non-exist key - v = GetContextData(ctx, field, "my_config1") + v, _ = c.Get(field, "my_config1") assert.Nil(t, v) - vInt, err := GetWithContextCache(ctx, field, "my_config1", func() (int, error) { + vInt, err := GetWithContextCache(ctx, field, "my_config1", func(context.Context, string) (int, error) { return 1, nil }) assert.NoError(t, err) - assert.EqualValues(t, 1, vInt) + assert.Equal(t, 1, vInt) - v = GetContextData(ctx, field, "my_config1") + v, _ = c.Get(field, "my_config1") assert.EqualValues(t, 1, v) - now := timeNow - defer func() { - timeNow = now - }() - timeNow = func() time.Time { - return now().Add(5 * time.Minute) - } - v = GetContextData(ctx, field, "my_config1") + defer test.MockVariableValue(&timeNow, func() time.Time { + return time.Now().Add(5 * time.Minute) + })() + v, _ = c.Get(field, "my_config1") assert.Nil(t, v) } - -func TestWithNoCacheContext(t *testing.T) { - ctx := t.Context() - - const field = "system_setting" - - v := GetContextData(ctx, field, "my_config1") - assert.Nil(t, v) - SetContextData(ctx, field, "my_config1", 1) - v = GetContextData(ctx, field, "my_config1") - assert.Nil(t, v) // still no cache - - ctx = WithCacheContext(ctx) - v = GetContextData(ctx, field, "my_config1") - assert.Nil(t, v) - SetContextData(ctx, field, "my_config1", 1) - v = GetContextData(ctx, field, "my_config1") - assert.NotNil(t, v) - - ctx = WithNoCacheContext(ctx) - v = GetContextData(ctx, field, "my_config1") - assert.Nil(t, v) - SetContextData(ctx, field, "my_config1", 1) - v = GetContextData(ctx, field, "my_config1") - assert.Nil(t, v) // still no cache -} diff --git a/modules/cache/ephemeral.go b/modules/cache/ephemeral.go new file mode 100644 index 0000000000..6996010ac4 --- /dev/null +++ b/modules/cache/ephemeral.go @@ -0,0 +1,90 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cache + +import ( + "context" + "sync" + "time" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" +) + +// EphemeralCache is a cache that can be used to store data in a request level context +// This is useful for caching data that is expensive to calculate and is likely to be +// used multiple times in a request. +type EphemeralCache struct { + data map[any]map[any]any + lock sync.RWMutex + created time.Time + checkLifeTime time.Duration +} + +var timeNow = time.Now + +func NewEphemeralCache(checkLifeTime ...time.Duration) *EphemeralCache { + return &EphemeralCache{ + data: make(map[any]map[any]any), + created: timeNow(), + checkLifeTime: util.OptionalArg(checkLifeTime, 0), + } +} + +func (cc *EphemeralCache) checkExceededLifeTime(tp, key any) bool { + if cc.checkLifeTime > 0 && timeNow().Sub(cc.created) > cc.checkLifeTime { + log.Warn("EphemeralCache is expired, is highly likely to be abused for long-life tasks: %v, %v", tp, key) + return true + } + return false +} + +func (cc *EphemeralCache) Get(tp, key any) (any, bool) { + if cc.checkExceededLifeTime(tp, key) { + return nil, false + } + cc.lock.RLock() + defer cc.lock.RUnlock() + ret, ok := cc.data[tp][key] + return ret, ok +} + +func (cc *EphemeralCache) Put(tp, key, value any) { + if cc.checkExceededLifeTime(tp, key) { + return + } + + cc.lock.Lock() + defer cc.lock.Unlock() + + d := cc.data[tp] + if d == nil { + d = make(map[any]any) + cc.data[tp] = d + } + d[key] = value +} + +func (cc *EphemeralCache) Delete(tp, key any) { + if cc.checkExceededLifeTime(tp, key) { + return + } + + cc.lock.Lock() + defer cc.lock.Unlock() + delete(cc.data[tp], key) +} + +func GetWithEphemeralCache[T, K any](ctx context.Context, c *EphemeralCache, groupKey string, targetKey K, f func(context.Context, K) (T, error)) (T, error) { + v, has := c.Get(groupKey, targetKey) + if vv, ok := v.(T); has && ok { + return vv, nil + } + t, err := f(ctx, targetKey) + if err != nil { + return t, err + } + c.Put(groupKey, targetKey, t) + return t, nil +} diff --git a/modules/cachegroup/cachegroup.go b/modules/cachegroup/cachegroup.go new file mode 100644 index 0000000000..06085f860f --- /dev/null +++ b/modules/cachegroup/cachegroup.go @@ -0,0 +1,12 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cachegroup + +const ( + User = "user" + EmailAvatarLink = "email_avatar_link" + UserEmailAddresses = "user_email_addresses" + GPGKeyWithSubKeys = "gpg_key_with_subkeys" + RepoUserPermission = "repo_user_permission" +) diff --git a/modules/charset/ambiguous_gen_test.go b/modules/charset/ambiguous_gen_test.go index 221c27d0e1..d3be0b1a13 100644 --- a/modules/charset/ambiguous_gen_test.go +++ b/modules/charset/ambiguous_gen_test.go @@ -14,7 +14,7 @@ import ( func TestAmbiguousCharacters(t *testing.T) { for locale, ambiguous := range AmbiguousCharacters { assert.Equal(t, locale, ambiguous.Locale) - assert.Equal(t, len(ambiguous.Confusable), len(ambiguous.With)) + assert.Len(t, ambiguous.With, len(ambiguous.Confusable)) assert.True(t, sort.SliceIsSorted(ambiguous.Confusable, func(i, j int) bool { return ambiguous.Confusable[i] < ambiguous.Confusable[j] })) diff --git a/modules/charset/charset_test.go b/modules/charset/charset_test.go index 19b1303365..1fb362654d 100644 --- a/modules/charset/charset_test.go +++ b/modules/charset/charset_test.go @@ -252,7 +252,7 @@ func TestToUTF8WithFallbackReader(t *testing.T) { input += "// Выключаем" rd := ToUTF8WithFallbackReader(bytes.NewReader([]byte(input)), ConvertOpts{}) r, _ := io.ReadAll(rd) - assert.EqualValuesf(t, input, string(r), "testing string len=%d", testLen) + assert.Equalf(t, input, string(r), "testing string len=%d", testLen) } truncatedOneByteExtension := failFastBytes diff --git a/modules/csv/csv_test.go b/modules/csv/csv_test.go index 25945113a7..be9fc5f823 100644 --- a/modules/csv/csv_test.go +++ b/modules/csv/csv_test.go @@ -99,10 +99,10 @@ j, ,\x20 for n, c := range cases { rd, err := CreateReaderAndDetermineDelimiter(nil, strings.NewReader(decodeSlashes(t, c.csv))) assert.NoError(t, err, "case %d: should not throw error: %v\n", n, err) - assert.EqualValues(t, c.expectedDelimiter, rd.Comma, "case %d: delimiter should be '%c', got '%c'", n, c.expectedDelimiter, rd.Comma) + assert.Equal(t, c.expectedDelimiter, rd.Comma, "case %d: delimiter should be '%c', got '%c'", n, c.expectedDelimiter, rd.Comma) rows, err := rd.ReadAll() assert.NoError(t, err, "case %d: should not throw error: %v\n", n, err) - assert.EqualValues(t, c.expectedRows, rows, "case %d: rows should be equal", n) + assert.Equal(t, c.expectedRows, rows, "case %d: rows should be equal", n) } } @@ -231,7 +231,7 @@ John Doe john@doe.com This,note,had,a,lot,of,commas,to,test,delimiters`, for n, c := range cases { delimiter := determineDelimiter(markup.NewRenderContext(t.Context()).WithRelativePath(c.filename), []byte(decodeSlashes(t, c.csv))) - assert.EqualValues(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter) + assert.Equal(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter) } } @@ -296,7 +296,7 @@ abc | |123 for n, c := range cases { modifiedText := removeQuotedString(decodeSlashes(t, c.text)) - assert.EqualValues(t, c.expectedText, modifiedText, "case %d: modified text should be equal", n) + assert.Equal(t, c.expectedText, modifiedText, "case %d: modified text should be equal", n) } } @@ -451,7 +451,7 @@ jkl`, for n, c := range cases { delimiter := guessDelimiter([]byte(decodeSlashes(t, c.csv))) - assert.EqualValues(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter) + assert.Equal(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter) } } @@ -543,7 +543,7 @@ a|"he said, ""here I am"""`, for n, c := range cases { delimiter := guessFromBeforeAfterQuotes([]byte(decodeSlashes(t, c.csv))) - assert.EqualValues(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter) + assert.Equal(t, c.expectedDelimiter, delimiter, "case %d: delimiter should be equal, expected '%c' got '%c'", n, c.expectedDelimiter, delimiter) } } @@ -579,7 +579,7 @@ func TestFormatError(t *testing.T) { assert.Error(t, err, "case %d: expected an error to be returned", n) } else { assert.NoError(t, err, "case %d: no error was expected, got error: %v", n, err) - assert.EqualValues(t, c.expectedMessage, message, "case %d: messages should be equal, expected '%s' got '%s'", n, c.expectedMessage, message) + assert.Equal(t, c.expectedMessage, message, "case %d: messages should be equal, expected '%s' got '%s'", n, c.expectedMessage, message) } } } diff --git a/modules/dump/dumper_test.go b/modules/dump/dumper_test.go index 2db3a598a4..8f06c1851d 100644 --- a/modules/dump/dumper_test.go +++ b/modules/dump/dumper_test.go @@ -103,11 +103,11 @@ func TestDumper(t *testing.T) { d.GlobalExcludeAbsPath(filepath.Join(tmpDir, "include/exclude1")) err := d.AddRecursiveExclude("include", filepath.Join(tmpDir, "include"), []string{filepath.Join(tmpDir, "include/exclude2")}) assert.NoError(t, err) - assert.EqualValues(t, sortStrings([]string{"include/a", "include/sub", "include/sub/b"}), sortStrings(tw.added)) + assert.Equal(t, sortStrings([]string{"include/a", "include/sub", "include/sub/b"}), sortStrings(tw.added)) tw = &testWriter{} d = &Dumper{Writer: tw} err = d.AddRecursiveExclude("include", filepath.Join(tmpDir, "include"), nil) assert.NoError(t, err) - assert.EqualValues(t, sortStrings([]string{"include/exclude2", "include/exclude2/a-2", "include/a", "include/sub", "include/sub/b", "include/exclude1", "include/exclude1/a-1"}), sortStrings(tw.added)) + assert.Equal(t, sortStrings([]string{"include/exclude2", "include/exclude2/a-2", "include/a", "include/sub", "include/sub/b", "include/exclude1", "include/exclude1/a-1"}), sortStrings(tw.added)) } diff --git a/modules/fileicon/material.go b/modules/fileicon/material.go index aa31cd8d7c..557f7ca9e4 100644 --- a/modules/fileicon/material.go +++ b/modules/fileicon/material.go @@ -13,7 +13,6 @@ import ( "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/options" - "code.gitea.io/gitea/modules/reqctx" "code.gitea.io/gitea/modules/svg" ) @@ -62,13 +61,7 @@ func (m *MaterialIconProvider) loadData() { log.Debug("Loaded material icon rules and SVG images") } -func (m *MaterialIconProvider) renderFileIconSVG(ctx reqctx.RequestContext, name, svg, extraClass string) template.HTML { - data := ctx.GetData() - renderedSVGs, _ := data["_RenderedSVGs"].(map[string]bool) - if renderedSVGs == nil { - renderedSVGs = make(map[string]bool) - data["_RenderedSVGs"] = renderedSVGs - } +func (m *MaterialIconProvider) renderFileIconSVG(p *RenderedIconPool, name, svg, extraClass string) template.HTML { // This part is a bit hacky, but it works really well. It should be safe to do so because all SVG icons are generated by us. // Will try to refactor this in the future. if !strings.HasPrefix(svg, "') - if renderedSVGs[svgID] && posOuterBefore != -1 { - return template.HTML(``) + if p.IconSVGs[svgID] == "" { + p.IconSVGs[svgID] = template.HTML(``) } -func (m *MaterialIconProvider) FileIcon(ctx reqctx.RequestContext, entry *git.TreeEntry) template.HTML { +func (m *MaterialIconProvider) FileIcon(p *RenderedIconPool, entry *git.TreeEntry) template.HTML { if m.rules == nil { return BasicThemeIcon(entry) } @@ -99,12 +89,9 @@ func (m *MaterialIconProvider) FileIcon(ctx reqctx.RequestContext, entry *git.Tr } name := m.findIconNameByGit(entry) - if name == "folder" { - // the material icon pack's "folder" icon doesn't look good, so use our built-in one - // keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work - return svg.RenderHTML("material-folder-generic", 16, "octicon-file-directory-fill") - } - if iconSVG, ok := m.svgs[name]; ok && iconSVG != "" { + // the material icon pack's "folder" icon doesn't look good, so use our built-in one + // keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work + if iconSVG, ok := m.svgs[name]; ok && name != "folder" && iconSVG != "" { // keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work extraClass := "octicon-file" switch { @@ -113,9 +100,10 @@ func (m *MaterialIconProvider) FileIcon(ctx reqctx.RequestContext, entry *git.Tr case entry.IsSubModule(): extraClass = "octicon-file-submodule" } - return m.renderFileIconSVG(ctx, name, iconSVG, extraClass) + return m.renderFileIconSVG(p, name, iconSVG, extraClass) } - return svg.RenderHTML("octicon-file") + // TODO: use an interface or wrapper for git.Entry to make the code testable. + return BasicThemeIcon(entry) } func (m *MaterialIconProvider) findIconNameWithLangID(s string) string { diff --git a/modules/fileicon/render.go b/modules/fileicon/render.go new file mode 100644 index 0000000000..1d014693fd --- /dev/null +++ b/modules/fileicon/render.go @@ -0,0 +1,52 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package fileicon + +import ( + "html/template" + "strings" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" +) + +type RenderedIconPool struct { + IconSVGs map[string]template.HTML +} + +func NewRenderedIconPool() *RenderedIconPool { + return &RenderedIconPool{ + IconSVGs: make(map[string]template.HTML), + } +} + +func (p *RenderedIconPool) RenderToHTML() template.HTML { + if len(p.IconSVGs) == 0 { + return "" + } + sb := &strings.Builder{} + sb.WriteString(``) + for _, icon := range p.IconSVGs { + sb.WriteString(string(icon)) + } + sb.WriteString(``) + return template.HTML(sb.String()) +} + +// TODO: use an interface or struct to replace "*git.TreeEntry", to decouple the fileicon module from git module + +func RenderEntryIcon(renderedIconPool *RenderedIconPool, entry *git.TreeEntry) template.HTML { + if setting.UI.FileIconTheme == "material" { + return DefaultMaterialIconProvider().FileIcon(renderedIconPool, entry) + } + return BasicThemeIcon(entry) +} + +func RenderEntryIconOpen(renderedIconPool *RenderedIconPool, entry *git.TreeEntry) template.HTML { + // TODO: add "open icon" support + if setting.UI.FileIconTheme == "material" { + return DefaultMaterialIconProvider().FileIcon(renderedIconPool, entry) + } + return BasicThemeIcon(entry) +} diff --git a/modules/git/attribute.go b/modules/git/attribute.go deleted file mode 100644 index 4dfa510369..0000000000 --- a/modules/git/attribute.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2024 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package git - -import ( - "code.gitea.io/gitea/modules/optional" -) - -const ( - AttributeLinguistVendored = "linguist-vendored" - AttributeLinguistGenerated = "linguist-generated" - AttributeLinguistDocumentation = "linguist-documentation" - AttributeLinguistDetectable = "linguist-detectable" - AttributeLinguistLanguage = "linguist-language" - AttributeGitlabLanguage = "gitlab-language" -) - -// true if "set"/"true", false if "unset"/"false", none otherwise -func AttributeToBool(attr map[string]string, name string) optional.Option[bool] { - switch attr[name] { - case "set", "true": - return optional.Some(true) - case "unset", "false": - return optional.Some(false) - } - return optional.None[bool]() -} - -func AttributeToString(attr map[string]string, name string) optional.Option[string] { - if value, has := attr[name]; has && value != "unspecified" { - return optional.Some(value) - } - return optional.None[string]() -} diff --git a/modules/git/attribute/attribute.go b/modules/git/attribute/attribute.go new file mode 100644 index 0000000000..adf323ef41 --- /dev/null +++ b/modules/git/attribute/attribute.go @@ -0,0 +1,114 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package attribute + +import ( + "strings" + + "code.gitea.io/gitea/modules/optional" +) + +type Attribute string + +const ( + LinguistVendored = "linguist-vendored" + LinguistGenerated = "linguist-generated" + LinguistDocumentation = "linguist-documentation" + LinguistDetectable = "linguist-detectable" + LinguistLanguage = "linguist-language" + GitlabLanguage = "gitlab-language" + Lockable = "lockable" + Filter = "filter" +) + +var LinguistAttributes = []string{ + LinguistVendored, + LinguistGenerated, + LinguistDocumentation, + LinguistDetectable, + LinguistLanguage, + GitlabLanguage, +} + +func (a Attribute) IsUnspecified() bool { + return a == "" || a == "unspecified" +} + +func (a Attribute) ToString() optional.Option[string] { + if !a.IsUnspecified() { + return optional.Some(string(a)) + } + return optional.None[string]() +} + +// ToBool converts the attribute value to optional boolean: true if "set"/"true", false if "unset"/"false", none otherwise +func (a Attribute) ToBool() optional.Option[bool] { + switch a { + case "set", "true": + return optional.Some(true) + case "unset", "false": + return optional.Some(false) + } + return optional.None[bool]() +} + +type Attributes struct { + m map[string]Attribute +} + +func NewAttributes() *Attributes { + return &Attributes{m: make(map[string]Attribute)} +} + +func (attrs *Attributes) Get(name string) Attribute { + if value, has := attrs.m[name]; has { + return value + } + return "" +} + +func (attrs *Attributes) GetVendored() optional.Option[bool] { + return attrs.Get(LinguistVendored).ToBool() +} + +func (attrs *Attributes) GetGenerated() optional.Option[bool] { + return attrs.Get(LinguistGenerated).ToBool() +} + +func (attrs *Attributes) GetDocumentation() optional.Option[bool] { + return attrs.Get(LinguistDocumentation).ToBool() +} + +func (attrs *Attributes) GetDetectable() optional.Option[bool] { + return attrs.Get(LinguistDetectable).ToBool() +} + +func (attrs *Attributes) GetLinguistLanguage() optional.Option[string] { + return attrs.Get(LinguistLanguage).ToString() +} + +func (attrs *Attributes) GetGitlabLanguage() optional.Option[string] { + attrStr := attrs.Get(GitlabLanguage).ToString() + if attrStr.Has() { + raw := attrStr.Value() + // gitlab-language may have additional parameters after the language + // ignore them and just use the main language + // https://docs.gitlab.com/ee/user/project/highlighting.html#override-syntax-highlighting-for-a-file-type + if idx := strings.IndexByte(raw, '?'); idx >= 0 { + return optional.Some(raw[:idx]) + } + } + return attrStr +} + +func (attrs *Attributes) GetLanguage() optional.Option[string] { + // prefer linguist-language over gitlab-language + // if linguist-language is not set, use gitlab-language + // if both are not set, return none + language := attrs.GetLinguistLanguage() + if language.Value() == "" { + language = attrs.GetGitlabLanguage() + } + return language +} diff --git a/modules/git/attribute/attribute_test.go b/modules/git/attribute/attribute_test.go new file mode 100644 index 0000000000..dadb5582a3 --- /dev/null +++ b/modules/git/attribute/attribute_test.go @@ -0,0 +1,37 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package attribute + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_Attribute(t *testing.T) { + assert.Empty(t, Attribute("").ToString().Value()) + assert.Empty(t, Attribute("unspecified").ToString().Value()) + assert.Equal(t, "python", Attribute("python").ToString().Value()) + assert.Equal(t, "Java", Attribute("Java").ToString().Value()) + + attributes := Attributes{ + m: map[string]Attribute{ + LinguistGenerated: "true", + LinguistDocumentation: "false", + LinguistDetectable: "set", + LinguistLanguage: "Python", + GitlabLanguage: "Java", + "filter": "unspecified", + "test": "", + }, + } + + assert.Empty(t, attributes.Get("test").ToString().Value()) + assert.Empty(t, attributes.Get("filter").ToString().Value()) + assert.Equal(t, "Python", attributes.Get(LinguistLanguage).ToString().Value()) + assert.Equal(t, "Java", attributes.Get(GitlabLanguage).ToString().Value()) + assert.True(t, attributes.Get(LinguistGenerated).ToBool().Value()) + assert.False(t, attributes.Get(LinguistDocumentation).ToBool().Value()) + assert.True(t, attributes.Get(LinguistDetectable).ToBool().Value()) +} diff --git a/modules/git/attribute/batch.go b/modules/git/attribute/batch.go new file mode 100644 index 0000000000..4e31fda575 --- /dev/null +++ b/modules/git/attribute/batch.go @@ -0,0 +1,216 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package attribute + +import ( + "bytes" + "context" + "fmt" + "os" + "path/filepath" + "time" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" +) + +// BatchChecker provides a reader for check-attribute content that can be long running +type BatchChecker struct { + attributesNum int + repo *git.Repository + stdinWriter *os.File + stdOut *nulSeparatedAttributeWriter + ctx context.Context + cancel context.CancelFunc + cmd *git.Command +} + +// NewBatchChecker creates a check attribute reader for the current repository and provided commit ID +// If treeish is empty, then it will use current working directory, otherwise it will use the provided treeish on the bare repo +func NewBatchChecker(repo *git.Repository, treeish string, attributes []string) (checker *BatchChecker, returnedErr error) { + ctx, cancel := context.WithCancel(repo.Ctx) + defer func() { + if returnedErr != nil { + cancel() + } + }() + + cmd, envs, cleanup, err := checkAttrCommand(repo, treeish, nil, attributes) + if err != nil { + return nil, err + } + defer func() { + if returnedErr != nil { + cleanup() + } + }() + + cmd.AddArguments("--stdin") + + checker = &BatchChecker{ + attributesNum: len(attributes), + repo: repo, + ctx: ctx, + cmd: cmd, + cancel: func() { + cancel() + cleanup() + }, + } + + stdinReader, stdinWriter, err := os.Pipe() + if err != nil { + return nil, err + } + checker.stdinWriter = stdinWriter + + lw := new(nulSeparatedAttributeWriter) + lw.attributes = make(chan attributeTriple, len(attributes)) + lw.closed = make(chan struct{}) + checker.stdOut = lw + + go func() { + defer func() { + _ = stdinReader.Close() + _ = lw.Close() + }() + stdErr := new(bytes.Buffer) + err := cmd.Run(ctx, &git.RunOpts{ + Env: envs, + Dir: repo.Path, + Stdin: stdinReader, + Stdout: lw, + Stderr: stdErr, + }) + + if err != nil && !git.IsErrCanceledOrKilled(err) { + log.Error("Attribute checker for commit %s exits with error: %v", treeish, err) + } + checker.cancel() + }() + + return checker, nil +} + +// CheckPath check attr for given path +func (c *BatchChecker) CheckPath(path string) (rs *Attributes, err error) { + defer func() { + if err != nil && err != c.ctx.Err() { + log.Error("Unexpected error when checking path %s in %s, error: %v", path, filepath.Base(c.repo.Path), err) + } + }() + + select { + case <-c.ctx.Done(): + return nil, c.ctx.Err() + default: + } + + if _, err = c.stdinWriter.Write([]byte(path + "\x00")); err != nil { + defer c.Close() + return nil, err + } + + reportTimeout := func() error { + stdOutClosed := false + select { + case <-c.stdOut.closed: + stdOutClosed = true + default: + } + debugMsg := fmt.Sprintf("check path %q in repo %q", path, filepath.Base(c.repo.Path)) + debugMsg += fmt.Sprintf(", stdOut: tmp=%q, pos=%d, closed=%v", string(c.stdOut.tmp), c.stdOut.pos, stdOutClosed) + if c.cmd != nil { + debugMsg += fmt.Sprintf(", process state: %q", c.cmd.ProcessState()) + } + _ = c.Close() + return fmt.Errorf("CheckPath timeout: %s", debugMsg) + } + + rs = NewAttributes() + for i := 0; i < c.attributesNum; i++ { + select { + case <-time.After(5 * time.Second): + // there is no "hang" problem now. This code is just used to catch other potential problems. + return nil, reportTimeout() + case attr, ok := <-c.stdOut.ReadAttribute(): + if !ok { + return nil, c.ctx.Err() + } + rs.m[attr.Attribute] = Attribute(attr.Value) + case <-c.ctx.Done(): + return nil, c.ctx.Err() + } + } + return rs, nil +} + +func (c *BatchChecker) Close() error { + c.cancel() + err := c.stdinWriter.Close() + return err +} + +type attributeTriple struct { + Filename string + Attribute string + Value string +} + +type nulSeparatedAttributeWriter struct { + tmp []byte + attributes chan attributeTriple + closed chan struct{} + working attributeTriple + pos int +} + +func (wr *nulSeparatedAttributeWriter) Write(p []byte) (n int, err error) { + l, read := len(p), 0 + + nulIdx := bytes.IndexByte(p, '\x00') + for nulIdx >= 0 { + wr.tmp = append(wr.tmp, p[:nulIdx]...) + switch wr.pos { + case 0: + wr.working = attributeTriple{ + Filename: string(wr.tmp), + } + case 1: + wr.working.Attribute = string(wr.tmp) + case 2: + wr.working.Value = string(wr.tmp) + } + wr.tmp = wr.tmp[:0] + wr.pos++ + if wr.pos > 2 { + wr.attributes <- wr.working + wr.pos = 0 + } + read += nulIdx + 1 + if l > read { + p = p[nulIdx+1:] + nulIdx = bytes.IndexByte(p, '\x00') + } else { + return l, nil + } + } + wr.tmp = append(wr.tmp, p...) + return l, nil +} + +func (wr *nulSeparatedAttributeWriter) ReadAttribute() <-chan attributeTriple { + return wr.attributes +} + +func (wr *nulSeparatedAttributeWriter) Close() error { + select { + case <-wr.closed: + return nil + default: + } + close(wr.attributes) + close(wr.closed) + return nil +} diff --git a/modules/git/attribute/batch_test.go b/modules/git/attribute/batch_test.go new file mode 100644 index 0000000000..30a3d805fe --- /dev/null +++ b/modules/git/attribute/batch_test.go @@ -0,0 +1,172 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package attribute + +import ( + "path/filepath" + "testing" + "time" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) { + wr := &nulSeparatedAttributeWriter{ + attributes: make(chan attributeTriple, 5), + } + + testStr := ".gitignore\"\n\x00linguist-vendored\x00unspecified\x00" + + n, err := wr.Write([]byte(testStr)) + + assert.Len(t, testStr, n) + assert.NoError(t, err) + select { + case attr := <-wr.ReadAttribute(): + assert.Equal(t, ".gitignore\"\n", attr.Filename) + assert.Equal(t, LinguistVendored, attr.Attribute) + assert.Equal(t, "unspecified", attr.Value) + case <-time.After(100 * time.Millisecond): + assert.FailNow(t, "took too long to read an attribute from the list") + } + // Write a second attribute again + n, err = wr.Write([]byte(testStr)) + + assert.Len(t, testStr, n) + assert.NoError(t, err) + + select { + case attr := <-wr.ReadAttribute(): + assert.Equal(t, ".gitignore\"\n", attr.Filename) + assert.Equal(t, LinguistVendored, attr.Attribute) + assert.Equal(t, "unspecified", attr.Value) + case <-time.After(100 * time.Millisecond): + assert.FailNow(t, "took too long to read an attribute from the list") + } + + // Write a partial attribute + _, err = wr.Write([]byte("incomplete-file")) + assert.NoError(t, err) + _, err = wr.Write([]byte("name\x00")) + assert.NoError(t, err) + + select { + case <-wr.ReadAttribute(): + assert.FailNow(t, "There should not be an attribute ready to read") + case <-time.After(100 * time.Millisecond): + } + _, err = wr.Write([]byte("attribute\x00")) + assert.NoError(t, err) + select { + case <-wr.ReadAttribute(): + assert.FailNow(t, "There should not be an attribute ready to read") + case <-time.After(100 * time.Millisecond): + } + + _, err = wr.Write([]byte("value\x00")) + assert.NoError(t, err) + + attr := <-wr.ReadAttribute() + assert.Equal(t, "incomplete-filename", attr.Filename) + assert.Equal(t, "attribute", attr.Attribute) + assert.Equal(t, "value", attr.Value) + + _, err = wr.Write([]byte("shouldbe.vendor\x00linguist-vendored\x00set\x00shouldbe.vendor\x00linguist-generated\x00unspecified\x00shouldbe.vendor\x00linguist-language\x00unspecified\x00")) + assert.NoError(t, err) + attr = <-wr.ReadAttribute() + assert.NoError(t, err) + assert.Equal(t, attributeTriple{ + Filename: "shouldbe.vendor", + Attribute: LinguistVendored, + Value: "set", + }, attr) + attr = <-wr.ReadAttribute() + assert.NoError(t, err) + assert.Equal(t, attributeTriple{ + Filename: "shouldbe.vendor", + Attribute: LinguistGenerated, + Value: "unspecified", + }, attr) + attr = <-wr.ReadAttribute() + assert.NoError(t, err) + assert.Equal(t, attributeTriple{ + Filename: "shouldbe.vendor", + Attribute: LinguistLanguage, + Value: "unspecified", + }, attr) +} + +func expectedAttrs() *Attributes { + return &Attributes{ + m: map[string]Attribute{ + LinguistGenerated: "unspecified", + LinguistDetectable: "unspecified", + LinguistDocumentation: "unspecified", + LinguistVendored: "unspecified", + LinguistLanguage: "Python", + GitlabLanguage: "unspecified", + }, + } +} + +func Test_BatchChecker(t *testing.T) { + setting.AppDataPath = t.TempDir() + repoPath := "../tests/repos/language_stats_repo" + gitRepo, err := git.OpenRepository(t.Context(), repoPath) + require.NoError(t, err) + defer gitRepo.Close() + + commitID := "8fee858da5796dfb37704761701bb8e800ad9ef3" + + t.Run("Create index file to run git check-attr", func(t *testing.T) { + defer test.MockVariableValue(&git.DefaultFeatures().SupportCheckAttrOnBare, false)() + checker, err := NewBatchChecker(gitRepo, commitID, LinguistAttributes) + assert.NoError(t, err) + defer checker.Close() + attributes, err := checker.CheckPath("i-am-a-python.p") + assert.NoError(t, err) + assert.Equal(t, expectedAttrs(), attributes) + }) + + // run git check-attr on work tree + t.Run("Run git check-attr on git work tree", func(t *testing.T) { + dir := filepath.Join(t.TempDir(), "test-repo") + err := git.Clone(t.Context(), repoPath, dir, git.CloneRepoOptions{ + Shared: true, + Branch: "master", + }) + assert.NoError(t, err) + + tempRepo, err := git.OpenRepository(t.Context(), dir) + assert.NoError(t, err) + defer tempRepo.Close() + + checker, err := NewBatchChecker(tempRepo, "", LinguistAttributes) + assert.NoError(t, err) + defer checker.Close() + attributes, err := checker.CheckPath("i-am-a-python.p") + assert.NoError(t, err) + assert.Equal(t, expectedAttrs(), attributes) + }) + + if !git.DefaultFeatures().SupportCheckAttrOnBare { + t.Skip("git version 2.40 is required to support run check-attr on bare repo") + return + } + + t.Run("Run git check-attr in bare repository", func(t *testing.T) { + checker, err := NewBatchChecker(gitRepo, commitID, LinguistAttributes) + assert.NoError(t, err) + defer checker.Close() + + attributes, err := checker.CheckPath("i-am-a-python.p") + assert.NoError(t, err) + assert.Equal(t, expectedAttrs(), attributes) + }) +} diff --git a/modules/git/attribute/checker.go b/modules/git/attribute/checker.go new file mode 100644 index 0000000000..c17006a154 --- /dev/null +++ b/modules/git/attribute/checker.go @@ -0,0 +1,96 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package attribute + +import ( + "bytes" + "context" + "errors" + "fmt" + "os" + + "code.gitea.io/gitea/modules/git" +) + +func checkAttrCommand(gitRepo *git.Repository, treeish string, filenames, attributes []string) (*git.Command, []string, func(), error) { + cancel := func() {} + envs := []string{"GIT_FLUSH=1"} + cmd := git.NewCommand("check-attr", "-z") + if len(attributes) == 0 { + cmd.AddArguments("--all") + } + + // there is treeish, read from bare repo or temp index created by "read-tree" + if treeish != "" { + if git.DefaultFeatures().SupportCheckAttrOnBare { + cmd.AddArguments("--source") + cmd.AddDynamicArguments(treeish) + } else { + indexFilename, worktree, deleteTemporaryFile, err := gitRepo.ReadTreeToTemporaryIndex(treeish) + if err != nil { + return nil, nil, nil, err + } + + cmd.AddArguments("--cached") + envs = append(envs, + "GIT_INDEX_FILE="+indexFilename, + "GIT_WORK_TREE="+worktree, + ) + cancel = deleteTemporaryFile + } + } // else: no treeish, assume it is a not a bare repo, read from working directory + + cmd.AddDynamicArguments(attributes...) + if len(filenames) > 0 { + cmd.AddDashesAndList(filenames...) + } + return cmd, envs, cancel, nil +} + +type CheckAttributeOpts struct { + Filenames []string + Attributes []string +} + +// CheckAttributes return the attributes of the given filenames and attributes in the given treeish. +// If treeish is empty, then it will use current working directory, otherwise it will use the provided treeish on the bare repo +func CheckAttributes(ctx context.Context, gitRepo *git.Repository, treeish string, opts CheckAttributeOpts) (map[string]*Attributes, error) { + cmd, envs, cancel, err := checkAttrCommand(gitRepo, treeish, opts.Filenames, opts.Attributes) + if err != nil { + return nil, err + } + defer cancel() + + stdOut := new(bytes.Buffer) + stdErr := new(bytes.Buffer) + + if err := cmd.Run(ctx, &git.RunOpts{ + Env: append(os.Environ(), envs...), + Dir: gitRepo.Path, + Stdout: stdOut, + Stderr: stdErr, + }); err != nil { + return nil, fmt.Errorf("failed to run check-attr: %w\n%s\n%s", err, stdOut.String(), stdErr.String()) + } + + fields := bytes.Split(stdOut.Bytes(), []byte{'\000'}) + if len(fields)%3 != 1 { + return nil, errors.New("wrong number of fields in return from check-attr") + } + + attributesMap := make(map[string]*Attributes) + for i := 0; i < (len(fields) / 3); i++ { + filename := string(fields[3*i]) + attribute := string(fields[3*i+1]) + info := string(fields[3*i+2]) + attribute2info, ok := attributesMap[filename] + if !ok { + attribute2info = NewAttributes() + attributesMap[filename] = attribute2info + } + attribute2info.m[attribute] = Attribute(info) + } + + return attributesMap, nil +} diff --git a/modules/git/attribute/checker_test.go b/modules/git/attribute/checker_test.go new file mode 100644 index 0000000000..97db43460b --- /dev/null +++ b/modules/git/attribute/checker_test.go @@ -0,0 +1,74 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package attribute + +import ( + "path/filepath" + "testing" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_Checker(t *testing.T) { + setting.AppDataPath = t.TempDir() + repoPath := "../tests/repos/language_stats_repo" + gitRepo, err := git.OpenRepository(t.Context(), repoPath) + require.NoError(t, err) + defer gitRepo.Close() + + commitID := "8fee858da5796dfb37704761701bb8e800ad9ef3" + + t.Run("Create index file to run git check-attr", func(t *testing.T) { + defer test.MockVariableValue(&git.DefaultFeatures().SupportCheckAttrOnBare, false)() + attrs, err := CheckAttributes(t.Context(), gitRepo, commitID, CheckAttributeOpts{ + Filenames: []string{"i-am-a-python.p"}, + Attributes: LinguistAttributes, + }) + assert.NoError(t, err) + assert.Len(t, attrs, 1) + assert.Equal(t, expectedAttrs(), attrs["i-am-a-python.p"]) + }) + + // run git check-attr on work tree + t.Run("Run git check-attr on git work tree", func(t *testing.T) { + dir := filepath.Join(t.TempDir(), "test-repo") + err := git.Clone(t.Context(), repoPath, dir, git.CloneRepoOptions{ + Shared: true, + Branch: "master", + }) + assert.NoError(t, err) + + tempRepo, err := git.OpenRepository(t.Context(), dir) + assert.NoError(t, err) + defer tempRepo.Close() + + attrs, err := CheckAttributes(t.Context(), tempRepo, "", CheckAttributeOpts{ + Filenames: []string{"i-am-a-python.p"}, + Attributes: LinguistAttributes, + }) + assert.NoError(t, err) + assert.Len(t, attrs, 1) + assert.Equal(t, expectedAttrs(), attrs["i-am-a-python.p"]) + }) + + if !git.DefaultFeatures().SupportCheckAttrOnBare { + t.Skip("git version 2.40 is required to support run check-attr on bare repo") + return + } + + t.Run("Run git check-attr in bare repository", func(t *testing.T) { + attrs, err := CheckAttributes(t.Context(), gitRepo, commitID, CheckAttributeOpts{ + Filenames: []string{"i-am-a-python.p"}, + Attributes: LinguistAttributes, + }) + assert.NoError(t, err) + assert.Len(t, attrs, 1) + assert.Equal(t, expectedAttrs(), attrs["i-am-a-python.p"]) + }) +} diff --git a/modules/git/attribute/main_test.go b/modules/git/attribute/main_test.go new file mode 100644 index 0000000000..df8241bfb0 --- /dev/null +++ b/modules/git/attribute/main_test.go @@ -0,0 +1,41 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package attribute + +import ( + "context" + "fmt" + "os" + "testing" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" +) + +func testRun(m *testing.M) error { + gitHomePath, err := os.MkdirTemp(os.TempDir(), "git-home") + if err != nil { + return fmt.Errorf("unable to create temp dir: %w", err) + } + defer util.RemoveAll(gitHomePath) + setting.Git.HomePath = gitHomePath + + if err = git.InitFull(context.Background()); err != nil { + return fmt.Errorf("failed to call Init: %w", err) + } + + exitCode := m.Run() + if exitCode != 0 { + return fmt.Errorf("run test failed, ExitCode=%d", exitCode) + } + return nil +} + +func TestMain(m *testing.M) { + if err := testRun(m); err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Test failed: %v", err) + os.Exit(1) + } +} diff --git a/modules/git/blame.go b/modules/git/blame.go index d1d732c716..6eb583a6b9 100644 --- a/modules/git/blame.go +++ b/modules/git/blame.go @@ -11,7 +11,7 @@ import ( "os" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/setting" ) // BlamePart represents block of blame - continuous lines with one sha @@ -29,12 +29,13 @@ type BlameReader struct { bufferedReader *bufio.Reader done chan error lastSha *string - ignoreRevsFile *string + ignoreRevsFile string objectFormat ObjectFormat + cleanupFuncs []func() } func (r *BlameReader) UsesIgnoreRevs() bool { - return r.ignoreRevsFile != nil + return r.ignoreRevsFile != "" } // NextPart returns next part of blame (sequential code lines with the same commit) @@ -122,36 +123,37 @@ func (r *BlameReader) Close() error { r.bufferedReader = nil _ = r.reader.Close() _ = r.output.Close() - if r.ignoreRevsFile != nil { - _ = util.Remove(*r.ignoreRevsFile) + for _, cleanup := range r.cleanupFuncs { + if cleanup != nil { + cleanup() + } } return err } // CreateBlameReader creates reader for given repository, commit and file func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) { - var ignoreRevsFile *string - if DefaultFeatures().CheckVersionAtLeast("2.23") && !bypassBlameIgnore { - ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit) - } - - cmd := NewCommandNoGlobals("blame", "--porcelain") - if ignoreRevsFile != nil { - // Possible improvement: use --ignore-revs-file /dev/stdin on unix - // There is no equivalent on Windows. May be implemented if Gitea uses an external git backend. - cmd.AddOptionValues("--ignore-revs-file", *ignoreRevsFile) - } - cmd.AddDynamicArguments(commit.ID.String()).AddDashesAndList(file) reader, stdout, err := os.Pipe() if err != nil { - if ignoreRevsFile != nil { - _ = util.Remove(*ignoreRevsFile) - } return nil, err } - done := make(chan error, 1) + cmd := NewCommandNoGlobals("blame", "--porcelain") + var ignoreRevsFileName string + var ignoreRevsFileCleanup func() // TODO: maybe it should check the returned err in a defer func to make sure the cleanup could always be executed correctly + if DefaultFeatures().CheckVersionAtLeast("2.23") && !bypassBlameIgnore { + ignoreRevsFileName, ignoreRevsFileCleanup = tryCreateBlameIgnoreRevsFile(commit) + if ignoreRevsFileName != "" { + // Possible improvement: use --ignore-revs-file /dev/stdin on unix + // There is no equivalent on Windows. May be implemented if Gitea uses an external git backend. + cmd.AddOptionValues("--ignore-revs-file", ignoreRevsFileName) + } + } + + cmd.AddDynamicArguments(commit.ID.String()).AddDashesAndList(file) + + done := make(chan error, 1) go func() { stderr := bytes.Buffer{} // TODO: it doesn't work for directories (the directories shouldn't be "blamed"), and the "err" should be returned by "Read" but not by "Close" @@ -169,40 +171,44 @@ func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath }() bufferedReader := bufio.NewReader(reader) - return &BlameReader{ output: stdout, reader: reader, bufferedReader: bufferedReader, done: done, - ignoreRevsFile: ignoreRevsFile, + ignoreRevsFile: ignoreRevsFileName, objectFormat: objectFormat, + cleanupFuncs: []func(){ignoreRevsFileCleanup}, }, nil } -func tryCreateBlameIgnoreRevsFile(commit *Commit) *string { +func tryCreateBlameIgnoreRevsFile(commit *Commit) (string, func()) { entry, err := commit.GetTreeEntryByPath(".git-blame-ignore-revs") if err != nil { - return nil + log.Error("Unable to get .git-blame-ignore-revs file: GetTreeEntryByPath: %v", err) + return "", nil } r, err := entry.Blob().DataAsync() if err != nil { - return nil + log.Error("Unable to get .git-blame-ignore-revs file data: DataAsync: %v", err) + return "", nil } defer r.Close() - f, err := os.CreateTemp("", "gitea_git-blame-ignore-revs") + f, cleanup, err := setting.AppDataTempDir("git-repo-content").CreateTempFileRandom("git-blame-ignore-revs") if err != nil { - return nil + log.Error("Unable to get .git-blame-ignore-revs file data: CreateTempFileRandom: %v", err) + return "", nil } - + filename := f.Name() _, err = io.Copy(f, r) _ = f.Close() if err != nil { - _ = util.Remove(f.Name()) - return nil + cleanup() + log.Error("Unable to get .git-blame-ignore-revs file data: Copy: %v", err) + return "", nil } - return util.ToPointer(f.Name()) + return filename, cleanup } diff --git a/modules/git/blame_sha256_test.go b/modules/git/blame_sha256_test.go index 99c23429e2..c0a97bed3b 100644 --- a/modules/git/blame_sha256_test.go +++ b/modules/git/blame_sha256_test.go @@ -7,10 +7,13 @@ import ( "context" "testing" + "code.gitea.io/gitea/modules/setting" + "github.com/stretchr/testify/assert" ) func TestReadingBlameOutputSha256(t *testing.T) { + setting.AppDataPath = t.TempDir() ctx, cancel := context.WithCancel(t.Context()) defer cancel() diff --git a/modules/git/blame_test.go b/modules/git/blame_test.go index 36b5fb9349..809d6fbcf7 100644 --- a/modules/git/blame_test.go +++ b/modules/git/blame_test.go @@ -7,10 +7,13 @@ import ( "context" "testing" + "code.gitea.io/gitea/modules/setting" + "github.com/stretchr/testify/assert" ) func TestReadingBlameOutput(t *testing.T) { + setting.AppDataPath = t.TempDir() ctx, cancel := context.WithCancel(t.Context()) defer cancel() diff --git a/modules/git/command.go b/modules/git/command.go index f449f1ff0e..eaaa4969d0 100644 --- a/modules/git/command.go +++ b/modules/git/command.go @@ -80,6 +80,13 @@ func (c *Command) LogString() string { return strings.Join(a, " ") } +func (c *Command) ProcessState() string { + if c.cmd == nil { + return "" + } + return c.cmd.ProcessState.String() +} + // NewCommand creates and returns a new Git Command based on given command and arguments. // Each argument should be safe to be trusted. User-provided arguments should be passed to AddDynamicArguments instead. func NewCommand(args ...internal.CmdArg) *Command { diff --git a/modules/git/command_test.go b/modules/git/command_test.go index 005a760ed1..eb112707e7 100644 --- a/modules/git/command_test.go +++ b/modules/git/command_test.go @@ -54,8 +54,8 @@ func TestGitArgument(t *testing.T) { func TestCommandString(t *testing.T) { cmd := NewCommandNoGlobals("a", "-m msg", "it's a test", `say "hello"`) - assert.EqualValues(t, cmd.prog+` a "-m msg" "it's a test" "say \"hello\""`, cmd.LogString()) + assert.Equal(t, cmd.prog+` a "-m msg" "it's a test" "say \"hello\""`, cmd.LogString()) cmd = NewCommandNoGlobals("url: https://a:b@c/", "/root/dir-a/dir-b") - assert.EqualValues(t, cmd.prog+` "url: https://sanitized-credential@c/" .../dir-a/dir-b`, cmd.LogString()) + assert.Equal(t, cmd.prog+` "url: https://sanitized-credential@c/" .../dir-a/dir-b`, cmd.LogString()) } diff --git a/modules/git/commit_sha256_test.go b/modules/git/commit_sha256_test.go index f6ca83c9ed..64a0f53908 100644 --- a/modules/git/commit_sha256_test.go +++ b/modules/git/commit_sha256_test.go @@ -97,7 +97,7 @@ signed commit` assert.NoError(t, err) require.NotNil(t, commitFromReader) assert.EqualValues(t, sha, commitFromReader.ID) - assert.EqualValues(t, `-----BEGIN PGP SIGNATURE----- + assert.Equal(t, `-----BEGIN PGP SIGNATURE----- iQIrBAABCgAtFiEES+fB08xlgTrzSdQvhkUIsBsmec8FAmU/wKoPHGFtYWplckBz dXNlLmRlAAoJEIZFCLAbJnnP4s4PQIJATa++WPzR6/H4etT7bsOGoMyguEJYyWOd @@ -114,19 +114,19 @@ HKRr3NlRM/DygzTyj0gN74uoa0goCIbyAQhiT42nm0cuhM7uN/W0ayrlZjGF1cbR =xybZ -----END PGP SIGNATURE----- `, commitFromReader.Signature.Signature) - assert.EqualValues(t, `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e + assert.Equal(t, `tree e7f9e96dd79c09b078cac8b303a7d3b9d65ff9b734e86060a4d20409fd379f9e parent 26e9ccc29fad747e9c5d9f4c9ddeb7eff61cc45ef6a8dc258cbeb181afc055e8 author Adam Majer 1698676906 +0100 committer Adam Majer 1698676906 +0100 signed commit`, commitFromReader.Signature.Payload) - assert.EqualValues(t, "Adam Majer ", commitFromReader.Author.String()) + assert.Equal(t, "Adam Majer ", commitFromReader.Author.String()) commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n")) assert.NoError(t, err) commitFromReader.CommitMessage += "\n\n" commitFromReader.Signature.Payload += "\n\n" - assert.EqualValues(t, commitFromReader, commitFromReader2) + assert.Equal(t, commitFromReader, commitFromReader2) } func TestHasPreviousCommitSha256(t *testing.T) { diff --git a/modules/git/commit_test.go b/modules/git/commit_test.go index 5319e09bb7..f43e0081fd 100644 --- a/modules/git/commit_test.go +++ b/modules/git/commit_test.go @@ -93,7 +93,7 @@ empty commit` assert.NoError(t, err) require.NotNil(t, commitFromReader) assert.EqualValues(t, sha, commitFromReader.ID) - assert.EqualValues(t, `-----BEGIN PGP SIGNATURE----- + assert.Equal(t, `-----BEGIN PGP SIGNATURE----- iQIzBAABCAAdFiEEWPb2jX6FS2mqyJRQLmK0HJOGlEMFAl00zmEACgkQLmK0HJOG lEMDFBAAhQKKqLD1VICygJMEB8t1gBmNLgvziOLfpX4KPWdPtBk3v/QJ7OrfMrVK @@ -110,19 +110,19 @@ mfeFhT57UbE4qukTDIQ0Y0WM40UYRTakRaDY7ubhXgLgx09Cnp9XTVMsHgT6j9/i =FRsO -----END PGP SIGNATURE----- `, commitFromReader.Signature.Signature) - assert.EqualValues(t, `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930 + assert.Equal(t, `tree f1a6cb52b2d16773290cefe49ad0684b50a4f930 parent 37991dec2c8e592043f47155ce4808d4580f9123 author silverwind 1563741793 +0200 committer silverwind 1563741793 +0200 empty commit`, commitFromReader.Signature.Payload) - assert.EqualValues(t, "silverwind ", commitFromReader.Author.String()) + assert.Equal(t, "silverwind ", commitFromReader.Author.String()) commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n")) assert.NoError(t, err) commitFromReader.CommitMessage += "\n\n" commitFromReader.Signature.Payload += "\n\n" - assert.EqualValues(t, commitFromReader, commitFromReader2) + assert.Equal(t, commitFromReader, commitFromReader2) } func TestCommitWithEncodingFromReader(t *testing.T) { @@ -159,7 +159,7 @@ ISO-8859-1` assert.NoError(t, err) require.NotNil(t, commitFromReader) assert.EqualValues(t, sha, commitFromReader.ID) - assert.EqualValues(t, `-----BEGIN PGP SIGNATURE----- + assert.Equal(t, `-----BEGIN PGP SIGNATURE----- iQGzBAABCgAdFiEE9HRrbqvYxPT8PXbefPSEkrowAa8FAmYGg7IACgkQfPSEkrow Aa9olwv+P0HhtCM6CRvlUmPaqswRsDPNR4i66xyXGiSxdI9V5oJL7HLiQIM7KrFR @@ -174,20 +174,20 @@ jw4YcO5u =r3UU -----END PGP SIGNATURE----- `, commitFromReader.Signature.Signature) - assert.EqualValues(t, `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5 + assert.Equal(t, `tree ca3fad42080dd1a6d291b75acdfc46e5b9b307e5 parent 47b24e7ab977ed31c5a39989d570847d6d0052af author KN4CK3R 1711702962 +0100 committer KN4CK3R 1711702962 +0100 encoding ISO-8859-1 ISO-8859-1`, commitFromReader.Signature.Payload) - assert.EqualValues(t, "KN4CK3R ", commitFromReader.Author.String()) + assert.Equal(t, "KN4CK3R ", commitFromReader.Author.String()) commitFromReader2, err := CommitFromReader(gitRepo, sha, strings.NewReader(commitString+"\n\n")) assert.NoError(t, err) commitFromReader.CommitMessage += "\n\n" commitFromReader.Signature.Payload += "\n\n" - assert.EqualValues(t, commitFromReader, commitFromReader2) + assert.Equal(t, commitFromReader, commitFromReader2) } func TestHasPreviousCommit(t *testing.T) { @@ -351,10 +351,10 @@ func Test_GetCommitBranchStart(t *testing.T) { defer repo.Close() commit, err := repo.GetBranchCommit("branch1") assert.NoError(t, err) - assert.EqualValues(t, "2839944139e0de9737a044f78b0e4b40d989a9e3", commit.ID.String()) + assert.Equal(t, "2839944139e0de9737a044f78b0e4b40d989a9e3", commit.ID.String()) startCommitID, err := repo.GetCommitBranchStart(os.Environ(), "branch1", commit.ID.String()) assert.NoError(t, err) assert.NotEmpty(t, startCommitID) - assert.EqualValues(t, "95bb4d39648ee7e325106df01a621c530863a653", startCommitID) + assert.Equal(t, "95bb4d39648ee7e325106df01a621c530863a653", startCommitID) } diff --git a/modules/git/diff_test.go b/modules/git/diff_test.go index 0f865c52a8..9a09347b30 100644 --- a/modules/git/diff_test.go +++ b/modules/git/diff_test.go @@ -177,8 +177,8 @@ func ExampleCutDiffAroundLine() { func TestParseDiffHunkString(t *testing.T) { leftLine, leftHunk, rightLine, rightHunk := ParseDiffHunkString("@@ -19,3 +19,5 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER") - assert.EqualValues(t, 19, leftLine) - assert.EqualValues(t, 3, leftHunk) - assert.EqualValues(t, 19, rightLine) - assert.EqualValues(t, 5, rightHunk) + assert.Equal(t, 19, leftLine) + assert.Equal(t, 3, leftHunk) + assert.Equal(t, 19, rightLine) + assert.Equal(t, 5, rightHunk) } diff --git a/modules/git/error.go b/modules/git/error.go index 10fb37be07..6c86d1b04d 100644 --- a/modules/git/error.go +++ b/modules/git/error.go @@ -32,19 +32,19 @@ func (err ErrNotExist) Unwrap() error { return util.ErrNotExist } -// ErrBadLink entry.FollowLink error -type ErrBadLink struct { +// ErrSymlinkUnresolved entry.FollowLink error +type ErrSymlinkUnresolved struct { Name string Message string } -func (err ErrBadLink) Error() string { +func (err ErrSymlinkUnresolved) Error() string { return fmt.Sprintf("%s: %s", err.Name, err.Message) } -// IsErrBadLink if some error is ErrBadLink -func IsErrBadLink(err error) bool { - _, ok := err.(ErrBadLink) +// IsErrSymlinkUnresolved if some error is ErrSymlinkUnresolved +func IsErrSymlinkUnresolved(err error) bool { + _, ok := err.(ErrSymlinkUnresolved) return ok } diff --git a/modules/git/git.go b/modules/git/git.go index 2b593910a2..a2ffd6d289 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -30,6 +30,7 @@ type Features struct { SupportProcReceive bool // >= 2.29 SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’ SupportedObjectFormats []ObjectFormat // sha1, sha256 + SupportCheckAttrOnBare bool // >= 2.40 } var ( @@ -77,6 +78,7 @@ func loadGitVersionFeatures() (*Features, error) { if features.SupportHashSha256 { features.SupportedObjectFormats = append(features.SupportedObjectFormats, Sha256ObjectFormat) } + features.SupportCheckAttrOnBare = features.CheckVersionAtLeast("2.40") return features, nil } diff --git a/modules/git/git_test.go b/modules/git/git_test.go index 5472842b76..58ba01cabc 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -10,18 +10,19 @@ import ( "testing" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/tempdir" "github.com/hashicorp/go-version" "github.com/stretchr/testify/assert" ) func testRun(m *testing.M) error { - gitHomePath, err := os.MkdirTemp(os.TempDir(), "git-home") + gitHomePath, cleanup, err := tempdir.OsTempDir("gitea-test").MkdirTempRandom("git-home") if err != nil { return fmt.Errorf("unable to create temp dir: %w", err) } - defer util.RemoveAll(gitHomePath) + defer cleanup() + setting.Git.HomePath = gitHomePath if err = InitFull(context.Background()); err != nil { diff --git a/modules/git/grep.go b/modules/git/grep.go index 44ec6ca2be..66711650c9 100644 --- a/modules/git/grep.go +++ b/modules/git/grep.go @@ -61,14 +61,15 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO */ var results []*GrepResult cmd := NewCommand("grep", "--null", "--break", "--heading", "--line-number", "--full-name") - cmd.AddOptionValues("--context", fmt.Sprint(opts.ContextLineNumber)) - if opts.GrepMode == GrepModeExact { + cmd.AddOptionValues("--context", strconv.Itoa(opts.ContextLineNumber)) + switch opts.GrepMode { + case GrepModeExact: cmd.AddArguments("--fixed-strings") cmd.AddOptionValues("-e", strings.TrimLeft(search, "-")) - } else if opts.GrepMode == GrepModeRegexp { + case GrepModeRegexp: cmd.AddArguments("--perl-regexp") cmd.AddOptionValues("-e", strings.TrimLeft(search, "-")) - } else /* words */ { + default: /* words */ words := strings.Fields(search) cmd.AddArguments("--fixed-strings", "--ignore-case") for i, word := range words { diff --git a/modules/git/repo_language_stats.go b/modules/git/languagestats/language_stats.go similarity index 59% rename from modules/git/repo_language_stats.go rename to modules/git/languagestats/language_stats.go index 8551ea9d24..a71284c3e4 100644 --- a/modules/git/repo_language_stats.go +++ b/modules/git/languagestats/language_stats.go @@ -1,13 +1,15 @@ // Copyright 2020 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package git +package languagestats import ( + "context" "strings" "unicode" - "code.gitea.io/gitea/modules/optional" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/attribute" ) const ( @@ -49,19 +51,15 @@ func mergeLanguageStats(stats map[string]int64) map[string]int64 { return res } -func TryReadLanguageAttribute(attrs map[string]string) optional.Option[string] { - language := AttributeToString(attrs, AttributeLinguistLanguage) - if language.Value() == "" { - language = AttributeToString(attrs, AttributeGitlabLanguage) - if language.Has() { - raw := language.Value() - // gitlab-language may have additional parameters after the language - // ignore them and just use the main language - // https://docs.gitlab.com/ee/user/project/highlighting.html#override-syntax-highlighting-for-a-file-type - if idx := strings.IndexByte(raw, '?'); idx >= 0 { - language = optional.Some(raw[:idx]) - } - } +// GetFileLanguage tries to get the (linguist) language of the file content +func GetFileLanguage(ctx context.Context, gitRepo *git.Repository, treeish, treePath string) (string, error) { + attributesMap, err := attribute.CheckAttributes(ctx, gitRepo, treeish, attribute.CheckAttributeOpts{ + Attributes: []string{attribute.LinguistLanguage, attribute.GitlabLanguage}, + Filenames: []string{treePath}, + }) + if err != nil { + return "", err } - return language + + return attributesMap[treePath].GetLanguage().Value(), nil } diff --git a/modules/git/repo_language_stats_gogit.go b/modules/git/languagestats/language_stats_gogit.go similarity index 73% rename from modules/git/repo_language_stats_gogit.go rename to modules/git/languagestats/language_stats_gogit.go index a34c03c781..418c05b157 100644 --- a/modules/git/repo_language_stats_gogit.go +++ b/modules/git/languagestats/language_stats_gogit.go @@ -3,13 +3,15 @@ //go:build gogit -package git +package languagestats import ( "bytes" "io" "code.gitea.io/gitea/modules/analyze" + git_module "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/attribute" "code.gitea.io/gitea/modules/optional" "github.com/go-enry/go-enry/v2" @@ -19,7 +21,7 @@ import ( ) // GetLanguageStats calculates language stats for git repository at specified commit -func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, error) { +func GetLanguageStats(repo *git_module.Repository, commitID string) (map[string]int64, error) { r, err := git.PlainOpen(repo.Path) if err != nil { return nil, err @@ -40,8 +42,11 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err return nil, err } - checker, deferable := repo.CheckAttributeReader(commitID) - defer deferable() + checker, err := attribute.NewBatchChecker(repo, commitID, attribute.LinguistAttributes) + if err != nil { + return nil, err + } + defer checker.Close() // sizes contains the current calculated size of all files by language sizes := make(map[string]int64) @@ -62,43 +67,41 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err isDocumentation := optional.None[bool]() isDetectable := optional.None[bool]() - if checker != nil { - attrs, err := checker.CheckPath(f.Name) - if err == nil { - isVendored = AttributeToBool(attrs, AttributeLinguistVendored) - if isVendored.ValueOrDefault(false) { - return nil + attrs, err := checker.CheckPath(f.Name) + if err == nil { + isVendored = attrs.GetVendored() + if isVendored.ValueOrDefault(false) { + return nil + } + + isGenerated = attrs.GetGenerated() + if isGenerated.ValueOrDefault(false) { + return nil + } + + isDocumentation = attrs.GetDocumentation() + if isDocumentation.ValueOrDefault(false) { + return nil + } + + isDetectable = attrs.GetDetectable() + if !isDetectable.ValueOrDefault(true) { + return nil + } + + hasLanguage := attrs.GetLanguage() + if hasLanguage.Value() != "" { + language := hasLanguage.Value() + + // group languages, such as Pug -> HTML; SCSS -> CSS + group := enry.GetLanguageGroup(language) + if len(group) != 0 { + language = group } - isGenerated = AttributeToBool(attrs, AttributeLinguistGenerated) - if isGenerated.ValueOrDefault(false) { - return nil - } - - isDocumentation = AttributeToBool(attrs, AttributeLinguistDocumentation) - if isDocumentation.ValueOrDefault(false) { - return nil - } - - isDetectable = AttributeToBool(attrs, AttributeLinguistDetectable) - if !isDetectable.ValueOrDefault(true) { - return nil - } - - hasLanguage := TryReadLanguageAttribute(attrs) - if hasLanguage.Value() != "" { - language := hasLanguage.Value() - - // group languages, such as Pug -> HTML; SCSS -> CSS - group := enry.GetLanguageGroup(language) - if len(group) != 0 { - language = group - } - - // this language will always be added to the size - sizes[language] += f.Size - return nil - } + // this language will always be added to the size + sizes[language] += f.Size + return nil } } diff --git a/modules/git/repo_language_stats_nogogit.go b/modules/git/languagestats/language_stats_nogogit.go similarity index 73% rename from modules/git/repo_language_stats_nogogit.go rename to modules/git/languagestats/language_stats_nogogit.go index de7707bd6c..34797263a6 100644 --- a/modules/git/repo_language_stats_nogogit.go +++ b/modules/git/languagestats/language_stats_nogogit.go @@ -3,13 +3,15 @@ //go:build !gogit -package git +package languagestats import ( "bytes" "io" "code.gitea.io/gitea/modules/analyze" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/attribute" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" @@ -17,7 +19,7 @@ import ( ) // GetLanguageStats calculates language stats for git repository at specified commit -func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, error) { +func GetLanguageStats(repo *git.Repository, commitID string) (map[string]int64, error) { // We will feed the commit IDs in order into cat-file --batch, followed by blobs as necessary. // so let's create a batch stdin and stdout batchStdinWriter, batchReader, cancel, err := repo.CatFileBatch(repo.Ctx) @@ -34,19 +36,19 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err if err := writeID(commitID); err != nil { return nil, err } - shaBytes, typ, size, err := ReadBatchLine(batchReader) + shaBytes, typ, size, err := git.ReadBatchLine(batchReader) if typ != "commit" { log.Debug("Unable to get commit for: %s. Err: %v", commitID, err) - return nil, ErrNotExist{commitID, ""} + return nil, git.ErrNotExist{ID: commitID} } - sha, err := NewIDFromString(string(shaBytes)) + sha, err := git.NewIDFromString(string(shaBytes)) if err != nil { log.Debug("Unable to get commit for: %s. Err: %v", commitID, err) - return nil, ErrNotExist{commitID, ""} + return nil, git.ErrNotExist{ID: commitID} } - commit, err := CommitFromReader(repo, sha, io.LimitReader(batchReader, size)) + commit, err := git.CommitFromReader(repo, sha, io.LimitReader(batchReader, size)) if err != nil { log.Debug("Unable to get commit for: %s. Err: %v", commitID, err) return nil, err @@ -62,8 +64,11 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err return nil, err } - checker, deferable := repo.CheckAttributeReader(commitID) - defer deferable() + checker, err := attribute.NewBatchChecker(repo, commitID, attribute.LinguistAttributes) + if err != nil { + return nil, err + } + defer checker.Close() contentBuf := bytes.Buffer{} var content []byte @@ -96,43 +101,36 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err isDocumentation := optional.None[bool]() isDetectable := optional.None[bool]() - if checker != nil { - attrs, err := checker.CheckPath(f.Name()) - if err == nil { - isVendored = AttributeToBool(attrs, AttributeLinguistVendored) - if isVendored.ValueOrDefault(false) { - continue + attrs, err := checker.CheckPath(f.Name()) + if err == nil { + if isVendored = attrs.GetVendored(); isVendored.ValueOrDefault(false) { + continue + } + + if isGenerated = attrs.GetGenerated(); isGenerated.ValueOrDefault(false) { + continue + } + + if isDocumentation = attrs.GetDocumentation(); isDocumentation.ValueOrDefault(false) { + continue + } + + if isDetectable = attrs.GetDetectable(); !isDetectable.ValueOrDefault(true) { + continue + } + + if hasLanguage := attrs.GetLanguage(); hasLanguage.Value() != "" { + language := hasLanguage.Value() + + // group languages, such as Pug -> HTML; SCSS -> CSS + group := enry.GetLanguageGroup(language) + if len(group) != 0 { + language = group } - isGenerated = AttributeToBool(attrs, AttributeLinguistGenerated) - if isGenerated.ValueOrDefault(false) { - continue - } - - isDocumentation = AttributeToBool(attrs, AttributeLinguistDocumentation) - if isDocumentation.ValueOrDefault(false) { - continue - } - - isDetectable = AttributeToBool(attrs, AttributeLinguistDetectable) - if !isDetectable.ValueOrDefault(true) { - continue - } - - hasLanguage := TryReadLanguageAttribute(attrs) - if hasLanguage.Value() != "" { - language := hasLanguage.Value() - - // group languages, such as Pug -> HTML; SCSS -> CSS - group := enry.GetLanguageGroup(language) - if len(group) != 0 { - language = group - } - - // this language will always be added to the size - sizes[language] += f.Size() - continue - } + // this language will always be added to the size + sizes[language] += f.Size() + continue } } @@ -149,7 +147,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err if err := writeID(f.ID.String()); err != nil { return nil, err } - _, _, size, err := ReadBatchLine(batchReader) + _, _, size, err := git.ReadBatchLine(batchReader) if err != nil { log.Debug("Error reading blob: %s Err: %v", f.ID.String(), err) return nil, err @@ -167,7 +165,7 @@ func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, err return nil, err } content = contentBuf.Bytes() - if err := DiscardFull(batchReader, discard); err != nil { + if err := git.DiscardFull(batchReader, discard); err != nil { return nil, err } } diff --git a/modules/git/repo_language_stats_test.go b/modules/git/languagestats/language_stats_test.go similarity index 60% rename from modules/git/repo_language_stats_test.go rename to modules/git/languagestats/language_stats_test.go index 1ee5f4c3af..b908ae6413 100644 --- a/modules/git/repo_language_stats_test.go +++ b/modules/git/languagestats/language_stats_test.go @@ -3,34 +3,36 @@ //go:build !gogit -package git +package languagestats import ( - "path/filepath" "testing" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestRepository_GetLanguageStats(t *testing.T) { - repoPath := filepath.Join(testReposDir, "language_stats_repo") - gitRepo, err := openRepositoryWithDefaultContext(repoPath) + setting.AppDataPath = t.TempDir() + repoPath := "../tests/repos/language_stats_repo" + gitRepo, err := git.OpenRepository(t.Context(), repoPath) require.NoError(t, err) - defer gitRepo.Close() - stats, err := gitRepo.GetLanguageStats("8fee858da5796dfb37704761701bb8e800ad9ef3") + stats, err := GetLanguageStats(gitRepo, "8fee858da5796dfb37704761701bb8e800ad9ef3") require.NoError(t, err) - assert.EqualValues(t, map[string]int64{ + assert.Equal(t, map[string]int64{ "Python": 134, "Java": 112, }, stats) } func TestMergeLanguageStats(t *testing.T) { - assert.EqualValues(t, map[string]int64{ + assert.Equal(t, map[string]int64{ "PHP": 1, "python": 10, "JAVA": 700, diff --git a/modules/git/languagestats/main_test.go b/modules/git/languagestats/main_test.go new file mode 100644 index 0000000000..707d268c81 --- /dev/null +++ b/modules/git/languagestats/main_test.go @@ -0,0 +1,41 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package languagestats + +import ( + "context" + "fmt" + "os" + "testing" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" +) + +func testRun(m *testing.M) error { + gitHomePath, err := os.MkdirTemp(os.TempDir(), "git-home") + if err != nil { + return fmt.Errorf("unable to create temp dir: %w", err) + } + defer util.RemoveAll(gitHomePath) + setting.Git.HomePath = gitHomePath + + if err = git.InitFull(context.Background()); err != nil { + return fmt.Errorf("failed to call Init: %w", err) + } + + exitCode := m.Run() + if exitCode != 0 { + return fmt.Errorf("run test failed, ExitCode=%d", exitCode) + } + return nil +} + +func TestMain(m *testing.M) { + if err := testRun(m); err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Test failed: %v", err) + os.Exit(1) + } +} diff --git a/modules/git/log_name_status.go b/modules/git/log_name_status.go index 0e9e22f1dc..3ee462f68e 100644 --- a/modules/git/log_name_status.go +++ b/modules/git/log_name_status.go @@ -118,11 +118,12 @@ func (g *LogNameStatusRepoParser) Next(treepath string, paths2ids map[string]int g.buffull = false g.next, err = g.rd.ReadSlice('\x00') if err != nil { - if err == bufio.ErrBufferFull { + switch err { + case bufio.ErrBufferFull: g.buffull = true - } else if err == io.EOF { + case io.EOF: return nil, nil - } else { + default: return nil, err } } @@ -132,11 +133,12 @@ func (g *LogNameStatusRepoParser) Next(treepath string, paths2ids map[string]int if bytes.Equal(g.next, []byte("commit\000")) { g.next, err = g.rd.ReadSlice('\x00') if err != nil { - if err == bufio.ErrBufferFull { + switch err { + case bufio.ErrBufferFull: g.buffull = true - } else if err == io.EOF { + case io.EOF: return nil, nil - } else { + default: return nil, err } } @@ -214,11 +216,12 @@ diffloop: } g.next, err = g.rd.ReadSlice('\x00') if err != nil { - if err == bufio.ErrBufferFull { + switch err { + case bufio.ErrBufferFull: g.buffull = true - } else if err == io.EOF { + case io.EOF: return &ret, nil - } else { + default: return nil, err } } diff --git a/modules/git/object_id.go b/modules/git/object_id.go index 82d30184df..25dfef3ec5 100644 --- a/modules/git/object_id.go +++ b/modules/git/object_id.go @@ -99,5 +99,5 @@ type ErrInvalidSHA struct { } func (err ErrInvalidSHA) Error() string { - return fmt.Sprintf("invalid sha: %s", err.SHA) + return "invalid sha: " + err.SHA } diff --git a/modules/git/parse_nogogit_test.go b/modules/git/parse_nogogit_test.go index a4436ce499..6594c84269 100644 --- a/modules/git/parse_nogogit_test.go +++ b/modules/git/parse_nogogit_test.go @@ -58,7 +58,7 @@ func TestParseTreeEntriesLong(t *testing.T) { assert.NoError(t, err) assert.Len(t, entries, len(testCase.Expected)) for i, entry := range entries { - assert.EqualValues(t, testCase.Expected[i], entry) + assert.Equal(t, testCase.Expected[i], entry) } } } @@ -91,7 +91,7 @@ func TestParseTreeEntriesShort(t *testing.T) { assert.NoError(t, err) assert.Len(t, entries, len(testCase.Expected)) for i, entry := range entries { - assert.EqualValues(t, testCase.Expected[i], entry) + assert.Equal(t, testCase.Expected[i], entry) } } } diff --git a/modules/git/repo.go b/modules/git/repo.go index 6459adf851..45937a8d5f 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -18,6 +18,7 @@ import ( "time" "code.gitea.io/gitea/modules/proxy" + "code.gitea.io/gitea/modules/setting" ) // GPGSettings represents the default GPG settings for this repository @@ -266,11 +267,11 @@ func GetDivergingCommits(ctx context.Context, repoPath, baseBranch, targetBranch // CreateBundle create bundle content to the target path func (repo *Repository) CreateBundle(ctx context.Context, commit string, out io.Writer) error { - tmp, err := os.MkdirTemp(os.TempDir(), "gitea-bundle") + tmp, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("gitea-bundle") if err != nil { return err } - defer os.RemoveAll(tmp) + defer cleanup() env := append(os.Environ(), "GIT_OBJECT_DIRECTORY="+filepath.Join(repo.Path, "objects")) _, _, err = NewCommand("init", "--bare").RunStdString(ctx, &RunOpts{Dir: tmp, Env: env}) diff --git a/modules/git/repo_attribute.go b/modules/git/repo_attribute.go deleted file mode 100644 index 8a5e5fa983..0000000000 --- a/modules/git/repo_attribute.go +++ /dev/null @@ -1,340 +0,0 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package git - -import ( - "bytes" - "context" - "fmt" - "io" - "os" - "path/filepath" - "time" - - "code.gitea.io/gitea/modules/log" -) - -// CheckAttributeOpts represents the possible options to CheckAttribute -type CheckAttributeOpts struct { - CachedOnly bool - AllAttributes bool - Attributes []string - Filenames []string - IndexFile string - WorkTree string -} - -// CheckAttribute return the Blame object of file -func (repo *Repository) CheckAttribute(opts CheckAttributeOpts) (map[string]map[string]string, error) { - env := []string{} - - if len(opts.IndexFile) > 0 { - env = append(env, "GIT_INDEX_FILE="+opts.IndexFile) - } - if len(opts.WorkTree) > 0 { - env = append(env, "GIT_WORK_TREE="+opts.WorkTree) - } - - if len(env) > 0 { - env = append(os.Environ(), env...) - } - - stdOut := new(bytes.Buffer) - stdErr := new(bytes.Buffer) - - cmd := NewCommand("check-attr", "-z") - - if opts.AllAttributes { - cmd.AddArguments("-a") - } else { - for _, attribute := range opts.Attributes { - if attribute != "" { - cmd.AddDynamicArguments(attribute) - } - } - } - - if opts.CachedOnly { - cmd.AddArguments("--cached") - } - - cmd.AddDashesAndList(opts.Filenames...) - - if err := cmd.Run(repo.Ctx, &RunOpts{ - Env: env, - Dir: repo.Path, - Stdout: stdOut, - Stderr: stdErr, - }); err != nil { - return nil, fmt.Errorf("failed to run check-attr: %w\n%s\n%s", err, stdOut.String(), stdErr.String()) - } - - // FIXME: This is incorrect on versions < 1.8.5 - fields := bytes.Split(stdOut.Bytes(), []byte{'\000'}) - - if len(fields)%3 != 1 { - return nil, fmt.Errorf("wrong number of fields in return from check-attr") - } - - name2attribute2info := make(map[string]map[string]string) - - for i := 0; i < (len(fields) / 3); i++ { - filename := string(fields[3*i]) - attribute := string(fields[3*i+1]) - info := string(fields[3*i+2]) - attribute2info := name2attribute2info[filename] - if attribute2info == nil { - attribute2info = make(map[string]string) - } - attribute2info[attribute] = info - name2attribute2info[filename] = attribute2info - } - - return name2attribute2info, nil -} - -// CheckAttributeReader provides a reader for check-attribute content that can be long running -type CheckAttributeReader struct { - // params - Attributes []string - Repo *Repository - IndexFile string - WorkTree string - - stdinReader io.ReadCloser - stdinWriter *os.File - stdOut *nulSeparatedAttributeWriter - cmd *Command - env []string - ctx context.Context - cancel context.CancelFunc -} - -// Init initializes the CheckAttributeReader -func (c *CheckAttributeReader) Init(ctx context.Context) error { - if len(c.Attributes) == 0 { - lw := new(nulSeparatedAttributeWriter) - lw.attributes = make(chan attributeTriple) - lw.closed = make(chan struct{}) - - c.stdOut = lw - c.stdOut.Close() - return fmt.Errorf("no provided Attributes to check") - } - - c.ctx, c.cancel = context.WithCancel(ctx) - c.cmd = NewCommand("check-attr", "--stdin", "-z") - - if len(c.IndexFile) > 0 { - c.cmd.AddArguments("--cached") - c.env = append(c.env, "GIT_INDEX_FILE="+c.IndexFile) - } - - if len(c.WorkTree) > 0 { - c.env = append(c.env, "GIT_WORK_TREE="+c.WorkTree) - } - - c.env = append(c.env, "GIT_FLUSH=1") - - c.cmd.AddDynamicArguments(c.Attributes...) - - var err error - - c.stdinReader, c.stdinWriter, err = os.Pipe() - if err != nil { - c.cancel() - return err - } - - lw := new(nulSeparatedAttributeWriter) - lw.attributes = make(chan attributeTriple, 5) - lw.closed = make(chan struct{}) - c.stdOut = lw - return nil -} - -func (c *CheckAttributeReader) Run() error { - defer func() { - _ = c.stdinReader.Close() - _ = c.stdOut.Close() - }() - stdErr := new(bytes.Buffer) - err := c.cmd.Run(c.ctx, &RunOpts{ - Env: c.env, - Dir: c.Repo.Path, - Stdin: c.stdinReader, - Stdout: c.stdOut, - Stderr: stdErr, - }) - if err != nil && !IsErrCanceledOrKilled(err) { - return fmt.Errorf("failed to run attr-check. Error: %w\nStderr: %s", err, stdErr.String()) - } - return nil -} - -// CheckPath check attr for given path -func (c *CheckAttributeReader) CheckPath(path string) (rs map[string]string, err error) { - defer func() { - if err != nil && err != c.ctx.Err() { - log.Error("Unexpected error when checking path %s in %s, error: %v", path, filepath.Base(c.Repo.Path), err) - } - }() - - select { - case <-c.ctx.Done(): - return nil, c.ctx.Err() - default: - } - - if _, err = c.stdinWriter.Write([]byte(path + "\x00")); err != nil { - defer c.Close() - return nil, err - } - - reportTimeout := func() error { - stdOutClosed := false - select { - case <-c.stdOut.closed: - stdOutClosed = true - default: - } - debugMsg := fmt.Sprintf("check path %q in repo %q", path, filepath.Base(c.Repo.Path)) - debugMsg += fmt.Sprintf(", stdOut: tmp=%q, pos=%d, closed=%v", string(c.stdOut.tmp), c.stdOut.pos, stdOutClosed) - if c.cmd.cmd != nil { - debugMsg += fmt.Sprintf(", process state: %q", c.cmd.cmd.ProcessState.String()) - } - _ = c.Close() - return fmt.Errorf("CheckPath timeout: %s", debugMsg) - } - - rs = make(map[string]string) - for range c.Attributes { - select { - case <-time.After(5 * time.Second): - // There is a strange "hang" problem in gitdiff.GetDiff -> CheckPath - // So add a timeout here to mitigate the problem, and output more logs for debug purpose - // In real world, if CheckPath runs long than seconds, it blocks the end user's operation, - // and at the moment the CheckPath result is not so important, so we can just ignore it. - return nil, reportTimeout() - case attr, ok := <-c.stdOut.ReadAttribute(): - if !ok { - return nil, c.ctx.Err() - } - rs[attr.Attribute] = attr.Value - case <-c.ctx.Done(): - return nil, c.ctx.Err() - } - } - return rs, nil -} - -func (c *CheckAttributeReader) Close() error { - c.cancel() - err := c.stdinWriter.Close() - return err -} - -type attributeTriple struct { - Filename string - Attribute string - Value string -} - -type nulSeparatedAttributeWriter struct { - tmp []byte - attributes chan attributeTriple - closed chan struct{} - working attributeTriple - pos int -} - -func (wr *nulSeparatedAttributeWriter) Write(p []byte) (n int, err error) { - l, read := len(p), 0 - - nulIdx := bytes.IndexByte(p, '\x00') - for nulIdx >= 0 { - wr.tmp = append(wr.tmp, p[:nulIdx]...) - switch wr.pos { - case 0: - wr.working = attributeTriple{ - Filename: string(wr.tmp), - } - case 1: - wr.working.Attribute = string(wr.tmp) - case 2: - wr.working.Value = string(wr.tmp) - } - wr.tmp = wr.tmp[:0] - wr.pos++ - if wr.pos > 2 { - wr.attributes <- wr.working - wr.pos = 0 - } - read += nulIdx + 1 - if l > read { - p = p[nulIdx+1:] - nulIdx = bytes.IndexByte(p, '\x00') - } else { - return l, nil - } - } - wr.tmp = append(wr.tmp, p...) - return l, nil -} - -func (wr *nulSeparatedAttributeWriter) ReadAttribute() <-chan attributeTriple { - return wr.attributes -} - -func (wr *nulSeparatedAttributeWriter) Close() error { - select { - case <-wr.closed: - return nil - default: - } - close(wr.attributes) - close(wr.closed) - return nil -} - -// CheckAttributeReader creates a check attribute reader for the current repository and provided commit ID -func (repo *Repository) CheckAttributeReader(commitID string) (*CheckAttributeReader, context.CancelFunc) { - indexFilename, worktree, deleteTemporaryFile, err := repo.ReadTreeToTemporaryIndex(commitID) - if err != nil { - return nil, func() {} - } - - checker := &CheckAttributeReader{ - Attributes: []string{ - AttributeLinguistVendored, - AttributeLinguistGenerated, - AttributeLinguistDocumentation, - AttributeLinguistDetectable, - AttributeLinguistLanguage, - AttributeGitlabLanguage, - }, - Repo: repo, - IndexFile: indexFilename, - WorkTree: worktree, - } - ctx, cancel := context.WithCancel(repo.Ctx) - if err := checker.Init(ctx); err != nil { - log.Error("Unable to open attribute checker for commit %s, error: %v", commitID, err) - } else { - go func() { - err := checker.Run() - if err != nil && !IsErrCanceledOrKilled(err) { - log.Error("Attribute checker for commit %s exits with error: %v", commitID, err) - } - cancel() - }() - } - deferrable := func() { - _ = checker.Close() - cancel() - deleteTemporaryFile() - } - - return checker, deferrable -} diff --git a/modules/git/repo_attribute_test.go b/modules/git/repo_attribute_test.go deleted file mode 100644 index e0bde146f6..0000000000 --- a/modules/git/repo_attribute_test.go +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2021 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package git - -import ( - "context" - mathRand "math/rand/v2" - "path/filepath" - "slices" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) { - wr := &nulSeparatedAttributeWriter{ - attributes: make(chan attributeTriple, 5), - } - - testStr := ".gitignore\"\n\x00linguist-vendored\x00unspecified\x00" - - n, err := wr.Write([]byte(testStr)) - - assert.Len(t, testStr, n) - assert.NoError(t, err) - select { - case attr := <-wr.ReadAttribute(): - assert.Equal(t, ".gitignore\"\n", attr.Filename) - assert.Equal(t, AttributeLinguistVendored, attr.Attribute) - assert.Equal(t, "unspecified", attr.Value) - case <-time.After(100 * time.Millisecond): - assert.FailNow(t, "took too long to read an attribute from the list") - } - // Write a second attribute again - n, err = wr.Write([]byte(testStr)) - - assert.Len(t, testStr, n) - assert.NoError(t, err) - - select { - case attr := <-wr.ReadAttribute(): - assert.Equal(t, ".gitignore\"\n", attr.Filename) - assert.Equal(t, AttributeLinguistVendored, attr.Attribute) - assert.Equal(t, "unspecified", attr.Value) - case <-time.After(100 * time.Millisecond): - assert.FailNow(t, "took too long to read an attribute from the list") - } - - // Write a partial attribute - _, err = wr.Write([]byte("incomplete-file")) - assert.NoError(t, err) - _, err = wr.Write([]byte("name\x00")) - assert.NoError(t, err) - - select { - case <-wr.ReadAttribute(): - assert.FailNow(t, "There should not be an attribute ready to read") - case <-time.After(100 * time.Millisecond): - } - _, err = wr.Write([]byte("attribute\x00")) - assert.NoError(t, err) - select { - case <-wr.ReadAttribute(): - assert.FailNow(t, "There should not be an attribute ready to read") - case <-time.After(100 * time.Millisecond): - } - - _, err = wr.Write([]byte("value\x00")) - assert.NoError(t, err) - - attr := <-wr.ReadAttribute() - assert.Equal(t, "incomplete-filename", attr.Filename) - assert.Equal(t, "attribute", attr.Attribute) - assert.Equal(t, "value", attr.Value) - - _, err = wr.Write([]byte("shouldbe.vendor\x00linguist-vendored\x00set\x00shouldbe.vendor\x00linguist-generated\x00unspecified\x00shouldbe.vendor\x00linguist-language\x00unspecified\x00")) - assert.NoError(t, err) - attr = <-wr.ReadAttribute() - assert.NoError(t, err) - assert.EqualValues(t, attributeTriple{ - Filename: "shouldbe.vendor", - Attribute: AttributeLinguistVendored, - Value: "set", - }, attr) - attr = <-wr.ReadAttribute() - assert.NoError(t, err) - assert.EqualValues(t, attributeTriple{ - Filename: "shouldbe.vendor", - Attribute: AttributeLinguistGenerated, - Value: "unspecified", - }, attr) - attr = <-wr.ReadAttribute() - assert.NoError(t, err) - assert.EqualValues(t, attributeTriple{ - Filename: "shouldbe.vendor", - Attribute: AttributeLinguistLanguage, - Value: "unspecified", - }, attr) -} - -func TestAttributeReader(t *testing.T) { - t.Skip() // for debug purpose only, do not run in CI - - ctx := t.Context() - - timeout := 1 * time.Second - repoPath := filepath.Join(testReposDir, "language_stats_repo") - commitRef := "HEAD" - - oneRound := func(t *testing.T, roundIdx int) { - ctx, cancel := context.WithTimeout(ctx, timeout) - _ = cancel - gitRepo, err := OpenRepository(ctx, repoPath) - require.NoError(t, err) - defer gitRepo.Close() - - commit, err := gitRepo.GetCommit(commitRef) - require.NoError(t, err) - - files, err := gitRepo.LsFiles() - require.NoError(t, err) - - randomFiles := slices.Clone(files) - randomFiles = append(randomFiles, "any-file-1", "any-file-2") - - t.Logf("Round %v with %d files", roundIdx, len(randomFiles)) - - attrReader, deferrable := gitRepo.CheckAttributeReader(commit.ID.String()) - defer deferrable() - - wg := sync.WaitGroup{} - wg.Add(1) - - go func() { - for { - file := randomFiles[mathRand.IntN(len(randomFiles))] - _, err := attrReader.CheckPath(file) - if err != nil { - for i := 0; i < 10; i++ { - _, _ = attrReader.CheckPath(file) - } - break - } - } - wg.Done() - }() - wg.Wait() - } - - for i := 0; i < 100; i++ { - oneRound(t, i) - } -} diff --git a/modules/git/repo_branch.go b/modules/git/repo_branch.go index 916391f167..e7ecf53f51 100644 --- a/modules/git/repo_branch.go +++ b/modules/git/repo_branch.go @@ -7,7 +7,6 @@ package git import ( "context" "errors" - "fmt" "strings" ) @@ -25,36 +24,6 @@ func IsBranchExist(ctx context.Context, repoPath, name string) bool { return IsReferenceExist(ctx, repoPath, BranchPrefix+name) } -// Branch represents a Git branch. -type Branch struct { - Name string - Path string - - gitRepo *Repository -} - -// GetHEADBranch returns corresponding branch of HEAD. -func (repo *Repository) GetHEADBranch() (*Branch, error) { - if repo == nil { - return nil, fmt.Errorf("nil repo") - } - stdout, _, err := NewCommand("symbolic-ref", "HEAD").RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) - if err != nil { - return nil, err - } - stdout = strings.TrimSpace(stdout) - - if !strings.HasPrefix(stdout, BranchPrefix) { - return nil, fmt.Errorf("invalid HEAD branch: %v", stdout) - } - - return &Branch{ - Name: stdout[len(BranchPrefix):], - Path: stdout, - gitRepo: repo, - }, nil -} - func GetDefaultBranch(ctx context.Context, repoPath string) (string, error) { stdout, _, err := NewCommand("symbolic-ref", "HEAD").RunStdString(ctx, &RunOpts{Dir: repoPath}) if err != nil { @@ -67,37 +36,6 @@ func GetDefaultBranch(ctx context.Context, repoPath string) (string, error) { return strings.TrimPrefix(stdout, BranchPrefix), nil } -// GetBranch returns a branch by it's name -func (repo *Repository) GetBranch(branch string) (*Branch, error) { - if !repo.IsBranchExist(branch) { - return nil, ErrBranchNotExist{branch} - } - return &Branch{ - Path: repo.Path, - Name: branch, - gitRepo: repo, - }, nil -} - -// GetBranches returns a slice of *git.Branch -func (repo *Repository) GetBranches(skip, limit int) ([]*Branch, int, error) { - brs, countAll, err := repo.GetBranchNames(skip, limit) - if err != nil { - return nil, 0, err - } - - branches := make([]*Branch, len(brs)) - for i := range brs { - branches[i] = &Branch{ - Path: repo.Path, - Name: brs[i], - gitRepo: repo, - } - } - - return branches, countAll, nil -} - // DeleteBranchOptions Option(s) for delete branch type DeleteBranchOptions struct { Force bool @@ -147,11 +85,6 @@ func (repo *Repository) RemoveRemote(name string) error { return err } -// GetCommit returns the head commit of a branch -func (branch *Branch) GetCommit() (*Commit, error) { - return branch.gitRepo.GetBranchCommit(branch.Name) -} - // RenameBranch rename a branch func (repo *Repository) RenameBranch(from, to string) error { _, _, err := NewCommand("branch", "-m").AddDynamicArguments(from, to).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path}) diff --git a/modules/git/repo_branch_test.go b/modules/git/repo_branch_test.go index cda170d976..8e8ea16fcd 100644 --- a/modules/git/repo_branch_test.go +++ b/modules/git/repo_branch_test.go @@ -21,21 +21,21 @@ func TestRepository_GetBranches(t *testing.T) { assert.NoError(t, err) assert.Len(t, branches, 2) - assert.EqualValues(t, 3, countAll) + assert.Equal(t, 3, countAll) assert.ElementsMatch(t, []string{"master", "branch2"}, branches) branches, countAll, err = bareRepo1.GetBranchNames(0, 0) assert.NoError(t, err) assert.Len(t, branches, 3) - assert.EqualValues(t, 3, countAll) + assert.Equal(t, 3, countAll) assert.ElementsMatch(t, []string{"master", "branch2", "branch1"}, branches) branches, countAll, err = bareRepo1.GetBranchNames(5, 1) assert.NoError(t, err) assert.Empty(t, branches) - assert.EqualValues(t, 3, countAll) + assert.Equal(t, 3, countAll) assert.ElementsMatch(t, []string{}, branches) } @@ -71,15 +71,15 @@ func TestGetRefsBySha(t *testing.T) { // refs/pull/1/head branches, err = bareRepo5.GetRefsBySha("c83380d7056593c51a699d12b9c00627bd5743e9", PullPrefix) assert.NoError(t, err) - assert.EqualValues(t, []string{"refs/pull/1/head"}, branches) + assert.Equal(t, []string{"refs/pull/1/head"}, branches) branches, err = bareRepo5.GetRefsBySha("d8e0bbb45f200e67d9a784ce55bd90821af45ebd", BranchPrefix) assert.NoError(t, err) - assert.EqualValues(t, []string{"refs/heads/master", "refs/heads/master-clone"}, branches) + assert.Equal(t, []string{"refs/heads/master", "refs/heads/master-clone"}, branches) branches, err = bareRepo5.GetRefsBySha("58a4bcc53ac13e7ff76127e0fb518b5262bf09af", BranchPrefix) assert.NoError(t, err) - assert.EqualValues(t, []string{"refs/heads/test-patch-1"}, branches) + assert.Equal(t, []string{"refs/heads/test-patch-1"}, branches) } func BenchmarkGetRefsBySha(b *testing.B) { diff --git a/modules/git/repo_commit_nogogit.go b/modules/git/repo_commit_nogogit.go index 5aa0e9ec04..3ead3e2216 100644 --- a/modules/git/repo_commit_nogogit.go +++ b/modules/git/repo_commit_nogogit.go @@ -81,10 +81,10 @@ func (repo *Repository) getCommit(id ObjectID) (*Commit, error) { _, _ = wr.Write([]byte(id.String() + "\n")) - return repo.getCommitFromBatchReader(rd, id) + return repo.getCommitFromBatchReader(wr, rd, id) } -func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id ObjectID) (*Commit, error) { +func (repo *Repository) getCommitFromBatchReader(wr WriteCloserError, rd *bufio.Reader, id ObjectID) (*Commit, error) { _, typ, size, err := ReadBatchLine(rd) if err != nil { if errors.Is(err, io.EOF) || IsErrNotExist(err) { @@ -112,7 +112,11 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id ObjectID) return nil, err } - commit, err := tag.Commit(repo) + if _, err := wr.Write([]byte(tag.Object.String() + "\n")); err != nil { + return nil, err + } + + commit, err := repo.getCommitFromBatchReader(wr, rd, tag.Object) if err != nil { return nil, err } diff --git a/modules/git/repo_index.go b/modules/git/repo_index.go index 1c7fcc063e..443a3a20d1 100644 --- a/modules/git/repo_index.go +++ b/modules/git/repo_index.go @@ -10,8 +10,7 @@ import ( "path/filepath" "strings" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/setting" ) // ReadTreeToIndex reads a treeish to the index @@ -59,26 +58,18 @@ func (repo *Repository) ReadTreeToTemporaryIndex(treeish string) (tmpIndexFilena } }() - removeDirFn := func(dir string) func() { // it can't use the return value "tmpDir" directly because it is empty when error occurs - return func() { - if err := util.RemoveAll(dir); err != nil { - log.Error("failed to remove tmp index dir: %v", err) - } - } - } - - tmpDir, err = os.MkdirTemp("", "index") + tmpDir, cancel, err = setting.AppDataTempDir("git-repo-content").MkdirTempRandom("index") if err != nil { return "", "", nil, err } tmpIndexFilename = filepath.Join(tmpDir, ".tmp-index") - cancel = removeDirFn(tmpDir) + err = repo.ReadTreeToIndex(treeish, tmpIndexFilename) if err != nil { return "", "", cancel, err } - return tmpIndexFilename, tmpDir, cancel, err + return tmpIndexFilename, tmpDir, cancel, nil } // EmptyIndex empties the index diff --git a/modules/git/repo_ref.go b/modules/git/repo_ref.go index 739cfb972c..554f9f73e1 100644 --- a/modules/git/repo_ref.go +++ b/modules/git/repo_ref.go @@ -19,11 +19,12 @@ func (repo *Repository) GetRefs() ([]*Reference, error) { // refType should only be a literal "branch" or "tag" and nothing else func (repo *Repository) ListOccurrences(ctx context.Context, refType, commitSHA string) ([]string, error) { cmd := NewCommand() - if refType == "branch" { + switch refType { + case "branch": cmd.AddArguments("branch") - } else if refType == "tag" { + case "tag": cmd.AddArguments("tag") - } else { + default: return nil, util.NewInvalidArgumentErrorf(`can only use "branch" or "tag" for refType, but got %q`, refType) } stdout, _, err := cmd.AddArguments("--no-color", "--sort=-creatordate", "--contains").AddDynamicArguments(commitSHA).RunStdString(ctx, &RunOpts{Dir: repo.Path}) diff --git a/modules/git/repo_stats_test.go b/modules/git/repo_stats_test.go index 3d032385ee..85d8807a6e 100644 --- a/modules/git/repo_stats_test.go +++ b/modules/git/repo_stats_test.go @@ -30,7 +30,7 @@ func TestRepository_GetCodeActivityStats(t *testing.T) { assert.EqualValues(t, 10, code.Additions) assert.EqualValues(t, 1, code.Deletions) assert.Len(t, code.Authors, 3) - assert.EqualValues(t, "tris.git@shoddynet.org", code.Authors[1].Email) + assert.Equal(t, "tris.git@shoddynet.org", code.Authors[1].Email) assert.EqualValues(t, 3, code.Authors[1].Commits) assert.EqualValues(t, 5, code.Authors[0].Commits) } diff --git a/modules/git/repo_tag_nogogit.go b/modules/git/repo_tag_nogogit.go index e0a3104249..3d2b4f52bd 100644 --- a/modules/git/repo_tag_nogogit.go +++ b/modules/git/repo_tag_nogogit.go @@ -41,8 +41,11 @@ func (repo *Repository) GetTagType(id ObjectID) (string, error) { return "", err } _, typ, _, err := ReadBatchLine(rd) - if IsErrNotExist(err) { - return "", ErrNotExist{ID: id.String()} + if err != nil { + if IsErrNotExist(err) { + return "", ErrNotExist{ID: id.String()} + } + return "", err } return typ, nil } diff --git a/modules/git/repo_tag_test.go b/modules/git/repo_tag_test.go index f1f081680a..f1f5ff6664 100644 --- a/modules/git/repo_tag_test.go +++ b/modules/git/repo_tag_test.go @@ -27,12 +27,12 @@ func TestRepository_GetTags(t *testing.T) { } assert.Len(t, tags, 2) assert.Len(t, tags, total) - assert.EqualValues(t, "signed-tag", tags[0].Name) - assert.EqualValues(t, "36f97d9a96457e2bab511db30fe2db03893ebc64", tags[0].ID.String()) - assert.EqualValues(t, "tag", tags[0].Type) - assert.EqualValues(t, "test", tags[1].Name) - assert.EqualValues(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[1].ID.String()) - assert.EqualValues(t, "tag", tags[1].Type) + assert.Equal(t, "signed-tag", tags[0].Name) + assert.Equal(t, "36f97d9a96457e2bab511db30fe2db03893ebc64", tags[0].ID.String()) + assert.Equal(t, "tag", tags[0].Type) + assert.Equal(t, "test", tags[1].Name) + assert.Equal(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[1].ID.String()) + assert.Equal(t, "tag", tags[1].Type) } func TestRepository_GetTag(t *testing.T) { @@ -64,18 +64,13 @@ func TestRepository_GetTag(t *testing.T) { // and try to get the Tag for lightweight tag lTag, err := bareRepo1.GetTag(lTagName) - if err != nil { - assert.NoError(t, err) - return - } - if lTag == nil { - assert.NotNil(t, lTag) - assert.FailNow(t, "nil lTag: %s", lTagName) - } - assert.EqualValues(t, lTagName, lTag.Name) - assert.EqualValues(t, lTagCommitID, lTag.ID.String()) - assert.EqualValues(t, lTagCommitID, lTag.Object.String()) - assert.EqualValues(t, "commit", lTag.Type) + require.NoError(t, err) + require.NotNil(t, lTag, "nil lTag: %s", lTagName) + + assert.Equal(t, lTagName, lTag.Name) + assert.Equal(t, lTagCommitID, lTag.ID.String()) + assert.Equal(t, lTagCommitID, lTag.Object.String()) + assert.Equal(t, "commit", lTag.Type) // ANNOTATED TAGS aTagCommitID := "8006ff9adbf0cb94da7dad9e537e53817f9fa5c0" @@ -97,19 +92,14 @@ func TestRepository_GetTag(t *testing.T) { } aTag, err := bareRepo1.GetTag(aTagName) - if err != nil { - assert.NoError(t, err) - return - } - if aTag == nil { - assert.NotNil(t, aTag) - assert.FailNow(t, "nil aTag: %s", aTagName) - } - assert.EqualValues(t, aTagName, aTag.Name) - assert.EqualValues(t, aTagID, aTag.ID.String()) + require.NoError(t, err) + require.NotNil(t, aTag, "nil aTag: %s", aTagName) + + assert.Equal(t, aTagName, aTag.Name) + assert.Equal(t, aTagID, aTag.ID.String()) assert.NotEqual(t, aTagID, aTag.Object.String()) - assert.EqualValues(t, aTagCommitID, aTag.Object.String()) - assert.EqualValues(t, "tag", aTag.Type) + assert.Equal(t, aTagCommitID, aTag.Object.String()) + assert.Equal(t, "tag", aTag.Type) // RELEASE TAGS @@ -127,14 +117,14 @@ func TestRepository_GetTag(t *testing.T) { assert.NoError(t, err) return } - assert.EqualValues(t, rTagCommitID, rTagID) + assert.Equal(t, rTagCommitID, rTagID) oTagID, err := bareRepo1.GetTagID(lTagName) if err != nil { assert.NoError(t, err) return } - assert.EqualValues(t, lTagCommitID, oTagID) + assert.Equal(t, lTagCommitID, oTagID) } func TestRepository_GetAnnotatedTag(t *testing.T) { @@ -170,9 +160,9 @@ func TestRepository_GetAnnotatedTag(t *testing.T) { return } assert.NotNil(t, tag) - assert.EqualValues(t, aTagName, tag.Name) - assert.EqualValues(t, aTagID, tag.ID.String()) - assert.EqualValues(t, "tag", tag.Type) + assert.Equal(t, aTagName, tag.Name) + assert.Equal(t, aTagID, tag.ID.String()) + assert.Equal(t, "tag", tag.Type) // Annotated tag's Commit ID should fail tag2, err := bareRepo1.GetAnnotatedTag(aTagCommitID) diff --git a/modules/git/repo_tree_nogogit.go b/modules/git/repo_tree_nogogit.go index d74769ccb2..1954f85162 100644 --- a/modules/git/repo_tree_nogogit.go +++ b/modules/git/repo_tree_nogogit.go @@ -35,7 +35,11 @@ func (repo *Repository) getTree(id ObjectID) (*Tree, error) { if err != nil { return nil, err } - commit, err := tag.Commit(repo) + + if _, err := wr.Write([]byte(tag.Object.String() + "\n")); err != nil { + return nil, err + } + commit, err := repo.getCommitFromBatchReader(wr, rd, tag.Object) if err != nil { return nil, err } diff --git a/modules/git/signature_test.go b/modules/git/signature_test.go index 92681feea9..b9b5aff61b 100644 --- a/modules/git/signature_test.go +++ b/modules/git/signature_test.go @@ -42,6 +42,6 @@ func TestParseSignatureFromCommitLine(t *testing.T) { } for _, test := range tests { got := parseSignatureFromCommitLine(test.line) - assert.EqualValues(t, test.want, got) + assert.Equal(t, test.want, got) } } diff --git a/modules/git/submodule_test.go b/modules/git/submodule_test.go index fbf8c32e1e..7893b95e3a 100644 --- a/modules/git/submodule_test.go +++ b/modules/git/submodule_test.go @@ -19,11 +19,11 @@ func TestGetTemplateSubmoduleCommits(t *testing.T) { assert.Len(t, submodules, 2) - assert.EqualValues(t, "<°)))><", submodules[0].Path) - assert.EqualValues(t, "d2932de67963f23d43e1c7ecf20173e92ee6c43c", submodules[0].Commit) + assert.Equal(t, "<°)))><", submodules[0].Path) + assert.Equal(t, "d2932de67963f23d43e1c7ecf20173e92ee6c43c", submodules[0].Commit) - assert.EqualValues(t, "libtest", submodules[1].Path) - assert.EqualValues(t, "1234567890123456789012345678901234567890", submodules[1].Commit) + assert.Equal(t, "libtest", submodules[1].Path) + assert.Equal(t, "1234567890123456789012345678901234567890", submodules[1].Commit) } func TestAddTemplateSubmoduleIndexes(t *testing.T) { @@ -42,6 +42,6 @@ func TestAddTemplateSubmoduleIndexes(t *testing.T) { submodules, err := GetTemplateSubmoduleCommits(DefaultContext, tmpDir) require.NoError(t, err) assert.Len(t, submodules, 1) - assert.EqualValues(t, "new-dir", submodules[0].Path) - assert.EqualValues(t, "1234567890123456789012345678901234567890", submodules[0].Commit) + assert.Equal(t, "new-dir", submodules[0].Path) + assert.Equal(t, "1234567890123456789012345678901234567890", submodules[0].Commit) } diff --git a/modules/git/tag.go b/modules/git/tag.go index f7666aa89b..8bf3658d62 100644 --- a/modules/git/tag.go +++ b/modules/git/tag.go @@ -21,11 +21,6 @@ type Tag struct { Signature *CommitSignature } -// Commit return the commit of the tag reference -func (tag *Tag) Commit(gitRepo *Repository) (*Commit, error) { - return gitRepo.getCommit(tag.Object) -} - func parsePayloadSignature(data []byte, messageStart int) (payload, msg, sign string) { pos := messageStart signStart, signEnd := -1, -1 diff --git a/modules/git/tests/repos/language_stats_repo/config b/modules/git/tests/repos/language_stats_repo/config index 515f483629..a4ef456cbc 100644 --- a/modules/git/tests/repos/language_stats_repo/config +++ b/modules/git/tests/repos/language_stats_repo/config @@ -1,5 +1,5 @@ [core] repositoryformatversion = 0 filemode = true - bare = false + bare = true logallrefupdates = true diff --git a/modules/git/tests/repos/repo3_notes/config b/modules/git/tests/repos/repo3_notes/config index d545cdabdb..5ed22e23d1 100644 --- a/modules/git/tests/repos/repo3_notes/config +++ b/modules/git/tests/repos/repo3_notes/config @@ -1,7 +1,7 @@ [core] repositoryformatversion = 0 filemode = false - bare = false + bare = true logallrefupdates = true symlinks = false ignorecase = true diff --git a/modules/git/tests/repos/repo4_commitsbetween/config b/modules/git/tests/repos/repo4_commitsbetween/config index d545cdabdb..5ed22e23d1 100644 --- a/modules/git/tests/repos/repo4_commitsbetween/config +++ b/modules/git/tests/repos/repo4_commitsbetween/config @@ -1,7 +1,7 @@ [core] repositoryformatversion = 0 filemode = false - bare = false + bare = true logallrefupdates = true symlinks = false ignorecase = true diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index 9513121487..a2e1579290 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -8,6 +8,8 @@ import ( "io" "sort" "strings" + + "code.gitea.io/gitea/modules/util" ) // Type returns the type of the entry (commit, tree, blob) @@ -25,7 +27,7 @@ func (te *TreeEntry) Type() string { // FollowLink returns the entry pointed to by a symlink func (te *TreeEntry) FollowLink() (*TreeEntry, error) { if !te.IsLink() { - return nil, ErrBadLink{te.Name(), "not a symlink"} + return nil, ErrSymlinkUnresolved{te.Name(), "not a symlink"} } // read the link @@ -56,13 +58,13 @@ func (te *TreeEntry) FollowLink() (*TreeEntry, error) { } if t == nil { - return nil, ErrBadLink{te.Name(), "points outside of repo"} + return nil, ErrSymlinkUnresolved{te.Name(), "points outside of repo"} } target, err := t.GetTreeEntryByPath(lnk) if err != nil { if IsErrNotExist(err) { - return nil, ErrBadLink{te.Name(), "broken link"} + return nil, ErrSymlinkUnresolved{te.Name(), "broken link"} } return nil, err } @@ -70,33 +72,27 @@ func (te *TreeEntry) FollowLink() (*TreeEntry, error) { } // FollowLinks returns the entry ultimately pointed to by a symlink -func (te *TreeEntry) FollowLinks() (*TreeEntry, error) { +func (te *TreeEntry) FollowLinks(optLimit ...int) (*TreeEntry, error) { if !te.IsLink() { - return nil, ErrBadLink{te.Name(), "not a symlink"} + return nil, ErrSymlinkUnresolved{te.Name(), "not a symlink"} } + limit := util.OptionalArg(optLimit, 10) entry := te - for i := 0; i < 999; i++ { - if entry.IsLink() { - next, err := entry.FollowLink() - if err != nil { - return nil, err - } - if next.ID == entry.ID { - return nil, ErrBadLink{ - entry.Name(), - "recursive link", - } - } - entry = next - } else { + for i := 0; i < limit; i++ { + if !entry.IsLink() { break } + next, err := entry.FollowLink() + if err != nil { + return nil, err + } + if next.ID == entry.ID { + return nil, ErrSymlinkUnresolved{entry.Name(), "recursive link"} + } + entry = next } if entry.IsLink() { - return nil, ErrBadLink{ - te.Name(), - "too many levels of symbolic links", - } + return nil, ErrSymlinkUnresolved{te.Name(), "too many levels of symbolic links"} } return entry, nil } diff --git a/modules/git/tree_entry_mode.go b/modules/git/tree_entry_mode.go index ec4487549d..1193bec4f1 100644 --- a/modules/git/tree_entry_mode.go +++ b/modules/git/tree_entry_mode.go @@ -17,16 +17,12 @@ const ( // EntryModeNoEntry is possible if the file was added or removed in a commit. In the case of // added the base commit will not have the file in its tree so a mode of 0o000000 is used. EntryModeNoEntry EntryMode = 0o000000 - // EntryModeBlob - EntryModeBlob EntryMode = 0o100644 - // EntryModeExec - EntryModeExec EntryMode = 0o100755 - // EntryModeSymlink + + EntryModeBlob EntryMode = 0o100644 + EntryModeExec EntryMode = 0o100755 EntryModeSymlink EntryMode = 0o120000 - // EntryModeCommit - EntryModeCommit EntryMode = 0o160000 - // EntryModeTree - EntryModeTree EntryMode = 0o040000 + EntryModeCommit EntryMode = 0o160000 + EntryModeTree EntryMode = 0o040000 ) // String converts an EntryMode to a string @@ -34,12 +30,6 @@ func (e EntryMode) String() string { return strconv.FormatInt(int64(e), 8) } -// ToEntryMode converts a string to an EntryMode -func ToEntryMode(value string) EntryMode { - v, _ := strconv.ParseInt(value, 8, 32) - return EntryMode(v) -} - func ParseEntryMode(mode string) (EntryMode, error) { switch mode { case "000000": diff --git a/modules/git/tree_test.go b/modules/git/tree_test.go index 5fee64b038..61e5482538 100644 --- a/modules/git/tree_test.go +++ b/modules/git/tree_test.go @@ -33,10 +33,10 @@ func Test_GetTreePathLatestCommit(t *testing.T) { commitID, err := repo.GetBranchCommitID("master") assert.NoError(t, err) - assert.EqualValues(t, "544d8f7a3b15927cddf2299b4b562d6ebd71b6a7", commitID) + assert.Equal(t, "544d8f7a3b15927cddf2299b4b562d6ebd71b6a7", commitID) commit, err := repo.GetTreePathLatestCommit("master", "blame.txt") assert.NoError(t, err) assert.NotNil(t, commit) - assert.EqualValues(t, "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", commit.ID.String()) + assert.Equal(t, "45fb6cbc12f970b04eacd5cd4165edd11c8d7376", commit.ID.String()) } diff --git a/modules/git/url/url.go b/modules/git/url/url.go index 1c5e8377a6..aa6fa31c5e 100644 --- a/modules/git/url/url.go +++ b/modules/git/url/url.go @@ -133,12 +133,13 @@ func ParseRepositoryURL(ctx context.Context, repoURL string) (*RepositoryURL, er } } - if parsed.URL.Scheme == "http" || parsed.URL.Scheme == "https" { + switch parsed.URL.Scheme { + case "http", "https": if !httplib.IsCurrentGiteaSiteURL(ctx, repoURL) { return ret, nil } fillPathParts(strings.TrimPrefix(parsed.URL.Path, setting.AppSubURL)) - } else if parsed.URL.Scheme == "ssh" || parsed.URL.Scheme == "git+ssh" { + case "ssh", "git+ssh": domainSSH := setting.SSH.Domain domainCur := httplib.GuessCurrentHostDomain(ctx) urlDomain, _, _ := net.SplitHostPort(parsed.URL.Host) @@ -166,9 +167,10 @@ func MakeRepositoryWebLink(repoURL *RepositoryURL) string { // now, let's guess, for example: // * git@github.com:owner/submodule.git // * https://github.com/example/submodule1.git - if repoURL.GitURL.Scheme == "http" || repoURL.GitURL.Scheme == "https" { + switch repoURL.GitURL.Scheme { + case "http", "https": return strings.TrimSuffix(repoURL.GitURL.String(), ".git") - } else if repoURL.GitURL.Scheme == "ssh" || repoURL.GitURL.Scheme == "git+ssh" { + case "ssh", "git+ssh": hostname, _, _ := net.SplitHostPort(repoURL.GitURL.Host) hostname = util.IfZero(hostname, repoURL.GitURL.Host) urlPath := strings.TrimSuffix(repoURL.GitURL.Path, ".git") diff --git a/modules/git/url/url_test.go b/modules/git/url/url_test.go index 681da564f9..6655c20be3 100644 --- a/modules/git/url/url_test.go +++ b/modules/git/url/url_test.go @@ -165,8 +165,8 @@ func TestParseGitURLs(t *testing.T) { t.Run(kase.kase, func(t *testing.T) { u, err := ParseGitURL(kase.kase) assert.NoError(t, err) - assert.EqualValues(t, kase.expected.extraMark, u.extraMark) - assert.EqualValues(t, *kase.expected, *u) + assert.Equal(t, kase.expected.extraMark, u.extraMark) + assert.Equal(t, *kase.expected, *u) }) } } diff --git a/modules/gitrepo/branch.go b/modules/gitrepo/branch.go index 25ea5abfca..d7857819e4 100644 --- a/modules/gitrepo/branch.go +++ b/modules/gitrepo/branch.go @@ -11,14 +11,14 @@ import ( // GetBranchesByPath returns a branch by its path // if limit = 0 it will not limit -func GetBranchesByPath(ctx context.Context, repo Repository, skip, limit int) ([]*git.Branch, int, error) { +func GetBranchesByPath(ctx context.Context, repo Repository, skip, limit int) ([]string, int, error) { gitRepo, err := OpenRepository(ctx, repo) if err != nil { return nil, 0, err } defer gitRepo.Close() - return gitRepo.GetBranches(skip, limit) + return gitRepo.GetBranchNames(skip, limit) } func GetBranchCommitID(ctx context.Context, repo Repository, branch string) (string, error) { diff --git a/modules/globallock/redis_locker.go b/modules/globallock/redis_locker.go index 34ed9e389b..45dc769fd4 100644 --- a/modules/globallock/redis_locker.go +++ b/modules/globallock/redis_locker.go @@ -6,7 +6,6 @@ package globallock import ( "context" "errors" - "fmt" "sync" "sync/atomic" "time" @@ -78,7 +77,7 @@ func (l *redisLocker) Close() error { func (l *redisLocker) lock(ctx context.Context, key string, tries int) (ReleaseFunc, error) { if l.closed.Load() { - return func() {}, fmt.Errorf("locker is closed") + return func() {}, errors.New("locker is closed") } options := []redsync.Option{ diff --git a/modules/graceful/releasereopen/releasereopen_test.go b/modules/graceful/releasereopen/releasereopen_test.go index 0e8b48257d..46e67c2046 100644 --- a/modules/graceful/releasereopen/releasereopen_test.go +++ b/modules/graceful/releasereopen/releasereopen_test.go @@ -30,14 +30,14 @@ func TestManager(t *testing.T) { _ = m.Register(t3) assert.NoError(t, m.ReleaseReopen()) - assert.EqualValues(t, 1, t1.count) - assert.EqualValues(t, 1, t2.count) - assert.EqualValues(t, 1, t3.count) + assert.Equal(t, 1, t1.count) + assert.Equal(t, 1, t2.count) + assert.Equal(t, 1, t3.count) c2() assert.NoError(t, m.ReleaseReopen()) - assert.EqualValues(t, 2, t1.count) - assert.EqualValues(t, 1, t2.count) - assert.EqualValues(t, 2, t3.count) + assert.Equal(t, 2, t1.count) + assert.Equal(t, 1, t2.count) + assert.Equal(t, 2, t3.count) } diff --git a/modules/gtprof/trace_builtin.go b/modules/gtprof/trace_builtin.go index 41743a25e4..2590ed3a13 100644 --- a/modules/gtprof/trace_builtin.go +++ b/modules/gtprof/trace_builtin.go @@ -40,7 +40,7 @@ func (t *traceBuiltinSpan) toString(out *strings.Builder, indent int) { if t.ts.endTime.IsZero() { out.WriteString(" duration: (not ended)") } else { - out.WriteString(fmt.Sprintf(" duration=%.4fs", t.ts.endTime.Sub(t.ts.startTime).Seconds())) + fmt.Fprintf(out, " duration=%.4fs", t.ts.endTime.Sub(t.ts.startTime).Seconds()) } for _, a := range t.ts.attributes { out.WriteString(" ") diff --git a/modules/highlight/highlight_test.go b/modules/highlight/highlight_test.go index 659688bd0f..b36de98c5c 100644 --- a/modules/highlight/highlight_test.go +++ b/modules/highlight/highlight_test.go @@ -114,7 +114,7 @@ c=2 t.Run(tt.name, func(t *testing.T) { out, lexerName, err := File(tt.name, "", []byte(tt.code)) assert.NoError(t, err) - assert.EqualValues(t, tt.want, out) + assert.Equal(t, tt.want, out) assert.Equal(t, tt.lexerName, lexerName) }) } @@ -177,7 +177,7 @@ c=2`), for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { out := PlainText([]byte(tt.code)) - assert.EqualValues(t, tt.want, out) + assert.Equal(t, tt.want, out) }) } } diff --git a/modules/httplib/request.go b/modules/httplib/request.go index 5e40922896..49ea6f4b73 100644 --- a/modules/httplib/request.go +++ b/modules/httplib/request.go @@ -108,7 +108,7 @@ func (r *Request) Body(data any) *Request { switch t := data.(type) { case nil: // do nothing case string: - bf := bytes.NewBufferString(t) + bf := strings.NewReader(t) r.req.Body = io.NopCloser(bf) r.req.ContentLength = int64(len(t)) case []byte: @@ -143,13 +143,13 @@ func (r *Request) getResponse() (*http.Response, error) { paramBody = paramBody[0 : len(paramBody)-1] } - if r.req.Method == "GET" && len(paramBody) > 0 { + if r.req.Method == http.MethodGet && len(paramBody) > 0 { if strings.Contains(r.url, "?") { r.url += "&" + paramBody } else { r.url = r.url + "?" + paramBody } - } else if r.req.Method == "POST" && r.req.Body == nil && len(paramBody) > 0 { + } else if r.req.Method == http.MethodPost && r.req.Body == nil && len(paramBody) > 0 { r.Header("Content-Type", "application/x-www-form-urlencoded") r.Body(paramBody) // string } diff --git a/modules/httplib/serve_test.go b/modules/httplib/serve_test.go index 06c95bc594..78b88c9b5f 100644 --- a/modules/httplib/serve_test.go +++ b/modules/httplib/serve_test.go @@ -4,11 +4,11 @@ package httplib import ( - "fmt" "net/http" "net/http/httptest" "net/url" "os" + "strconv" "strings" "testing" @@ -23,14 +23,14 @@ func TestServeContentByReader(t *testing.T) { _, rangeStr, _ := strings.Cut(t.Name(), "_range_") r := &http.Request{Header: http.Header{}, Form: url.Values{}} if rangeStr != "" { - r.Header.Set("Range", fmt.Sprintf("bytes=%s", rangeStr)) + r.Header.Set("Range", "bytes="+rangeStr) } reader := strings.NewReader(data) w := httptest.NewRecorder() ServeContentByReader(r, w, int64(len(data)), reader, &ServeHeaderOptions{}) assert.Equal(t, expectedStatusCode, w.Code) if expectedStatusCode == http.StatusPartialContent || expectedStatusCode == http.StatusOK { - assert.Equal(t, fmt.Sprint(len(expectedContent)), w.Header().Get("Content-Length")) + assert.Equal(t, strconv.Itoa(len(expectedContent)), w.Header().Get("Content-Length")) assert.Equal(t, expectedContent, w.Body.String()) } } @@ -68,7 +68,7 @@ func TestServeContentByReadSeeker(t *testing.T) { _, rangeStr, _ := strings.Cut(t.Name(), "_range_") r := &http.Request{Header: http.Header{}, Form: url.Values{}} if rangeStr != "" { - r.Header.Set("Range", fmt.Sprintf("bytes=%s", rangeStr)) + r.Header.Set("Range", "bytes="+rangeStr) } seekReader, err := os.OpenFile(tmpFile, os.O_RDONLY, 0o644) @@ -79,7 +79,7 @@ func TestServeContentByReadSeeker(t *testing.T) { ServeContentByReadSeeker(r, w, nil, seekReader, &ServeHeaderOptions{}) assert.Equal(t, expectedStatusCode, w.Code) if expectedStatusCode == http.StatusPartialContent || expectedStatusCode == http.StatusOK { - assert.Equal(t, fmt.Sprint(len(expectedContent)), w.Header().Get("Content-Length")) + assert.Equal(t, strconv.Itoa(len(expectedContent)), w.Header().Get("Content-Length")) assert.Equal(t, expectedContent, w.Body.String()) } } diff --git a/modules/indexer/code/bleve/token/path/path.go b/modules/indexer/code/bleve/token/path/path.go index 107e0da109..19f4b5bb5e 100644 --- a/modules/indexer/code/bleve/token/path/path.go +++ b/modules/indexer/code/bleve/token/path/path.go @@ -53,11 +53,11 @@ func generatePathTokens(input analysis.TokenStream, reversed bool) analysis.Toke for i := 0; i < len(input); i++ { var sb strings.Builder - sb.WriteString(string(input[0].Term)) + sb.Write(input[0].Term) for j := 1; j < i; j++ { sb.WriteString("/") - sb.WriteString(string(input[j].Term)) + sb.Write(input[j].Term) } term := sb.String() diff --git a/modules/indexer/code/elasticsearch/elasticsearch_test.go b/modules/indexer/code/elasticsearch/elasticsearch_test.go index a6d2af92b2..e8f1f202ce 100644 --- a/modules/indexer/code/elasticsearch/elasticsearch_test.go +++ b/modules/indexer/code/elasticsearch/elasticsearch_test.go @@ -11,6 +11,6 @@ import ( func TestIndexPos(t *testing.T) { startIdx, endIdx := contentMatchIndexPos("test index start and end", "start", "end") - assert.EqualValues(t, 11, startIdx) - assert.EqualValues(t, 15, endIdx) + assert.Equal(t, 11, startIdx) + assert.Equal(t, 15, endIdx) } diff --git a/modules/indexer/code/gitgrep/gitgrep.go b/modules/indexer/code/gitgrep/gitgrep.go index 093c189ba3..6f6e0b47b9 100644 --- a/modules/indexer/code/gitgrep/gitgrep.go +++ b/modules/indexer/code/gitgrep/gitgrep.go @@ -26,9 +26,10 @@ func indexSettingToGitGrepPathspecList() (list []string) { func PerformSearch(ctx context.Context, page int, repoID int64, gitRepo *git.Repository, ref git.RefName, keyword string, searchMode indexer.SearchModeType) (searchResults []*code_indexer.Result, total int, err error) { grepMode := git.GrepModeWords - if searchMode == indexer.SearchModeExact { + switch searchMode { + case indexer.SearchModeExact: grepMode = git.GrepModeExact - } else if searchMode == indexer.SearchModeRegexp { + case indexer.SearchModeRegexp: grepMode = git.GrepModeRegexp } res, err := git.GrepSearch(ctx, gitRepo, keyword, git.GrepOptions{ diff --git a/modules/indexer/code/indexer_test.go b/modules/indexer/code/indexer_test.go index 1d9bf99d40..78fea22f10 100644 --- a/modules/indexer/code/indexer_test.go +++ b/modules/indexer/code/indexer_test.go @@ -310,7 +310,7 @@ func TestESIndexAndSearch(t *testing.T) { if indexer != nil { indexer.Close() } - assert.FailNow(t, "Unable to init ES indexer Error: %v", err) + require.NoError(t, err, "Unable to init ES indexer") } defer indexer.Close() diff --git a/modules/indexer/code/internal/indexer.go b/modules/indexer/code/internal/indexer.go index 6c9a8af635..d58b028124 100644 --- a/modules/indexer/code/internal/indexer.go +++ b/modules/indexer/code/internal/indexer.go @@ -5,7 +5,7 @@ package internal import ( "context" - "fmt" + "errors" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" @@ -48,13 +48,13 @@ func (d *dummyIndexer) SupportedSearchModes() []indexer.SearchMode { } func (d *dummyIndexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *RepoChanges) error { - return fmt.Errorf("indexer is not ready") + return errors.New("indexer is not ready") } func (d *dummyIndexer) Delete(ctx context.Context, repoID int64) error { - return fmt.Errorf("indexer is not ready") + return errors.New("indexer is not ready") } func (d *dummyIndexer) Search(ctx context.Context, opts *SearchOptions) (int64, []*SearchResult, []*SearchResultLanguages, error) { - return 0, nil, nil, fmt.Errorf("indexer is not ready") + return 0, nil, nil, errors.New("indexer is not ready") } diff --git a/modules/indexer/internal/bleve/indexer.go b/modules/indexer/internal/bleve/indexer.go index 01e53ca636..9d1e24a874 100644 --- a/modules/indexer/internal/bleve/indexer.go +++ b/modules/indexer/internal/bleve/indexer.go @@ -5,7 +5,7 @@ package bleve import ( "context" - "fmt" + "errors" "code.gitea.io/gitea/modules/indexer/internal" "code.gitea.io/gitea/modules/log" @@ -39,11 +39,11 @@ func NewIndexer(indexDir string, version int, mappingGetter func() (mapping.Inde // Init initializes the indexer func (i *Indexer) Init(_ context.Context) (bool, error) { if i == nil { - return false, fmt.Errorf("cannot init nil indexer") + return false, errors.New("cannot init nil indexer") } if i.Indexer != nil { - return false, fmt.Errorf("indexer is already initialized") + return false, errors.New("indexer is already initialized") } indexer, version, err := openIndexer(i.indexDir, i.version) @@ -83,10 +83,10 @@ func (i *Indexer) Init(_ context.Context) (bool, error) { // Ping checks if the indexer is available func (i *Indexer) Ping(_ context.Context) error { if i == nil { - return fmt.Errorf("cannot ping nil indexer") + return errors.New("cannot ping nil indexer") } if i.Indexer == nil { - return fmt.Errorf("indexer is not initialized") + return errors.New("indexer is not initialized") } return nil } diff --git a/modules/indexer/internal/elasticsearch/indexer.go b/modules/indexer/internal/elasticsearch/indexer.go index 395eea3bce..265ce26585 100644 --- a/modules/indexer/internal/elasticsearch/indexer.go +++ b/modules/indexer/internal/elasticsearch/indexer.go @@ -5,6 +5,7 @@ package elasticsearch import ( "context" + "errors" "fmt" "code.gitea.io/gitea/modules/indexer/internal" @@ -36,10 +37,10 @@ func NewIndexer(url, indexName string, version int, mapping string) *Indexer { // Init initializes the indexer func (i *Indexer) Init(ctx context.Context) (bool, error) { if i == nil { - return false, fmt.Errorf("cannot init nil indexer") + return false, errors.New("cannot init nil indexer") } if i.Client != nil { - return false, fmt.Errorf("indexer is already initialized") + return false, errors.New("indexer is already initialized") } client, err := i.initClient() @@ -66,10 +67,10 @@ func (i *Indexer) Init(ctx context.Context) (bool, error) { // Ping checks if the indexer is available func (i *Indexer) Ping(ctx context.Context) error { if i == nil { - return fmt.Errorf("cannot ping nil indexer") + return errors.New("cannot ping nil indexer") } if i.Client == nil { - return fmt.Errorf("indexer is not initialized") + return errors.New("indexer is not initialized") } resp, err := i.Client.ClusterHealth().Do(ctx) diff --git a/modules/indexer/internal/indexer.go b/modules/indexer/internal/indexer.go index c7f356da1e..3442bbaff2 100644 --- a/modules/indexer/internal/indexer.go +++ b/modules/indexer/internal/indexer.go @@ -5,7 +5,7 @@ package internal import ( "context" - "fmt" + "errors" ) // Indexer defines an basic indexer interface @@ -27,11 +27,11 @@ func NewDummyIndexer() Indexer { type dummyIndexer struct{} func (d *dummyIndexer) Init(ctx context.Context) (bool, error) { - return false, fmt.Errorf("indexer is not ready") + return false, errors.New("indexer is not ready") } func (d *dummyIndexer) Ping(ctx context.Context) error { - return fmt.Errorf("indexer is not ready") + return errors.New("indexer is not ready") } func (d *dummyIndexer) Close() {} diff --git a/modules/indexer/internal/meilisearch/indexer.go b/modules/indexer/internal/meilisearch/indexer.go index 01bb49bbfc..65db75bb55 100644 --- a/modules/indexer/internal/meilisearch/indexer.go +++ b/modules/indexer/internal/meilisearch/indexer.go @@ -5,6 +5,7 @@ package meilisearch import ( "context" + "errors" "fmt" "github.com/meilisearch/meilisearch-go" @@ -33,11 +34,11 @@ func NewIndexer(url, apiKey, indexName string, version int, settings *meilisearc // Init initializes the indexer func (i *Indexer) Init(_ context.Context) (bool, error) { if i == nil { - return false, fmt.Errorf("cannot init nil indexer") + return false, errors.New("cannot init nil indexer") } if i.Client != nil { - return false, fmt.Errorf("indexer is already initialized") + return false, errors.New("indexer is already initialized") } i.Client = meilisearch.New(i.url, meilisearch.WithAPIKey(i.apiKey)) @@ -62,10 +63,10 @@ func (i *Indexer) Init(_ context.Context) (bool, error) { // Ping checks if the indexer is available func (i *Indexer) Ping(ctx context.Context) error { if i == nil { - return fmt.Errorf("cannot ping nil indexer") + return errors.New("cannot ping nil indexer") } if i.Client == nil { - return fmt.Errorf("indexer is not initialized") + return errors.New("indexer is not initialized") } resp, err := i.Client.Health() if err != nil { diff --git a/modules/indexer/issues/db/db.go b/modules/indexer/issues/db/db.go index 6124ad2515..50951f9c88 100644 --- a/modules/indexer/issues/db/db.go +++ b/modules/indexer/issues/db/db.go @@ -6,6 +6,7 @@ package db import ( "context" "strings" + "sync" "code.gitea.io/gitea/models/db" issue_model "code.gitea.io/gitea/models/issues" @@ -18,7 +19,7 @@ import ( "xorm.io/builder" ) -var _ internal.Indexer = &Indexer{} +var _ internal.Indexer = (*Indexer)(nil) // Indexer implements Indexer interface to use database's like search type Indexer struct { @@ -29,11 +30,9 @@ func (i *Indexer) SupportedSearchModes() []indexer.SearchMode { return indexer.SearchModesExactWords() } -func NewIndexer() *Indexer { - return &Indexer{ - Indexer: &inner_db.Indexer{}, - } -} +var GetIndexer = sync.OnceValue(func() *Indexer { + return &Indexer{Indexer: &inner_db.Indexer{}} +}) // Index dummy function func (i *Indexer) Index(_ context.Context, _ ...*internal.IndexerData) error { @@ -122,7 +121,11 @@ func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( }, nil } - ids, total, err := issue_model.IssueIDs(ctx, opt, cond) + return i.FindWithIssueOptions(ctx, opt, cond) +} + +func (i *Indexer) FindWithIssueOptions(ctx context.Context, opt *issue_model.IssuesOptions, otherConds ...builder.Cond) (*internal.SearchResult, error) { + ids, total, err := issue_model.IssueIDs(ctx, opt, otherConds...) if err != nil { return nil, err } diff --git a/modules/indexer/issues/db/options.go b/modules/indexer/issues/db/options.go index 3b19d742ba..380a25dc23 100644 --- a/modules/indexer/issues/db/options.go +++ b/modules/indexer/issues/db/options.go @@ -6,6 +6,7 @@ package db import ( "context" "fmt" + "strings" "code.gitea.io/gitea/models/db" issue_model "code.gitea.io/gitea/models/issues" @@ -34,7 +35,11 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m case internal.SortByDeadlineAsc: sortType = "nearduedate" default: - sortType = "newest" + if strings.HasPrefix(string(options.SortBy), issue_model.ScopeSortPrefix) { + sortType = string(options.SortBy) + } else { + sortType = "newest" + } } // See the comment of issues_model.SearchOptions for the reason why we need to convert @@ -68,7 +73,6 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m ExcludedLabelNames: nil, IncludeMilestones: nil, SortType: sortType, - IssueIDs: nil, UpdatedAfterUnix: options.UpdatedAfterUnix.Value(), UpdatedBeforeUnix: options.UpdatedBeforeUnix.Value(), PriorityRepoID: 0, diff --git a/modules/indexer/issues/dboptions.go b/modules/indexer/issues/dboptions.go index 4e2dff572a..f17724664d 100644 --- a/modules/indexer/issues/dboptions.go +++ b/modules/indexer/issues/dboptions.go @@ -4,12 +4,19 @@ package issues import ( + "strings" + "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/modules/indexer/issues/internal" "code.gitea.io/gitea/modules/optional" + "code.gitea.io/gitea/modules/setting" ) func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOptions { + if opts.IssueIDs != nil { + setting.PanicInDevOrTesting("Indexer SearchOptions doesn't support IssueIDs") + } searchOpt := &SearchOptions{ Keyword: keyword, RepoIDs: opts.RepoIDs, @@ -95,7 +102,11 @@ func ToSearchOptions(keyword string, opts *issues_model.IssuesOptions) *SearchOp // Unsupported sort type for search fallthrough default: - searchOpt.SortBy = SortByUpdatedDesc + if strings.HasPrefix(opts.SortType, issues_model.ScopeSortPrefix) { + searchOpt.SortBy = internal.SortBy(opts.SortType) + } else { + searchOpt.SortBy = SortByUpdatedDesc + } } return searchOpt diff --git a/modules/indexer/issues/elasticsearch/elasticsearch.go b/modules/indexer/issues/elasticsearch/elasticsearch.go index e3b1b17059..9d627466ef 100644 --- a/modules/indexer/issues/elasticsearch/elasticsearch.go +++ b/modules/indexer/issues/elasticsearch/elasticsearch.go @@ -5,7 +5,6 @@ package elasticsearch import ( "context" - "fmt" "strconv" "strings" @@ -96,7 +95,7 @@ func (b *Indexer) Index(ctx context.Context, issues ...*internal.IndexerData) er issue := issues[0] _, err := b.inner.Client.Index(). Index(b.inner.VersionedIndexName()). - Id(fmt.Sprintf("%d", issue.ID)). + Id(strconv.FormatInt(issue.ID, 10)). BodyJson(issue). Do(ctx) return err @@ -107,7 +106,7 @@ func (b *Indexer) Index(ctx context.Context, issues ...*internal.IndexerData) er reqs = append(reqs, elastic.NewBulkIndexRequest(). Index(b.inner.VersionedIndexName()). - Id(fmt.Sprintf("%d", issue.ID)). + Id(strconv.FormatInt(issue.ID, 10)). Doc(issue), ) } @@ -126,7 +125,7 @@ func (b *Indexer) Delete(ctx context.Context, ids ...int64) error { } else if len(ids) == 1 { _, err := b.inner.Client.Delete(). Index(b.inner.VersionedIndexName()). - Id(fmt.Sprintf("%d", ids[0])). + Id(strconv.FormatInt(ids[0], 10)). Do(ctx) return err } @@ -136,7 +135,7 @@ func (b *Indexer) Delete(ctx context.Context, ids ...int64) error { reqs = append(reqs, elastic.NewBulkDeleteRequest(). Index(b.inner.VersionedIndexName()). - Id(fmt.Sprintf("%d", id)), + Id(strconv.FormatInt(id, 10)), ) } diff --git a/modules/indexer/issues/indexer.go b/modules/indexer/issues/indexer.go index 4741235d47..9e63ad1ad8 100644 --- a/modules/indexer/issues/indexer.go +++ b/modules/indexer/issues/indexer.go @@ -103,7 +103,7 @@ func InitIssueIndexer(syncReindex bool) { log.Fatal("Unable to issueIndexer.Init with connection %s Error: %v", setting.Indexer.IssueConnStr, err) } case "db": - issueIndexer = db.NewIndexer() + issueIndexer = db.GetIndexer() case "meilisearch": issueIndexer = meilisearch.NewIndexer(setting.Indexer.IssueConnStr, setting.Indexer.IssueConnAuth, setting.Indexer.IssueIndexerName) existed, err = issueIndexer.Init(ctx) @@ -291,19 +291,22 @@ func SearchIssues(ctx context.Context, opts *SearchOptions) ([]int64, int64, err // So if the user creates an issue and list issues immediately, the issue may not be listed because the indexer needs time to index the issue. // Even worse, the external indexer like elastic search may not be available for a while, // and the user may not be able to list issues completely until it is available again. - ix = db.NewIndexer() + ix = db.GetIndexer() } + result, err := ix.Search(ctx, opts) if err != nil { return nil, 0, err } + return SearchResultToIDSlice(result), result.Total, nil +} +func SearchResultToIDSlice(result *internal.SearchResult) []int64 { ret := make([]int64, 0, len(result.Hits)) for _, hit := range result.Hits { ret = append(ret, hit.ID) } - - return ret, result.Total, nil + return ret } // CountIssues counts issues by options. It is a shortcut of SearchIssues(ctx, opts) but only returns the total count. diff --git a/modules/indexer/issues/internal/indexer.go b/modules/indexer/issues/internal/indexer.go index 415f442d0c..59c6f48485 100644 --- a/modules/indexer/issues/internal/indexer.go +++ b/modules/indexer/issues/internal/indexer.go @@ -5,7 +5,7 @@ package internal import ( "context" - "fmt" + "errors" "code.gitea.io/gitea/modules/indexer" "code.gitea.io/gitea/modules/indexer/internal" @@ -36,13 +36,13 @@ func (d *dummyIndexer) SupportedSearchModes() []indexer.SearchMode { } func (d *dummyIndexer) Index(_ context.Context, _ ...*IndexerData) error { - return fmt.Errorf("indexer is not ready") + return errors.New("indexer is not ready") } func (d *dummyIndexer) Delete(_ context.Context, _ ...int64) error { - return fmt.Errorf("indexer is not ready") + return errors.New("indexer is not ready") } func (d *dummyIndexer) Search(_ context.Context, _ *SearchOptions) (*SearchResult, error) { - return nil, fmt.Errorf("indexer is not ready") + return nil, errors.New("indexer is not ready") } diff --git a/modules/indexer/issues/internal/tests/tests.go b/modules/indexer/issues/internal/tests/tests.go index 6e92c7885c..a42ec9a2bc 100644 --- a/modules/indexer/issues/internal/tests/tests.go +++ b/modules/indexer/issues/internal/tests/tests.go @@ -93,7 +93,7 @@ var cases = []*testIndexerCase{ Name: "default", SearchOptions: &internal.SearchOptions{}, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, len(data), len(result.Hits)) + assert.Len(t, result.Hits, len(data)) assert.Equal(t, len(data), int(result.Total)) }, }, @@ -526,7 +526,7 @@ var cases = []*testIndexerCase{ SortBy: internal.SortByCreatedDesc, }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, len(data), len(result.Hits)) + assert.Len(t, result.Hits, len(data)) assert.Equal(t, len(data), int(result.Total)) for i, v := range result.Hits { if i < len(result.Hits)-1 { @@ -542,7 +542,7 @@ var cases = []*testIndexerCase{ SortBy: internal.SortByUpdatedDesc, }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, len(data), len(result.Hits)) + assert.Len(t, result.Hits, len(data)) assert.Equal(t, len(data), int(result.Total)) for i, v := range result.Hits { if i < len(result.Hits)-1 { @@ -558,7 +558,7 @@ var cases = []*testIndexerCase{ SortBy: internal.SortByCommentsDesc, }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, len(data), len(result.Hits)) + assert.Len(t, result.Hits, len(data)) assert.Equal(t, len(data), int(result.Total)) for i, v := range result.Hits { if i < len(result.Hits)-1 { @@ -574,7 +574,7 @@ var cases = []*testIndexerCase{ SortBy: internal.SortByDeadlineDesc, }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, len(data), len(result.Hits)) + assert.Len(t, result.Hits, len(data)) assert.Equal(t, len(data), int(result.Total)) for i, v := range result.Hits { if i < len(result.Hits)-1 { @@ -590,7 +590,7 @@ var cases = []*testIndexerCase{ SortBy: internal.SortByCreatedAsc, }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, len(data), len(result.Hits)) + assert.Len(t, result.Hits, len(data)) assert.Equal(t, len(data), int(result.Total)) for i, v := range result.Hits { if i < len(result.Hits)-1 { @@ -606,7 +606,7 @@ var cases = []*testIndexerCase{ SortBy: internal.SortByUpdatedAsc, }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, len(data), len(result.Hits)) + assert.Len(t, result.Hits, len(data)) assert.Equal(t, len(data), int(result.Total)) for i, v := range result.Hits { if i < len(result.Hits)-1 { @@ -622,7 +622,7 @@ var cases = []*testIndexerCase{ SortBy: internal.SortByCommentsAsc, }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, len(data), len(result.Hits)) + assert.Len(t, result.Hits, len(data)) assert.Equal(t, len(data), int(result.Total)) for i, v := range result.Hits { if i < len(result.Hits)-1 { @@ -638,7 +638,7 @@ var cases = []*testIndexerCase{ SortBy: internal.SortByDeadlineAsc, }, Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { - assert.Equal(t, len(data), len(result.Hits)) + assert.Len(t, result.Hits, len(data)) assert.Equal(t, len(data), int(result.Total)) for i, v := range result.Hits { if i < len(result.Hits)-1 { diff --git a/modules/indexer/issues/meilisearch/meilisearch_test.go b/modules/indexer/issues/meilisearch/meilisearch_test.go index a3a332554a..2fea4004cb 100644 --- a/modules/indexer/issues/meilisearch/meilisearch_test.go +++ b/modules/indexer/issues/meilisearch/meilisearch_test.go @@ -74,13 +74,13 @@ func TestConvertHits(t *testing.T) { } hits, err := convertHits(validResponse) assert.NoError(t, err) - assert.EqualValues(t, []internal.Match{{ID: 11}, {ID: 22}, {ID: 33}}, hits) + assert.Equal(t, []internal.Match{{ID: 11}, {ID: 22}, {ID: 33}}, hits) } func TestDoubleQuoteKeyword(t *testing.T) { - assert.EqualValues(t, "", doubleQuoteKeyword("")) - assert.EqualValues(t, `"a" "b" "c"`, doubleQuoteKeyword("a b c")) - assert.EqualValues(t, `"a" "d" "g"`, doubleQuoteKeyword("a d g")) - assert.EqualValues(t, `"a" "d" "g"`, doubleQuoteKeyword("a d g")) - assert.EqualValues(t, `"a" "d" "g"`, doubleQuoteKeyword(`a "" "d" """g`)) + assert.Empty(t, doubleQuoteKeyword("")) + assert.Equal(t, `"a" "b" "c"`, doubleQuoteKeyword("a b c")) + assert.Equal(t, `"a" "d" "g"`, doubleQuoteKeyword("a d g")) + assert.Equal(t, `"a" "d" "g"`, doubleQuoteKeyword("a d g")) + assert.Equal(t, `"a" "d" "g"`, doubleQuoteKeyword(`a "" "d" """g`)) } diff --git a/modules/indexer/stats/db.go b/modules/indexer/stats/db.go index 067a6f609b..199d493e97 100644 --- a/modules/indexer/stats/db.go +++ b/modules/indexer/stats/db.go @@ -8,6 +8,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/languagestats" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" @@ -62,7 +63,7 @@ func (db *DBIndexer) Index(id int64) error { } // Calculate and save language statistics to database - stats, err := gitRepo.GetLanguageStats(commitID) + stats, err := languagestats.GetLanguageStats(gitRepo, commitID) if err != nil { if !setting.IsInTesting { log.Error("Unable to get language stats for ID %s for default branch %s in %s. Error: %v", commitID, repo.DefaultBranch, repo.FullName(), err) diff --git a/modules/indexer/stats/queue.go b/modules/indexer/stats/queue.go index d002bd57cf..69cde321d8 100644 --- a/modules/indexer/stats/queue.go +++ b/modules/indexer/stats/queue.go @@ -4,7 +4,7 @@ package stats import ( - "fmt" + "errors" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/graceful" @@ -31,7 +31,7 @@ func handler(items ...int64) []int64 { func initStatsQueue() error { statsQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "repo_stats_update", handler) if statsQueue == nil { - return fmt.Errorf("unable to create repo_stats_update queue") + return errors.New("unable to create repo_stats_update queue") } go graceful.GetManager().RunWithCancel(statsQueue) return nil diff --git a/modules/issue/template/template.go b/modules/issue/template/template.go index a2b9d6b33e..84ae90e4ed 100644 --- a/modules/issue/template/template.go +++ b/modules/issue/template/template.go @@ -4,6 +4,7 @@ package template import ( + "errors" "fmt" "net/url" "regexp" @@ -31,17 +32,17 @@ func Validate(template *api.IssueTemplate) error { func validateMetadata(template *api.IssueTemplate) error { if strings.TrimSpace(template.Name) == "" { - return fmt.Errorf("'name' is required") + return errors.New("'name' is required") } if strings.TrimSpace(template.About) == "" { - return fmt.Errorf("'about' is required") + return errors.New("'about' is required") } return nil } func validateYaml(template *api.IssueTemplate) error { if len(template.Fields) == 0 { - return fmt.Errorf("'body' is required") + return errors.New("'body' is required") } ids := make(container.Set[string]) for idx, field := range template.Fields { @@ -401,7 +402,7 @@ func (f *valuedField) Render() string { } func (f *valuedField) Value() string { - return strings.TrimSpace(f.Get(fmt.Sprintf("form-field-%s", f.ID))) + return strings.TrimSpace(f.Get("form-field-" + f.ID)) } func (f *valuedField) Options() []*valuedOption { @@ -444,7 +445,7 @@ func (o *valuedOption) Label() string { func (o *valuedOption) IsChecked() bool { switch o.field.Type { case api.IssueFormFieldTypeDropdown: - checks := strings.Split(o.field.Get(fmt.Sprintf("form-field-%s", o.field.ID)), ",") + checks := strings.Split(o.field.Get("form-field-"+o.field.ID), ",") idx := strconv.Itoa(o.index) for _, v := range checks { if v == idx { diff --git a/modules/issue/template/template_test.go b/modules/issue/template/template_test.go index 575e23def9..7fec9431b6 100644 --- a/modules/issue/template/template_test.go +++ b/modules/issue/template/template_test.go @@ -904,7 +904,7 @@ Option 1 of dropdown, Option 2 of dropdown t.Fatal(err) } if got := RenderToMarkdown(template, tt.args.values); got != tt.want { - assert.EqualValues(t, tt.want, got) + assert.Equal(t, tt.want, got) } }) } diff --git a/modules/label/label.go b/modules/label/label.go index d3ef0e1dc9..ce028aa9f3 100644 --- a/modules/label/label.go +++ b/modules/label/label.go @@ -14,10 +14,11 @@ var colorPattern = regexp.MustCompile("^#?(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{3})$") // Label represents label information loaded from template type Label struct { - Name string `yaml:"name"` - Color string `yaml:"color"` - Description string `yaml:"description,omitempty"` - Exclusive bool `yaml:"exclusive,omitempty"` + Name string `yaml:"name"` + Color string `yaml:"color"` + Description string `yaml:"description,omitempty"` + Exclusive bool `yaml:"exclusive,omitempty"` + ExclusiveOrder int `yaml:"exclusive_order,omitempty"` } // NormalizeColor normalizes a color string to a 6-character hex code diff --git a/modules/lfs/http_client.go b/modules/lfs/http_client.go index 0a27fb0c86..4b51193846 100644 --- a/modules/lfs/http_client.go +++ b/modules/lfs/http_client.go @@ -70,7 +70,7 @@ func (c *HTTPClient) transferNames() []string { func (c *HTTPClient) batch(ctx context.Context, operation string, objects []Pointer) (*BatchResponse, error) { log.Trace("BATCH operation with objects: %v", objects) - url := fmt.Sprintf("%s/objects/batch", c.endpoint) + url := c.endpoint + "/objects/batch" // Original: In some lfs server implementations, they require the ref attribute. #32838 // `ref` is an "optional object describing the server ref that the objects belong to" diff --git a/modules/lfs/http_client_test.go b/modules/lfs/http_client_test.go index 7869c0a21a..179bcdb29a 100644 --- a/modules/lfs/http_client_test.go +++ b/modules/lfs/http_client_test.go @@ -31,7 +31,7 @@ func (a *DummyTransferAdapter) Name() string { } func (a *DummyTransferAdapter) Download(ctx context.Context, l *Link) (io.ReadCloser, error) { - return io.NopCloser(bytes.NewBufferString("dummy")), nil + return io.NopCloser(strings.NewReader("dummy")), nil } func (a *DummyTransferAdapter) Upload(ctx context.Context, l *Link, p Pointer, r io.Reader) error { @@ -49,7 +49,7 @@ func lfsTestRoundtripHandler(req *http.Request) *http.Response { if strings.Contains(url, "status-not-ok") { return &http.Response{StatusCode: http.StatusBadRequest} } else if strings.Contains(url, "invalid-json-response") { - return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewBufferString("invalid json"))} + return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader("invalid json"))} } else if strings.Contains(url, "valid-batch-request-download") { batchResponse = &BatchResponse{ Transfer: "dummy", diff --git a/modules/lfs/transferadapter_test.go b/modules/lfs/transferadapter_test.go index 8bbd45771a..ef72d76db4 100644 --- a/modules/lfs/transferadapter_test.go +++ b/modules/lfs/transferadapter_test.go @@ -32,7 +32,7 @@ func TestBasicTransferAdapter(t *testing.T) { if strings.Contains(url, "download-request") { assert.Equal(t, "GET", req.Method) - return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewBufferString("dummy"))} + return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader("dummy"))} } else if strings.Contains(url, "upload-request") { assert.Equal(t, "PUT", req.Method) assert.Equal(t, "application/octet-stream", req.Header.Get("Content-Type")) @@ -126,7 +126,7 @@ func TestBasicTransferAdapter(t *testing.T) { } for n, c := range cases { - err := a.Upload(t.Context(), c.link, p, bytes.NewBufferString("dummy")) + err := a.Upload(t.Context(), c.link, p, strings.NewReader("dummy")) if len(c.expectederror) > 0 { assert.Contains(t, err.Error(), c.expectederror, "case %d: '%s' should contain '%s'", n, err.Error(), c.expectederror) } else { diff --git a/modules/lfstransfer/backend/backend.go b/modules/lfstransfer/backend/backend.go index 1328d93a48..dd4108ea56 100644 --- a/modules/lfstransfer/backend/backend.go +++ b/modules/lfstransfer/backend/backend.go @@ -47,7 +47,7 @@ func New(ctx context.Context, repo, op, token string, logger transfer.Logger) (t return nil, err } server = server.JoinPath("api/internal/repo", repo, "info/lfs") - return &GiteaBackend{ctx: ctx, server: server, op: op, authToken: token, internalAuth: fmt.Sprintf("Bearer %s", setting.InternalToken), logger: logger}, nil + return &GiteaBackend{ctx: ctx, server: server, op: op, authToken: token, internalAuth: "Bearer " + setting.InternalToken, logger: logger}, nil } // Batch implements transfer.Backend diff --git a/modules/lfstransfer/backend/lock.go b/modules/lfstransfer/backend/lock.go index 639f8b184e..2c3c16a9bb 100644 --- a/modules/lfstransfer/backend/lock.go +++ b/modules/lfstransfer/backend/lock.go @@ -5,6 +5,7 @@ package backend import ( "context" + "errors" "fmt" "io" "net/http" @@ -74,7 +75,7 @@ func (g *giteaLockBackend) Create(path, refname string) (transfer.Lock, error) { if respBody.Lock == nil { g.logger.Log("api returned nil lock") - return nil, fmt.Errorf("api returned nil lock") + return nil, errors.New("api returned nil lock") } respLock := respBody.Lock owner := userUnknown @@ -263,7 +264,7 @@ func (g *giteaLock) CurrentUser() (string, error) { // AsLockSpec implements transfer.Lock func (g *giteaLock) AsLockSpec(ownerID bool) ([]string, error) { msgs := []string{ - fmt.Sprintf("lock %s", g.ID()), + "lock " + g.ID(), fmt.Sprintf("path %s %s", g.ID(), g.Path()), fmt.Sprintf("locked-at %s %s", g.ID(), g.FormattedTimestamp()), fmt.Sprintf("ownername %s %s", g.ID(), g.OwnerName()), @@ -285,9 +286,9 @@ func (g *giteaLock) AsLockSpec(ownerID bool) ([]string, error) { // AsArguments implements transfer.Lock func (g *giteaLock) AsArguments() []string { return []string{ - fmt.Sprintf("id=%s", g.ID()), - fmt.Sprintf("path=%s", g.Path()), - fmt.Sprintf("locked-at=%s", g.FormattedTimestamp()), - fmt.Sprintf("ownername=%s", g.OwnerName()), + "id=" + g.ID(), + "path=" + g.Path(), + "locked-at=" + g.FormattedTimestamp(), + "ownername=" + g.OwnerName(), } } diff --git a/modules/log/event_writer_base.go b/modules/log/event_writer_base.go index c327c48ca2..9189ca4e90 100644 --- a/modules/log/event_writer_base.go +++ b/modules/log/event_writer_base.go @@ -105,7 +105,7 @@ func (b *EventWriterBaseImpl) Run(ctx context.Context) { case io.WriterTo: _, err = msg.WriteTo(b.OutputWriteCloser) default: - _, err = b.OutputWriteCloser.Write([]byte(fmt.Sprint(msg))) + _, err = fmt.Fprint(b.OutputWriteCloser, msg) } if err != nil { FallbackErrorf("unable to write log message of %q (%v): %v", b.Name, err, event.Msg) diff --git a/modules/log/flags_test.go b/modules/log/flags_test.go index 03972a9fb0..6eb65d8114 100644 --- a/modules/log/flags_test.go +++ b/modules/log/flags_test.go @@ -12,19 +12,19 @@ import ( ) func TestFlags(t *testing.T) { - assert.EqualValues(t, Ldefault, Flags{}.Bits()) + assert.Equal(t, Ldefault, Flags{}.Bits()) assert.EqualValues(t, 0, FlagsFromString("").Bits()) - assert.EqualValues(t, Lgopid, FlagsFromString("", Lgopid).Bits()) + assert.Equal(t, Lgopid, FlagsFromString("", Lgopid).Bits()) assert.EqualValues(t, 0, FlagsFromString("none", Lgopid).Bits()) - assert.EqualValues(t, Ldate|Ltime, FlagsFromString("date,time", Lgopid).Bits()) + assert.Equal(t, Ldate|Ltime, FlagsFromString("date,time", Lgopid).Bits()) - assert.EqualValues(t, "stdflags", FlagsFromString("stdflags").String()) - assert.EqualValues(t, "medfile", FlagsFromString("medfile").String()) + assert.Equal(t, "stdflags", FlagsFromString("stdflags").String()) + assert.Equal(t, "medfile", FlagsFromString("medfile").String()) bs, err := json.Marshal(FlagsFromString("utc,level")) assert.NoError(t, err) - assert.EqualValues(t, `"level,utc"`, string(bs)) + assert.Equal(t, `"level,utc"`, string(bs)) var flags Flags assert.NoError(t, json.Unmarshal(bs, &flags)) - assert.EqualValues(t, LUTC|Llevel, flags.Bits()) + assert.Equal(t, LUTC|Llevel, flags.Bits()) } diff --git a/modules/log/logger_test.go b/modules/log/logger_test.go index 9f604815f6..a74139dc51 100644 --- a/modules/log/logger_test.go +++ b/modules/log/logger_test.go @@ -57,16 +57,16 @@ func TestLogger(t *testing.T) { dump := logger.DumpWriters() assert.Empty(t, dump) - assert.EqualValues(t, NONE, logger.GetLevel()) + assert.Equal(t, NONE, logger.GetLevel()) assert.False(t, logger.IsEnabled()) w1 := newDummyWriter("dummy-1", DEBUG, 0) logger.AddWriters(w1) - assert.EqualValues(t, DEBUG, logger.GetLevel()) + assert.Equal(t, DEBUG, logger.GetLevel()) w2 := newDummyWriter("dummy-2", WARN, 200*time.Millisecond) logger.AddWriters(w2) - assert.EqualValues(t, DEBUG, logger.GetLevel()) + assert.Equal(t, DEBUG, logger.GetLevel()) dump = logger.DumpWriters() assert.Len(t, dump, 2) diff --git a/modules/markup/common/footnote.go b/modules/markup/common/footnote.go index 4406803694..9a4f18ed7f 100644 --- a/modules/markup/common/footnote.go +++ b/modules/markup/common/footnote.go @@ -53,7 +53,7 @@ type FootnoteLink struct { // Dump implements Node.Dump. func (n *FootnoteLink) Dump(source []byte, level int) { m := map[string]string{} - m["Index"] = fmt.Sprintf("%v", n.Index) + m["Index"] = strconv.Itoa(n.Index) m["Name"] = fmt.Sprintf("%v", n.Name) ast.DumpHelper(n, source, level, m, nil) } @@ -85,7 +85,7 @@ type FootnoteBackLink struct { // Dump implements Node.Dump. func (n *FootnoteBackLink) Dump(source []byte, level int) { m := map[string]string{} - m["Index"] = fmt.Sprintf("%v", n.Index) + m["Index"] = strconv.Itoa(n.Index) m["Name"] = fmt.Sprintf("%v", n.Name) ast.DumpHelper(n, source, level, m, nil) } @@ -151,7 +151,7 @@ type FootnoteList struct { // Dump implements Node.Dump. func (n *FootnoteList) Dump(source []byte, level int) { m := map[string]string{} - m["Count"] = fmt.Sprintf("%v", n.Count) + m["Count"] = strconv.Itoa(n.Count) ast.DumpHelper(n, source, level, m, nil) } diff --git a/modules/markup/common/linkify.go b/modules/markup/common/linkify.go index 52888958fa..3eecb97eac 100644 --- a/modules/markup/common/linkify.go +++ b/modules/markup/common/linkify.go @@ -85,9 +85,10 @@ func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Cont } else if lastChar == ')' { closing := 0 for i := m[1] - 1; i >= m[0]; i-- { - if line[i] == ')' { + switch line[i] { + case ')': closing++ - } else if line[i] == '(' { + case '(': closing-- } } diff --git a/modules/markup/console/console_test.go b/modules/markup/console/console_test.go index 040ec151f4..539f965ea1 100644 --- a/modules/markup/console/console_test.go +++ b/modules/markup/console/console_test.go @@ -25,6 +25,6 @@ func TestRenderConsole(t *testing.T) { err := render.Render(markup.NewRenderContext(t.Context()), strings.NewReader(k), &buf) assert.NoError(t, err) - assert.EqualValues(t, v, buf.String()) + assert.Equal(t, v, buf.String()) } } diff --git a/modules/markup/csv/csv_test.go b/modules/markup/csv/csv_test.go index b0b18ab467..fff7f0baca 100644 --- a/modules/markup/csv/csv_test.go +++ b/modules/markup/csv/csv_test.go @@ -25,6 +25,6 @@ func TestRenderCSV(t *testing.T) { var buf strings.Builder err := render.Render(markup.NewRenderContext(t.Context()), strings.NewReader(k), &buf) assert.NoError(t, err) - assert.EqualValues(t, v, buf.String()) + assert.Equal(t, v, buf.String()) } } diff --git a/modules/markup/external/external.go b/modules/markup/external/external.go index 03242e569e..39861ade12 100644 --- a/modules/markup/external/external.go +++ b/modules/markup/external/external.go @@ -12,11 +12,9 @@ import ( "runtime" "strings" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" ) // RegisterRenderers registers all supported third part renderers according settings @@ -77,27 +75,22 @@ func envMark(envName string) string { // Render renders the data of the document to HTML via the external tool. func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { - var ( - command = strings.NewReplacer( - envMark("GITEA_PREFIX_SRC"), ctx.RenderHelper.ResolveLink("", markup.LinkTypeDefault), - envMark("GITEA_PREFIX_RAW"), ctx.RenderHelper.ResolveLink("", markup.LinkTypeRaw), - ).Replace(p.Command) - commands = strings.Fields(command) - args = commands[1:] - ) + baseLinkSrc := ctx.RenderHelper.ResolveLink("", markup.LinkTypeDefault) + baseLinkRaw := ctx.RenderHelper.ResolveLink("", markup.LinkTypeRaw) + command := strings.NewReplacer( + envMark("GITEA_PREFIX_SRC"), baseLinkSrc, + envMark("GITEA_PREFIX_RAW"), baseLinkRaw, + ).Replace(p.Command) + commands := strings.Fields(command) + args := commands[1:] if p.IsInputFile { // write to temp file - f, err := os.CreateTemp("", "gitea_input") + f, cleanup, err := setting.AppDataTempDir("git-repo-content").CreateTempFileRandom("gitea_input") if err != nil { return fmt.Errorf("%s create temp file when rendering %s failed: %w", p.Name(), p.Command, err) } - tmpPath := f.Name() - defer func() { - if err := util.Remove(tmpPath); err != nil { - log.Warn("Unable to remove temporary file: %s: Error: %v", tmpPath, err) - } - }() + defer cleanup() _, err = io.Copy(f, input) if err != nil { @@ -112,14 +105,14 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io. args = append(args, f.Name()) } - processCtx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.RenderHelper.ResolveLink("", markup.LinkTypeDefault))) + processCtx, _, finished := process.GetManager().AddContext(ctx, fmt.Sprintf("Render [%s] for %s", commands[0], baseLinkSrc)) defer finished() cmd := exec.CommandContext(processCtx, commands[0], args...) cmd.Env = append( os.Environ(), - "GITEA_PREFIX_SRC="+ctx.RenderHelper.ResolveLink("", markup.LinkTypeDefault), - "GITEA_PREFIX_RAW="+ctx.RenderHelper.ResolveLink("", markup.LinkTypeRaw), + "GITEA_PREFIX_SRC="+baseLinkSrc, + "GITEA_PREFIX_RAW="+baseLinkRaw, ) if !p.IsInputFile { cmd.Stdin = input diff --git a/modules/markup/html.go b/modules/markup/html.go index 3aaf669c63..0e074cbcfa 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -32,7 +32,6 @@ type globalVarsType struct { comparePattern *regexp.Regexp fullURLPattern *regexp.Regexp emailRegex *regexp.Regexp - blackfridayExtRegex *regexp.Regexp emojiShortCodeRegex *regexp.Regexp issueFullPattern *regexp.Regexp filesChangedFullPattern *regexp.Regexp @@ -74,9 +73,6 @@ var globalVars = sync.OnceValue(func() *globalVarsType { // https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type%3Demail) v.emailRegex = regexp.MustCompile("(?:\\s|^|\\(|\\[)([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)(?:\\s|$|\\)|\\]|;|,|\\?|!|\\.(\\s|$))") - // blackfridayExtRegex is for blackfriday extensions create IDs like fn:user-content-footnote - v.blackfridayExtRegex = regexp.MustCompile(`[^:]*:user-content-`) - // emojiShortCodeRegex find emoji by alias like :smile: v.emojiShortCodeRegex = regexp.MustCompile(`:[-+\w]+:`) @@ -89,22 +85,18 @@ var globalVars = sync.OnceValue(func() *globalVarsType { // codePreviewPattern matches "http://domain/.../{owner}/{repo}/src/commit/{commit}/{filepath}#L10-L20" v.codePreviewPattern = regexp.MustCompile(`https?://\S+/([^\s/]+)/([^\s/]+)/src/commit/([0-9a-f]{7,64})(/\S+)#(L\d+(-L\d+)?)`) - v.tagCleaner = regexp.MustCompile(`<((?:/?\w+/\w+)|(?:/[\w ]+/)|(/?[hH][tT][mM][lL]\b)|(/?[hH][eE][aA][dD]\b))`) + // cleans: "`, then the next iteration will visit the "span" - // if we don't stop it, it will go into the TextNode again and create an infinite recursion - return node.NextSibling - } else if node.Data == "code" || node.Data == "pre" { - return node.NextSibling // ignore code and pre nodes - } else if node.Data == "img" { - return visitNodeImg(ctx, node) - } else if node.Data == "video" { - return visitNodeVideo(ctx, node) - } else if node.Data == "a" { - procs = emojiProcessors // Restrict text in links to emojis - } - for n := node.FirstChild; n != nil; { - n = visitNode(ctx, procs, n) - } - default: + processNodeAttrID(node) + + if isEmojiNode(node) { + // TextNode emoji will be converted to ``, then the next iteration will visit the "span" + // if we don't stop it, it will go into the TextNode again and create an infinite recursion + return node.NextSibling + } else if node.Data == "code" || node.Data == "pre" { + return node.NextSibling // ignore code and pre nodes + } else if node.Data == "img" { + return visitNodeImg(ctx, node) + } else if node.Data == "video" { + return visitNodeVideo(ctx, node) + } + + if node.Data == "a" { + processNodeA(ctx, node) + // only use emoji processors for the content in the "A" tag, + // because the content there is not processable, for example: the content is a commit id or a full URL. + procs = emojiProcessors + } + for n := node.FirstChild; n != nil; { + n = visitNode(ctx, procs, n) } return node.NextSibling } diff --git a/modules/markup/html_commit.go b/modules/markup/html_commit.go index aa1b7d034a..967c327f36 100644 --- a/modules/markup/html_commit.go +++ b/modules/markup/html_commit.go @@ -43,7 +43,6 @@ func createCodeLink(href, content, class string) *html.Node { code := &html.Node{ Type: html.ElementNode, Data: atom.Code.String(), - Attr: []html.Attribute{{Key: "class", Val: "nohighlight"}}, } code.AppendChild(text) @@ -189,7 +188,7 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) { continue } - link := ctx.RenderHelper.ResolveLink(util.URLJoin(ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], "commit", hash), LinkTypeApp) + link := "/:root/" + util.URLJoin(ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], "commit", hash) replaceContent(node, m[2], m[3], createCodeLink(link, base.ShortSha(hash), "commit")) start = 0 node = node.NextSibling.NextSibling @@ -205,9 +204,9 @@ func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) { return } - reftext := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha) - linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ref.Owner, ref.Name, "commit", ref.CommitSha), LinkTypeApp) - link := createLink(ctx, linkHref, reftext, "commit") + refText := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha) + linkHref := "/:root/" + util.URLJoin(ref.Owner, ref.Name, "commit", ref.CommitSha) + link := createLink(ctx, linkHref, refText, "commit") replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link) node = node.NextSibling.NextSibling diff --git a/modules/markup/html_internal_test.go b/modules/markup/html_internal_test.go index 159d712955..467cc509d0 100644 --- a/modules/markup/html_internal_test.go +++ b/modules/markup/html_internal_test.go @@ -107,7 +107,7 @@ func TestRender_IssueIndexPattern2(t *testing.T) { isExternal := false if marker == "!" { path = "pulls" - prefix = "http://localhost:3000/someUser/someRepo/pulls/" + prefix = "/someUser/someRepo/pulls/" } else { path = "issues" prefix = "https://someurl.com/someUser/someRepo/" @@ -116,7 +116,7 @@ func TestRender_IssueIndexPattern2(t *testing.T) { links := make([]any, len(indices)) for i, index := range indices { - links[i] = numericIssueLink(util.URLJoin(TestRepoURL, path), "ref-issue", index, marker) + links[i] = numericIssueLink(util.URLJoin("/test-owner/test-repo", path), "ref-issue", index, marker) } expectedNil := fmt.Sprintf(expectedFmt, links...) testRenderIssueIndexPattern(t, s, expectedNil, NewTestRenderContext(TestAppURL, localMetas)) @@ -293,13 +293,13 @@ func TestRender_AutoLink(t *testing.T) { // render valid commit URLs tmp := util.URLJoin(TestRepoURL, "commit", "d8a994ef243349f321568f9e36d5c3f444b99cae") - test(tmp, "d8a994ef24") + test(tmp, "d8a994ef24") tmp += "#diff-2" - test(tmp, "d8a994ef24 (diff-2)") + test(tmp, "d8a994ef24 (diff-2)") // render other commit URLs tmp = "https://external-link.gitea.io/go-gitea/gitea/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2" - test(tmp, "d8a994ef24 (diff-2)") + test(tmp, "d8a994ef24 (diff-2)") } func TestRender_FullIssueURLs(t *testing.T) { @@ -405,10 +405,10 @@ func TestRegExp_anySHA1Pattern(t *testing.T) { if v.CommitID == "" { assert.False(t, ok) } else { - assert.EqualValues(t, strings.TrimSuffix(k, "."), ret.FullURL) - assert.EqualValues(t, v.CommitID, ret.CommitID) - assert.EqualValues(t, v.SubPath, ret.SubPath) - assert.EqualValues(t, v.QueryHash, ret.QueryHash) + assert.Equal(t, strings.TrimSuffix(k, "."), ret.FullURL) + assert.Equal(t, v.CommitID, ret.CommitID) + assert.Equal(t, v.SubPath, ret.SubPath) + assert.Equal(t, v.QueryHash, ret.QueryHash) } } } diff --git a/modules/markup/html_issue.go b/modules/markup/html_issue.go index 7a6f33011a..85bec5db20 100644 --- a/modules/markup/html_issue.go +++ b/modules/markup/html_issue.go @@ -82,7 +82,7 @@ func createIssueLinkContentWithSummary(ctx *RenderContext, linkHref string, ref h, err := DefaultRenderHelperFuncs.RenderRepoIssueIconTitle(ctx, RenderIssueIconTitleOptions{ OwnerName: ref.Owner, RepoName: ref.Name, - LinkHref: linkHref, + LinkHref: ctx.RenderHelper.ResolveLink(linkHref, LinkTypeDefault), IssueIndex: issueIndex, }) if err != nil { @@ -162,7 +162,7 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) { issueOwner := util.Iif(ref.Owner == "", ctx.RenderOptions.Metas["user"], ref.Owner) issueRepo := util.Iif(ref.Owner == "", ctx.RenderOptions.Metas["repo"], ref.Name) issuePath := util.Iif(ref.IsPull, "pulls", "issues") - linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(issueOwner, issueRepo, issuePath, ref.Issue), LinkTypeApp) + linkHref := "/:root/" + util.URLJoin(issueOwner, issueRepo, issuePath, ref.Issue) // at the moment, only render the issue index in a full line (or simple line) as icon+title // otherwise it would be too noisy for "take #1 as an example" in a sentence diff --git a/modules/markup/html_issue_test.go b/modules/markup/html_issue_test.go index 8d189fbdf6..c68429641f 100644 --- a/modules/markup/html_issue_test.go +++ b/modules/markup/html_issue_test.go @@ -39,7 +39,7 @@ func TestRender_IssueList(t *testing.T) { t.Run("NormalIssueRef", func(t *testing.T) { test( "#12345", - `#12345`, + `#12345`, ) }) @@ -56,7 +56,7 @@ func TestRender_IssueList(t *testing.T) { test( "* foo #12345 bar", ` -foo #12345 bar +foo #12345 bar `, ) }) diff --git a/modules/markup/html_link.go b/modules/markup/html_link.go index 0e7a988d36..1ea0b14028 100644 --- a/modules/markup/html_link.go +++ b/modules/markup/html_link.go @@ -125,7 +125,6 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) { } } if image { - link = ctx.RenderHelper.ResolveLink(link, LinkTypeMedia) title := props["title"] if title == "" { title = props["alt"] @@ -151,7 +150,6 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) { childNode.Attr = childNode.Attr[:2] } } else { - link = ctx.RenderHelper.ResolveLink(link, LinkTypeDefault) childNode.Type = html.TextNode childNode.Data = name } diff --git a/modules/markup/html_mention.go b/modules/markup/html_mention.go index fffa12e7b7..f97c034cf3 100644 --- a/modules/markup/html_mention.go +++ b/modules/markup/html_mention.go @@ -33,7 +33,7 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) { if ok && strings.Contains(mention, "/") { mentionOrgAndTeam := strings.Split(mention, "/") if mentionOrgAndTeam[0][1:] == ctx.RenderOptions.Metas["org"] && strings.Contains(teams, ","+strings.ToLower(mentionOrgAndTeam[1])+",") { - link := ctx.RenderHelper.ResolveLink(util.URLJoin("org", ctx.RenderOptions.Metas["org"], "teams", mentionOrgAndTeam[1]), LinkTypeApp) + link := "/:root/" + util.URLJoin("org", ctx.RenderOptions.Metas["org"], "teams", mentionOrgAndTeam[1]) replaceContent(node, loc.Start, loc.End, createLink(ctx, link, mention, "" /*mention*/)) node = node.NextSibling.NextSibling start = 0 @@ -45,7 +45,7 @@ func mentionProcessor(ctx *RenderContext, node *html.Node) { mentionedUsername := mention[1:] if DefaultRenderHelperFuncs != nil && DefaultRenderHelperFuncs.IsUsernameMentionable(ctx, mentionedUsername) { - link := ctx.RenderHelper.ResolveLink(mentionedUsername, LinkTypeApp) + link := "/:root/" + mentionedUsername replaceContent(node, loc.Start, loc.End, createLink(ctx, link, mention, "" /*mention*/)) node = node.NextSibling.NextSibling start = 0 diff --git a/modules/markup/html_node.go b/modules/markup/html_node.go index 6e8ca67900..68858b024a 100644 --- a/modules/markup/html_node.go +++ b/modules/markup/html_node.go @@ -4,42 +4,79 @@ package markup import ( + "strings" + "golang.org/x/net/html" ) +func isAnchorIDUserContent(s string) bool { + // blackfridayExtRegex is for blackfriday extensions create IDs like fn:user-content-footnote + // old logic: blackfridayExtRegex = regexp.MustCompile(`[^:]*:user-content-`) + return strings.HasPrefix(s, "user-content-") || strings.Contains(s, ":user-content-") +} + +func processNodeAttrID(node *html.Node) { + // Add user-content- to IDs and "#" links if they don't already have them, + // and convert the link href to a relative link to the host root + for idx, attr := range node.Attr { + if attr.Key == "id" { + if !isAnchorIDUserContent(attr.Val) { + node.Attr[idx].Val = "user-content-" + attr.Val + } + } + } +} + +func processNodeA(ctx *RenderContext, node *html.Node) { + for idx, attr := range node.Attr { + if attr.Key == "href" { + if anchorID, ok := strings.CutPrefix(attr.Val, "#"); ok { + if !isAnchorIDUserContent(attr.Val) { + node.Attr[idx].Val = "#user-content-" + anchorID + } + } else { + node.Attr[idx].Val = ctx.RenderHelper.ResolveLink(attr.Val, LinkTypeDefault) + } + } + } +} + func visitNodeImg(ctx *RenderContext, img *html.Node) (next *html.Node) { next = img.NextSibling - for i, attr := range img.Attr { - if attr.Key != "src" { + for i, imgAttr := range img.Attr { + if imgAttr.Key != "src" { continue } - if IsNonEmptyRelativePath(attr.Val) { - attr.Val = ctx.RenderHelper.ResolveLink(attr.Val, LinkTypeMedia) + imgSrcOrigin := imgAttr.Val + isLinkable := imgSrcOrigin != "" && !strings.HasPrefix(imgSrcOrigin, "data:") - // By default, the "" tag should also be clickable, - // because frontend use `` to paste the re-scaled image into the markdown, - // so it must match the default markdown image behavior. - hasParentAnchor := false - for p := img.Parent; p != nil; p = p.Parent { - if hasParentAnchor = p.Type == html.ElementNode && p.Data == "a"; hasParentAnchor { - break - } - } - if !hasParentAnchor { - imgA := &html.Node{Type: html.ElementNode, Data: "a", Attr: []html.Attribute{ - {Key: "href", Val: attr.Val}, - {Key: "target", Val: "_blank"}, - }} - parent := img.Parent - imgNext := img.NextSibling - parent.RemoveChild(img) - parent.InsertBefore(imgA, imgNext) - imgA.AppendChild(img) + // By default, the "" tag should also be clickable, + // because frontend use `` to paste the re-scaled image into the markdown, + // so it must match the default markdown image behavior. + cnt := 0 + for p := img.Parent; isLinkable && p != nil && cnt < 2; p = p.Parent { + if hasParentAnchor := p.Type == html.ElementNode && p.Data == "a"; hasParentAnchor { + isLinkable = false + break } + cnt++ } - attr.Val = camoHandleLink(attr.Val) - img.Attr[i] = attr + if isLinkable { + wrapper := &html.Node{Type: html.ElementNode, Data: "a", Attr: []html.Attribute{ + {Key: "href", Val: ctx.RenderHelper.ResolveLink(imgSrcOrigin, LinkTypeDefault)}, + {Key: "target", Val: "_blank"}, + }} + parent := img.Parent + imgNext := img.NextSibling + parent.RemoveChild(img) + parent.InsertBefore(wrapper, imgNext) + wrapper.AppendChild(img) + } + + imgAttr.Val = ctx.RenderHelper.ResolveLink(imgSrcOrigin, LinkTypeMedia) + imgAttr.Val = camoHandleLink(imgAttr.Val) + img.Attr[i] = imgAttr } return next } diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index f0f062fa64..aab9fddd91 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -35,6 +35,7 @@ func TestRender_Commits(t *testing.T) { sha := "65f1bf27bc3bf70f64657658635e66094edbcb4d" repo := markup.TestAppURL + testRepoOwnerName + "/" + testRepoName + "/" commit := util.URLJoin(repo, "commit", sha) + commitPath := "/user13/repo11/commit/" + sha tree := util.URLJoin(repo, "tree", sha, "src") file := util.URLJoin(repo, "commit", sha, "example.txt") @@ -44,9 +45,9 @@ func TestRender_Commits(t *testing.T) { commitCompare := util.URLJoin(repo, "compare", sha+"..."+sha) commitCompareWithHash := commitCompare + "#L2" - test(sha, `65f1bf27bc`) - test(sha[:7], `65f1bf2`) - test(sha[:39], `65f1bf27bc`) + test(sha, `65f1bf27bc`) + test(sha[:7], `65f1bf2`) + test(sha[:39], `65f1bf27bc`) test(commit, `65f1bf27bc`) test(tree, `65f1bf27bc/src`) @@ -57,13 +58,13 @@ func TestRender_Commits(t *testing.T) { test(commitCompare, `65f1bf27bc...65f1bf27bc`) test(commitCompareWithHash, `65f1bf27bc...65f1bf27bc (L2)`) - test("commit "+sha, `commit 65f1bf27bc`) + test("commit "+sha, `commit 65f1bf27bc`) test("/home/gitea/"+sha, "/home/gitea/"+sha+"") test("deadbeef", `deadbeef`) test("d27ace93", `d27ace93`) test(sha[:14]+".x", ``+sha[:14]+`.x`) - expected14 := `` + sha[:10] + `` + expected14 := `` + sha[:10] + `` test(sha[:14]+".", ``+expected14+`.`) test(sha[:14]+",", ``+expected14+`,`) test("["+sha[:14]+"]", `[`+expected14+`]`) @@ -80,10 +81,10 @@ func TestRender_CrossReferences(t *testing.T) { test( "test-owner/test-repo#12345", - `test-owner/test-repo#12345`) + `test-owner/test-repo#12345`) test( "go-gitea/gitea#12345", - `go-gitea/gitea#12345`) + `go-gitea/gitea#12345`) test( "/home/gitea/go-gitea/gitea#12345", `/home/gitea/go-gitea/gitea#12345`) @@ -468,7 +469,7 @@ func Test_ParseClusterFuzz(t *testing.T) { assert.NotContains(t, res.String(), "go-gitea/gitea#12345`) + `go-gitea/gitea#12345`) - // Test that other post processing still works. + // Test that other post-processing still works. test( ":gitea:", ``) @@ -498,6 +499,12 @@ func TestPostProcess_RenderDocument(t *testing.T) { `Some text with 😄 in the middle`) test("http://localhost:3000/person/repo/issues/4#issuecomment-1234", `person/repo#4 (comment)`) + + // special tags, GitHub's behavior, and for unclosed tags, output as text content as much as possible + test("", `<script>a</script>`) + test("", `<style>a</STYLE>`) } func TestIssue16020(t *testing.T) { @@ -543,7 +550,7 @@ func TestIssue18471(t *testing.T) { err := markup.PostProcessDefault(markup.NewTestRenderContext(localMetas), strings.NewReader(data), &res) assert.NoError(t, err) - assert.Equal(t, `783b039...da951ce`, res.String()) + assert.Equal(t, `783b039...da951ce`, res.String()) } func TestIsFullURL(t *testing.T) { diff --git a/modules/markup/internal/internal_test.go b/modules/markup/internal/internal_test.go index 98ff3bc079..590bcbb67f 100644 --- a/modules/markup/internal/internal_test.go +++ b/modules/markup/internal/internal_test.go @@ -35,7 +35,7 @@ func TestRenderInternal(t *testing.T) { assert.EqualValues(t, c.protected, protected) _, _ = io.WriteString(in, string(protected)) _ = in.Close() - assert.EqualValues(t, c.recovered, out.String()) + assert.Equal(t, c.recovered, out.String()) } var r1, r2 RenderInternal @@ -44,11 +44,11 @@ func TestRenderInternal(t *testing.T) { _ = r1.init("sec", nil) protected = r1.ProtectSafeAttrs(``) assert.EqualValues(t, ``, protected) - assert.EqualValues(t, "data-attr-class", r1.SafeAttr("class")) - assert.EqualValues(t, "sec:val", r1.SafeValue("val")) + assert.Equal(t, "data-attr-class", r1.SafeAttr("class")) + assert.Equal(t, "sec:val", r1.SafeValue("val")) recovered, ok := r1.RecoverProtectedValue("sec:val") assert.True(t, ok) - assert.EqualValues(t, "val", recovered) + assert.Equal(t, "val", recovered) recovered, ok = r1.RecoverProtectedValue("other:val") assert.False(t, ok) assert.Empty(t, recovered) @@ -57,5 +57,5 @@ func TestRenderInternal(t *testing.T) { in2 := r2.init("sec-other", out2) _, _ = io.WriteString(in2, string(protected)) _ = in2.Close() - assert.EqualValues(t, ``, out2.String(), "different secureID should not recover the value") + assert.Equal(t, ``, out2.String(), "different secureID should not recover the value") } diff --git a/modules/markup/markdown/ast.go b/modules/markup/markdown/ast.go index ca165b1ba0..f29f883734 100644 --- a/modules/markup/markdown/ast.go +++ b/modules/markup/markdown/ast.go @@ -4,6 +4,7 @@ package markdown import ( + "html/template" "strconv" "github.com/yuin/goldmark/ast" @@ -29,9 +30,7 @@ func (n *Details) Kind() ast.NodeKind { // NewDetails returns a new Paragraph node. func NewDetails() *Details { - return &Details{ - BaseBlock: ast.BaseBlock{}, - } + return &Details{} } // Summary is a block that contains the summary of details block @@ -54,9 +53,7 @@ func (n *Summary) Kind() ast.NodeKind { // NewSummary returns a new Summary node. func NewSummary() *Summary { - return &Summary{ - BaseBlock: ast.BaseBlock{}, - } + return &Summary{} } // TaskCheckBoxListItem is a block that represents a list item of a markdown block with a checkbox @@ -95,29 +92,6 @@ type Icon struct { Name []byte } -// Dump implements Node.Dump . -func (n *Icon) Dump(source []byte, level int) { - m := map[string]string{} - m["Name"] = string(n.Name) - ast.DumpHelper(n, source, level, m, nil) -} - -// KindIcon is the NodeKind for Icon -var KindIcon = ast.NewNodeKind("Icon") - -// Kind implements Node.Kind. -func (n *Icon) Kind() ast.NodeKind { - return KindIcon -} - -// NewIcon returns a new Paragraph node. -func NewIcon(name string) *Icon { - return &Icon{ - BaseInline: ast.BaseInline{}, - Name: []byte(name), - } -} - // ColorPreview is an inline for a color preview type ColorPreview struct { ast.BaseInline @@ -175,3 +149,24 @@ func NewAttention(attentionType string) *Attention { AttentionType: attentionType, } } + +var KindRawHTML = ast.NewNodeKind("RawHTML") + +type RawHTML struct { + ast.BaseBlock + rawHTML template.HTML +} + +func (n *RawHTML) Dump(source []byte, level int) { + m := map[string]string{} + m["RawHTML"] = string(n.rawHTML) + ast.DumpHelper(n, source, level, m, nil) +} + +func (n *RawHTML) Kind() ast.NodeKind { + return KindRawHTML +} + +func NewRawHTML(rawHTML template.HTML) *RawHTML { + return &RawHTML{rawHTML: rawHTML} +} diff --git a/modules/markup/markdown/convertyaml.go b/modules/markup/markdown/convertyaml.go index 1675b68be2..04664a9c1d 100644 --- a/modules/markup/markdown/convertyaml.go +++ b/modules/markup/markdown/convertyaml.go @@ -4,23 +4,22 @@ package markdown import ( + "strings" + + "code.gitea.io/gitea/modules/htmlutil" + "code.gitea.io/gitea/modules/svg" + "github.com/yuin/goldmark/ast" east "github.com/yuin/goldmark/extension/ast" "gopkg.in/yaml.v3" ) func nodeToTable(meta *yaml.Node) ast.Node { - for { - if meta == nil { - return nil - } - switch meta.Kind { - case yaml.DocumentNode: - meta = meta.Content[0] - continue - default: - } - break + for meta != nil && meta.Kind == yaml.DocumentNode { + meta = meta.Content[0] + } + if meta == nil { + return nil } switch meta.Kind { case yaml.MappingNode: @@ -72,12 +71,28 @@ func sequenceNodeToTable(meta *yaml.Node) ast.Node { return table } -func nodeToDetails(meta *yaml.Node, icon string) ast.Node { +func nodeToDetails(g *ASTTransformer, meta *yaml.Node) ast.Node { + for meta != nil && meta.Kind == yaml.DocumentNode { + meta = meta.Content[0] + } + if meta == nil { + return nil + } + if meta.Kind != yaml.MappingNode { + return nil + } + var keys []string + for i := 0; i < len(meta.Content); i += 2 { + if meta.Content[i].Kind == yaml.ScalarNode { + keys = append(keys, meta.Content[i].Value) + } + } details := NewDetails() + details.SetAttributeString(g.renderInternal.SafeAttr("class"), g.renderInternal.SafeValue("frontmatter-content")) summary := NewSummary() - summary.AppendChild(summary, NewIcon(icon)) + summaryInnerHTML := htmlutil.HTMLFormat("%s %s", svg.RenderHTML("octicon-table", 12), strings.Join(keys, ", ")) + summary.AppendChild(summary, NewRawHTML(summaryInnerHTML)) details.AppendChild(details, summary) details.AppendChild(details, nodeToTable(meta)) - return details } diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go index 69c2a96ff1..b28fa9824e 100644 --- a/modules/markup/markdown/goldmark.go +++ b/modules/markup/markdown/goldmark.go @@ -5,14 +5,10 @@ package markdown import ( "fmt" - "regexp" - "strings" - "sync" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup/internal" - "code.gitea.io/gitea/modules/setting" "github.com/yuin/goldmark/ast" east "github.com/yuin/goldmark/extension/ast" @@ -51,7 +47,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa tocList := make([]Header, 0, 20) if rc.yamlNode != nil { - metaNode := rc.toMetaNode() + metaNode := rc.toMetaNode(g) if metaNode != nil { node.InsertBefore(node, firstChild, metaNode) } @@ -68,23 +64,12 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa g.transformHeading(ctx, v, reader, &tocList) case *ast.Paragraph: g.applyElementDir(v) - case *ast.Image: - g.transformImage(ctx, v) - case *ast.Link: - g.transformLink(ctx, v) case *ast.List: g.transformList(ctx, v, rc) case *ast.Text: if v.SoftLineBreak() && !v.HardLineBreak() { - // TODO: this was a quite unclear part, old code: `if metas["mode"] != "document" { use comment link break setting }` - // many places render non-comment contents with no mode=document, then these contents also use comment's hard line break setting - // especially in many tests. - markdownLineBreakStyle := ctx.RenderOptions.Metas["markdownLineBreakStyle"] - if markdownLineBreakStyle == "comment" { - v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInComments) - } else if markdownLineBreakStyle == "document" { - v.SetHardLineBreak(setting.Markdown.EnableHardLineBreakInDocuments) - } + newLineHardBreak := ctx.RenderOptions.Metas["markdownNewLineHardBreak"] == "true" + v.SetHardLineBreak(newLineHardBreak) } case *ast.CodeSpan: g.transformCodeSpan(ctx, v, reader) @@ -111,11 +96,6 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa } } -// it is copied from old code, which is quite doubtful whether it is correct -var reValidIconName = sync.OnceValue(func() *regexp.Regexp { - return regexp.MustCompile(`^[-\w]+$`) // old: regexp.MustCompile("^[a-z ]+$") -}) - // NewHTMLRenderer creates a HTMLRenderer to render in the gitea form. func NewHTMLRenderer(renderInternal *internal.RenderInternal, opts ...html.Option) renderer.NodeRenderer { r := &HTMLRenderer{ @@ -140,11 +120,11 @@ func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { reg.Register(ast.KindDocument, r.renderDocument) reg.Register(KindDetails, r.renderDetails) reg.Register(KindSummary, r.renderSummary) - reg.Register(KindIcon, r.renderIcon) reg.Register(ast.KindCodeSpan, r.renderCodeSpan) reg.Register(KindAttention, r.renderAttention) reg.Register(KindTaskCheckBoxListItem, r.renderTaskCheckBoxListItem) reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox) + reg.Register(KindRawHTML, r.renderRawHTML) } func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { @@ -155,7 +135,7 @@ func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast. if entering { _, err = w.WriteString("') @@ -206,30 +186,14 @@ func (r *HTMLRenderer) renderSummary(w util.BufWriter, source []byte, node ast.N return ast.WalkContinue, nil } -func (r *HTMLRenderer) renderIcon(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { +func (r *HTMLRenderer) renderRawHTML(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { if !entering { return ast.WalkContinue, nil } - - n := node.(*Icon) - - name := strings.TrimSpace(strings.ToLower(string(n.Name))) - - if len(name) == 0 { - // skip this - return ast.WalkContinue, nil - } - - if !reValidIconName().MatchString(name) { - // skip this - return ast.WalkContinue, nil - } - - // FIXME: the "icon xxx" is from Fomantic UI, it's really questionable whether it still works correctly - err := r.renderInternal.FormatWithSafeAttrs(w, ``, name) + n := node.(*RawHTML) + _, err := w.WriteString(string(r.renderInternal.ProtectSafeAttrs(n.rawHTML))) if err != nil { return ast.WalkStop, err } - return ast.WalkContinue, nil } diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index ace31eb540..0d7180c6b1 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -5,7 +5,7 @@ package markdown import ( - "fmt" + "errors" "html/template" "io" "strings" @@ -48,7 +48,7 @@ func (l *limitWriter) Write(data []byte) (int, error) { if err != nil { return n, err } - return n, fmt.Errorf("rendered content too large - truncating render") + return n, errors.New("rendered content too large - truncating render") } n, err := l.w.Write(data) l.sum += int64(n) @@ -126,11 +126,11 @@ func SpecializedMarkdown(ctx *markup.RenderContext) *GlodmarkRender { highlighting.WithWrapperRenderer(r.highlightingRenderer), ), math.NewExtension(&ctx.RenderInternal, math.Options{ - Enabled: setting.Markdown.EnableMath, - ParseDollarInline: true, - ParseDollarBlock: true, - ParseSquareBlock: true, // TODO: this is a bad syntax "\[ ... \]", it conflicts with normal markdown escaping, it should be deprecated in the future (by some config options) - // ParseBracketInline: true, // TODO: this is also a bad syntax "\( ... \)", it also conflicts, it should be deprecated in the future + Enabled: setting.Markdown.EnableMath, + ParseInlineDollar: setting.Markdown.MathCodeBlockOptions.ParseInlineDollar, + ParseInlineParentheses: setting.Markdown.MathCodeBlockOptions.ParseInlineParentheses, // this is a bad syntax "\( ... \)", it conflicts with normal markdown escaping + ParseBlockDollar: setting.Markdown.MathCodeBlockOptions.ParseBlockDollar, + ParseBlockSquareBrackets: setting.Markdown.MathCodeBlockOptions.ParseBlockSquareBrackets, // this is a bad syntax "\[ ... \]", it conflicts with normal markdown escaping }), meta.Meta, ), @@ -184,11 +184,7 @@ func render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error // Preserve original length. bufWithMetadataLength := len(buf) - rc := &RenderConfig{ - Meta: markup.RenderMetaAsDetails, - Icon: "table", - Lang: "", - } + rc := &RenderConfig{Meta: markup.RenderMetaAsDetails} buf, _ = ExtractMetadataBytes(buf, rc) metaLength := bufWithMetadataLength - len(buf) diff --git a/modules/markup/markdown/markdown_math_test.go b/modules/markup/markdown/markdown_math_test.go index 813f050965..a75f18d36a 100644 --- a/modules/markup/markdown/markdown_math_test.go +++ b/modules/markup/markdown/markdown_math_test.go @@ -8,6 +8,8 @@ import ( "testing" "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" "github.com/stretchr/testify/assert" ) @@ -15,6 +17,7 @@ import ( const nl = "\n" func TestMathRender(t *testing.T) { + setting.Markdown.MathCodeBlockOptions = setting.MarkdownMathCodeBlockOptions{ParseInlineDollar: true, ParseInlineParentheses: true} testcases := []struct { testcase string expected string @@ -69,7 +72,7 @@ func TestMathRender(t *testing.T) { }, { "$$a$$", - `a` + nl, + `a` + nl, }, { "$$a$$ test", @@ -111,6 +114,7 @@ func TestMathRender(t *testing.T) { } func TestMathRenderBlockIndent(t *testing.T) { + setting.Markdown.MathCodeBlockOptions = setting.MarkdownMathCodeBlockOptions{ParseBlockDollar: true, ParseBlockSquareBrackets: true} testcases := []struct { name string testcase string @@ -243,3 +247,64 @@ x }) } } + +func TestMathRenderOptions(t *testing.T) { + setting.Markdown.MathCodeBlockOptions = setting.MarkdownMathCodeBlockOptions{} + defer test.MockVariableValue(&setting.Markdown.MathCodeBlockOptions) + test := func(t *testing.T, expected, input string) { + res, err := RenderString(markup.NewTestRenderContext(), input) + assert.NoError(t, err) + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(res)), "input: %s", input) + } + + // default (non-conflict) inline syntax + test(t, `a`, "$`a`$") + + // ParseInlineDollar + test(t, `$a$`, `$a$`) + setting.Markdown.MathCodeBlockOptions.ParseInlineDollar = true + test(t, `a`, `$a$`) + + // ParseInlineParentheses + test(t, `(a)`, `\(a\)`) + setting.Markdown.MathCodeBlockOptions.ParseInlineParentheses = true + test(t, `a`, `\(a\)`) + + // ParseBlockDollar + test(t, `$$ +a +$$ +`, ` +$$ +a +$$ +`) + setting.Markdown.MathCodeBlockOptions.ParseBlockDollar = true + test(t, ` +a + +`, ` +$$ +a +$$ +`) + + // ParseBlockSquareBrackets + test(t, `[ +a +] +`, ` +\[ +a +\] +`) + setting.Markdown.MathCodeBlockOptions.ParseBlockSquareBrackets = true + test(t, ` +a + +`, ` +\[ +a +\] +`) +} diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index 7a09be8665..2310895fc3 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -308,12 +308,12 @@ func TestRenderSiblingImages_Issue12925(t *testing.T) { testcase := `  ` - expected := ` - + expected := ` + ` - res, err := markdown.RenderRawString(markup.NewTestRenderContext(), testcase) + res, err := markdown.RenderString(markup.NewTestRenderContext(), testcase) assert.NoError(t, err) - assert.Equal(t, expected, res) + assert.Equal(t, expected, string(res)) } func TestRenderEmojiInLinks_Issue12331(t *testing.T) { @@ -383,18 +383,74 @@ func TestColorPreview(t *testing.T) { } } -func TestTaskList(t *testing.T) { +func TestMarkdownFrontmatter(t *testing.T) { testcases := []struct { - testcase string + name string + input string expected string }{ + { + "MapInFrontmatter", + `--- +key1: val1 +key2: val2 +--- +test +`, + `octicon-table(12/) key1, key2 + + +key1 +key2 + + + + +val1 +val2 + + + +test +`, + }, + + { + "ListInFrontmatter", + `--- +- item1 +- item2 +--- +test +`, + `- item1 +- item2 + +test +`, + }, + + { + "StringInFrontmatter", + `--- +anything +--- +test +`, + `anything + +test +`, + }, + { // data-source-position should take into account YAML frontmatter. + "ListAfterFrontmatter", `--- foo: bar --- - [ ] task 1`, - ` + `octicon-table(12/) foo foo @@ -414,9 +470,9 @@ foo: bar } for _, test := range testcases { - res, err := markdown.RenderString(markup.NewTestRenderContext(), test.testcase) - assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase) - assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase) + res, err := markdown.RenderString(markup.NewTestRenderContext(), test.input) + assert.NoError(t, err, "Unexpected error in testcase: %q", test.name) + assert.Equal(t, test.expected, string(res), "Unexpected result in testcase %q", test.name) } } @@ -473,3 +529,16 @@ space assert.NoError(t, err) assert.Equal(t, expected, string(result)) } + +func TestMarkdownLink(t *testing.T) { + defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)() + input := `link1 +link2 +link3` + result, err := markdown.RenderString(markup.NewTestRenderContext("/base", localMetas), input) + assert.NoError(t, err) + assert.Equal(t, `link1 +link2 +link3 +`, string(result)) +} diff --git a/modules/markup/markdown/math/inline_parser.go b/modules/markup/markdown/math/inline_parser.go index a57abe9f9b..a711d1e1cd 100644 --- a/modules/markup/markdown/math/inline_parser.go +++ b/modules/markup/markdown/math/inline_parser.go @@ -15,26 +15,26 @@ type inlineParser struct { trigger []byte endBytesSingleDollar []byte endBytesDoubleDollar []byte - endBytesBracket []byte + endBytesParentheses []byte + enableInlineDollar bool } -var defaultInlineDollarParser = &inlineParser{ - trigger: []byte{'$'}, - endBytesSingleDollar: []byte{'$'}, - endBytesDoubleDollar: []byte{'$', '$'}, +func NewInlineDollarParser(enableInlineDollar bool) parser.InlineParser { + return &inlineParser{ + trigger: []byte{'$'}, + endBytesSingleDollar: []byte{'$'}, + endBytesDoubleDollar: []byte{'$', '$'}, + enableInlineDollar: enableInlineDollar, + } } -func NewInlineDollarParser() parser.InlineParser { - return defaultInlineDollarParser +var defaultInlineParenthesesParser = &inlineParser{ + trigger: []byte{'\\', '('}, + endBytesParentheses: []byte{'\\', ')'}, } -var defaultInlineBracketParser = &inlineParser{ - trigger: []byte{'\\', '('}, - endBytesBracket: []byte{'\\', ')'}, -} - -func NewInlineBracketParser() parser.InlineParser { - return defaultInlineBracketParser +func NewInlineParenthesesParser() parser.InlineParser { + return defaultInlineParenthesesParser } // Trigger triggers this parser on $ or \ @@ -46,7 +46,7 @@ func isPunctuation(b byte) bool { return b == '.' || b == '!' || b == '?' || b == ',' || b == ';' || b == ':' } -func isBracket(b byte) bool { +func isParenthesesClose(b byte) bool { return b == ')' } @@ -70,10 +70,11 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser. startMarkLen = 1 stopMark = parser.endBytesSingleDollar if len(line) > 1 { - if line[1] == '$' { + switch line[1] { + case '$': startMarkLen = 2 stopMark = parser.endBytesDoubleDollar - } else if line[1] == '`' { + case '`': pos := 1 for ; pos < len(line) && line[pos] == '`'; pos++ { } @@ -85,7 +86,11 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser. } } else { startMarkLen = 2 - stopMark = parser.endBytesBracket + stopMark = parser.endBytesParentheses + } + + if line[0] == '$' && !parser.enableInlineDollar && (len(line) == 1 || line[1] != '`') { + return nil } if checkSurrounding { @@ -109,7 +114,7 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser. succeedingCharacter = line[i+len(stopMark)] } // check valid ending character - isValidEndingChar := isPunctuation(succeedingCharacter) || isBracket(succeedingCharacter) || + isValidEndingChar := isPunctuation(succeedingCharacter) || isParenthesesClose(succeedingCharacter) || succeedingCharacter == ' ' || succeedingCharacter == '\n' || succeedingCharacter == 0 if checkSurrounding && !isValidEndingChar { break @@ -121,9 +126,10 @@ func (parser *inlineParser) Parse(parent ast.Node, block text.Reader, pc parser. i++ continue } - if line[i] == '{' { + switch line[i] { + case '{': depth++ - } else if line[i] == '}' { + case '}': depth-- } } diff --git a/modules/markup/markdown/math/math.go b/modules/markup/markdown/math/math.go index a6ff593d62..4b74db2d76 100644 --- a/modules/markup/markdown/math/math.go +++ b/modules/markup/markdown/math/math.go @@ -14,10 +14,11 @@ import ( ) type Options struct { - Enabled bool - ParseDollarInline bool - ParseDollarBlock bool - ParseSquareBlock bool + Enabled bool + ParseInlineDollar bool // inline $$ xxx $$ text + ParseInlineParentheses bool // inline \( xxx \) text + ParseBlockDollar bool // block $$ multiple-line $$ text + ParseBlockSquareBrackets bool // block \[ multiple-line \] text } // Extension is a math extension @@ -42,16 +43,16 @@ func (e *Extension) Extend(m goldmark.Markdown) { return } - inlines := []util.PrioritizedValue{util.Prioritized(NewInlineBracketParser(), 501)} - if e.options.ParseDollarInline { - inlines = append(inlines, util.Prioritized(NewInlineDollarParser(), 502)) + var inlines []util.PrioritizedValue + if e.options.ParseInlineParentheses { + inlines = append(inlines, util.Prioritized(NewInlineParenthesesParser(), 501)) } + inlines = append(inlines, util.Prioritized(NewInlineDollarParser(e.options.ParseInlineDollar), 502)) + m.Parser().AddOptions(parser.WithInlineParsers(inlines...)) - m.Parser().AddOptions(parser.WithBlockParsers( - util.Prioritized(NewBlockParser(e.options.ParseDollarBlock, e.options.ParseSquareBlock), 701), + util.Prioritized(NewBlockParser(e.options.ParseBlockDollar, e.options.ParseBlockSquareBrackets), 701), )) - m.Renderer().AddOptions(renderer.WithNodeRenderers( util.Prioritized(NewBlockRenderer(e.renderInternal), 501), util.Prioritized(NewInlineRenderer(e.renderInternal), 502), diff --git a/modules/markup/markdown/meta_test.go b/modules/markup/markdown/meta_test.go index 278c33f1d2..3f74adeaef 100644 --- a/modules/markup/markdown/meta_test.go +++ b/modules/markup/markdown/meta_test.go @@ -51,7 +51,7 @@ func TestExtractMetadata(t *testing.T) { var meta IssueTemplate body, err := ExtractMetadata(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest), &meta) assert.NoError(t, err) - assert.Equal(t, "", body) + assert.Empty(t, body) assert.Equal(t, metaTest, meta) assert.True(t, meta.Valid()) }) @@ -83,7 +83,7 @@ func TestExtractMetadataBytes(t *testing.T) { var meta IssueTemplate body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest)), &meta) assert.NoError(t, err) - assert.Equal(t, "", string(body)) + assert.Empty(t, string(body)) assert.Equal(t, metaTest, meta) assert.True(t, meta.Valid()) }) diff --git a/modules/markup/markdown/renderconfig.go b/modules/markup/markdown/renderconfig.go index f4c48d1b3d..d8b1b10ce6 100644 --- a/modules/markup/markdown/renderconfig.go +++ b/modules/markup/markdown/renderconfig.go @@ -16,7 +16,6 @@ import ( // RenderConfig represents rendering configuration for this file type RenderConfig struct { Meta markup.RenderMetaMode - Icon string TOC string // "false": hide, "side"/empty: in sidebar, "main"/"true": in main view Lang string yamlNode *yaml.Node @@ -74,7 +73,7 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error { type yamlRenderConfig struct { Meta *string `yaml:"meta"` - Icon *string `yaml:"details_icon"` + Icon *string `yaml:"details_icon"` // deprecated, because there is no font icon, so no custom icon TOC *string `yaml:"include_toc"` Lang *string `yaml:"lang"` } @@ -96,10 +95,6 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error { rc.Meta = renderMetaModeFromString(*cfg.Gitea.Meta) } - if cfg.Gitea.Icon != nil { - rc.Icon = strings.TrimSpace(strings.ToLower(*cfg.Gitea.Icon)) - } - if cfg.Gitea.Lang != nil && *cfg.Gitea.Lang != "" { rc.Lang = *cfg.Gitea.Lang } @@ -111,7 +106,7 @@ func (rc *RenderConfig) UnmarshalYAML(value *yaml.Node) error { return nil } -func (rc *RenderConfig) toMetaNode() ast.Node { +func (rc *RenderConfig) toMetaNode(g *ASTTransformer) ast.Node { if rc.yamlNode == nil { return nil } @@ -119,7 +114,7 @@ func (rc *RenderConfig) toMetaNode() ast.Node { case markup.RenderMetaAsTable: return nodeToTable(rc.yamlNode) case markup.RenderMetaAsDetails: - return nodeToDetails(rc.yamlNode, rc.Icon) + return nodeToDetails(g, rc.yamlNode) default: return nil } diff --git a/modules/markup/markdown/renderconfig_test.go b/modules/markup/markdown/renderconfig_test.go index 13346570fa..53c52177a7 100644 --- a/modules/markup/markdown/renderconfig_test.go +++ b/modules/markup/markdown/renderconfig_test.go @@ -21,42 +21,36 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) { { "empty", &RenderConfig{ Meta: "table", - Icon: "table", Lang: "", }, "", }, { "lang", &RenderConfig{ Meta: "table", - Icon: "table", Lang: "test", }, "lang: test", }, { "metatable", &RenderConfig{ Meta: "table", - Icon: "table", Lang: "", }, "gitea: table", }, { "metanone", &RenderConfig{ Meta: "none", - Icon: "table", Lang: "", }, "gitea: none", }, { "metadetails", &RenderConfig{ Meta: "details", - Icon: "table", Lang: "", }, "gitea: details", }, { "metawrong", &RenderConfig{ Meta: "details", - Icon: "table", Lang: "", }, "gitea: wrong", }, @@ -64,7 +58,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) { "toc", &RenderConfig{ TOC: "true", Meta: "table", - Icon: "table", Lang: "", }, "include_toc: true", }, @@ -72,14 +65,12 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) { "tocfalse", &RenderConfig{ TOC: "false", Meta: "table", - Icon: "table", Lang: "", }, "include_toc: false", }, { "toclang", &RenderConfig{ Meta: "table", - Icon: "table", TOC: "true", Lang: "testlang", }, ` @@ -90,7 +81,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) { { "complexlang", &RenderConfig{ Meta: "table", - Icon: "table", Lang: "testlang", }, ` gitea: @@ -100,7 +90,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) { { "complexlang2", &RenderConfig{ Meta: "table", - Icon: "table", Lang: "testlang", }, ` lang: notright @@ -111,7 +100,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) { { "complexlang", &RenderConfig{ Meta: "table", - Icon: "table", Lang: "testlang", }, ` gitea: @@ -123,7 +111,6 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) { Lang: "two", Meta: "table", TOC: "true", - Icon: "smiley", }, ` lang: one include_toc: true @@ -139,14 +126,12 @@ func TestRenderConfig_UnmarshalYAML(t *testing.T) { t.Run(tt.name, func(t *testing.T) { got := &RenderConfig{ Meta: "table", - Icon: "table", Lang: "", } err := yaml.Unmarshal([]byte(strings.ReplaceAll(tt.args, "\t", " ")), got) require.NoError(t, err) assert.Equal(t, tt.expected.Meta, got.Meta) - assert.Equal(t, tt.expected.Icon, got.Icon) assert.Equal(t, tt.expected.Lang, got.Lang) assert.Equal(t, tt.expected.TOC, got.TOC) }) diff --git a/modules/markup/markdown/toc.go b/modules/markup/markdown/toc.go index ea1af83a3e..a11b9d0390 100644 --- a/modules/markup/markdown/toc.go +++ b/modules/markup/markdown/toc.go @@ -4,7 +4,6 @@ package markdown import ( - "fmt" "net/url" "code.gitea.io/gitea/modules/translation" @@ -50,7 +49,7 @@ func createTOCNode(toc []Header, lang string, detailsAttrs map[string]string) as } li := ast.NewListItem(currentLevel * 2) a := ast.NewLink() - a.Destination = []byte(fmt.Sprintf("#%s", url.QueryEscape(header.ID))) + a.Destination = []byte("#" + url.QueryEscape(header.ID)) a.AppendChild(a, ast.NewString([]byte(header.Text))) li.AppendChild(li, a) ul.AppendChild(ul, li) diff --git a/modules/markup/markdown/transform_image.go b/modules/markup/markdown/transform_image.go deleted file mode 100644 index 36512e59a8..0000000000 --- a/modules/markup/markdown/transform_image.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2024 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package markdown - -import ( - "code.gitea.io/gitea/modules/markup" - - "github.com/yuin/goldmark/ast" -) - -func (g *ASTTransformer) transformImage(ctx *markup.RenderContext, v *ast.Image) { - // Images need two things: - // - // 1. Their src needs to munged to be a real value - // 2. If they're not wrapped with a link they need a link wrapper - - // Check if the destination is a real link - if len(v.Destination) > 0 && !markup.IsFullURLBytes(v.Destination) { - v.Destination = []byte(ctx.RenderHelper.ResolveLink(string(v.Destination), markup.LinkTypeMedia)) - } - - parent := v.Parent() - // Create a link around image only if parent is not already a link - if _, ok := parent.(*ast.Link); !ok && parent != nil { - next := v.NextSibling() - - // Create a link wrapper - wrap := ast.NewLink() - wrap.Destination = v.Destination - wrap.Title = v.Title - wrap.SetAttributeString("target", []byte("_blank")) - - // Duplicate the current image node - image := ast.NewImage(ast.NewLink()) - image.Destination = v.Destination - image.Title = v.Title - for _, attr := range v.Attributes() { - image.SetAttribute(attr.Name, attr.Value) - } - for child := v.FirstChild(); child != nil; { - next := child.NextSibling() - image.AppendChild(image, child) - child = next - } - - // Append our duplicate image to the wrapper link - wrap.AppendChild(wrap, image) - - // Wire in the next sibling - wrap.SetNextSibling(next) - - // Replace the current node with the wrapper link - parent.ReplaceChild(parent, v, wrap) - - // But most importantly ensure the next sibling is still on the old image too - v.SetNextSibling(next) - } -} diff --git a/modules/markup/markdown/transform_link.go b/modules/markup/markdown/transform_link.go deleted file mode 100644 index 51c2c915d8..0000000000 --- a/modules/markup/markdown/transform_link.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2024 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package markdown - -import ( - "code.gitea.io/gitea/modules/markup" - - "github.com/yuin/goldmark/ast" -) - -func resolveLink(ctx *markup.RenderContext, link, userContentAnchorPrefix string) (result string, resolved bool) { - isAnchorFragment := link != "" && link[0] == '#' - if !isAnchorFragment && !markup.IsFullURLString(link) { - link, resolved = ctx.RenderHelper.ResolveLink(link, markup.LinkTypeDefault), true - } - if isAnchorFragment && userContentAnchorPrefix != "" { - link, resolved = userContentAnchorPrefix+link[1:], true - } - return link, resolved -} - -func (g *ASTTransformer) transformLink(ctx *markup.RenderContext, v *ast.Link) { - if link, resolved := resolveLink(ctx, string(v.Destination), "#user-content-"); resolved { - v.Destination = []byte(link) - } -} diff --git a/modules/markup/mdstripper/mdstripper.go b/modules/markup/mdstripper/mdstripper.go index fe0eabb473..c589926b5e 100644 --- a/modules/markup/mdstripper/mdstripper.go +++ b/modules/markup/mdstripper/mdstripper.go @@ -107,11 +107,12 @@ func (r *stripRenderer) processAutoLink(w io.Writer, link []byte) { } var sep string - if parts[3] == "issues" { + switch parts[3] { + case "issues": sep = "#" - } else if parts[3] == "pulls" { + case "pulls": sep = "!" - } else { + default: // Process out of band r.links = append(r.links, linkStr) return diff --git a/modules/markup/mdstripper/mdstripper_test.go b/modules/markup/mdstripper/mdstripper_test.go index ea34df0a3b..7fb49c1e01 100644 --- a/modules/markup/mdstripper/mdstripper_test.go +++ b/modules/markup/mdstripper/mdstripper_test.go @@ -79,7 +79,7 @@ A HIDDEN ` + "`" + `GHOST` + "`" + ` IN THIS LINE. lines = append(lines, line) } } - assert.EqualValues(t, test.expectedText, lines) - assert.EqualValues(t, test.expectedLinks, links) + assert.Equal(t, test.expectedText, lines) + assert.Equal(t, test.expectedLinks, links) } } diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go index 70d02c1321..93c335d244 100644 --- a/modules/markup/orgmode/orgmode.go +++ b/modules/markup/orgmode/orgmode.go @@ -1,7 +1,7 @@ // Copyright 2017 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package markup +package orgmode import ( "fmt" @@ -125,27 +125,13 @@ type orgWriter struct { var _ org.Writer = (*orgWriter)(nil) -func (r *orgWriter) resolveLink(kind, link string) string { - link = strings.TrimPrefix(link, "file:") - if !strings.HasPrefix(link, "#") && // not a URL fragment - !markup.IsFullURLString(link) { - if kind == "regular" { - // orgmode reports the link kind as "regular" for "[[ImageLink.svg][The Image Desc]]" - // so we need to try to guess the link kind again here - kind = org.RegularLink{URL: link}.Kind() - } - if kind == "image" || kind == "video" { - link = r.rctx.RenderHelper.ResolveLink(link, markup.LinkTypeMedia) - } else { - link = r.rctx.RenderHelper.ResolveLink(link, markup.LinkTypeDefault) - } - } - return link +func (r *orgWriter) resolveLink(link string) string { + return strings.TrimPrefix(link, "file:") } // WriteRegularLink renders images, links or videos func (r *orgWriter) WriteRegularLink(l org.RegularLink) { - link := r.resolveLink(l.Kind(), l.URL) + link := r.resolveLink(l.URL) printHTML := func(html template.HTML, a ...any) { _, _ = fmt.Fprint(r, htmlutil.HTMLFormat(html, a...)) @@ -156,14 +142,14 @@ func (r *orgWriter) WriteRegularLink(l org.RegularLink) { if l.Description == nil { printHTML(``, link, link) } else { - imageSrc := r.resolveLink(l.Kind(), org.String(l.Description...)) + imageSrc := r.resolveLink(org.String(l.Description...)) printHTML(``, link, imageSrc, imageSrc) } case "video": if l.Description == nil { printHTML(`%s`, link, link) } else { - videoSrc := r.resolveLink(l.Kind(), org.String(l.Description...)) + videoSrc := r.resolveLink(org.String(l.Description...)) printHTML(`%s`, link, videoSrc, videoSrc) } default: diff --git a/modules/markup/orgmode/orgmode_test.go b/modules/markup/orgmode/orgmode_test.go index de39bafebe..df4bb38ad1 100644 --- a/modules/markup/orgmode/orgmode_test.go +++ b/modules/markup/orgmode/orgmode_test.go @@ -1,7 +1,7 @@ // Copyright 2017 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package markup +package orgmode_test import ( "os" @@ -9,6 +9,7 @@ import ( "testing" "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/markup/orgmode" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" @@ -22,7 +23,7 @@ func TestMain(m *testing.M) { func TestRender_StandardLinks(t *testing.T) { test := func(input, expected string) { - buffer, err := RenderString(markup.NewTestRenderContext("/relative-path/media/branch/main/"), input) + buffer, err := orgmode.RenderString(markup.NewTestRenderContext(), input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } @@ -30,37 +31,37 @@ func TestRender_StandardLinks(t *testing.T) { test("[[https://google.com/]]", `https://google.com/`) test("[[ImageLink.svg][The Image Desc]]", - `The Image Desc`) + `The Image Desc`) } func TestRender_InternalLinks(t *testing.T) { test := func(input, expected string) { - buffer, err := RenderString(markup.NewTestRenderContext("/relative-path/src/branch/main"), input) + buffer, err := orgmode.RenderString(markup.NewTestRenderContext(), input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } test("[[file:test.org][Test]]", - `Test`) + `Test`) test("[[./test.org][Test]]", - `Test`) + `Test`) test("[[test.org][Test]]", - `Test`) + `Test`) test("[[path/to/test.org][Test]]", - `Test`) + `Test`) } func TestRender_Media(t *testing.T) { test := func(input, expected string) { - buffer, err := RenderString(markup.NewTestRenderContext("./relative-path"), input) + buffer, err := orgmode.RenderString(markup.NewTestRenderContext(), input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } test("[[file:../../.images/src/02/train.jpg]]", - ``) + ``) test("[[file:train.jpg]]", - ``) + ``) // With description. test("[[https://example.com][https://example.com/example.svg]]", @@ -91,7 +92,7 @@ func TestRender_Media(t *testing.T) { func TestRender_Source(t *testing.T) { test := func(input, expected string) { - buffer, err := RenderString(markup.NewTestRenderContext(), input) + buffer, err := orgmode.RenderString(markup.NewTestRenderContext(), input) assert.NoError(t, err) assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) } diff --git a/modules/markup/render.go b/modules/markup/render.go index 37a2a86687..79f1f473c2 100644 --- a/modules/markup/render.go +++ b/modules/markup/render.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "net/url" + "strconv" "strings" "time" @@ -46,7 +47,7 @@ type RenderOptions struct { // user&repo, format&style®exp (for external issue pattern), teams&org (for mention) // RefTypeNameSubURL (for iframe&asciicast) // markupAllowShortIssuePattern - // markdownLineBreakStyle (comment, document) + // markdownNewLineHardBreak Metas map[string]string // used by external render. the router "/org/repo/render/..." will output the rendered content in a standalone page @@ -247,7 +248,8 @@ func Init(renderHelpFuncs *RenderHelperFuncs) { } func ComposeSimpleDocumentMetas() map[string]string { - return map[string]string{"markdownLineBreakStyle": "document"} + // TODO: there is no separate config option for "simple document" rendering, so temporarily use the same config as "repo file" + return map[string]string{"markdownNewLineHardBreak": strconv.FormatBool(setting.Markdown.RenderOptionsRepoFile.NewLineHardBreak)} } type TestRenderHelper struct { @@ -261,8 +263,14 @@ func (r *TestRenderHelper) IsCommitIDExisting(commitID string) bool { return strings.HasPrefix(commitID, "65f1bf2") //|| strings.HasPrefix(commitID, "88fc37a") } -func (r *TestRenderHelper) ResolveLink(link string, likeType LinkType) string { - return r.ctx.ResolveLinkRelative(r.BaseLink, "", link) +func (r *TestRenderHelper) ResolveLink(link, preferLinkType string) string { + linkType, link := ParseRenderedLink(link, preferLinkType) + switch linkType { + case LinkTypeRoot: + return r.ctx.ResolveLinkRoot(link) + default: + return r.ctx.ResolveLinkRelative(r.BaseLink, "", link) + } } var _ RenderHelper = (*TestRenderHelper)(nil) diff --git a/modules/markup/render_helper.go b/modules/markup/render_helper.go index 8ff0e7d6fb..b16f1189c5 100644 --- a/modules/markup/render_helper.go +++ b/modules/markup/render_helper.go @@ -10,13 +10,11 @@ import ( "code.gitea.io/gitea/modules/setting" ) -type LinkType string - const ( - LinkTypeApp LinkType = "app" // the link is relative to the AppSubURL - LinkTypeDefault LinkType = "default" // the link is relative to the default base (eg: repo link, or current ref tree path) - LinkTypeMedia LinkType = "media" // the link should be used to access media files (images, videos) - LinkTypeRaw LinkType = "raw" // not really useful, mainly for environment GITEA_PREFIX_RAW for external renders + LinkTypeDefault = "" + LinkTypeRoot = "/:root" // the link is relative to the AppSubURL(ROOT_URL) + LinkTypeMedia = "/:media" // the link should be used to access media files (images, videos) + LinkTypeRaw = "/:raw" // not really useful, mainly for environment GITEA_PREFIX_RAW for external renders ) type RenderHelper interface { @@ -27,7 +25,7 @@ type RenderHelper interface { // but not make processors to guess "is it rendering a comment or a wiki?" or "does it need to check commit ID?" IsCommitIDExisting(commitID string) bool - ResolveLink(link string, likeType LinkType) string + ResolveLink(link, preferLinkType string) string } // RenderHelperFuncs is used to decouple cycle-import @@ -51,7 +49,8 @@ func (r *SimpleRenderHelper) IsCommitIDExisting(commitID string) bool { return false } -func (r *SimpleRenderHelper) ResolveLink(link string, likeType LinkType) string { +func (r *SimpleRenderHelper) ResolveLink(link, preferLinkType string) string { + _, link = ParseRenderedLink(link, preferLinkType) return resolveLinkRelative(context.Background(), setting.AppSubURL+"/", "", link, false) } diff --git a/modules/markup/render_link.go b/modules/markup/render_link.go index b2e0699681..046544ce81 100644 --- a/modules/markup/render_link.go +++ b/modules/markup/render_link.go @@ -33,10 +33,24 @@ func resolveLinkRelative(ctx context.Context, base, cur, link string, absolute b return finalLink } -func (ctx *RenderContext) ResolveLinkRelative(base, cur, link string) (finalLink string) { +func (ctx *RenderContext) ResolveLinkRelative(base, cur, link string) string { + if strings.HasPrefix(link, "/:") { + setting.PanicInDevOrTesting("invalid link %q, forgot to cut?", link) + } return resolveLinkRelative(ctx, base, cur, link, ctx.RenderOptions.UseAbsoluteLink) } -func (ctx *RenderContext) ResolveLinkApp(link string) string { +func (ctx *RenderContext) ResolveLinkRoot(link string) string { return ctx.ResolveLinkRelative(setting.AppSubURL+"/", "", link) } + +func ParseRenderedLink(s, preferLinkType string) (linkType, link string) { + if strings.HasPrefix(s, "/:") { + p := strings.IndexByte(s[1:], '/') + if p == -1 { + return s, "" + } + return s[:p+1], s[p+2:] + } + return preferLinkType, s +} diff --git a/modules/optional/serialization_test.go b/modules/optional/serialization_test.go index 09a4bddea0..21d3ad8470 100644 --- a/modules/optional/serialization_test.go +++ b/modules/optional/serialization_test.go @@ -51,11 +51,11 @@ func TestOptionalToJson(t *testing.T) { t.Run(tc.name, func(t *testing.T) { b, err := json.Marshal(tc.obj) assert.NoError(t, err) - assert.EqualValues(t, tc.want, string(b), "gitea json module returned unexpected") + assert.Equal(t, tc.want, string(b), "gitea json module returned unexpected") b, err = std_json.Marshal(tc.obj) assert.NoError(t, err) - assert.EqualValues(t, tc.want, string(b), "std json module returned unexpected") + assert.Equal(t, tc.want, string(b), "std json module returned unexpected") }) } } @@ -89,12 +89,12 @@ func TestOptionalFromJson(t *testing.T) { var obj1 testSerializationStruct err := json.Unmarshal([]byte(tc.data), &obj1) assert.NoError(t, err) - assert.EqualValues(t, tc.want, obj1, "gitea json module returned unexpected") + assert.Equal(t, tc.want, obj1, "gitea json module returned unexpected") var obj2 testSerializationStruct err = std_json.Unmarshal([]byte(tc.data), &obj2) assert.NoError(t, err) - assert.EqualValues(t, tc.want, obj2, "std json module returned unexpected") + assert.Equal(t, tc.want, obj2, "std json module returned unexpected") }) } } @@ -135,7 +135,7 @@ optional_two_string: null t.Run(tc.name, func(t *testing.T) { b, err := yaml.Marshal(tc.obj) assert.NoError(t, err) - assert.EqualValues(t, tc.want, string(b), "yaml module returned unexpected") + assert.Equal(t, tc.want, string(b), "yaml module returned unexpected") }) } } @@ -184,7 +184,7 @@ optional_twostring: null var obj testSerializationStruct err := yaml.Unmarshal([]byte(tc.data), &obj) assert.NoError(t, err) - assert.EqualValues(t, tc.want, obj, "yaml module returned unexpected") + assert.Equal(t, tc.want, obj, "yaml module returned unexpected") }) } } diff --git a/modules/packages/goproxy/metadata.go b/modules/packages/goproxy/metadata.go index 40f7d20508..a67b149f4d 100644 --- a/modules/packages/goproxy/metadata.go +++ b/modules/packages/goproxy/metadata.go @@ -5,7 +5,6 @@ package goproxy import ( "archive/zip" - "fmt" "io" "path" "strings" @@ -88,7 +87,7 @@ func ParsePackage(r io.ReaderAt, size int64) (*Package, error) { return nil, ErrInvalidStructure } - p.GoMod = fmt.Sprintf("module %s", p.Name) + p.GoMod = "module " + p.Name return p, nil } diff --git a/modules/packages/hashed_buffer.go b/modules/packages/hashed_buffer.go index 4ab45edcec..0cd657cd44 100644 --- a/modules/packages/hashed_buffer.go +++ b/modules/packages/hashed_buffer.go @@ -6,6 +6,7 @@ package packages import ( "io" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util/filebuffer" ) @@ -34,11 +35,11 @@ func NewHashedBuffer() (*HashedBuffer, error) { // NewHashedBufferWithSize creates a hashed buffer with a specific memory size func NewHashedBufferWithSize(maxMemorySize int) (*HashedBuffer, error) { - b, err := filebuffer.New(maxMemorySize) + tempDir, err := setting.AppDataTempDir("package-hashed-buffer").MkdirAllSub("") if err != nil { return nil, err } - + b := filebuffer.New(maxMemorySize, tempDir) hash := NewMultiHasher() combinedWriter := io.MultiWriter(b, hash) diff --git a/modules/packages/hashed_buffer_test.go b/modules/packages/hashed_buffer_test.go index 564e782f18..5104c1fb25 100644 --- a/modules/packages/hashed_buffer_test.go +++ b/modules/packages/hashed_buffer_test.go @@ -9,10 +9,13 @@ import ( "strings" "testing" + "code.gitea.io/gitea/modules/setting" + "github.com/stretchr/testify/assert" ) func TestHashedBuffer(t *testing.T) { + setting.AppDataPath = t.TempDir() cases := []struct { MaxMemorySize int Data string diff --git a/modules/packages/nuget/symbol_extractor_test.go b/modules/packages/nuget/symbol_extractor_test.go index fa1b80ee82..711ad6d096 100644 --- a/modules/packages/nuget/symbol_extractor_test.go +++ b/modules/packages/nuget/symbol_extractor_test.go @@ -9,6 +9,8 @@ import ( "encoding/base64" "testing" + "code.gitea.io/gitea/modules/setting" + "github.com/stretchr/testify/assert" ) @@ -17,6 +19,7 @@ fgAA3AEAAAQAAAAjU3RyaW5ncwAAAADgAQAABAAAACNVUwDkAQAAMAAAACNHVUlEAAAAFAIAACgB AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==` func TestExtractPortablePdb(t *testing.T) { + setting.AppDataPath = t.TempDir() createArchive := func(name string, content []byte) []byte { var buf bytes.Buffer archive := zip.NewWriter(&buf) diff --git a/modules/private/internal.go b/modules/private/internal.go index 35eed1d608..e599c6eb8e 100644 --- a/modules/private/internal.go +++ b/modules/private/internal.go @@ -6,7 +6,6 @@ package private import ( "context" "crypto/tls" - "fmt" "net" "net/http" "os" @@ -47,7 +46,7 @@ Ensure you are running in the correct environment or set the correct configurati req := httplib.NewRequest(url, method). SetContext(ctx). Header("X-Real-IP", getClientIP()). - Header("X-Gitea-Internal-Auth", fmt.Sprintf("Bearer %s", setting.InternalToken)). + Header("X-Gitea-Internal-Auth", "Bearer "+setting.InternalToken). SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true, ServerName: setting.Domain, diff --git a/modules/private/serv.go b/modules/private/serv.go index 2ccc6c1129..10e9f7995c 100644 --- a/modules/private/serv.go +++ b/modules/private/serv.go @@ -55,7 +55,7 @@ func ServCommand(ctx context.Context, keyID int64, ownerName, repoName string, m ) for _, verb := range verbs { if verb != "" { - reqURL += fmt.Sprintf("&verb=%s", url.QueryEscape(verb)) + reqURL += "&verb=" + url.QueryEscape(verb) } } req := newInternalRequestAPI(ctx, reqURL, "GET") diff --git a/modules/proxyprotocol/errors.go b/modules/proxyprotocol/errors.go index 5439a86bd8..f76c82b7f6 100644 --- a/modules/proxyprotocol/errors.go +++ b/modules/proxyprotocol/errors.go @@ -20,7 +20,7 @@ type ErrBadAddressType struct { } func (e *ErrBadAddressType) Error() string { - return fmt.Sprintf("Unexpected proxy header address type: %s", e.Address) + return "Unexpected proxy header address type: " + e.Address } // ErrBadRemote is an error demonstrating a bad proxy header with bad Remote diff --git a/modules/queue/base_test.go b/modules/queue/base_test.go index 73abf49091..1a96ac1e1d 100644 --- a/modules/queue/base_test.go +++ b/modules/queue/base_test.go @@ -21,7 +21,7 @@ func testQueueBasic(t *testing.T, newFn func(cfg *BaseConfig) (baseQueue, error) _ = q.RemoveAll(ctx) cnt, err := q.Len(ctx) assert.NoError(t, err) - assert.EqualValues(t, 0, cnt) + assert.Equal(t, 0, cnt) // push the first item err = q.PushItem(ctx, []byte("foo")) @@ -29,7 +29,7 @@ func testQueueBasic(t *testing.T, newFn func(cfg *BaseConfig) (baseQueue, error) cnt, err = q.Len(ctx) assert.NoError(t, err) - assert.EqualValues(t, 1, cnt) + assert.Equal(t, 1, cnt) // push a duplicate item err = q.PushItem(ctx, []byte("foo")) @@ -45,10 +45,10 @@ func testQueueBasic(t *testing.T, newFn func(cfg *BaseConfig) (baseQueue, error) has, err := q.HasItem(ctx, []byte("foo")) assert.NoError(t, err) if !isUnique { - assert.EqualValues(t, 2, cnt) + assert.Equal(t, 2, cnt) assert.False(t, has) // non-unique queues don't check for duplicates } else { - assert.EqualValues(t, 1, cnt) + assert.Equal(t, 1, cnt) assert.True(t, has) } @@ -59,18 +59,18 @@ func testQueueBasic(t *testing.T, newFn func(cfg *BaseConfig) (baseQueue, error) // pop the first item (and the duplicate if non-unique) it, err := q.PopItem(ctx) assert.NoError(t, err) - assert.EqualValues(t, "foo", string(it)) + assert.Equal(t, "foo", string(it)) if !isUnique { it, err = q.PopItem(ctx) assert.NoError(t, err) - assert.EqualValues(t, "foo", string(it)) + assert.Equal(t, "foo", string(it)) } // pop another item it, err = q.PopItem(ctx) assert.NoError(t, err) - assert.EqualValues(t, "bar", string(it)) + assert.Equal(t, "bar", string(it)) // pop an empty queue (timeout, cancel) ctxTimed, cancel := context.WithTimeout(ctx, 10*time.Millisecond) @@ -107,13 +107,13 @@ func testQueueBasic(t *testing.T, newFn func(cfg *BaseConfig) (baseQueue, error) // remove all cnt, err = q.Len(ctx) assert.NoError(t, err) - assert.EqualValues(t, cfg.Length, cnt) + assert.Equal(t, cfg.Length, cnt) _ = q.RemoveAll(ctx) cnt, err = q.Len(ctx) assert.NoError(t, err) - assert.EqualValues(t, 0, cnt) + assert.Equal(t, 0, cnt) }) } @@ -126,7 +126,7 @@ func TestBaseDummy(t *testing.T) { cnt, err := q.Len(ctx) assert.NoError(t, err) - assert.EqualValues(t, 0, cnt) + assert.Equal(t, 0, cnt) has, err := q.HasItem(ctx, []byte("foo")) assert.NoError(t, err) diff --git a/modules/queue/manager_test.go b/modules/queue/manager_test.go index f55ee85a22..fda498cc84 100644 --- a/modules/queue/manager_test.go +++ b/modules/queue/manager_test.go @@ -47,7 +47,7 @@ CONN_STR = redis:// assert.Equal(t, filepath.Join(setting.AppDataPath, "queues/common"), q.baseConfig.DataFullDir) assert.Equal(t, 100000, q.baseConfig.Length) assert.Equal(t, 20, q.batchLength) - assert.Equal(t, "", q.baseConfig.ConnStr) + assert.Empty(t, q.baseConfig.ConnStr) assert.Equal(t, "default_queue", q.baseConfig.QueueFullName) assert.Equal(t, "default_queue_unique", q.baseConfig.SetFullName) assert.NotZero(t, q.GetWorkerMaxNumber()) @@ -101,7 +101,7 @@ MAX_WORKERS = 123 assert.Equal(t, filepath.Join(setting.AppDataPath, "queues/dir2"), q2.baseConfig.DataFullDir) assert.Equal(t, 102, q2.baseConfig.Length) assert.Equal(t, 22, q2.batchLength) - assert.Equal(t, "", q2.baseConfig.ConnStr) + assert.Empty(t, q2.baseConfig.ConnStr) assert.Equal(t, "sub_q2", q2.baseConfig.QueueFullName) assert.Equal(t, "sub_q2_u2", q2.baseConfig.SetFullName) assert.Equal(t, 123, q2.GetWorkerMaxNumber()) diff --git a/modules/queue/workerqueue_test.go b/modules/queue/workerqueue_test.go index 0ca2be1d21..487c2f1a92 100644 --- a/modules/queue/workerqueue_test.go +++ b/modules/queue/workerqueue_test.go @@ -63,9 +63,9 @@ func TestWorkerPoolQueueUnhandled(t *testing.T) { ok := true for i := 0; i < queueSetting.Length; i++ { if i%2 == 0 { - ok = ok && assert.EqualValues(t, 2, m[i], "test %s: item %d", t.Name(), i) + ok = ok && assert.Equal(t, 2, m[i], "test %s: item %d", t.Name(), i) } else { - ok = ok && assert.EqualValues(t, 1, m[i], "test %s: item %d", t.Name(), i) + ok = ok && assert.Equal(t, 1, m[i], "test %s: item %d", t.Name(), i) } } if !ok { @@ -173,7 +173,7 @@ func testWorkerPoolQueuePersistence(t *testing.T, queueSetting setting.QueueSett assert.NotEmpty(t, tasksQ1) assert.NotEmpty(t, tasksQ2) - assert.EqualValues(t, testCount, len(tasksQ1)+len(tasksQ2)) + assert.Equal(t, testCount, len(tasksQ1)+len(tasksQ2)) } func TestWorkerPoolQueueActiveWorkers(t *testing.T) { @@ -191,13 +191,13 @@ func TestWorkerPoolQueueActiveWorkers(t *testing.T) { } time.Sleep(50 * time.Millisecond) - assert.EqualValues(t, 1, q.GetWorkerNumber()) - assert.EqualValues(t, 1, q.GetWorkerActiveNumber()) + assert.Equal(t, 1, q.GetWorkerNumber()) + assert.Equal(t, 1, q.GetWorkerActiveNumber()) time.Sleep(500 * time.Millisecond) - assert.EqualValues(t, 1, q.GetWorkerNumber()) - assert.EqualValues(t, 0, q.GetWorkerActiveNumber()) + assert.Equal(t, 1, q.GetWorkerNumber()) + assert.Equal(t, 0, q.GetWorkerActiveNumber()) time.Sleep(workerIdleDuration) - assert.EqualValues(t, 1, q.GetWorkerNumber()) // there is at least one worker after the queue begins working + assert.Equal(t, 1, q.GetWorkerNumber()) // there is at least one worker after the queue begins working stop() q, _ = newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: 3, Length: 100}, handler, false) @@ -207,13 +207,13 @@ func TestWorkerPoolQueueActiveWorkers(t *testing.T) { } time.Sleep(50 * time.Millisecond) - assert.EqualValues(t, 3, q.GetWorkerNumber()) - assert.EqualValues(t, 3, q.GetWorkerActiveNumber()) + assert.Equal(t, 3, q.GetWorkerNumber()) + assert.Equal(t, 3, q.GetWorkerActiveNumber()) time.Sleep(500 * time.Millisecond) - assert.EqualValues(t, 3, q.GetWorkerNumber()) - assert.EqualValues(t, 0, q.GetWorkerActiveNumber()) + assert.Equal(t, 3, q.GetWorkerNumber()) + assert.Equal(t, 0, q.GetWorkerActiveNumber()) time.Sleep(workerIdleDuration) - assert.EqualValues(t, 1, q.GetWorkerNumber()) // there is at least one worker after the queue begins working + assert.Equal(t, 1, q.GetWorkerNumber()) // there is at least one worker after the queue begins working stop() } @@ -240,13 +240,13 @@ func TestWorkerPoolQueueShutdown(t *testing.T) { } <-handlerCalled time.Sleep(200 * time.Millisecond) // wait for a while to make sure all workers are active - assert.EqualValues(t, 4, q.GetWorkerActiveNumber()) + assert.Equal(t, 4, q.GetWorkerActiveNumber()) stop() // stop triggers shutdown - assert.EqualValues(t, 0, q.GetWorkerActiveNumber()) + assert.Equal(t, 0, q.GetWorkerActiveNumber()) // no item was ever handled, so we still get all of them again q, _ = newWorkerPoolQueueForTest("test-workpoolqueue", qs, handler, false) - assert.EqualValues(t, 20, q.GetQueueItemNumber()) + assert.Equal(t, 20, q.GetQueueItemNumber()) } func TestWorkerPoolQueueWorkerIdleReset(t *testing.T) { diff --git a/modules/references/references.go b/modules/references/references.go index a5b102b7f2..592bd4cbe4 100644 --- a/modules/references/references.go +++ b/modules/references/references.go @@ -462,11 +462,12 @@ func findAllIssueReferencesBytes(content []byte, links []string) []*rawReference continue } var sep string - if parts[3] == "issues" { + switch parts[3] { + case "issues": sep = "#" - } else if parts[3] == "pulls" { + case "pulls": sep = "!" - } else { + default: continue } // Note: closing/reopening keywords not supported with URLs diff --git a/modules/references/references_test.go b/modules/references/references_test.go index 1b6a968d6a..a15ae99f79 100644 --- a/modules/references/references_test.go +++ b/modules/references/references_test.go @@ -46,7 +46,7 @@ owner/repo!123456789 contentBytes := []byte(test) convertFullHTMLReferencesToShortRefs(re, &contentBytes) result := string(contentBytes) - assert.EqualValues(t, expect, result) + assert.Equal(t, expect, result) } func TestFindAllIssueReferences(t *testing.T) { @@ -283,9 +283,9 @@ func testFixtures(t *testing.T, fixtures []testFixture, context string) { } expref := rawToIssueReferenceList(expraw) refs := FindAllIssueReferencesMarkdown(fixture.input) - assert.EqualValues(t, expref, refs, "[%s] Failed to parse: {%s}", context, fixture.input) + assert.Equal(t, expref, refs, "[%s] Failed to parse: {%s}", context, fixture.input) rawrefs := findAllIssueReferencesMarkdown(fixture.input) - assert.EqualValues(t, expraw, rawrefs, "[%s] Failed to parse: {%s}", context, fixture.input) + assert.Equal(t, expraw, rawrefs, "[%s] Failed to parse: {%s}", context, fixture.input) } // Restore for other tests that may rely on the original value @@ -294,7 +294,7 @@ func testFixtures(t *testing.T, fixtures []testFixture, context string) { func TestFindAllMentions(t *testing.T) { res := FindAllMentionsBytes([]byte("@tasha, @mike; @lucy: @john")) - assert.EqualValues(t, []RefSpan{ + assert.Equal(t, []RefSpan{ {Start: 0, End: 6}, {Start: 8, End: 13}, {Start: 15, End: 20}, @@ -554,7 +554,7 @@ func TestParseCloseKeywords(t *testing.T) { res := pat.FindAllStringSubmatch(test.match, -1) assert.Len(t, res, 1) assert.Len(t, res[0], 2) - assert.EqualValues(t, test.expected, res[0][1]) + assert.Equal(t, test.expected, res[0][1]) } } } diff --git a/modules/regexplru/regexplru_test.go b/modules/regexplru/regexplru_test.go index 9c24b23fa9..4b539c31e9 100644 --- a/modules/regexplru/regexplru_test.go +++ b/modules/regexplru/regexplru_test.go @@ -18,9 +18,9 @@ func TestRegexpLru(t *testing.T) { assert.NoError(t, err) assert.True(t, r.MatchString("a")) - assert.EqualValues(t, 1, lruCache.Len()) + assert.Equal(t, 1, lruCache.Len()) _, err = GetCompiled("(") assert.Error(t, err) - assert.EqualValues(t, 2, lruCache.Len()) + assert.Equal(t, 2, lruCache.Len()) } diff --git a/modules/repository/branch_test.go b/modules/repository/branch_test.go index acf75a1ac0..ead28aa141 100644 --- a/modules/repository/branch_test.go +++ b/modules/repository/branch_test.go @@ -27,5 +27,5 @@ func TestSyncRepoBranches(t *testing.T) { assert.Equal(t, "sha1", repo.ObjectFormatName) branch, err := git_model.GetBranch(db.DefaultContext, 1, "master") assert.NoError(t, err) - assert.EqualValues(t, "master", branch.Name) + assert.Equal(t, "master", branch.Name) } diff --git a/modules/repository/commits.go b/modules/repository/commits.go index 16520fb28a..878fdc1603 100644 --- a/modules/repository/commits.go +++ b/modules/repository/commits.go @@ -13,6 +13,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/cache" + "code.gitea.io/gitea/modules/cachegroup" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -131,7 +132,7 @@ func (pc *PushCommits) ToAPIPayloadCommits(ctx context.Context, repo *repo_model func (pc *PushCommits) AvatarLink(ctx context.Context, email string) string { size := avatars.DefaultAvatarPixelSize * setting.Avatar.RenderedSizeFactor - v, _ := cache.GetWithContextCache(ctx, "push_commits", email, func() (string, error) { + v, _ := cache.GetWithContextCache(ctx, cachegroup.EmailAvatarLink, email, func(ctx context.Context, email string) (string, error) { u, err := user_model.GetUserByEmail(ctx, email) if err != nil { if !user_model.IsErrUserNotExist(err) { diff --git a/modules/repository/commits_test.go b/modules/repository/commits_test.go index e8dbf0b4e9..6e407015c2 100644 --- a/modules/repository/commits_test.go +++ b/modules/repository/commits_test.go @@ -62,9 +62,9 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) { assert.Equal(t, "user2", payloadCommits[0].Committer.UserName) assert.Equal(t, "User2", payloadCommits[0].Author.Name) assert.Equal(t, "user2", payloadCommits[0].Author.UserName) - assert.EqualValues(t, []string{}, payloadCommits[0].Added) - assert.EqualValues(t, []string{}, payloadCommits[0].Removed) - assert.EqualValues(t, []string{"readme.md"}, payloadCommits[0].Modified) + assert.Equal(t, []string{}, payloadCommits[0].Added) + assert.Equal(t, []string{}, payloadCommits[0].Removed) + assert.Equal(t, []string{"readme.md"}, payloadCommits[0].Modified) assert.Equal(t, "27566bd", payloadCommits[1].ID) assert.Equal(t, "good signed commit (with not yet validated email)", payloadCommits[1].Message) @@ -73,9 +73,9 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) { assert.Equal(t, "user2", payloadCommits[1].Committer.UserName) assert.Equal(t, "User2", payloadCommits[1].Author.Name) assert.Equal(t, "user2", payloadCommits[1].Author.UserName) - assert.EqualValues(t, []string{}, payloadCommits[1].Added) - assert.EqualValues(t, []string{}, payloadCommits[1].Removed) - assert.EqualValues(t, []string{"readme.md"}, payloadCommits[1].Modified) + assert.Equal(t, []string{}, payloadCommits[1].Added) + assert.Equal(t, []string{}, payloadCommits[1].Removed) + assert.Equal(t, []string{"readme.md"}, payloadCommits[1].Modified) assert.Equal(t, "5099b81", payloadCommits[2].ID) assert.Equal(t, "good signed commit", payloadCommits[2].Message) @@ -84,9 +84,9 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) { assert.Equal(t, "user2", payloadCommits[2].Committer.UserName) assert.Equal(t, "User2", payloadCommits[2].Author.Name) assert.Equal(t, "user2", payloadCommits[2].Author.UserName) - assert.EqualValues(t, []string{"readme.md"}, payloadCommits[2].Added) - assert.EqualValues(t, []string{}, payloadCommits[2].Removed) - assert.EqualValues(t, []string{}, payloadCommits[2].Modified) + assert.Equal(t, []string{"readme.md"}, payloadCommits[2].Added) + assert.Equal(t, []string{}, payloadCommits[2].Removed) + assert.Equal(t, []string{}, payloadCommits[2].Modified) assert.Equal(t, "69554a6", headCommit.ID) assert.Equal(t, "not signed commit", headCommit.Message) @@ -95,9 +95,9 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) { assert.Equal(t, "user2", headCommit.Committer.UserName) assert.Equal(t, "User2", headCommit.Author.Name) assert.Equal(t, "user2", headCommit.Author.UserName) - assert.EqualValues(t, []string{}, headCommit.Added) - assert.EqualValues(t, []string{}, headCommit.Removed) - assert.EqualValues(t, []string{"readme.md"}, headCommit.Modified) + assert.Equal(t, []string{}, headCommit.Added) + assert.Equal(t, []string{}, headCommit.Removed) + assert.Equal(t, []string{"readme.md"}, headCommit.Modified) } func TestPushCommits_AvatarLink(t *testing.T) { diff --git a/modules/repository/create.go b/modules/repository/create.go index a53632bb57..a75598a84b 100644 --- a/modules/repository/create.go +++ b/modules/repository/create.go @@ -8,17 +8,9 @@ import ( "fmt" "os" "path/filepath" - "strings" - activities_model "code.gitea.io/gitea/models/activities" - "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" - access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" - issue_indexer "code.gitea.io/gitea/modules/indexer/issues" - "code.gitea.io/gitea/modules/log" - api "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/util" ) const notRegularFileMode = os.ModeSymlink | os.ModeNamedPipe | os.ModeSocket | os.ModeDevice | os.ModeCharDevice | os.ModeIrregular @@ -63,97 +55,3 @@ func UpdateRepoSize(ctx context.Context, repo *repo_model.Repository) error { return repo_model.UpdateRepoSize(ctx, repo.ID, size, lfsSize) } - -// CheckDaemonExportOK creates/removes git-daemon-export-ok for git-daemon... -func CheckDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error { - if err := repo.LoadOwner(ctx); err != nil { - return err - } - - // Create/Remove git-daemon-export-ok for git-daemon... - daemonExportFile := filepath.Join(repo.RepoPath(), `git-daemon-export-ok`) - - isExist, err := util.IsExist(daemonExportFile) - if err != nil { - log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err) - return err - } - - isPublic := !repo.IsPrivate && repo.Owner.Visibility == api.VisibleTypePublic - if !isPublic && isExist { - if err = util.Remove(daemonExportFile); err != nil { - log.Error("Failed to remove %s: %v", daemonExportFile, err) - } - } else if isPublic && !isExist { - if f, err := os.Create(daemonExportFile); err != nil { - log.Error("Failed to create %s: %v", daemonExportFile, err) - } else { - f.Close() - } - } - - return nil -} - -// UpdateRepository updates a repository with db context -func UpdateRepository(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) { - repo.LowerName = strings.ToLower(repo.Name) - - e := db.GetEngine(ctx) - - if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil { - return fmt.Errorf("update: %w", err) - } - - if err = UpdateRepoSize(ctx, repo); err != nil { - log.Error("Failed to update size for repository: %v", err) - } - - if visibilityChanged { - if err = repo.LoadOwner(ctx); err != nil { - return fmt.Errorf("LoadOwner: %w", err) - } - if repo.Owner.IsOrganization() { - // Organization repository need to recalculate access table when visibility is changed. - if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil { - return fmt.Errorf("recalculateTeamAccesses: %w", err) - } - } - - // If repo has become private, we need to set its actions to private. - if repo.IsPrivate { - _, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&activities_model.Action{ - IsPrivate: true, - }) - if err != nil { - return err - } - - if err = repo_model.ClearRepoStars(ctx, repo.ID); err != nil { - return err - } - } - - // Create/Remove git-daemon-export-ok for git-daemon... - if err := CheckDaemonExportOK(ctx, repo); err != nil { - return err - } - - forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID) - if err != nil { - return fmt.Errorf("getRepositoriesByForkID: %w", err) - } - for i := range forkRepos { - forkRepos[i].IsPrivate = repo.IsPrivate || repo.Owner.Visibility == api.VisibleTypePrivate - if err = UpdateRepository(ctx, forkRepos[i], true); err != nil { - return fmt.Errorf("updateRepository[%d]: %w", forkRepos[i].ID, err) - } - } - - // If visibility is changed, we need to update the issue indexer. - // Since the data in the issue indexer have field to indicate if the repo is public or not. - issue_indexer.UpdateRepoIndexer(ctx, repo.ID) - } - - return nil -} diff --git a/modules/repository/create_test.go b/modules/repository/create_test.go index a9151482b4..b85a10adad 100644 --- a/modules/repository/create_test.go +++ b/modules/repository/create_test.go @@ -6,7 +6,6 @@ package repository import ( "testing" - activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" @@ -14,26 +13,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestUpdateRepositoryVisibilityChanged(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - - // Get sample repo and change visibility - repo, err := repo_model.GetRepositoryByID(db.DefaultContext, 9) - assert.NoError(t, err) - repo.IsPrivate = true - - // Update it - err = UpdateRepository(db.DefaultContext, repo, true) - assert.NoError(t, err) - - // Check visibility of action has become private - act := activities_model.Action{} - _, err = db.GetEngine(db.DefaultContext).ID(3).Get(&act) - - assert.NoError(t, err) - assert.True(t, act.IsPrivate) -} - func TestGetDirectorySize(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo, err := repo_model.GetRepositoryByID(db.DefaultContext, 1) @@ -41,5 +20,5 @@ func TestGetDirectorySize(t *testing.T) { size, err := getDirectorySize(repo.RepoPath()) assert.NoError(t, err) repo.Size = 8165 // real size on the disk - assert.EqualValues(t, repo.Size, size) + assert.Equal(t, repo.Size, size) } diff --git a/modules/repository/env.go b/modules/repository/env.go index e4f32092fc..78e06f86fb 100644 --- a/modules/repository/env.go +++ b/modules/repository/env.go @@ -4,8 +4,8 @@ package repository import ( - "fmt" "os" + "strconv" "strings" repo_model "code.gitea.io/gitea/models/repo" @@ -72,9 +72,9 @@ func FullPushingEnvironment(author, committer *user_model.User, repo *repo_model EnvRepoUsername+"="+repo.OwnerName, EnvRepoIsWiki+"="+isWiki, EnvPusherName+"="+committer.Name, - EnvPusherID+"="+fmt.Sprintf("%d", committer.ID), - EnvRepoID+"="+fmt.Sprintf("%d", repo.ID), - EnvPRID+"="+fmt.Sprintf("%d", prID), + EnvPusherID+"="+strconv.FormatInt(committer.ID, 10), + EnvRepoID+"="+strconv.FormatInt(repo.ID, 10), + EnvPRID+"="+strconv.FormatInt(prID, 10), EnvAppURL+"="+setting.AppURL, "SSH_ORIGINAL_COMMAND=gitea-internal", ) diff --git a/modules/repository/init.go b/modules/repository/init.go index ace21254ba..91d4889782 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -11,11 +11,7 @@ import ( "strings" issues_model "code.gitea.io/gitea/models/issues" - repo_model "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/label" - "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/options" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -121,29 +117,6 @@ func LoadRepoConfig() error { return nil } -func CheckInitRepository(ctx context.Context, repo *repo_model.Repository) (err error) { - // Somehow the directory could exist. - isExist, err := gitrepo.IsRepositoryExist(ctx, repo) - if err != nil { - log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err) - return err - } - if isExist { - return repo_model.ErrRepoFilesAlreadyExist{ - Uname: repo.OwnerName, - Name: repo.Name, - } - } - - // Init git bare new repository. - if err = git.InitRepository(ctx, repo.RepoPath(), true, repo.ObjectFormatName); err != nil { - return fmt.Errorf("git.InitRepository: %w", err) - } else if err = gitrepo.CreateDelegateHooks(ctx, repo); err != nil { - return fmt.Errorf("createDelegateHooks: %w", err) - } - return nil -} - // InitializeLabels adds a label set to a repository using a template func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg bool) error { list, err := LoadTemplateLabelsByDisplayName(labelTemplate) @@ -154,10 +127,11 @@ func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg labels := make([]*issues_model.Label, len(list)) for i := 0; i < len(list); i++ { labels[i] = &issues_model.Label{ - Name: list[i].Name, - Exclusive: list[i].Exclusive, - Description: list[i].Description, - Color: list[i].Color, + Name: list[i].Name, + Exclusive: list[i].Exclusive, + ExclusiveOrder: list[i].ExclusiveOrder, + Description: list[i].Description, + Color: list[i].Color, } if isOrg { labels[i].OrgID = id diff --git a/modules/repository/init_test.go b/modules/repository/init_test.go index 227efdc1db..1fa928105c 100644 --- a/modules/repository/init_test.go +++ b/modules/repository/init_test.go @@ -14,17 +14,17 @@ func TestMergeCustomLabels(t *testing.T) { all: []string{"a", "a.yaml", "a.yml"}, custom: nil, }) - assert.EqualValues(t, []string{"a.yaml"}, files, "yaml file should win") + assert.Equal(t, []string{"a.yaml"}, files, "yaml file should win") files = mergeCustomLabelFiles(optionFileList{ all: []string{"a", "a.yaml"}, custom: []string{"a"}, }) - assert.EqualValues(t, []string{"a"}, files, "custom file should win") + assert.Equal(t, []string{"a"}, files, "custom file should win") files = mergeCustomLabelFiles(optionFileList{ all: []string{"a", "a.yml", "a.yaml"}, custom: []string{"a", "a.yml"}, }) - assert.EqualValues(t, []string{"a.yml"}, files, "custom yml file should win if no yaml") + assert.Equal(t, []string{"a.yml"}, files, "custom yml file should win if no yaml") } diff --git a/modules/repository/repo.go b/modules/repository/repo.go index 97b0343381..bc147a4dd5 100644 --- a/modules/repository/repo.go +++ b/modules/repository/repo.go @@ -126,7 +126,7 @@ func PushUpdateAddTag(ctx context.Context, repo *repo_model.Repository, gitRepo if err != nil { return fmt.Errorf("unable to GetTag: %w", err) } - commit, err := tag.Commit(gitRepo) + commit, err := gitRepo.GetTagCommit(tag.Name) if err != nil { return fmt.Errorf("unable to get tag Commit: %w", err) } diff --git a/modules/repository/repo_test.go b/modules/repository/repo_test.go index f3e7be6d7d..f79a79ccbd 100644 --- a/modules/repository/repo_test.go +++ b/modules/repository/repo_test.go @@ -63,7 +63,7 @@ func Test_calcSync(t *testing.T) { inserts, deletes, updates := calcSync(gitTags, dbReleases) if assert.Len(t, inserts, 1, "inserts") { - assert.EqualValues(t, *gitTags[2], *inserts[0], "inserts equal") + assert.Equal(t, *gitTags[2], *inserts[0], "inserts equal") } if assert.Len(t, deletes, 1, "deletes") { @@ -71,6 +71,6 @@ func Test_calcSync(t *testing.T) { } if assert.Len(t, updates, 1, "updates") { - assert.EqualValues(t, *gitTags[1], *updates[0], "updates equal") + assert.Equal(t, *gitTags[1], *updates[0], "updates equal") } } diff --git a/modules/repository/temp.go b/modules/repository/temp.go index 76b9bda4ad..d7253d9e02 100644 --- a/modules/repository/temp.go +++ b/modules/repository/temp.go @@ -4,41 +4,19 @@ package repository import ( + "context" "fmt" - "os" - "path/filepath" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" ) -// LocalCopyPath returns the local repository temporary copy path. -func LocalCopyPath() string { - if filepath.IsAbs(setting.Repository.Local.LocalCopyPath) { - return setting.Repository.Local.LocalCopyPath - } - return filepath.Join(setting.AppDataPath, setting.Repository.Local.LocalCopyPath) -} - // CreateTemporaryPath creates a temporary path -func CreateTemporaryPath(prefix string) (string, error) { - if err := os.MkdirAll(LocalCopyPath(), os.ModePerm); err != nil { - log.Error("Unable to create localcopypath directory: %s (%v)", LocalCopyPath(), err) - return "", fmt.Errorf("Failed to create localcopypath directory %s: %w", LocalCopyPath(), err) - } - basePath, err := os.MkdirTemp(LocalCopyPath(), prefix+".git") +func CreateTemporaryPath(prefix string) (string, context.CancelFunc, error) { + basePath, cleanup, err := setting.AppDataTempDir("local-repo").MkdirTempRandom(prefix + ".git") if err != nil { log.Error("Unable to create temporary directory: %s-*.git (%v)", prefix, err) - return "", fmt.Errorf("Failed to create dir %s-*.git: %w", prefix, err) + return "", nil, fmt.Errorf("failed to create dir %s-*.git: %w", prefix, err) } - return basePath, nil -} - -// RemoveTemporaryPath removes the temporary path -func RemoveTemporaryPath(basePath string) error { - if _, err := os.Stat(basePath); !os.IsNotExist(err) { - return util.RemoveAll(basePath) - } - return nil + return basePath, cleanup, nil } diff --git a/modules/setting/actions_test.go b/modules/setting/actions_test.go index 3645a3f5da..353cc657fa 100644 --- a/modules/setting/actions_test.go +++ b/modules/setting/actions_test.go @@ -21,9 +21,9 @@ func Test_getStorageInheritNameSectionTypeForActions(t *testing.T) { assert.NoError(t, loadActionsFrom(cfg)) assert.EqualValues(t, "minio", Actions.LogStorage.Type) - assert.EqualValues(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) + assert.Equal(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) assert.EqualValues(t, "minio", Actions.ArtifactStorage.Type) - assert.EqualValues(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) + assert.Equal(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) iniStr = ` [storage.actions_log] @@ -34,9 +34,9 @@ STORAGE_TYPE = minio assert.NoError(t, loadActionsFrom(cfg)) assert.EqualValues(t, "minio", Actions.LogStorage.Type) - assert.EqualValues(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) + assert.Equal(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) assert.EqualValues(t, "local", Actions.ArtifactStorage.Type) - assert.EqualValues(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path)) + assert.Equal(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path)) iniStr = ` [storage.actions_log] @@ -50,9 +50,9 @@ STORAGE_TYPE = minio assert.NoError(t, loadActionsFrom(cfg)) assert.EqualValues(t, "minio", Actions.LogStorage.Type) - assert.EqualValues(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) + assert.Equal(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) assert.EqualValues(t, "local", Actions.ArtifactStorage.Type) - assert.EqualValues(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path)) + assert.Equal(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path)) iniStr = ` [storage.actions_artifacts] @@ -66,9 +66,9 @@ STORAGE_TYPE = minio assert.NoError(t, loadActionsFrom(cfg)) assert.EqualValues(t, "local", Actions.LogStorage.Type) - assert.EqualValues(t, "actions_log", filepath.Base(Actions.LogStorage.Path)) + assert.Equal(t, "actions_log", filepath.Base(Actions.LogStorage.Path)) assert.EqualValues(t, "minio", Actions.ArtifactStorage.Type) - assert.EqualValues(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) + assert.Equal(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) iniStr = ` [storage.actions_artifacts] @@ -82,9 +82,9 @@ STORAGE_TYPE = minio assert.NoError(t, loadActionsFrom(cfg)) assert.EqualValues(t, "local", Actions.LogStorage.Type) - assert.EqualValues(t, "actions_log", filepath.Base(Actions.LogStorage.Path)) + assert.Equal(t, "actions_log", filepath.Base(Actions.LogStorage.Path)) assert.EqualValues(t, "minio", Actions.ArtifactStorage.Type) - assert.EqualValues(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) + assert.Equal(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) iniStr = `` cfg, err = NewConfigProviderFromData(iniStr) @@ -92,9 +92,9 @@ STORAGE_TYPE = minio assert.NoError(t, loadActionsFrom(cfg)) assert.EqualValues(t, "local", Actions.LogStorage.Type) - assert.EqualValues(t, "actions_log", filepath.Base(Actions.LogStorage.Path)) + assert.Equal(t, "actions_log", filepath.Base(Actions.LogStorage.Path)) assert.EqualValues(t, "local", Actions.ArtifactStorage.Type) - assert.EqualValues(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path)) + assert.Equal(t, "actions_artifacts", filepath.Base(Actions.ArtifactStorage.Path)) } func Test_getDefaultActionsURLForActions(t *testing.T) { @@ -175,7 +175,7 @@ DEFAULT_ACTIONS_URL = gitea if !tt.wantErr(t, loadActionsFrom(cfg)) { return } - assert.EqualValues(t, tt.wantURL, Actions.DefaultActionsURL.URL()) + assert.Equal(t, tt.wantURL, Actions.DefaultActionsURL.URL()) }) } } diff --git a/modules/setting/attachment_test.go b/modules/setting/attachment_test.go index 3e8d2da4d9..c566dfa60c 100644 --- a/modules/setting/attachment_test.go +++ b/modules/setting/attachment_test.go @@ -25,9 +25,9 @@ MINIO_ENDPOINT = my_minio:9000 assert.NoError(t, loadAttachmentFrom(cfg)) assert.EqualValues(t, "minio", Attachment.Storage.Type) - assert.EqualValues(t, "my_minio:9000", Attachment.Storage.MinioConfig.Endpoint) - assert.EqualValues(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) + assert.Equal(t, "my_minio:9000", Attachment.Storage.MinioConfig.Endpoint) + assert.Equal(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket) + assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) } func Test_getStorageTypeSectionOverridesStorageSection(t *testing.T) { @@ -47,8 +47,8 @@ MINIO_BUCKET = gitea assert.NoError(t, loadAttachmentFrom(cfg)) assert.EqualValues(t, "minio", Attachment.Storage.Type) - assert.EqualValues(t, "gitea-minio", Attachment.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea-minio", Attachment.Storage.MinioConfig.Bucket) + assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) } func Test_getStorageSpecificOverridesStorage(t *testing.T) { @@ -69,8 +69,8 @@ STORAGE_TYPE = local assert.NoError(t, loadAttachmentFrom(cfg)) assert.EqualValues(t, "minio", Attachment.Storage.Type) - assert.EqualValues(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket) + assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) } func Test_getStorageGetDefaults(t *testing.T) { @@ -80,7 +80,7 @@ func Test_getStorageGetDefaults(t *testing.T) { assert.NoError(t, loadAttachmentFrom(cfg)) // default storage is local, so bucket is empty - assert.EqualValues(t, "", Attachment.Storage.MinioConfig.Bucket) + assert.Empty(t, Attachment.Storage.MinioConfig.Bucket) } func Test_getStorageInheritNameSectionType(t *testing.T) { @@ -115,7 +115,7 @@ MINIO_SECRET_ACCESS_KEY = correct_key storage := Attachment.Storage assert.EqualValues(t, "minio", storage.Type) - assert.EqualValues(t, "gitea", storage.MinioConfig.Bucket) + assert.Equal(t, "gitea", storage.MinioConfig.Bucket) } func Test_AttachmentStorage1(t *testing.T) { @@ -128,6 +128,6 @@ STORAGE_TYPE = minio assert.NoError(t, loadAttachmentFrom(cfg)) assert.EqualValues(t, "minio", Attachment.Storage.Type) - assert.EqualValues(t, "gitea", Attachment.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", Attachment.Storage.MinioConfig.Bucket) + assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) } diff --git a/modules/setting/config_env_test.go b/modules/setting/config_env_test.go index 7d07c479a1..217ea53860 100644 --- a/modules/setting/config_env_test.go +++ b/modules/setting/config_env_test.go @@ -28,8 +28,8 @@ func TestDecodeEnvSectionKey(t *testing.T) { ok, section, key = decodeEnvSectionKey("SEC") assert.False(t, ok) - assert.Equal(t, "", section) - assert.Equal(t, "", key) + assert.Empty(t, section) + assert.Empty(t, key) } func TestDecodeEnvironmentKey(t *testing.T) { @@ -38,19 +38,19 @@ func TestDecodeEnvironmentKey(t *testing.T) { ok, section, key, file := decodeEnvironmentKey(prefix, suffix, "SEC__KEY") assert.False(t, ok) - assert.Equal(t, "", section) - assert.Equal(t, "", key) + assert.Empty(t, section) + assert.Empty(t, key) assert.False(t, file) ok, section, key, file = decodeEnvironmentKey(prefix, suffix, "GITEA__SEC") assert.False(t, ok) - assert.Equal(t, "", section) - assert.Equal(t, "", key) + assert.Empty(t, section) + assert.Empty(t, key) assert.False(t, file) ok, section, key, file = decodeEnvironmentKey(prefix, suffix, "GITEA____KEY") assert.True(t, ok) - assert.Equal(t, "", section) + assert.Empty(t, section) assert.Equal(t, "KEY", key) assert.False(t, file) @@ -64,8 +64,8 @@ func TestDecodeEnvironmentKey(t *testing.T) { // but it could be fixed in the future by adding a new suffix like "__VALUE" (no such key VALUE is used in Gitea either) ok, section, key, file = decodeEnvironmentKey(prefix, suffix, "GITEA__SEC__FILE") assert.False(t, ok) - assert.Equal(t, "", section) - assert.Equal(t, "", key) + assert.Empty(t, section) + assert.Empty(t, key) assert.True(t, file) ok, section, key, file = decodeEnvironmentKey(prefix, suffix, "GITEA__SEC__KEY__FILE") diff --git a/modules/setting/config_provider.go b/modules/setting/config_provider.go index 3138f8a63e..a0c53a1032 100644 --- a/modules/setting/config_provider.go +++ b/modules/setting/config_provider.go @@ -26,6 +26,7 @@ type ConfigKey interface { In(defaultVal string, candidates []string) string String() string Strings(delim string) []string + Bool() (bool, error) MustString(defaultVal string) string MustBool(defaultVal ...bool) bool @@ -257,7 +258,7 @@ func (p *iniConfigProvider) Save() error { } filename := p.file if filename == "" { - return fmt.Errorf("config file path must not be empty") + return errors.New("config file path must not be empty") } if p.loadedFromEmpty { if err := os.MkdirAll(filepath.Dir(filename), os.ModePerm); err != nil { diff --git a/modules/setting/config_provider_test.go b/modules/setting/config_provider_test.go index a666d124c7..63121f0074 100644 --- a/modules/setting/config_provider_test.go +++ b/modules/setting/config_provider_test.go @@ -62,17 +62,17 @@ key = 123 // test default behavior assert.Equal(t, "123", ConfigSectionKeyString(sec, "key")) - assert.Equal(t, "", ConfigSectionKeyString(secSub, "key")) + assert.Empty(t, ConfigSectionKeyString(secSub, "key")) assert.Equal(t, "def", ConfigSectionKeyString(secSub, "key", "def")) assert.Equal(t, "123", ConfigInheritedKeyString(secSub, "key")) // Workaround for ini package's BuggyKeyOverwritten behavior - assert.Equal(t, "", ConfigSectionKeyString(sec, "empty")) - assert.Equal(t, "", ConfigSectionKeyString(secSub, "empty")) + assert.Empty(t, ConfigSectionKeyString(sec, "empty")) + assert.Empty(t, ConfigSectionKeyString(secSub, "empty")) assert.Equal(t, "def", ConfigInheritedKey(secSub, "empty").MustString("def")) assert.Equal(t, "def", ConfigInheritedKey(secSub, "empty").MustString("xyz")) - assert.Equal(t, "", ConfigSectionKeyString(sec, "empty")) + assert.Empty(t, ConfigSectionKeyString(sec, "empty")) assert.Equal(t, "def", ConfigSectionKeyString(secSub, "empty")) } diff --git a/modules/setting/cron_test.go b/modules/setting/cron_test.go index 55244d7075..39a228068a 100644 --- a/modules/setting/cron_test.go +++ b/modules/setting/cron_test.go @@ -38,6 +38,6 @@ EXTEND = true _, err = getCronSettings(cfg, "test", extended) assert.NoError(t, err) assert.True(t, extended.Base) - assert.EqualValues(t, "white rabbit", extended.Second) + assert.Equal(t, "white rabbit", extended.Second) assert.True(t, extended.Extend) } diff --git a/modules/setting/git_test.go b/modules/setting/git_test.go index 441c514d8c..818bcf9df6 100644 --- a/modules/setting/git_test.go +++ b/modules/setting/git_test.go @@ -23,8 +23,8 @@ a.b = 1 `) assert.NoError(t, err) loadGitFrom(cfg) - assert.EqualValues(t, "1", GitConfig.Options["a.b"]) - assert.EqualValues(t, "histogram", GitConfig.Options["diff.algorithm"]) + assert.Equal(t, "1", GitConfig.Options["a.b"]) + assert.Equal(t, "histogram", GitConfig.Options["diff.algorithm"]) cfg, err = NewConfigProviderFromData(` [git.config] @@ -32,7 +32,7 @@ diff.algorithm = other `) assert.NoError(t, err) loadGitFrom(cfg) - assert.EqualValues(t, "other", GitConfig.Options["diff.algorithm"]) + assert.Equal(t, "other", GitConfig.Options["diff.algorithm"]) } func TestGitReflog(t *testing.T) { @@ -48,8 +48,8 @@ func TestGitReflog(t *testing.T) { assert.NoError(t, err) loadGitFrom(cfg) - assert.EqualValues(t, "true", GitConfig.GetOption("core.logAllRefUpdates")) - assert.EqualValues(t, "90", GitConfig.GetOption("gc.reflogExpire")) + assert.Equal(t, "true", GitConfig.GetOption("core.logAllRefUpdates")) + assert.Equal(t, "90", GitConfig.GetOption("gc.reflogExpire")) // custom reflog config by legacy options cfg, err = NewConfigProviderFromData(` @@ -60,6 +60,6 @@ EXPIRATION = 123 assert.NoError(t, err) loadGitFrom(cfg) - assert.EqualValues(t, "false", GitConfig.GetOption("core.logAllRefUpdates")) - assert.EqualValues(t, "123", GitConfig.GetOption("gc.reflogExpire")) + assert.Equal(t, "false", GitConfig.GetOption("core.logAllRefUpdates")) + assert.Equal(t, "123", GitConfig.GetOption("gc.reflogExpire")) } diff --git a/modules/setting/global_lock_test.go b/modules/setting/global_lock_test.go index 5eeb275523..5e15eb3483 100644 --- a/modules/setting/global_lock_test.go +++ b/modules/setting/global_lock_test.go @@ -16,7 +16,7 @@ func TestLoadGlobalLockConfig(t *testing.T) { assert.NoError(t, err) loadGlobalLockFrom(cfg) - assert.EqualValues(t, "memory", GlobalLock.ServiceType) + assert.Equal(t, "memory", GlobalLock.ServiceType) }) t.Run("RedisGlobalLockConfig", func(t *testing.T) { @@ -29,7 +29,7 @@ SERVICE_CONN_STR = addrs=127.0.0.1:6379 db=0 assert.NoError(t, err) loadGlobalLockFrom(cfg) - assert.EqualValues(t, "redis", GlobalLock.ServiceType) - assert.EqualValues(t, "addrs=127.0.0.1:6379 db=0", GlobalLock.ServiceConnStr) + assert.Equal(t, "redis", GlobalLock.ServiceType) + assert.Equal(t, "addrs=127.0.0.1:6379 db=0", GlobalLock.ServiceConnStr) }) } diff --git a/modules/setting/incoming_email.go b/modules/setting/incoming_email.go index bf81f292a2..4e433dde60 100644 --- a/modules/setting/incoming_email.go +++ b/modules/setting/incoming_email.go @@ -4,6 +4,7 @@ package setting import ( + "errors" "fmt" "net/mail" "strings" @@ -50,7 +51,7 @@ func checkReplyToAddress() error { } if parsed.Name != "" { - return fmt.Errorf("name must not be set") + return errors.New("name must not be set") } c := strings.Count(IncomingEmail.ReplyToAddress, IncomingEmail.TokenPlaceholder) diff --git a/modules/setting/lfs_test.go b/modules/setting/lfs_test.go index d27dd7c5bf..1b829d8839 100644 --- a/modules/setting/lfs_test.go +++ b/modules/setting/lfs_test.go @@ -19,7 +19,7 @@ func Test_getStorageInheritNameSectionTypeForLFS(t *testing.T) { assert.NoError(t, loadLFSFrom(cfg)) assert.EqualValues(t, "minio", LFS.Storage.Type) - assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath) + assert.Equal(t, "lfs/", LFS.Storage.MinioConfig.BasePath) iniStr = ` [server] @@ -54,7 +54,7 @@ STORAGE_TYPE = minio assert.NoError(t, loadLFSFrom(cfg)) assert.EqualValues(t, "minio", LFS.Storage.Type) - assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath) + assert.Equal(t, "lfs/", LFS.Storage.MinioConfig.BasePath) iniStr = ` [lfs] @@ -68,7 +68,7 @@ STORAGE_TYPE = minio assert.NoError(t, loadLFSFrom(cfg)) assert.EqualValues(t, "minio", LFS.Storage.Type) - assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath) + assert.Equal(t, "lfs/", LFS.Storage.MinioConfig.BasePath) iniStr = ` [lfs] @@ -83,7 +83,7 @@ STORAGE_TYPE = minio assert.NoError(t, loadLFSFrom(cfg)) assert.EqualValues(t, "minio", LFS.Storage.Type) - assert.EqualValues(t, "my_lfs/", LFS.Storage.MinioConfig.BasePath) + assert.Equal(t, "my_lfs/", LFS.Storage.MinioConfig.BasePath) } func Test_LFSStorage1(t *testing.T) { @@ -96,8 +96,8 @@ STORAGE_TYPE = minio assert.NoError(t, loadLFSFrom(cfg)) assert.EqualValues(t, "minio", LFS.Storage.Type) - assert.EqualValues(t, "gitea", LFS.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", LFS.Storage.MinioConfig.Bucket) + assert.Equal(t, "lfs/", LFS.Storage.MinioConfig.BasePath) } func Test_LFSClientServerConfigs(t *testing.T) { @@ -112,9 +112,9 @@ BATCH_SIZE = 0 assert.NoError(t, err) assert.NoError(t, loadLFSFrom(cfg)) - assert.EqualValues(t, 100, LFS.MaxBatchSize) - assert.EqualValues(t, 20, LFSClient.BatchSize) - assert.EqualValues(t, 8, LFSClient.BatchOperationConcurrency) + assert.Equal(t, 100, LFS.MaxBatchSize) + assert.Equal(t, 20, LFSClient.BatchSize) + assert.Equal(t, 8, LFSClient.BatchOperationConcurrency) iniStr = ` [lfs_client] @@ -125,6 +125,6 @@ BATCH_OPERATION_CONCURRENCY = 10 assert.NoError(t, err) assert.NoError(t, loadLFSFrom(cfg)) - assert.EqualValues(t, 50, LFSClient.BatchSize) - assert.EqualValues(t, 10, LFSClient.BatchOperationConcurrency) + assert.Equal(t, 50, LFSClient.BatchSize) + assert.Equal(t, 10, LFSClient.BatchOperationConcurrency) } diff --git a/modules/setting/mailer_test.go b/modules/setting/mailer_test.go index fbabf11378..ceef35b051 100644 --- a/modules/setting/mailer_test.go +++ b/modules/setting/mailer_test.go @@ -34,8 +34,8 @@ func Test_loadMailerFrom(t *testing.T) { // Check mailer setting loadMailerFrom(cfg) - assert.EqualValues(t, kase.SMTPAddr, MailService.SMTPAddr) - assert.EqualValues(t, kase.SMTPPort, MailService.SMTPPort) + assert.Equal(t, kase.SMTPAddr, MailService.SMTPAddr) + assert.Equal(t, kase.SMTPPort, MailService.SMTPPort) }) } } diff --git a/modules/setting/markup.go b/modules/setting/markup.go index dfce8afa77..365af05fcf 100644 --- a/modules/setting/markup.go +++ b/modules/setting/markup.go @@ -8,6 +8,7 @@ import ( "strings" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" ) // ExternalMarkupRenderers represents the external markup renderers @@ -23,18 +24,33 @@ const ( RenderContentModeIframe = "iframe" ) +type MarkdownRenderOptions struct { + NewLineHardBreak bool + ShortIssuePattern bool // Actually it is a "markup" option because it is used in "post processor" +} + +type MarkdownMathCodeBlockOptions struct { + ParseInlineDollar bool + ParseInlineParentheses bool + ParseBlockDollar bool + ParseBlockSquareBrackets bool +} + // Markdown settings var Markdown = struct { - EnableHardLineBreakInComments bool - EnableHardLineBreakInDocuments bool - CustomURLSchemes []string `ini:"CUSTOM_URL_SCHEMES"` - FileExtensions []string - EnableMath bool + RenderOptionsComment MarkdownRenderOptions `ini:"-"` + RenderOptionsWiki MarkdownRenderOptions `ini:"-"` + RenderOptionsRepoFile MarkdownRenderOptions `ini:"-"` + + CustomURLSchemes []string `ini:"CUSTOM_URL_SCHEMES"` // Actually it is a "markup" option because it is used in "post processor" + FileExtensions []string + + EnableMath bool + MathCodeBlockDetection []string + MathCodeBlockOptions MarkdownMathCodeBlockOptions `ini:"-"` }{ - EnableHardLineBreakInComments: true, - EnableHardLineBreakInDocuments: false, - FileExtensions: strings.Split(".md,.markdown,.mdown,.mkd,.livemd", ","), - EnableMath: true, + FileExtensions: strings.Split(".md,.markdown,.mdown,.mkd,.livemd", ","), + EnableMath: true, } // MarkupRenderer defines the external parser configured in ini @@ -60,8 +76,58 @@ type MarkupSanitizerRule struct { func loadMarkupFrom(rootCfg ConfigProvider) { mustMapSetting(rootCfg, "markdown", &Markdown) + const none = "none" - MermaidMaxSourceCharacters = rootCfg.Section("markup").Key("MERMAID_MAX_SOURCE_CHARACTERS").MustInt(5000) + const renderOptionShortIssuePattern = "short-issue-pattern" + const renderOptionNewLineHardBreak = "new-line-hard-break" + cfgMarkdown := rootCfg.Section("markdown") + parseMarkdownRenderOptions := func(key string, defaults []string) (ret MarkdownRenderOptions) { + options := cfgMarkdown.Key(key).Strings(",") + options = util.IfEmpty(options, defaults) + for _, opt := range options { + switch opt { + case renderOptionShortIssuePattern: + ret.ShortIssuePattern = true + case renderOptionNewLineHardBreak: + ret.NewLineHardBreak = true + case none: + ret = MarkdownRenderOptions{} + case "": + default: + log.Error("Unknown markdown render option in %s: %s", key, opt) + } + } + return ret + } + Markdown.RenderOptionsComment = parseMarkdownRenderOptions("RENDER_OPTIONS_COMMENT", []string{renderOptionShortIssuePattern, renderOptionNewLineHardBreak}) + Markdown.RenderOptionsWiki = parseMarkdownRenderOptions("RENDER_OPTIONS_WIKI", []string{renderOptionShortIssuePattern}) + Markdown.RenderOptionsRepoFile = parseMarkdownRenderOptions("RENDER_OPTIONS_REPO_FILE", nil) + + const mathCodeInlineDollar = "inline-dollar" + const mathCodeInlineParentheses = "inline-parentheses" + const mathCodeBlockDollar = "block-dollar" + const mathCodeBlockSquareBrackets = "block-square-brackets" + Markdown.MathCodeBlockDetection = util.IfEmpty(Markdown.MathCodeBlockDetection, []string{mathCodeInlineDollar, mathCodeBlockDollar}) + Markdown.MathCodeBlockOptions = MarkdownMathCodeBlockOptions{} + for _, s := range Markdown.MathCodeBlockDetection { + switch s { + case mathCodeInlineDollar: + Markdown.MathCodeBlockOptions.ParseInlineDollar = true + case mathCodeInlineParentheses: + Markdown.MathCodeBlockOptions.ParseInlineParentheses = true + case mathCodeBlockDollar: + Markdown.MathCodeBlockOptions.ParseBlockDollar = true + case mathCodeBlockSquareBrackets: + Markdown.MathCodeBlockOptions.ParseBlockSquareBrackets = true + case none: + Markdown.MathCodeBlockOptions = MarkdownMathCodeBlockOptions{} + case "": + default: + log.Error("Unknown math code block detection option: %s", s) + } + } + + MermaidMaxSourceCharacters = rootCfg.Section("markup").Key("MERMAID_MAX_SOURCE_CHARACTERS").MustInt(50000) ExternalMarkupRenderers = make([]*MarkupRenderer, 0, 10) ExternalSanitizerRules = make([]MarkupSanitizerRule, 0, 10) diff --git a/modules/setting/markup_test.go b/modules/setting/markup_test.go new file mode 100644 index 0000000000..c47a38ce15 --- /dev/null +++ b/modules/setting/markup_test.go @@ -0,0 +1,51 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLoadMarkup(t *testing.T) { + cfg, _ := NewConfigProviderFromData(``) + loadMarkupFrom(cfg) + assert.Equal(t, MarkdownMathCodeBlockOptions{ParseInlineDollar: true, ParseBlockDollar: true}, Markdown.MathCodeBlockOptions) + assert.Equal(t, MarkdownRenderOptions{NewLineHardBreak: true, ShortIssuePattern: true}, Markdown.RenderOptionsComment) + assert.Equal(t, MarkdownRenderOptions{ShortIssuePattern: true}, Markdown.RenderOptionsWiki) + assert.Equal(t, MarkdownRenderOptions{}, Markdown.RenderOptionsRepoFile) + + t.Run("Math", func(t *testing.T) { + cfg, _ = NewConfigProviderFromData(` +[markdown] +MATH_CODE_BLOCK_DETECTION = none +`) + loadMarkupFrom(cfg) + assert.Equal(t, MarkdownMathCodeBlockOptions{}, Markdown.MathCodeBlockOptions) + + cfg, _ = NewConfigProviderFromData(` +[markdown] +MATH_CODE_BLOCK_DETECTION = inline-dollar, inline-parentheses, block-dollar, block-square-brackets +`) + loadMarkupFrom(cfg) + assert.Equal(t, MarkdownMathCodeBlockOptions{ParseInlineDollar: true, ParseInlineParentheses: true, ParseBlockDollar: true, ParseBlockSquareBrackets: true}, Markdown.MathCodeBlockOptions) + }) + + t.Run("Render", func(t *testing.T) { + cfg, _ = NewConfigProviderFromData(` +[markdown] +RENDER_OPTIONS_COMMENT = none +`) + loadMarkupFrom(cfg) + assert.Equal(t, MarkdownRenderOptions{}, Markdown.RenderOptionsComment) + + cfg, _ = NewConfigProviderFromData(` +[markdown] +RENDER_OPTIONS_REPO_FILE = short-issue-pattern, new-line-hard-break +`) + loadMarkupFrom(cfg) + assert.Equal(t, MarkdownRenderOptions{NewLineHardBreak: true, ShortIssuePattern: true}, Markdown.RenderOptionsRepoFile) + }) +} diff --git a/modules/setting/oauth2_test.go b/modules/setting/oauth2_test.go index d0e5ccf13d..c6e66cad02 100644 --- a/modules/setting/oauth2_test.go +++ b/modules/setting/oauth2_test.go @@ -31,7 +31,7 @@ JWT_SECRET = BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB actual := GetGeneralTokenSigningSecret() expected, _ := generate.DecodeJwtSecretBase64("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB") assert.Len(t, actual, 32) - assert.EqualValues(t, expected, actual) + assert.Equal(t, expected, actual) } func TestGetGeneralSigningSecretSave(t *testing.T) { diff --git a/modules/setting/packages.go b/modules/setting/packages.go index 3f618cfd64..845f6d4e12 100644 --- a/modules/setting/packages.go +++ b/modules/setting/packages.go @@ -6,8 +6,6 @@ package setting import ( "fmt" "math" - "os" - "path/filepath" "github.com/dustin/go-humanize" ) @@ -67,14 +65,10 @@ func loadPackagesFrom(rootCfg ConfigProvider) (err error) { return err } - Packages.ChunkedUploadPath = filepath.ToSlash(sec.Key("CHUNKED_UPLOAD_PATH").MustString("tmp/package-upload")) - if !filepath.IsAbs(Packages.ChunkedUploadPath) { - Packages.ChunkedUploadPath = filepath.ToSlash(filepath.Join(AppDataPath, Packages.ChunkedUploadPath)) - } - if HasInstallLock(rootCfg) { - if err := os.MkdirAll(Packages.ChunkedUploadPath, os.ModePerm); err != nil { - return fmt.Errorf("unable to create chunked upload directory: %s (%v)", Packages.ChunkedUploadPath, err) + Packages.ChunkedUploadPath, err = AppDataTempDir("package-upload").MkdirAllSub("") + if err != nil { + return fmt.Errorf("unable to create chunked upload directory: %w", err) } } diff --git a/modules/setting/packages_test.go b/modules/setting/packages_test.go index 87de276041..47378f35ad 100644 --- a/modules/setting/packages_test.go +++ b/modules/setting/packages_test.go @@ -41,7 +41,7 @@ STORAGE_TYPE = minio assert.NoError(t, loadPackagesFrom(cfg)) assert.EqualValues(t, "minio", Packages.Storage.Type) - assert.EqualValues(t, "packages/", Packages.Storage.MinioConfig.BasePath) + assert.Equal(t, "packages/", Packages.Storage.MinioConfig.BasePath) // we can also configure packages storage directly iniStr = ` @@ -53,7 +53,7 @@ STORAGE_TYPE = minio assert.NoError(t, loadPackagesFrom(cfg)) assert.EqualValues(t, "minio", Packages.Storage.Type) - assert.EqualValues(t, "packages/", Packages.Storage.MinioConfig.BasePath) + assert.Equal(t, "packages/", Packages.Storage.MinioConfig.BasePath) // or we can indicate the storage type in the packages section iniStr = ` @@ -68,7 +68,7 @@ STORAGE_TYPE = minio assert.NoError(t, loadPackagesFrom(cfg)) assert.EqualValues(t, "minio", Packages.Storage.Type) - assert.EqualValues(t, "packages/", Packages.Storage.MinioConfig.BasePath) + assert.Equal(t, "packages/", Packages.Storage.MinioConfig.BasePath) // or we can indicate the storage type and minio base path in the packages section iniStr = ` @@ -84,7 +84,7 @@ STORAGE_TYPE = minio assert.NoError(t, loadPackagesFrom(cfg)) assert.EqualValues(t, "minio", Packages.Storage.Type) - assert.EqualValues(t, "my_packages/", Packages.Storage.MinioConfig.BasePath) + assert.Equal(t, "my_packages/", Packages.Storage.MinioConfig.BasePath) } func Test_PackageStorage1(t *testing.T) { @@ -109,8 +109,8 @@ MINIO_SECRET_ACCESS_KEY = correct_key storage := Packages.Storage assert.EqualValues(t, "minio", storage.Type) - assert.EqualValues(t, "gitea", storage.MinioConfig.Bucket) - assert.EqualValues(t, "packages/", storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", storage.MinioConfig.Bucket) + assert.Equal(t, "packages/", storage.MinioConfig.BasePath) assert.True(t, storage.MinioConfig.ServeDirect) } @@ -136,8 +136,8 @@ MINIO_SECRET_ACCESS_KEY = correct_key storage := Packages.Storage assert.EqualValues(t, "minio", storage.Type) - assert.EqualValues(t, "gitea", storage.MinioConfig.Bucket) - assert.EqualValues(t, "packages/", storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", storage.MinioConfig.Bucket) + assert.Equal(t, "packages/", storage.MinioConfig.BasePath) assert.True(t, storage.MinioConfig.ServeDirect) } @@ -164,8 +164,8 @@ MINIO_SECRET_ACCESS_KEY = correct_key storage := Packages.Storage assert.EqualValues(t, "minio", storage.Type) - assert.EqualValues(t, "gitea", storage.MinioConfig.Bucket) - assert.EqualValues(t, "my_packages/", storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", storage.MinioConfig.Bucket) + assert.Equal(t, "my_packages/", storage.MinioConfig.BasePath) assert.True(t, storage.MinioConfig.ServeDirect) } @@ -192,7 +192,7 @@ MINIO_SECRET_ACCESS_KEY = correct_key storage := Packages.Storage assert.EqualValues(t, "minio", storage.Type) - assert.EqualValues(t, "gitea", storage.MinioConfig.Bucket) - assert.EqualValues(t, "my_packages/", storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", storage.MinioConfig.Bucket) + assert.Equal(t, "my_packages/", storage.MinioConfig.BasePath) assert.True(t, storage.MinioConfig.ServeDirect) } diff --git a/modules/setting/path.go b/modules/setting/path.go index 0fdc305aa1..f51457a620 100644 --- a/modules/setting/path.go +++ b/modules/setting/path.go @@ -11,6 +11,7 @@ import ( "strings" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/tempdir" ) var ( @@ -196,3 +197,18 @@ func InitWorkPathAndCfgProvider(getEnvFn func(name string) string, args ArgWorkP CustomPath = tmpCustomPath.Value CustomConf = tmpCustomConf.Value } + +// AppDataTempDir returns a managed temporary directory for the application data. +// Using empty sub will get the managed base temp directory, and it's safe to delete it. +// Gitea only creates subdirectories under it, but not the APP_TEMP_PATH directory itself. +// * When APP_TEMP_PATH="/tmp": the managed temp directory is "/tmp/gitea-tmp" +// * When APP_TEMP_PATH is not set: the managed temp directory is "/{APP_DATA_PATH}/tmp" +func AppDataTempDir(sub string) *tempdir.TempDir { + if appTempPathInternal != "" { + return tempdir.New(appTempPathInternal, "gitea-tmp/"+sub) + } + if AppDataPath == "" { + panic("setting.AppDataPath is not set") + } + return tempdir.New(AppDataPath, "tmp/"+sub) +} diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 43bfb3256d..f99c854e4f 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -62,17 +62,11 @@ var ( // Repository upload settings Upload struct { Enabled bool - TempPath string AllowedTypes string FileMaxSize int64 MaxFiles int } `ini:"-"` - // Repository local settings - Local struct { - LocalCopyPath string - } `ini:"-"` - // Pull request settings PullRequest struct { WorkInProgressPrefixes []string @@ -181,25 +175,16 @@ var ( // Repository upload settings Upload: struct { Enabled bool - TempPath string AllowedTypes string FileMaxSize int64 MaxFiles int }{ Enabled: true, - TempPath: "data/tmp/uploads", AllowedTypes: "", FileMaxSize: 50, MaxFiles: 5, }, - // Repository local settings - Local: struct { - LocalCopyPath string - }{ - LocalCopyPath: "tmp/local-repo", - }, - // Pull request settings PullRequest: struct { WorkInProgressPrefixes []string @@ -308,8 +293,6 @@ func loadRepositoryFrom(rootCfg ConfigProvider) { log.Fatal("Failed to map Repository.Editor settings: %v", err) } else if err = rootCfg.Section("repository.upload").MapTo(&Repository.Upload); err != nil { log.Fatal("Failed to map Repository.Upload settings: %v", err) - } else if err = rootCfg.Section("repository.local").MapTo(&Repository.Local); err != nil { - log.Fatal("Failed to map Repository.Local settings: %v", err) } else if err = rootCfg.Section("repository.pull-request").MapTo(&Repository.PullRequest); err != nil { log.Fatal("Failed to map Repository.PullRequest settings: %v", err) } @@ -361,10 +344,6 @@ func loadRepositoryFrom(rootCfg ConfigProvider) { } } - if !filepath.IsAbs(Repository.Upload.TempPath) { - Repository.Upload.TempPath = filepath.Join(AppWorkPath, Repository.Upload.TempPath) - } - if err := loadRepoArchiveFrom(rootCfg); err != nil { log.Fatal("loadRepoArchiveFrom: %v", err) } diff --git a/modules/setting/repository_archive_test.go b/modules/setting/repository_archive_test.go index a0f91f0da1..d5b95272d6 100644 --- a/modules/setting/repository_archive_test.go +++ b/modules/setting/repository_archive_test.go @@ -20,7 +20,7 @@ STORAGE_TYPE = minio assert.NoError(t, loadRepoArchiveFrom(cfg)) assert.EqualValues(t, "minio", RepoArchive.Storage.Type) - assert.EqualValues(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) + assert.Equal(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) // we can also configure packages storage directly iniStr = ` @@ -32,7 +32,7 @@ STORAGE_TYPE = minio assert.NoError(t, loadRepoArchiveFrom(cfg)) assert.EqualValues(t, "minio", RepoArchive.Storage.Type) - assert.EqualValues(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) + assert.Equal(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) // or we can indicate the storage type in the packages section iniStr = ` @@ -47,7 +47,7 @@ STORAGE_TYPE = minio assert.NoError(t, loadRepoArchiveFrom(cfg)) assert.EqualValues(t, "minio", RepoArchive.Storage.Type) - assert.EqualValues(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) + assert.Equal(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) // or we can indicate the storage type and minio base path in the packages section iniStr = ` @@ -63,7 +63,7 @@ STORAGE_TYPE = minio assert.NoError(t, loadRepoArchiveFrom(cfg)) assert.EqualValues(t, "minio", RepoArchive.Storage.Type) - assert.EqualValues(t, "my_archive/", RepoArchive.Storage.MinioConfig.BasePath) + assert.Equal(t, "my_archive/", RepoArchive.Storage.MinioConfig.BasePath) } func Test_RepoArchiveStorage(t *testing.T) { @@ -85,7 +85,7 @@ MINIO_SECRET_ACCESS_KEY = correct_key storage := RepoArchive.Storage assert.EqualValues(t, "minio", storage.Type) - assert.EqualValues(t, "gitea", storage.MinioConfig.Bucket) + assert.Equal(t, "gitea", storage.MinioConfig.Bucket) iniStr = ` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -107,5 +107,5 @@ MINIO_SECRET_ACCESS_KEY = correct_key storage = RepoArchive.Storage assert.EqualValues(t, "minio", storage.Type) - assert.EqualValues(t, "gitea", storage.MinioConfig.Bucket) + assert.Equal(t, "gitea", storage.MinioConfig.Bucket) } diff --git a/modules/setting/server.go b/modules/setting/server.go index e15b790906..ca635c8abe 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "net" "net/url" + "os" "path/filepath" "strconv" "strings" @@ -59,6 +60,8 @@ var ( // AssetVersion holds a opaque value that is used for cache-busting assets AssetVersion string + appTempPathInternal string // the temporary path for the app, it is only an internal variable, do not use it, always use AppDataTempDir + Protocol Scheme UseProxyProtocol bool // `ini:"USE_PROXY_PROTOCOL"` ProxyProtocolTLSBridging bool //`ini:"PROXY_PROTOCOL_TLS_BRIDGING"` @@ -330,6 +333,19 @@ func loadServerFrom(rootCfg ConfigProvider) { if !filepath.IsAbs(AppDataPath) { AppDataPath = filepath.ToSlash(filepath.Join(AppWorkPath, AppDataPath)) } + if IsInTesting && HasInstallLock(rootCfg) { + // FIXME: in testing, the "app data" directory is not correctly initialized before loading settings + if _, err := os.Stat(AppDataPath); err != nil { + _ = os.MkdirAll(AppDataPath, os.ModePerm) + } + } + + appTempPathInternal = sec.Key("APP_TEMP_PATH").String() + if appTempPathInternal != "" { + if _, err := os.Stat(appTempPathInternal); err != nil { + log.Fatal("APP_TEMP_PATH %q is not accessible: %v", appTempPathInternal, err) + } + } EnableGzip = sec.Key("ENABLE_GZIP").MustBool() EnablePprof = sec.Key("ENABLE_PPROF").MustBool(false) diff --git a/modules/setting/service.go b/modules/setting/service.go index 8c1843eeb7..d9535efec6 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -43,7 +43,8 @@ var Service = struct { ShowRegistrationButton bool EnablePasswordSignInForm bool ShowMilestonesDashboardPage bool - RequireSignInView bool + RequireSignInViewStrict bool + BlockAnonymousAccessExpensive bool EnableNotifyMail bool EnableBasicAuth bool EnablePasskeyAuth bool @@ -159,7 +160,18 @@ func loadServiceFrom(rootCfg ConfigProvider) { Service.EmailDomainBlockList = CompileEmailGlobList(sec, "EMAIL_DOMAIN_BLOCKLIST") Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!(Service.DisableRegistration || Service.AllowOnlyExternalRegistration)) Service.ShowMilestonesDashboardPage = sec.Key("SHOW_MILESTONES_DASHBOARD_PAGE").MustBool(true) - Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool() + + // boolean values are considered as "strict" + var err error + Service.RequireSignInViewStrict, err = sec.Key("REQUIRE_SIGNIN_VIEW").Bool() + if s := sec.Key("REQUIRE_SIGNIN_VIEW").String(); err != nil && s != "" { + // non-boolean value only supports "expensive" at the moment + Service.BlockAnonymousAccessExpensive = s == "expensive" + if !Service.BlockAnonymousAccessExpensive { + log.Fatal("Invalid config option: REQUIRE_SIGNIN_VIEW = %s", s) + } + } + Service.EnableBasicAuth = sec.Key("ENABLE_BASIC_AUTHENTICATION").MustBool(true) Service.EnablePasswordSignInForm = sec.Key("ENABLE_PASSWORD_SIGNIN_FORM").MustBool(true) Service.EnablePasskeyAuth = sec.Key("ENABLE_PASSKEY_AUTHENTICATION").MustBool(true) diff --git a/modules/setting/service_test.go b/modules/setting/service_test.go index 1647bcec16..73736b793a 100644 --- a/modules/setting/service_test.go +++ b/modules/setting/service_test.go @@ -7,16 +7,14 @@ import ( "testing" "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/test" "github.com/gobwas/glob" "github.com/stretchr/testify/assert" ) func TestLoadServices(t *testing.T) { - oldService := Service - defer func() { - Service = oldService - }() + defer test.MockVariableValue(&Service)() cfg, err := NewConfigProviderFromData(` [service] @@ -48,10 +46,7 @@ EMAIL_DOMAIN_BLOCKLIST = d3, *.b } func TestLoadServiceVisibilityModes(t *testing.T) { - oldService := Service - defer func() { - Service = oldService - }() + defer test.MockVariableValue(&Service)() kases := map[string]func(){ ` @@ -130,3 +125,33 @@ ALLOWED_USER_VISIBILITY_MODES = public, limit, privated }) } } + +func TestLoadServiceRequireSignInView(t *testing.T) { + defer test.MockVariableValue(&Service)() + + cfg, err := NewConfigProviderFromData(` +[service] +`) + assert.NoError(t, err) + loadServiceFrom(cfg) + assert.False(t, Service.RequireSignInViewStrict) + assert.False(t, Service.BlockAnonymousAccessExpensive) + + cfg, err = NewConfigProviderFromData(` +[service] +REQUIRE_SIGNIN_VIEW = true +`) + assert.NoError(t, err) + loadServiceFrom(cfg) + assert.True(t, Service.RequireSignInViewStrict) + assert.False(t, Service.BlockAnonymousAccessExpensive) + + cfg, err = NewConfigProviderFromData(` +[service] +REQUIRE_SIGNIN_VIEW = expensive +`) + assert.NoError(t, err) + loadServiceFrom(cfg) + assert.False(t, Service.RequireSignInViewStrict) + assert.True(t, Service.BlockAnonymousAccessExpensive) +} diff --git a/modules/setting/ssh.go b/modules/setting/ssh.go index 46eb49bfd4..da8cdf58d2 100644 --- a/modules/setting/ssh.go +++ b/modules/setting/ssh.go @@ -4,7 +4,6 @@ package setting import ( - "os" "path/filepath" "strings" "text/template" @@ -31,8 +30,6 @@ var SSH = struct { ServerKeyExchanges []string `ini:"SSH_SERVER_KEY_EXCHANGES"` ServerMACs []string `ini:"SSH_SERVER_MACS"` ServerHostKeys []string `ini:"SSH_SERVER_HOST_KEYS"` - KeyTestPath string `ini:"SSH_KEY_TEST_PATH"` - KeygenPath string `ini:"SSH_KEYGEN_PATH"` AuthorizedKeysBackup bool `ini:"SSH_AUTHORIZED_KEYS_BACKUP"` AuthorizedPrincipalsBackup bool `ini:"SSH_AUTHORIZED_PRINCIPALS_BACKUP"` AuthorizedKeysCommandTemplate string `ini:"SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE"` @@ -57,7 +54,6 @@ var SSH = struct { ServerCiphers: []string{"chacha20-poly1305@openssh.com", "aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "aes256-gcm@openssh.com"}, ServerKeyExchanges: []string{"curve25519-sha256", "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521", "diffie-hellman-group14-sha256", "diffie-hellman-group14-sha1"}, ServerMACs: []string{"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1"}, - KeygenPath: "", MinimumKeySizeCheck: true, MinimumKeySizes: map[string]int{"ed25519": 256, "ed25519-sk": 256, "ecdsa": 256, "ecdsa-sk": 256, "rsa": 3071}, ServerHostKeys: []string{"ssh/gitea.rsa", "ssh/gogs.rsa"}, @@ -123,7 +119,6 @@ func loadSSHFrom(rootCfg ConfigProvider) { if len(serverMACs) > 0 { SSH.ServerMACs = serverMACs } - SSH.KeyTestPath = os.TempDir() if err = sec.MapTo(&SSH); err != nil { log.Fatal("Failed to map SSH settings: %v", err) } @@ -133,7 +128,6 @@ func loadSSHFrom(rootCfg ConfigProvider) { } } - SSH.KeygenPath = sec.Key("SSH_KEYGEN_PATH").String() SSH.Port = sec.Key("SSH_PORT").MustInt(22) SSH.ListenPort = sec.Key("SSH_LISTEN_PORT").MustInt(SSH.Port) SSH.UseProxyProtocol = sec.Key("SSH_SERVER_USE_PROXY_PROTOCOL").MustBool(false) diff --git a/modules/setting/storage.go b/modules/setting/storage.go index d3d1fb9f30..e1d9b1fa7a 100644 --- a/modules/setting/storage.go +++ b/modules/setting/storage.go @@ -210,8 +210,8 @@ func getStorageTargetSection(rootCfg ConfigProvider, name, typ string, sec Confi targetSec, _ := rootCfg.GetSection(storageSectionName + "." + name) if targetSec != nil { targetType := targetSec.Key("STORAGE_TYPE").String() - switch { - case targetType == "": + switch targetType { + case "": if targetSec.Key("PATH").String() == "" { // both storage type and path are empty, use default return getDefaultStorageSection(rootCfg), targetSecIsDefault, nil } diff --git a/modules/setting/storage_test.go b/modules/setting/storage_test.go index afff85537e..6f5a54c41c 100644 --- a/modules/setting/storage_test.go +++ b/modules/setting/storage_test.go @@ -26,16 +26,16 @@ MINIO_BUCKET = gitea-storage assert.NoError(t, err) assert.NoError(t, loadAttachmentFrom(cfg)) - assert.EqualValues(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea-attachment", Attachment.Storage.MinioConfig.Bucket) + assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) assert.NoError(t, loadLFSFrom(cfg)) - assert.EqualValues(t, "gitea-lfs", LFS.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea-lfs", LFS.Storage.MinioConfig.Bucket) + assert.Equal(t, "lfs/", LFS.Storage.MinioConfig.BasePath) assert.NoError(t, loadAvatarsFrom(cfg)) - assert.EqualValues(t, "gitea-storage", Avatar.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "avatars/", Avatar.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea-storage", Avatar.Storage.MinioConfig.Bucket) + assert.Equal(t, "avatars/", Avatar.Storage.MinioConfig.BasePath) } func Test_getStorageUseOtherNameAsType(t *testing.T) { @@ -51,12 +51,12 @@ MINIO_BUCKET = gitea-storage assert.NoError(t, err) assert.NoError(t, loadAttachmentFrom(cfg)) - assert.EqualValues(t, "gitea-storage", Attachment.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea-storage", Attachment.Storage.MinioConfig.Bucket) + assert.Equal(t, "attachments/", Attachment.Storage.MinioConfig.BasePath) assert.NoError(t, loadLFSFrom(cfg)) - assert.EqualValues(t, "gitea-storage", LFS.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "lfs/", LFS.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea-storage", LFS.Storage.MinioConfig.Bucket) + assert.Equal(t, "lfs/", LFS.Storage.MinioConfig.BasePath) } func Test_getStorageInheritStorageType(t *testing.T) { @@ -69,32 +69,32 @@ STORAGE_TYPE = minio assert.NoError(t, loadPackagesFrom(cfg)) assert.EqualValues(t, "minio", Packages.Storage.Type) - assert.EqualValues(t, "gitea", Packages.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "packages/", Packages.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", Packages.Storage.MinioConfig.Bucket) + assert.Equal(t, "packages/", Packages.Storage.MinioConfig.BasePath) assert.NoError(t, loadRepoArchiveFrom(cfg)) assert.EqualValues(t, "minio", RepoArchive.Storage.Type) - assert.EqualValues(t, "gitea", RepoArchive.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", RepoArchive.Storage.MinioConfig.Bucket) + assert.Equal(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) assert.NoError(t, loadActionsFrom(cfg)) assert.EqualValues(t, "minio", Actions.LogStorage.Type) - assert.EqualValues(t, "gitea", Actions.LogStorage.MinioConfig.Bucket) - assert.EqualValues(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) + assert.Equal(t, "gitea", Actions.LogStorage.MinioConfig.Bucket) + assert.Equal(t, "actions_log/", Actions.LogStorage.MinioConfig.BasePath) assert.EqualValues(t, "minio", Actions.ArtifactStorage.Type) - assert.EqualValues(t, "gitea", Actions.ArtifactStorage.MinioConfig.Bucket) - assert.EqualValues(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) + assert.Equal(t, "gitea", Actions.ArtifactStorage.MinioConfig.Bucket) + assert.Equal(t, "actions_artifacts/", Actions.ArtifactStorage.MinioConfig.BasePath) assert.NoError(t, loadAvatarsFrom(cfg)) assert.EqualValues(t, "minio", Avatar.Storage.Type) - assert.EqualValues(t, "gitea", Avatar.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "avatars/", Avatar.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", Avatar.Storage.MinioConfig.Bucket) + assert.Equal(t, "avatars/", Avatar.Storage.MinioConfig.BasePath) assert.NoError(t, loadRepoAvatarFrom(cfg)) assert.EqualValues(t, "minio", RepoAvatar.Storage.Type) - assert.EqualValues(t, "gitea", RepoAvatar.Storage.MinioConfig.Bucket) - assert.EqualValues(t, "repo-avatars/", RepoAvatar.Storage.MinioConfig.BasePath) + assert.Equal(t, "gitea", RepoAvatar.Storage.MinioConfig.Bucket) + assert.Equal(t, "repo-avatars/", RepoAvatar.Storage.MinioConfig.BasePath) } func Test_getStorageInheritStorageTypeAzureBlob(t *testing.T) { @@ -107,32 +107,32 @@ STORAGE_TYPE = azureblob assert.NoError(t, loadPackagesFrom(cfg)) assert.EqualValues(t, "azureblob", Packages.Storage.Type) - assert.EqualValues(t, "gitea", Packages.Storage.AzureBlobConfig.Container) - assert.EqualValues(t, "packages/", Packages.Storage.AzureBlobConfig.BasePath) + assert.Equal(t, "gitea", Packages.Storage.AzureBlobConfig.Container) + assert.Equal(t, "packages/", Packages.Storage.AzureBlobConfig.BasePath) assert.NoError(t, loadRepoArchiveFrom(cfg)) assert.EqualValues(t, "azureblob", RepoArchive.Storage.Type) - assert.EqualValues(t, "gitea", RepoArchive.Storage.AzureBlobConfig.Container) - assert.EqualValues(t, "repo-archive/", RepoArchive.Storage.AzureBlobConfig.BasePath) + assert.Equal(t, "gitea", RepoArchive.Storage.AzureBlobConfig.Container) + assert.Equal(t, "repo-archive/", RepoArchive.Storage.AzureBlobConfig.BasePath) assert.NoError(t, loadActionsFrom(cfg)) assert.EqualValues(t, "azureblob", Actions.LogStorage.Type) - assert.EqualValues(t, "gitea", Actions.LogStorage.AzureBlobConfig.Container) - assert.EqualValues(t, "actions_log/", Actions.LogStorage.AzureBlobConfig.BasePath) + assert.Equal(t, "gitea", Actions.LogStorage.AzureBlobConfig.Container) + assert.Equal(t, "actions_log/", Actions.LogStorage.AzureBlobConfig.BasePath) assert.EqualValues(t, "azureblob", Actions.ArtifactStorage.Type) - assert.EqualValues(t, "gitea", Actions.ArtifactStorage.AzureBlobConfig.Container) - assert.EqualValues(t, "actions_artifacts/", Actions.ArtifactStorage.AzureBlobConfig.BasePath) + assert.Equal(t, "gitea", Actions.ArtifactStorage.AzureBlobConfig.Container) + assert.Equal(t, "actions_artifacts/", Actions.ArtifactStorage.AzureBlobConfig.BasePath) assert.NoError(t, loadAvatarsFrom(cfg)) assert.EqualValues(t, "azureblob", Avatar.Storage.Type) - assert.EqualValues(t, "gitea", Avatar.Storage.AzureBlobConfig.Container) - assert.EqualValues(t, "avatars/", Avatar.Storage.AzureBlobConfig.BasePath) + assert.Equal(t, "gitea", Avatar.Storage.AzureBlobConfig.Container) + assert.Equal(t, "avatars/", Avatar.Storage.AzureBlobConfig.BasePath) assert.NoError(t, loadRepoAvatarFrom(cfg)) assert.EqualValues(t, "azureblob", RepoAvatar.Storage.Type) - assert.EqualValues(t, "gitea", RepoAvatar.Storage.AzureBlobConfig.Container) - assert.EqualValues(t, "repo-avatars/", RepoAvatar.Storage.AzureBlobConfig.BasePath) + assert.Equal(t, "gitea", RepoAvatar.Storage.AzureBlobConfig.Container) + assert.Equal(t, "repo-avatars/", RepoAvatar.Storage.AzureBlobConfig.BasePath) } type testLocalStoragePathCase struct { @@ -151,7 +151,7 @@ func testLocalStoragePath(t *testing.T, appDataPath, iniStr string, cases []test assert.EqualValues(t, "local", storage.Type) assert.True(t, filepath.IsAbs(storage.Path)) - assert.EqualValues(t, filepath.Clean(c.expectedPath), filepath.Clean(storage.Path)) + assert.Equal(t, filepath.Clean(c.expectedPath), filepath.Clean(storage.Path)) } } @@ -389,8 +389,8 @@ MINIO_SECRET_ACCESS_KEY = my_secret_key assert.NoError(t, loadRepoArchiveFrom(cfg)) cp := RepoArchive.Storage.ToShadowCopy() - assert.EqualValues(t, "******", cp.MinioConfig.AccessKeyID) - assert.EqualValues(t, "******", cp.MinioConfig.SecretAccessKey) + assert.Equal(t, "******", cp.MinioConfig.AccessKeyID) + assert.Equal(t, "******", cp.MinioConfig.SecretAccessKey) } func Test_getStorageConfiguration24(t *testing.T) { @@ -445,10 +445,10 @@ MINIO_USE_SSL = true `) assert.NoError(t, err) assert.NoError(t, loadRepoArchiveFrom(cfg)) - assert.EqualValues(t, "my_access_key", RepoArchive.Storage.MinioConfig.AccessKeyID) - assert.EqualValues(t, "my_secret_key", RepoArchive.Storage.MinioConfig.SecretAccessKey) + assert.Equal(t, "my_access_key", RepoArchive.Storage.MinioConfig.AccessKeyID) + assert.Equal(t, "my_secret_key", RepoArchive.Storage.MinioConfig.SecretAccessKey) assert.True(t, RepoArchive.Storage.MinioConfig.UseSSL) - assert.EqualValues(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) + assert.Equal(t, "repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) } func Test_getStorageConfiguration28(t *testing.T) { @@ -462,10 +462,10 @@ MINIO_BASE_PATH = /prefix `) assert.NoError(t, err) assert.NoError(t, loadRepoArchiveFrom(cfg)) - assert.EqualValues(t, "my_access_key", RepoArchive.Storage.MinioConfig.AccessKeyID) - assert.EqualValues(t, "my_secret_key", RepoArchive.Storage.MinioConfig.SecretAccessKey) + assert.Equal(t, "my_access_key", RepoArchive.Storage.MinioConfig.AccessKeyID) + assert.Equal(t, "my_secret_key", RepoArchive.Storage.MinioConfig.SecretAccessKey) assert.True(t, RepoArchive.Storage.MinioConfig.UseSSL) - assert.EqualValues(t, "/prefix/repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) + assert.Equal(t, "/prefix/repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) cfg, err = NewConfigProviderFromData(` [storage] @@ -476,9 +476,9 @@ MINIO_BASE_PATH = /prefix `) assert.NoError(t, err) assert.NoError(t, loadRepoArchiveFrom(cfg)) - assert.EqualValues(t, "127.0.0.1", RepoArchive.Storage.MinioConfig.IamEndpoint) + assert.Equal(t, "127.0.0.1", RepoArchive.Storage.MinioConfig.IamEndpoint) assert.True(t, RepoArchive.Storage.MinioConfig.UseSSL) - assert.EqualValues(t, "/prefix/repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) + assert.Equal(t, "/prefix/repo-archive/", RepoArchive.Storage.MinioConfig.BasePath) cfg, err = NewConfigProviderFromData(` [storage] @@ -493,10 +493,10 @@ MINIO_BASE_PATH = /lfs `) assert.NoError(t, err) assert.NoError(t, loadLFSFrom(cfg)) - assert.EqualValues(t, "my_access_key", LFS.Storage.MinioConfig.AccessKeyID) - assert.EqualValues(t, "my_secret_key", LFS.Storage.MinioConfig.SecretAccessKey) + assert.Equal(t, "my_access_key", LFS.Storage.MinioConfig.AccessKeyID) + assert.Equal(t, "my_secret_key", LFS.Storage.MinioConfig.SecretAccessKey) assert.True(t, LFS.Storage.MinioConfig.UseSSL) - assert.EqualValues(t, "/lfs", LFS.Storage.MinioConfig.BasePath) + assert.Equal(t, "/lfs", LFS.Storage.MinioConfig.BasePath) cfg, err = NewConfigProviderFromData(` [storage] @@ -511,10 +511,10 @@ MINIO_BASE_PATH = /lfs `) assert.NoError(t, err) assert.NoError(t, loadLFSFrom(cfg)) - assert.EqualValues(t, "my_access_key", LFS.Storage.MinioConfig.AccessKeyID) - assert.EqualValues(t, "my_secret_key", LFS.Storage.MinioConfig.SecretAccessKey) + assert.Equal(t, "my_access_key", LFS.Storage.MinioConfig.AccessKeyID) + assert.Equal(t, "my_secret_key", LFS.Storage.MinioConfig.SecretAccessKey) assert.True(t, LFS.Storage.MinioConfig.UseSSL) - assert.EqualValues(t, "/lfs", LFS.Storage.MinioConfig.BasePath) + assert.Equal(t, "/lfs", LFS.Storage.MinioConfig.BasePath) } func Test_getStorageConfiguration29(t *testing.T) { @@ -539,9 +539,9 @@ AZURE_BLOB_ACCOUNT_KEY = my_account_key `) assert.NoError(t, err) assert.NoError(t, loadRepoArchiveFrom(cfg)) - assert.EqualValues(t, "my_account_name", RepoArchive.Storage.AzureBlobConfig.AccountName) - assert.EqualValues(t, "my_account_key", RepoArchive.Storage.AzureBlobConfig.AccountKey) - assert.EqualValues(t, "repo-archive/", RepoArchive.Storage.AzureBlobConfig.BasePath) + assert.Equal(t, "my_account_name", RepoArchive.Storage.AzureBlobConfig.AccountName) + assert.Equal(t, "my_account_key", RepoArchive.Storage.AzureBlobConfig.AccountKey) + assert.Equal(t, "repo-archive/", RepoArchive.Storage.AzureBlobConfig.BasePath) } func Test_getStorageConfiguration31(t *testing.T) { @@ -554,9 +554,9 @@ AZURE_BLOB_BASE_PATH = /prefix `) assert.NoError(t, err) assert.NoError(t, loadRepoArchiveFrom(cfg)) - assert.EqualValues(t, "my_account_name", RepoArchive.Storage.AzureBlobConfig.AccountName) - assert.EqualValues(t, "my_account_key", RepoArchive.Storage.AzureBlobConfig.AccountKey) - assert.EqualValues(t, "/prefix/repo-archive/", RepoArchive.Storage.AzureBlobConfig.BasePath) + assert.Equal(t, "my_account_name", RepoArchive.Storage.AzureBlobConfig.AccountName) + assert.Equal(t, "my_account_key", RepoArchive.Storage.AzureBlobConfig.AccountKey) + assert.Equal(t, "/prefix/repo-archive/", RepoArchive.Storage.AzureBlobConfig.BasePath) cfg, err = NewConfigProviderFromData(` [storage] @@ -570,9 +570,9 @@ AZURE_BLOB_BASE_PATH = /lfs `) assert.NoError(t, err) assert.NoError(t, loadLFSFrom(cfg)) - assert.EqualValues(t, "my_account_name", LFS.Storage.AzureBlobConfig.AccountName) - assert.EqualValues(t, "my_account_key", LFS.Storage.AzureBlobConfig.AccountKey) - assert.EqualValues(t, "/lfs", LFS.Storage.AzureBlobConfig.BasePath) + assert.Equal(t, "my_account_name", LFS.Storage.AzureBlobConfig.AccountName) + assert.Equal(t, "my_account_key", LFS.Storage.AzureBlobConfig.AccountKey) + assert.Equal(t, "/lfs", LFS.Storage.AzureBlobConfig.BasePath) cfg, err = NewConfigProviderFromData(` [storage] @@ -586,7 +586,7 @@ AZURE_BLOB_BASE_PATH = /lfs `) assert.NoError(t, err) assert.NoError(t, loadLFSFrom(cfg)) - assert.EqualValues(t, "my_account_name", LFS.Storage.AzureBlobConfig.AccountName) - assert.EqualValues(t, "my_account_key", LFS.Storage.AzureBlobConfig.AccountKey) - assert.EqualValues(t, "/lfs", LFS.Storage.AzureBlobConfig.BasePath) + assert.Equal(t, "my_account_name", LFS.Storage.AzureBlobConfig.AccountName) + assert.Equal(t, "my_account_key", LFS.Storage.AzureBlobConfig.AccountKey) + assert.Equal(t, "/lfs", LFS.Storage.AzureBlobConfig.BasePath) } diff --git a/modules/ssh/init.go b/modules/ssh/init.go index 21d4f89936..fdc11632e2 100644 --- a/modules/ssh/init.go +++ b/modules/ssh/init.go @@ -32,11 +32,6 @@ func Init() error { builtinUnused() - // FIXME: why 0o644 for a directory ..... - if err := os.MkdirAll(setting.SSH.KeyTestPath, 0o644); err != nil { - return fmt.Errorf("failed to create directory %q for ssh key test: %w", setting.SSH.KeyTestPath, err) - } - if len(setting.SSH.TrustedUserCAKeys) > 0 && setting.SSH.AuthorizedPrincipalsEnabled { caKeysFileName := setting.SSH.TrustedUserCAKeysFile caKeysFileDir := filepath.Dir(caKeysFileName) diff --git a/modules/ssh/ssh.go b/modules/ssh/ssh.go index 7479cfbd95..ff0ad34a0d 100644 --- a/modules/ssh/ssh.go +++ b/modules/ssh/ssh.go @@ -11,7 +11,6 @@ import ( "crypto/x509" "encoding/pem" "errors" - "fmt" "io" "net" "os" @@ -216,7 +215,7 @@ func publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool { ctx.Permissions().Permissions = &gossh.Permissions{} setPermExt := func(keyID int64) { ctx.Permissions().Permissions.Extensions = map[string]string{ - giteaPermissionExtensionKeyID: fmt.Sprint(keyID), + giteaPermissionExtensionKeyID: strconv.FormatInt(keyID, 10), } } diff --git a/modules/storage/azureblob_test.go b/modules/storage/azureblob_test.go index 6905db5008..b3791b4916 100644 --- a/modules/storage/azureblob_test.go +++ b/modules/storage/azureblob_test.go @@ -4,9 +4,9 @@ package storage import ( - "bytes" "io" "os" + "strings" "testing" "code.gitea.io/gitea/modules/setting" @@ -33,14 +33,14 @@ func TestAzureBlobStorageIterator(t *testing.T) { func TestAzureBlobStoragePath(t *testing.T) { m := &AzureBlobStorage{cfg: &setting.AzureBlobStorageConfig{BasePath: ""}} - assert.Equal(t, "", m.buildAzureBlobPath("/")) - assert.Equal(t, "", m.buildAzureBlobPath(".")) + assert.Empty(t, m.buildAzureBlobPath("/")) + assert.Empty(t, m.buildAzureBlobPath(".")) assert.Equal(t, "a", m.buildAzureBlobPath("/a")) assert.Equal(t, "a/b", m.buildAzureBlobPath("/a/b/")) m = &AzureBlobStorage{cfg: &setting.AzureBlobStorageConfig{BasePath: "/"}} - assert.Equal(t, "", m.buildAzureBlobPath("/")) - assert.Equal(t, "", m.buildAzureBlobPath(".")) + assert.Empty(t, m.buildAzureBlobPath("/")) + assert.Empty(t, m.buildAzureBlobPath(".")) assert.Equal(t, "a", m.buildAzureBlobPath("/a")) assert.Equal(t, "a/b", m.buildAzureBlobPath("/a/b/")) @@ -76,7 +76,7 @@ func Test_azureBlobObject(t *testing.T) { assert.NoError(t, err) data := "Q2xTckt6Y1hDOWh0" - _, err = s.Save("test.txt", bytes.NewBufferString(data), int64(len(data))) + _, err = s.Save("test.txt", strings.NewReader(data), int64(len(data))) assert.NoError(t, err) obj, err := s.Open("test.txt") assert.NoError(t, err) @@ -86,7 +86,7 @@ func Test_azureBlobObject(t *testing.T) { buf1 := make([]byte, 3) read, err := obj.Read(buf1) assert.NoError(t, err) - assert.EqualValues(t, 3, read) + assert.Equal(t, 3, read) assert.Equal(t, data[2:5], string(buf1)) offset, err = obj.Seek(-5, io.SeekEnd) assert.NoError(t, err) @@ -94,7 +94,7 @@ func Test_azureBlobObject(t *testing.T) { buf2 := make([]byte, 4) read, err = obj.Read(buf2) assert.NoError(t, err) - assert.EqualValues(t, 4, read) + assert.Equal(t, 4, read) assert.Equal(t, data[11:15], string(buf2)) assert.NoError(t, obj.Close()) assert.NoError(t, s.Delete("test.txt")) diff --git a/modules/storage/local_test.go b/modules/storage/local_test.go index 540ced1655..0592fd716b 100644 --- a/modules/storage/local_test.go +++ b/modules/storage/local_test.go @@ -48,7 +48,7 @@ func TestBuildLocalPath(t *testing.T) { t.Run(k.path, func(t *testing.T) { l := LocalStorage{dir: k.localDir} - assert.EqualValues(t, k.expected, l.buildLocalPath(k.path)) + assert.Equal(t, k.expected, l.buildLocalPath(k.path)) }) } } diff --git a/modules/storage/minio.go b/modules/storage/minio.go index 6b92be61fb..1c5d25b2d4 100644 --- a/modules/storage/minio.go +++ b/modules/storage/minio.go @@ -86,13 +86,14 @@ func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage, log.Info("Creating Minio storage at %s:%s with base path %s", config.Endpoint, config.Bucket, config.BasePath) var lookup minio.BucketLookupType - if config.BucketLookUpType == "auto" || config.BucketLookUpType == "" { + switch config.BucketLookUpType { + case "auto", "": lookup = minio.BucketLookupAuto - } else if config.BucketLookUpType == "dns" { + case "dns": lookup = minio.BucketLookupDNS - } else if config.BucketLookUpType == "path" { + case "path": lookup = minio.BucketLookupPath - } else { + default: return nil, fmt.Errorf("invalid minio bucket lookup type: %s", config.BucketLookUpType) } diff --git a/modules/storage/minio_test.go b/modules/storage/minio_test.go index 395da051e8..2726d765dd 100644 --- a/modules/storage/minio_test.go +++ b/modules/storage/minio_test.go @@ -34,19 +34,19 @@ func TestMinioStorageIterator(t *testing.T) { func TestMinioStoragePath(t *testing.T) { m := &MinioStorage{basePath: ""} - assert.Equal(t, "", m.buildMinioPath("/")) - assert.Equal(t, "", m.buildMinioPath(".")) + assert.Empty(t, m.buildMinioPath("/")) + assert.Empty(t, m.buildMinioPath(".")) assert.Equal(t, "a", m.buildMinioPath("/a")) assert.Equal(t, "a/b", m.buildMinioPath("/a/b/")) - assert.Equal(t, "", m.buildMinioDirPrefix("")) + assert.Empty(t, m.buildMinioDirPrefix("")) assert.Equal(t, "a/", m.buildMinioDirPrefix("/a/")) m = &MinioStorage{basePath: "/"} - assert.Equal(t, "", m.buildMinioPath("/")) - assert.Equal(t, "", m.buildMinioPath(".")) + assert.Empty(t, m.buildMinioPath("/")) + assert.Empty(t, m.buildMinioPath(".")) assert.Equal(t, "a", m.buildMinioPath("/a")) assert.Equal(t, "a/b", m.buildMinioPath("/a/b/")) - assert.Equal(t, "", m.buildMinioDirPrefix("")) + assert.Empty(t, m.buildMinioDirPrefix("")) assert.Equal(t, "a/", m.buildMinioDirPrefix("/a/")) m = &MinioStorage{basePath: "/base"} diff --git a/modules/storage/storage_test.go b/modules/storage/storage_test.go index 7edde558f3..08f274e74b 100644 --- a/modules/storage/storage_test.go +++ b/modules/storage/storage_test.go @@ -4,7 +4,7 @@ package storage import ( - "bytes" + "strings" "testing" "code.gitea.io/gitea/modules/setting" @@ -26,7 +26,7 @@ func testStorageIterator(t *testing.T, typStr Type, cfg *setting.Storage) { {"b/x 4.txt", "bx4"}, } for _, f := range testFiles { - _, err = l.Save(f[0], bytes.NewBufferString(f[1]), -1) + _, err = l.Save(f[0], strings.NewReader(f[1]), -1) assert.NoError(t, err) } diff --git a/modules/structs/package.go b/modules/structs/package.go index a9a9429de2..1973f925a5 100644 --- a/modules/structs/package.go +++ b/modules/structs/package.go @@ -23,8 +23,8 @@ type Package struct { // PackageFile represents a package file type PackageFile struct { - ID int64 `json:"id"` - Size int64 + ID int64 `json:"id"` + Size int64 `json:"size"` Name string `json:"name"` HashMD5 string `json:"md5"` HashSHA1 string `json:"sha1"` diff --git a/modules/structs/repo_file.go b/modules/structs/repo_file.go index 82bde96ab6..0cd88b3703 100644 --- a/modules/structs/repo_file.go +++ b/modules/structs/repo_file.go @@ -4,6 +4,8 @@ package structs +import "time" + // FileOptions options for all file APIs type FileOptions struct { // message (optional) for the commit of this file. if not supplied, a default message will be used @@ -121,6 +123,10 @@ type ContentsResponse struct { Path string `json:"path"` SHA string `json:"sha"` LastCommitSHA string `json:"last_commit_sha"` + // swagger:strfmt date-time + LastCommitterDate time.Time `json:"last_committer_date"` + // swagger:strfmt date-time + LastAuthorDate time.Time `json:"last_author_date"` // `type` will be `file`, `dir`, `symlink`, or `submodule` Type string `json:"type"` Size int64 `json:"size"` diff --git a/modules/system/appstate_test.go b/modules/system/appstate_test.go index 911319d00a..b5c057cf88 100644 --- a/modules/system/appstate_test.go +++ b/modules/system/appstate_test.go @@ -38,8 +38,8 @@ func TestAppStateDB(t *testing.T) { item1 := new(testItem1) assert.NoError(t, as.Get(db.DefaultContext, item1)) - assert.Equal(t, "", item1.Val1) - assert.EqualValues(t, 0, item1.Val2) + assert.Empty(t, item1.Val1) + assert.Equal(t, 0, item1.Val2) item1 = new(testItem1) item1.Val1 = "a" @@ -53,7 +53,7 @@ func TestAppStateDB(t *testing.T) { item1 = new(testItem1) assert.NoError(t, as.Get(db.DefaultContext, item1)) assert.Equal(t, "a", item1.Val1) - assert.EqualValues(t, 2, item1.Val2) + assert.Equal(t, 2, item1.Val2) item2 = new(testItem2) assert.NoError(t, as.Get(db.DefaultContext, item2)) diff --git a/modules/tempdir/tempdir.go b/modules/tempdir/tempdir.go new file mode 100644 index 0000000000..22c2e4ea16 --- /dev/null +++ b/modules/tempdir/tempdir.go @@ -0,0 +1,112 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package tempdir + +import ( + "os" + "path/filepath" + "time" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/util" +) + +type TempDir struct { + // base is the base directory for temporary files, it must exist before accessing and won't be created automatically. + // for example: base="/system-tmpdir", sub="gitea-tmp" + base, sub string +} + +func (td *TempDir) JoinPath(elems ...string) string { + return filepath.Join(append([]string{td.base, td.sub}, elems...)...) +} + +// MkdirAllSub works like os.MkdirAll, but the base directory must exist +func (td *TempDir) MkdirAllSub(dir string) (string, error) { + if _, err := os.Stat(td.base); err != nil { + return "", err + } + full := filepath.Join(td.base, td.sub, dir) + if err := os.MkdirAll(full, os.ModePerm); err != nil { + return "", err + } + return full, nil +} + +func (td *TempDir) prepareDirWithPattern(elems ...string) (dir, pattern string, err error) { + if _, err = os.Stat(td.base); err != nil { + return "", "", err + } + dir, pattern = filepath.Split(filepath.Join(append([]string{td.base, td.sub}, elems...)...)) + if err = os.MkdirAll(dir, os.ModePerm); err != nil { + return "", "", err + } + return dir, pattern, nil +} + +// MkdirTempRandom works like os.MkdirTemp, the last path field is the "pattern" +func (td *TempDir) MkdirTempRandom(elems ...string) (string, func(), error) { + dir, pattern, err := td.prepareDirWithPattern(elems...) + if err != nil { + return "", nil, err + } + dir, err = os.MkdirTemp(dir, pattern) + if err != nil { + return "", nil, err + } + return dir, func() { + if err := util.RemoveAll(dir); err != nil { + log.Error("Failed to remove temp directory %s: %v", dir, err) + } + }, nil +} + +// CreateTempFileRandom works like os.CreateTemp, the last path field is the "pattern" +func (td *TempDir) CreateTempFileRandom(elems ...string) (*os.File, func(), error) { + dir, pattern, err := td.prepareDirWithPattern(elems...) + if err != nil { + return nil, nil, err + } + f, err := os.CreateTemp(dir, pattern) + if err != nil { + return nil, nil, err + } + filename := f.Name() + return f, func() { + _ = f.Close() + if err := util.Remove(filename); err != nil { + log.Error("Unable to remove temporary file: %s: Error: %v", filename, err) + } + }, err +} + +func (td *TempDir) RemoveOutdated(d time.Duration) { + var remove func(path string) + remove = func(path string) { + entries, _ := os.ReadDir(path) + for _, entry := range entries { + full := filepath.Join(path, entry.Name()) + if entry.IsDir() { + remove(full) + _ = os.Remove(full) + continue + } + info, err := entry.Info() + if err == nil && time.Since(info.ModTime()) > d { + _ = os.Remove(full) + } + } + } + remove(td.JoinPath("")) +} + +// New create a new TempDir instance, "base" must be an existing directory, +// "sub" could be a multi-level directory and will be created if not exist +func New(base, sub string) *TempDir { + return &TempDir{base: base, sub: sub} +} + +func OsTempDir(sub string) *TempDir { + return New(os.TempDir(), sub) +} diff --git a/modules/tempdir/tempdir_test.go b/modules/tempdir/tempdir_test.go new file mode 100644 index 0000000000..d6afcb7bed --- /dev/null +++ b/modules/tempdir/tempdir_test.go @@ -0,0 +1,75 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package tempdir + +import ( + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestTempDir(t *testing.T) { + base := t.TempDir() + + t.Run("Create", func(t *testing.T) { + td := New(base, "sub1/sub2") // make sure the sub dir supports "/" in the path + assert.Equal(t, filepath.Join(base, "sub1", "sub2"), td.JoinPath()) + assert.Equal(t, filepath.Join(base, "sub1", "sub2/test"), td.JoinPath("test")) + + t.Run("MkdirTempRandom", func(t *testing.T) { + s, cleanup, err := td.MkdirTempRandom("foo") + assert.NoError(t, err) + assert.True(t, strings.HasPrefix(s, filepath.Join(base, "sub1/sub2", "foo"))) + + _, err = os.Stat(s) + assert.NoError(t, err) + cleanup() + _, err = os.Stat(s) + assert.ErrorIs(t, err, os.ErrNotExist) + }) + + t.Run("CreateTempFileRandom", func(t *testing.T) { + f, cleanup, err := td.CreateTempFileRandom("foo", "bar") + filename := f.Name() + assert.NoError(t, err) + assert.True(t, strings.HasPrefix(filename, filepath.Join(base, "sub1/sub2", "foo", "bar"))) + _, err = os.Stat(filename) + assert.NoError(t, err) + cleanup() + _, err = os.Stat(filename) + assert.ErrorIs(t, err, os.ErrNotExist) + }) + + t.Run("RemoveOutDated", func(t *testing.T) { + fa1, _, err := td.CreateTempFileRandom("dir-a", "f1") + assert.NoError(t, err) + fa2, _, err := td.CreateTempFileRandom("dir-a", "f2") + assert.NoError(t, err) + _ = os.Chtimes(fa2.Name(), time.Now().Add(-time.Hour), time.Now().Add(-time.Hour)) + fb1, _, err := td.CreateTempFileRandom("dir-b", "f1") + assert.NoError(t, err) + _ = os.Chtimes(fb1.Name(), time.Now().Add(-time.Hour), time.Now().Add(-time.Hour)) + _, _, _ = fa1.Close(), fa2.Close(), fb1.Close() + + td.RemoveOutdated(time.Minute) + + _, err = os.Stat(fa1.Name()) + assert.NoError(t, err) + _, err = os.Stat(fa2.Name()) + assert.ErrorIs(t, err, os.ErrNotExist) + _, err = os.Stat(fb1.Name()) + assert.ErrorIs(t, err, os.ErrNotExist) + }) + }) + + t.Run("BaseNotExist", func(t *testing.T) { + td := New(filepath.Join(base, "not-exist"), "sub") + _, _, err := td.MkdirTempRandom("foo") + assert.ErrorIs(t, err, os.ErrNotExist) + }) +} diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 3237f8b295..c9d93e089c 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -9,6 +9,7 @@ import ( "html" "html/template" "net/url" + "strconv" "strings" "time" @@ -73,7 +74,7 @@ func NewFuncMap() template.FuncMap { "TimeEstimateString": timeEstimateString, "LoadTimes": func(startTime time.Time) string { - return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms" + return strconv.FormatInt(time.Since(startTime).Nanoseconds()/1e6, 10) + "ms" }, // ----------------------------------------------------------------- diff --git a/modules/templates/helper_test.go b/modules/templates/helper_test.go index 5d7bc93622..81f8235bd2 100644 --- a/modules/templates/helper_test.go +++ b/modules/templates/helper_test.go @@ -15,7 +15,7 @@ import ( func TestSubjectBodySeparator(t *testing.T) { test := func(input, subject, body string) { - loc := mailSubjectSplit.FindIndex([]byte(input)) + loc := mailSubjectSplit.FindStringIndex(input) if loc == nil { assert.Empty(t, subject, "no subject found, but one expected") assert.Equal(t, body, input) @@ -120,8 +120,8 @@ func TestTemplateEscape(t *testing.T) { func TestQueryBuild(t *testing.T) { t.Run("construct", func(t *testing.T) { - assert.Equal(t, "", string(QueryBuild())) - assert.Equal(t, "", string(QueryBuild("a", nil, "b", false, "c", 0, "d", ""))) + assert.Empty(t, string(QueryBuild())) + assert.Empty(t, string(QueryBuild("a", nil, "b", false, "c", 0, "d", ""))) assert.Equal(t, "a=1&b=true", string(QueryBuild("a", 1, "b", "true"))) // path with query parameters @@ -136,9 +136,9 @@ func TestQueryBuild(t *testing.T) { // only query parameters assert.Equal(t, "&k=1", string(QueryBuild("&", "k", 1))) - assert.Equal(t, "", string(QueryBuild("&", "k", 0))) - assert.Equal(t, "", string(QueryBuild("&k=a", "k", 0))) - assert.Equal(t, "", string(QueryBuild("k=a&", "k", 0))) + assert.Empty(t, string(QueryBuild("&", "k", 0))) + assert.Empty(t, string(QueryBuild("&k=a", "k", 0))) + assert.Empty(t, string(QueryBuild("k=a&", "k", 0))) assert.Equal(t, "a=1&b=2", string(QueryBuild("a=1", "b", 2))) assert.Equal(t, "&a=1&b=2", string(QueryBuild("&a=1", "b", 2))) assert.Equal(t, "a=1&b=2&", string(QueryBuild("a=1&", "b", 2))) diff --git a/modules/templates/htmlrenderer_test.go b/modules/templates/htmlrenderer_test.go index 2a74b74c23..e8b01c30fe 100644 --- a/modules/templates/htmlrenderer_test.go +++ b/modules/templates/htmlrenderer_test.go @@ -65,7 +65,7 @@ func TestHandleError(t *testing.T) { _, err = tmpl.Parse(s) assert.Error(t, err) msg := h(err) - assert.EqualValues(t, strings.TrimSpace(expect), strings.TrimSpace(msg)) + assert.Equal(t, strings.TrimSpace(expect), strings.TrimSpace(msg)) } test("{{", p.handleGenericTemplateError, ` @@ -102,5 +102,5 @@ god knows XXX ---------------------------------------------------------------------- ` actualMsg := p.handleExpectedEndError(errors.New("template: test:1: expected end; found XXX")) - assert.EqualValues(t, strings.TrimSpace(expectedMsg), strings.TrimSpace(actualMsg)) + assert.Equal(t, strings.TrimSpace(expectedMsg), strings.TrimSpace(actualMsg)) } diff --git a/modules/templates/util_avatar.go b/modules/templates/util_avatar.go index 470e24fa61..73fde99f40 100644 --- a/modules/templates/util_avatar.go +++ b/modules/templates/util_avatar.go @@ -5,9 +5,9 @@ package templates import ( "context" - "fmt" "html" "html/template" + "strconv" activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/avatars" @@ -28,7 +28,7 @@ func NewAvatarUtils(ctx context.Context) *AvatarUtils { // AvatarHTML creates the HTML for an avatar func AvatarHTML(src string, size int, class, name string) template.HTML { - sizeStr := fmt.Sprintf(`%d`, size) + sizeStr := strconv.Itoa(size) if name == "" { name = "avatar" diff --git a/modules/templates/util_date.go b/modules/templates/util_date.go index 658691ee40..fc3f3f2339 100644 --- a/modules/templates/util_date.go +++ b/modules/templates/util_date.go @@ -99,7 +99,7 @@ func dateTimeFormat(format string, datetime any) template.HTML { attrs = append(attrs, `format="datetime"`, `month="short"`, `day="numeric"`, `hour="numeric"`, `minute="numeric"`, `second="numeric"`, `data-tooltip-content`, `data-tooltip-interactive="true"`) return template.HTML(fmt.Sprintf(`%s`, strings.Join(attrs, " "), datetimeEscaped, textEscaped)) default: - panic(fmt.Sprintf("Unsupported format %s", format)) + panic("Unsupported format " + format) } } diff --git a/modules/templates/util_dict.go b/modules/templates/util_dict.go index 8d6376b522..cc3018a71c 100644 --- a/modules/templates/util_dict.go +++ b/modules/templates/util_dict.go @@ -4,6 +4,7 @@ package templates import ( + "errors" "fmt" "html" "html/template" @@ -33,7 +34,7 @@ func dictMerge(base map[string]any, arg any) bool { // The dot syntax is highly discouraged because it might cause unclear key conflicts. It's always good to use explicit keys. func dict(args ...any) (map[string]any, error) { if len(args)%2 != 0 { - return nil, fmt.Errorf("invalid dict constructor syntax: must have key-value pairs") + return nil, errors.New("invalid dict constructor syntax: must have key-value pairs") } m := make(map[string]any, len(args)/2) for i := 0; i < len(args); i += 2 { diff --git a/modules/templates/util_format.go b/modules/templates/util_format.go index bee6fb7b75..3485e3251e 100644 --- a/modules/templates/util_format.go +++ b/modules/templates/util_format.go @@ -5,6 +5,7 @@ package templates import ( "fmt" + "strconv" "code.gitea.io/gitea/modules/util" ) @@ -24,7 +25,7 @@ func countFmt(data any) string { return "" } if num < 1000 { - return fmt.Sprintf("%d", num) + return strconv.FormatInt(num, 10) } else if num < 1_000_000 { num2 := float32(num) / 1000.0 return fmt.Sprintf("%.1fk", num2) diff --git a/modules/templates/util_format_test.go b/modules/templates/util_format_test.go index 8d466faff0..13a57c24e2 100644 --- a/modules/templates/util_format_test.go +++ b/modules/templates/util_format_test.go @@ -14,5 +14,5 @@ func TestCountFmt(t *testing.T) { assert.Equal(t, "1.3k", countFmt(int64(1317))) assert.Equal(t, "21.3M", countFmt(21317675)) assert.Equal(t, "45.7G", countFmt(45721317675)) - assert.Equal(t, "", countFmt("test")) + assert.Empty(t, countFmt("test")) } diff --git a/modules/templates/util_misc.go b/modules/templates/util_misc.go index 2d42bc76b5..cc5bf67b42 100644 --- a/modules/templates/util_misc.go +++ b/modules/templates/util_misc.go @@ -38,10 +38,11 @@ func sortArrow(normSort, revSort, urlSort string, isDefault bool) template.HTML } else { // if sort arg is in url test if it correlates with column header sort arguments // the direction of the arrow should indicate the "current sort order", up means ASC(normal), down means DESC(rev) - if urlSort == normSort { + switch urlSort { + case normSort: // the table is sorted with this header normal return svg.RenderHTML("octicon-triangle-up", 16) - } else if urlSort == revSort { + case revSort: // the table is sorted with this header reverse return svg.RenderHTML("octicon-triangle-down", 16) } diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go index 27316bbfec..521233db40 100644 --- a/modules/templates/util_render.go +++ b/modules/templates/util_render.go @@ -15,8 +15,6 @@ import ( issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/modules/emoji" - "code.gitea.io/gitea/modules/fileicon" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/htmlutil" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" @@ -172,20 +170,28 @@ func (ut *RenderUtils) RenderLabel(label *issues_model.Label) template.HTML { itemColor := "#" + hex.EncodeToString(itemBytes) scopeColor := "#" + hex.EncodeToString(scopeBytes) + if label.ExclusiveOrder > 0 { + // | | + return htmlutil.HTMLFormat(``+ + `%s`+ + `%s`+ + `%d`+ + ``, + extraCSSClasses, descriptionText, + textColor, scopeColor, scopeHTML, + textColor, itemColor, itemHTML, + label.ExclusiveOrder) + } + + // | return htmlutil.HTMLFormat(``+ `%s`+ `%s`+ ``, extraCSSClasses, descriptionText, textColor, scopeColor, scopeHTML, - textColor, itemColor, itemHTML) -} - -func (ut *RenderUtils) RenderFileIcon(entry *git.TreeEntry) template.HTML { - if setting.UI.FileIconTheme == "material" { - return fileicon.DefaultMaterialIconProvider().FileIcon(ut.ctx, entry) - } - return fileicon.BasicThemeIcon(entry) + textColor, itemColor, itemHTML, + ) } // RenderEmoji renders html text with emoji post processors diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go index 617021e510..460b9dc190 100644 --- a/modules/templates/util_render_test.go +++ b/modules/templates/util_render_test.go @@ -51,12 +51,12 @@ var testMetas = map[string]string{ "user": "user13", "repo": "repo11", "repoPath": "../../tests/gitea-repositories-meta/user13/repo11.git/", - "markdownLineBreakStyle": "comment", + "markdownNewLineHardBreak": "true", "markupAllowShortIssuePattern": "true", } func TestMain(m *testing.M) { - unittest.InitSettings() + unittest.InitSettingsForTesting() if err := git.InitSimple(context.Background()); err != nil { log.Fatal("git init failed, err: %v", err) } @@ -123,16 +123,16 @@ func TestRenderCommitBody(t *testing.T) {  [[local image|image.jpg]] [[remote link|https://example.com/image.jpg]] -88fc37a3c0...12fc37a3c0 (hash) +88fc37a3c0...12fc37a3c0 (hash) com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare -88fc37a3c0 +88fc37a3c0 com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit 👍 mail@domain.com @mention-user test #123 space` - assert.EqualValues(t, expected, string(newTestRenderUtils(t).RenderCommitBody(testInput(), testMetas))) + assert.Equal(t, expected, string(newTestRenderUtils(t).RenderCommitBody(testInput(), testMetas))) } func TestRenderCommitMessage(t *testing.T) { @@ -169,7 +169,7 @@ mail@domain.com space ` expected = strings.ReplaceAll(expected, "", " ") - assert.EqualValues(t, expected, string(newTestRenderUtils(t).RenderIssueTitle(testInput(), testMetas))) + assert.Equal(t, expected, string(newTestRenderUtils(t).RenderIssueTitle(testInput(), testMetas))) } func TestRenderMarkdownToHtml(t *testing.T) { @@ -214,5 +214,5 @@ func TestRenderLabels(t *testing.T) { func TestUserMention(t *testing.T) { markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true rendered := newTestRenderUtils(t).MarkdownToHtml("@no-such-user @mention-user @mention-user") - assert.EqualValues(t, `@no-such-user @mention-user @mention-user`, strings.TrimSpace(string(rendered))) + assert.Equal(t, `@no-such-user @mention-user @mention-user`, strings.TrimSpace(string(rendered))) } diff --git a/modules/templates/util_test.go b/modules/templates/util_test.go index febaf7fa88..a6448a6ff2 100644 --- a/modules/templates/util_test.go +++ b/modules/templates/util_test.go @@ -28,7 +28,7 @@ func TestDict(t *testing.T) { for _, c := range cases { got, err := dict(c.args...) if assert.NoError(t, err) { - assert.EqualValues(t, c.want, got) + assert.Equal(t, c.want, got) } } diff --git a/modules/templates/vars/vars.go b/modules/templates/vars/vars.go index cc9d0e976f..500078d4b8 100644 --- a/modules/templates/vars/vars.go +++ b/modules/templates/vars/vars.go @@ -16,7 +16,7 @@ type ErrWrongSyntax struct { } func (err ErrWrongSyntax) Error() string { - return fmt.Sprintf("wrong syntax found in %s", err.Template) + return "wrong syntax found in " + err.Template } // ErrVarMissing represents an error that no matched variable diff --git a/modules/templates/vars/vars_test.go b/modules/templates/vars/vars_test.go index 8f421d9e4b..9b48167237 100644 --- a/modules/templates/vars/vars_test.go +++ b/modules/templates/vars/vars_test.go @@ -60,7 +60,7 @@ func TestExpandVars(t *testing.T) { for _, kase := range kases { t.Run(kase.tmpl, func(t *testing.T) { res, err := Expand(kase.tmpl, kase.data) - assert.EqualValues(t, kase.out, res) + assert.Equal(t, kase.out, res) if kase.error { assert.Error(t, err) } else { diff --git a/modules/test/logchecker.go b/modules/test/logchecker.go index 7bf234f560..829f735c7c 100644 --- a/modules/test/logchecker.go +++ b/modules/test/logchecker.go @@ -5,7 +5,7 @@ package test import ( "context" - "fmt" + "strconv" "strings" "sync" "sync/atomic" @@ -58,7 +58,7 @@ var checkerIndex int64 func NewLogChecker(namePrefix string) (logChecker *LogChecker, cancel func()) { logger := log.GetManager().GetLogger(namePrefix) newCheckerIndex := atomic.AddInt64(&checkerIndex, 1) - writerName := namePrefix + "-" + fmt.Sprint(newCheckerIndex) + writerName := namePrefix + "-" + strconv.FormatInt(newCheckerIndex, 10) lc := &LogChecker{} lc.EventWriterBaseImpl = log.NewEventWriterBase(writerName, "test-log-checker", log.WriterMode{}) diff --git a/modules/test/utils.go b/modules/test/utils.go index ec4c976388..3051d3d286 100644 --- a/modules/test/utils.go +++ b/modules/test/utils.go @@ -4,7 +4,6 @@ package test import ( - "fmt" "net/http" "net/http/httptest" "os" @@ -57,7 +56,7 @@ func SetupGiteaRoot() string { giteaRoot = filepath.Dir(filepath.Dir(filepath.Dir(filename))) fixturesDir := filepath.Join(giteaRoot, "models", "fixtures") if exist, _ := util.IsDir(fixturesDir); !exist { - panic(fmt.Sprintf("fixtures directory not found: %s", fixturesDir)) + panic("fixtures directory not found: " + fixturesDir) } _ = os.Setenv("GITEA_ROOT", giteaRoot) return giteaRoot diff --git a/modules/translation/translation_test.go b/modules/translation/translation_test.go index 464aa32661..87df9eb825 100644 --- a/modules/translation/translation_test.go +++ b/modules/translation/translation_test.go @@ -20,13 +20,13 @@ func TestPrettyNumber(t *testing.T) { allLangMap["id-ID"] = &LangType{Lang: "id-ID", Name: "Bahasa Indonesia"} l := NewLocale("id-ID") - assert.EqualValues(t, "1.000.000", l.PrettyNumber(1000000)) - assert.EqualValues(t, "1.000.000,1", l.PrettyNumber(1000000.1)) - assert.EqualValues(t, "1.000.000", l.PrettyNumber("1000000")) - assert.EqualValues(t, "1.000.000", l.PrettyNumber("1000000.0")) - assert.EqualValues(t, "1.000.000,1", l.PrettyNumber("1000000.1")) + assert.Equal(t, "1.000.000", l.PrettyNumber(1000000)) + assert.Equal(t, "1.000.000,1", l.PrettyNumber(1000000.1)) + assert.Equal(t, "1.000.000", l.PrettyNumber("1000000")) + assert.Equal(t, "1.000.000", l.PrettyNumber("1000000.0")) + assert.Equal(t, "1.000.000,1", l.PrettyNumber("1000000.1")) l = NewLocale("nosuch") - assert.EqualValues(t, "1,000,000", l.PrettyNumber(1000000)) - assert.EqualValues(t, "1,000,000.1", l.PrettyNumber(1000000.1)) + assert.Equal(t, "1,000,000", l.PrettyNumber(1000000)) + assert.Equal(t, "1,000,000.1", l.PrettyNumber(1000000.1)) } diff --git a/modules/updatechecker/update_checker.go b/modules/updatechecker/update_checker.go index 3c1e05d060..f0686c0f78 100644 --- a/modules/updatechecker/update_checker.go +++ b/modules/updatechecker/update_checker.go @@ -34,7 +34,7 @@ func GiteaUpdateChecker(httpEndpoint string) error { }, } - req, err := http.NewRequest("GET", httpEndpoint, nil) + req, err := http.NewRequest(http.MethodGet, httpEndpoint, nil) if err != nil { return err } diff --git a/modules/util/filebuffer/file_backed_buffer.go b/modules/util/filebuffer/file_backed_buffer.go index 739543e297..0731ba30c8 100644 --- a/modules/util/filebuffer/file_backed_buffer.go +++ b/modules/util/filebuffer/file_backed_buffer.go @@ -7,16 +7,10 @@ import ( "bytes" "errors" "io" - "math" "os" ) -var ( - // ErrInvalidMemorySize occurs if the memory size is not in a valid range - ErrInvalidMemorySize = errors.New("Memory size must be greater 0 and lower math.MaxInt32") - // ErrWriteAfterRead occurs if Write is called after a read operation - ErrWriteAfterRead = errors.New("Write is unsupported after a read operation") -) +var ErrWriteAfterRead = errors.New("write is unsupported after a read operation") // occurs if Write is called after a read operation type readAtSeeker interface { io.ReadSeeker @@ -30,34 +24,17 @@ type FileBackedBuffer struct { maxMemorySize int64 size int64 buffer bytes.Buffer + tempDir string file *os.File reader readAtSeeker } // New creates a file backed buffer with a specific maximum memory size -func New(maxMemorySize int) (*FileBackedBuffer, error) { - if maxMemorySize < 0 || maxMemorySize > math.MaxInt32 { - return nil, ErrInvalidMemorySize - } - +func New(maxMemorySize int, tempDir string) *FileBackedBuffer { return &FileBackedBuffer{ maxMemorySize: int64(maxMemorySize), - }, nil -} - -// CreateFromReader creates a file backed buffer and copies the provided reader data into it. -func CreateFromReader(r io.Reader, maxMemorySize int) (*FileBackedBuffer, error) { - b, err := New(maxMemorySize) - if err != nil { - return nil, err + tempDir: tempDir, } - - _, err = io.Copy(b, r) - if err != nil { - return nil, err - } - - return b, nil } // Write implements io.Writer @@ -73,7 +50,7 @@ func (b *FileBackedBuffer) Write(p []byte) (int, error) { n, err = b.file.Write(p) } else { if b.size+int64(len(p)) > b.maxMemorySize { - b.file, err = os.CreateTemp("", "gitea-buffer-") + b.file, err = os.CreateTemp(b.tempDir, "gitea-buffer-") if err != nil { return 0, err } @@ -148,7 +125,7 @@ func (b *FileBackedBuffer) Seek(offset int64, whence int) (int64, error) { func (b *FileBackedBuffer) Close() error { if b.file != nil { err := b.file.Close() - os.Remove(b.file.Name()) + _ = os.Remove(b.file.Name()) b.file = nil return err } diff --git a/modules/util/filebuffer/file_backed_buffer_test.go b/modules/util/filebuffer/file_backed_buffer_test.go index 16d5a1965f..3f13c6ac7b 100644 --- a/modules/util/filebuffer/file_backed_buffer_test.go +++ b/modules/util/filebuffer/file_backed_buffer_test.go @@ -21,7 +21,8 @@ func TestFileBackedBuffer(t *testing.T) { } for _, c := range cases { - buf, err := CreateFromReader(strings.NewReader(c.Data), c.MaxMemorySize) + buf := New(c.MaxMemorySize, t.TempDir()) + _, err := io.Copy(buf, strings.NewReader(c.Data)) assert.NoError(t, err) assert.EqualValues(t, len(c.Data), buf.Size()) diff --git a/modules/util/paginate_test.go b/modules/util/paginate_test.go index 6e69dd19cc..3dc5095071 100644 --- a/modules/util/paginate_test.go +++ b/modules/util/paginate_test.go @@ -13,23 +13,23 @@ func TestPaginateSlice(t *testing.T) { stringSlice := []string{"a", "b", "c", "d", "e"} result, ok := PaginateSlice(stringSlice, 1, 2).([]string) assert.True(t, ok) - assert.EqualValues(t, []string{"a", "b"}, result) + assert.Equal(t, []string{"a", "b"}, result) result, ok = PaginateSlice(stringSlice, 100, 2).([]string) assert.True(t, ok) - assert.EqualValues(t, []string{}, result) + assert.Equal(t, []string{}, result) result, ok = PaginateSlice(stringSlice, 3, 2).([]string) assert.True(t, ok) - assert.EqualValues(t, []string{"e"}, result) + assert.Equal(t, []string{"e"}, result) result, ok = PaginateSlice(stringSlice, 1, 0).([]string) assert.True(t, ok) - assert.EqualValues(t, []string{"a", "b", "c", "d", "e"}, result) + assert.Equal(t, []string{"a", "b", "c", "d", "e"}, result) result, ok = PaginateSlice(stringSlice, 1, -1).([]string) assert.True(t, ok) - assert.EqualValues(t, []string{"a", "b", "c", "d", "e"}, result) + assert.Equal(t, []string{"a", "b", "c", "d", "e"}, result) type Test struct { Val int @@ -38,9 +38,9 @@ func TestPaginateSlice(t *testing.T) { testVar := []*Test{{Val: 2}, {Val: 3}, {Val: 4}} testVar, ok = PaginateSlice(testVar, 1, 50).([]*Test) assert.True(t, ok) - assert.EqualValues(t, []*Test{{Val: 2}, {Val: 3}, {Val: 4}}, testVar) + assert.Equal(t, []*Test{{Val: 2}, {Val: 3}, {Val: 4}}, testVar) testVar, ok = PaginateSlice(testVar, 2, 2).([]*Test) assert.True(t, ok) - assert.EqualValues(t, []*Test{{Val: 4}}, testVar) + assert.Equal(t, []*Test{{Val: 4}}, testVar) } diff --git a/modules/util/path.go b/modules/util/path.go index d9f17bd124..0e56348978 100644 --- a/modules/util/path.go +++ b/modules/util/path.go @@ -36,9 +36,10 @@ func PathJoinRel(elem ...string) string { elems[i] = path.Clean("/" + e) } p := path.Join(elems...) - if p == "" { + switch p { + case "": return "" - } else if p == "/" { + case "/": return "." } return p[1:] diff --git a/modules/util/sec_to_time_test.go b/modules/util/sec_to_time_test.go index b67926bbcf..84e767c6e0 100644 --- a/modules/util/sec_to_time_test.go +++ b/modules/util/sec_to_time_test.go @@ -24,5 +24,5 @@ func TestSecToHours(t *testing.T) { assert.Equal(t, "672 hours", SecToHours(4*7*day)) assert.Equal(t, "1 second", SecToHours(1)) assert.Equal(t, "2 seconds", SecToHours(2)) - assert.Equal(t, "", SecToHours(nil)) // old behavior, empty means no output + assert.Empty(t, SecToHours(nil)) // old behavior, empty means no output } diff --git a/modules/util/truncate_test.go b/modules/util/truncate_test.go index 8789c824f5..9f4ad7dc20 100644 --- a/modules/util/truncate_test.go +++ b/modules/util/truncate_test.go @@ -5,6 +5,7 @@ package util import ( "fmt" + "strconv" "strings" "testing" @@ -100,7 +101,7 @@ func TestEllipsisString(t *testing.T) { {limit: 7, left: "\xef\x03\xfe\xef\x03\xfe", right: ""}, } for _, c := range invalidCases { - t.Run(fmt.Sprintf("%d", c.limit), func(t *testing.T) { + t.Run(strconv.Itoa(c.limit), func(t *testing.T) { left, right := EllipsisDisplayStringX("\xef\x03\xfe\xef\x03\xfe", c.limit) assert.Equal(t, c.left, left, "left") assert.Equal(t, c.right, right, "right") @@ -115,15 +116,15 @@ func TestEllipsisString(t *testing.T) { } func TestTruncateRunes(t *testing.T) { - assert.Equal(t, "", TruncateRunes("", 0)) - assert.Equal(t, "", TruncateRunes("", 1)) + assert.Empty(t, TruncateRunes("", 0)) + assert.Empty(t, TruncateRunes("", 1)) - assert.Equal(t, "", TruncateRunes("ab", 0)) + assert.Empty(t, TruncateRunes("ab", 0)) assert.Equal(t, "a", TruncateRunes("ab", 1)) assert.Equal(t, "ab", TruncateRunes("ab", 2)) assert.Equal(t, "ab", TruncateRunes("ab", 3)) - assert.Equal(t, "", TruncateRunes("测试", 0)) + assert.Empty(t, TruncateRunes("测试", 0)) assert.Equal(t, "测", TruncateRunes("测试", 1)) assert.Equal(t, "测试", TruncateRunes("测试", 2)) assert.Equal(t, "测试", TruncateRunes("测试", 3)) diff --git a/modules/util/util.go b/modules/util/util.go index 72fcddbe13..dd8e073888 100644 --- a/modules/util/util.go +++ b/modules/util/util.go @@ -219,6 +219,13 @@ func IfZero[T comparable](v, def T) T { return v } +func IfEmpty[T any](v, def []T) []T { + if len(v) == 0 { + return def + } + return v +} + // OptionalArg helps the "optional argument" in Golang: // // func foo(optArg ...int) { return OptionalArg(optArg) } diff --git a/modules/validation/binding_test.go b/modules/validation/binding_test.go index 28d0f57b5c..0cd328f312 100644 --- a/modules/validation/binding_test.go +++ b/modules/validation/binding_test.go @@ -47,7 +47,7 @@ func performValidationTest(t *testing.T, testCase validationTestCase) { assert.Equal(t, testCase.expectedErrors, actual) }) - req, err := http.NewRequest("POST", testRoute, nil) + req, err := http.NewRequest(http.MethodPost, testRoute, nil) if err != nil { panic(err) } diff --git a/modules/web/routemock_test.go b/modules/web/routemock_test.go index 89cfaacdd1..a0949bf622 100644 --- a/modules/web/routemock_test.go +++ b/modules/web/routemock_test.go @@ -30,13 +30,13 @@ func TestRouteMock(t *testing.T) { // normal request recorder := httptest.NewRecorder() - req, err := http.NewRequest("GET", "http://localhost:8000/foo", nil) + req, err := http.NewRequest(http.MethodGet, "http://localhost:8000/foo", nil) assert.NoError(t, err) r.ServeHTTP(recorder, req) assert.Len(t, recorder.Header(), 3) - assert.EqualValues(t, "m1", recorder.Header().Get("X-Test-Middleware1")) - assert.EqualValues(t, "m2", recorder.Header().Get("X-Test-Middleware2")) - assert.EqualValues(t, "h", recorder.Header().Get("X-Test-Handler")) + assert.Equal(t, "m1", recorder.Header().Get("X-Test-Middleware1")) + assert.Equal(t, "m2", recorder.Header().Get("X-Test-Middleware2")) + assert.Equal(t, "h", recorder.Header().Get("X-Test-Handler")) RouteMockReset() // mock at "mock-point" @@ -45,12 +45,12 @@ func TestRouteMock(t *testing.T) { resp.WriteHeader(http.StatusOK) }) recorder = httptest.NewRecorder() - req, err = http.NewRequest("GET", "http://localhost:8000/foo", nil) + req, err = http.NewRequest(http.MethodGet, "http://localhost:8000/foo", nil) assert.NoError(t, err) r.ServeHTTP(recorder, req) assert.Len(t, recorder.Header(), 2) - assert.EqualValues(t, "m1", recorder.Header().Get("X-Test-Middleware1")) - assert.EqualValues(t, "a", recorder.Header().Get("X-Test-MockPoint")) + assert.Equal(t, "m1", recorder.Header().Get("X-Test-Middleware1")) + assert.Equal(t, "a", recorder.Header().Get("X-Test-MockPoint")) RouteMockReset() // mock at MockAfterMiddlewares @@ -59,12 +59,12 @@ func TestRouteMock(t *testing.T) { resp.WriteHeader(http.StatusOK) }) recorder = httptest.NewRecorder() - req, err = http.NewRequest("GET", "http://localhost:8000/foo", nil) + req, err = http.NewRequest(http.MethodGet, "http://localhost:8000/foo", nil) assert.NoError(t, err) r.ServeHTTP(recorder, req) assert.Len(t, recorder.Header(), 3) - assert.EqualValues(t, "m1", recorder.Header().Get("X-Test-Middleware1")) - assert.EqualValues(t, "m2", recorder.Header().Get("X-Test-Middleware2")) - assert.EqualValues(t, "b", recorder.Header().Get("X-Test-MockPoint")) + assert.Equal(t, "m1", recorder.Header().Get("X-Test-Middleware1")) + assert.Equal(t, "m2", recorder.Header().Get("X-Test-Middleware2")) + assert.Equal(t, "b", recorder.Header().Get("X-Test-MockPoint")) RouteMockReset() } diff --git a/modules/web/router_path.go b/modules/web/router_path.go index b59948581a..baf1b522af 100644 --- a/modules/web/router_path.go +++ b/modules/web/router_path.go @@ -4,7 +4,6 @@ package web import ( - "fmt" "net/http" "regexp" "strings" @@ -103,7 +102,7 @@ func newRouterPathMatcher(methods, pattern string, h ...any) *routerPathMatcher for _, method := range strings.Split(methods, ",") { method = strings.TrimSpace(method) if !isValidMethod(method) { - panic(fmt.Sprintf("invalid HTTP method: %s", method)) + panic("invalid HTTP method: " + method) } p.methods.Add(method) } @@ -117,7 +116,7 @@ func newRouterPathMatcher(methods, pattern string, h ...any) *routerPathMatcher } end := strings.IndexByte(pattern[lastEnd+start:], '>') if end == -1 { - panic(fmt.Sprintf("invalid pattern: %s", pattern)) + panic("invalid pattern: " + pattern) } re = append(re, pattern[lastEnd:lastEnd+start]...) partName, partExp, _ := strings.Cut(pattern[lastEnd+start+1:lastEnd+start+end], ":") diff --git a/modules/web/router_test.go b/modules/web/router_test.go index 582980a27a..21619012ea 100644 --- a/modules/web/router_test.go +++ b/modules/web/router_test.go @@ -51,7 +51,7 @@ func TestPathProcessor(t *testing.T) { } func TestRouter(t *testing.T) { - buff := bytes.NewBufferString("") + buff := &bytes.Buffer{} recorder := httptest.NewRecorder() recorder.Body = buff @@ -121,7 +121,7 @@ func TestRouter(t *testing.T) { req, err := http.NewRequest(methodPathFields[0], methodPathFields[1], nil) assert.NoError(t, err) r.ServeHTTP(recorder, req) - assert.EqualValues(t, expected, res) + assert.Equal(t, expected, res) }) } @@ -224,7 +224,7 @@ func TestRouteNormalizePath(t *testing.T) { actualPaths.Path = req.URL.Path }) - req, err := http.NewRequest("GET", reqPath, nil) + req, err := http.NewRequest(http.MethodGet, reqPath, nil) assert.NoError(t, err) r.ServeHTTP(recorder, req) assert.Equal(t, expectedPaths, actualPaths, "req path = %q", reqPath) diff --git a/options/fileicon/material-icon-rules.json b/options/fileicon/material-icon-rules.json index fd058c82dc..d097399252 100644 --- a/options/fileicon/material-icon-rules.json +++ b/options/fileicon/material-icon-rules.json @@ -1112,6 +1112,18 @@ ".routers": "folder-routes", "_routers": "folder-routes", "__routers__": "folder-routes", + "navigation": "folder-routes", + ".navigation": "folder-routes", + "_navigation": "folder-routes", + "__navigation__": "folder-routes", + "navigations": "folder-routes", + ".navigations": "folder-routes", + "_navigations": "folder-routes", + "__navigations__": "folder-routes", + "routing": "folder-routes", + ".routing": "folder-routes", + "_routing": "folder-routes", + "__routing__": "folder-routes", "ci": "folder-ci", ".ci": "folder-ci", "_ci": "folder-ci", @@ -2092,6 +2104,34 @@ ".DS_Store": "folder-macos", "_DS_Store": "folder-macos", "__DS_Store__": "folder-macos", + "iPhone": "folder-macos", + ".iPhone": "folder-macos", + "_iPhone": "folder-macos", + "__iPhone__": "folder-macos", + "iPad": "folder-macos", + ".iPad": "folder-macos", + "_iPad": "folder-macos", + "__iPad__": "folder-macos", + "iPod": "folder-macos", + ".iPod": "folder-macos", + "_iPod": "folder-macos", + "__iPod__": "folder-macos", + "macbook": "folder-macos", + ".macbook": "folder-macos", + "_macbook": "folder-macos", + "__macbook__": "folder-macos", + "macbook-air": "folder-macos", + ".macbook-air": "folder-macos", + "_macbook-air": "folder-macos", + "__macbook-air__": "folder-macos", + "macosx": "folder-macos", + ".macosx": "folder-macos", + "_macosx": "folder-macos", + "__macosx__": "folder-macos", + "apple": "folder-macos", + ".apple": "folder-macos", + "_apple": "folder-macos", + "__apple__": "folder-macos", "error": "folder-error", ".error": "folder-error", "_error": "folder-error", @@ -2428,6 +2468,34 @@ ".firebase": "folder-firebase", "_firebase": "folder-firebase", "__firebase__": "folder-firebase", + "firestore": "folder-firestore", + ".firestore": "folder-firestore", + "_firestore": "folder-firestore", + "__firestore__": "folder-firestore", + "cloud-firestore": "folder-firestore", + ".cloud-firestore": "folder-firestore", + "_cloud-firestore": "folder-firestore", + "__cloud-firestore__": "folder-firestore", + "firebase-firestore": "folder-firestore", + ".firebase-firestore": "folder-firestore", + "_firebase-firestore": "folder-firestore", + "__firebase-firestore__": "folder-firestore", + "cloud-functions": "folder-cloud-functions", + ".cloud-functions": "folder-cloud-functions", + "_cloud-functions": "folder-cloud-functions", + "__cloud-functions__": "folder-cloud-functions", + "cloudfunctions": "folder-cloud-functions", + ".cloudfunctions": "folder-cloud-functions", + "_cloudfunctions": "folder-cloud-functions", + "__cloudfunctions__": "folder-cloud-functions", + "firebase-cloud-functions": "folder-cloud-functions", + ".firebase-cloud-functions": "folder-cloud-functions", + "_firebase-cloud-functions": "folder-cloud-functions", + "__firebase-cloud-functions__": "folder-cloud-functions", + "firebase-cloudfunctions": "folder-cloud-functions", + ".firebase-cloudfunctions": "folder-cloud-functions", + "_firebase-cloudfunctions": "folder-cloud-functions", + "__firebase-cloudfunctions__": "folder-cloud-functions", "svelte": "folder-svelte", ".svelte": "folder-svelte", "_svelte": "folder-svelte", @@ -2975,6 +3043,14 @@ ".zeabur": "folder-zeabur", "_zeabur": "folder-zeabur", "__zeabur__": "folder-zeabur", + "kusto": "folder-kusto", + ".kusto": "folder-kusto", + "_kusto": "folder-kusto", + "__kusto__": "folder-kusto", + "kql": "folder-kusto", + ".kql": "folder-kusto", + "_kql": "folder-kusto", + "__kql__": "folder-kusto", "meta-inf": "folder-config", ".meta-inf": "folder-config", "_meta-inf": "folder-config", @@ -2990,7 +3066,19 @@ "ds_store": "folder-macos", ".ds_store": "folder-macos", "_ds_store": "folder-macos", - "__ds_store__": "folder-macos" + "__ds_store__": "folder-macos", + "iphone": "folder-macos", + ".iphone": "folder-macos", + "_iphone": "folder-macos", + "__iphone__": "folder-macos", + "ipad": "folder-macos", + ".ipad": "folder-macos", + "_ipad": "folder-macos", + "__ipad__": "folder-macos", + "ipod": "folder-macos", + ".ipod": "folder-macos", + "_ipod": "folder-macos", + "__ipod__": "folder-macos" }, "folderNamesExpanded": { "rust": "folder-rust-open", @@ -4105,6 +4193,18 @@ ".routers": "folder-routes-open", "_routers": "folder-routes-open", "__routers__": "folder-routes-open", + "navigation": "folder-routes-open", + ".navigation": "folder-routes-open", + "_navigation": "folder-routes-open", + "__navigation__": "folder-routes-open", + "navigations": "folder-routes-open", + ".navigations": "folder-routes-open", + "_navigations": "folder-routes-open", + "__navigations__": "folder-routes-open", + "routing": "folder-routes-open", + ".routing": "folder-routes-open", + "_routing": "folder-routes-open", + "__routing__": "folder-routes-open", "ci": "folder-ci-open", ".ci": "folder-ci-open", "_ci": "folder-ci-open", @@ -5085,6 +5185,34 @@ ".DS_Store": "folder-macos-open", "_DS_Store": "folder-macos-open", "__DS_Store__": "folder-macos-open", + "iPhone": "folder-macos-open", + ".iPhone": "folder-macos-open", + "_iPhone": "folder-macos-open", + "__iPhone__": "folder-macos-open", + "iPad": "folder-macos-open", + ".iPad": "folder-macos-open", + "_iPad": "folder-macos-open", + "__iPad__": "folder-macos-open", + "iPod": "folder-macos-open", + ".iPod": "folder-macos-open", + "_iPod": "folder-macos-open", + "__iPod__": "folder-macos-open", + "macbook": "folder-macos-open", + ".macbook": "folder-macos-open", + "_macbook": "folder-macos-open", + "__macbook__": "folder-macos-open", + "macbook-air": "folder-macos-open", + ".macbook-air": "folder-macos-open", + "_macbook-air": "folder-macos-open", + "__macbook-air__": "folder-macos-open", + "macosx": "folder-macos-open", + ".macosx": "folder-macos-open", + "_macosx": "folder-macos-open", + "__macosx__": "folder-macos-open", + "apple": "folder-macos-open", + ".apple": "folder-macos-open", + "_apple": "folder-macos-open", + "__apple__": "folder-macos-open", "error": "folder-error-open", ".error": "folder-error-open", "_error": "folder-error-open", @@ -5421,6 +5549,34 @@ ".firebase": "folder-firebase-open", "_firebase": "folder-firebase-open", "__firebase__": "folder-firebase-open", + "firestore": "folder-firestore-open", + ".firestore": "folder-firestore-open", + "_firestore": "folder-firestore-open", + "__firestore__": "folder-firestore-open", + "cloud-firestore": "folder-firestore-open", + ".cloud-firestore": "folder-firestore-open", + "_cloud-firestore": "folder-firestore-open", + "__cloud-firestore__": "folder-firestore-open", + "firebase-firestore": "folder-firestore-open", + ".firebase-firestore": "folder-firestore-open", + "_firebase-firestore": "folder-firestore-open", + "__firebase-firestore__": "folder-firestore-open", + "cloud-functions": "folder-cloud-functions-open", + ".cloud-functions": "folder-cloud-functions-open", + "_cloud-functions": "folder-cloud-functions-open", + "__cloud-functions__": "folder-cloud-functions-open", + "cloudfunctions": "folder-cloud-functions-open", + ".cloudfunctions": "folder-cloud-functions-open", + "_cloudfunctions": "folder-cloud-functions-open", + "__cloudfunctions__": "folder-cloud-functions-open", + "firebase-cloud-functions": "folder-cloud-functions-open", + ".firebase-cloud-functions": "folder-cloud-functions-open", + "_firebase-cloud-functions": "folder-cloud-functions-open", + "__firebase-cloud-functions__": "folder-cloud-functions-open", + "firebase-cloudfunctions": "folder-cloud-functions-open", + ".firebase-cloudfunctions": "folder-cloud-functions-open", + "_firebase-cloudfunctions": "folder-cloud-functions-open", + "__firebase-cloudfunctions__": "folder-cloud-functions-open", "svelte": "folder-svelte-open", ".svelte": "folder-svelte-open", "_svelte": "folder-svelte-open", @@ -5967,7 +6123,15 @@ "zeabur": "folder-zeabur-open", ".zeabur": "folder-zeabur-open", "_zeabur": "folder-zeabur-open", - "__zeabur__": "folder-zeabur-open" + "__zeabur__": "folder-zeabur-open", + "kusto": "folder-kusto-open", + ".kusto": "folder-kusto-open", + "_kusto": "folder-kusto-open", + "__kusto__": "folder-kusto-open", + "kql": "folder-kusto-open", + ".kql": "folder-kusto-open", + "_kql": "folder-kusto-open", + "__kql__": "folder-kusto-open" }, "rootFolderNames": {}, "rootFolderNamesExpanded": {}, @@ -6032,8 +6196,6 @@ "ico": "image", "tif": "image", "tiff": "image", - "psd": "image", - "psb": "image", "ami": "image", "apx": "image", "avif": "image", @@ -6128,6 +6290,10 @@ "routing.tsx": "routing", "routing.js": "routing", "routing.jsx": "routing", + "route.ts": "routing", + "route.tsx": "routing", + "route.js": "routing", + "route.jsx": "routing", "routes.ts": "routing", "routes.tsx": "routing", "routes.js": "routing", @@ -6288,6 +6454,7 @@ "py": "python", "pyc": "python-misc", "whl": "python-misc", + "egg": "python-misc", "url": "url", "sh": "console", "ksh": "console", @@ -6780,6 +6947,11 @@ "gltf": "3d", "glb": "3d", "svg": "svg", + "ai": "adobe-illustrator", + "ait": "adobe-illustrator", + "psd": "adobe-photoshop", + "psb": "adobe-photoshop", + "psdt": "adobe-photoshop", "svelte": "svelte", "svelte.js": "svelte_js", "svelte.ts": "svelte_ts", @@ -7031,6 +7203,7 @@ "dfxp": "subtitles", "vtt": "subtitles", "sub": "subtitles", + "ass": "subtitles", "beancount": "beancount", "bean": "beancount", "epub": "epub", @@ -7272,6 +7445,10 @@ "router.jsx": "routing", "router.ts": "routing", "router.tsx": "routing", + "route.js": "routing", + "route.jsx": "routing", + "route.ts": "routing", + "route.tsx": "routing", "routes.js": "routing", "routes.jsx": "routing", "routes.ts": "routing", @@ -7310,6 +7487,11 @@ ".pylintrc": "python-misc", "pyproject.toml": "python-misc", "py.typed": "python-misc", + ".coveragerc": "python-misc", + ".coverage": "python-misc", + ".scrapy": "python-misc", + "celerybeat-schedule": "python-misc", + "celerybeat.pid": "python-misc", "ruff.toml": "ruff", ".ruff.toml": "ruff", "uv.toml": "uv", @@ -9317,6 +9499,11 @@ "drone.yml": "drone_light", ".wakatime-project": "wakatime_light", "hcl": "hcl_light", + "ai": "adobe-illustrator_light", + "ait": "adobe-illustrator_light", + "psd": "adobe-photoshop_light", + "psb": "adobe-photoshop_light", + "psdt": "adobe-photoshop_light", "iuml": "uml_light", "pu": "uml_light", "puml": "uml_light", diff --git a/options/fileicon/material-icon-svgs.json b/options/fileicon/material-icon-svgs.json index dbda90665b..50bc2d2b8a 100644 --- a/options/fileicon/material-icon-svgs.json +++ b/options/fileicon/material-icon-svgs.json @@ -4,6 +4,10 @@ "abc": "", "actionscript": "", "ada": "", + "adobe-illustrator": "", + "adobe-illustrator_light": "", + "adobe-photoshop": "", + "adobe-photoshop_light": "", "adobe-swc": "", "adonis": "", "advpl-include.clone": "", @@ -163,8 +167,8 @@ "fastlane": "", "favicon": "", "figma": "", - "file": "", - "firebase": "", + "file": "", + "firebase": "", "flash": "", "flow": "", "folder-admin-open": "", @@ -223,6 +227,8 @@ "folder-client": "", "folder-cline-open": "", "folder-cline": "", + "folder-cloud-functions-open": "", + "folder-cloud-functions": "", "folder-cloudflare-open": "", "folder-cloudflare": "", "folder-cluster-open": "", @@ -273,8 +279,8 @@ "folder-delta": "", "folder-desktop-open": "", "folder-desktop": "", - "folder-development-open.clone": "", - "folder-development.clone": "", + "folder-development-open.clone": "", + "folder-development.clone": "", "folder-directive-open": "", "folder-directive": "", "folder-dist-open": "", @@ -309,8 +315,10 @@ "folder-fastlane": "", "folder-favicon-open": "", "folder-favicon": "", - "folder-firebase-open": "", - "folder-firebase": "", + "folder-firebase-open": "", + "folder-firebase": "", + "folder-firestore-open": "", + "folder-firestore": "", "folder-flow-open": "", "folder-flow": "", "folder-flutter-open": "", @@ -391,6 +399,8 @@ "folder-keys": "", "folder-kubernetes-open": "", "folder-kubernetes": "", + "folder-kusto-open": "", + "folder-kusto": "", "folder-layout-open": "", "folder-layout": "", "folder-lefthook-open": "", @@ -443,10 +453,10 @@ "folder-next": "", "folder-ngrx-actions-open.clone": "", "folder-ngrx-actions.clone": "", - "folder-ngrx-effects-open.clone": "", - "folder-ngrx-effects.clone": "", - "folder-ngrx-entities-open.clone": "", - "folder-ngrx-entities.clone": "", + "folder-ngrx-effects-open.clone": "", + "folder-ngrx-effects.clone": "", + "folder-ngrx-entities-open.clone": "", + "folder-ngrx-entities.clone": "", "folder-ngrx-reducer-open.clone": "", "folder-ngrx-reducer.clone": "", "folder-ngrx-selectors-open.clone": "", @@ -461,7 +471,7 @@ "folder-nuxt": "", "folder-obsidian-open": "", "folder-obsidian": "", - "folder-open": "", + "folder-open": "", "folder-other-open": "", "folder-other": "", "folder-packages-open": "", @@ -500,8 +510,8 @@ "folder-queue": "", "folder-react-components-open": "", "folder-react-components": "", - "folder-redux-actions-open.clone": "", - "folder-redux-actions.clone": "", + "folder-redux-actions-open.clone": "", + "folder-redux-actions.clone": "", "folder-redux-reducer-open": "", "folder-redux-reducer": "", "folder-redux-selector-open.clone": "", @@ -518,8 +528,8 @@ "folder-review": "", "folder-robot-open": "", "folder-robot": "", - "folder-root-open": "", - "folder-root": "", + "folder-root-open": "", + "folder-root": "", "folder-routes-open": "", "folder-routes": "", "folder-rules-open": "", @@ -644,7 +654,7 @@ "folder-yarn": "", "folder-zeabur-open": "", "folder-zeabur": "", - "folder": "", + "folder": "", "font": "", "forth": "", "fortran": "", @@ -738,7 +748,7 @@ "knip": "", "kotlin": "", "kubernetes": "", - "kusto": "", + "kusto": "", "label": "", "laravel": "", "lefthook": "", diff --git a/options/label/Advanced.yaml b/options/label/Advanced.yaml index b1ecdd6d93..860645d5d5 100644 --- a/options/label/Advanced.yaml +++ b/options/label/Advanced.yaml @@ -22,49 +22,60 @@ labels: description: Breaking change that won't be backward compatible - name: "Reviewed/Duplicate" exclusive: true + exclusive_order: 2 color: 616161 description: This issue or pull request already exists - name: "Reviewed/Invalid" exclusive: true + exclusive_order: 3 color: 546e7a description: Invalid issue - name: "Reviewed/Confirmed" exclusive: true + exclusive_order: 1 color: 795548 description: Issue has been confirmed - name: "Reviewed/Won't Fix" exclusive: true + exclusive_order: 3 color: eeeeee description: This issue won't be fixed - name: "Status/Need More Info" exclusive: true + exclusive_order: 2 color: 424242 description: Feedback is required to reproduce issue or to continue work - name: "Status/Blocked" exclusive: true + exclusive_order: 1 color: 880e4f description: Something is blocking this issue or pull request - name: "Status/Abandoned" exclusive: true + exclusive_order: 3 color: "222222" description: Somebody has started to work on this but abandoned work - name: "Priority/Critical" exclusive: true + exclusive_order: 1 color: b71c1c description: The priority is critical priority: critical - name: "Priority/High" exclusive: true + exclusive_order: 2 color: d32f2f description: The priority is high priority: high - name: "Priority/Medium" exclusive: true + exclusive_order: 3 color: e64a19 description: The priority is medium priority: medium - name: "Priority/Low" exclusive: true + exclusive_order: 4 color: 4caf50 description: The priority is low priority: low diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 57d7e8e076..da7aef22d5 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -718,8 +718,6 @@ public_profile=Veřejný profil biography_placeholder=Řekněte nám něco o sobě! (Můžete použít Markdown) location_placeholder=Sdílejte svou přibližnou polohu s ostatními profile_desc=Nastavte, jak bude váš profil zobrazen ostatním uživatelům. Vaše hlavní e-mailová adresa bude použita pro oznámení, obnovení hesla a operace Git. -password_username_disabled=Nemáte oprávnění měnit jejich uživatelské jméno. Pro více informací kontaktujte svého administrátora. -password_full_name_disabled=Nemáte oprávnění měnit jejich celé jméno. Pro více informací kontaktujte správce webu. full_name=Celé jméno website=Web location=Místo @@ -1529,7 +1527,6 @@ issues.filter_project=Projekt issues.filter_project_all=Všechny projekty issues.filter_project_none=Žádný projekt issues.filter_assignee=Zpracovatel -issues.filter_assginee_no_assignee=Bez zpracovatele issues.filter_poster=Autor issues.filter_user_placeholder=Hledat uživatele issues.filter_user_no_select=Všichni uživatelé @@ -2157,7 +2154,6 @@ settings.advanced_settings=Pokročilá nastavení settings.wiki_desc=Povolit Wiki repozitáře settings.use_internal_wiki=Používat vestavěnou Wiki settings.default_wiki_branch_name=Výchozí název větve Wiki -settings.default_permission_everyone_access=Výchozí přístupová práva pro všechny přihlášené uživatele: settings.failed_to_change_default_wiki_branch=Změna výchozí větve wiki se nezdařila. settings.use_external_wiki=Používat externí Wiki settings.external_wiki_url=URL externí Wiki @@ -3242,8 +3238,6 @@ config.ssh_domain=Doména SSH serveru config.ssh_port=Port config.ssh_listen_port=Port pro naslouchání config.ssh_root_path=Kořenová cesta -config.ssh_key_test_path=Cesta testu klíčů -config.ssh_keygen_path=Cesta ke generátoru klíčů ('ssh-keygen') config.ssh_minimum_key_size_check=Kontrola minimální velikosti klíčů config.ssh_minimum_key_sizes=Minimální velikost klíčů diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 0b0544b89c..57f1c01404 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -724,8 +724,6 @@ public_profile=Öffentliches Profil biography_placeholder=Erzähle uns ein wenig über Dich selbst! (Du kannst Markdown verwenden) location_placeholder=Teile Deinen ungefähren Standort mit anderen profile_desc=Lege fest, wie dein Profil anderen Benutzern angezeigt wird. Deine primäre E-Mail-Adresse wird für Benachrichtigungen, Passwort-Wiederherstellung und webbasierte Git-Operationen verwendet. -password_username_disabled=Du bist nicht berechtigt, den Benutzernamen zu ändern. Bitte kontaktiere Deinen Seitenadministrator für weitere Details. -password_full_name_disabled=Du bist nicht berechtigt, den vollständigen Namen zu ändern. Bitte kontaktiere Deinen Seitenadministrator für weitere Details. full_name=Vollständiger Name website=Webseite location=Standort @@ -1530,7 +1528,6 @@ issues.filter_project=Projekt issues.filter_project_all=Alle Projekte issues.filter_project_none=Kein Projekt issues.filter_assignee=Zuständig -issues.filter_assginee_no_assignee=Niemand zuständig issues.filter_poster=Autor issues.filter_user_placeholder=Benutzer suchen issues.filter_user_no_select=Alle Benutzer @@ -3238,8 +3235,6 @@ config.ssh_domain=SSH-Server-Domain config.ssh_port=Port config.ssh_listen_port=Listen-Port config.ssh_root_path=Wurzelverzeichnis -config.ssh_key_test_path=Schlüssel-Test-Pfad -config.ssh_keygen_path=Keygen-Pfad („ssh-keygen“) config.ssh_minimum_key_size_check=Prüfung der Mindestschlüssellänge config.ssh_minimum_key_sizes=Mindestschlüssellängen diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index f430c0506c..c1e2f35abc 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -1377,7 +1377,6 @@ issues.filter_project=Έργο issues.filter_project_all=Όλα τα έργα issues.filter_project_none=Χωρίς έργα issues.filter_assignee=Αποδέκτης -issues.filter_assginee_no_assignee=Κανένας Αποδέκτης issues.filter_poster=Συγγραφέας issues.filter_type=Τύπος issues.filter_type.all_issues=Όλα τα ζητήματα @@ -2928,8 +2927,6 @@ config.ssh_domain=Domain Διακομιστή SSH config.ssh_port=Θύρα config.ssh_listen_port=Θύρα Ακρόασης config.ssh_root_path=Ριζική Διαδρομή -config.ssh_key_test_path=Διαδρομή Δοκιμής Κλειδιού -config.ssh_keygen_path=Διαδρομή Keygen ('ssh-keygen') config.ssh_minimum_key_size_check=Έλεγχος Ελάχιστου Μεγέθους Κλειδιού config.ssh_minimum_key_sizes=Ελάχιστα Μεγέθη Κλειδιών diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index b2e079c696..9e6c5e61ac 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -730,8 +730,8 @@ public_profile = Public Profile biography_placeholder = Tell us a little bit about yourself! (You can use Markdown) location_placeholder = Share your approximate location with others profile_desc = Control how your profile is show to other users. Your primary email address will be used for notifications, password recovery and web-based Git operations. -password_username_disabled = You are not allowed to change their username. Please contact your site administrator for more details. -password_full_name_disabled = You are not allowed to change their full name. Please contact your site administrator for more details. +password_username_disabled = You are not allowed to change your username. Please contact your site administrator for more details. +password_full_name_disabled = You are not allowed to change your full name. Please contact your site administrator for more details. full_name = Full Name website = Website location = Location @@ -926,6 +926,9 @@ permission_not_set = Not set permission_no_access = No Access permission_read = Read permission_write = Read and Write +permission_anonymous_read = Anonymous Read +permission_everyone_read = Everyone Read +permission_everyone_write = Everyone Write access_token_desc = Selected token permissions limit authorization only to the corresponding API routes. Read the documentation for more information. at_least_one_permission = You must select at least one permission to create a token permissions_list = Permissions: @@ -1138,6 +1141,7 @@ transfer.no_permission_to_reject = You do not have permission to reject this tra desc.private = Private desc.public = Public +desc.public_access = Public Access desc.template = Template desc.internal = Internal desc.archived = Archived @@ -1547,7 +1551,7 @@ issues.filter_project = Project issues.filter_project_all = All projects issues.filter_project_none = No project issues.filter_assignee = Assignee -issues.filter_assginee_no_assignee = Assigned to nobody +issues.filter_assignee_no_assignee = Assigned to nobody issues.filter_assignee_any_assignee = Assigned to anybody issues.filter_poster = Author issues.filter_user_placeholder = Search users @@ -1651,6 +1655,8 @@ issues.label_archived_filter = Show archived labels issues.label_archive_tooltip = Archived labels are excluded by default from the suggestions when searching by label. issues.label_exclusive_desc = Name the label scope/item to make it mutually exclusive with other scope/ labels. issues.label_exclusive_warning = Any conflicting scoped labels will be removed when editing the labels of an issue or pull request. +issues.label_exclusive_order = Sort Order +issues.label_exclusive_order_tooltip = Exclusive labels in the same scope will be sorted according to this numeric order. issues.label_count = %d labels issues.label_open_issues = %d open issues/pull requests issues.label_edit = Edit @@ -2133,6 +2139,12 @@ contributors.contribution_type.deletions = Deletions settings = Settings settings.desc = Settings is where you can manage the settings for the repository settings.options = Repository +settings.public_access = Public Access +settings.public_access_desc = Configure public visitor's access permissions to override the defaults of this repository. +settings.public_access.docs.not_set = Not Set: no extra public access permission. The visitor's permission follows the repository's visibility and member permissions. +settings.public_access.docs.anonymous_read = Anonymous Read: users who are not logged in can access the unit with read permission. +settings.public_access.docs.everyone_read = Everyone Read: all logged-in users can access the unit with read permission. Read permission of issues/pull-requests units also means users can create new issues/pull-requests. +settings.public_access.docs.everyone_write = Everyone Write: all logged-in users have write permission to the unit. Only Wiki unit supports this permission. settings.collaboration = Collaborators settings.collaboration.admin = Administrator settings.collaboration.write = Write @@ -2179,7 +2191,6 @@ settings.advanced_settings = Advanced Settings settings.wiki_desc = Enable Repository Wiki settings.use_internal_wiki = Use Built-In Wiki settings.default_wiki_branch_name = Default Wiki Branch Name -settings.default_permission_everyone_access = Default access permission for all signed-in users: settings.failed_to_change_default_wiki_branch = Failed to change the default wiki branch. settings.use_external_wiki = Use External Wiki settings.external_wiki_url = External Wiki URL @@ -2724,6 +2735,7 @@ branch.restore_success = Branch "%s" has been restored. branch.restore_failed = Failed to restore branch "%s". branch.protected_deletion_failed = Branch "%s" is protected. It cannot be deleted. branch.default_deletion_failed = Branch "%s" is the default branch. It cannot be deleted. +branch.default_branch_not_exist = Default branch "%s" does not exist. branch.restore = Restore Branch "%s" branch.download = Download Branch "%s" branch.rename = Rename Branch "%s" @@ -3277,8 +3289,6 @@ config.ssh_domain = SSH Server Domain config.ssh_port = Port config.ssh_listen_port = Listen Port config.ssh_root_path = Root Path -config.ssh_key_test_path = Key Test Path -config.ssh_keygen_path = Keygen ('ssh-keygen') Path config.ssh_minimum_key_size_check = Minimum Key Size Check config.ssh_minimum_key_sizes = Minimum Key Sizes diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index debf9a6f5d..82d2e2a3b9 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -1367,7 +1367,6 @@ issues.filter_project=Proyecto issues.filter_project_all=Todos los proyectos issues.filter_project_none=Ningún proyecto issues.filter_assignee=Asignada a -issues.filter_assginee_no_assignee=Sin asignado issues.filter_poster=Autor issues.filter_type=Tipo issues.filter_type.all_issues=Todas las incidencias @@ -2908,8 +2907,6 @@ config.ssh_domain=Dominio del servidor SSH config.ssh_port=Puerto config.ssh_listen_port=Puerto de escucha config.ssh_root_path=Ruta raíz -config.ssh_key_test_path=Ruta de la clave de prueba -config.ssh_keygen_path=Ruta del generador de claves ('ssh-keygen') config.ssh_minimum_key_size_check=Tamaño mínimo de la clave de verificación config.ssh_minimum_key_sizes=Tamaños de clave mínimos diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index 5c96cea8f6..b550916fb0 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -1059,7 +1059,6 @@ issues.filter_label_no_select=تمامی برچسبها issues.filter_milestone=نقطه عطف issues.filter_project_none=هیچ پروژه ثبت نشده issues.filter_assignee=مسئول رسیدگی -issues.filter_assginee_no_assignee=بدون مسئول رسیدگی issues.filter_type=نوع issues.filter_type.all_issues=همه مسائل issues.filter_type.assigned_to_you=به شما محول شده @@ -2280,8 +2279,6 @@ config.ssh_domain=دامنه سرور SSH config.ssh_port=درگاه (پورت) config.ssh_listen_port=گوش دادن به پورت config.ssh_root_path=مسیر ریشه -config.ssh_key_test_path=مسیر کلید آزمایش -config.ssh_keygen_path=مسیر فایل ssh-keygen config.ssh_minimum_key_size_check=بررسی حداقل طول کلید config.ssh_minimum_key_sizes=حداقل اندازهی کلید ها diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index e853273375..69cee090fe 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -1538,8 +1538,6 @@ config.ssh_enabled=Käytössä config.ssh_port=Portti config.ssh_listen_port=Kuuntele porttia config.ssh_root_path=Juuren polku -config.ssh_key_test_path=Polku jossa avaimet testataan -config.ssh_keygen_path=Keygen ('ssh-keygen') polku config.ssh_minimum_key_size_check=Avaimen vähimmäiskoko tarkistus config.ssh_minimum_key_sizes=Avaimen vähimmäiskoot diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index c9b3345382..466a89bdfa 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -457,6 +457,7 @@ oauth_signup_submit=Finaliser la création du compte oauth_signin_tab=Lier à un compte existant oauth_signin_title=Connectez-vous pour autoriser le compte lié oauth_signin_submit=Lier un compte +oauth.signin.error.general=Une erreur s’est produite lors du traitement de la demande d’autorisation : %s. Si l’erreur persiste, veuillez contacter l’administrateur du site. oauth.signin.error.access_denied=La demande d'autorisation a été refusée. oauth.signin.error.temporarily_unavailable=L'autorisation a échoué car le serveur d'authentification est temporairement indisponible. Veuillez réessayer plus tard. oauth_callback_unable_auto_reg=L’inscription automatique est activée, mais le fournisseur OAuth2 %[1]s a signalé des champs manquants : %[2]s, impossible de créer un compte automatiquement, veuillez créer ou lier un compte, ou bien contacter l’administrateur du site. @@ -729,8 +730,8 @@ public_profile=Profil public biography_placeholder=Parlez-nous un peu de vous ! (Vous pouvez utiliser Markdown) location_placeholder=Partagez votre position approximative avec d'autres personnes profile_desc=Contrôlez comment votre profil est affiché aux autres utilisateurs. Votre adresse courriel principale sera utilisée pour les notifications, la récupération de mot de passe et les opérations Git basées sur le Web. -password_username_disabled=Vous n’êtes pas autorisé à modifier leur nom d’utilisateur. Veuillez contacter l’administrateur de votre site pour plus de détails. -password_full_name_disabled=Vous n’êtes pas autorisé à modifier leur nom complet. Veuillez contacter l’administrateur du site pour plus de détails. +password_username_disabled=Vous n’êtes pas autorisé à modifier votre nom d’utilisateur. Veuillez contacter l’administrateur de votre site pour plus de détails. +password_full_name_disabled=Vous n’êtes pas autorisé à modifier votre nom complet. Veuillez contacter l’administrateur du site pour plus de détails. full_name=Nom complet website=Site Web location=Localisation @@ -925,6 +926,9 @@ permission_not_set=Non défini permission_no_access=Aucun accès permission_read=Lecture permission_write=Lecture et écriture +permission_anonymous_read=Consultation anonyme +permission_everyone_read=Consultation collective +permission_everyone_write=Participation collective access_token_desc=Les autorisations des jetons sélectionnées se limitent aux routes API correspondantes. Lisez la documentation pour plus d’informations. at_least_one_permission=Vous devez sélectionner au moins une permission pour créer un jeton. permissions_list=Autorisations : @@ -1137,6 +1141,7 @@ transfer.no_permission_to_reject=Vous n’êtes pas autorisé à rejeter ce tran desc.private=Privé desc.public=Publique +desc.public_access=Accès public desc.template=Modèle desc.internal=Interne desc.archived=Archivé @@ -1545,6 +1550,8 @@ issues.filter_project=Projet issues.filter_project_all=Tous les projets issues.filter_project_none=Aucun projet issues.filter_assignee=Assigné +issues.filter_assignee_no_assignee=Non-assigné +issues.filter_assignee_any_assignee=Assigné issues.filter_poster=Auteur issues.filter_user_placeholder=Rechercher des utilisateurs issues.filter_user_no_select=Tous les utilisateurs @@ -1647,6 +1654,8 @@ issues.label_archived_filter=Afficher les labels archivés issues.label_archive_tooltip=Les labels archivés sont par défaut exclus des suggestions lors de la recherche par label. issues.label_exclusive_desc=Remarque : pour rendre des labels mutuellement exclusifs, préfixez leur nom d’une portée au format portée/label. issues.label_exclusive_warning=Tout label d'une portée en conflit sera retiré lors de la modification des labels d’un ticket ou d’une demande d’ajout. +issues.label_exclusive_order=Ordre de tri +issues.label_exclusive_order_tooltip=Les labels exclusifs partageant la même portée seront triées selon cet ordre numérique. issues.label_count=%d labels issues.label_open_issues=%d tickets ouverts issues.label_edit=Éditer @@ -1706,11 +1715,11 @@ issues.start_tracking_history=`a commencé son travail %s.` issues.tracker_auto_close=Le minuteur sera automatiquement arrêté quand le ticket sera fermé. issues.tracking_already_started=`Vous avez déjà un minuteur en cours sur un autre ticket !` issues.stop_tracking=Arrêter le minuteur -issues.stop_tracking_history=a travaillé sur %[1]s %[2]s +issues.stop_tracking_history=a travaillé %[1]s %[2]s issues.cancel_tracking=Abandonner issues.cancel_tracking_history=`a abandonné son minuteur %s.` issues.del_time=Supprimer ce minuteur du journal -issues.add_time_history=a pointé du temps de travail sur %[1]s, %[2]s +issues.add_time_history=a pointé %[1]s de travail %[2]s. issues.del_time_history=`a supprimé son temps de travail %s.` issues.add_time_manually=Temps pointé manuellement issues.add_time_hours=Heures @@ -2129,6 +2138,12 @@ contributors.contribution_type.deletions=Suppressions settings=Paramètres settings.desc=Les paramètres sont l'endroit où gérer les options du dépôt settings.options=Dépôt +settings.public_access=Accès public +settings.public_access_desc=Configurer les permissions des visiteurs publics remplaçant les valeurs par défaut de ce dépôt. +settings.public_access.docs.not_set=Non défini : ne donne aucune permission supplémentaire. Les règles du dépôt et les permissions des utilisateurs font foi. +settings.public_access.docs.anonymous_read=Lecture anonyme : les utilisateurs qui ne sont pas connectés peuvent consulter la ressource. +settings.public_access.docs.everyone_read=Consultation publique : tous les utilisateurs connectés peuvent consulter la ressource. Mettre les tickets et demandes d’ajouts en accès public signifie que les utilisateurs connectés peuvent en créer. +settings.public_access.docs.everyone_write=Participation publique : tous les utilisateurs connectés ont la permission d’écrire sur la ressource. Seule le Wiki supporte cette autorisation. settings.collaboration=Collaborateurs settings.collaboration.admin=Administrateur settings.collaboration.write=Écriture @@ -2175,7 +2190,6 @@ settings.advanced_settings=Paramètres avancés settings.wiki_desc=Activer le wiki du dépôt settings.use_internal_wiki=Utiliser le wiki interne settings.default_wiki_branch_name=Nom de la branche du Wiki par défaut -settings.default_permission_everyone_access=Autorisation d’accès par défaut pour tous les utilisateurs connectés : settings.failed_to_change_default_wiki_branch=Impossible de modifier la branche du wiki par défaut. settings.use_external_wiki=Utiliser un wiki externe settings.external_wiki_url=URL Wiki externe @@ -2720,6 +2734,7 @@ branch.restore_success=La branche "%s" a été restaurée. branch.restore_failed=Impossible de restaurer la branche "%s". branch.protected_deletion_failed=La branche "%s" est protégé. Elle ne peut pas être supprimée. branch.default_deletion_failed=La branche "%s" est la branche par défaut. Elle ne peut pas être supprimée. +branch.default_branch_not_exist=La branche par défaut « %s » n‘existe pas. branch.restore=`Restaurer la branche "%s"` branch.download=`Télécharger la branche "%s"` branch.rename=`Renommer la branche "%s"` @@ -3273,8 +3288,6 @@ config.ssh_domain=Domaine du serveur SSH config.ssh_port=Port config.ssh_listen_port=Port d'écoute config.ssh_root_path=Emplacement racine -config.ssh_key_test_path=Chemin de test des clés -config.ssh_keygen_path=Chemin vers le générateur de clés (« ssh-keygen ») config.ssh_minimum_key_size_check=Vérification de la longueur de clé minimale config.ssh_minimum_key_sizes=Tailles de clé minimales diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini index 35ca330d6d..a65ed357b1 100644 --- a/options/locale/locale_ga-IE.ini +++ b/options/locale/locale_ga-IE.ini @@ -730,8 +730,6 @@ public_profile=Próifíl Phoiblí biography_placeholder=Inis dúinn beagán fút féin! (Is féidir leat Markdown a úsáid) location_placeholder=Comhroinn do shuíomh thart le daoine eile profile_desc=Rialú conas a thaispeánfar do phróifíl d'úsáideoirí eile. Úsáidfear do phríomhsheoladh ríomhphoist le haghaidh fógraí, aisghabháil pasfhocail agus oibríochtaí Git gréasán-bhunaithe. -password_username_disabled=Níl cead agat a n-ainm úsáideora a athrú. Déan teagmháil le do riarthóir suímh le haghaidh tuilleadh sonraí. -password_full_name_disabled=Níl cead agat a n-ainm iomlán a athrú. Déan teagmháil le do riarthóir suímh le haghaidh tuilleadh sonraí. full_name=Ainm Iomlán website=Láithreán Gréasáin location=Suíomh @@ -926,6 +924,9 @@ permission_not_set=Níl leagtha permission_no_access=Gan rochtain permission_read=Léigh permission_write=Léigh agus Scríobh +permission_anonymous_read=Léamh gan Ainm +permission_everyone_read=Léigh gach duine +permission_everyone_write=Scríobh gach duine access_token_desc=Ní chuireann ceadchomharthaí roghnaithe ach teorainn leis an údarú do na bealaí API comhfhreagracha. Léigh doiciméadúchán chun tuilleadh eolais a fháil. at_least_one_permission=Ní mór duit cead amháin ar a laghad a roghnú chun comhartha a chruthú permissions_list=Ceadanna: @@ -1138,6 +1139,7 @@ transfer.no_permission_to_reject=Níl cead agat an aistriú seo a dhiúltú. desc.private=Príobháideach desc.public=Poiblí +desc.public_access=Rochtain Phoiblí desc.template=Teimpléad desc.internal=Inmheánach desc.archived=Cartlannaithe @@ -1546,7 +1548,6 @@ issues.filter_project=Tionscadal issues.filter_project_all=Gach tionscadal issues.filter_project_none=Gan aon tionscadal issues.filter_assignee=Sannaitheoir -issues.filter_assginee_no_assignee=Sannta do dhuine ar bith issues.filter_assignee_any_assignee=Sannta do dhuine ar bith issues.filter_poster=Údar issues.filter_user_placeholder=Cuardaigh úsáideoirí @@ -2132,6 +2133,12 @@ contributors.contribution_type.deletions=Scriosadh settings=Socruithe settings.desc=Is é socruithe an áit ar féidir leat na socruithe don stóras a bhainistiú settings.options=Stóras +settings.public_access=Rochtain Phoiblí +settings.public_access_desc=Cumraigh ceadanna rochtana an chuairteora phoiblí chun réamhshocruithe an stóras seo a shárú. +settings.public_access.docs.not_set=Gan Socrú: níl cead rochtana poiblí breise ar bith. Leanann cead an chuairteora infheictheacht an stór agus ceadanna na mball. +settings.public_access.docs.anonymous_read=Léamh gan Ainm: is féidir le húsáideoirí nach bhfuil logáilte isteach rochtain a fháil ar an aonad le cead léite. +settings.public_access.docs.everyone_read=Léamh ag Gach Duine: is féidir le gach úsáideoir logáilte isteach rochtain a fháil ar an aonad le cead léite. Ciallaíonn cead léite na n-aonad eisiúna/iarrataí tarraingthe freisin gur féidir le húsáideoirí saincheisteanna nua/iarratais tarraingthe a chruthú. +settings.public_access.docs.everyone_write=Scríobh Gach Duine: tá cead scríofa ag gach úsáideoir logáilte isteach don aonad. Ní thacaíonn ach aonad Vicí leis an gcead seo. settings.collaboration=Comhoibritheoirí settings.collaboration.admin=Riarthóir settings.collaboration.write=Scríobh @@ -2178,7 +2185,6 @@ settings.advanced_settings=Ardsocruithe settings.wiki_desc=Cumasaigh Stór Vicí settings.use_internal_wiki=Úsáid Vicí Insuite settings.default_wiki_branch_name=Ainm Brainse Réamhshocraithe Vicí -settings.default_permission_everyone_access=Cead rochtana réamhshocraithe do gach úsáideoir sínithe isteach: settings.failed_to_change_default_wiki_branch=Theip ar an brainse réamhshocraithe vicí a athrú. settings.use_external_wiki=Úsáid Vicí Seachtrach settings.external_wiki_url=URL Vicí Seachtrach @@ -3276,8 +3282,6 @@ config.ssh_domain=Fearainn Freastalaí SSH config.ssh_port=Calafort config.ssh_listen_port=Éist Calafort config.ssh_root_path=Cosán Fréimhe -config.ssh_key_test_path=Cosán Tástáil Eochair -config.ssh_keygen_path=Keygen ('ssh-keygen') Cosán config.ssh_minimum_key_size_check=Seiceáil Íosta Méid Eochair config.ssh_minimum_key_sizes=Méideanna Íosta Eochrach diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini index a57d6960dd..9be048c42f 100644 --- a/options/locale/locale_hu-HU.ini +++ b/options/locale/locale_hu-HU.ini @@ -1412,8 +1412,6 @@ config.ssh_start_builtin_server=Beépített szerver használata config.ssh_port=Port config.ssh_listen_port=Figyelő port config.ssh_root_path=Gyökérkönyvtár -config.ssh_key_test_path=Kulcs ellenőrzés útvonala -config.ssh_keygen_path=Kulcsgeneráló ('ssh-keygen') elérési útja config.ssh_minimum_key_size_check=Kulcsok minimum méretének ellenőrzése config.ssh_minimum_key_sizes=Minimális kulcsok méretek diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini index c54bfbb924..9e2244c1ab 100644 --- a/options/locale/locale_id-ID.ini +++ b/options/locale/locale_id-ID.ini @@ -1219,8 +1219,6 @@ config.ssh_enabled=Aktif config.ssh_port=Port config.ssh_listen_port=Listen Port config.ssh_root_path=Path Induk -config.ssh_key_test_path=Path Key Test -config.ssh_keygen_path=Path Keygen ('ssh-keygen') config.ssh_minimum_key_size_check=Periksa ukuran kunci minimum config.ssh_minimum_key_sizes=Ukuran kunci minimum diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index 3ce5a6770f..73d1762c59 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -1143,7 +1143,6 @@ issues.filter_milestone=Traguardo issues.filter_project=Progetto issues.filter_project_none=Nessun progetto issues.filter_assignee=Assegnatario -issues.filter_assginee_no_assignee=Nessun assegnatario issues.filter_poster=Autore issues.filter_type=Tipo issues.filter_type.all_issues=Tutti i problemi @@ -2465,8 +2464,6 @@ config.ssh_domain=Dominio Server Ssh config.ssh_port=Porta config.ssh_listen_port=Porta in ascolto config.ssh_root_path=Percorso Root -config.ssh_key_test_path=Percorso chiave di test -config.ssh_keygen_path=Percorso Keygen ('ssh-keygen') config.ssh_minimum_key_size_check=Verifica delle dimensioni minime della chiave config.ssh_minimum_key_sizes=Dimensioni minime della chiave diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 9c25c568fa..e98c453744 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -730,8 +730,6 @@ public_profile=公開プロフィール biography_placeholder=自己紹介してください!(Markdownを使うことができます) location_placeholder=おおよその場所を他の人と共有 profile_desc=あなたのプロフィールが他のユーザーにどのように表示されるかを制御します。あなたのプライマリメールアドレスは、通知、パスワードの回復、WebベースのGit操作に使用されます。 -password_username_disabled=ユーザー名の変更は許可されていません。詳細はサイト管理者にお問い合わせください。 -password_full_name_disabled=フルネームの変更は許可されていません。詳細はサイト管理者にお問い合わせください。 full_name=フルネーム website=Webサイト location=場所 @@ -1546,7 +1544,6 @@ issues.filter_project=プロジェクト issues.filter_project_all=すべてのプロジェクト issues.filter_project_none=プロジェクトなし issues.filter_assignee=担当者 -issues.filter_assginee_no_assignee=担当者なし issues.filter_assignee_any_assignee=担当者あり issues.filter_poster=作成者 issues.filter_user_placeholder=ユーザーを検索 @@ -2178,7 +2175,6 @@ settings.advanced_settings=拡張設定 settings.wiki_desc=Wikiを有効にする settings.use_internal_wiki=ビルトインのWikiを使用する settings.default_wiki_branch_name=デフォルトのWikiブランチ名 -settings.default_permission_everyone_access=すべてのサインインユーザーにデフォルトで許可するアクセス権限: settings.failed_to_change_default_wiki_branch=デフォルトのWikiブランチを変更できませんでした。 settings.use_external_wiki=外部のWikiを使用する settings.external_wiki_url=外部WikiのURL @@ -3276,8 +3272,6 @@ config.ssh_domain=SSHサーバーのドメイン config.ssh_port=ポート config.ssh_listen_port=待受ポート config.ssh_root_path=ルートパス -config.ssh_key_test_path=キーテストパス -config.ssh_keygen_path=キージェネレータ('ssh-keygen')パス config.ssh_minimum_key_size_check=最小キー長のチェック config.ssh_minimum_key_sizes=最小キー長 diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini index 697371c2c9..b6b9cf373e 100644 --- a/options/locale/locale_ko-KR.ini +++ b/options/locale/locale_ko-KR.ini @@ -707,7 +707,6 @@ issues.filter_label=레이블 issues.filter_label_no_select=모든 레이블 issues.filter_milestone=마일스톤 issues.filter_assignee=담당자 -issues.filter_assginee_no_assignee=담당자 없음 issues.filter_type=유형 issues.filter_type.all_issues=모든 이슈 issues.filter_type.assigned_to_you=나에게 할당됨 @@ -1373,8 +1372,6 @@ config.ssh_start_builtin_server=빌트-인 서버 사용 config.ssh_port=포트 config.ssh_listen_port=수신 대기 포트 config.ssh_root_path=최상위 경로 -config.ssh_key_test_path=주 테스트 경로 -config.ssh_keygen_path=키 생성 ('ssh-keygen') 경로 config.ssh_minimum_key_size_check=최소 키 사이즈 검사 config.ssh_minimum_key_sizes=최소 키 사이즈 diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 4439537fd7..fa6736df1a 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -1383,7 +1383,6 @@ issues.filter_project=Projekts issues.filter_project_all=Visi projekti issues.filter_project_none=Nav projekta issues.filter_assignee=Atbildīgais -issues.filter_assginee_no_assignee=Nav atbildīgā issues.filter_poster=Autors issues.filter_type=Veids issues.filter_type.all_issues=Visas problēmas @@ -2931,8 +2930,6 @@ config.ssh_domain=SSH servera domēns config.ssh_port=Ports config.ssh_listen_port=Klausīšanās ports config.ssh_root_path=Saknes ceļš -config.ssh_key_test_path=Atslēgu pārbaudes ceļš -config.ssh_keygen_path=Keygen ('ssh-keygen') ceļš config.ssh_minimum_key_size_check=Minimālā atslēgas lieluma pārbaude config.ssh_minimum_key_sizes=Minimālais atslēgas lielums diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index 77bf2d2d59..345990b532 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -1141,7 +1141,6 @@ issues.filter_milestone=Mijlpaal issues.filter_project=Project issues.filter_project_none=Geen project issues.filter_assignee=Aangewezene -issues.filter_assginee_no_assignee=Geen verantwoordelijke issues.filter_poster=Auteur issues.filter_type=Type issues.filter_type.all_issues=Alle kwesties @@ -2311,8 +2310,6 @@ config.ssh_start_builtin_server=Gebruik de ingebouwde server config.ssh_port=Poort config.ssh_listen_port=Luister op poort config.ssh_root_path=Root-pad -config.ssh_key_test_path=Pad voor key-tests -config.ssh_keygen_path=Pad van keygen ('ssh-keygen') config.ssh_minimum_key_size_check=Controleer minimale key-lengte config.ssh_minimum_key_sizes=Minimale key-lengtes diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index f64b8771ac..30c08bc6db 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -1054,7 +1054,6 @@ issues.filter_label_no_select=Wszystkie etykiety issues.filter_milestone=Kamień milowy issues.filter_project_none=Brak projektu issues.filter_assignee=Przypisany -issues.filter_assginee_no_assignee=Brak przypisania issues.filter_type=Typ issues.filter_type.all_issues=Wszystkie zgłoszenia issues.filter_type.assigned_to_you=Przypisane do Ciebie @@ -2199,8 +2198,6 @@ config.ssh_start_builtin_server=Wykorzystaj wbudowany serwer config.ssh_port=Port config.ssh_listen_port=Port nasłuchiwania config.ssh_root_path=Ścieżka do katalogu głównego -config.ssh_key_test_path=Ścieżka do klucza testowego -config.ssh_keygen_path=Ścieżka do generatora ('ssh-keygen') config.ssh_minimum_key_size_check=Sprawdzanie minimalnej długości klucza config.ssh_minimum_key_sizes=Minimalne rozmiary kluczy diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 204fedc311..3dcbdf87aa 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -2878,8 +2878,6 @@ config.ssh_domain=Domínio do servidor SSH config.ssh_port=Porta config.ssh_listen_port=Porta de escuta config.ssh_root_path=Caminho da raiz -config.ssh_key_test_path=Caminho da chave de teste -config.ssh_keygen_path=Caminho do keygen ('ssh-keygen') config.ssh_minimum_key_size_check=Verificar tamanho mínimo da chave config.ssh_minimum_key_sizes=Tamanhos mínimos da chave diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 8b4d66a718..03ffb1df14 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -730,8 +730,8 @@ public_profile=Perfil público biography_placeholder=Conte-nos um pouco sobre si! (Pode usar Markdown) location_placeholder=Partilhe a sua localização aproximada com outros profile_desc=Controle como o seu perfil é apresentado aos outros utilizadores. O seu endereço de email principal será usado para notificações, recuperação de senha e operações Git baseadas na web. -password_username_disabled=Não tem permissão para alterar os nomes de utilizador deles/delas. Entre em contacto com o administrador para saber mais detalhes. -password_full_name_disabled=Não tem permissão para alterar o nome completo deles/delas. Entre em contacto com o administrador para saber mais detalhes. +password_username_disabled=Não tem permissão para alterar o seu nome de utilizador. Entre em contacto com o administrador para saber mais detalhes. +password_full_name_disabled=Não tem permissão para alterar o seu nome completo. Entre em contacto com o administrador para saber mais detalhes. full_name=Nome completo website=Sítio web location=Localização @@ -926,6 +926,9 @@ permission_not_set=Não definido permission_no_access=Sem acesso permission_read=Lidas permission_write=Leitura e escrita +permission_anonymous_read=Leitura anónima +permission_everyone_read=Leitura pública +permission_everyone_write=Escrita pública access_token_desc=As permissões dos códigos escolhidos limitam a autorização apenas às rotas da API correspondentes. Leia a documentação para obter mais informação. at_least_one_permission=Tem que escolher pelo menos uma permissão para criar um código permissions_list=Permissões: @@ -1138,6 +1141,7 @@ transfer.no_permission_to_reject=Você não tem permissão para rejeitar esta tr desc.private=Privado desc.public=Público +desc.public_access=Acesso público desc.template=Modelo desc.internal=Interno desc.archived=Arquivado @@ -1546,7 +1550,7 @@ issues.filter_project=Planeamento issues.filter_project_all=Todos os planeamentos issues.filter_project_none=Nenhum planeamento issues.filter_assignee=Encarregado -issues.filter_assginee_no_assignee=Sem encarregado +issues.filter_assignee_no_assignee=Não atribuído issues.filter_assignee_any_assignee=Atribuído a qualquer pessoa issues.filter_poster=Autor(a) issues.filter_user_placeholder=Procurar utilizadores @@ -1650,6 +1654,8 @@ issues.label_archived_filter=Mostrar rótulos arquivados issues.label_archive_tooltip=Os rótulos arquivados são, por norma, excluídos das sugestões ao pesquisar por rótulo. issues.label_exclusive_desc=Nomeie o rótulo âmbito/item para torná-lo mutuamente exclusivo com outros rótulos do âmbito/. issues.label_exclusive_warning=Quaisquer rótulos com âmbito que estejam em conflito irão ser removidos ao editar os rótulos de uma questão ou de um pedido de integração. +issues.label_exclusive_order=Ordenação +issues.label_exclusive_order_tooltip=Rótulos exclusivos no mesmo âmbito serão ordenados de acordo com esta ordem numérica. issues.label_count=%d rótulos issues.label_open_issues=%d questões abertas issues.label_edit=Editar @@ -2132,6 +2138,12 @@ contributors.contribution_type.deletions=Eliminações settings=Configurações settings.desc=Configurações é onde pode gerir as configurações do repositório settings.options=Repositório +settings.public_access=Acesso público +settings.public_access_desc=Configurar as permissões de acesso público do visitante para substituir os valores predefinidos deste repositório. +settings.public_access.docs.not_set=Não definido: nenhuma permissão extra de acesso público. As permissões do visitante seguem a visibilidade e as permissões de membro do repositório. +settings.public_access.docs.anonymous_read=Leitura anónima: utilizadores sem sessão iniciada podem consultar a unidade. +settings.public_access.docs.everyone_read=Leitura pública: todos os utilizadores com sessão iniciada podem aceder à unidade com permissão de leitura. Permissão de leitura das unidades de questões / pedidos de integração também significa que os utilizadores podem criar novas questões / pedidos de integração. +settings.public_access.docs.everyone_write=Escrita pública: Todos os utilizadores com sessão iniciada têm permissão de escrita na unidade. Apenas a unidade Wiki suporta esta permissão. settings.collaboration=Colaboradores settings.collaboration.admin=Administrador settings.collaboration.write=Escrita @@ -2178,7 +2190,6 @@ settings.advanced_settings=Configurações avançadas settings.wiki_desc=Habilitar wiki do repositório settings.use_internal_wiki=Usar o wiki integrado settings.default_wiki_branch_name=Nome do ramo predefinido do wiki -settings.default_permission_everyone_access=Permissão de acesso predefinida para todos os utilizadores registados: settings.failed_to_change_default_wiki_branch=Falhou ao mudar o nome do ramo predefinido do wiki. settings.use_external_wiki=Usar um wiki externo settings.external_wiki_url=URL do wiki externo @@ -2723,6 +2734,7 @@ branch.restore_success=O ramo "%s" foi restaurado. branch.restore_failed=Falhou a restauração do ramo "%s". branch.protected_deletion_failed=O ramo "%s" está protegido, não pode ser eliminado. branch.default_deletion_failed=O ramo "%s" é o ramo principal, não pode ser eliminado. +branch.default_branch_not_exist=O ramo predefinido "%s" não existe. branch.restore=`Restaurar o ramo "%s"` branch.download=`Descarregar o ramo "%s"` branch.rename=`Renomear ramo "%s"` @@ -3276,8 +3288,6 @@ config.ssh_domain=Domínio do servidor SSH config.ssh_port=Porto config.ssh_listen_port=Porto de escuta config.ssh_root_path=Localização base -config.ssh_key_test_path=Localização do teste das chaves -config.ssh_keygen_path=Localização do gerador de chaves ('ssh-keygen') config.ssh_minimum_key_size_check=Verificação de tamanho mínimo da chave config.ssh_minimum_key_sizes=Tamanhos mínimos da chave diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index fe52165837..ae3c9a4ed9 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -1355,7 +1355,6 @@ issues.filter_project=Проект issues.filter_project_all=Все проекты issues.filter_project_none=Нет проекта issues.filter_assignee=Назначено -issues.filter_assginee_no_assignee=Нет ответственного issues.filter_poster=Автор issues.filter_type=Тип issues.filter_type.all_issues=Все задачи @@ -2871,8 +2870,6 @@ config.ssh_domain=Домен SSH сервера config.ssh_port=Порт config.ssh_listen_port=Прослушиваемый порт config.ssh_root_path=Корневой путь -config.ssh_key_test_path=Путь к тестовому ключу -config.ssh_keygen_path=Путь к генератору ключей ('ssh-keygen') config.ssh_minimum_key_size_check=Минимальный размер ключа проверки config.ssh_minimum_key_sizes=Минимальные размеры ключа diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini index 2cd7fb29b9..fbeea4591c 100644 --- a/options/locale/locale_si-LK.ini +++ b/options/locale/locale_si-LK.ini @@ -1025,7 +1025,6 @@ issues.filter_label_no_select=සියලු ලේබල issues.filter_milestone=සන්ධිස්ථානය issues.filter_project_none=ව්යාපෘති නැත issues.filter_assignee=අස්ගිනී -issues.filter_assginee_no_assignee=කිසිදු අස්වැද්දුමක් issues.filter_type=වර්ගය issues.filter_type.all_issues=සියලු ගැටළු issues.filter_type.assigned_to_you=ඔබට පවරා ඇත @@ -2241,8 +2240,6 @@ config.ssh_domain=SSH සේවාදායකය වසම් config.ssh_port=වරාය config.ssh_listen_port=සවන් වරාය config.ssh_root_path=මූල මාර්ගය -config.ssh_key_test_path=ප්රධාන ටෙස්ට් මාර්ගය -config.ssh_keygen_path=Keygen ('ssh-keygen') මාර්ගය config.ssh_minimum_key_size_check=අවම කී ප්රමාණය පරීක්ෂා config.ssh_minimum_key_sizes=අවම යතුරෙහි ප්රමාණ diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index dbe53aaadf..5a3091adac 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -873,7 +873,6 @@ issues.filter_label_no_select=Alla etiketter issues.filter_milestone=Milsten issues.filter_project_none=Inget projekt issues.filter_assignee=Förvärvare -issues.filter_assginee_no_assignee=Ingen tilldelad issues.filter_type=Typ issues.filter_type.all_issues=Alla ärenden issues.filter_type.assigned_to_you=Tilldelad dig @@ -1795,8 +1794,6 @@ config.ssh_start_builtin_server=Använd inbyggd Server config.ssh_port=Port config.ssh_listen_port=Lyssningsport config.ssh_root_path=Rotsökväg -config.ssh_key_test_path=Testsökväg för nyckel -config.ssh_keygen_path=Sökväg för nyckelgenerator ('ssh-keygen') config.ssh_minimum_key_size_check=Kontroll av minsta tillåtna nyckelstorlek config.ssh_minimum_key_sizes=Minsta tillåtna nyckelstorlek diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 1a044fca83..bec38b2ed1 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -1484,7 +1484,6 @@ issues.filter_project=Proje issues.filter_project_all=Tüm projeler issues.filter_project_none=Proje yok issues.filter_assignee=Atanan -issues.filter_assginee_no_assignee=Atanan yok issues.filter_poster=Yazar issues.filter_type=Tür issues.filter_type.all_issues=Tüm konular @@ -3107,8 +3106,6 @@ config.ssh_domain=SSH Sunucusu Alan Adı config.ssh_port=Bağlantı Noktası config.ssh_listen_port=Port'u Dinle config.ssh_root_path=Kök Yol -config.ssh_key_test_path=Anahtar Test Yolu -config.ssh_keygen_path=Keygen ('ssh-keygen') Yolu config.ssh_minimum_key_size_check=Minimum Anahtar Uzunluğu Kontrolü config.ssh_minimum_key_sizes=Minimum Anahtar Uzunlukları diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 8c8911ceb6..2aae533e7e 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -1071,7 +1071,6 @@ issues.filter_milestone=Етап issues.filter_project=Проєкт issues.filter_project_none=Проєкт відсутній issues.filter_assignee=Виконавець -issues.filter_assginee_no_assignee=Немає виконавця issues.filter_type=Тип issues.filter_type.all_issues=Всі задачі issues.filter_type.assigned_to_you=Призначене вам @@ -2291,8 +2290,6 @@ config.ssh_domain=Домен SSH сервера config.ssh_port=Порт config.ssh_listen_port=Порт що прослуховується config.ssh_root_path=Шлях до кореню -config.ssh_key_test_path=Шлях до тестового ключа -config.ssh_keygen_path=Шлях до генератора ключів ('ssh-keygen') config.ssh_minimum_key_size_check=Мінімальний розмір ключа перевірки config.ssh_minimum_key_sizes=Мінімальні розміри ключів diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index f36789921e..4d2ff7fb78 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -113,6 +113,7 @@ copy_type_unsupported=无法复制此类型的文件内容 write=撰写 preview=预览 loading=正在加载... +files=文件 error=错误 error404=您正尝试访问的页面 不存在 或 您尚未被授权 查看该页面。 @@ -169,6 +170,10 @@ search=搜索... type_tooltip=搜索类型 fuzzy=模糊 fuzzy_tooltip=包含近似匹配搜索词的结果 +words=词 +words_tooltip=仅包含匹配搜索词的结果 +regexp=正则表达式 +regexp_tooltip=仅包含匹配正则表达式搜索词的结果 exact=精确 exact_tooltip=仅包含精确匹配搜索词的结果 repo_kind=搜索仓库... @@ -385,6 +390,12 @@ show_only_public=只显示公开的 issues.in_your_repos=在您的仓库中 +guide_title=无活动 +guide_desc=您目前没有关注任何仓库或用户,所以没有要显示的内容。 您可以从下面的链接中探索感兴趣的仓库或用户。 +explore_repos=探索仓库 +explore_users=探索用户 +empty_org=目前还没有组织。 +empty_repo=目前还没有仓库。 [explore] repos=仓库 @@ -446,6 +457,7 @@ oauth_signup_submit=完成账号 oauth_signin_tab=绑定到现有帐号 oauth_signin_title=登录以授权绑定帐户 oauth_signin_submit=绑定账号 +oauth.signin.error.general=处理授权请求时出错:%s。如果此错误仍然存在,请与站点管理员联系。 oauth.signin.error.access_denied=授权请求被拒绝。 oauth.signin.error.temporarily_unavailable=授权失败,因为认证服务器暂时不可用。请稍后再试。 oauth_callback_unable_auto_reg=自动注册已启用,但OAuth2 提供商 %[1]s 返回缺失的字段:%[2]s,无法自动创建帐户,请创建或链接到一个帐户,或联系站点管理员。 @@ -718,8 +730,8 @@ public_profile=公开信息 biography_placeholder=告诉我们一点您自己! (您可以使用Markdown) location_placeholder=与他人分享你的大概位置 profile_desc=控制您的个人资料对其他用户的显示方式。您的主要电子邮件地址将用于通知、密码恢复和基于网页界面的 Git 操作 -password_username_disabled=不允许非本地用户更改他们的用户名。更多详情请联系您的系统管理员。 -password_full_name_disabled=您无权更改他们的全名。请联系您的站点管理员了解更多详情。 +password_username_disabled=您不被允许更改你的用户名。更多详情请联系您的系统管理员。 +password_full_name_disabled=您不被允许更改你的全名。请联系您的站点管理员了解更多详情。 full_name=自定义名称 website=个人网站 location=所在地区 @@ -914,6 +926,9 @@ permission_not_set=未设置 permission_no_access=无访问权限 permission_read=可读 permission_write=读写 +permission_anonymous_read=匿名读 +permission_everyone_read=所有人可读 +permission_everyone_write=所有人可写 access_token_desc=所选令牌权限仅限于对应的 API 路由的授权。阅读 文档 以获取更多信息。 at_least_one_permission=你需要选择至少一个权限才能创建令牌 permissions_list=权限: @@ -1016,6 +1031,9 @@ new_repo_helper=代码仓库包含了所有的项目文件,包括版本历史 owner=拥有者 owner_helper=由于最大仓库数量限制,一些组织可能不会显示在下拉列表中。 repo_name=仓库名称 +repo_name_profile_public_hint=.profile 是一个特殊的存储库,您可以使用它将 README.md 添加到您的公共组织资料中,任何人都可以看到。请确保它是公开的,并使用个人资料目录中的 README 对其进行初始化以开始使用。 +repo_name_profile_private_hint=.profile-private 是一个特殊的存储库,您可以使用它向您的组织成员个人资料添加 README.md,仅对组织成员可见。请确保它是私有的,并使用个人资料目录中的 README 对其进行初始化以开始使用。 +repo_name_helper=理想的仓库名称应由简短、有意义和独特的关键词组成。".profile" 和 ".profile-private" 可用于为用户/组织添加 README.md。 repo_size=仓库大小 template=模板 template_select=选择模板 @@ -1112,6 +1130,7 @@ blame.ignore_revs=忽略 .git-blame-ignore-revs 的修订。点 blame.ignore_revs.failed=忽略 .git-blame-ignore-revs 版本失败。 user_search_tooltip=最多显示30名用户 +tree_path_not_found=%[2]s 中不存在路径 %[1]s transfer.accept=接受转移 transfer.accept_desc=`转移到 "%s"` @@ -1122,6 +1141,7 @@ transfer.no_permission_to_reject=您没有权限拒绝此转让。 desc.private=私有库 desc.public=公开 +desc.public_access=公开访问 desc.template=模板 desc.internal=内部 desc.archived=已存档 @@ -1229,6 +1249,7 @@ create_new_repo_command=从命令行创建一个新的仓库 push_exist_repo=从命令行推送已经创建的仓库 empty_message=这个家伙很懒,什么都没有推送。 broken_message=无法读取此仓库下的 Git 数据。 联系此实例的管理员或删除此仓库。 +no_branch=该仓库没有任何分支。 code=代码 code.desc=查看源码、文件、提交和分支。 @@ -1341,6 +1362,8 @@ editor.new_branch_name_desc=新的分支名称... editor.cancel=取消 editor.filename_cannot_be_empty=文件名不能为空。 editor.filename_is_invalid=文件名 %s 无效 +editor.commit_email=提交邮箱地址 +editor.invalid_commit_email=提交的邮箱地址无效。 editor.branch_does_not_exist=此仓库中不存在名为 %s 的分支。 editor.branch_already_exists=此仓库已存在名为 %s 的分支。 editor.directory_is_a_file=%s 已经作为文件名在此仓库中存在。 @@ -1389,6 +1412,7 @@ commits.signed_by_untrusted_user_unmatched=由与提交者不匹配的未授信 commits.gpg_key_id=GPG 密钥 ID commits.ssh_key_fingerprint=SSH 密钥指纹 commits.view_path=在历史记录中的此处查看 +commits.view_file_diff=查看提交中的文件更改 commit.operations=操作 commit.revert=还原 @@ -1400,7 +1424,7 @@ commit.cherry-pick-content=选择 cherry-pick 的目标分支: commitstatus.error=错误 commitstatus.failure=失败 -commitstatus.pending=待定 +commitstatus.pending=队列 commitstatus.success=成功 ext_issues=访问外部工单 @@ -1435,7 +1459,7 @@ projects.column.set_default=设为默认 projects.column.set_default_desc=设置此列为未分类问题和合并请求的默认值 projects.column.delete=删除列 projects.column.deletion_desc=删除项目列会将所有相关问题移到“未分类”。是否继续? -projects.column.color=彩色 +projects.column.color=颜色 projects.open=开启 projects.close=关闭 projects.column.assigned_to=指派给 @@ -1449,6 +1473,8 @@ issues.filter_milestones=筛选里程碑 issues.filter_projects=筛选项目 issues.filter_labels=筛选标签 issues.filter_reviewers=筛选审核者 +issues.filter_no_results=没有结果 +issues.filter_no_results_placeholder=请尝试调整您的搜索过滤器。 issues.new=创建工单 issues.new.title_empty=标题不能为空 issues.new.labels=标签 @@ -1522,8 +1548,10 @@ issues.filter_milestone_open=进行中的里程碑 issues.filter_milestone_closed=已关闭的里程碑 issues.filter_project=项目 issues.filter_project_all=所有项目 -issues.filter_project_none=暂无项目 +issues.filter_project_none=未加项目 issues.filter_assignee=指派人筛选 +issues.filter_assignee_no_assignee=未指派给任何人 +issues.filter_assignee_any_assignee=已有指派 issues.filter_poster=作者 issues.filter_user_placeholder=搜索用户 issues.filter_user_no_select=所有用户 @@ -1597,9 +1625,9 @@ issues.ref_reopened_from=`重新打开这个工单 %[4]s scope/item 以使其与其他以 scope/ 开头的标签互斥。 issues.label_exclusive_warning=在编辑工单或合并请求的标签时,任何冲突的范围标签都将被删除。 +issues.label_exclusive_order=排序顺序 +issues.label_exclusive_order_tooltip=在同一个范围内的互斥标签将按照这个数字进行排序 issues.label_count=%d 个标签 issues.label_open_issues=%d 个开启的工单 issues.label_edit=编辑 @@ -1678,13 +1708,18 @@ issues.timetracker_timer_manually_add=添加时间 issues.time_estimate_set=设置预计时间 issues.time_estimate_display=预计: %s +issues.change_time_estimate_at=预估时间已修改为 %[1]s %[2]s issues.remove_time_estimate_at=删除预计时间 %s issues.time_estimate_invalid=预计时间格式无效 issues.start_tracking_history=`开始工作 %s` issues.tracker_auto_close=当此工单关闭时,自动停止计时器 issues.tracking_already_started=`你已经开始对 另一个工单 进行时间跟踪!` +issues.stop_tracking=停止计时器 +issues.stop_tracking_history=工作 %[1]s 于 %[2]s 停止 +issues.cancel_tracking=取消 issues.cancel_tracking_history=`取消时间跟踪 %s` issues.del_time=删除此时间跟踪日志 +issues.add_time_history=已于 %[2]s 添加计时 %[1] issues.del_time_history=`已删除时间 %s` issues.add_time_manually=手动添加时间 issues.add_time_hours=小时 @@ -1914,6 +1949,7 @@ pulls.outdated_with_base_branch=此分支相比基础分支已过期 pulls.close=关闭合并请求 pulls.closed_at=`于 %[2]s 关闭此合并请求 ` pulls.reopened_at=`重新打开此合并请求 %[2]s` +pulls.cmd_instruction_hint=查看命令行提示 pulls.cmd_instruction_checkout_title=检出 pulls.cmd_instruction_checkout_desc=从你的仓库中检出一个新的分支并测试变更。 pulls.cmd_instruction_merge_title=合并 @@ -1942,6 +1978,7 @@ pulls.upstream_diverging_prompt_behind_1=该分支落后于 %[2]s %[1]d 个提 pulls.upstream_diverging_prompt_behind_n=该分支落后于 %[2]s %[1]d 个提交 pulls.upstream_diverging_prompt_base_newer=基础分支 %s 有新的更改 pulls.upstream_diverging_merge=同步派生 +pulls.upstream_diverging_merge_confirm=要将 %[1]s 合并到 %[2]s 吗? pull.deleted_branch=(已删除): %s pull.agit_documentation=查看有关 AGit 的文档 @@ -2101,6 +2138,12 @@ contributors.contribution_type.deletions=删除 settings=设置 settings.desc=设置是你可以管理仓库设置的地方 settings.options=仓库 +settings.public_access=公开访问 +settings.public_access_desc=配置公共访客访问权限以覆盖此存储库的默认值。 +settings.public_access.docs.not_set=未设置:没有额外的公共访问权限。访客权限遵循存储库的可见性和成员权限。 +settings.public_access.docs.anonymous_read=匿名可读:未登录的用户可以通过读取权限访问单元。 +settings.public_access.docs.everyone_read=所有人可读:所有登录用户都可以通过读取权限访问单元。读取问题/拉取请求单元的权限也意味着用户可以创建新的问题/拉取请求。 +settings.public_access.docs.everyone_write=所有人可写:所有登录用户都有写入权限。只有百科支持此权限。 settings.collaboration=协作者 settings.collaboration.admin=管理员 settings.collaboration.write=可写权限 @@ -2314,6 +2357,8 @@ settings.event_fork=派生 settings.event_fork_desc=仓库被派生。 settings.event_wiki=百科 settings.event_wiki_desc=创建、重命名、编辑或删除了百科页面。 +settings.event_statuses=状态 +settings.event_statuses_desc=已从 API 更新提交状态。 settings.event_release=版本发布 settings.event_release_desc=发布、更新或删除版本时。 settings.event_push=推送 @@ -2351,6 +2396,9 @@ settings.event_pull_request_review_request=发起合并请求评审 settings.event_pull_request_review_request_desc=合并请求评审已请求或已取消 settings.event_pull_request_approvals=合并请求批准 settings.event_pull_request_merge=合并请求合并 +settings.event_header_workflow=工作流程事件 +settings.event_workflow_job=工作流任务 +settings.event_workflow_job_desc=Gitea Actions 工作流队列中、等待中、正在进行或已完成任务。 settings.event_package=软件包 settings.event_package_desc=软件包已在仓库中被创建或删除。 settings.branch_filter=分支过滤 @@ -2613,6 +2661,9 @@ diff.image.overlay=叠加 diff.has_escaped=这一行有隐藏的 Unicode 字符 diff.show_file_tree=显示文件树 diff.hide_file_tree=隐藏文件树 +diff.submodule_added=子模块 %[1]s 已添加到 %[2]s +diff.submodule_deleted=子模块 %[1]s 已从 %[2]s 中删除 +diff.submodule_updated=子模块 %[1]s 已更新:%[2]s releases.desc=跟踪项目版本和下载。 release.releases=版本发布 @@ -2683,6 +2734,7 @@ branch.restore_success=分支 "%s"已还原。 branch.restore_failed=还原分支 "%s"失败。 branch.protected_deletion_failed=不能删除受保护的分支 "%s"。 branch.default_deletion_failed=不能删除默认分支"%s"。 +branch.default_branch_not_exist=默认分支 %s 不存在。 branch.restore=`还原分支 "%s"` branch.download=`下载分支 "%s"` branch.rename=`重命名分支 "%s"` @@ -2697,6 +2749,8 @@ branch.create_branch_operation=创建分支 branch.new_branch=创建新分支 branch.new_branch_from=基于"%s"创建新分支 branch.renamed=分支 %s 被重命名为 %s。 +branch.rename_default_or_protected_branch_error=只有管理员能重命名默认分支和受保护的分支。 +branch.rename_protected_branch_failed=此分支受到 glob 语法规则的保护。 tag.create_tag=创建标签 %s tag.create_tag_operation=创建标签 @@ -2851,7 +2905,18 @@ teams.invite.title=您已被邀请加入组织 %s 中的团队 teams.invite.by=邀请人 %s teams.invite.description=请点击下面的按钮加入团队。 +view_as_role=以 %s 身份查看 +view_as_public_hint=您正在以公开用户的身份查看 README +view_as_member_hint=您正在以组织成员的身份查看 README +worktime=工作时间 +worktime.date_range_start=起始日期 +worktime.date_range_end=结束日期 +worktime.query=查询 +worktime.time=时间 +worktime.by_repositories=按仓库 +worktime.by_milestones=按里程碑 +worktime.by_members=按成员 [admin] maintenance=维护 @@ -3223,8 +3288,6 @@ config.ssh_domain=SSH 服务器域名 config.ssh_port=端口 config.ssh_listen_port=监听端口 config.ssh_root_path=根目录 -config.ssh_key_test_path=密钥测试路径 -config.ssh_keygen_path=密钥生成器('ssh-keygen')路径 config.ssh_minimum_key_size_check=密钥最小长度检查 config.ssh_minimum_key_sizes=密钥最小长度限制 @@ -3347,6 +3410,7 @@ monitor.previous=上次执行时间 monitor.execute_times=执行次数 monitor.process=运行中进程 monitor.stacktrace=调用栈踪迹 +monitor.trace=追踪 monitor.performance_logs=性能日志 monitor.processes_count=%d 个进程 monitor.download_diagnosis_report=下载诊断报告 @@ -3522,10 +3586,11 @@ versions=版本 versions.view_all=查看全部 dependency.id=ID dependency.version=版本 +search_in_external_registry=在 %s 中搜索 alpine.registry=通过在您的 /etc/apk/repositories 文件中添加 URL 来设置此注册中心: alpine.registry.key=下载注册中心公开的 RSA 密钥到 /etc/apk/keys/ 文件夹来验证索引签名: alpine.registry.info=从下面的列表中选择 $branch 和 $repository。 -alpine.install=要安装包,请运行以下命令: +alpine.install=要安装软件包,请运行以下命令: alpine.repository=仓库信息 alpine.repository.branches=分支 alpine.repository.repositories=仓库 @@ -3538,7 +3603,7 @@ arch.repository.architectures=架构 cargo.registry=在 Cargo 配置文件中设置此注册中心(例如:~/.cargo/config.toml): cargo.install=要使用 Cargo 安装软件包,请运行以下命令: chef.registry=在您的 ~/.chef/config.rb 文件中设置此注册中心: -chef.install=要安装包,请运行以下命令: +chef.install=要安装软件包,请运行以下命令: composer.registry=在您的 ~/.composer/config.json 文件中设置此注册中心: composer.install=要使用 Composer 安装软件包,请运行以下命令: composer.dependencies=依赖 @@ -3552,16 +3617,17 @@ container.details.type=镜像类型 container.details.platform=平台 container.pull=从命令行拉取镜像: container.images=镜像 +container.digest=摘要 container.multi_arch=OS / Arch container.layers=镜像层 container.labels=标签 container.labels.key=键 container.labels.value=值 cran.registry=在您的 Rprofile.site 文件中设置此注册中心: -cran.install=要安装包,请运行以下命令: +cran.install=要安装软件包,请运行以下命令: debian.registry=从命令行设置此注册中心: debian.registry.info=从下面的列表中选择 $distribution 和 $component。 -debian.install=要安装包,请运行以下命令: +debian.install=要安装软件包,请运行以下命令: debian.repository=仓库信息 debian.repository.distributions=发行版 debian.repository.components=组件 @@ -3592,7 +3658,7 @@ pypi.install=要使用 pip 安装软件包,请运行以下命令: rpm.registry=从命令行设置此注册中心: rpm.distros.redhat=在基于 RedHat 的发行版 rpm.distros.suse=在基于 SUSE 的发行版 -rpm.install=要安装包,请运行以下命令: +rpm.install=要安装软件包,请运行以下命令: rpm.repository=仓库信息 rpm.repository.architectures=架构 rpm.repository.multiple_groups=此软件包可在多个组中使用。 @@ -3658,6 +3724,7 @@ creation=添加密钥 creation.description=组织描述 creation.name_placeholder=不区分大小写,字母数字或下划线不能以GITEA_ 或 GITHUB_ 开头。 creation.value_placeholder=输入任何内容,开头和结尾的空白都会被省略 +creation.description_placeholder=输入简短描述(可选)。 creation.success=您的密钥 '%s' 添加成功。 creation.failed=添加密钥失败。 deletion=删除密钥 @@ -3688,7 +3755,7 @@ runners.status=状态 runners.id=ID runners.name=名称 runners.owner_type=类型 -runners.description=组织描述 +runners.description=描述 runners.labels=标签 runners.last_online=上次在线时间 runners.runner_title=Runner @@ -3711,10 +3778,11 @@ runners.delete_runner_notice=如果一个任务正在运行在此运行器上, runners.none=无可用的 Runner runners.status.unspecified=未知 runners.status.idle=空闲 -runners.status.active=激活 +runners.status.active=启用 runners.status.offline=离线 runners.version=版本 runners.reset_registration_token=重置注册令牌 +runners.reset_registration_token_confirm=是否吊销当前令牌并生成一个新令牌? runners.reset_registration_token_success=成功重置运行器注册令牌 runs.all_workflows=所有工作流 @@ -3747,6 +3815,7 @@ workflow.not_found=工作流 %s 未找到。 workflow.run_success=工作流 %s 已成功运行。 workflow.from_ref=使用工作流从 workflow.has_workflow_dispatch=此 Workflow 有一个 Workflow_dispatch 事件触发器。 +workflow.has_no_workflow_dispatch=工作流 %s 没有 workflow_dispatch 事件的触发器。 need_approval_desc=该工作流由派生仓库的合并请求所触发,需要批准方可运行。 @@ -3766,6 +3835,8 @@ variables.creation.success=变量 “%s” 添加成功。 variables.update.failed=编辑变量失败。 variables.update.success=该变量已被编辑。 +logs.always_auto_scroll=总是自动滚动日志 +logs.always_expand_running=总是展开运行日志 [projects] deleted.display_name=已删除项目 diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini index b924faba09..73602fe36a 100644 --- a/options/locale/locale_zh-HK.ini +++ b/options/locale/locale_zh-HK.ini @@ -416,7 +416,6 @@ issues.delete_branch_at=`刪除分支 %s %s` issues.filter_label=標籤篩選 issues.filter_milestone=里程碑篩選 issues.filter_assignee=指派人篩選 -issues.filter_assginee_no_assignee=無負責人 issues.filter_type=類型篩選 issues.filter_type.all_issues=所有問題 issues.filter_type.assigned_to_you=指派給您的 @@ -817,8 +816,6 @@ config.ssh_enabled=已啟用 config.ssh_port=埠 config.ssh_listen_port=監聽埠 config.ssh_root_path=根路徑 -config.ssh_key_test_path=金鑰測試路徑 -config.ssh_keygen_path=金鑰產生 (' ssh-keygen ') 路徑 config.ssh_minimum_key_size_check=金鑰最小大小檢查 config.ssh_minimum_key_sizes=金鑰最小大小 diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 10d6676ac5..343fb401c8 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -714,8 +714,6 @@ public_profile=公開的個人資料 biography_placeholder=告訴我們一些關於您的事情吧! (您可以使用 Markdown) location_placeholder=與其他人分享您的大概位置 profile_desc=控制您的個人檔案會如何呈現給其她使用者。您的主要電子郵件地址會被用於通知、密碼救援以及網頁上的 Git 操作。 -password_username_disabled=非本地使用者不允許更改他們的帳號。詳細資訊請聯絡您的系統管理員。 -password_full_name_disabled=您不被允許更改他們的全名。詳細資訊請聯絡您的系統管理員。 full_name=全名 website=個人網站 location=所在地區 @@ -3213,8 +3211,6 @@ config.ssh_domain=SSH 伺服器域名 config.ssh_port=連接埠 config.ssh_listen_port=監聽埠 config.ssh_root_path=根路徑 -config.ssh_key_test_path=金鑰測試路徑 -config.ssh_keygen_path=金鑰產生 (' ssh-keygen ') 路徑 config.ssh_minimum_key_size_check=金鑰最小大小檢查 config.ssh_minimum_key_sizes=金鑰最小大小 diff --git a/package-lock.json b/package-lock.json index c347cf5fd8..e516dcd91a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,12 +16,12 @@ "@primer/octicons": "19.15.1", "@silverwind/vue3-calendar-heatmap": "2.0.6", "add-asset-webpack-plugin": "3.0.0", - "ansi_up": "6.0.2", + "ansi_up": "6.0.5", "asciinema-player": "3.9.0", "chart.js": "4.4.8", "chartjs-adapter-dayjs-4": "1.0.4", "chartjs-plugin-zoom": "2.2.0", - "clippie": "4.1.5", + "clippie": "4.1.6", "cropperjs": "1.6.2", "css-loader": "7.1.2", "dayjs": "1.11.13", @@ -35,7 +35,7 @@ "jquery": "3.7.1", "katex": "0.16.21", "license-checker-webpack-plugin": "0.2.1", - "mermaid": "11.5.0", + "mermaid": "11.6.0", "mini-css-extract-plugin": "2.9.2", "minimatch": "10.0.1", "monaco-editor": "0.52.2", @@ -46,14 +46,14 @@ "postcss-loader": "8.1.1", "postcss-nesting": "13.0.1", "sortablejs": "1.15.6", - "swagger-ui-dist": "5.20.1", + "swagger-ui-dist": "5.20.7", "tailwindcss": "3.4.17", "throttle-debounce": "5.0.2", "tinycolor2": "1.6.0", "tippy.js": "6.3.7", "toastify-js": "1.12.0", "tributejs": "5.1.3", - "typescript": "5.8.2", + "typescript": "5.8.3", "uint8-to-base64": "0.2.0", "vanilla-colorful": "0.7.2", "vue": "3.5.13", @@ -80,15 +80,15 @@ "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/toastify-js": "1.12.3", - "@typescript-eslint/eslint-plugin": "8.26.1", - "@typescript-eslint/parser": "8.26.1", - "@vitejs/plugin-vue": "5.2.1", - "@vitest/eslint-plugin": "1.1.37", + "@typescript-eslint/eslint-plugin": "8.29.1", + "@typescript-eslint/parser": "8.29.1", + "@vitejs/plugin-vue": "5.2.3", + "@vitest/eslint-plugin": "1.1.39", "eslint": "8.57.0", - "eslint-import-resolver-typescript": "3.9.0", + "eslint-import-resolver-typescript": "4.3.2", "eslint-plugin-array-func": "4.0.0", "eslint-plugin-github": "5.0.2", - "eslint-plugin-import-x": "4.7.2", + "eslint-plugin-import-x": "4.10.2", "eslint-plugin-no-jquery": "3.1.1", "eslint-plugin-no-use-extend-native": "0.5.0", "eslint-plugin-playwright": "2.2.0", @@ -97,23 +97,23 @@ "eslint-plugin-unicorn": "56.0.1", "eslint-plugin-vue": "10.0.0", "eslint-plugin-vue-scoped-css": "2.9.0", - "eslint-plugin-wc": "2.2.1", + "eslint-plugin-wc": "3.0.0", "happy-dom": "17.4.4", "markdownlint-cli": "0.44.0", - "material-icon-theme": "5.20.0", + "material-icon-theme": "5.21.1", "nolyfill": "1.0.44", "postcss-html": "1.8.0", - "stylelint": "16.16.0", - "stylelint-config-recommended": "15.0.0", + "stylelint": "16.18.0", + "stylelint-config-recommended": "16.0.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.11", - "stylelint-define-config": "16.15.0", + "stylelint-define-config": "16.17.0", "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.3.2", - "type-fest": "4.37.0", + "type-fest": "4.39.1", "updates": "16.4.2", "vite-string-plugin": "1.4.4", - "vitest": "3.0.8", + "vitest": "3.1.1", "vue-tsc": "2.2.8" }, "engines": { @@ -521,9 +521,9 @@ } }, "node_modules/@emnapi/core": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.3.1.tgz", - "integrity": "sha512-pVGjBIt1Y6gg3EJN8jTcfpP/+uuRksIo055oE/OBkDNcjZqVbfkWCksG1Jp4yZnj3iKWyWX8fdG/j6UDYPbFog==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.0.tgz", + "integrity": "sha512-H+N/FqT07NmLmt6OFFtDfwe8PNygprzBikrEMyQfgqSmT0vzE515Pz7R8izwB9q/zsH/MA64AKoul3sA6/CzVg==", "dev": true, "license": "MIT", "optional": true, @@ -533,9 +533,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", - "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.0.tgz", + "integrity": "sha512-64WYIf4UYcdLnbKn/umDlNjQDSS8AgZrI/R9+x5ilkUVFxXcA1Ebl+gQLc/6mERA4407Xof0R7wEyEuj091CVw==", "dev": true, "license": "MIT", "optional": true, @@ -1540,24 +1540,24 @@ } }, "node_modules/@mermaid-js/parser": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.3.0.tgz", - "integrity": "sha512-HsvL6zgE5sUPGgkIDlmAWR1HTNHz2Iy11BAWPTa4Jjabkpguy4Ze2gzfLrg6pdRuBvFwgUYyxiaNqZwrEEXepA==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.4.0.tgz", + "integrity": "sha512-wla8XOWvQAwuqy+gxiZqY+c7FokraOTHRWMsbB4AgRx9Sy7zKslNyejy7E+a77qHfey5GXw/ik3IXv/NHMJgaA==", "license": "MIT", "dependencies": { - "langium": "3.0.0" + "langium": "3.3.1" } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.7.tgz", - "integrity": "sha512-5yximcFK5FNompXfJFoWanu5l8v1hNGqNHh9du1xETp9HWk/B/PzvchX55WYOPaIeNglG8++68AAiauBAtbnzw==", + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.8.tgz", + "integrity": "sha512-OBlgKdX7gin7OIq4fadsjpg+cp2ZphvAIKucHsNfTdJiqdOmOEwQd/bHi0VwNrcw5xpBJyUw6cK/QilCqy1BSg==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.3.1", - "@emnapi/runtime": "^1.3.1", + "@emnapi/core": "^1.4.0", + "@emnapi/runtime": "^1.4.0", "@tybys/wasm-util": "^0.9.0" } }, @@ -1596,16 +1596,6 @@ "node": ">= 8" } }, - "node_modules/@nolyfill/is-core-module": { - "version": "1.0.39", - "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", - "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.4.0" - } - }, "node_modules/@nolyfill/shared": { "version": "1.0.44", "resolved": "https://registry.npmjs.org/@nolyfill/shared/-/shared-1.0.44.tgz", @@ -1613,163 +1603,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@oxc-resolver/binding-darwin-arm64": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-5.0.0.tgz", - "integrity": "sha512-zwHAf+owoxSWTDD4dFuwW+FkpaDzbaL30H5Ltocb+RmLyg4WKuteusRLKh5Y8b/cyu7UzhxM0haIqQjyqA1iuA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@oxc-resolver/binding-darwin-x64": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-5.0.0.tgz", - "integrity": "sha512-1lS3aBNVjVQKBvZdHm13+8tSjvu2Tl1Cv4FnUyMYxqx6+rsom2YaOylS5LhDUwfZu0zAgpLMwK6kGpF/UPncNg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@oxc-resolver/binding-freebsd-x64": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-5.0.0.tgz", - "integrity": "sha512-q9sRd68wC1/AJ0eu6ClhxlklVfe8gH4wrUkSyEbIYTZ8zY5yjsLY3fpqqsaCvWJUx65nW+XtnAxCGCi5AXr1Mw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-5.0.0.tgz", - "integrity": "sha512-catYavWsvqViYnCveQjhrK6yVYDEPFvIOgGLxnz5r2dcgrjpmquzREoyss0L2QG/J5HTTbwqwZ1kk+g56hE/1A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-5.0.0.tgz", - "integrity": "sha512-l/0pWoQM5kVmJLg4frQ1mKZOXgi0ex/hzvFt8E4WK2ifXr5JgKFUokxsb/oat7f5YzdJJh5r9p+qS/t3dA26Aw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-arm64-musl": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-5.0.0.tgz", - "integrity": "sha512-bx0oz/oaAW4FGYqpIIxJCnmgb906YfMhTEWCJvYkxjpEI8VKLJEL3PQevYiqDq36SA0yRLJ/sQK2fqry8AFBfA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-x64-gnu": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-5.0.0.tgz", - "integrity": "sha512-4PH++qbSIhlRsFYdN1P9neDov4OGhTGo5nbQ1D7AL6gWFLo3gdZTc00FM2y8JjeTcPWEXkViZuwpuc0w5i6qHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-x64-musl": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-5.0.0.tgz", - "integrity": "sha512-mLfQFpX3/5y9oWi0b+9FbWDkL2hM0Y29653beCHiHxAdGyVgb2DsJbK74WkMTwtSz9by8vyBh8jGPZcg1yLZbQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-wasm32-wasi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-5.0.0.tgz", - "integrity": "sha512-uEhsAZSo65qsRi6+IfBTEUUFbjg7T2yruJeLYpFfEATpm3ory5Mgo5vx3L0c2/Cz1OUZXBgp3A8x6VMUB2jT2A==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.7" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-5.0.0.tgz", - "integrity": "sha512-8DbSso9Jp1ns8AYuZFXdRfAcdJrzZwkFm/RjPuvAPTENsm685dosBF8G6gTHQlHvULnk6o3sa9ygZaTGC/UoEw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@oxc-resolver/binding-win32-x64-msvc": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-5.0.0.tgz", - "integrity": "sha512-ylppfPEg63NuRXOPNsXFlgyl37JrtRn0QMO26X3K3Ytp5HtLrMreQMGVtgr30e1l2YmAWqhvmKlCryOqzGPD/g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -3314,17 +3147,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.1.tgz", - "integrity": "sha512-2X3mwqsj9Bd3Ciz508ZUtoQQYpOhU/kWoUqIf49H8Z0+Vbh6UF/y0OEYp0Q0axOGzaBGs7QxRwq0knSQ8khQNA==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.1.tgz", + "integrity": "sha512-ba0rr4Wfvg23vERs3eB+P3lfj2E+2g3lhWcCVukUuhtcdUx5lSIFZlGFEBHKr+3zizDa/TvZTptdNHVZWAkSBg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.26.1", - "@typescript-eslint/type-utils": "8.26.1", - "@typescript-eslint/utils": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1", + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/type-utils": "8.29.1", + "@typescript-eslint/utils": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -3344,16 +3177,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.1.tgz", - "integrity": "sha512-w6HZUV4NWxqd8BdeFf81t07d7/YV9s7TCWrQQbG5uhuvGUAW+fq1usZ1Hmz9UPNLniFnD8GLSsDpjP0hm1S4lQ==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.1.tgz", + "integrity": "sha512-zczrHVEqEaTwh12gWBIJWj8nx+ayDcCJs06yoNMY0kwjMWDM6+kppljY+BxWI06d2Ja+h4+WdufDcwMnnMEWmg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.26.1", - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/typescript-estree": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1", + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/typescript-estree": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4" }, "engines": { @@ -3369,14 +3202,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.1.tgz", - "integrity": "sha512-6EIvbE5cNER8sqBu6V7+KeMZIC1664d2Yjt+B9EWUXrsyWpxx4lEZrmvxgSKRC6gX+efDL/UY9OpPZ267io3mg==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.1.tgz", + "integrity": "sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1" + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3387,14 +3220,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.1.tgz", - "integrity": "sha512-Kcj/TagJLwoY/5w9JGEFV0dclQdyqw9+VMndxOJKtoFSjfZhLXhYjzsQEeyza03rwHx2vFEGvrJWJBXKleRvZg==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.1.tgz", + "integrity": "sha512-DkDUSDwZVCYN71xA4wzySqqcZsHKic53A4BLqmrWFFpOpNSoxX233lwGu/2135ymTCR04PoKiEEEvN1gFYg4Tw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.26.1", - "@typescript-eslint/utils": "8.26.1", + "@typescript-eslint/typescript-estree": "8.29.1", + "@typescript-eslint/utils": "8.29.1", "debug": "^4.3.4", "ts-api-utils": "^2.0.1" }, @@ -3411,9 +3244,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.1.tgz", - "integrity": "sha512-n4THUQW27VmQMx+3P+B0Yptl7ydfceUj4ON/AQILAASwgYdZ/2dhfymRMh5egRUrvK5lSmaOm77Ry+lmXPOgBQ==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.1.tgz", + "integrity": "sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==", "dev": true, "license": "MIT", "engines": { @@ -3425,14 +3258,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.1.tgz", - "integrity": "sha512-yUwPpUHDgdrv1QJ7YQal3cMVBGWfnuCdKbXw1yyjArax3353rEJP1ZA+4F8nOlQ3RfS2hUN/wze3nlY+ZOhvoA==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.1.tgz", + "integrity": "sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/visitor-keys": "8.26.1", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/visitor-keys": "8.29.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3468,16 +3301,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.1.tgz", - "integrity": "sha512-V4Urxa/XtSUroUrnI7q6yUTD3hDtfJ2jzVfeT3VK0ciizfK2q/zGC0iDh1lFMUZR8cImRrep6/q0xd/1ZGPQpg==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.1.tgz", + "integrity": "sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.26.1", - "@typescript-eslint/types": "8.26.1", - "@typescript-eslint/typescript-estree": "8.26.1" + "@typescript-eslint/scope-manager": "8.29.1", + "@typescript-eslint/types": "8.29.1", + "@typescript-eslint/typescript-estree": "8.29.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3492,13 +3325,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.26.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.1.tgz", - "integrity": "sha512-AjOC3zfnxd6S4Eiy3jwktJPclqhFHNyd8L6Gycf9WUPoKZpgM5PjkxY1X7uSy61xVpiJDhhk7XT2NVsN3ALTWg==", + "version": "8.29.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.1.tgz", + "integrity": "sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.26.1", + "@typescript-eslint/types": "8.29.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -3516,10 +3349,10 @@ "dev": true, "license": "ISC" }, - "node_modules/@unrs/rspack-resolver-binding-darwin-arm64": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-darwin-arm64/-/rspack-resolver-binding-darwin-arm64-1.1.0.tgz", - "integrity": "sha512-otdWnJrycP8Ow0rbiKKjhrW7PPeHPoIglYjBruqh75fEwQGV2EmA9oZMgD4YA6g+/hGzD0mXI26YnWL0G0SkTA==", + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.4.1.tgz", + "integrity": "sha512-8Tv+Bsd0BjGwfEedIyor4inw8atppRxM5BdUnIt+3mAm/QXUm7Dw74CHnXpfZKXkp07EXJGiA8hStqCINAWhdw==", "cpu": [ "arm64" ], @@ -3530,10 +3363,10 @@ "darwin" ] }, - "node_modules/@unrs/rspack-resolver-binding-darwin-x64": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-darwin-x64/-/rspack-resolver-binding-darwin-x64-1.1.0.tgz", - "integrity": "sha512-MqHyrtIw2ra0KZlniDITROq6rEiMsBnaJtQDYLNDv/y+pvPSXdB3VveZCwSldXJ9TrST2b3NnIbmehljjsMVhg==", + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.4.1.tgz", + "integrity": "sha512-X8c3PhWziEMKAzZz+YAYWfwawi5AEgzy/hmfizAB4C70gMHLKmInJcp1270yYAOs7z07YVFI220pp50z24Jk3A==", "cpu": [ "x64" ], @@ -3544,10 +3377,10 @@ "darwin" ] }, - "node_modules/@unrs/rspack-resolver-binding-freebsd-x64": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-freebsd-x64/-/rspack-resolver-binding-freebsd-x64-1.1.0.tgz", - "integrity": "sha512-bopyOqmtWn8np1d4iN90PE1tYHopdWwei7mK8/8mf4qhc99f7WRNXtWa1MoL5sjN3DWef3jvr0+MGnMS9MTfJA==", + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.4.1.tgz", + "integrity": "sha512-UUr/nREy1UdtxXQnmLaaTXFGOcGxPwNIzeJdb3KXai3TKtC1UgNOB9s8KOA4TaxOUBR/qVgL5BvBwmUjD5yuVA==", "cpu": [ "x64" ], @@ -3558,10 +3391,10 @@ "freebsd" ] }, - "node_modules/@unrs/rspack-resolver-binding-linux-arm-gnueabihf": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-arm-gnueabihf/-/rspack-resolver-binding-linux-arm-gnueabihf-1.1.0.tgz", - "integrity": "sha512-XldXRkQurDBXCiCuIaWcqOX6UtvjFW8O3CH/kFEZxNJISOAt+ztgyRQRxYhf+T1p18R4boripKmWKEE0uBCiYw==", + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.4.1.tgz", + "integrity": "sha512-e3pII53dEeS8inkX6A1ad2UXE0nuoWCqik4kOxaDnls0uJUq0ntdj5d9IYd+bv5TDwf9DSge/xPOvCmRYH+Tsw==", "cpu": [ "arm" ], @@ -3572,10 +3405,24 @@ "linux" ] }, - "node_modules/@unrs/rspack-resolver-binding-linux-arm64-gnu": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-arm64-gnu/-/rspack-resolver-binding-linux-arm64-gnu-1.1.0.tgz", - "integrity": "sha512-8zubI4MY3whPfLNHEiJ0TeSC5eSmNVWTEGAeMGALCUQtVD9TyZTd6wGwWrQVRN7ESIapWUSChkPLr+Bi13d9sQ==", + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.4.1.tgz", + "integrity": "sha512-e/AKKd9gR+HNmVyDEPI/PIz2t0DrA3cyonHNhHVjrkxe8pMCiYiqhtn1+h+yIpHUtUlM6Y1FNIdivFa+r7wrEQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.4.1.tgz", + "integrity": "sha512-vtIu34luF1jRktlHtiwm2mjuE8oJCsFiFr8hT5+tFQdqFKjPhbJXn83LswKsOhy0GxAEevpXDI4xxEwkjuXIPA==", "cpu": [ "arm64" ], @@ -3586,10 +3433,10 @@ "linux" ] }, - "node_modules/@unrs/rspack-resolver-binding-linux-arm64-musl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-arm64-musl/-/rspack-resolver-binding-linux-arm64-musl-1.1.0.tgz", - "integrity": "sha512-+GAyOhl8KPqJsILpHTB/mMc4hfOwI4INed8VAZnSvdaL0ec3Sz/6UXEeTtucW1fWhwaP3lVlpjv2xuRhOCjehA==", + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.4.1.tgz", + "integrity": "sha512-H3PaOuGyhFXiyJd+09uPhGl4gocmhyi1BRzvsP8Lv5AQO3p3/ZY7WjV4t2NkBksm9tMjf3YbOVHyPWi2eWsNYw==", "cpu": [ "arm64" ], @@ -3600,10 +3447,38 @@ "linux" ] }, - "node_modules/@unrs/rspack-resolver-binding-linux-x64-gnu": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-x64-gnu/-/rspack-resolver-binding-linux-x64-gnu-1.1.0.tgz", - "integrity": "sha512-0zoy6UwRFoto5boJKGjgDpA+4kv+G1kysgrAe0KVefJXOnDNJlfgcV7mOV2O9J+FqtIQsXvzmOJxDB9e1Hhbzw==", + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.4.1.tgz", + "integrity": "sha512-4+GmJcaaFntCi1S01YByqp8wLMjV/FyQyHVGm0vedIhL1Vfx7uHkz/sZmKsidRwokBGuxi92GFmSzqT2O8KcNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.4.1.tgz", + "integrity": "sha512-6RDQVCmtFYTlhy89D5ixTqo9bTQqFhvNN0Ey1wJs5r+01Dq15gPHRXv2jF2bQATtMrOfYwv+R2ZR9ew1N1N3YQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.4.1.tgz", + "integrity": "sha512-XpU9uzIkD86+19NjCXxlVPISMUrVXsXo5htxtuG+uJ59p5JauSRZsIxQxzzfKzkxEjdvANPM/lS1HFoX6A6QeA==", "cpu": [ "x64" ], @@ -3614,10 +3489,10 @@ "linux" ] }, - "node_modules/@unrs/rspack-resolver-binding-linux-x64-musl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-x64-musl/-/rspack-resolver-binding-linux-x64-musl-1.1.0.tgz", - "integrity": "sha512-XjC+aZKi+X+ma5e6yaVTvojK0v0kxfikbP1dB0klx80NjCW9KRrldiZxAo7ME8Tb4a7Fz0J6PDOVzd9tFYwkQQ==", + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.4.1.tgz", + "integrity": "sha512-3CDjG/spbTKCSHl66QP2ekHSD+H34i7utuDIM5gzoNBcZ1gTO0Op09Wx5cikXnhORRf9+HyDWzm37vU1PLSM1A==", "cpu": [ "x64" ], @@ -3628,10 +3503,10 @@ "linux" ] }, - "node_modules/@unrs/rspack-resolver-binding-wasm32-wasi": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-wasm32-wasi/-/rspack-resolver-binding-wasm32-wasi-1.1.0.tgz", - "integrity": "sha512-eVBK4z9VN3vahAh2+bQBl+vs9JgWEF1xeccilDcerGNkmlFHB1My5++sbeZzou+DExkioVAdfK+uVyVnHS4k7Q==", + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.4.1.tgz", + "integrity": "sha512-50tYhvbCTnuzMn7vmP8IV2UKF7ITo1oihygEYq9wW2DUb/Y+QMqBHJUSCABRngATjZ4shOK6f2+s0gQX6ElENQ==", "cpu": [ "wasm32" ], @@ -3639,16 +3514,16 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.7" + "@napi-rs/wasm-runtime": "^0.2.8" }, "engines": { "node": ">=14.0.0" } }, - "node_modules/@unrs/rspack-resolver-binding-win32-arm64-msvc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-win32-arm64-msvc/-/rspack-resolver-binding-win32-arm64-msvc-1.1.0.tgz", - "integrity": "sha512-ktm/CnSKOt/Wwdi2SBECLPJ+gL5oaw8LDHG4IfOQO5oT6qlIP0DaOUPrTf8g/WTlLnSp4TryDBM0B/gGla3LEw==", + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.4.1.tgz", + "integrity": "sha512-KyJiIne/AqV4IW0wyQO34wSMuJwy3VxVQOfIXIPyQ/Up6y/zi2P/WwXb78gHsLiGRUqCA9LOoCX+6dQZde0g1g==", "cpu": [ "arm64" ], @@ -3659,10 +3534,24 @@ "win32" ] }, - "node_modules/@unrs/rspack-resolver-binding-win32-x64-msvc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-win32-x64-msvc/-/rspack-resolver-binding-win32-x64-msvc-1.1.0.tgz", - "integrity": "sha512-cdMid8RdR6XaQ5uAudzdu9Ydl3HbYjiwpsh+X01APmTZG2ph7OeaRTozW4F8ScUHkPHfrYedv9McICbHxgBvXQ==", + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.4.1.tgz", + "integrity": "sha512-y2NUD7pygrBolN2NoXUrwVqBpKPhF8DiSNE5oB5/iFO49r2DpoYqdj5HPb3F42fPBH5qNqj6Zg63+xCEzAD2hw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.4.1.tgz", + "integrity": "sha512-hVXaObGI2lGFmrtT77KSbPQ3I+zk9IU500wobjk0+oX59vg/0VqAzABNtt3YSQYgXTC2a/LYxekLfND/wlt0yQ==", "cpu": [ "x64" ], @@ -3674,9 +3563,9 @@ ] }, "node_modules/@vitejs/plugin-vue": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz", - "integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz", + "integrity": "sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==", "dev": true, "license": "MIT", "engines": { @@ -3688,9 +3577,9 @@ } }, "node_modules/@vitest/eslint-plugin": { - "version": "1.1.37", - "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.37.tgz", - "integrity": "sha512-cnlBV8zr0oaBu1Vk6ruqWzpPzFCtwY0yuwUQpNIeFOUl3UhXVwNUoOYfWkZzeToGuNBaXvIsr6/RgHrXiHXqXA==", + "version": "1.1.39", + "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.39.tgz", + "integrity": "sha512-l5/MUFCYI8nxwr62JHlWwXfeQNS8E7xy71lSLGQ3CrjGjBdWLs1Rtee+BvYwy2m4YVPwYqUwdcAIOaZOwPUpfg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -3709,14 +3598,14 @@ } }, "node_modules/@vitest/expect": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.8.tgz", - "integrity": "sha512-Xu6TTIavTvSSS6LZaA3EebWFr6tsoXPetOWNMOlc7LO88QVVBwq2oQWBoDiLCN6YTvNYsGSjqOO8CAdjom5DCQ==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.1.tgz", + "integrity": "sha512-q/zjrW9lgynctNbwvFtQkGK9+vvHA5UzVi2V8APrp1C6fG6/MuYYkmlx4FubuqLycCeSdHD5aadWfua/Vr0EUA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.8", - "@vitest/utils": "3.0.8", + "@vitest/spy": "3.1.1", + "@vitest/utils": "3.1.1", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -3725,13 +3614,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.8.tgz", - "integrity": "sha512-n3LjS7fcW1BCoF+zWZxG7/5XvuYH+lsFg+BDwwAz0arIwHQJFUEsKBQ0BLU49fCxuM/2HSeBPHQD8WjgrxMfow==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.1.tgz", + "integrity": "sha512-bmpJJm7Y7i9BBELlLuuM1J1Q6EQ6K5Ye4wcyOpOMXMcePYKSIYlpcrCm4l/O6ja4VJA5G2aMJiuZkZdnxlC3SA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.0.8", + "@vitest/spy": "3.1.1", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -3752,9 +3641,9 @@ } }, "node_modules/@vitest/mocker/node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "dev": true, "license": "MIT" }, @@ -3779,9 +3668,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.8.tgz", - "integrity": "sha512-BNqwbEyitFhzYMYHUVbIvepOyeQOSFA/NeJMIP9enMntkkxLgOcgABH6fjyXG85ipTgvero6noreavGIqfJcIg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.1.tgz", + "integrity": "sha512-dg0CIzNx+hMMYfNmSqJlLSXEmnNhMswcn3sXO7Tpldr0LiGmg3eXdLLhwkv2ZqgHb/d5xg5F7ezNFRA1fA13yA==", "dev": true, "license": "MIT", "dependencies": { @@ -3792,13 +3681,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.8.tgz", - "integrity": "sha512-c7UUw6gEcOzI8fih+uaAXS5DwjlBaCJUo7KJ4VvJcjL95+DSR1kova2hFuRt3w41KZEFcOEiq098KkyrjXeM5w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.1.tgz", + "integrity": "sha512-X/d46qzJuEDO8ueyjtKfxffiXraPRfmYasoC4i5+mlLEJ10UvPb0XH5M9C3gWuxd7BAQhpK42cJgJtq53YnWVA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.0.8", + "@vitest/utils": "3.1.1", "pathe": "^2.0.3" }, "funding": { @@ -3806,13 +3695,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.8.tgz", - "integrity": "sha512-x8IlMGSEMugakInj44nUrLSILh/zy1f2/BgH0UeHpNyOocG18M9CWVIFBaXPt8TrqVZWmcPjwfG/ht5tnpba8A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.1.tgz", + "integrity": "sha512-bByMwaVWe/+1WDf9exFxWWgAixelSdiwo2p33tpqIlM14vW7PRV5ppayVXtfycqze4Qhtwag5sVhX400MLBOOw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.8", + "@vitest/pretty-format": "3.1.1", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -3831,9 +3720,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.8.tgz", - "integrity": "sha512-MR+PzJa+22vFKYb934CejhR4BeRpMSoxkvNoDit68GQxRLSf11aT6CTj3XaqUU9rxgWJFnqicN/wxw6yBRkI1Q==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.1.tgz", + "integrity": "sha512-+EmrUOOXbKzLkTDwlsc/xrwOlPDXyVk3Z6P6K4oiCndxz7YLpp/0R0UsWVOKT0IXWjjBJuSMk6D27qipaupcvQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3844,13 +3733,13 @@ } }, "node_modules/@vitest/utils": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.8.tgz", - "integrity": "sha512-nkBC3aEhfX2PdtQI/QwAWp8qZWwzASsU4Npbcd5RdMPBSSLCpkZp52P3xku3s3uA0HIEhGvEcF8rNkBsz9dQ4Q==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.1.tgz", + "integrity": "sha512-1XIjflyaU2k3HMArJ50bwSh3wKWPD6Q47wz/NUSmRV0zNywPc4w79ARjg/i/aNINHwA+mIALhUVqD9/aUvZNgg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.0.8", + "@vitest/pretty-format": "3.1.1", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" }, @@ -4378,9 +4267,9 @@ "license": "MIT" }, "node_modules/ansi_up": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ansi_up/-/ansi_up-6.0.2.tgz", - "integrity": "sha512-3G3vKvl1ilEp7J1u6BmULpMA0xVoW/f4Ekqhl8RTrJrhEBkonKn5k3bUc5Xt+qDayA6iDX0jyUh3AbZjB/l0tw==", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/ansi_up/-/ansi_up-6.0.5.tgz", + "integrity": "sha512-bo4K8S5usgFivfgvgQozTC2EfusPf76o7w0LUVdAOkpISvVmQqtwCdF5c6okokrgIN13KhFIVB/0BhnNXueQeA==", "license": "MIT", "engines": { "node": "*" @@ -5103,9 +4992,9 @@ } }, "node_modules/clippie": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/clippie/-/clippie-4.1.5.tgz", - "integrity": "sha512-ZNGL7+p8HZWM2G8fecb3N7izoagXGktTg7nSYxzBID4OAtOBU7SUFvI9EL/0IZpy9VkU8AY6Zp8AvaH4/Xj7pA==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/clippie/-/clippie-4.1.6.tgz", + "integrity": "sha512-1M3xZRNWcVwRkh3i2XcVYFjVtfC6FCLmIpk5s54uT3+jkBa25KRE3dB0Fgkt/YLoeR7AABWkVTlb0WziQYGgaw==", "license": "BSD-2-Clause" }, "node_modules/cliui": { @@ -6586,25 +6475,24 @@ } }, "node_modules/eslint-import-resolver-typescript": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.9.0.tgz", - "integrity": "sha512-EUcFmaz0zAa6P2C9jAb5XDymRld8S6TURvWcIW7y+czOW+K8hrjgQgbhBsNE0J/dDZ6HLfcn70LqnIil9W+ICw==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.3.2.tgz", + "integrity": "sha512-T2LqBXj87ndEC9t1LrDiPkzalSFzD4rrXr6BTzGdgMx1jdQM4T972guQvg7Ih+LNO51GURXI/qMHS5GF3h1ilw==", "dev": true, "license": "ISC", "dependencies": { - "@nolyfill/is-core-module": "1.0.39", - "debug": "^4.3.7", + "debug": "^4.4.0", "get-tsconfig": "^4.10.0", - "is-bun-module": "^1.0.2", - "oxc-resolver": "^5.0.0", + "is-bun-module": "^2.0.0", "stable-hash": "^0.0.5", - "tinyglobby": "^0.2.12" + "tinyglobby": "^0.2.12", + "unrs-resolver": "^1.4.1" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^16.17.0 || >=18.6.0" }, "funding": { - "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + "url": "https://opencollective.com/eslint-import-resolver-typescript" }, "peerDependencies": { "eslint": "*", @@ -6797,24 +6685,25 @@ } }, "node_modules/eslint-plugin-import-x": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.7.2.tgz", - "integrity": "sha512-+GpGWKbQMK3izrwU4XoRGdAJHwvxuboiNusqU25nNXlRsmnxj8B5niQRuFK1aHEvcbIKE6D9ZfwjsLmBQbnJmw==", + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.10.2.tgz", + "integrity": "sha512-jO3Y6+zBUyTX5MVbbLSzoz6fe65t+WEBaXStRLM4EBhZWbuSwAH3cLwARtM0Yp4zRtZGp9sL2zzK7G9JkHR8LA==", "dev": true, "license": "MIT", "dependencies": { + "@pkgr/core": "^0.2.1", "@types/doctrine": "^0.0.9", - "@typescript-eslint/utils": "^8.26.1", + "@typescript-eslint/utils": "^8.29.0", "debug": "^4.4.0", "doctrine": "^3.0.0", "eslint-import-resolver-node": "^0.3.9", "get-tsconfig": "^4.10.0", "is-glob": "^4.0.3", - "minimatch": "^10.0.1", - "rspack-resolver": "^1.1.0", + "minimatch": "^9.0.3 || ^10.0.1", "semver": "^7.7.1", "stable-hash": "^0.0.5", - "tslib": "^2.8.1" + "tslib": "^2.8.1", + "unrs-resolver": "^1.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6823,6 +6712,19 @@ "eslint": "^8.57.0 || ^9.0.0" } }, + "node_modules/eslint-plugin-import-x/node_modules/@pkgr/core": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.1.tgz", + "integrity": "sha512-VzgHzGblFmUeBmmrk55zPyrQIArQN4vujc9shWytaPdB3P7qhi0cpaiKIr7tlCmFv2lYUwnLospIqjL9ZSAhhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -7176,14 +7078,14 @@ } }, "node_modules/eslint-plugin-wc": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-2.2.1.tgz", - "integrity": "sha512-KstLqGmyQz088DvFlDYHg0sHih+w2QeulreCi1D1ftr357klO2zqHdG/bbnNMmuQdVFDuNkopNIyNhmG0XCT/g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-3.0.0.tgz", + "integrity": "sha512-TVbwa4ytaKoUGCCAG14+FPDF3v4nGoPcEOd4kjN2MLZ2NTo1lHUs7HsVaiwC0ReKQVivJ7+v6d7u0GYu1c6GJQ==", "dev": true, "license": "MIT", "dependencies": { "is-valid-element-name": "^1.0.0", - "js-levenshtein-esm": "^1.2.0" + "js-levenshtein-esm": "^2.0.0" }, "peerDependencies": { "eslint": ">=8.40.0" @@ -8275,13 +8177,13 @@ } }, "node_modules/is-bun-module": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.3.0.tgz", - "integrity": "sha512-DgXeu5UWI0IsMQundYb5UAOzm6G2eVnarJ0byP6Tm55iZNKceD59LNPA2L4VvsScTtHcw0yEkVwSf7PC+QoLSA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", "dev": true, "license": "MIT", "dependencies": { - "semver": "^7.6.3" + "semver": "^7.7.1" } }, "node_modules/is-core-module": { @@ -8520,9 +8422,9 @@ "license": "MIT" }, "node_modules/js-levenshtein-esm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-1.2.0.tgz", - "integrity": "sha512-fzreKVq1eD7eGcQr7MtRpQH94f8gIfhdrc7yeih38xh684TNMK9v5aAu2wxfIRMk/GpAJRrzcirMAPIaSDaByQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-2.0.0.tgz", + "integrity": "sha512-1n4LEPOL4wRXY8rOQcuA7Iuaphe5xCMayvufCzlLAi+hRsnBRDbSS6XPuV58CBVJxj5D9ApFLyjQ7KzFToyHBw==", "dev": true, "license": "MIT" }, @@ -8751,9 +8653,9 @@ "license": "MIT" }, "node_modules/langium": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/langium/-/langium-3.0.0.tgz", - "integrity": "sha512-+Ez9EoiByeoTu/2BXmEaZ06iPNXM6thWJp02KfBO/raSMyCJ4jw7AkWWa+zBCTm0+Tw1Fj9FOxdqSskyN5nAwg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", + "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", "license": "MIT", "dependencies": { "chevrotain": "~11.0.3", @@ -9244,9 +9146,9 @@ } }, "node_modules/material-icon-theme": { - "version": "5.20.0", - "resolved": "https://registry.npmjs.org/material-icon-theme/-/material-icon-theme-5.20.0.tgz", - "integrity": "sha512-EAz5I2O7Hq6G8Rv0JdO6NXL+jK/mvDppcVUVbsUMpSqSmFczNdaR5WJ3lOiRz4HNBlEN2i2sVSfuqI5iNQfGLg==", + "version": "5.21.1", + "resolved": "https://registry.npmjs.org/material-icon-theme/-/material-icon-theme-5.21.1.tgz", + "integrity": "sha512-7gWH20MC3rvf1fBXwTpeMEAJqfuhXaciY2IN/hb74hR0XU/TnicoggblFoWtZvt0HrCzxyqX5P+u60BHWXEEHw==", "dev": true, "license": "MIT", "dependencies": { @@ -9316,14 +9218,14 @@ } }, "node_modules/mermaid": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.5.0.tgz", - "integrity": "sha512-IYhyukID3zzDj1EihKiN1lp+PXNImoJ3Iyz73qeDAgnus4BNGsJV1n471P4PyeGxPVONerZxignwGxGTSwZnlg==", + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.6.0.tgz", + "integrity": "sha512-PE8hGUy1LDlWIHWBP05SFdqUHGmRcCcK4IzpOKPE35eOw+G9zZgcnMpyunJVUEOgb//KBORPjysKndw8bFLuRg==", "license": "MIT", "dependencies": { "@braintree/sanitize-url": "^7.0.4", "@iconify/utils": "^2.1.33", - "@mermaid-js/parser": "^0.3.0", + "@mermaid-js/parser": "^0.4.0", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", "cytoscape-cose-bilkent": "^4.1.0", @@ -10327,29 +10229,6 @@ "node": ">= 0.8.0" } }, - "node_modules/oxc-resolver": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-5.0.0.tgz", - "integrity": "sha512-66fopyAqCN8Mx4tzNiBXWbk8asCSuxUWN62gwTc3yfRs7JfWhX/eVJCf+fUrfbNOdQVOWn+o8pAKllp76ysMXA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - }, - "optionalDependencies": { - "@oxc-resolver/binding-darwin-arm64": "5.0.0", - "@oxc-resolver/binding-darwin-x64": "5.0.0", - "@oxc-resolver/binding-freebsd-x64": "5.0.0", - "@oxc-resolver/binding-linux-arm-gnueabihf": "5.0.0", - "@oxc-resolver/binding-linux-arm64-gnu": "5.0.0", - "@oxc-resolver/binding-linux-arm64-musl": "5.0.0", - "@oxc-resolver/binding-linux-x64-gnu": "5.0.0", - "@oxc-resolver/binding-linux-x64-musl": "5.0.0", - "@oxc-resolver/binding-wasm32-wasi": "5.0.0", - "@oxc-resolver/binding-win32-arm64-msvc": "5.0.0", - "@oxc-resolver/binding-win32-x64-msvc": "5.0.0" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -11652,29 +11531,6 @@ "points-on-path": "^0.2.1" } }, - "node_modules/rspack-resolver": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/rspack-resolver/-/rspack-resolver-1.1.0.tgz", - "integrity": "sha512-pJfTX5KuwbJc4agd2AQ9sMwrBxMAGkLt4/HHw5+L06WuzxjsEjg3oDKdbfn43QGq0Stw8wQ7VpZjWA/T03L0Pg==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/JounQin" - }, - "optionalDependencies": { - "@unrs/rspack-resolver-binding-darwin-arm64": "1.1.0", - "@unrs/rspack-resolver-binding-darwin-x64": "1.1.0", - "@unrs/rspack-resolver-binding-freebsd-x64": "1.1.0", - "@unrs/rspack-resolver-binding-linux-arm-gnueabihf": "1.1.0", - "@unrs/rspack-resolver-binding-linux-arm64-gnu": "1.1.0", - "@unrs/rspack-resolver-binding-linux-arm64-musl": "1.1.0", - "@unrs/rspack-resolver-binding-linux-x64-gnu": "1.1.0", - "@unrs/rspack-resolver-binding-linux-x64-musl": "1.1.0", - "@unrs/rspack-resolver-binding-wasm32-wasi": "1.1.0", - "@unrs/rspack-resolver-binding-win32-arm64-msvc": "1.1.0", - "@unrs/rspack-resolver-binding-win32-x64-msvc": "1.1.0" - } - }, "node_modules/run-con": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/run-con/-/run-con-1.3.2.tgz", @@ -12258,9 +12114,9 @@ "license": "ISC" }, "node_modules/stylelint": { - "version": "16.16.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.16.0.tgz", - "integrity": "sha512-40X5UOb/0CEFnZVEHyN260HlSSUxPES+arrUphOumGWgXERHfwCD0kNBVILgQSij8iliYVwlc0V7M5bcLP9vPg==", + "version": "16.18.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.18.0.tgz", + "integrity": "sha512-OXb68qzesv7J70BSbFwfK3yTVLEVXiQ/ro6wUE4UrSbKCMjLLA02S8Qq3LC01DxKyVjk7z8xh35aB4JzO3/sNA==", "dev": true, "funding": [ { @@ -12321,9 +12177,9 @@ } }, "node_modules/stylelint-config-recommended": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-15.0.0.tgz", - "integrity": "sha512-9LejMFsat7L+NXttdHdTq94byn25TD+82bzGRiV1Pgasl99pWnwipXS5DguTpp3nP1XjvLXVnEJIuYBfsRjRkA==", + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-16.0.0.tgz", + "integrity": "sha512-4RSmPjQegF34wNcK1e1O3Uz91HN8P1aFdFzio90wNK9mjgAI19u5vsU868cVZboKzCaa5XbpvtTzAAGQAxpcXA==", "dev": true, "funding": [ { @@ -12340,7 +12196,7 @@ "node": ">=18.12.0" }, "peerDependencies": { - "stylelint": "^16.13.0" + "stylelint": "^16.16.0" } }, "node_modules/stylelint-declaration-block-no-ignored-properties": { @@ -12370,9 +12226,9 @@ } }, "node_modules/stylelint-define-config": { - "version": "16.15.0", - "resolved": "https://registry.npmjs.org/stylelint-define-config/-/stylelint-define-config-16.15.0.tgz", - "integrity": "sha512-nzHX9ZpI/k4A7izGYPS79xLAf2HyGvYkk/UXMgsQ7ZQEvkOZpQt4Aca4Qn5DYqNmWnqNlW5E3wK+qUmdR3vdxg==", + "version": "16.17.0", + "resolved": "https://registry.npmjs.org/stylelint-define-config/-/stylelint-define-config-16.17.0.tgz", + "integrity": "sha512-HToy83mn05K4g72GjXfyw3PhFV9QZKVJWt6m+w7+XNqAOsegiBI+sYXq8sg0R+cedsi2KVyfmEEpKO550yMmrQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12777,9 +12633,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.20.1", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.20.1.tgz", - "integrity": "sha512-qBPCis2w8nP4US7SvUxdJD3OwKcqiWeZmjN2VWhq2v+ESZEXOP/7n4DeiOiiZcGYTKMHAHUUrroHaTsjUWTEGw==", + "version": "5.20.7", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.20.7.tgz", + "integrity": "sha512-gLpb1wrWinUwMFKfSvDYsIlCyGQSryftzi6uWc9Qo98zO3mFT6oHOqmDUu5OoahvepuS6HGTe/3MsGUCVtpLig==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -13181,9 +13037,9 @@ } }, "node_modules/type-fest": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.37.0.tgz", - "integrity": "sha512-S/5/0kFftkq27FPNye0XM1e2NsnoD/3FS+pBmbjmmtLT6I+i344KoOf7pvXreaFsDamWeaJX55nczA1m5PsBDg==", + "version": "4.39.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.39.1.tgz", + "integrity": "sha512-uW9qzd66uyHYxwyVBYiwS4Oi0qZyUqwjU+Oevr6ZogYiXt99EOYtwvzMSLw1c3lYo2HzJsep/NB23iEVEgjG/w==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -13194,9 +13050,9 @@ } }, "node_modules/typescript": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -13247,6 +13103,33 @@ "node": ">= 10.0.0" } }, + "node_modules/unrs-resolver": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.4.1.tgz", + "integrity": "sha512-MhPB3wBI5BR8TGieTb08XuYlE8oFVEXdSAgat3psdlRyejl8ojQ8iqPcjh094qCZ1r+TnkxzP6BeCd/umfHckQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/JounQin" + }, + "optionalDependencies": { + "@unrs/resolver-binding-darwin-arm64": "1.4.1", + "@unrs/resolver-binding-darwin-x64": "1.4.1", + "@unrs/resolver-binding-freebsd-x64": "1.4.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.4.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.4.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.4.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.4.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.4.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.4.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.4.1", + "@unrs/resolver-binding-linux-x64-musl": "1.4.1", + "@unrs/resolver-binding-wasm32-wasi": "1.4.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.4.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.4.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.4.1" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -13436,9 +13319,9 @@ } }, "node_modules/vite-node": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.8.tgz", - "integrity": "sha512-6PhR4H9VGlcwXZ+KWCdMqbtG649xCPZqfI9j2PsK1FcXgEzro5bGHcVKFCTqPLaNKZES8Evqv4LwvZARsq5qlg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.1.tgz", + "integrity": "sha512-V+IxPAE2FvXpTCHXyNem0M+gWm6J7eRyWPR6vYoG/Gl+IscNOjXzztUhimQgTxaAoUoj40Qqimaa0NLIOOAH4w==", "dev": true, "license": "MIT", "dependencies": { @@ -13527,31 +13410,31 @@ } }, "node_modules/vitest": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.8.tgz", - "integrity": "sha512-dfqAsNqRGUc8hB9OVR2P0w8PZPEckti2+5rdZip0WIz9WW0MnImJ8XiR61QhqLa92EQzKP2uPkzenKOAHyEIbA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.1.tgz", + "integrity": "sha512-kiZc/IYmKICeBAZr9DQ5rT7/6bD9G7uqQEki4fxazi1jdVl2mWGzedtBs5s6llz59yQhVb7FFY2MbHzHCnT79Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "3.0.8", - "@vitest/mocker": "3.0.8", - "@vitest/pretty-format": "^3.0.8", - "@vitest/runner": "3.0.8", - "@vitest/snapshot": "3.0.8", - "@vitest/spy": "3.0.8", - "@vitest/utils": "3.0.8", + "@vitest/expect": "3.1.1", + "@vitest/mocker": "3.1.1", + "@vitest/pretty-format": "^3.1.1", + "@vitest/runner": "3.1.1", + "@vitest/snapshot": "3.1.1", + "@vitest/spy": "3.1.1", + "@vitest/utils": "3.1.1", "chai": "^5.2.0", "debug": "^4.4.0", - "expect-type": "^1.1.0", + "expect-type": "^1.2.0", "magic-string": "^0.30.17", "pathe": "^2.0.3", - "std-env": "^3.8.0", + "std-env": "^3.8.1", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.0.8", + "vite-node": "3.1.1", "why-is-node-running": "^2.3.0" }, "bin": { @@ -13567,8 +13450,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.0.8", - "@vitest/ui": "3.0.8", + "@vitest/browser": "3.1.1", + "@vitest/ui": "3.1.1", "happy-dom": "*", "jsdom": "*" }, diff --git a/package.json b/package.json index ef1a132994..040d6b24fd 100644 --- a/package.json +++ b/package.json @@ -15,12 +15,12 @@ "@primer/octicons": "19.15.1", "@silverwind/vue3-calendar-heatmap": "2.0.6", "add-asset-webpack-plugin": "3.0.0", - "ansi_up": "6.0.2", + "ansi_up": "6.0.5", "asciinema-player": "3.9.0", "chart.js": "4.4.8", "chartjs-adapter-dayjs-4": "1.0.4", "chartjs-plugin-zoom": "2.2.0", - "clippie": "4.1.5", + "clippie": "4.1.6", "cropperjs": "1.6.2", "css-loader": "7.1.2", "dayjs": "1.11.13", @@ -34,7 +34,7 @@ "jquery": "3.7.1", "katex": "0.16.21", "license-checker-webpack-plugin": "0.2.1", - "mermaid": "11.5.0", + "mermaid": "11.6.0", "mini-css-extract-plugin": "2.9.2", "minimatch": "10.0.1", "monaco-editor": "0.52.2", @@ -45,14 +45,14 @@ "postcss-loader": "8.1.1", "postcss-nesting": "13.0.1", "sortablejs": "1.15.6", - "swagger-ui-dist": "5.20.1", + "swagger-ui-dist": "5.20.7", "tailwindcss": "3.4.17", "throttle-debounce": "5.0.2", "tinycolor2": "1.6.0", "tippy.js": "6.3.7", "toastify-js": "1.12.0", "tributejs": "5.1.3", - "typescript": "5.8.2", + "typescript": "5.8.3", "uint8-to-base64": "0.2.0", "vanilla-colorful": "0.7.2", "vue": "3.5.13", @@ -79,15 +79,15 @@ "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/toastify-js": "1.12.3", - "@typescript-eslint/eslint-plugin": "8.26.1", - "@typescript-eslint/parser": "8.26.1", - "@vitejs/plugin-vue": "5.2.1", - "@vitest/eslint-plugin": "1.1.37", + "@typescript-eslint/eslint-plugin": "8.29.1", + "@typescript-eslint/parser": "8.29.1", + "@vitejs/plugin-vue": "5.2.3", + "@vitest/eslint-plugin": "1.1.39", "eslint": "8.57.0", - "eslint-import-resolver-typescript": "3.9.0", + "eslint-import-resolver-typescript": "4.3.2", "eslint-plugin-array-func": "4.0.0", "eslint-plugin-github": "5.0.2", - "eslint-plugin-import-x": "4.7.2", + "eslint-plugin-import-x": "4.10.2", "eslint-plugin-no-jquery": "3.1.1", "eslint-plugin-no-use-extend-native": "0.5.0", "eslint-plugin-playwright": "2.2.0", @@ -96,23 +96,23 @@ "eslint-plugin-unicorn": "56.0.1", "eslint-plugin-vue": "10.0.0", "eslint-plugin-vue-scoped-css": "2.9.0", - "eslint-plugin-wc": "2.2.1", + "eslint-plugin-wc": "3.0.0", "happy-dom": "17.4.4", "markdownlint-cli": "0.44.0", - "material-icon-theme": "5.20.0", + "material-icon-theme": "5.21.1", "nolyfill": "1.0.44", "postcss-html": "1.8.0", - "stylelint": "16.16.0", - "stylelint-config-recommended": "15.0.0", + "stylelint": "16.18.0", + "stylelint-config-recommended": "16.0.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.11", - "stylelint-define-config": "16.15.0", + "stylelint-define-config": "16.17.0", "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.3.2", - "type-fest": "4.37.0", + "type-fest": "4.39.1", "updates": "16.4.2", "vite-string-plugin": "1.4.4", - "vitest": "3.0.8", + "vitest": "3.1.1", "vue-tsc": "2.2.8" }, "browserslist": [ diff --git a/poetry.lock b/poetry.lock index ca7ae78cb8..b532bf0a3a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. [[package]] name = "click" @@ -119,18 +119,18 @@ six = ">=1.13.0" [[package]] name = "json5" -version = "0.10.0" +version = "0.12.0" description = "A Python implementation of the JSON5 data format." optional = false python-versions = ">=3.8.0" groups = ["dev"] files = [ - {file = "json5-0.10.0-py3-none-any.whl", hash = "sha256:19b23410220a7271e8377f81ba8aacba2fdd56947fbb137ee5977cbe1f5e8dfa"}, - {file = "json5-0.10.0.tar.gz", hash = "sha256:e66941c8f0a02026943c52c2eb34ebeb2a6f819a0be05920a6f5243cd30fd559"}, + {file = "json5-0.12.0-py3-none-any.whl", hash = "sha256:6d37aa6c08b0609f16e1ec5ff94697e2cbbfbad5ac112afa05794da9ab7810db"}, + {file = "json5-0.12.0.tar.gz", hash = "sha256:0b4b6ff56801a1c7dc817b0241bca4ce474a0e6a163bfef3fc594d3fd263ff3a"}, ] [package.extras] -dev = ["build (==1.2.2.post1)", "coverage (==7.5.3)", "mypy (==1.13.0)", "pip (==24.3.1)", "pylint (==3.2.3)", "ruff (==0.7.3)", "twine (==5.1.1)", "uv (==0.5.1)"] +dev = ["build (==1.2.2.post1)", "coverage (==7.5.4) ; python_version < \"3.9\"", "coverage (==7.8.0) ; python_version >= \"3.9\"", "mypy (==1.14.1) ; python_version < \"3.9\"", "mypy (==1.15.0) ; python_version >= \"3.9\"", "pip (==25.0.1)", "pylint (==3.2.7) ; python_version < \"3.9\"", "pylint (==3.3.6) ; python_version >= \"3.9\"", "ruff (==0.11.2)", "twine (==6.1.0)", "uv (==0.6.11)"] [[package]] name = "pathspec" @@ -330,7 +330,7 @@ description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version < \"3.11\"" +markers = "python_version == \"3.10\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -390,27 +390,27 @@ telegram = ["requests"] [[package]] name = "typing-extensions" -version = "4.12.2" +version = "4.13.1" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version < \"3.11\"" +markers = "python_version == \"3.10\"" files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, + {file = "typing_extensions-4.13.1-py3-none-any.whl", hash = "sha256:4b6cf02909eb5495cfbc3f6e8fd49217e6cc7944e145cdda8caa3734777f9e69"}, + {file = "typing_extensions-4.13.1.tar.gz", hash = "sha256:98795af00fb9640edec5b8e31fc647597b4691f099ad75f469a2616be1a76dff"}, ] [[package]] name = "yamllint" -version = "1.36.1" +version = "1.37.0" description = "A linter for YAML files." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "yamllint-1.36.1-py3-none-any.whl", hash = "sha256:3e2ccd47ea12449837adf6b2c56fd9e31172ce42bc1470380806be461f25df66"}, - {file = "yamllint-1.36.1.tar.gz", hash = "sha256:a287689daaafc301a80549b2d0170452ebfdcabd826e3fe3b4c66e322d4851fa"}, + {file = "yamllint-1.37.0-py3-none-any.whl", hash = "sha256:c03ab4e79ab4af964c8eb16ac9746880fc76a3bb0ffb14925b9a55220ae7dda0"}, + {file = "yamllint-1.37.0.tar.gz", hash = "sha256:ead81921d4d87216b2528b7a055664708f9fb8267beb0c427cb706ac6ab93580"}, ] [package.dependencies] @@ -423,4 +423,4 @@ dev = ["doc8", "flake8", "flake8-import-order", "rstcheck[sphinx]", "sphinx"] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "d48a461813418f7b803a7f94713d93076a009a96554e0f4c707be0cc2a95b563" +content-hash = "d8ed1d7f88ff4be57be2c599b9d906dee25d0f6f6e46fdc9051c892c4e812a1c" diff --git a/pyproject.toml b/pyproject.toml index a2aedfe148..4eed5f0e74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ python = "^3.10" [tool.poetry.group.dev.dependencies] djlint = "1.36.4" -yamllint = "1.36.1" +yamllint = "1.37.0" [tool.djlint] profile="golang" diff --git a/routers/api/actions/artifacts_chunks.go b/routers/api/actions/artifacts_chunks.go index 9d2b69820c..708931d1ac 100644 --- a/routers/api/actions/artifacts_chunks.go +++ b/routers/api/actions/artifacts_chunks.go @@ -51,7 +51,7 @@ func saveUploadChunkBase(st storage.ObjectStorage, ctx *ArtifactContext, log.Info("[artifact] check chunk md5, sum: %s, header: %s", chunkMd5String, reqMd5String) // if md5 not match, delete the chunk if reqMd5String != chunkMd5String { - checkErr = fmt.Errorf("md5 not match") + checkErr = errors.New("md5 not match") } } if writtenSize != contentSize { @@ -261,7 +261,7 @@ func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st st return fmt.Errorf("save merged file error: %v", err) } if written != artifact.FileCompressedSize { - return fmt.Errorf("merged file size is not equal to chunk length") + return errors.New("merged file size is not equal to chunk length") } defer func() { diff --git a/routers/api/actions/artifactsv4.go b/routers/api/actions/artifactsv4.go index 665156d936..9fb0a31549 100644 --- a/routers/api/actions/artifactsv4.go +++ b/routers/api/actions/artifactsv4.go @@ -162,15 +162,15 @@ func (r artifactV4Routes) buildSignature(endp, expires, artifactName string, tas mac.Write([]byte(endp)) mac.Write([]byte(expires)) mac.Write([]byte(artifactName)) - mac.Write([]byte(fmt.Sprint(taskID))) - mac.Write([]byte(fmt.Sprint(artifactID))) + fmt.Fprint(mac, taskID) + fmt.Fprint(mac, artifactID) return mac.Sum(nil) } func (r artifactV4Routes) buildArtifactURL(ctx *ArtifactContext, endp, artifactName string, taskID, artifactID int64) string { expires := time.Now().Add(60 * time.Minute).Format("2006-01-02 15:04:05.999999999 -0700 MST") uploadURL := strings.TrimSuffix(httplib.GuessCurrentAppURL(ctx), "/") + strings.TrimSuffix(r.prefix, "/") + - "/" + endp + "?sig=" + base64.URLEncoding.EncodeToString(r.buildSignature(endp, expires, artifactName, taskID, artifactID)) + "&expires=" + url.QueryEscape(expires) + "&artifactName=" + url.QueryEscape(artifactName) + "&taskID=" + fmt.Sprint(taskID) + "&artifactID=" + fmt.Sprint(artifactID) + "/" + endp + "?sig=" + base64.URLEncoding.EncodeToString(r.buildSignature(endp, expires, artifactName, taskID, artifactID)) + "&expires=" + url.QueryEscape(expires) + "&artifactName=" + url.QueryEscape(artifactName) + "&taskID=" + strconv.FormatInt(taskID, 10) + "&artifactID=" + strconv.FormatInt(artifactID, 10) return uploadURL } diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index b64306037f..ae4ea7ea87 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -46,13 +46,14 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) { if ok { // it's a personal access token but not oauth2 token scopeMatched := false var err error - if accessMode == perm.AccessModeRead { + switch accessMode { + case perm.AccessModeRead: scopeMatched, err = scope.HasScope(auth_model.AccessTokenScopeReadPackage) if err != nil { ctx.HTTPError(http.StatusInternalServerError, "HasScope", err.Error()) return } - } else if accessMode == perm.AccessModeWrite { + case perm.AccessModeWrite: scopeMatched, err = scope.HasScope(auth_model.AccessTokenScopeWritePackage) if err != nil { ctx.HTTPError(http.StatusInternalServerError, "HasScope", err.Error()) @@ -551,10 +552,10 @@ func CommonRoutes() *web.Router { r.Methods("HEAD,GET,PUT,DELETE", "*", func(ctx *context.Context) { path := ctx.PathParam("*") - isHead := ctx.Req.Method == "HEAD" - isGetHead := ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET" - isPut := ctx.Req.Method == "PUT" - isDelete := ctx.Req.Method == "DELETE" + isHead := ctx.Req.Method == http.MethodHead + isGetHead := ctx.Req.Method == http.MethodHead || ctx.Req.Method == http.MethodGet + isPut := ctx.Req.Method == http.MethodPut + isDelete := ctx.Req.Method == http.MethodDelete m := repoPattern.FindStringSubmatch(path) if len(m) == 2 && isGetHead { @@ -703,13 +704,14 @@ func ContainerRoutes() *web.Router { g.MatchPath("POST", "//blobs/uploads", reqPackageAccess(perm.AccessModeWrite), container.VerifyImageName, container.InitiateUploadBlob) g.MatchPath("GET", "//tags/list", container.VerifyImageName, container.GetTagList) g.MatchPath("GET,PATCH,PUT,DELETE", `//blobs/uploads/`, reqPackageAccess(perm.AccessModeWrite), container.VerifyImageName, func(ctx *context.Context) { - if ctx.Req.Method == http.MethodGet { + switch ctx.Req.Method { + case http.MethodGet: container.GetUploadBlob(ctx) - } else if ctx.Req.Method == http.MethodPatch { + case http.MethodPatch: container.UploadBlob(ctx) - } else if ctx.Req.Method == http.MethodPut { + case http.MethodPut: container.EndUploadBlob(ctx) - } else /* DELETE */ { + default: /* DELETE */ container.CancelUploadBlob(ctx) } }) diff --git a/routers/api/packages/cargo/cargo.go b/routers/api/packages/cargo/cargo.go index 42ef13476c..710c614c6e 100644 --- a/routers/api/packages/cargo/cargo.go +++ b/routers/api/packages/cargo/cargo.go @@ -51,7 +51,7 @@ func apiError(ctx *context.Context, status int, obj any) { // https://rust-lang.github.io/rfcs/2789-sparse-index.html func RepositoryConfig(ctx *context.Context) { - ctx.JSON(http.StatusOK, cargo_service.BuildConfig(ctx.Package.Owner, setting.Service.RequireSignInView || ctx.Package.Owner.Visibility != structs.VisibleTypePublic)) + ctx.JSON(http.StatusOK, cargo_service.BuildConfig(ctx.Package.Owner, setting.Service.RequireSignInViewStrict || ctx.Package.Owner.Visibility != structs.VisibleTypePublic)) } func EnumeratePackageVersions(ctx *context.Context) { diff --git a/routers/api/packages/chef/auth.go b/routers/api/packages/chef/auth.go index a790e9a363..c6808300a2 100644 --- a/routers/api/packages/chef/auth.go +++ b/routers/api/packages/chef/auth.go @@ -12,6 +12,7 @@ import ( "crypto/x509" "encoding/base64" "encoding/pem" + "errors" "fmt" "hash" "math/big" @@ -121,7 +122,7 @@ func verifyTimestamp(req *http.Request) error { } if diff > maxTimeDifference { - return fmt.Errorf("time difference") + return errors.New("time difference") } return nil @@ -190,7 +191,7 @@ func getAuthorizationData(req *http.Request) ([]byte, error) { tmp := make([]string, len(valueList)) for k, v := range valueList { if k > len(tmp) { - return nil, fmt.Errorf("invalid X-Ops-Authorization headers") + return nil, errors.New("invalid X-Ops-Authorization headers") } tmp[k-1] = v } @@ -267,7 +268,7 @@ func verifyDataOld(signature, data []byte, pub *rsa.PublicKey) error { } if !slices.Equal(out[skip:], data) { - return fmt.Errorf("could not verify signature") + return errors.New("could not verify signature") } return nil diff --git a/routers/api/packages/container/blob.go b/routers/api/packages/container/blob.go index 671803788a..4a2320ab76 100644 --- a/routers/api/packages/container/blob.go +++ b/routers/api/packages/container/blob.go @@ -149,7 +149,7 @@ func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageI } func createFileForBlob(ctx context.Context, pv *packages_model.PackageVersion, pb *packages_model.PackageBlob) error { - filename := strings.ToLower(fmt.Sprintf("sha256_%s", pb.HashSHA256)) + filename := strings.ToLower("sha256_" + pb.HashSHA256) pf := &packages_model.PackageFile{ VersionID: pv.ID, diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go index bb14db9db7..6ef1655235 100644 --- a/routers/api/packages/container/container.go +++ b/routers/api/packages/container/container.go @@ -126,7 +126,7 @@ func apiUnauthorizedError(ctx *context.Context) { // ReqContainerAccess is a middleware which checks the current user valid (real user or ghost if anonymous access is enabled) func ReqContainerAccess(ctx *context.Context) { - if ctx.Doer == nil || (setting.Service.RequireSignInView && ctx.Doer.IsGhost()) { + if ctx.Doer == nil || (setting.Service.RequireSignInViewStrict && ctx.Doer.IsGhost()) { apiUnauthorizedError(ctx) } } @@ -152,7 +152,7 @@ func Authenticate(ctx *context.Context) { u := ctx.Doer packageScope := auth_service.GetAccessScope(ctx.Data) if u == nil { - if setting.Service.RequireSignInView { + if setting.Service.RequireSignInViewStrict { apiUnauthorizedError(ctx) return } diff --git a/routers/api/packages/container/manifest.go b/routers/api/packages/container/manifest.go index ad035cf473..26faa7b024 100644 --- a/routers/api/packages/container/manifest.go +++ b/routers/api/packages/container/manifest.go @@ -406,7 +406,7 @@ func createFileFromBlobReference(ctx context.Context, pv, uploadVersion *package } if ref.Name == "" { - ref.Name = strings.ToLower(fmt.Sprintf("sha256_%s", ref.File.Blob.HashSHA256)) + ref.Name = strings.ToLower("sha256_" + ref.File.Blob.HashSHA256) } pf := &packages_model.PackageFile{ diff --git a/routers/api/packages/nuget/nuget.go b/routers/api/packages/nuget/nuget.go index 07a8de0a68..fa5067a278 100644 --- a/routers/api/packages/nuget/nuget.go +++ b/routers/api/packages/nuget/nuget.go @@ -488,7 +488,7 @@ func UploadPackage(ctx *context.Context) { pv, &packages_service.PackageFileCreationInfo{ PackageFileInfo: packages_service.PackageFileInfo{ - Filename: strings.ToLower(fmt.Sprintf("%s.nuspec", np.ID)), + Filename: strings.ToLower(np.ID + ".nuspec"), }, Data: nuspecBuf, }, diff --git a/routers/api/v1/activitypub/reqsignature.go b/routers/api/v1/activitypub/reqsignature.go index 957d593d89..4eff51782f 100644 --- a/routers/api/v1/activitypub/reqsignature.go +++ b/routers/api/v1/activitypub/reqsignature.go @@ -7,6 +7,7 @@ import ( "crypto" "crypto/x509" "encoding/pem" + "errors" "fmt" "io" "net/http" @@ -34,7 +35,7 @@ func getPublicKeyFromResponse(b []byte, keyID *url.URL) (p crypto.PublicKey, err pubKeyPem := pubKey.PublicKeyPem block, _ := pem.Decode([]byte(pubKeyPem)) if block == nil || block.Type != "PUBLIC KEY" { - return nil, fmt.Errorf("could not decode publicKeyPem to PUBLIC KEY pem block type") + return nil, errors.New("could not decode publicKeyPem to PUBLIC KEY pem block type") } p, err = x509.ParsePKIXPublicKey(block.Bytes) return p, err diff --git a/routers/api/v1/admin/hooks.go b/routers/api/v1/admin/hooks.go index fb1ea4eab6..a687541be5 100644 --- a/routers/api/v1/admin/hooks.go +++ b/routers/api/v1/admin/hooks.go @@ -51,9 +51,10 @@ func ListHooks(ctx *context.APIContext) { // for compatibility the default value is true isSystemWebhook := optional.Some(true) typeValue := ctx.FormString("type") - if typeValue == "default" { + switch typeValue { + case "default": isSystemWebhook = optional.Some(false) - } else if typeValue == "all" { + case "all": isSystemWebhook = optional.None[bool]() } diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index c4bb85de55..3ba77604ec 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -296,7 +296,7 @@ func DeleteUser(ctx *context.APIContext) { // admin should not delete themself if ctx.ContextUser.ID == ctx.Doer.ID { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("you cannot delete yourself")) + ctx.APIError(http.StatusUnprocessableEntity, errors.New("you cannot delete yourself")) return } diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index c49152a64d..b9b590725b 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -307,7 +307,7 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC // use the http method to determine the access level requiredScopeLevel := auth_model.Read - if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || ctx.Req.Method == "PATCH" || ctx.Req.Method == "DELETE" { + if ctx.Req.Method == http.MethodPost || ctx.Req.Method == http.MethodPut || ctx.Req.Method == http.MethodPatch || ctx.Req.Method == http.MethodDelete { requiredScopeLevel = auth_model.Write } @@ -355,7 +355,7 @@ func reqToken() func(ctx *context.APIContext) { func reqExploreSignIn() func(ctx *context.APIContext) { return func(ctx *context.APIContext) { - if (setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView) && !ctx.IsSigned { + if (setting.Service.RequireSignInViewStrict || setting.Service.Explore.RequireSigninView) && !ctx.IsSigned { ctx.APIError(http.StatusUnauthorized, "you must be signed in to search for users") } } @@ -842,13 +842,13 @@ 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: + switch ctx.ContextUser.Visibility { + case api.VisibleTypePrivate: if ctx.Doer == nil || (ctx.ContextUser.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin) { ctx.APIErrorNotFound("Visit Project", nil) return } - case ctx.ContextUser.Visibility == api.VisibleTypeLimited: + case api.VisibleTypeLimited: if ctx.Doer == nil { ctx.APIErrorNotFound("Visit Project", nil) return @@ -886,7 +886,7 @@ func Routes() *web.Router { m.Use(apiAuth(buildAuthGroup())) m.Use(verifyAuthWithOptions(&common.VerifyOptions{ - SignInRequired: setting.Service.RequireSignInView, + SignInRequired: setting.Service.RequireSignInViewStrict, })) addActionsRoutes := func( @@ -1544,14 +1544,19 @@ func Routes() *web.Router { // NOTE: these are Gitea package management API - see packages.CommonRoutes and packages.DockerContainerRoutes for endpoints that implement package manager APIs m.Group("/packages/{username}", func() { m.Group("/{type}/{name}", func() { + m.Get("/", packages.ListPackageVersions) + m.Group("/{version}", func() { m.Get("", packages.GetPackage) m.Delete("", reqPackageAccess(perm.AccessModeWrite), packages.DeletePackage) m.Get("/files", packages.ListPackageFiles) }) - m.Post("/-/link/{repo_name}", reqPackageAccess(perm.AccessModeWrite), packages.LinkPackage) - m.Post("/-/unlink", reqPackageAccess(perm.AccessModeWrite), packages.UnlinkPackage) + m.Group("/-", func() { + m.Get("/latest", packages.GetLatestPackageVersion) + m.Post("/link/{repo_name}", reqPackageAccess(perm.AccessModeWrite), packages.LinkPackage) + m.Post("/unlink", reqPackageAccess(perm.AccessModeWrite), packages.UnlinkPackage) + }) }) m.Get("/", packages.ListPackages) diff --git a/routers/api/v1/misc/markup_test.go b/routers/api/v1/misc/markup_test.go index 6063e54cdc..38a1a3be9e 100644 --- a/routers/api/v1/misc/markup_test.go +++ b/routers/api/v1/misc/markup_test.go @@ -134,7 +134,7 @@ Here are some links to the most important topics. You can find the full list of Quick Links Here are some links to the most important topics. You can find the full list of pages at the sidebar. Configuration - + `, } @@ -158,19 +158,19 @@ Here are some links to the most important topics. You can find the full list of input := "[Link](test.md)\n" testRenderMarkdown(t, "gfm", false, input, `Link - + `, http.StatusOK) testRenderMarkdown(t, "gfm", false, input, `Link - + `, http.StatusOK) testRenderMarkup(t, "gfm", false, "", input, `Link - + `, http.StatusOK) testRenderMarkup(t, "file", false, "path/new-file.md", input, `Link - + `, http.StatusOK) testRenderMarkup(t, "file", false, "path/test.unknown", "## Test", "unsupported file to render: \"path/test.unknown\"\n", http.StatusUnprocessableEntity) diff --git a/routers/api/v1/org/member.go b/routers/api/v1/org/member.go index 2663d78b73..a1875a7886 100644 --- a/routers/api/v1/org/member.go +++ b/routers/api/v1/org/member.go @@ -8,6 +8,7 @@ import ( "net/url" "code.gitea.io/gitea/models/organization" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/routers/api/v1/user" @@ -210,6 +211,20 @@ func IsPublicMember(ctx *context.APIContext) { } } +func checkCanChangeOrgUserStatus(ctx *context.APIContext, targetUser *user_model.User) { + // allow user themselves to change their status, and allow admins to change any user + if targetUser.ID == ctx.Doer.ID || ctx.Doer.IsAdmin { + return + } + // allow org owners to change status of members + isOwner, err := ctx.Org.Organization.IsOwnedBy(ctx, ctx.Doer.ID) + if err != nil { + ctx.APIError(http.StatusInternalServerError, err) + } else if !isOwner { + ctx.APIError(http.StatusForbidden, "Cannot change member visibility") + } +} + // PublicizeMember make a member's membership public func PublicizeMember(ctx *context.APIContext) { // swagger:operation PUT /orgs/{org}/public_members/{username} organization orgPublicizeMember @@ -240,8 +255,8 @@ func PublicizeMember(ctx *context.APIContext) { if ctx.Written() { return } - if userToPublicize.ID != ctx.Doer.ID { - ctx.APIError(http.StatusForbidden, "Cannot publicize another member") + checkCanChangeOrgUserStatus(ctx, userToPublicize) + if ctx.Written() { return } err := organization.ChangeOrgUserStatus(ctx, ctx.Org.Organization.ID, userToPublicize.ID, true) @@ -282,8 +297,8 @@ func ConcealMember(ctx *context.APIContext) { if ctx.Written() { return } - if userToConceal.ID != ctx.Doer.ID { - ctx.APIError(http.StatusForbidden, "Cannot conceal another member") + checkCanChangeOrgUserStatus(ctx, userToConceal) + if ctx.Written() { return } err := organization.ChangeOrgUserStatus(ctx, ctx.Org.Organization.ID, userToConceal.ID, false) diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index f70e5dd235..71c21f2dde 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -141,26 +141,18 @@ func GetTeam(ctx *context.APIContext) { ctx.JSON(http.StatusOK, apiTeam) } -func attachTeamUnits(team *organization.Team, units []string) { +func attachTeamUnits(team *organization.Team, defaultAccessMode perm.AccessMode, units []string) { unitTypes, _ := unit_model.FindUnitTypes(units...) team.Units = make([]*organization.TeamUnit, 0, len(units)) for _, tp := range unitTypes { team.Units = append(team.Units, &organization.TeamUnit{ OrgID: team.OrgID, Type: tp, - AccessMode: team.AccessMode, + AccessMode: defaultAccessMode, }) } } -func convertUnitsMap(unitsMap map[string]string) map[unit_model.Type]perm.AccessMode { - res := make(map[unit_model.Type]perm.AccessMode, len(unitsMap)) - for unitKey, p := range unitsMap { - res[unit_model.TypeFromKey(unitKey)] = perm.ParseAccessMode(p) - } - return res -} - func attachTeamUnitsMap(team *organization.Team, unitsMap map[string]string) { team.Units = make([]*organization.TeamUnit, 0, len(unitsMap)) for unitKey, p := range unitsMap { @@ -214,24 +206,22 @@ func CreateTeam(ctx *context.APIContext) { // "422": // "$ref": "#/responses/validationError" form := web.GetForm(ctx).(*api.CreateTeamOption) - p := perm.ParseAccessMode(form.Permission) - if p < perm.AccessModeAdmin && len(form.UnitsMap) > 0 { - p = unit_model.MinUnitAccessMode(convertUnitsMap(form.UnitsMap)) - } + teamPermission := perm.ParseAccessMode(form.Permission, perm.AccessModeNone, perm.AccessModeAdmin) team := &organization.Team{ OrgID: ctx.Org.Organization.ID, Name: form.Name, Description: form.Description, IncludesAllRepositories: form.IncludesAllRepositories, CanCreateOrgRepo: form.CanCreateOrgRepo, - AccessMode: p, + AccessMode: teamPermission, } if team.AccessMode < perm.AccessModeAdmin { if len(form.UnitsMap) > 0 { attachTeamUnitsMap(team, form.UnitsMap) } else if len(form.Units) > 0 { - attachTeamUnits(team, form.Units) + unitPerm := perm.ParseAccessMode(form.Permission, perm.AccessModeRead, perm.AccessModeWrite) + attachTeamUnits(team, unitPerm, form.Units) } else { ctx.APIErrorInternal(errors.New("units permission should not be empty")) return @@ -304,15 +294,10 @@ func EditTeam(ctx *context.APIContext) { isAuthChanged := false isIncludeAllChanged := false if !team.IsOwnerTeam() && len(form.Permission) != 0 { - // Validate permission level. - p := perm.ParseAccessMode(form.Permission) - if p < perm.AccessModeAdmin && len(form.UnitsMap) > 0 { - p = unit_model.MinUnitAccessMode(convertUnitsMap(form.UnitsMap)) - } - - if team.AccessMode != p { + teamPermission := perm.ParseAccessMode(form.Permission, perm.AccessModeNone, perm.AccessModeAdmin) + if team.AccessMode != teamPermission { isAuthChanged = true - team.AccessMode = p + team.AccessMode = teamPermission } if form.IncludesAllRepositories != nil { @@ -325,7 +310,8 @@ func EditTeam(ctx *context.APIContext) { if len(form.UnitsMap) > 0 { attachTeamUnitsMap(team, form.UnitsMap) } else if len(form.Units) > 0 { - attachTeamUnits(team, form.Units) + unitPerm := perm.ParseAccessMode(form.Permission, perm.AccessModeRead, perm.AccessModeWrite) + attachTeamUnits(team, unitPerm, form.Units) } } else { attachAdminTeamUnits(team) diff --git a/routers/api/v1/packages/package.go b/routers/api/v1/packages/package.go index f869519344..41b7f2a43f 100644 --- a/routers/api/v1/packages/package.go +++ b/routers/api/v1/packages/package.go @@ -56,13 +56,10 @@ func ListPackages(ctx *context.APIContext) { listOptions := utils.GetListOptions(ctx) - packageType := ctx.FormTrim("type") - query := ctx.FormTrim("q") - - pvs, count, err := packages.SearchVersions(ctx, &packages.PackageSearchOptions{ + apiPackages, count, err := searchPackages(ctx, &packages.PackageSearchOptions{ OwnerID: ctx.Package.Owner.ID, - Type: packages.Type(packageType), - Name: packages.SearchValue{Value: query}, + Type: packages.Type(ctx.FormTrim("type")), + Name: packages.SearchValue{Value: ctx.FormTrim("q")}, IsInternal: optional.Some(false), Paginator: &listOptions, }) @@ -71,22 +68,6 @@ func ListPackages(ctx *context.APIContext) { return } - pds, err := packages.GetPackageDescriptors(ctx, pvs) - if err != nil { - ctx.APIErrorInternal(err) - return - } - - apiPackages := make([]*api.Package, 0, len(pds)) - for _, pd := range pds { - apiPackage, err := convert.ToPackage(ctx, pd, ctx.Doer) - if err != nil { - ctx.APIErrorInternal(err) - return - } - apiPackages = append(apiPackages, apiPackage) - } - ctx.SetLinkHeader(int(count), listOptions.PageSize) ctx.SetTotalCountHeader(count) ctx.JSON(http.StatusOK, apiPackages) @@ -217,6 +198,121 @@ func ListPackageFiles(ctx *context.APIContext) { ctx.JSON(http.StatusOK, apiPackageFiles) } +// ListPackageVersions gets all versions of a package +func ListPackageVersions(ctx *context.APIContext) { + // swagger:operation GET /packages/{owner}/{type}/{name} package listPackageVersions + // --- + // summary: Gets all versions of a package + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the package + // type: string + // required: true + // - name: type + // in: path + // description: type of the package + // type: string + // required: true + // - name: name + // in: path + // description: name of the package + // type: string + // required: true + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results + // type: integer + // responses: + // "200": + // "$ref": "#/responses/PackageList" + // "404": + // "$ref": "#/responses/notFound" + + listOptions := utils.GetListOptions(ctx) + + apiPackages, count, err := searchPackages(ctx, &packages.PackageSearchOptions{ + OwnerID: ctx.Package.Owner.ID, + Type: packages.Type(ctx.PathParam("type")), + Name: packages.SearchValue{Value: ctx.PathParam("name"), ExactMatch: true}, + IsInternal: optional.Some(false), + Paginator: &listOptions, + }) + if err != nil { + ctx.APIErrorInternal(err) + return + } + + ctx.SetLinkHeader(int(count), listOptions.PageSize) + ctx.SetTotalCountHeader(count) + ctx.JSON(http.StatusOK, apiPackages) +} + +// GetLatestPackageVersion gets the latest version of a package +func GetLatestPackageVersion(ctx *context.APIContext) { + // swagger:operation GET /packages/{owner}/{type}/{name}/-/latest package getLatestPackageVersion + // --- + // summary: Gets the latest version of a package + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the package + // type: string + // required: true + // - name: type + // in: path + // description: type of the package + // type: string + // required: true + // - name: name + // in: path + // description: name of the package + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/Package" + // "404": + // "$ref": "#/responses/notFound" + + pvs, _, err := packages.SearchLatestVersions(ctx, &packages.PackageSearchOptions{ + OwnerID: ctx.Package.Owner.ID, + Type: packages.Type(ctx.PathParam("type")), + Name: packages.SearchValue{Value: ctx.PathParam("name"), ExactMatch: true}, + IsInternal: optional.Some(false), + }) + if err != nil { + ctx.APIErrorInternal(err) + return + } + if len(pvs) == 0 { + ctx.APIError(http.StatusNotFound, err) + return + } + + pd, err := packages.GetPackageDescriptor(ctx, pvs[0]) + if err != nil { + ctx.APIErrorInternal(err) + return + } + + apiPackage, err := convert.ToPackage(ctx, pd, ctx.Doer) + if err != nil { + ctx.APIErrorInternal(err) + return + } + + ctx.JSON(http.StatusOK, apiPackage) +} + // LinkPackage sets a repository link for a package func LinkPackage(ctx *context.APIContext) { // swagger:operation POST /packages/{owner}/{type}/{name}/-/link/{repo_name} package linkPackage @@ -335,3 +431,26 @@ func UnlinkPackage(ctx *context.APIContext) { } ctx.Status(http.StatusNoContent) } + +func searchPackages(ctx *context.APIContext, opts *packages.PackageSearchOptions) ([]*api.Package, int64, error) { + pvs, count, err := packages.SearchVersions(ctx, opts) + if err != nil { + return nil, 0, err + } + + pds, err := packages.GetPackageDescriptors(ctx, pvs) + if err != nil { + return nil, 0, err + } + + apiPackages := make([]*api.Package, 0, len(pds)) + for _, pd := range pds { + apiPackage, err := convert.ToPackage(ctx, pd, ctx.Doer) + if err != nil { + return nil, 0, err + } + apiPackages = append(apiPackages, apiPackage) + } + + return apiPackages, count, nil +} diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go index 2ace9fa295..ed2017a372 100644 --- a/routers/api/v1/repo/action.go +++ b/routers/api/v1/repo/action.go @@ -1103,8 +1103,8 @@ func DeleteArtifact(ctx *context.APIContext) { func buildSignature(endp string, expires, artifactID int64) []byte { mac := hmac.New(sha256.New, setting.GetGeneralTokenSigningSecret()) mac.Write([]byte(endp)) - mac.Write([]byte(fmt.Sprint(expires))) - mac.Write([]byte(fmt.Sprint(artifactID))) + fmt.Fprint(mac, expires) + fmt.Fprint(mac, artifactID) return mac.Sum(nil) } diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index 9c6e572fb4..fe82550fdd 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -6,7 +6,6 @@ package repo import ( "errors" - "fmt" "net/http" "code.gitea.io/gitea/models/db" @@ -60,17 +59,16 @@ func GetBranch(ctx *context.APIContext) { branchName := ctx.PathParam("*") - branch, err := ctx.Repo.GitRepo.GetBranch(branchName) + exist, err := git_model.IsBranchExist(ctx, ctx.Repo.Repository.ID, branchName) if err != nil { - if git.IsErrBranchNotExist(err) { - ctx.APIErrorNotFound(err) - } else { - ctx.APIErrorInternal(err) - } + ctx.APIErrorInternal(err) + return + } else if !exist { + ctx.APIErrorNotFound(err) return } - c, err := branch.GetCommit() + c, err := ctx.Repo.GitRepo.GetBranchCommit(branchName) if err != nil { ctx.APIErrorInternal(err) return @@ -82,7 +80,7 @@ func GetBranch(ctx *context.APIContext) { return } - br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) + br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branchName, c, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) if err != nil { ctx.APIErrorInternal(err) return @@ -157,9 +155,9 @@ func DeleteBranch(ctx *context.APIContext) { case git.IsErrBranchNotExist(err): ctx.APIErrorNotFound(err) case errors.Is(err, repo_service.ErrBranchIsDefault): - ctx.APIError(http.StatusForbidden, fmt.Errorf("can not delete default branch")) + ctx.APIError(http.StatusForbidden, errors.New("can not delete default branch")) case errors.Is(err, git_model.ErrBranchIsProtected): - ctx.APIError(http.StatusForbidden, fmt.Errorf("branch protected")) + ctx.APIError(http.StatusForbidden, errors.New("branch protected")) default: ctx.APIErrorInternal(err) } @@ -261,25 +259,19 @@ func CreateBranch(ctx *context.APIContext) { return } - branch, err := ctx.Repo.GitRepo.GetBranch(opt.BranchName) + commit, err := ctx.Repo.GitRepo.GetBranchCommit(opt.BranchName) if err != nil { ctx.APIErrorInternal(err) return } - commit, err := branch.GetCommit() + branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, opt.BranchName) if err != nil { ctx.APIErrorInternal(err) return } - branchProtection, err := git_model.GetFirstMatchProtectedBranchRule(ctx, ctx.Repo.Repository.ID, branch.Name) - if err != nil { - ctx.APIErrorInternal(err) - return - } - - br, err := convert.ToBranch(ctx, ctx.Repo.Repository, branch.Name, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) + br, err := convert.ToBranch(ctx, ctx.Repo.Repository, opt.BranchName, commit, branchProtection, ctx.Doer, ctx.Repo.IsAdmin()) if err != nil { ctx.APIErrorInternal(err) return diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go index a54225f0fd..d1652c1d51 100644 --- a/routers/api/v1/repo/collaborators.go +++ b/routers/api/v1/repo/collaborators.go @@ -181,7 +181,7 @@ func AddOrUpdateCollaborator(ctx *context.APIContext) { p := perm.AccessModeWrite if form.Permission != nil { - p = perm.ParseAccessMode(*form.Permission) + p = perm.ParseAccessMode(*form.Permission, perm.AccessModeRead, perm.AccessModeWrite, perm.AccessModeAdmin) } if err := repo_service.AddOrUpdateCollaborator(ctx, ctx.Repo.Repository, collaborator, p); err != nil { diff --git a/routers/api/v1/repo/commits.go b/routers/api/v1/repo/commits.go index 03489d777b..34a81bd7f3 100644 --- a/routers/api/v1/repo/commits.go +++ b/routers/api/v1/repo/commits.go @@ -5,7 +5,6 @@ package repo import ( - "fmt" "math" "net/http" "strconv" @@ -65,7 +64,7 @@ func GetSingleCommit(ctx *context.APIContext) { sha := ctx.PathParam("sha") if !git.IsValidRefPattern(sha) { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("no valid ref or sha: %s", sha)) + ctx.APIError(http.StatusUnprocessableEntity, "no valid ref or sha: "+sha) return } @@ -180,13 +179,7 @@ func GetAllCommits(ctx *context.APIContext) { var baseCommit *git.Commit if len(sha) == 0 { // no sha supplied - use default branch - head, err := ctx.Repo.GitRepo.GetHEADBranch() - if err != nil { - ctx.APIErrorInternal(err) - return - } - - baseCommit, err = ctx.Repo.GitRepo.GetBranchCommit(head.Name) + baseCommit, err = ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) if err != nil { ctx.APIErrorInternal(err) return diff --git a/routers/api/v1/repo/download.go b/routers/api/v1/repo/download.go index 20901badfb..acd93ecf2e 100644 --- a/routers/api/v1/repo/download.go +++ b/routers/api/v1/repo/download.go @@ -4,7 +4,6 @@ package repo import ( - "fmt" "net/http" "code.gitea.io/gitea/modules/git" @@ -23,7 +22,7 @@ func DownloadArchive(ctx *context.APIContext) { case "bundle": tp = git.ArchiveBundle default: - ctx.APIError(http.StatusBadRequest, fmt.Sprintf("Unknown archive type: %s", ballType)) + ctx.APIError(http.StatusBadRequest, "Unknown archive type: "+ballType) return } diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index 1ba71aa8a3..d8e9fde2c0 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -661,7 +661,7 @@ func UpdateFile(ctx *context.APIContext) { // "$ref": "#/responses/repoArchivedError" apiOpts := web.GetForm(ctx).(*api.UpdateFileOptions) if ctx.Repo.Repository.IsEmpty { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("repo is empty")) + ctx.APIError(http.StatusUnprocessableEntity, errors.New("repo is empty")) return } diff --git a/routers/api/v1/repo/hook_test.go b/routers/api/v1/repo/hook_test.go index 2d15c6e078..f8d61ccf00 100644 --- a/routers/api/v1/repo/hook_test.go +++ b/routers/api/v1/repo/hook_test.go @@ -23,7 +23,7 @@ func TestTestHook(t *testing.T) { contexttest.LoadRepoCommit(t, ctx) contexttest.LoadUser(t, ctx, 2) TestHook(ctx) - assert.EqualValues(t, http.StatusNoContent, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusNoContent, ctx.Resp.WrittenStatus()) unittest.AssertExistsAndLoadBean(t, &webhook.HookTask{ HookID: 1, diff --git a/routers/api/v1/repo/issue_label.go b/routers/api/v1/repo/issue_label.go index f8e14e0490..d5eee2d469 100644 --- a/routers/api/v1/repo/issue_label.go +++ b/routers/api/v1/repo/issue_label.go @@ -5,7 +5,7 @@ package repo import ( - "fmt" + "errors" "net/http" "reflect" @@ -321,7 +321,7 @@ func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption) if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { ctx.APIError(http.StatusForbidden, "write permission is required") - return nil, nil, fmt.Errorf("permission denied") + return nil, nil, errors.New("permission denied") } var ( @@ -337,12 +337,12 @@ func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption) labelNames = append(labelNames, rv.String()) default: ctx.APIError(http.StatusBadRequest, "a label must be an integer or a string") - return nil, nil, fmt.Errorf("invalid label") + return nil, nil, errors.New("invalid label") } } if len(labelIDs) > 0 && len(labelNames) > 0 { ctx.APIError(http.StatusBadRequest, "labels should be an array of strings or integers") - return nil, nil, fmt.Errorf("invalid labels") + return nil, nil, errors.New("invalid labels") } if len(labelNames) > 0 { repoLabelIDs, err := issues_model.GetLabelIDsInRepoByNames(ctx, ctx.Repo.Repository.ID, labelNames) diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go index dbb2afa920..49531f1824 100644 --- a/routers/api/v1/repo/issue_tracked_time.go +++ b/routers/api/v1/repo/issue_tracked_time.go @@ -4,6 +4,7 @@ package repo import ( + "errors" "fmt" "net/http" "time" @@ -116,7 +117,7 @@ func ListTrackedTimes(ctx *context.APIContext) { if opts.UserID == 0 { opts.UserID = ctx.Doer.ID } else { - ctx.APIError(http.StatusForbidden, fmt.Errorf("query by user not allowed; not enough rights")) + ctx.APIError(http.StatusForbidden, errors.New("query by user not allowed; not enough rights")) return } } @@ -437,7 +438,7 @@ func ListTrackedTimesByUser(ctx *context.APIContext) { } if !ctx.IsUserRepoAdmin() && !ctx.Doer.IsAdmin && ctx.Doer.ID != user.ID { - ctx.APIError(http.StatusForbidden, fmt.Errorf("query by user not allowed; not enough rights")) + ctx.APIError(http.StatusForbidden, errors.New("query by user not allowed; not enough rights")) return } @@ -545,7 +546,7 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) { if opts.UserID == 0 { opts.UserID = ctx.Doer.ID } else { - ctx.APIError(http.StatusForbidden, fmt.Errorf("query by user not allowed; not enough rights")) + ctx.APIError(http.StatusForbidden, errors.New("query by user not allowed; not enough rights")) return } } diff --git a/routers/api/v1/repo/migrate.go b/routers/api/v1/repo/migrate.go index d7508684a1..87bb269031 100644 --- a/routers/api/v1/repo/migrate.go +++ b/routers/api/v1/repo/migrate.go @@ -115,12 +115,12 @@ func Migrate(ctx *context.APIContext) { gitServiceType := convert.ToGitServiceType(form.Service) if form.Mirror && setting.Mirror.DisableNewPull { - ctx.APIError(http.StatusForbidden, fmt.Errorf("the site administrator has disabled the creation of new pull mirrors")) + ctx.APIError(http.StatusForbidden, errors.New("the site administrator has disabled the creation of new pull mirrors")) return } if setting.Repository.DisableMigrations { - ctx.APIError(http.StatusForbidden, fmt.Errorf("the site administrator has disabled migrations")) + ctx.APIError(http.StatusForbidden, errors.New("the site administrator has disabled migrations")) return } diff --git a/routers/api/v1/repo/mirror.go b/routers/api/v1/repo/mirror.go index b5f4c12c50..f11a1603c4 100644 --- a/routers/api/v1/repo/mirror.go +++ b/routers/api/v1/repo/mirror.go @@ -5,7 +5,6 @@ package repo import ( "errors" - "fmt" "net/http" "time" @@ -367,7 +366,7 @@ func CreatePushMirror(ctx *context.APIContext, mirrorOption *api.CreatePushMirro pushMirror := &repo_model.PushMirror{ RepoID: repo.ID, Repo: repo, - RemoteName: fmt.Sprintf("remote_mirror_%s", remoteSuffix), + RemoteName: "remote_mirror_" + remoteSuffix, Interval: interval, SyncOnCommit: mirrorOption.SyncOnCommit, RemoteAddress: remoteAddress, diff --git a/routers/api/v1/repo/notes.go b/routers/api/v1/repo/notes.go index dcb512256c..eb048df6fe 100644 --- a/routers/api/v1/repo/notes.go +++ b/routers/api/v1/repo/notes.go @@ -4,7 +4,7 @@ package repo import ( - "fmt" + "errors" "net/http" "code.gitea.io/gitea/modules/git" @@ -54,7 +54,7 @@ func GetNote(ctx *context.APIContext) { sha := ctx.PathParam("sha") if !git.IsValidRefPattern(sha) { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("no valid ref or sha: %s", sha)) + ctx.APIError(http.StatusUnprocessableEntity, "no valid ref or sha: "+sha) return } getNote(ctx, sha) @@ -62,7 +62,7 @@ func GetNote(ctx *context.APIContext) { func getNote(ctx *context.APIContext, identifier string) { if ctx.Repo.GitRepo == nil { - ctx.APIErrorInternal(fmt.Errorf("no open git repo")) + ctx.APIErrorInternal(errors.New("no open git repo")) return } diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index f5d0e37c65..c0b1810191 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -1054,9 +1054,9 @@ func MergePullRequest(ctx *context.APIContext) { case git.IsErrBranchNotExist(err): ctx.APIErrorNotFound(err) case errors.Is(err, repo_service.ErrBranchIsDefault): - ctx.APIError(http.StatusForbidden, fmt.Errorf("can not delete default branch")) + ctx.APIError(http.StatusForbidden, errors.New("can not delete default branch")) case errors.Is(err, git_model.ErrBranchIsProtected): - ctx.APIError(http.StatusForbidden, fmt.Errorf("branch protected")) + ctx.APIError(http.StatusForbidden, errors.New("branch protected")) default: ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/repo/pull_review.go b/routers/api/v1/repo/pull_review.go index fb35126a99..9421a052db 100644 --- a/routers/api/v1/repo/pull_review.go +++ b/routers/api/v1/repo/pull_review.go @@ -439,7 +439,7 @@ func SubmitPullReview(ctx *context.APIContext) { } if review.Type != issues_model.ReviewTypePending { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("only a pending review can be submitted")) + ctx.APIError(http.StatusUnprocessableEntity, errors.New("only a pending review can be submitted")) return } @@ -451,7 +451,7 @@ func SubmitPullReview(ctx *context.APIContext) { // if review stay pending return if reviewType == issues_model.ReviewTypePending { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("review stay pending")) + ctx.APIError(http.StatusUnprocessableEntity, errors.New("review stay pending")) return } @@ -496,7 +496,7 @@ func preparePullReviewType(ctx *context.APIContext, pr *issues_model.PullRequest case api.ReviewStateApproved: // can not approve your own PR if pr.Issue.IsPoster(ctx.Doer.ID) { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("approve your own pull is not allowed")) + ctx.APIError(http.StatusUnprocessableEntity, errors.New("approve your own pull is not allowed")) return -1, true } reviewType = issues_model.ReviewTypeApprove @@ -505,7 +505,7 @@ func preparePullReviewType(ctx *context.APIContext, pr *issues_model.PullRequest case api.ReviewStateRequestChanges: // can not reject your own PR if pr.Issue.IsPoster(ctx.Doer.ID) { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("reject your own pull is not allowed")) + ctx.APIError(http.StatusUnprocessableEntity, errors.New("reject your own pull is not allowed")) return -1, true } reviewType = issues_model.ReviewTypeReject diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go index 36fff126e1..b6f5a3ac9e 100644 --- a/routers/api/v1/repo/release.go +++ b/routers/api/v1/repo/release.go @@ -4,6 +4,7 @@ package repo import ( + "errors" "fmt" "net/http" @@ -220,7 +221,7 @@ func CreateRelease(ctx *context.APIContext) { form := web.GetForm(ctx).(*api.CreateReleaseOption) if ctx.Repo.Repository.IsEmpty { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("repo is empty")) + ctx.APIError(http.StatusUnprocessableEntity, errors.New("repo is empty")) return } rel, err := repo_model.GetRelease(ctx, ctx.Repo.Repository.ID, form.TagName) diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 787ca2672d..23046bc275 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -5,6 +5,7 @@ package repo import ( + "errors" "fmt" "net/http" "slices" @@ -711,7 +712,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err visibilityChanged = repo.IsPrivate != *opts.Private // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public if visibilityChanged && setting.Repository.ForcePrivate && !*opts.Private && !ctx.Doer.IsAdmin { - err := fmt.Errorf("cannot change private repository to public") + err := errors.New("cannot change private repository to public") ctx.APIError(http.StatusUnprocessableEntity, err) return err } @@ -780,12 +781,12 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { if newHasIssues && opts.ExternalTracker != nil && !unit_model.TypeExternalTracker.UnitGlobalDisabled() { // Check that values are valid if !validation.IsValidExternalURL(opts.ExternalTracker.ExternalTrackerURL) { - err := fmt.Errorf("External tracker URL not valid") + err := errors.New("External tracker URL not valid") ctx.APIError(http.StatusUnprocessableEntity, err) return err } if len(opts.ExternalTracker.ExternalTrackerFormat) != 0 && !validation.IsValidExternalTrackerURLFormat(opts.ExternalTracker.ExternalTrackerFormat) { - err := fmt.Errorf("External tracker URL format not valid") + err := errors.New("External tracker URL format not valid") ctx.APIError(http.StatusUnprocessableEntity, err) return err } @@ -847,7 +848,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { if newHasWiki && opts.ExternalWiki != nil && !unit_model.TypeExternalWiki.UnitGlobalDisabled() { // Check that values are valid if !validation.IsValidExternalURL(opts.ExternalWiki.ExternalWikiURL) { - err := fmt.Errorf("External wiki URL not valid") + err := errors.New("External wiki URL not valid") ctx.APIError(http.StatusUnprocessableEntity, "Invalid external wiki URL") return err } @@ -1038,7 +1039,7 @@ func updateRepoArchivedState(ctx *context.APIContext, opts api.EditRepoOption) e // archive / un-archive if opts.Archived != nil { if repo.IsMirror { - err := fmt.Errorf("repo is a mirror, cannot archive/un-archive") + err := errors.New("repo is a mirror, cannot archive/un-archive") ctx.APIError(http.StatusUnprocessableEntity, err) return err } diff --git a/routers/api/v1/repo/repo_test.go b/routers/api/v1/repo/repo_test.go index 0a63b16a99..97233f85dc 100644 --- a/routers/api/v1/repo/repo_test.go +++ b/routers/api/v1/repo/repo_test.go @@ -58,7 +58,7 @@ func TestRepoEdit(t *testing.T) { web.SetForm(ctx, &opts) Edit(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ ID: 1, }, unittest.Cond("name = ? AND is_archived = 1", *opts.Name)) @@ -78,7 +78,7 @@ func TestRepoEditNameChange(t *testing.T) { web.SetForm(ctx, &opts) Edit(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ ID: 1, diff --git a/routers/api/v1/repo/tag.go b/routers/api/v1/repo/tag.go index 2e6c1c1023..e71c6fb443 100644 --- a/routers/api/v1/repo/tag.go +++ b/routers/api/v1/repo/tag.go @@ -110,7 +110,7 @@ func GetAnnotatedTag(ctx *context.APIContext) { if tag, err := ctx.Repo.GitRepo.GetAnnotatedTag(sha); err != nil { ctx.APIError(http.StatusBadRequest, err) } else { - commit, err := tag.Commit(ctx.Repo.GitRepo) + commit, err := ctx.Repo.GitRepo.GetTagCommit(tag.Name) if err != nil { ctx.APIError(http.StatusBadRequest, err) } diff --git a/routers/api/v1/repo/transfer.go b/routers/api/v1/repo/transfer.go index 7b890c9e5c..cbf3d10c39 100644 --- a/routers/api/v1/repo/transfer.go +++ b/routers/api/v1/repo/transfer.go @@ -108,19 +108,16 @@ func Transfer(ctx *context.APIContext) { oldFullname := ctx.Repo.Repository.FullName() if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, ctx.Repo.Repository, teams); err != nil { - if repo_model.IsErrRepoTransferInProgress(err) { + switch { + case repo_model.IsErrRepoTransferInProgress(err): ctx.APIError(http.StatusConflict, err) - return - } - - if repo_model.IsErrRepoAlreadyExist(err) { + case repo_model.IsErrRepoAlreadyExist(err): ctx.APIError(http.StatusUnprocessableEntity, err) - return - } - - if errors.Is(err, user_model.ErrBlockedUser) { + case repo_service.IsRepositoryLimitReached(err): ctx.APIError(http.StatusForbidden, err) - } else { + case errors.Is(err, user_model.ErrBlockedUser): + ctx.APIError(http.StatusForbidden, err) + default: ctx.APIErrorInternal(err) } return @@ -169,6 +166,8 @@ func AcceptTransfer(ctx *context.APIContext) { ctx.APIError(http.StatusNotFound, err) case errors.Is(err, util.ErrPermissionDenied): ctx.APIError(http.StatusForbidden, err) + case repo_service.IsRepositoryLimitReached(err): + ctx.APIError(http.StatusForbidden, err) default: ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/user/gpg_key.go b/routers/api/v1/user/gpg_key.go index 504e74ae10..b76bd8a1ee 100644 --- a/routers/api/v1/user/gpg_key.go +++ b/routers/api/v1/user/gpg_key.go @@ -4,7 +4,7 @@ package user import ( - "fmt" + "errors" "net/http" "strings" @@ -135,7 +135,7 @@ func GetGPGKey(ctx *context.APIContext) { // CreateUserGPGKey creates new GPG key to given user by ID. func CreateUserGPGKey(ctx *context.APIContext, form api.CreateGPGKeyOption, uid int64) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { - ctx.APIErrorNotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited")) + ctx.APIErrorNotFound("Not Found", errors.New("gpg keys setting is not allowed to be visited")) return } @@ -205,7 +205,7 @@ func VerifyUserGPGKey(ctx *context.APIContext) { if err != nil { if asymkey_model.IsErrGPGInvalidTokenSignature(err) { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("The provided GPG key, signature and token do not match or token is out of date. Provide a valid signature for the token: %s", token)) + ctx.APIError(http.StatusUnprocessableEntity, "The provided GPG key, signature and token do not match or token is out of date. Provide a valid signature for the token: "+token) return } ctx.APIErrorInternal(err) @@ -276,7 +276,7 @@ func DeleteGPGKey(ctx *context.APIContext) { // "$ref": "#/responses/notFound" if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { - ctx.APIErrorNotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited")) + ctx.APIErrorNotFound("Not Found", errors.New("gpg keys setting is not allowed to be visited")) return } @@ -302,9 +302,9 @@ func HandleAddGPGKeyError(ctx *context.APIContext, err error, token string) { case asymkey_model.IsErrGPGKeyParsing(err): ctx.APIError(http.StatusUnprocessableEntity, err) case asymkey_model.IsErrGPGNoEmailFound(err): - ctx.APIError(http.StatusNotFound, fmt.Sprintf("None of the emails attached to the GPG key could be found. It may still be added if you provide a valid signature for the token: %s", token)) + ctx.APIError(http.StatusNotFound, "None of the emails attached to the GPG key could be found. It may still be added if you provide a valid signature for the token: "+token) case asymkey_model.IsErrGPGInvalidTokenSignature(err): - ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("The provided GPG key, signature and token do not match or token is out of date. Provide a valid signature for the token: %s", token)) + ctx.APIError(http.StatusUnprocessableEntity, "The provided GPG key, signature and token do not match or token is out of date. Provide a valid signature for the token: "+token) default: ctx.APIErrorInternal(err) } diff --git a/routers/api/v1/user/key.go b/routers/api/v1/user/key.go index 6295f4753b..628f5d6cac 100644 --- a/routers/api/v1/user/key.go +++ b/routers/api/v1/user/key.go @@ -5,7 +5,7 @@ package user import ( std_ctx "context" - "fmt" + "errors" "net/http" asymkey_model "code.gitea.io/gitea/models/asymkey" @@ -24,9 +24,10 @@ import ( // appendPrivateInformation appends the owner and key type information to api.PublicKey func appendPrivateInformation(ctx std_ctx.Context, apiKey *api.PublicKey, key *asymkey_model.PublicKey, defaultUser *user_model.User) (*api.PublicKey, error) { - if key.Type == asymkey_model.KeyTypeDeploy { + switch key.Type { + case asymkey_model.KeyTypeDeploy: apiKey.KeyType = "deploy" - } else if key.Type == asymkey_model.KeyTypeUser { + case asymkey_model.KeyTypeUser: apiKey.KeyType = "user" if defaultUser.ID == key.OwnerID { @@ -38,7 +39,7 @@ func appendPrivateInformation(ctx std_ctx.Context, apiKey *api.PublicKey, key *a } apiKey.Owner = convert.ToUser(ctx, user, user) } - } else { + default: apiKey.KeyType = "unknown" } apiKey.ReadOnly = key.Mode == perm.AccessModeRead @@ -200,7 +201,7 @@ func GetPublicKey(ctx *context.APIContext) { // CreateUserPublicKey creates new public key to given user by ID. func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid int64) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { - ctx.APIErrorNotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) + ctx.APIErrorNotFound("Not Found", errors.New("ssh keys setting is not allowed to be visited")) return } @@ -270,7 +271,7 @@ func DeletePublicKey(ctx *context.APIContext) { // "$ref": "#/responses/notFound" if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { - ctx.APIErrorNotFound("Not Found", fmt.Errorf("ssh keys setting is not allowed to be visited")) + ctx.APIErrorNotFound("Not Found", errors.New("ssh keys setting is not allowed to be visited")) return } diff --git a/routers/api/v1/utils/git.go b/routers/api/v1/utils/git.go index af672ba147..7d276d9d98 100644 --- a/routers/api/v1/utils/git.go +++ b/routers/api/v1/utils/git.go @@ -5,6 +5,7 @@ package utils import ( gocontext "context" + "errors" "fmt" "net/http" @@ -50,7 +51,7 @@ func ResolveRefOrSha(ctx *context.APIContext, ref string) string { // GetGitRefs return git references based on filter func GetGitRefs(ctx *context.APIContext, filter string) ([]*git.Reference, string, error) { if ctx.Repo.GitRepo == nil { - return nil, "", fmt.Errorf("no open git repo found in context") + return nil, "", errors.New("no open git repo found in context") } if len(filter) > 0 { filter = "refs/" + filter diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go index ce0c1b5097..532d157e35 100644 --- a/routers/api/v1/utils/hook.go +++ b/routers/api/v1/utils/hook.go @@ -4,7 +4,6 @@ package utils import ( - "fmt" "net/http" "strconv" "strings" @@ -80,7 +79,7 @@ func GetRepoHook(ctx *context.APIContext, repoID, hookID int64) (*webhook.Webhoo // write the appropriate error to `ctx`. Return whether the form is valid func checkCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption) bool { if !webhook_service.IsValidHookTaskType(form.Type) { - ctx.APIError(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid hook type: %s", form.Type)) + ctx.APIError(http.StatusUnprocessableEntity, "Invalid hook type: "+form.Type) return false } for _, name := range []string{"url", "content_type"} { diff --git a/routers/common/blockexpensive.go b/routers/common/blockexpensive.go new file mode 100644 index 0000000000..f52aa2b709 --- /dev/null +++ b/routers/common/blockexpensive.go @@ -0,0 +1,90 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package common + +import ( + "net/http" + "strings" + + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/reqctx" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/web/middleware" + + "github.com/go-chi/chi/v5" +) + +func BlockExpensive() func(next http.Handler) http.Handler { + if !setting.Service.BlockAnonymousAccessExpensive { + return nil + } + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + ret := determineRequestPriority(reqctx.FromContext(req.Context())) + if !ret.SignedIn { + if ret.Expensive || ret.LongPolling { + http.Redirect(w, req, setting.AppSubURL+"/user/login", http.StatusSeeOther) + return + } + } + next.ServeHTTP(w, req) + }) + } +} + +func isRoutePathExpensive(routePattern string) bool { + if strings.HasPrefix(routePattern, "/user/") || strings.HasPrefix(routePattern, "/login/") { + return false + } + + expensivePaths := []string{ + // code related + "/{username}/{reponame}/archive/", + "/{username}/{reponame}/blame/", + "/{username}/{reponame}/commit/", + "/{username}/{reponame}/commits/", + "/{username}/{reponame}/graph", + "/{username}/{reponame}/media/", + "/{username}/{reponame}/raw/", + "/{username}/{reponame}/src/", + + // issue & PR related (no trailing slash) + "/{username}/{reponame}/issues", + "/{username}/{reponame}/{type:issues}", + "/{username}/{reponame}/pulls", + "/{username}/{reponame}/{type:pulls}", + + // wiki + "/{username}/{reponame}/wiki/", + + // activity + "/{username}/{reponame}/activity/", + } + for _, path := range expensivePaths { + if strings.HasPrefix(routePattern, path) { + return true + } + } + return false +} + +func isRoutePathForLongPolling(routePattern string) bool { + return routePattern == "/user/events" +} + +func determineRequestPriority(reqCtx reqctx.RequestContext) (ret struct { + SignedIn bool + Expensive bool + LongPolling bool +}, +) { + chiRoutePath := chi.RouteContext(reqCtx).RoutePattern() + if _, ok := reqCtx.GetData()[middleware.ContextDataKeySignedUser].(*user_model.User); ok { + ret.SignedIn = true + } else { + ret.Expensive = isRoutePathExpensive(chiRoutePath) + ret.LongPolling = isRoutePathForLongPolling(chiRoutePath) + } + return ret +} diff --git a/routers/common/blockexpensive_test.go b/routers/common/blockexpensive_test.go new file mode 100644 index 0000000000..db5c0db7dd --- /dev/null +++ b/routers/common/blockexpensive_test.go @@ -0,0 +1,30 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package common + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBlockExpensive(t *testing.T) { + cases := []struct { + expensive bool + routePath string + }{ + {false, "/user/xxx"}, + {false, "/login/xxx"}, + {true, "/{username}/{reponame}/archive/xxx"}, + {true, "/{username}/{reponame}/graph"}, + {true, "/{username}/{reponame}/src/xxx"}, + {true, "/{username}/{reponame}/wiki/xxx"}, + {true, "/{username}/{reponame}/activity/xxx"}, + } + for _, c := range cases { + assert.Equal(t, c.expensive, isRoutePathExpensive(c.routePath), "routePath: %s", c.routePath) + } + + assert.True(t, isRoutePathForLongPolling("/user/events")) +} diff --git a/routers/common/db.go b/routers/common/db.go index cb163c867d..01c0261427 100644 --- a/routers/common/db.go +++ b/routers/common/db.go @@ -5,7 +5,7 @@ package common import ( "context" - "fmt" + "errors" "time" "code.gitea.io/gitea/models/db" @@ -25,7 +25,7 @@ func InitDBEngine(ctx context.Context) (err error) { for i := 0; i < setting.Database.DBConnectRetries; i++ { select { case <-ctx.Done(): - return fmt.Errorf("Aborted due to shutdown:\nin retry ORM engine initialization") + return errors.New("Aborted due to shutdown:\nin retry ORM engine initialization") default: } log.Info("ORM engine initialization attempt #%d/%d...", i+1, setting.Database.DBConnectRetries) diff --git a/routers/common/markup.go b/routers/common/markup.go index 60bf0dba54..4c77ff33ed 100644 --- a/routers/common/markup.go +++ b/routers/common/markup.go @@ -88,7 +88,7 @@ func RenderMarkup(ctx *context.Base, ctxRepo *context.Repository, mode, text, ur }) rctx = rctx.WithMarkupType("").WithRelativePath(filePath) // render the repo file content by its extension default: - ctx.HTTPError(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode)) + ctx.HTTPError(http.StatusUnprocessableEntity, "Unknown mode: "+mode) return } rctx = rctx.WithUseAbsoluteLink(true) diff --git a/routers/install/install.go b/routers/install/install.go index b81a5680d3..2962f3948f 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -5,7 +5,6 @@ package install import ( - "fmt" "net/http" "net/mail" "os" @@ -151,7 +150,7 @@ func Install(ctx *context.Context) { form.DisableRegistration = setting.Service.DisableRegistration form.AllowOnlyExternalRegistration = setting.Service.AllowOnlyExternalRegistration form.EnableCaptcha = setting.Service.EnableCaptcha - form.RequireSignInView = setting.Service.RequireSignInView + form.RequireSignInView = setting.Service.RequireSignInViewStrict form.DefaultKeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate form.DefaultAllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization form.DefaultEnableTimetracking = setting.Service.DefaultEnableTimetracking @@ -398,7 +397,7 @@ func SubmitInstall(ctx *context.Context) { cfg.Section("server").Key("DISABLE_SSH").SetValue("true") } else { cfg.Section("server").Key("DISABLE_SSH").SetValue("false") - cfg.Section("server").Key("SSH_PORT").SetValue(fmt.Sprint(form.SSHPort)) + cfg.Section("server").Key("SSH_PORT").SetValue(strconv.Itoa(form.SSHPort)) } if form.LFSRootPath != "" { @@ -429,10 +428,10 @@ func SubmitInstall(ctx *context.Context) { } else { cfg.Section("mailer").Key("ENABLED").SetValue("false") } - cfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").SetValue(fmt.Sprint(form.RegisterConfirm)) - cfg.Section("service").Key("ENABLE_NOTIFY_MAIL").SetValue(fmt.Sprint(form.MailNotify)) + cfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").SetValue(strconv.FormatBool(form.RegisterConfirm)) + cfg.Section("service").Key("ENABLE_NOTIFY_MAIL").SetValue(strconv.FormatBool(form.MailNotify)) - cfg.Section("server").Key("OFFLINE_MODE").SetValue(fmt.Sprint(form.OfflineMode)) + cfg.Section("server").Key("OFFLINE_MODE").SetValue(strconv.FormatBool(form.OfflineMode)) if err := system_model.SetSettings(ctx, map[string]string{ setting.Config().Picture.DisableGravatar.DynKey(): strconv.FormatBool(form.DisableGravatar), setting.Config().Picture.EnableFederatedAvatar.DynKey(): strconv.FormatBool(form.EnableFederatedAvatar), @@ -441,17 +440,17 @@ func SubmitInstall(ctx *context.Context) { return } - cfg.Section("openid").Key("ENABLE_OPENID_SIGNIN").SetValue(fmt.Sprint(form.EnableOpenIDSignIn)) - cfg.Section("openid").Key("ENABLE_OPENID_SIGNUP").SetValue(fmt.Sprint(form.EnableOpenIDSignUp)) - cfg.Section("service").Key("DISABLE_REGISTRATION").SetValue(fmt.Sprint(form.DisableRegistration)) - cfg.Section("service").Key("ALLOW_ONLY_EXTERNAL_REGISTRATION").SetValue(fmt.Sprint(form.AllowOnlyExternalRegistration)) - cfg.Section("service").Key("ENABLE_CAPTCHA").SetValue(fmt.Sprint(form.EnableCaptcha)) - cfg.Section("service").Key("REQUIRE_SIGNIN_VIEW").SetValue(fmt.Sprint(form.RequireSignInView)) - cfg.Section("service").Key("DEFAULT_KEEP_EMAIL_PRIVATE").SetValue(fmt.Sprint(form.DefaultKeepEmailPrivate)) - cfg.Section("service").Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").SetValue(fmt.Sprint(form.DefaultAllowCreateOrganization)) - cfg.Section("service").Key("DEFAULT_ENABLE_TIMETRACKING").SetValue(fmt.Sprint(form.DefaultEnableTimetracking)) - cfg.Section("service").Key("NO_REPLY_ADDRESS").SetValue(fmt.Sprint(form.NoReplyAddress)) - cfg.Section("cron.update_checker").Key("ENABLED").SetValue(fmt.Sprint(form.EnableUpdateChecker)) + cfg.Section("openid").Key("ENABLE_OPENID_SIGNIN").SetValue(strconv.FormatBool(form.EnableOpenIDSignIn)) + cfg.Section("openid").Key("ENABLE_OPENID_SIGNUP").SetValue(strconv.FormatBool(form.EnableOpenIDSignUp)) + cfg.Section("service").Key("DISABLE_REGISTRATION").SetValue(strconv.FormatBool(form.DisableRegistration)) + cfg.Section("service").Key("ALLOW_ONLY_EXTERNAL_REGISTRATION").SetValue(strconv.FormatBool(form.AllowOnlyExternalRegistration)) + cfg.Section("service").Key("ENABLE_CAPTCHA").SetValue(strconv.FormatBool(form.EnableCaptcha)) + cfg.Section("service").Key("REQUIRE_SIGNIN_VIEW").SetValue(strconv.FormatBool(form.RequireSignInView)) + cfg.Section("service").Key("DEFAULT_KEEP_EMAIL_PRIVATE").SetValue(strconv.FormatBool(form.DefaultKeepEmailPrivate)) + cfg.Section("service").Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").SetValue(strconv.FormatBool(form.DefaultAllowCreateOrganization)) + cfg.Section("service").Key("DEFAULT_ENABLE_TIMETRACKING").SetValue(strconv.FormatBool(form.DefaultEnableTimetracking)) + cfg.Section("service").Key("NO_REPLY_ADDRESS").SetValue(form.NoReplyAddress) + cfg.Section("cron.update_checker").Key("ENABLED").SetValue(strconv.FormatBool(form.EnableUpdateChecker)) cfg.Section("session").Key("PROVIDER").SetValue("file") diff --git a/routers/install/routes.go b/routers/install/routes.go index 7309a405d4..bc7a0eb48c 100644 --- a/routers/install/routes.go +++ b/routers/install/routes.go @@ -36,7 +36,7 @@ func Routes() *web.Router { func installNotFound(w http.ResponseWriter, req *http.Request) { w.Header().Add("Content-Type", "text/html; charset=utf-8") - w.Header().Add("Refresh", fmt.Sprintf("1; url=%s", setting.AppSubURL+"/")) + w.Header().Add("Refresh", "1; url="+setting.AppSubURL+"/") // do not use 30x status, because the "post-install" page needs to use 404/200 to detect if Gitea has been installed. // the fetch API could follow 30x requests to the page with 200 status. w.WriteHeader(http.StatusNotFound) diff --git a/routers/install/routes_test.go b/routers/install/routes_test.go index 2aa7f5d7b7..e8902ba3f1 100644 --- a/routers/install/routes_test.go +++ b/routers/install/routes_test.go @@ -4,6 +4,7 @@ package install import ( + "net/http" "net/http/httptest" "testing" @@ -17,20 +18,20 @@ func TestRoutes(t *testing.T) { assert.NotNil(t, r) w := httptest.NewRecorder() - req := httptest.NewRequest("GET", "/", nil) + req := httptest.NewRequest(http.MethodGet, "/", nil) r.ServeHTTP(w, req) - assert.EqualValues(t, 200, w.Code) + assert.Equal(t, 200, w.Code) assert.Contains(t, w.Body.String(), `class="page-content install"`) w = httptest.NewRecorder() - req = httptest.NewRequest("GET", "/no-such", nil) + req = httptest.NewRequest(http.MethodGet, "/no-such", nil) r.ServeHTTP(w, req) - assert.EqualValues(t, 404, w.Code) + assert.Equal(t, 404, w.Code) w = httptest.NewRecorder() - req = httptest.NewRequest("GET", "/assets/img/gitea.svg", nil) + req = httptest.NewRequest(http.MethodGet, "/assets/img/gitea.svg", nil) r.ServeHTTP(w, req) - assert.EqualValues(t, 200, w.Code) + assert.Equal(t, 200, w.Code) } func TestMain(m *testing.M) { diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go index 442d0a76c9..8b1e849e7a 100644 --- a/routers/private/hook_post_receive.go +++ b/routers/private/hook_post_receive.go @@ -14,6 +14,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/cache" + "code.gitea.io/gitea/modules/cachegroup" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" @@ -326,9 +327,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { } func loadContextCacheUser(ctx context.Context, id int64) (*user_model.User, error) { - return cache.GetWithContextCache(ctx, "hook_post_receive_user", id, func() (*user_model.User, error) { - return user_model.GetUserByID(ctx, id) - }) + return cache.GetWithContextCache(ctx, cachegroup.User, id, user_model.GetUserByID) } // handlePullRequestMerging handle pull request merging, a pull request action should push at least 1 commit diff --git a/routers/private/hook_post_receive_test.go b/routers/private/hook_post_receive_test.go index 34722f910d..ca721b16d1 100644 --- a/routers/private/hook_post_receive_test.go +++ b/routers/private/hook_post_receive_test.go @@ -43,7 +43,7 @@ func TestHandlePullRequestMerging(t *testing.T) { pr, err = issues_model.GetPullRequestByID(db.DefaultContext, pr.ID) assert.NoError(t, err) assert.True(t, pr.HasMerged) - assert.EqualValues(t, "01234567", pr.MergedCommitID) + assert.Equal(t, "01234567", pr.MergedCommitID) unittest.AssertNotExistsBean(t, &pull_model.AutoMerge{ID: autoMerge.ID}) } diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index ae23abc542..be9923c98f 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -311,13 +311,13 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r if isForcePush { log.Warn("Forbidden: User %d is not allowed to force-push to protected branch: %s in %-v", ctx.opts.UserID, branchName, repo) ctx.JSON(http.StatusForbidden, private.Response{ - UserMsg: fmt.Sprintf("Not allowed to force-push to protected branch %s", branchName), + UserMsg: "Not allowed to force-push to protected branch " + branchName, }) return } log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v", ctx.opts.UserID, branchName, repo) ctx.JSON(http.StatusForbidden, private.Response{ - UserMsg: fmt.Sprintf("Not allowed to push to protected branch %s", branchName), + UserMsg: "Not allowed to push to protected branch " + branchName, }) return } @@ -353,7 +353,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r if !allowedMerge { log.Warn("Forbidden: User %d is not allowed to push to protected branch: %s in %-v and is not allowed to merge pr #%d", ctx.opts.UserID, branchName, repo, pr.Index) ctx.JSON(http.StatusForbidden, private.Response{ - UserMsg: fmt.Sprintf("Not allowed to push to protected branch %s", branchName), + UserMsg: "Not allowed to push to protected branch " + branchName, }) return } @@ -447,10 +447,7 @@ func preReceiveFor(ctx *preReceiveContext, refFullName git.RefName) { baseBranchName := refFullName.ForBranchName() - baseBranchExist := false - if gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, baseBranchName) { - baseBranchExist = true - } + baseBranchExist := gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, baseBranchName) if !baseBranchExist { for p, v := range baseBranchName { diff --git a/routers/private/hook_verification.go b/routers/private/hook_verification.go index 7c06cf8557..57d0964ead 100644 --- a/routers/private/hook_verification.go +++ b/routers/private/hook_verification.go @@ -6,7 +6,6 @@ package private import ( "bufio" "context" - "fmt" "io" "os" @@ -113,7 +112,7 @@ type errUnverifiedCommit struct { } func (e *errUnverifiedCommit) Error() string { - return fmt.Sprintf("Unverified commit: %s", e.sha) + return "Unverified commit: " + e.sha } func isErrUnverifiedCommit(err error) bool { diff --git a/routers/private/manager.go b/routers/private/manager.go index c712bbcf21..00e52d6511 100644 --- a/routers/private/manager.go +++ b/routers/private/manager.go @@ -180,7 +180,7 @@ func AddLogger(ctx *context.PrivateContext) { writerOption.Addr, _ = opts.Config["address"].(string) writerMode.WriterOption = writerOption default: - panic(fmt.Sprintf("invalid log writer mode: %s", writerType)) + panic("invalid log writer mode: " + writerType) } writer, err := log.NewEventWriter(opts.Writer, writerType, writerMode) if err != nil { diff --git a/routers/private/serv.go b/routers/private/serv.go index ecff3b7a53..37fbc0730c 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -286,7 +286,7 @@ func ServCommand(ctx *context.PrivateContext) { repo.IsPrivate || owner.Visibility.IsPrivate() || (user != nil && user.IsRestricted) || // user will be nil if the key is a deploykey - setting.Service.RequireSignInView) { + setting.Service.RequireSignInViewStrict) { if key.Type == asymkey_model.KeyTypeDeploy { if deployKey.Mode < mode { ctx.JSON(http.StatusUnauthorized, private.Response{ diff --git a/routers/web/admin/admin_test.go b/routers/web/admin/admin_test.go index 6c38f0b509..a568c7c5c8 100644 --- a/routers/web/admin/admin_test.go +++ b/routers/web/admin/admin_test.go @@ -69,7 +69,7 @@ func TestShadowPassword(t *testing.T) { } for _, k := range kases { - assert.EqualValues(t, k.Result, shadowPassword(k.Provider, k.CfgItem)) + assert.Equal(t, k.Result, shadowPassword(k.Provider, k.CfgItem)) } } @@ -79,7 +79,7 @@ func TestSelfCheckPost(t *testing.T) { ctx, resp := contexttest.MockContext(t, "GET http://host/sub/admin/self_check?location_origin=http://frontend") SelfCheckPost(ctx) - assert.EqualValues(t, http.StatusOK, resp.Code) + assert.Equal(t, http.StatusOK, resp.Code) data := struct { Problems []string `json:"problems"` diff --git a/routers/web/admin/applications.go b/routers/web/admin/applications.go index aec6349f21..79c3a08808 100644 --- a/routers/web/admin/applications.go +++ b/routers/web/admin/applications.go @@ -4,7 +4,6 @@ package admin import ( - "fmt" "net/http" "code.gitea.io/gitea/models/auth" @@ -23,8 +22,8 @@ var ( func newOAuth2CommonHandlers() *user_setting.OAuth2CommonHandlers { return &user_setting.OAuth2CommonHandlers{ OwnerID: 0, - BasePathList: fmt.Sprintf("%s/-/admin/applications", setting.AppSubURL), - BasePathEditPrefix: fmt.Sprintf("%s/-/admin/applications/oauth2", setting.AppSubURL), + BasePathList: setting.AppSubURL + "/-/admin/applications", + BasePathEditPrefix: setting.AppSubURL + "/-/admin/applications/oauth2", TplAppEdit: tplSettingsOauth2ApplicationEdit, } } diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index f07ef98931..1de8d7e8a3 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -534,7 +534,8 @@ func createUserInContext(ctx *context.Context, tpl templates.TplName, form any, } if err := user_model.CreateUser(ctx, u, meta, overwrites); err != nil { if allowLink && (user_model.IsErrUserAlreadyExist(err) || user_model.IsErrEmailAlreadyUsed(err)) { - if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingAuto { + switch setting.OAuth2Client.AccountLinking { + case setting.OAuth2AccountLinkingAuto: var user *user_model.User user = &user_model.User{Name: u.Name} hasUser, err := user_model.GetUser(ctx, user) @@ -550,7 +551,7 @@ func createUserInContext(ctx *context.Context, tpl templates.TplName, form any, // TODO: probably we should respect 'remember' user's choice... linkAccount(ctx, user, *gothUser, true) return false // user is already created here, all redirects are handled - } else if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingLogin { + case setting.OAuth2AccountLinkingLogin: showLinkingLogin(ctx, *gothUser) return false // user will be created only after linking login } diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 277f8bed31..94a8bec565 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -155,9 +155,10 @@ func SignInOAuthCallback(ctx *context.Context) { return } if uname == "" { - if setting.OAuth2Client.Username == setting.OAuth2UsernameNickname { + switch setting.OAuth2Client.Username { + case setting.OAuth2UsernameNickname: missingFields = append(missingFields, "nickname") - } else if setting.OAuth2Client.Username == setting.OAuth2UsernamePreferredUsername { + case setting.OAuth2UsernamePreferredUsername: missingFields = append(missingFields, "preferred_username") } // else: "UserID" and "Email" have been handled above separately } diff --git a/routers/web/auth/oauth2_provider.go b/routers/web/auth/oauth2_provider.go index ff571fbf2c..1804b5b193 100644 --- a/routers/web/auth/oauth2_provider.go +++ b/routers/web/auth/oauth2_provider.go @@ -10,6 +10,7 @@ import ( "html/template" "net/http" "net/url" + "strconv" "strings" "code.gitea.io/gitea/models/auth" @@ -98,7 +99,7 @@ func InfoOAuth(ctx *context.Context) { } response := &userInfoResponse{ - Sub: fmt.Sprint(ctx.Doer.ID), + Sub: strconv.FormatInt(ctx.Doer.ID, 10), Name: ctx.Doer.DisplayName(), PreferredUsername: ctx.Doer.Name, Email: ctx.Doer.Email, @@ -171,7 +172,7 @@ func IntrospectOAuth(ctx *context.Context) { response.Scope = grant.Scope response.Issuer = setting.AppURL response.Audience = []string{app.ClientID} - response.Subject = fmt.Sprint(grant.UserID) + response.Subject = strconv.FormatInt(grant.UserID, 10) } if user, err := user_model.GetUserByID(ctx, grant.UserID); err == nil { response.Username = user.Name diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go index c3415cccac..8c2d3276a8 100644 --- a/routers/web/auth/openid.go +++ b/routers/web/auth/openid.go @@ -4,7 +4,7 @@ package auth import ( - "fmt" + "errors" "net/http" "net/url" @@ -55,13 +55,13 @@ func allowedOpenIDURI(uri string) (err error) { } } // must match one of this or be refused - return fmt.Errorf("URI not allowed by whitelist") + return errors.New("URI not allowed by whitelist") } // A blacklist match expliclty forbids for _, pat := range setting.Service.OpenIDBlacklist { if pat.MatchString(uri) { - return fmt.Errorf("URI forbidden by blacklist") + return errors.New("URI forbidden by blacklist") } } @@ -99,7 +99,7 @@ func SignInOpenIDPost(ctx *context.Context) { url, err := openid.RedirectURL(id, redirectTo, setting.AppURL) if err != nil { log.Error("Error in OpenID redirect URL: %s, %v", redirectTo, err.Error()) - ctx.RenderWithErr(fmt.Sprintf("Unable to find OpenID provider in %s", redirectTo), tplSignInOpenID, &form) + ctx.RenderWithErr("Unable to find OpenID provider in "+redirectTo, tplSignInOpenID, &form) return } diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go index 8dbde85fe6..537ad4b994 100644 --- a/routers/web/auth/password.go +++ b/routers/web/auth/password.go @@ -5,7 +5,6 @@ package auth import ( "errors" - "fmt" "net/http" "code.gitea.io/gitea/models/auth" @@ -108,14 +107,14 @@ func commonResetPassword(ctx *context.Context) (*user_model.User, *auth.TwoFacto } if len(code) == 0 { - ctx.Flash.Error(ctx.Tr("auth.invalid_code_forgot_password", fmt.Sprintf("%s/user/forgot_password", setting.AppSubURL)), true) + ctx.Flash.Error(ctx.Tr("auth.invalid_code_forgot_password", setting.AppSubURL+"/user/forgot_password"), true) return nil, nil } // Fail early, don't frustrate the user u := user_model.VerifyUserTimeLimitCode(ctx, &user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeResetPassword}, code) if u == nil { - ctx.Flash.Error(ctx.Tr("auth.invalid_code_forgot_password", fmt.Sprintf("%s/user/forgot_password", setting.AppSubURL)), true) + ctx.Flash.Error(ctx.Tr("auth.invalid_code_forgot_password", setting.AppSubURL+"/user/forgot_password"), true) return nil, nil } diff --git a/routers/web/base.go b/routers/web/base.go index a284dd0288..e43f36a97b 100644 --- a/routers/web/base.go +++ b/routers/web/base.go @@ -25,7 +25,7 @@ func avatarStorageHandler(storageSetting *setting.Storage, prefix string, objSto if storageSetting.ServeDirect() { return func(w http.ResponseWriter, req *http.Request) { - if req.Method != "GET" && req.Method != "HEAD" { + if req.Method != http.MethodGet && req.Method != http.MethodHead { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } @@ -56,7 +56,7 @@ func avatarStorageHandler(storageSetting *setting.Storage, prefix string, objSto } return func(w http.ResponseWriter, req *http.Request) { - if req.Method != "GET" && req.Method != "HEAD" { + if req.Method != http.MethodGet && req.Method != http.MethodHead { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } diff --git a/routers/web/devtest/devtest.go b/routers/web/devtest/devtest.go index 063ff42409..765931a730 100644 --- a/routers/web/devtest/devtest.go +++ b/routers/web/devtest/devtest.go @@ -4,6 +4,7 @@ package devtest import ( + "fmt" "html/template" "net/http" "path" @@ -128,6 +129,7 @@ func prepareMockDataBadgeCommitSign(ctx *context.Context) { func prepareMockDataBadgeActionsSvg(ctx *context.Context) { fontFamilyNames := strings.Split(badge.DefaultFontFamily, ",") selectedFontFamilyName := ctx.FormString("font", fontFamilyNames[0]) + selectedStyle := ctx.FormString("style", badge.DefaultStyle) var badges []badge.Badge badges = append(badges, badge.GenerateBadge("啊啊啊啊啊啊啊啊啊啊啊啊", "🌞🌞🌞🌞🌞", "green")) for r := rune(0); r < 256; r++ { @@ -141,7 +143,16 @@ func prepareMockDataBadgeActionsSvg(ctx *context.Context) { for i, b := range badges { b.IDPrefix = "devtest-" + strconv.FormatInt(int64(i), 10) + "-" b.FontFamily = selectedFontFamilyName - h, err := ctx.RenderToHTML("shared/actions/runner_badge", map[string]any{"Badge": b}) + var h template.HTML + var err error + switch selectedStyle { + case badge.StyleFlat: + h, err = ctx.RenderToHTML("shared/actions/runner_badge_flat", map[string]any{"Badge": b}) + case badge.StyleFlatSquare: + h, err = ctx.RenderToHTML("shared/actions/runner_badge_flat-square", map[string]any{"Badge": b}) + default: + err = fmt.Errorf("unknown badge style: %s", selectedStyle) + } if err != nil { ctx.ServerError("RenderToHTML", err) return @@ -151,6 +162,8 @@ func prepareMockDataBadgeActionsSvg(ctx *context.Context) { ctx.Data["BadgeSVGs"] = badgeSVGs ctx.Data["BadgeFontFamilyNames"] = fontFamilyNames ctx.Data["SelectedFontFamilyName"] = selectedFontFamilyName + ctx.Data["BadgeStyles"] = badge.GlobalVars().AllStyles + ctx.Data["SelectedStyle"] = selectedStyle } func prepareMockData(ctx *context.Context) { @@ -166,7 +179,7 @@ func prepareMockData(ctx *context.Context) { func TmplCommon(ctx *context.Context) { prepareMockData(ctx) - if ctx.Req.Method == "POST" { + if ctx.Req.Method == http.MethodPost { _ = ctx.Req.ParseForm() ctx.Flash.Info("form: "+ctx.Req.Method+" "+ctx.Req.RequestURI+""+ "Form: "+ctx.Req.Form.Encode()+""+ diff --git a/routers/web/devtest/mock_actions.go b/routers/web/devtest/mock_actions.go index 3ce75dfad2..023909aceb 100644 --- a/routers/web/devtest/mock_actions.go +++ b/routers/web/devtest/mock_actions.go @@ -4,9 +4,9 @@ package devtest import ( - "fmt" mathRand "math/rand/v2" "net/http" + "strconv" "strings" "time" @@ -38,8 +38,8 @@ func generateMockStepsLog(logCur actions.LogCursor) (stepsLog []*actions.ViewSte for i := 0; i < mockCount; i++ { logStr := mockedLogs[int(cur)%len(mockedLogs)] cur++ - logStr = strings.ReplaceAll(logStr, "{step}", fmt.Sprintf("%d", logCur.Step)) - logStr = strings.ReplaceAll(logStr, "{cursor}", fmt.Sprintf("%d", cur)) + logStr = strings.ReplaceAll(logStr, "{step}", strconv.Itoa(logCur.Step)) + logStr = strings.ReplaceAll(logStr, "{cursor}", strconv.FormatInt(cur, 10)) stepsLog = append(stepsLog, &actions.ViewStepLog{ Step: logCur.Step, Cursor: cur, diff --git a/routers/web/feed/branch.go b/routers/web/feed/branch.go index d3dae9503e..4a6fe9603d 100644 --- a/routers/web/feed/branch.go +++ b/routers/web/feed/branch.go @@ -4,7 +4,6 @@ package feed import ( - "fmt" "strings" "time" @@ -22,7 +21,7 @@ func ShowBranchFeed(ctx *context.Context, repo *repo.Repository, formatType stri return } - title := fmt.Sprintf("Latest commits for branch %s", ctx.Repo.BranchName) + title := "Latest commits for branch " + ctx.Repo.BranchName link := &feeds.Link{Href: repo.HTMLURL() + "/" + ctx.Repo.RefTypeNameSubURL()} feed := &feeds.Feed{ diff --git a/routers/web/feed/file.go b/routers/web/feed/file.go index 407e4fa2d5..026c15c43a 100644 --- a/routers/web/feed/file.go +++ b/routers/web/feed/file.go @@ -4,7 +4,6 @@ package feed import ( - "fmt" "strings" "time" @@ -33,7 +32,7 @@ func ShowFileFeed(ctx *context.Context, repo *repo.Repository, formatType string return } - title := fmt.Sprintf("Latest commits for file %s", ctx.Repo.TreePath) + title := "Latest commits for file " + ctx.Repo.TreePath link := &feeds.Link{Href: repo.HTMLURL() + "/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)} diff --git a/routers/web/goget.go b/routers/web/goget.go index 79d5c2b207..67e0bee866 100644 --- a/routers/web/goget.go +++ b/routers/web/goget.go @@ -18,7 +18,7 @@ import ( ) func goGet(ctx *context.Context) { - if ctx.Req.Method != "GET" || len(ctx.Req.URL.RawQuery) < 8 || ctx.FormString("go-get") != "1" { + if ctx.Req.Method != http.MethodGet || len(ctx.Req.URL.RawQuery) < 8 || ctx.FormString("go-get") != "1" { return } diff --git a/routers/web/nodeinfo.go b/routers/web/nodeinfo.go index f1cc7bf530..47856bf98b 100644 --- a/routers/web/nodeinfo.go +++ b/routers/web/nodeinfo.go @@ -4,7 +4,6 @@ package web import ( - "fmt" "net/http" "code.gitea.io/gitea/modules/setting" @@ -24,7 +23,7 @@ type nodeInfoLink struct { func NodeInfoLinks(ctx *context.Context) { nodeinfolinks := &nodeInfoLinks{ Links: []nodeInfoLink{{ - fmt.Sprintf("%sapi/v1/nodeinfo", setting.AppURL), + setting.AppURL + "api/v1/nodeinfo", "http://nodeinfo.diaspora.software/ns/schema/2.1", }}, } diff --git a/routers/web/org/block.go b/routers/web/org/block.go index aeb4bd51a8..60f722dd39 100644 --- a/routers/web/org/block.go +++ b/routers/web/org/block.go @@ -20,6 +20,11 @@ func BlockedUsers(ctx *context.Context) { ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsSettingsBlockedUsers"] = true + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } + shared_user.BlockedUsers(ctx, ctx.ContextUser) if ctx.Written() { return @@ -29,6 +34,11 @@ func BlockedUsers(ctx *context.Context) { } func BlockedUsersPost(ctx *context.Context) { + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } + shared_user.BlockedUsersPost(ctx, ctx.ContextUser) if ctx.Written() { return diff --git a/routers/web/org/home.go b/routers/web/org/home.go index e3c2dcf0bd..8981af1691 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -86,12 +86,6 @@ func home(ctx *context.Context, viewRepositories bool) { private := ctx.FormOptionalBool("private") ctx.Data["IsPrivate"] = private - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) - return - } - opts := &organization.FindOrgMembersOpts{ Doer: ctx.Doer, OrgID: org.ID, @@ -109,9 +103,9 @@ func home(ctx *context.Context, viewRepositories bool) { ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull ctx.Data["ShowMemberAndTeamTab"] = ctx.Org.IsMember || len(members) > 0 - prepareResult, err := shared_user.PrepareOrgHeader(ctx) + prepareResult, err := shared_user.RenderUserOrgHeader(ctx) if err != nil { - ctx.ServerError("PrepareOrgHeader", err) + ctx.ServerError("RenderUserOrgHeader", err) return } @@ -154,7 +148,7 @@ func home(ctx *context.Context, viewRepositories bool) { ctx.HTML(http.StatusOK, tplOrgHome) } -func prepareOrgProfileReadme(ctx *context.Context, prepareResult *shared_user.PrepareOrgHeaderResult) bool { +func prepareOrgProfileReadme(ctx *context.Context, prepareResult *shared_user.PrepareOwnerHeaderResult) bool { viewAs := ctx.FormString("view_as", util.Iif(ctx.Org.IsMember, "member", "public")) viewAsMember := viewAs == "member" diff --git a/routers/web/org/members.go b/routers/web/org/members.go index 7d88d6b1ad..2cbe75989a 100644 --- a/routers/web/org/members.go +++ b/routers/web/org/members.go @@ -54,9 +54,8 @@ func Members(ctx *context.Context) { return } - _, err = shared_user.PrepareOrgHeader(ctx) - if err != nil { - ctx.ServerError("PrepareOrgHeader", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } diff --git a/routers/web/org/org_labels.go b/routers/web/org/org_labels.go index ccab2131db..456ed3f01e 100644 --- a/routers/web/org/org_labels.go +++ b/routers/web/org/org_labels.go @@ -44,11 +44,12 @@ func NewLabel(ctx *context.Context) { } l := &issues_model.Label{ - OrgID: ctx.Org.Organization.ID, - Name: form.Title, - Exclusive: form.Exclusive, - Description: form.Description, - Color: form.Color, + OrgID: ctx.Org.Organization.ID, + Name: form.Title, + Exclusive: form.Exclusive, + Description: form.Description, + Color: form.Color, + ExclusiveOrder: form.ExclusiveOrder, } if err := issues_model.NewLabel(ctx, l); err != nil { ctx.ServerError("NewLabel", err) @@ -73,6 +74,7 @@ func UpdateLabel(ctx *context.Context) { l.Name = form.Title l.Exclusive = form.Exclusive + l.ExclusiveOrder = form.ExclusiveOrder l.Description = form.Description l.Color = form.Color l.SetArchived(form.IsArchived) diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go index 49f4792772..f423e9cb36 100644 --- a/routers/web/org/projects.go +++ b/routers/web/org/projects.go @@ -43,7 +43,10 @@ func MustEnableProjects(ctx *context.Context) { // Projects renders the home page of projects func Projects(ctx *context.Context) { - shared_user.PrepareContextForProfileBigAvatar(ctx) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } ctx.Data["Title"] = ctx.Tr("repo.projects") sortType := ctx.FormTrim("sort") @@ -101,7 +104,6 @@ func Projects(ctx *context.Context) { } ctx.Data["Projects"] = projects - shared_user.RenderUserHeader(ctx) if isShowClosed { ctx.Data["State"] = "closed" @@ -113,12 +115,6 @@ func Projects(ctx *context.Context) { project.RenderedContent = renderUtils.MarkdownToHtml(project.Description) } - err = shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) - return - } - numPages := 0 if total > 0 { numPages = (int(total) - 1/setting.UI.IssuePagingNum) @@ -152,11 +148,8 @@ func RenderNewProject(ctx *context.Context) { ctx.Data["PageIsViewProjects"] = true ctx.Data["HomeLink"] = ctx.ContextUser.HomeLink() ctx.Data["CancelLink"] = ctx.ContextUser.HomeLink() + "/-/projects" - shared_user.RenderUserHeader(ctx) - - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } @@ -167,7 +160,10 @@ func RenderNewProject(ctx *context.Context) { func NewProjectPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.CreateProjectForm) ctx.Data["Title"] = ctx.Tr("repo.projects.new") - shared_user.RenderUserHeader(ctx) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } if ctx.HasError() { RenderNewProject(ctx) @@ -248,7 +244,10 @@ func RenderEditProject(ctx *context.Context) { ctx.Data["CanWriteProjects"] = canWriteProjects(ctx) ctx.Data["CardTypes"] = project_model.GetCardConfig() - shared_user.RenderUserHeader(ctx) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } p, err := project_model.GetProjectByID(ctx, ctx.PathParamInt64("id")) if err != nil { @@ -282,11 +281,8 @@ func EditProjectPost(ctx *context.Context) { ctx.Data["CardTypes"] = project_model.GetCardConfig() ctx.Data["CancelLink"] = project_model.ProjectLinkForOrg(ctx.ContextUser, projectID) - shared_user.RenderUserHeader(ctx) - - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } @@ -343,14 +339,14 @@ func ViewProject(ctx *context.Context) { return } - labelIDs := issue.PrepareFilterIssueLabels(ctx, project.RepoID, project.Owner) + preparedLabelFilter := issue.PrepareFilterIssueLabels(ctx, project.RepoID, project.Owner) if ctx.Written() { return } assigneeID := ctx.FormString("assignee") opts := issues_model.IssuesOptions{ - LabelIDs: labelIDs, + LabelIDs: preparedLabelFilter.SelectedLabelIDs, AssigneeID: assigneeID, Owner: project.Owner, Doer: ctx.Doer, @@ -406,8 +402,8 @@ func ViewProject(ctx *context.Context) { } // Get the exclusive scope for every label ID - labelExclusiveScopes := make([]string, 0, len(labelIDs)) - for _, labelID := range labelIDs { + labelExclusiveScopes := make([]string, 0, len(preparedLabelFilter.SelectedLabelIDs)) + for _, labelID := range preparedLabelFilter.SelectedLabelIDs { foundExclusiveScope := false for _, label := range labels { if label.ID == labelID || label.ID == -labelID { @@ -422,7 +418,7 @@ func ViewProject(ctx *context.Context) { } for _, l := range labels { - l.LoadSelectedLabelsAfterClick(labelIDs, labelExclusiveScopes) + l.LoadSelectedLabelsAfterClick(preparedLabelFilter.SelectedLabelIDs, labelExclusiveScopes) } ctx.Data["Labels"] = labels ctx.Data["NumLabels"] = len(labels) @@ -443,11 +439,9 @@ func ViewProject(ctx *context.Context) { ctx.Data["Project"] = project ctx.Data["IssuesMap"] = issuesMap ctx.Data["Columns"] = columns - shared_user.RenderUserHeader(ctx) - err = shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index cb1c4213c9..82c3bce722 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -48,9 +48,8 @@ func Settings(ctx *context.Context) { ctx.Data["RepoAdminChangeTeamAccess"] = ctx.Org.Organization.RepoAdminChangeTeamAccess ctx.Data["ContextUser"] = ctx.ContextUser - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } @@ -170,7 +169,7 @@ func SettingsDelete(ctx *context.Context) { ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsSettingsDelete"] = true - if ctx.Req.Method == "POST" { + if ctx.Req.Method == http.MethodPost { if ctx.Org.Organization.Name != ctx.FormString("org_name") { ctx.Data["Err_OrgName"] = true ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_org_name"), tplSettingsDelete, nil) @@ -194,9 +193,8 @@ func SettingsDelete(ctx *context.Context) { return } - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } @@ -218,9 +216,8 @@ func Webhooks(ctx *context.Context) { return } - err = shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } @@ -246,9 +243,8 @@ func Labels(ctx *context.Context) { ctx.Data["PageIsOrgSettingsLabels"] = true ctx.Data["LabelTemplateFiles"] = repo_module.LabelTemplateFiles - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } diff --git a/routers/web/org/setting_oauth2.go b/routers/web/org/setting_oauth2.go index c93058477e..47f653bf88 100644 --- a/routers/web/org/setting_oauth2.go +++ b/routers/web/org/setting_oauth2.go @@ -45,9 +45,8 @@ func Applications(ctx *context.Context) { } ctx.Data["Applications"] = apps - err = shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } diff --git a/routers/web/org/setting_packages.go b/routers/web/org/setting_packages.go index 0912a9e0fd..ec80e2867c 100644 --- a/routers/web/org/setting_packages.go +++ b/routers/web/org/setting_packages.go @@ -25,9 +25,8 @@ func Packages(ctx *context.Context) { ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsSettingsPackages"] = true - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } @@ -41,9 +40,8 @@ func PackagesRuleAdd(ctx *context.Context) { ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsSettingsPackages"] = true - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } @@ -57,9 +55,8 @@ func PackagesRuleEdit(ctx *context.Context) { ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsSettingsPackages"] = true - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } @@ -99,9 +96,8 @@ func PackagesRulePreview(ctx *context.Context) { ctx.Data["PageIsOrgSettings"] = true ctx.Data["PageIsSettingsPackages"] = true - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index aeea3708b2..676c6d0c63 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -46,6 +46,10 @@ const ( // Teams render teams list page func Teams(ctx *context.Context) { + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } org := ctx.Org.Organization ctx.Data["Title"] = org.FullName ctx.Data["PageIsOrgTeams"] = true @@ -58,12 +62,6 @@ func Teams(ctx *context.Context) { } ctx.Data["Teams"] = ctx.Org.Teams - _, err := shared_user.PrepareOrgHeader(ctx) - if err != nil { - ctx.ServerError("PrepareOrgHeader", err) - return - } - ctx.HTML(http.StatusOK, tplTeams) } @@ -272,18 +270,20 @@ func TeamsRepoAction(ctx *context.Context) { // NewTeam render create new team page func NewTeam(ctx *context.Context) { + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } ctx.Data["Title"] = ctx.Org.Organization.FullName ctx.Data["PageIsOrgTeams"] = true ctx.Data["PageIsOrgTeamsNew"] = true ctx.Data["Team"] = &org_model.Team{} ctx.Data["Units"] = unit_model.Units - if err := shared_user.LoadHeaderCount(ctx); err != nil { - ctx.ServerError("LoadHeaderCount", err) - return - } ctx.HTML(http.StatusOK, tplTeamNew) } +// FIXME: TEAM-UNIT-PERMISSION: this design is not right, when a new unit is added in the future, +// admin team won't inherit the correct admin permission for the new unit. func getUnitPerms(forms url.Values, teamPermission perm.AccessMode) map[unit_model.Type]perm.AccessMode { unitPerms := make(map[unit_model.Type]perm.AccessMode) for _, ut := range unit_model.AllRepoUnitTypes { @@ -314,19 +314,14 @@ func getUnitPerms(forms url.Values, teamPermission perm.AccessMode) map[unit_mod func NewTeamPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.CreateTeamForm) includesAllRepositories := form.RepoAccess == "all" - p := perm.ParseAccessMode(form.Permission) - unitPerms := getUnitPerms(ctx.Req.Form, p) - if p < perm.AccessModeAdmin { - // if p is less than admin accessmode, then it should be general accessmode, - // so we should calculate the minial accessmode from units accessmodes. - p = unit_model.MinUnitAccessMode(unitPerms) - } + teamPermission := perm.ParseAccessMode(form.Permission, perm.AccessModeNone, perm.AccessModeAdmin) + unitPerms := getUnitPerms(ctx.Req.Form, teamPermission) t := &org_model.Team{ OrgID: ctx.Org.Organization.ID, Name: form.TeamName, Description: form.Description, - AccessMode: p, + AccessMode: teamPermission, IncludesAllRepositories: includesAllRepositories, CanCreateOrgRepo: form.CanCreateOrgRepo, } @@ -373,15 +368,15 @@ func NewTeamPost(ctx *context.Context) { // TeamMembers render team members page func TeamMembers(ctx *context.Context) { + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } + ctx.Data["Title"] = ctx.Org.Team.Name ctx.Data["PageIsOrgTeams"] = true ctx.Data["PageIsOrgTeamMembers"] = true - if err := shared_user.LoadHeaderCount(ctx); err != nil { - ctx.ServerError("LoadHeaderCount", err) - return - } - if err := ctx.Org.Team.LoadMembers(ctx); err != nil { ctx.ServerError("GetMembers", err) return @@ -401,15 +396,15 @@ func TeamMembers(ctx *context.Context) { // TeamRepositories show the repositories of team func TeamRepositories(ctx *context.Context) { + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } + ctx.Data["Title"] = ctx.Org.Team.Name ctx.Data["PageIsOrgTeams"] = true ctx.Data["PageIsOrgTeamRepos"] = true - if err := shared_user.LoadHeaderCount(ctx); err != nil { - ctx.ServerError("LoadHeaderCount", err) - return - } - repos, err := repo_model.GetTeamRepositories(ctx, &repo_model.SearchTeamRepoOptions{ TeamID: ctx.Org.Team.ID, }) @@ -466,16 +461,16 @@ func SearchTeam(ctx *context.Context) { // EditTeam render team edit page func EditTeam(ctx *context.Context) { + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } ctx.Data["Title"] = ctx.Org.Organization.FullName ctx.Data["PageIsOrgTeams"] = true if err := ctx.Org.Team.LoadUnits(ctx); err != nil { ctx.ServerError("LoadUnits", err) return } - if err := shared_user.LoadHeaderCount(ctx); err != nil { - ctx.ServerError("LoadHeaderCount", err) - return - } ctx.Data["Team"] = ctx.Org.Team ctx.Data["Units"] = unit_model.Units ctx.HTML(http.StatusOK, tplTeamNew) @@ -485,13 +480,8 @@ func EditTeam(ctx *context.Context) { func EditTeamPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.CreateTeamForm) t := ctx.Org.Team - newAccessMode := perm.ParseAccessMode(form.Permission) - unitPerms := getUnitPerms(ctx.Req.Form, newAccessMode) - if newAccessMode < perm.AccessModeAdmin { - // if newAccessMode is less than admin accessmode, then it should be general accessmode, - // so we should calculate the minial accessmode from units accessmodes. - newAccessMode = unit_model.MinUnitAccessMode(unitPerms) - } + teamPermission := perm.ParseAccessMode(form.Permission, perm.AccessModeNone, perm.AccessModeAdmin) + unitPerms := getUnitPerms(ctx.Req.Form, teamPermission) isAuthChanged := false isIncludeAllChanged := false includesAllRepositories := form.RepoAccess == "all" @@ -503,9 +493,9 @@ func EditTeamPost(ctx *context.Context) { if !t.IsOwnerTeam() { t.Name = form.TeamName - if t.AccessMode != newAccessMode { + if t.AccessMode != teamPermission { isAuthChanged = true - t.AccessMode = newAccessMode + t.AccessMode = teamPermission } if t.IncludesAllRepositories != includesAllRepositories { diff --git a/routers/web/org/worktime.go b/routers/web/org/worktime.go index 2336984825..c7b44baf7b 100644 --- a/routers/web/org/worktime.go +++ b/routers/web/org/worktime.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/modules/templates" + shared_user "code.gitea.io/gitea/routers/web/shared/user" "code.gitea.io/gitea/services/context" ) @@ -55,13 +56,14 @@ func Worktime(ctx *context.Context) { var worktimeSumResult any var err error - if worktimeBy == "milestones" { + switch worktimeBy { + case "milestones": worktimeSumResult, err = organization.GetWorktimeByMilestones(ctx.Org.Organization, unixFrom, unixTo) ctx.Data["WorktimeByMilestones"] = true - } else if worktimeBy == "members" { + case "members": worktimeSumResult, err = organization.GetWorktimeByMembers(ctx.Org.Organization, unixFrom, unixTo) ctx.Data["WorktimeByMembers"] = true - } else /* by repos */ { + default: /* by repos */ worktimeSumResult, err = organization.GetWorktimeByRepos(ctx.Org.Organization, unixFrom, unixTo) ctx.Data["WorktimeByRepos"] = true } @@ -69,6 +71,12 @@ func Worktime(ctx *context.Context) { ctx.ServerError("GetWorktime", err) return } + + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } + ctx.Data["WorktimeSumResult"] = worktimeSumResult ctx.HTML(http.StatusOK, tplByRepos) } diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go index d07d195713..5014ff52e3 100644 --- a/routers/web/repo/actions/actions.go +++ b/routers/web/repo/actions/actions.go @@ -6,6 +6,7 @@ package actions import ( "bytes" stdCtx "context" + "errors" "net/http" "slices" "strings" @@ -67,7 +68,11 @@ func List(ctx *context.Context) { ctx.Data["PageIsActions"] = true commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.Repository.DefaultBranch) - if err != nil { + if errors.Is(err, util.ErrNotExist) { + ctx.Data["NotFoundPrompt"] = ctx.Tr("repo.branch.default_branch_not_exist", ctx.Repo.Repository.DefaultBranch) + ctx.NotFound(nil) + return + } else if err != nil { ctx.ServerError("GetBranchCommit", err) return } diff --git a/routers/web/repo/actions/badge.go b/routers/web/repo/actions/badge.go index e920ecaf58..d268a8df8a 100644 --- a/routers/web/repo/actions/badge.go +++ b/routers/web/repo/actions/badge.go @@ -5,35 +5,38 @@ package actions import ( "errors" - "fmt" "net/http" "path/filepath" "strings" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/modules/badge" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/context" ) func GetWorkflowBadge(ctx *context.Context) { workflowFile := ctx.PathParam("workflow_name") - branch := ctx.Req.URL.Query().Get("branch") - if branch == "" { - branch = ctx.Repo.Repository.DefaultBranch - } - branchRef := fmt.Sprintf("refs/heads/%s", branch) - event := ctx.Req.URL.Query().Get("event") + branch := ctx.FormString("branch", ctx.Repo.Repository.DefaultBranch) + event := ctx.FormString("event") + style := ctx.FormString("style") - badge, err := getWorkflowBadge(ctx, workflowFile, branchRef, event) + branchRef := git.RefNameFromBranch(branch) + b, err := getWorkflowBadge(ctx, workflowFile, branchRef.String(), event) if err != nil { ctx.ServerError("GetWorkflowBadge", err) return } - ctx.Data["Badge"] = badge + ctx.Data["Badge"] = b ctx.RespHeader().Set("Content-Type", "image/svg+xml") - ctx.HTML(http.StatusOK, "shared/actions/runner_badge") + switch style { + case badge.StyleFlatSquare: + ctx.HTML(http.StatusOK, "shared/actions/runner_badge_flat-square") + default: // defaults to badge.StyleFlat + ctx.HTML(http.StatusOK, "shared/actions/runner_badge_flat") + } } func getWorkflowBadge(ctx *context.Context, workflowFile, branchName, event string) (badge.Badge, error) { @@ -48,7 +51,7 @@ func getWorkflowBadge(ctx *context.Context, workflowFile, branchName, event stri return badge.Badge{}, err } - color, ok := badge.StatusColorMap[run.Status] + color, ok := badge.GlobalVars().StatusColorMap[run.Status] if !ok { return badge.GenerateBadge(workflowName, "unknown status", badge.DefaultColor), nil } diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index eb6fc6ded6..2ec6389263 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -200,9 +200,9 @@ func ViewPost(ctx *context_module.Context) { } } - // TODO: "ComposeMetas" (usually for comment) is not quite right, but it is still the same as what template "RenderCommitMessage" does. + // TODO: "ComposeCommentMetas" (usually for comment) is not quite right, but it is still the same as what template "RenderCommitMessage" does. // need to be refactored together in the future - metas := ctx.Repo.Repository.ComposeMetas(ctx) + metas := ctx.Repo.Repository.ComposeCommentMetas(ctx) // the title for the "run" is from the commit message resp.State.Run.Title = run.Title @@ -508,7 +508,7 @@ func Cancel(ctx *context_module.Context) { return err } if n == 0 { - return fmt.Errorf("job has changed, try again") + return errors.New("job has changed, try again") } if n > 0 { updatedjobs = append(updatedjobs, job) diff --git a/routers/web/repo/activity.go b/routers/web/repo/activity.go index 1d809ad8e9..8232f0cc04 100644 --- a/routers/web/repo/activity.go +++ b/routers/web/repo/activity.go @@ -8,6 +8,7 @@ import ( "time" activities_model "code.gitea.io/gitea/models/activities" + "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/services/context" @@ -52,12 +53,26 @@ func Activity(ctx *context.Context) { ctx.Data["DateUntil"] = timeUntil ctx.Data["PeriodText"] = ctx.Tr("repo.activity.period." + ctx.Data["Period"].(string)) + canReadCode := ctx.Repo.CanRead(unit.TypeCode) + if canReadCode { + // GetActivityStats needs to read the default branch to get some information + branchExist, _ := git.IsBranchExist(ctx, ctx.Repo.Repository.ID, ctx.Repo.Repository.DefaultBranch) + if !branchExist { + ctx.Data["NotFoundPrompt"] = ctx.Tr("repo.branch.default_branch_not_exist", ctx.Repo.Repository.DefaultBranch) + ctx.NotFound(nil) + return + } + } + var err error - if ctx.Data["Activity"], err = activities_model.GetActivityStats(ctx, ctx.Repo.Repository, timeFrom, + // TODO: refactor these arguments to a struct + ctx.Data["Activity"], err = activities_model.GetActivityStats(ctx, ctx.Repo.Repository, timeFrom, ctx.Repo.CanRead(unit.TypeReleases), ctx.Repo.CanRead(unit.TypeIssues), ctx.Repo.CanRead(unit.TypePullRequests), - ctx.Repo.CanRead(unit.TypeCode)); err != nil { + canReadCode, + ) + if err != nil { ctx.ServerError("GetActivityStats", err) return } diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go index efd85b9452..e304633f95 100644 --- a/routers/web/repo/blame.go +++ b/routers/web/repo/blame.go @@ -8,6 +8,7 @@ import ( gotemplate "html/template" "net/http" "net/url" + "path" "strconv" "strings" @@ -15,13 +16,13 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/languagestats" "code.gitea.io/gitea/modules/highlight" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/context" - files_service "code.gitea.io/gitea/services/repository/files" ) type blameRow struct { @@ -69,7 +70,7 @@ func RefBlame(ctx *context.Context) { blob := entry.Blob() fileSize := blob.Size() ctx.Data["FileSize"] = fileSize - ctx.Data["FileName"] = blob.Name() + ctx.Data["FileTreePath"] = ctx.Repo.TreePath tplName := tplRepoViewContent if !ctx.FormBool("only_content") { @@ -234,7 +235,7 @@ func processBlameParts(ctx *context.Context, blameParts []*git.BlamePart) map[st func renderBlame(ctx *context.Context, blameParts []*git.BlamePart, commitNames map[string]*user_model.UserCommit) { repoLink := ctx.Repo.RepoLink - language, err := files_service.TryGetContentLanguage(ctx.Repo.GitRepo, ctx.Repo.CommitID, ctx.Repo.TreePath) + language, err := languagestats.GetFileLanguage(ctx, ctx.Repo.GitRepo, ctx.Repo.CommitID, ctx.Repo.TreePath) if err != nil { log.Error("Unable to get file language for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err) } @@ -285,8 +286,7 @@ func renderBlame(ctx *context.Context, blameParts []*git.BlamePart, commitNames if i != len(lines)-1 { line += "\n" } - fileName := fmt.Sprintf("%v", ctx.Data["FileName"]) - line, lexerNameForLine := highlight.Code(fileName, language, line) + line, lexerNameForLine := highlight.Code(path.Base(ctx.Repo.TreePath), language, line) // set lexer name to the first detected lexer. this is certainly suboptimal and // we should instead highlight the whole file at once diff --git a/routers/web/repo/cherry_pick.go b/routers/web/repo/cherry_pick.go index ec50e1435e..690b830bc2 100644 --- a/routers/web/repo/cherry_pick.go +++ b/routers/web/repo/cherry_pick.go @@ -6,6 +6,7 @@ package repo import ( "bytes" "errors" + "net/http" "strings" git_model "code.gitea.io/gitea/models/git" @@ -59,7 +60,7 @@ func CherryPick(ctx *context.Context) { ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",") ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL() - ctx.HTML(200, tplCherryPick) + ctx.HTML(http.StatusOK, tplCherryPick) } // CherryPickPost handles cherrypick POSTs @@ -88,7 +89,7 @@ func CherryPickPost(ctx *context.Context) { ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL() if ctx.HasError() { - ctx.HTML(200, tplCherryPick) + ctx.HTML(http.StatusOK, tplCherryPick) return } diff --git a/routers/web/repo/code_frequency.go b/routers/web/repo/code_frequency.go index e212d3b60c..2b2dd5744a 100644 --- a/routers/web/repo/code_frequency.go +++ b/routers/web/repo/code_frequency.go @@ -34,7 +34,7 @@ func CodeFrequencyData(ctx *context.Context) { ctx.Status(http.StatusAccepted) return } - ctx.ServerError("GetCodeFrequencyData", err) + ctx.ServerError("GetContributorStats", err) } else { ctx.JSON(http.StatusOK, contributorStats["total"].Weeks) } diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 3fd1eacb58..3569a356d1 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -215,13 +215,12 @@ func SearchCommits(ctx *context.Context) { // FileHistory show a file's reversions func FileHistory(ctx *context.Context) { - fileName := ctx.Repo.TreePath - if len(fileName) == 0 { + if ctx.Repo.TreePath == "" { Commits(ctx) return } - commitsCount, err := ctx.Repo.GitRepo.FileCommitsCount(ctx.Repo.RefFullName.ShortName(), fileName) // FIXME: legacy code used ShortName + commitsCount, err := ctx.Repo.GitRepo.FileCommitsCount(ctx.Repo.RefFullName.ShortName(), ctx.Repo.TreePath) if err != nil { ctx.ServerError("FileCommitsCount", err) return @@ -238,7 +237,7 @@ func FileHistory(ctx *context.Context) { commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange( git.CommitsByFileAndRangeOptions{ Revision: ctx.Repo.RefFullName.ShortName(), // FIXME: legacy code used ShortName - File: fileName, + File: ctx.Repo.TreePath, Page: page, }) if err != nil { @@ -253,7 +252,7 @@ func FileHistory(ctx *context.Context) { ctx.Data["Username"] = ctx.Repo.Owner.Name ctx.Data["Reponame"] = ctx.Repo.Repository.Name - ctx.Data["FileName"] = fileName + ctx.Data["FileTreePath"] = ctx.Repo.TreePath ctx.Data["CommitCount"] = commitsCount pager := context.NewPagination(int(commitsCount), setting.Git.CommitsRangeSize, page, 5) diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 4d4969fa87..2c36477e6a 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -938,9 +938,10 @@ func ExcerptBlob(ctx *context.Context) { RightHunkSize: rightHunkSize, }, } - if direction == "up" { + switch direction { + case "up": section.Lines = append([]*gitdiff.DiffLine{lineSection}, section.Lines...) - } else if direction == "down" { + case "down": section.Lines = append(section.Lines, lineSection) } } diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index 113622f872..c925b61151 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -160,7 +160,6 @@ func editFile(ctx *context.Context, isNewFile bool) { defer dataRc.Close() ctx.Data["FileSize"] = blob.Size() - ctx.Data["FileName"] = blob.Name() buf := make([]byte, 1024) n, _ := util.ReadAtMost(dataRc, buf) @@ -668,7 +667,7 @@ func UploadFilePost(ctx *context.Context) { } if oldBranchName != branchName { - if _, err := ctx.Repo.GitRepo.GetBranch(branchName); err == nil { + if exist, err := git_model.IsBranchExist(ctx, ctx.Repo.Repository.ID, branchName); err == nil && exist { ctx.Data["Err_NewBranchName"] = true ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchName), tplUploadFile, &form) return @@ -875,12 +874,11 @@ func GetUniquePatchBranchName(ctx *context.Context) string { prefix := ctx.Doer.LowerName + "-patch-" for i := 1; i <= 1000; i++ { branchName := fmt.Sprintf("%s%d", prefix, i) - if _, err := ctx.Repo.GitRepo.GetBranch(branchName); err != nil { - if git.IsErrBranchNotExist(err) { - return branchName - } + if exist, err := git_model.IsBranchExist(ctx, ctx.Repo.Repository.ID, branchName); err != nil { log.Error("GetUniquePatchBranchName: %v", err) return "" + } else if !exist { + return branchName } } return "" diff --git a/routers/web/repo/editor_test.go b/routers/web/repo/editor_test.go index 566db31693..89bf8f309c 100644 --- a/routers/web/repo/editor_test.go +++ b/routers/web/repo/editor_test.go @@ -36,7 +36,7 @@ func TestCleanUploadName(t *testing.T) { "..a.dotty../.folder../.name...": "..a.dotty../.folder../.name...", } for k, v := range kases { - assert.EqualValues(t, cleanUploadFileName(k), v) + assert.Equal(t, cleanUploadFileName(k), v) } } diff --git a/routers/web/repo/fork.go b/routers/web/repo/fork.go index 36e64bfee3..79f033659b 100644 --- a/routers/web/repo/fork.go +++ b/routers/web/repo/fork.go @@ -91,12 +91,17 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository { ctx.Data["CanForkToUser"] = canForkToUser ctx.Data["Orgs"] = orgs + // TODO: this message should only be shown for the "current doer" when it is selected, just like the "new repo" page. + // msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", ctx.Doer.MaxCreationLimit()) + if canForkToUser { ctx.Data["ContextUser"] = ctx.Doer + ctx.Data["CanForkRepoInNewOwner"] = true } else if len(orgs) > 0 { ctx.Data["ContextUser"] = orgs[0] + ctx.Data["CanForkRepoInNewOwner"] = true } else { - ctx.Data["CanForkRepo"] = false + ctx.Data["CanForkRepoInNewOwner"] = false ctx.Flash.Error(ctx.Tr("repo.fork_no_valid_owners"), true) return nil } @@ -120,15 +125,6 @@ func getForkRepository(ctx *context.Context) *repo_model.Repository { // Fork render repository fork page func Fork(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("new_fork") - - if ctx.Doer.CanForkRepo() { - ctx.Data["CanForkRepo"] = true - } else { - maxCreationLimit := ctx.Doer.MaxCreationLimit() - msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit) - ctx.Flash.Error(msg, true) - } - getForkRepository(ctx) if ctx.Written() { return @@ -141,7 +137,6 @@ func Fork(ctx *context.Context) { func ForkPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.CreateRepoForm) ctx.Data["Title"] = ctx.Tr("new_fork") - ctx.Data["CanForkRepo"] = true ctxUser := checkContextUser(ctx, form.UID) if ctx.Written() { diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index f93d7fc66a..61606f8c5f 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -29,7 +29,6 @@ import ( repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/context" repo_service "code.gitea.io/gitea/services/repository" @@ -78,7 +77,7 @@ func httpBase(ctx *context.Context) *serviceHandler { strings.HasSuffix(ctx.Req.URL.Path, "git-upload-archive") { isPull = true } else { - isPull = ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET" + isPull = ctx.Req.Method == http.MethodHead || ctx.Req.Method == http.MethodGet } var accessMode perm.AccessMode @@ -127,7 +126,7 @@ func httpBase(ctx *context.Context) *serviceHandler { // Only public pull don't need auth. isPublicPull := repoExist && !repo.IsPrivate && isPull var ( - askAuth = !isPublicPull || setting.Service.RequireSignInView + askAuth = !isPublicPull || setting.Service.RequireSignInViewStrict environ []string ) @@ -303,17 +302,12 @@ var ( func dummyInfoRefs(ctx *context.Context) { infoRefsOnce.Do(func() { - tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-info-refs-cache") + tmpDir, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("gitea-info-refs-cache") if err != nil { log.Error("Failed to create temp dir for git-receive-pack cache: %v", err) return } - - defer func() { - if err := util.RemoveAll(tmpDir); err != nil { - log.Error("RemoveAll: %v", err) - } - }() + defer cleanup() if err := git.InitRepository(ctx, tmpDir, true, git.Sha1ObjectFormat.Name()); err != nil { log.Error("Failed to init bare repo for git-receive-pack cache: %v", err) @@ -360,8 +354,8 @@ func setHeaderNoCache(ctx *context.Context) { func setHeaderCacheForever(ctx *context.Context) { now := time.Now().Unix() expires := now + 31536000 - ctx.Resp.Header().Set("Date", fmt.Sprintf("%d", now)) - ctx.Resp.Header().Set("Expires", fmt.Sprintf("%d", expires)) + ctx.Resp.Header().Set("Date", strconv.FormatInt(now, 10)) + ctx.Resp.Header().Set("Expires", strconv.FormatInt(expires, 10)) ctx.Resp.Header().Set("Cache-Control", "public, max-age=31536000") } @@ -394,7 +388,7 @@ func (h *serviceHandler) sendFile(ctx *context.Context, contentType, file string } ctx.Resp.Header().Set("Content-Type", contentType) - ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size())) + ctx.Resp.Header().Set("Content-Length", strconv.FormatInt(fi.Size(), 10)) // http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat ctx.Resp.Header().Set("Last-Modified", fi.ModTime().UTC().Format(http.TimeFormat)) http.ServeFile(ctx.Resp, ctx.Req, reqFile) diff --git a/routers/web/repo/githttp_test.go b/routers/web/repo/githttp_test.go index 5ba8de3d63..0164b11f66 100644 --- a/routers/web/repo/githttp_test.go +++ b/routers/web/repo/githttp_test.go @@ -37,6 +37,6 @@ func TestContainsParentDirectorySeparator(t *testing.T) { } for i := range tests { - assert.EqualValues(t, tests[i].b, containsParentDirectorySeparator(tests[i].v)) + assert.Equal(t, tests[i].b, containsParentDirectorySeparator(tests[i].v)) } } diff --git a/routers/web/repo/issue_content_history.go b/routers/web/repo/issue_content_history.go index c2c208736c..3602f4ec8a 100644 --- a/routers/web/repo/issue_content_history.go +++ b/routers/web/repo/issue_content_history.go @@ -157,15 +157,16 @@ func GetContentHistoryDetail(ctx *context.Context) { diffHTMLBuf := bytes.Buffer{} diffHTMLBuf.WriteString("") for _, it := range diff { - if it.Type == diffmatchpatch.DiffInsert { + switch it.Type { + case diffmatchpatch.DiffInsert: diffHTMLBuf.WriteString("") diffHTMLBuf.WriteString(html.EscapeString(it.Text)) diffHTMLBuf.WriteString("") - } else if it.Type == diffmatchpatch.DiffDelete { + case diffmatchpatch.DiffDelete: diffHTMLBuf.WriteString("") diffHTMLBuf.WriteString(html.EscapeString(it.Text)) diffHTMLBuf.WriteString("") - } else { + default: diffHTMLBuf.WriteString(html.EscapeString(it.Text)) } } diff --git a/routers/web/repo/issue_label.go b/routers/web/repo/issue_label.go index 62c0128f19..f9c41adbcf 100644 --- a/routers/web/repo/issue_label.go +++ b/routers/web/repo/issue_label.go @@ -111,11 +111,12 @@ func NewLabel(ctx *context.Context) { } l := &issues_model.Label{ - RepoID: ctx.Repo.Repository.ID, - Name: form.Title, - Exclusive: form.Exclusive, - Description: form.Description, - Color: form.Color, + RepoID: ctx.Repo.Repository.ID, + Name: form.Title, + Exclusive: form.Exclusive, + ExclusiveOrder: form.ExclusiveOrder, + Description: form.Description, + Color: form.Color, } if err := issues_model.NewLabel(ctx, l); err != nil { ctx.ServerError("NewLabel", err) @@ -139,6 +140,7 @@ func UpdateLabel(ctx *context.Context) { } l.Name = form.Title l.Exclusive = form.Exclusive + l.ExclusiveOrder = form.ExclusiveOrder l.Description = form.Description l.Color = form.Color diff --git a/routers/web/repo/issue_label_test.go b/routers/web/repo/issue_label_test.go index 486c2e35a2..c3fba07034 100644 --- a/routers/web/repo/issue_label_test.go +++ b/routers/web/repo/issue_label_test.go @@ -38,7 +38,7 @@ func TestInitializeLabels(t *testing.T) { contexttest.LoadRepo(t, ctx, 2) web.SetForm(ctx, &forms.InitializeLabelsForm{TemplateName: "Default"}) InitializeLabels(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ RepoID: 2, Name: "enhancement", @@ -68,7 +68,7 @@ func TestRetrieveLabels(t *testing.T) { assert.True(t, ok) if assert.Len(t, labels, len(testCase.ExpectedLabelIDs)) { for i, label := range labels { - assert.EqualValues(t, testCase.ExpectedLabelIDs[i], label.ID) + assert.Equal(t, testCase.ExpectedLabelIDs[i], label.ID) } } } @@ -84,7 +84,7 @@ func TestNewLabel(t *testing.T) { Color: "#abcdef", }) NewLabel(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ Name: "newlabel", Color: "#abcdef", @@ -104,7 +104,7 @@ func TestUpdateLabel(t *testing.T) { IsArchived: true, }) UpdateLabel(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ ID: 2, Name: "newnameforlabel", @@ -120,7 +120,7 @@ func TestDeleteLabel(t *testing.T) { contexttest.LoadRepo(t, ctx, 1) ctx.Req.Form.Set("id", "2") DeleteLabel(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) unittest.AssertNotExistsBean(t, &issues_model.Label{ID: 2}) unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{LabelID: 2}) assert.EqualValues(t, ctx.Tr("repo.issues.label_deletion_success"), ctx.Flash.SuccessMsg) @@ -134,7 +134,7 @@ func TestUpdateIssueLabel_Clear(t *testing.T) { ctx.Req.Form.Set("issue_ids", "1,3") ctx.Req.Form.Set("action", "clear") UpdateIssueLabel(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{IssueID: 1}) unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{IssueID: 3}) unittest.CheckConsistencyFor(t, &issues_model.Label{}) @@ -160,7 +160,7 @@ func TestUpdateIssueLabel_Toggle(t *testing.T) { ctx.Req.Form.Set("action", testCase.Action) ctx.Req.Form.Set("id", strconv.Itoa(int(testCase.LabelID))) UpdateIssueLabel(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) for _, issueID := range testCase.IssueIDs { if testCase.ExpectedAdd { unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issueID, LabelID: testCase.LabelID}) diff --git a/routers/web/repo/issue_list.go b/routers/web/repo/issue_list.go index 69b38c81ec..35107bc585 100644 --- a/routers/web/repo/issue_list.go +++ b/routers/web/repo/issue_list.go @@ -5,8 +5,10 @@ package repo import ( "bytes" - "fmt" + "maps" "net/http" + "slices" + "sort" "strconv" "strings" @@ -18,6 +20,7 @@ import ( "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" issue_indexer "code.gitea.io/gitea/modules/indexer/issues" + db_indexer "code.gitea.io/gitea/modules/indexer/issues/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" @@ -30,14 +33,6 @@ import ( pull_service "code.gitea.io/gitea/services/pull" ) -func issueIDsFromSearch(ctx *context.Context, keyword string, opts *issues_model.IssuesOptions) ([]int64, error) { - ids, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts)) - if err != nil { - return nil, fmt.Errorf("SearchIssues: %w", err) - } - return ids, nil -} - func retrieveProjectsForIssueList(ctx *context.Context, repo *repo_model.Repository) { ctx.Data["OpenProjects"], ctx.Data["ClosedProjects"] = retrieveProjectsInternal(ctx, repo) } @@ -459,6 +454,19 @@ func UpdateIssueStatus(ctx *context.Context) { ctx.JSONOK() } +func prepareIssueFilterExclusiveOrderScopes(ctx *context.Context, allLabels []*issues_model.Label) { + scopeSet := make(map[string]bool) + for _, label := range allLabels { + scope := label.ExclusiveScope() + if len(scope) > 0 && label.ExclusiveOrder > 0 { + scopeSet[scope] = true + } + } + scopes := slices.Collect(maps.Keys(scopeSet)) + sort.Strings(scopes) + ctx.Data["ExclusiveLabelScopes"] = scopes +} + func renderMilestones(ctx *context.Context) { // Get milestones milestones, err := db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{ @@ -481,7 +489,7 @@ func renderMilestones(ctx *context.Context) { ctx.Data["ClosedMilestones"] = closedMilestones } -func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption optional.Option[bool]) { +func prepareIssueFilterAndList(ctx *context.Context, milestoneID, projectID int64, isPullOption optional.Option[bool]) { var err error viewType := ctx.FormString("type") sortType := ctx.FormString("sort") @@ -502,7 +510,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt case "mentioned": mentionedID = ctx.Doer.ID case "assigned": - assigneeID = fmt.Sprint(ctx.Doer.ID) + assigneeID = strconv.FormatInt(ctx.Doer.ID, 10) case "review_requested": reviewRequestedID = ctx.Doer.ID case "reviewed_by": @@ -521,15 +529,18 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt mileIDs = []int64{milestoneID} } - labelIDs := issue.PrepareFilterIssueLabels(ctx, repo.ID, ctx.Repo.Owner) + preparedLabelFilter := issue.PrepareFilterIssueLabels(ctx, repo.ID, ctx.Repo.Owner) if ctx.Written() { return } + prepareIssueFilterExclusiveOrderScopes(ctx, preparedLabelFilter.AllLabels) + + var keywordMatchedIssueIDs []int64 var issueStats *issues_model.IssueStats statsOpts := &issues_model.IssuesOptions{ RepoIDs: []int64{repo.ID}, - LabelIDs: labelIDs, + LabelIDs: preparedLabelFilter.SelectedLabelIDs, MilestoneIDs: mileIDs, ProjectID: projectID, AssigneeID: assigneeID, @@ -541,7 +552,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt IssueIDs: nil, } if keyword != "" { - allIssueIDs, err := issueIDsFromSearch(ctx, keyword, statsOpts) + keywordMatchedIssueIDs, _, err = issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, statsOpts)) if err != nil { if issue_indexer.IsAvailable(ctx) { ctx.ServerError("issueIDsFromSearch", err) @@ -550,14 +561,17 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt ctx.Data["IssueIndexerUnavailable"] = true return } - statsOpts.IssueIDs = allIssueIDs + if len(keywordMatchedIssueIDs) == 0 { + // It did search with the keyword, but no issue found, just set issueStats to empty, then no need to do query again. + issueStats = &issues_model.IssueStats{} + // set keywordMatchedIssueIDs to empty slice, so we can distinguish it from "nil" + keywordMatchedIssueIDs = []int64{} + } + statsOpts.IssueIDs = keywordMatchedIssueIDs } - if keyword != "" && len(statsOpts.IssueIDs) == 0 { - // So it did search with the keyword, but no issue found. - // Just set issueStats to empty. - issueStats = &issues_model.IssueStats{} - } else { - // So it did search with the keyword, and found some issues. It needs to get issueStats of these issues. + + if issueStats == nil { + // Either it did search with the keyword, and found some issues, it needs to get issueStats of these issues. // Or the keyword is empty, so it doesn't need issueIDs as filter, just get issueStats with statsOpts. issueStats, err = issues_model.GetIssueStats(ctx, statsOpts) if err != nil { @@ -589,25 +603,21 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt ctx.Data["TotalTrackedTime"] = totalTrackedTime } - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } - - var total int - switch { - case isShowClosed.Value(): - total = int(issueStats.ClosedCount) - case !isShowClosed.Has(): - total = int(issueStats.OpenCount + issueStats.ClosedCount) - default: - total = int(issueStats.OpenCount) + // prepare pager + total := int(issueStats.OpenCount + issueStats.ClosedCount) + if isShowClosed.Has() { + total = util.Iif(isShowClosed.Value(), int(issueStats.ClosedCount), int(issueStats.OpenCount)) } + page := max(ctx.FormInt("page"), 1) pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5) + // prepare real issue list: var issues issues_model.IssueList - { - ids, err := issueIDsFromSearch(ctx, keyword, &issues_model.IssuesOptions{ + if keywordMatchedIssueIDs == nil || len(keywordMatchedIssueIDs) > 0 { + // Either it did search with the keyword, and found some issues, then keywordMatchedIssueIDs is not null, it needs to use db indexer. + // Or the keyword is empty, it also needs to usd db indexer. + // In either case, no need to use keyword anymore + searchResult, err := db_indexer.GetIndexer().FindWithIssueOptions(ctx, &issues_model.IssuesOptions{ Paginator: &db.ListOptions{ Page: pager.Paginater.Current(), PageSize: setting.UI.IssuePagingNum, @@ -622,18 +632,16 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt ProjectID: projectID, IsClosed: isShowClosed, IsPull: isPullOption, - LabelIDs: labelIDs, + LabelIDs: preparedLabelFilter.SelectedLabelIDs, SortType: sortType, + IssueIDs: keywordMatchedIssueIDs, }) if err != nil { - if issue_indexer.IsAvailable(ctx) { - ctx.ServerError("issueIDsFromSearch", err) - return - } - ctx.Data["IssueIndexerUnavailable"] = true + ctx.ServerError("DBIndexer.Search", err) return } - issues, err = issues_model.GetIssuesByIDs(ctx, ids, true) + issueIDs := issue_indexer.SearchResultToIDSlice(searchResult) + issues, err = issues_model.GetIssuesByIDs(ctx, issueIDs, true) if err != nil { ctx.ServerError("GetIssuesByIDs", err) return @@ -696,9 +704,10 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt return 0 } reviewTyp := issues_model.ReviewTypeApprove - if typ == "reject" { + switch typ { + case "reject": reviewTyp = issues_model.ReviewTypeReject - } else if typ == "waiting" { + case "waiting": reviewTyp = issues_model.ReviewTypeRequest } for _, count := range counts { @@ -727,7 +736,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt ctx.Data["IssueStats"] = issueStats ctx.Data["OpenCount"] = issueStats.OpenCount ctx.Data["ClosedCount"] = issueStats.ClosedCount - ctx.Data["SelLabelIDs"] = labelIDs + ctx.Data["SelLabelIDs"] = preparedLabelFilter.SelectedLabelIDs ctx.Data["ViewType"] = viewType ctx.Data["SortType"] = sortType ctx.Data["MilestoneID"] = milestoneID @@ -768,7 +777,7 @@ func Issues(ctx *context.Context) { ctx.Data["NewIssueChooseTemplate"] = issue_service.HasTemplatesOrContactLinks(ctx.Repo.Repository, ctx.Repo.GitRepo) } - issues(ctx, ctx.FormInt64("milestone"), ctx.FormInt64("project"), optional.Some(isPullList)) + prepareIssueFilterAndList(ctx, ctx.FormInt64("milestone"), ctx.FormInt64("project"), optional.Some(isPullList)) if ctx.Written() { return } diff --git a/routers/web/repo/issue_new.go b/routers/web/repo/issue_new.go index 9f52396414..d8863961ff 100644 --- a/routers/web/repo/issue_new.go +++ b/routers/web/repo/issue_new.go @@ -223,11 +223,11 @@ func DeleteIssue(ctx *context.Context) { } if issue.IsPull { - ctx.Redirect(fmt.Sprintf("%s/pulls", ctx.Repo.Repository.Link()), http.StatusSeeOther) + ctx.Redirect(ctx.Repo.Repository.Link()+"/pulls", http.StatusSeeOther) return } - ctx.Redirect(fmt.Sprintf("%s/issues", ctx.Repo.Repository.Link()), http.StatusSeeOther) + ctx.Redirect(ctx.Repo.Repository.Link()+"/issues", http.StatusSeeOther) } func toSet[ItemType any, KeyType comparable](slice []ItemType, keyFunc func(ItemType) KeyType) container.Set[KeyType] { diff --git a/routers/web/repo/issue_view.go b/routers/web/repo/issue_view.go index b312f1260a..3ffcdfe676 100644 --- a/routers/web/repo/issue_view.go +++ b/routers/web/repo/issue_view.go @@ -278,7 +278,7 @@ func ViewIssue(ctx *context.Context) { extIssueUnit, err := ctx.Repo.Repository.GetUnit(ctx, unit.TypeExternalTracker) if err == nil && extIssueUnit != nil { if extIssueUnit.ExternalTrackerConfig().ExternalTrackerStyle == markup.IssueNameStyleNumeric || extIssueUnit.ExternalTrackerConfig().ExternalTrackerStyle == "" { - metas := ctx.Repo.Repository.ComposeMetas(ctx) + metas := ctx.Repo.Repository.ComposeCommentMetas(ctx) metas["index"] = ctx.PathParam("index") res, err := vars.Expand(extIssueUnit.ExternalTrackerConfig().ExternalTrackerFormat, metas) if err != nil { diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go index f1d0a857ea..8a26a0dcc3 100644 --- a/routers/web/repo/milestone.go +++ b/routers/web/repo/milestone.go @@ -263,7 +263,7 @@ func MilestoneIssuesAndPulls(ctx *context.Context) { ctx.Data["Title"] = milestone.Name ctx.Data["Milestone"] = milestone - issues(ctx, milestoneID, projectID, optional.None[bool]()) + prepareIssueFilterAndList(ctx, milestoneID, projectID, optional.None[bool]()) ret := issue.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo) ctx.Data["NewIssueChooseTemplate"] = len(ret.IssueTemplates) > 0 diff --git a/routers/web/repo/patch.go b/routers/web/repo/patch.go index 120b3469f6..ca346b7e6c 100644 --- a/routers/web/repo/patch.go +++ b/routers/web/repo/patch.go @@ -4,6 +4,7 @@ package repo import ( + "net/http" "strings" git_model "code.gitea.io/gitea/models/git" @@ -39,7 +40,7 @@ func NewDiffPatch(ctx *context.Context) { ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",") ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL() - ctx.HTML(200, tplPatchFile) + ctx.HTML(http.StatusOK, tplPatchFile) } // NewDiffPatchPost response for sending patch page @@ -62,7 +63,7 @@ func NewDiffPatchPost(ctx *context.Context) { ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",") if ctx.HasError() { - ctx.HTML(200, tplPatchFile) + ctx.HTML(http.StatusOK, tplPatchFile) return } diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go index 6810025c6f..0bf1f64d09 100644 --- a/routers/web/repo/projects.go +++ b/routers/web/repo/projects.go @@ -313,13 +313,13 @@ func ViewProject(ctx *context.Context) { return } - labelIDs := issue.PrepareFilterIssueLabels(ctx, ctx.Repo.Repository.ID, ctx.Repo.Owner) + preparedLabelFilter := issue.PrepareFilterIssueLabels(ctx, ctx.Repo.Repository.ID, ctx.Repo.Owner) assigneeID := ctx.FormString("assignee") issuesMap, err := project_service.LoadIssuesFromProject(ctx, project, &issues_model.IssuesOptions{ RepoIDs: []int64{ctx.Repo.Repository.ID}, - LabelIDs: labelIDs, + LabelIDs: preparedLabelFilter.SelectedLabelIDs, AssigneeID: assigneeID, }) if err != nil { @@ -381,8 +381,8 @@ func ViewProject(ctx *context.Context) { } // Get the exclusive scope for every label ID - labelExclusiveScopes := make([]string, 0, len(labelIDs)) - for _, labelID := range labelIDs { + labelExclusiveScopes := make([]string, 0, len(preparedLabelFilter.SelectedLabelIDs)) + for _, labelID := range preparedLabelFilter.SelectedLabelIDs { foundExclusiveScope := false for _, label := range labels { if label.ID == labelID || label.ID == -labelID { @@ -397,7 +397,7 @@ func ViewProject(ctx *context.Context) { } for _, l := range labels { - l.LoadSelectedLabelsAfterClick(labelIDs, labelExclusiveScopes) + l.LoadSelectedLabelsAfterClick(preparedLabelFilter.SelectedLabelIDs, labelExclusiveScopes) } ctx.Data["Labels"] = labels ctx.Data["NumLabels"] = len(labels) diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go index fb92d24394..929e131d61 100644 --- a/routers/web/repo/pull_review.go +++ b/routers/web/repo/pull_review.go @@ -209,11 +209,12 @@ func renderConversation(ctx *context.Context, comment *issues_model.Comment, ori return user_service.CanBlockUser(ctx, ctx.Doer, blocker, blockee) } - if origin == "diff" { + switch origin { + case "diff": ctx.HTML(http.StatusOK, tplDiffConversation) - } else if origin == "timeline" { + case "timeline": ctx.HTML(http.StatusOK, tplTimelineConversation) - } else { + default: ctx.HTTPError(http.StatusBadRequest, "Unknown origin: "+origin) } } diff --git a/routers/web/repo/recent_commits.go b/routers/web/repo/recent_commits.go index 228eb0dbac..2660116062 100644 --- a/routers/web/repo/recent_commits.go +++ b/routers/web/repo/recent_commits.go @@ -4,12 +4,10 @@ package repo import ( - "errors" "net/http" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/services/context" - contributors_service "code.gitea.io/gitea/services/repository" ) const ( @@ -26,16 +24,3 @@ func RecentCommits(ctx *context.Context) { ctx.HTML(http.StatusOK, tplRecentCommits) } - -// RecentCommitsData returns JSON of recent commits data -func RecentCommitsData(ctx *context.Context) { - if contributorStats, err := contributors_service.GetContributorStats(ctx, ctx.Cache, ctx.Repo.Repository, ctx.Repo.Repository.DefaultBranch); err != nil { - if errors.Is(err, contributors_service.ErrAwaitGeneration) { - ctx.Status(http.StatusAccepted) - return - } - ctx.ServerError("RecentCommitsData", err) - } else { - ctx.JSON(http.StatusOK, contributorStats["total"].Weeks) - } -} diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 54b7448a89..ee112b83f2 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -87,17 +87,13 @@ func checkContextUser(ctx *context.Context, uid int64) *user_model.User { return nil } - if !ctx.Doer.IsAdmin { - orgsAvailable := []*organization.Organization{} - for i := 0; i < len(orgs); i++ { - if orgs[i].CanCreateRepo() { - orgsAvailable = append(orgsAvailable, orgs[i]) - } + var orgsAvailable []*organization.Organization + for i := 0; i < len(orgs); i++ { + if ctx.Doer.CanCreateRepoIn(orgs[i].AsUser()) { + orgsAvailable = append(orgsAvailable, orgs[i]) } - ctx.Data["Orgs"] = orgsAvailable - } else { - ctx.Data["Orgs"] = orgs } + ctx.Data["Orgs"] = orgsAvailable // Not equal means current user is an organization. if uid == ctx.Doer.ID || uid == 0 { @@ -154,7 +150,7 @@ func createCommon(ctx *context.Context) { ctx.Data["Licenses"] = repo_module.Licenses ctx.Data["Readmes"] = repo_module.Readmes ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate - ctx.Data["CanCreateRepoInDoer"] = ctx.Doer.CanCreateRepo() + ctx.Data["CanCreateRepoInDoer"] = ctx.Doer.CanCreateRepoIn(ctx.Doer) ctx.Data["MaxCreationLimitOfDoer"] = ctx.Doer.MaxCreationLimit() ctx.Data["SupportedObjectFormats"] = git.DefaultFeatures().SupportedObjectFormats ctx.Data["DefaultObjectFormat"] = git.Sha1ObjectFormat @@ -305,11 +301,15 @@ func CreatePost(ctx *context.Context) { } func handleActionError(ctx *context.Context, err error) { - if errors.Is(err, user_model.ErrBlockedUser) { + switch { + case errors.Is(err, user_model.ErrBlockedUser): ctx.Flash.Error(ctx.Tr("repo.action.blocked_user")) - } else if errors.Is(err, util.ErrPermissionDenied) { + case repo_service.IsRepositoryLimitReached(err): + limit := err.(repo_service.LimitReachedError).Limit + ctx.Flash.Error(ctx.TrN(limit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", limit)) + case errors.Is(err, util.ErrPermissionDenied): ctx.HTTPError(http.StatusNotFound) - } else { + default: ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.PathParam("action")), err) } } diff --git a/routers/web/repo/setting/lfs.go b/routers/web/repo/setting/lfs.go index 655291d25c..a065620b2b 100644 --- a/routers/web/repo/setting/lfs.go +++ b/routers/web/repo/setting/lfs.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/attribute" "code.gitea.io/gitea/modules/git/pipeline" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" @@ -109,17 +110,13 @@ func LFSLocks(ctx *context.Context) { } // Clone base repo. - tmpBasePath, err := repo_module.CreateTemporaryPath("locks") + tmpBasePath, cleanup, err := repo_module.CreateTemporaryPath("locks") if err != nil { log.Error("Failed to create temporary path: %v", err) ctx.ServerError("LFSLocks", err) return } - defer func() { - if err := repo_module.RemoveTemporaryPath(tmpBasePath); err != nil { - log.Error("LFSLocks: RemoveTemporaryPath: %v", err) - } - }() + defer cleanup() if err := git.Clone(ctx, ctx.Repo.Repository.RepoPath(), tmpBasePath, git.CloneRepoOptions{ Bare: true, @@ -138,39 +135,24 @@ func LFSLocks(ctx *context.Context) { } defer gitRepo.Close() - filenames := make([]string, len(lfsLocks)) - - for i, lock := range lfsLocks { - filenames[i] = lock.Path - } - - if err := gitRepo.ReadTreeToIndex(ctx.Repo.Repository.DefaultBranch); err != nil { - log.Error("Unable to read the default branch to the index: %s (%v)", ctx.Repo.Repository.DefaultBranch, err) - ctx.ServerError("LFSLocks", fmt.Errorf("unable to read the default branch to the index: %s (%w)", ctx.Repo.Repository.DefaultBranch, err)) - return - } - - name2attribute2info, err := gitRepo.CheckAttribute(git.CheckAttributeOpts{ - Attributes: []string{"lockable"}, - Filenames: filenames, - CachedOnly: true, - }) + checker, err := attribute.NewBatchChecker(gitRepo, ctx.Repo.Repository.DefaultBranch, []string{attribute.Lockable}) if err != nil { log.Error("Unable to check attributes in %s (%v)", tmpBasePath, err) ctx.ServerError("LFSLocks", err) return } + defer checker.Close() lockables := make([]bool, len(lfsLocks)) + filenames := make([]string, len(lfsLocks)) for i, lock := range lfsLocks { - attribute2info, has := name2attribute2info[lock.Path] - if !has { + filenames[i] = lock.Path + attrs, err := checker.CheckPath(lock.Path) + if err != nil { + log.Error("Unable to check attributes in %s: %s (%v)", tmpBasePath, lock.Path, err) continue } - if attribute2info["lockable"] != "set" { - continue - } - lockables[i] = true + lockables[i] = attrs.Get(attribute.Lockable).ToBool().Value() } ctx.Data["Lockables"] = lockables diff --git a/routers/web/repo/setting/protected_branch.go b/routers/web/repo/setting/protected_branch.go index 75de2ba1e7..f241242f02 100644 --- a/routers/web/repo/setting/protected_branch.go +++ b/routers/web/repo/setting/protected_branch.go @@ -8,6 +8,7 @@ import ( "fmt" "net/http" "net/url" + "strconv" "strings" "time" @@ -110,7 +111,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) { var protectBranch *git_model.ProtectedBranch if f.RuleName == "" { ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_rule_name")) - ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit", ctx.Repo.RepoLink)) + ctx.Redirect(ctx.Repo.RepoLink + "/settings/branches/edit") return } @@ -283,32 +284,32 @@ func SettingsProtectedBranchPost(ctx *context.Context) { func DeleteProtectedBranchRulePost(ctx *context.Context) { ruleID := ctx.PathParamInt64("id") if ruleID <= 0 { - ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID))) - ctx.JSONRedirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) + ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", strconv.FormatInt(ruleID, 10))) + ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches") return } rule, err := git_model.GetProtectedBranchRuleByID(ctx, ctx.Repo.Repository.ID, ruleID) if err != nil { - ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID))) - ctx.JSONRedirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) + ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", strconv.FormatInt(ruleID, 10))) + ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches") return } if rule == nil { - ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID))) - ctx.JSONRedirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) + ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", strconv.FormatInt(ruleID, 10))) + ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches") return } if err := git_model.DeleteProtectedBranch(ctx, ctx.Repo.Repository, ruleID); err != nil { ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", rule.RuleName)) - ctx.JSONRedirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) + ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches") return } ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success", rule.RuleName)) - ctx.JSONRedirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) + ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches") } func UpdateBranchProtectionPriories(ctx *context.Context) { @@ -332,7 +333,7 @@ func RenameBranchPost(ctx *context.Context) { if ctx.HasError() { ctx.Flash.Error(ctx.GetErrMsg()) - ctx.Redirect(fmt.Sprintf("%s/branches", ctx.Repo.RepoLink)) + ctx.Redirect(ctx.Repo.RepoLink + "/branches") return } @@ -341,13 +342,13 @@ func RenameBranchPost(ctx *context.Context) { switch { case repo_model.IsErrUserDoesNotHaveAccessToRepo(err): ctx.Flash.Error(ctx.Tr("repo.branch.rename_default_or_protected_branch_error")) - ctx.Redirect(fmt.Sprintf("%s/branches", ctx.Repo.RepoLink)) + ctx.Redirect(ctx.Repo.RepoLink + "/branches") case git_model.IsErrBranchAlreadyExists(err): ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.To)) - ctx.Redirect(fmt.Sprintf("%s/branches", ctx.Repo.RepoLink)) + ctx.Redirect(ctx.Repo.RepoLink + "/branches") case errors.Is(err, git_model.ErrBranchIsProtected): ctx.Flash.Error(ctx.Tr("repo.branch.rename_protected_branch_failed")) - ctx.Redirect(fmt.Sprintf("%s/branches", ctx.Repo.RepoLink)) + ctx.Redirect(ctx.Repo.RepoLink + "/branches") default: ctx.ServerError("RenameBranch", err) } @@ -356,16 +357,16 @@ func RenameBranchPost(ctx *context.Context) { if msg == "target_exist" { ctx.Flash.Error(ctx.Tr("repo.settings.rename_branch_failed_exist", form.To)) - ctx.Redirect(fmt.Sprintf("%s/branches", ctx.Repo.RepoLink)) + ctx.Redirect(ctx.Repo.RepoLink + "/branches") return } if msg == "from_not_exist" { ctx.Flash.Error(ctx.Tr("repo.settings.rename_branch_failed_not_exist", form.From)) - ctx.Redirect(fmt.Sprintf("%s/branches", ctx.Repo.RepoLink)) + ctx.Redirect(ctx.Repo.RepoLink + "/branches") return } ctx.Flash.Success(ctx.Tr("repo.settings.rename_branch_success", form.From, form.To)) - ctx.Redirect(fmt.Sprintf("%s/branches", ctx.Repo.RepoLink)) + ctx.Redirect(ctx.Repo.RepoLink + "/branches") } diff --git a/routers/web/repo/setting/public_access.go b/routers/web/repo/setting/public_access.go new file mode 100644 index 0000000000..368d34294a --- /dev/null +++ b/routers/web/repo/setting/public_access.go @@ -0,0 +1,155 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "net/http" + "slices" + "strconv" + + "code.gitea.io/gitea/models/perm" + "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/services/context" +) + +const tplRepoSettingsPublicAccess templates.TplName = "repo/settings/public_access" + +func parsePublicAccessMode(permission string, allowed []string) (ret struct { + AnonymousAccessMode, EveryoneAccessMode perm.AccessMode +}, +) { + ret.AnonymousAccessMode = perm.AccessModeNone + ret.EveryoneAccessMode = perm.AccessModeNone + + // if site admin forces repositories to be private, then do not allow any other access mode, + // otherwise the "force private" setting would be bypassed + if setting.Repository.ForcePrivate { + return ret + } + if !slices.Contains(allowed, permission) { + return ret + } + switch permission { + case paAnonymousRead: + ret.AnonymousAccessMode = perm.AccessModeRead + case paEveryoneRead: + ret.EveryoneAccessMode = perm.AccessModeRead + case paEveryoneWrite: + ret.EveryoneAccessMode = perm.AccessModeWrite + } + return ret +} + +const ( + paNotSet = "not-set" + paAnonymousRead = "anonymous-read" + paEveryoneRead = "everyone-read" + paEveryoneWrite = "everyone-write" +) + +type repoUnitPublicAccess struct { + UnitType unit.Type + FormKey string + DisplayName string + PublicAccessTypes []string + UnitPublicAccess string +} + +func repoUnitPublicAccesses(ctx *context.Context) []*repoUnitPublicAccess { + accesses := []*repoUnitPublicAccess{ + { + UnitType: unit.TypeCode, + DisplayName: ctx.Locale.TrString("repo.code"), + PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead}, + }, + { + UnitType: unit.TypeIssues, + DisplayName: ctx.Locale.TrString("issues"), + PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead}, + }, + { + UnitType: unit.TypePullRequests, + DisplayName: ctx.Locale.TrString("pull_requests"), + PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead}, + }, + { + UnitType: unit.TypeReleases, + DisplayName: ctx.Locale.TrString("repo.releases"), + PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead}, + }, + { + UnitType: unit.TypeWiki, + DisplayName: ctx.Locale.TrString("repo.wiki"), + PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead, paEveryoneWrite}, + }, + { + UnitType: unit.TypeProjects, + DisplayName: ctx.Locale.TrString("repo.projects"), + PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead}, + }, + { + UnitType: unit.TypePackages, + DisplayName: ctx.Locale.TrString("repo.packages"), + PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead}, + }, + { + UnitType: unit.TypeActions, + DisplayName: ctx.Locale.TrString("repo.actions"), + PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead}, + }, + } + for _, ua := range accesses { + ua.FormKey = "repo-unit-access-" + strconv.Itoa(int(ua.UnitType)) + for _, u := range ctx.Repo.Repository.Units { + if u.Type == ua.UnitType { + ua.UnitPublicAccess = paNotSet + switch { + case u.EveryoneAccessMode == perm.AccessModeWrite: + ua.UnitPublicAccess = paEveryoneWrite + case u.EveryoneAccessMode == perm.AccessModeRead: + ua.UnitPublicAccess = paEveryoneRead + case u.AnonymousAccessMode == perm.AccessModeRead: + ua.UnitPublicAccess = paAnonymousRead + } + break + } + } + } + return slices.DeleteFunc(accesses, func(ua *repoUnitPublicAccess) bool { + return ua.UnitPublicAccess == "" + }) +} + +func PublicAccess(ctx *context.Context) { + ctx.Data["PageIsSettingsPublicAccess"] = true + ctx.Data["RepoUnitPublicAccesses"] = repoUnitPublicAccesses(ctx) + ctx.Data["GlobalForcePrivate"] = setting.Repository.ForcePrivate + if setting.Repository.ForcePrivate { + ctx.Flash.Error(ctx.Tr("form.repository_force_private"), true) + } + ctx.HTML(http.StatusOK, tplRepoSettingsPublicAccess) +} + +func PublicAccessPost(ctx *context.Context) { + accesses := repoUnitPublicAccesses(ctx) + for _, ua := range accesses { + formVal := ctx.FormString(ua.FormKey) + parsed := parsePublicAccessMode(formVal, ua.PublicAccessTypes) + err := repo.UpdateRepoUnitPublicAccess(ctx, &repo.RepoUnit{ + RepoID: ctx.Repo.Repository.ID, + Type: ua.UnitType, + AnonymousAccessMode: parsed.AnonymousAccessMode, + EveryoneAccessMode: parsed.EveryoneAccessMode, + }) + if err != nil { + ctx.ServerError("UpdateRepoUnitPublicAccess", err) + return + } + } + ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) + ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/public_access") +} diff --git a/routers/web/repo/setting/secrets.go b/routers/web/repo/setting/secrets.go index 46cb875f9b..c6e2d18249 100644 --- a/routers/web/repo/setting/secrets.go +++ b/routers/web/repo/setting/secrets.go @@ -44,9 +44,8 @@ func getSecretsCtx(ctx *context.Context) (*secretsCtx, error) { } if ctx.Data["PageIsOrgSettings"] == true { - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return nil, nil } return &secretsCtx{ diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index e8da443b67..5552a8726c 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -6,14 +6,12 @@ package setting import ( "errors" - "fmt" "net/http" "strings" "time" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" - "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" unit_model "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -37,6 +35,8 @@ import ( mirror_service "code.gitea.io/gitea/services/mirror" repo_service "code.gitea.io/gitea/services/repository" wiki_service "code.gitea.io/gitea/services/wiki" + + "xorm.io/xorm/convert" ) const ( @@ -48,15 +48,6 @@ const ( tplDeployKeys templates.TplName = "repo/settings/deploy_keys" ) -func parseEveryoneAccessMode(permission string, allowed ...perm.AccessMode) perm.AccessMode { - // if site admin forces repositories to be private, then do not allow any other access mode, - // otherwise the "force private" setting would be bypassed - if setting.Repository.ForcePrivate { - return perm.AccessModeNone - } - return perm.ParseAccessMode(permission, allowed...) -} - // SettingsCtxData is a middleware that sets all the general context data for the // settings template. func SettingsCtxData(ctx *context.Context) { @@ -68,6 +59,7 @@ func SettingsCtxData(ctx *context.Context) { ctx.Data["DisableNewPushMirrors"] = setting.Mirror.DisableNewPush ctx.Data["DefaultMirrorInterval"] = setting.Mirror.DefaultInterval ctx.Data["MinimumMirrorInterval"] = setting.Mirror.MinInterval + ctx.Data["CanConvertFork"] = ctx.Repo.Repository.IsFork && ctx.Doer.CanCreateRepoIn(ctx.Repo.Repository.Owner) signing, _ := asymkey_service.SigningKey(ctx, ctx.Repo.Repository.RepoPath()) ctx.Data["SigningKeyAvailable"] = len(signing) > 0 @@ -482,7 +474,7 @@ func handleSettingsPostPushMirrorAdd(ctx *context.Context) { m := &repo_model.PushMirror{ RepoID: repo.ID, Repo: repo, - RemoteName: fmt.Sprintf("remote_mirror_%s", remoteSuffix), + RemoteName: "remote_mirror_" + remoteSuffix, SyncOnCommit: form.PushMirrorSyncOnCommit, Interval: interval, RemoteAddress: remoteAddress, @@ -504,6 +496,17 @@ func handleSettingsPostPushMirrorAdd(ctx *context.Context) { ctx.Redirect(repo.Link() + "/settings") } +func newRepoUnit(repo *repo_model.Repository, unitType unit_model.Type, config convert.Conversion) repo_model.RepoUnit { + repoUnit := repo_model.RepoUnit{RepoID: repo.ID, Type: unitType, Config: config} + for _, u := range repo.Units { + if u.Type == unitType { + repoUnit.EveryoneAccessMode = u.EveryoneAccessMode + repoUnit.AnonymousAccessMode = u.AnonymousAccessMode + } + } + return repoUnit +} + func handleSettingsPostAdvanced(ctx *context.Context) { form := web.GetForm(ctx).(*forms.RepoSettingForm) repo := ctx.Repo.Repository @@ -521,11 +524,7 @@ func handleSettingsPostAdvanced(ctx *context.Context) { } if form.EnableCode && !unit_model.TypeCode.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeCode, - EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultCodeEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead), - }) + units = append(units, newRepoUnit(repo, unit_model.TypeCode, nil)) } else if !unit_model.TypeCode.UnitGlobalDisabled() { deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeCode) } @@ -537,21 +536,12 @@ func handleSettingsPostAdvanced(ctx *context.Context) { return } - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeExternalWiki, - Config: &repo_model.ExternalWikiConfig{ - ExternalWikiURL: form.ExternalWikiURL, - }, - }) + units = append(units, newRepoUnit(repo, unit_model.TypeExternalWiki, &repo_model.ExternalWikiConfig{ + ExternalWikiURL: form.ExternalWikiURL, + })) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki) } else if form.EnableWiki && !form.EnableExternalWiki && !unit_model.TypeWiki.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeWiki, - Config: new(repo_model.UnitConfig), - EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultWikiEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead, perm.AccessModeWrite), - }) + units = append(units, newRepoUnit(repo, unit_model.TypeWiki, new(repo_model.UnitConfig))) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki) } else { if !unit_model.TypeExternalWiki.UnitGlobalDisabled() { @@ -580,28 +570,19 @@ func handleSettingsPostAdvanced(ctx *context.Context) { ctx.Redirect(repo.Link() + "/settings") return } - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeExternalTracker, - Config: &repo_model.ExternalTrackerConfig{ - ExternalTrackerURL: form.ExternalTrackerURL, - ExternalTrackerFormat: form.TrackerURLFormat, - ExternalTrackerStyle: form.TrackerIssueStyle, - ExternalTrackerRegexpPattern: form.ExternalTrackerRegexpPattern, - }, - }) + units = append(units, newRepoUnit(repo, unit_model.TypeExternalTracker, &repo_model.ExternalTrackerConfig{ + ExternalTrackerURL: form.ExternalTrackerURL, + ExternalTrackerFormat: form.TrackerURLFormat, + ExternalTrackerStyle: form.TrackerIssueStyle, + ExternalTrackerRegexpPattern: form.ExternalTrackerRegexpPattern, + })) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues) } else if form.EnableIssues && !form.EnableExternalTracker && !unit_model.TypeIssues.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeIssues, - Config: &repo_model.IssuesConfig{ - EnableTimetracker: form.EnableTimetracker, - AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, - EnableDependencies: form.EnableIssueDependencies, - }, - EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultIssuesEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead), - }) + units = append(units, newRepoUnit(repo, unit_model.TypeIssues, &repo_model.IssuesConfig{ + EnableTimetracker: form.EnableTimetracker, + AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, + EnableDependencies: form.EnableIssueDependencies, + })) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker) } else { if !unit_model.TypeExternalTracker.UnitGlobalDisabled() { @@ -613,63 +594,46 @@ func handleSettingsPostAdvanced(ctx *context.Context) { } if form.EnableProjects && !unit_model.TypeProjects.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeProjects, - Config: &repo_model.ProjectsConfig{ - ProjectsMode: repo_model.ProjectsMode(form.ProjectsMode), - }, - }) + units = append(units, newRepoUnit(repo, unit_model.TypeProjects, &repo_model.ProjectsConfig{ + ProjectsMode: repo_model.ProjectsMode(form.ProjectsMode), + })) } else if !unit_model.TypeProjects.UnitGlobalDisabled() { deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects) } if form.EnableReleases && !unit_model.TypeReleases.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeReleases, - }) + units = append(units, newRepoUnit(repo, unit_model.TypeReleases, nil)) } else if !unit_model.TypeReleases.UnitGlobalDisabled() { deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeReleases) } if form.EnablePackages && !unit_model.TypePackages.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypePackages, - }) + units = append(units, newRepoUnit(repo, unit_model.TypePackages, nil)) } else if !unit_model.TypePackages.UnitGlobalDisabled() { deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePackages) } if form.EnableActions && !unit_model.TypeActions.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeActions, - }) + units = append(units, newRepoUnit(repo, unit_model.TypeActions, nil)) } else if !unit_model.TypeActions.UnitGlobalDisabled() { deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeActions) } if form.EnablePulls && !unit_model.TypePullRequests.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypePullRequests, - Config: &repo_model.PullRequestsConfig{ - IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace, - AllowMerge: form.PullsAllowMerge, - AllowRebase: form.PullsAllowRebase, - AllowRebaseMerge: form.PullsAllowRebaseMerge, - AllowSquash: form.PullsAllowSquash, - AllowFastForwardOnly: form.PullsAllowFastForwardOnly, - AllowManualMerge: form.PullsAllowManualMerge, - AutodetectManualMerge: form.EnableAutodetectManualMerge, - AllowRebaseUpdate: form.PullsAllowRebaseUpdate, - DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge, - DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle), - DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit, - }, - }) + units = append(units, newRepoUnit(repo, unit_model.TypePullRequests, &repo_model.PullRequestsConfig{ + IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace, + AllowMerge: form.PullsAllowMerge, + AllowRebase: form.PullsAllowRebase, + AllowRebaseMerge: form.PullsAllowRebaseMerge, + AllowSquash: form.PullsAllowSquash, + AllowFastForwardOnly: form.PullsAllowFastForwardOnly, + AllowManualMerge: form.PullsAllowManualMerge, + AutodetectManualMerge: form.EnableAutodetectManualMerge, + AllowRebaseUpdate: form.PullsAllowRebaseUpdate, + DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge, + DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle), + DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit, + })) } else if !unit_model.TypePullRequests.UnitGlobalDisabled() { deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests) } @@ -823,7 +787,7 @@ func handleSettingsPostConvertFork(ctx *context.Context) { return } - if !ctx.Repo.Owner.CanCreateRepo() { + if !ctx.Doer.CanForkRepoIn(ctx.Repo.Owner) { maxCreationLimit := ctx.Repo.Owner.MaxCreationLimit() msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit) ctx.Flash.Error(msg) @@ -884,6 +848,9 @@ func handleSettingsPostTransfer(ctx *context.Context) { ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil) } else if repo_model.IsErrRepoTransferInProgress(err) { ctx.RenderWithErr(ctx.Tr("repo.settings.transfer_in_progress"), tplSettingsOptions, nil) + } else if repo_service.IsRepositoryLimitReached(err) { + limit := err.(repo_service.LimitReachedError).Limit + ctx.RenderWithErr(ctx.TrN(limit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", limit), tplSettingsOptions, nil) } else if errors.Is(err, user_model.ErrBlockedUser) { ctx.RenderWithErr(ctx.Tr("repo.settings.transfer.blocked_user"), tplSettingsOptions, nil) } else { diff --git a/routers/web/repo/setting/settings_test.go b/routers/web/repo/setting/settings_test.go index ad33dac514..ba6b0d1efc 100644 --- a/routers/web/repo/setting/settings_test.go +++ b/routers/web/repo/setting/settings_test.go @@ -54,7 +54,7 @@ func TestAddReadOnlyDeployKey(t *testing.T) { } web.SetForm(ctx, &addKeyForm) DeployKeysPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) unittest.AssertExistsAndLoadBean(t, &asymkey_model.DeployKey{ Name: addKeyForm.Title, @@ -84,7 +84,7 @@ func TestAddReadWriteOnlyDeployKey(t *testing.T) { } web.SetForm(ctx, &addKeyForm) DeployKeysPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) unittest.AssertExistsAndLoadBean(t, &asymkey_model.DeployKey{ Name: addKeyForm.Title, @@ -121,7 +121,7 @@ func TestCollaborationPost(t *testing.T) { CollaborationPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) exists, err := repo_model.IsCollaborator(ctx, re.ID, 4) assert.NoError(t, err) @@ -147,7 +147,7 @@ func TestCollaborationPost_InactiveUser(t *testing.T) { CollaborationPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assert.NotEmpty(t, ctx.Flash.ErrorMsg) } @@ -179,7 +179,7 @@ func TestCollaborationPost_AddCollaboratorTwice(t *testing.T) { CollaborationPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) exists, err := repo_model.IsCollaborator(ctx, re.ID, 4) assert.NoError(t, err) @@ -188,7 +188,7 @@ func TestCollaborationPost_AddCollaboratorTwice(t *testing.T) { // Try adding the same collaborator again CollaborationPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assert.NotEmpty(t, ctx.Flash.ErrorMsg) } @@ -210,7 +210,7 @@ func TestCollaborationPost_NonExistentUser(t *testing.T) { CollaborationPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assert.NotEmpty(t, ctx.Flash.ErrorMsg) } @@ -250,7 +250,7 @@ func TestAddTeamPost(t *testing.T) { AddTeamPost(ctx) assert.True(t, repo_service.HasRepository(db.DefaultContext, team, re.ID)) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assert.Empty(t, ctx.Flash.ErrorMsg) } @@ -290,7 +290,7 @@ func TestAddTeamPost_NotAllowed(t *testing.T) { AddTeamPost(ctx) assert.False(t, repo_service.HasRepository(db.DefaultContext, team, re.ID)) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assert.NotEmpty(t, ctx.Flash.ErrorMsg) } @@ -331,7 +331,7 @@ func TestAddTeamPost_AddTeamTwice(t *testing.T) { AddTeamPost(ctx) assert.True(t, repo_service.HasRepository(db.DefaultContext, team, re.ID)) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assert.NotEmpty(t, ctx.Flash.ErrorMsg) } @@ -364,7 +364,7 @@ func TestAddTeamPost_NonExistentTeam(t *testing.T) { ctx.Repo = repo AddTeamPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assert.NotEmpty(t, ctx.Flash.ErrorMsg) } diff --git a/routers/web/repo/treelist.go b/routers/web/repo/treelist.go index ab74741e61..9c5ec8f206 100644 --- a/routers/web/repo/treelist.go +++ b/routers/web/repo/treelist.go @@ -8,6 +8,7 @@ import ( pull_model "code.gitea.io/gitea/models/pull" "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/fileicon" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/gitdiff" @@ -87,10 +88,11 @@ func transformDiffTreeForUI(diffTree *gitdiff.DiffTree, filesViewedState map[str } func TreeViewNodes(ctx *context.Context) { - results, err := files_service.GetTreeViewNodes(ctx, ctx.Repo.Commit, ctx.Repo.TreePath, ctx.FormString("sub_path")) + renderedIconPool := fileicon.NewRenderedIconPool() + results, err := files_service.GetTreeViewNodes(ctx, renderedIconPool, ctx.Repo.Commit, ctx.Repo.TreePath, ctx.FormString("sub_path")) if err != nil { ctx.ServerError("GetTreeViewNodes", err) return } - ctx.JSON(http.StatusOK, map[string]any{"fileTreeNodes": results}) + ctx.JSON(http.StatusOK, map[string]any{"fileTreeNodes": results, "renderedIconPool": renderedIconPool.IconSVGs}) } diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 6ed5801d10..77240f0431 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -29,6 +29,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/charset" + "code.gitea.io/gitea/modules/fileicon" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" @@ -252,6 +253,16 @@ func LastCommit(ctx *context.Context) { ctx.HTML(http.StatusOK, tplRepoViewList) } +func prepareDirectoryFileIcons(ctx *context.Context, files []git.CommitInfo) { + renderedIconPool := fileicon.NewRenderedIconPool() + fileIcons := map[string]template.HTML{} + for _, f := range files { + fileIcons[f.Entry.Name()] = fileicon.RenderEntryIcon(renderedIconPool, f.Entry) + } + ctx.Data["FileIcons"] = fileIcons + ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML() +} + func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entries { tree, err := ctx.Repo.Commit.SubTree(ctx.Repo.TreePath) if err != nil { @@ -293,6 +304,7 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri return nil } ctx.Data["Files"] = files + prepareDirectoryFileIcons(ctx, files) for _, f := range files { if f.Commit == nil { ctx.Data["HasFilesWithoutLatestCommit"] = true diff --git a/routers/web/repo/view_file.go b/routers/web/repo/view_file.go index 4ce7a8e3a4..3df6051975 100644 --- a/routers/web/repo/view_file.go +++ b/routers/web/repo/view_file.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/actions" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/attribute" "code.gitea.io/gitea/modules/highlight" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" @@ -25,36 +26,34 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/context" issue_service "code.gitea.io/gitea/services/issue" - files_service "code.gitea.io/gitea/services/repository/files" "github.com/nektos/act/pkg/model" ) -func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) { - ctx.Data["IsViewFile"] = true - ctx.Data["HideRepoInfo"] = true - blob := entry.Blob() - buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, blob) - if err != nil { - ctx.ServerError("getFileReader", err) - return - } - defer dataRc.Close() - - ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefFullName.ShortName()) - ctx.Data["FileIsSymlink"] = entry.IsLink() - ctx.Data["FileName"] = blob.Name() - ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) - +func prepareLatestCommitInfo(ctx *context.Context) bool { commit, err := ctx.Repo.Commit.GetCommitByPath(ctx.Repo.TreePath) if err != nil { ctx.ServerError("GetCommitByPath", err) + return false + } + + return loadLatestCommitData(ctx, commit) +} + +func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) { + ctx.Data["IsViewFile"] = true + ctx.Data["HideRepoInfo"] = true + + if !prepareLatestCommitInfo(ctx) { return } - if !loadLatestCommitData(ctx, commit) { - return - } + blob := entry.Blob() + + ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefFullName.ShortName()) + ctx.Data["FileIsSymlink"] = entry.IsLink() + ctx.Data["FileTreePath"] = ctx.Repo.TreePath + ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) if ctx.Repo.TreePath == ".editorconfig" { _, editorconfigWarning, editorconfigErr := ctx.Repo.GetEditorconfig(ctx.Repo.Commit) @@ -90,6 +89,15 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) { isDisplayingSource := ctx.FormString("display") == "source" isDisplayingRendered := !isDisplayingSource + // Don't call any other repository functions depends on git.Repository until the dataRc closed to + // avoid create unnecessary temporary cat file. + buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, blob) + if err != nil { + ctx.ServerError("getFileReader", err) + return + } + defer dataRc.Close() + if fInfo.isLFSFile { ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/media/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) } @@ -139,6 +147,23 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) { ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files") } + // read all needed attributes which will be used later + // there should be no performance different between reading 2 or 4 here + attrsMap, err := attribute.CheckAttributes(ctx, ctx.Repo.GitRepo, ctx.Repo.CommitID, attribute.CheckAttributeOpts{ + Filenames: []string{ctx.Repo.TreePath}, + Attributes: []string{attribute.LinguistGenerated, attribute.LinguistVendored, attribute.LinguistLanguage, attribute.GitlabLanguage}, + }) + if err != nil { + ctx.ServerError("attribute.CheckAttributes", err) + return + } + attrs := attrsMap[ctx.Repo.TreePath] + if attrs == nil { + // this case shouldn't happen, just in case. + setting.PanicInDevOrTesting("no attributes found for %s", ctx.Repo.TreePath) + attrs = attribute.NewAttributes() + } + switch { case isRepresentableAsText: if fInfo.fileSize >= setting.UI.MaxDisplayFileSize { @@ -168,7 +193,7 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) { if markupType != "" && !shouldRenderSource { ctx.Data["IsMarkup"] = true ctx.Data["MarkupType"] = markupType - metas := ctx.Repo.Repository.ComposeDocumentMetas(ctx) + metas := ctx.Repo.Repository.ComposeRepoFileMetas(ctx) metas["RefTypeNameSubURL"] = ctx.Repo.RefTypeNameSubURL() rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{ CurrentRefPath: ctx.Repo.RefTypeNameSubURL(), @@ -201,11 +226,7 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) { ctx.Data["NumLines"] = bytes.Count(buf, []byte{'\n'}) + 1 } - language, err := files_service.TryGetContentLanguage(ctx.Repo.GitRepo, ctx.Repo.CommitID, ctx.Repo.TreePath) - if err != nil { - log.Error("Unable to get file language for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err) - } - + language := attrs.GetLanguage().Value() fileContent, lexerName, err := highlight.File(blob.Name(), language, buf) ctx.Data["LexerName"] = lexerName if err != nil { @@ -275,17 +296,7 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) { } } - if ctx.Repo.GitRepo != nil { - checker, deferable := ctx.Repo.GitRepo.CheckAttributeReader(ctx.Repo.CommitID) - if checker != nil { - defer deferable() - attrs, err := checker.CheckPath(ctx.Repo.TreePath) - if err == nil { - ctx.Data["IsVendored"] = git.AttributeToBool(attrs, git.AttributeLinguistVendored).Value() - ctx.Data["IsGenerated"] = git.AttributeToBool(attrs, git.AttributeLinguistGenerated).Value() - } - } - } + ctx.Data["IsVendored"], ctx.Data["IsGenerated"] = attrs.GetVendored().Value(), attrs.GetGenerated().Value() if fInfo.st.IsImage() && !fInfo.st.IsSvgImage() { img, _, err := image.DecodeConfig(bytes.NewReader(buf)) diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go index d538406035..3b053821ee 100644 --- a/routers/web/repo/view_home.go +++ b/routers/web/repo/view_home.go @@ -20,6 +20,8 @@ import ( unit_model "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + giturl "code.gitea.io/gitea/modules/git/url" + "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" @@ -76,7 +78,7 @@ func prepareOpenWithEditorApps(ctx *context.Context) { schema, _, _ := strings.Cut(app.OpenURL, ":") var iconHTML template.HTML if schema == "vscode" || schema == "vscodium" || schema == "jetbrains" { - iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-%s", schema), 16) + iconHTML = svg.RenderHTML("gitea-"+schema, 16) } else { iconHTML = svg.RenderHTML("gitea-git", 16) // TODO: it could support user's customized icon in the future } @@ -269,7 +271,7 @@ func handleRepoEmptyOrBroken(ctx *context.Context) { } else if reallyEmpty { showEmpty = true // the repo is really empty updateContextRepoEmptyAndStatus(ctx, true, repo_model.RepositoryReady) - } else if branches, _, _ := ctx.Repo.GitRepo.GetBranches(0, 1); len(branches) == 0 { + } else if branches, _, _ := ctx.Repo.GitRepo.GetBranchNames(0, 1); len(branches) == 0 { showEmpty = true // it is not really empty, but there is no branch // at the moment, other repo units like "actions" are not able to handle such case, // so we just mark the repo as empty to prevent from displaying these units. @@ -302,8 +304,33 @@ func handleRepoEmptyOrBroken(ctx *context.Context) { ctx.Redirect(link) } +func handleRepoViewSubmodule(ctx *context.Context, submodule *git.SubModule) { + submoduleRepoURL, err := giturl.ParseRepositoryURL(ctx, submodule.URL) + if err != nil { + HandleGitError(ctx, "prepareToRenderDirOrFile: ParseRepositoryURL", err) + return + } + submoduleURL := giturl.MakeRepositoryWebLink(submoduleRepoURL) + if httplib.IsCurrentGiteaSiteURL(ctx, submoduleURL) { + ctx.RedirectToCurrentSite(submoduleURL) + } else { + // don't auto-redirect to external URL, to avoid open redirect or phishing + ctx.Data["NotFoundPrompt"] = submoduleURL + ctx.NotFound(nil) + } +} + func prepareToRenderDirOrFile(entry *git.TreeEntry) func(ctx *context.Context) { return func(ctx *context.Context) { + if entry.IsSubModule() { + submodule, err := ctx.Repo.Commit.GetSubModule(entry.Name()) + if err != nil { + HandleGitError(ctx, "prepareToRenderDirOrFile: GetSubModule", err) + return + } + handleRepoViewSubmodule(ctx, submodule) + return + } if entry.IsDir() { prepareToRenderDirectory(ctx) } else { @@ -343,12 +370,25 @@ func prepareHomeTreeSideBarSwitch(ctx *context.Context) { ctx.Data["UserSettingCodeViewShowFileTree"] = showFileTree } +func redirectSrcToRaw(ctx *context.Context) bool { + // GitHub redirects a tree path with "?raw=1" to the raw path + // It is useful to embed some raw contents into markdown files, + // then viewing the markdown in "src" path could embed the raw content correctly. + if ctx.Repo.TreePath != "" && ctx.FormBool("raw") { + ctx.Redirect(ctx.Repo.RepoLink + "/raw/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)) + return true + } + return false +} + // Home render repository home page func Home(ctx *context.Context) { if handleRepoHomeFeed(ctx) { return } - + if redirectSrcToRaw(ctx) { + return + } // Check whether the repo is viewable: not in migration, and the code unit should be enabled // Ideally the "feed" logic should be after this, but old code did so, so keep it as-is. checkHomeCodeViewable(ctx) diff --git a/routers/web/repo/view_home_test.go b/routers/web/repo/view_home_test.go new file mode 100644 index 0000000000..6264dba71c --- /dev/null +++ b/routers/web/repo/view_home_test.go @@ -0,0 +1,32 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/models/unittest" + git_module "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/contexttest" + + "github.com/stretchr/testify/assert" +) + +func TestViewHomeSubmoduleRedirect(t *testing.T) { + unittest.PrepareTestEnv(t) + + ctx, _ := contexttest.MockContext(t, "/user2/repo1/src/branch/master/test-submodule") + submodule := &git_module.SubModule{Path: "test-submodule", URL: setting.AppURL + "user2/repo-other.git"} + handleRepoViewSubmodule(ctx, submodule) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, "/user2/repo-other", ctx.Resp.Header().Get("Location")) + + ctx, _ = contexttest.MockContext(t, "/user2/repo1/src/branch/master/test-submodule") + submodule = &git_module.SubModule{Path: "test-submodule", URL: "https://other/user2/repo-other.git"} + handleRepoViewSubmodule(ctx, submodule) + // do not auto-redirect for external URLs, to avoid open redirect or phishing + assert.Equal(t, http.StatusNotFound, ctx.Resp.WrittenStatus()) +} diff --git a/routers/web/repo/view_readme.go b/routers/web/repo/view_readme.go index 48befe47f8..459cf0a616 100644 --- a/routers/web/repo/view_readme.go +++ b/routers/web/repo/view_readme.go @@ -69,7 +69,7 @@ func findReadmeFileInEntries(ctx *context.Context, entries []*git.TreeEntry, try if readmeFiles[i] == nil || base.NaturalSortLess(readmeFiles[i].Name(), entry.Blob().Name()) { if entry.IsLink() { target, err := entry.FollowLinks() - if err != nil && !git.IsErrBadLink(err) { + if err != nil && !git.IsErrSymlinkUnresolved(err) { return "", nil, err } else if target != nil && (target.IsExecutable() || target.IsRegular()) { readmeFiles[i] = entry @@ -162,7 +162,7 @@ func prepareToRenderReadmeFile(ctx *context.Context, subfolder string, readmeFil defer dataRc.Close() ctx.Data["FileIsText"] = fInfo.isTextFile - ctx.Data["FileName"] = path.Join(subfolder, readmeFile.Name()) + ctx.Data["FileTreePath"] = path.Join(subfolder, readmeFile.Name()) ctx.Data["FileSize"] = fInfo.fileSize ctx.Data["IsLFSFile"] = fInfo.isLFSFile diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index 20c8c2b406..d70760bc36 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -7,7 +7,6 @@ package repo import ( "bytes" gocontext "context" - "fmt" "io" "net/http" "net/url" @@ -581,7 +580,7 @@ func Wiki(ctx *context.Context) { wikiPath := entry.Name() if markup.DetectMarkupTypeByFileName(wikiPath) != markdown.MarkupName { ext := strings.ToUpper(filepath.Ext(wikiPath)) - ctx.Data["FormatWarning"] = fmt.Sprintf("%s rendering is not supported at the moment. Rendered as Markdown.", ext) + ctx.Data["FormatWarning"] = ext + " rendering is not supported at the moment. Rendered as Markdown." } // Get last change information. lastCommit, err := wikiRepo.GetCommitByPath(wikiPath) diff --git a/routers/web/repo/wiki_test.go b/routers/web/repo/wiki_test.go index e44cf46ba8..b5dfa9f856 100644 --- a/routers/web/repo/wiki_test.go +++ b/routers/web/repo/wiki_test.go @@ -71,7 +71,7 @@ func assertPagesMetas(t *testing.T, expectedNames []string, metas any) { require.Len(t, pageMetas, len(expectedNames)) for i, pageMeta := range pageMetas { - assert.EqualValues(t, expectedNames[i], pageMeta.Name) + assert.Equal(t, expectedNames[i], pageMeta.Name) } } @@ -82,7 +82,7 @@ func TestWiki(t *testing.T) { ctx.SetPathParam("*", "Home") contexttest.LoadRepo(t, ctx, 1) Wiki(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.EqualValues(t, "Home", ctx.Data["Title"]) assertPagesMetas(t, []string{"Home", "Page With Image", "Page With Spaced Name", "Unescaped File"}, ctx.Data["Pages"]) @@ -90,7 +90,7 @@ func TestWiki(t *testing.T) { ctx.SetPathParam("*", "jpeg.jpg") contexttest.LoadRepo(t, ctx, 1) Wiki(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assert.Equal(t, "/user2/repo1/wiki/raw/jpeg.jpg", ctx.Resp.Header().Get("Location")) } @@ -100,7 +100,7 @@ func TestWikiPages(t *testing.T) { ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki/?action=_pages") contexttest.LoadRepo(t, ctx, 1) WikiPages(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assertPagesMetas(t, []string{"Home", "Page With Image", "Page With Spaced Name", "Unescaped File"}, ctx.Data["Pages"]) } @@ -111,7 +111,7 @@ func TestNewWiki(t *testing.T) { contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) NewWiki(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.EqualValues(t, ctx.Tr("repo.wiki.new_page"), ctx.Data["Title"]) } @@ -131,7 +131,7 @@ func TestNewWikiPost(t *testing.T) { Message: message, }) NewWikiPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assertWikiExists(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title)) assert.Equal(t, content, wikiContent(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title))) } @@ -149,7 +149,7 @@ func TestNewWikiPost_ReservedName(t *testing.T) { Message: message, }) NewWikiPost(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.EqualValues(t, ctx.Tr("repo.wiki.reserved_page", "_edit"), ctx.Flash.ErrorMsg) assertWikiNotExists(t, ctx.Repo.Repository, "_edit") } @@ -162,7 +162,7 @@ func TestEditWiki(t *testing.T) { contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) EditWiki(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.EqualValues(t, "Home", ctx.Data["Title"]) assert.Equal(t, wikiContent(t, ctx.Repo.Repository, "Home"), ctx.Data["content"]) @@ -171,7 +171,7 @@ func TestEditWiki(t *testing.T) { contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) EditWiki(ctx) - assert.EqualValues(t, http.StatusForbidden, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusForbidden, ctx.Resp.WrittenStatus()) } func TestEditWikiPost(t *testing.T) { @@ -190,7 +190,7 @@ func TestEditWikiPost(t *testing.T) { Message: message, }) EditWikiPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assertWikiExists(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title)) assert.Equal(t, content, wikiContent(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title))) if title != "Home" { @@ -206,7 +206,7 @@ func TestDeleteWikiPagePost(t *testing.T) { contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) DeleteWikiPagePost(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assertWikiNotExists(t, ctx.Repo.Repository, "Home") } @@ -228,10 +228,10 @@ func TestWikiRaw(t *testing.T) { contexttest.LoadRepo(t, ctx, 1) WikiRaw(ctx) if filetype == "" { - assert.EqualValues(t, http.StatusNotFound, ctx.Resp.WrittenStatus(), "filepath: %s", filepath) + assert.Equal(t, http.StatusNotFound, ctx.Resp.WrittenStatus(), "filepath: %s", filepath) } else { - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus(), "filepath: %s", filepath) - assert.EqualValues(t, filetype, ctx.Resp.Header().Get("Content-Type"), "filepath: %s", filepath) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus(), "filepath: %s", filepath) + assert.Equal(t, filetype, ctx.Resp.Header().Get("Content-Type"), "filepath: %s", filepath) } } } diff --git a/routers/web/shared/actions/runners.go b/routers/web/shared/actions/runners.go index 444bd960db..a87f6ce4dc 100644 --- a/routers/web/shared/actions/runners.go +++ b/routers/web/shared/actions/runners.go @@ -57,9 +57,8 @@ func getRunnersCtx(ctx *context.Context) (*runnersCtx, error) { } if ctx.Data["PageIsOrgSettings"] == true { - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return nil, nil } return &runnersCtx{ diff --git a/routers/web/shared/actions/variables.go b/routers/web/shared/actions/variables.go index 9cc1676d7b..a43c2c2690 100644 --- a/routers/web/shared/actions/variables.go +++ b/routers/web/shared/actions/variables.go @@ -49,9 +49,8 @@ func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) { } if ctx.Data["PageIsOrgSettings"] == true { - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return nil, nil } return &variablesCtx{ diff --git a/routers/web/shared/issue/issue_label.go b/routers/web/shared/issue/issue_label.go index eacea36b02..e2eeaaf0af 100644 --- a/routers/web/shared/issue/issue_label.go +++ b/routers/web/shared/issue/issue_label.go @@ -14,14 +14,18 @@ import ( ) // PrepareFilterIssueLabels reads the "labels" query parameter, sets `ctx.Data["Labels"]` and `ctx.Data["SelectLabels"]` -func PrepareFilterIssueLabels(ctx *context.Context, repoID int64, owner *user_model.User) (labelIDs []int64) { +func PrepareFilterIssueLabels(ctx *context.Context, repoID int64, owner *user_model.User) (ret struct { + AllLabels []*issues_model.Label + SelectedLabelIDs []int64 +}, +) { // 1,-2 means including label 1 and excluding label 2 // 0 means issues with no label // blank means labels will not be filtered for issues selectLabels := ctx.FormString("labels") if selectLabels != "" { var err error - labelIDs, err = base.StringsToInt64s(strings.Split(selectLabels, ",")) + ret.SelectedLabelIDs, err = base.StringsToInt64s(strings.Split(selectLabels, ",")) if err != nil { ctx.Flash.Error(ctx.Tr("invalid_data", selectLabels), true) } @@ -32,7 +36,7 @@ func PrepareFilterIssueLabels(ctx *context.Context, repoID int64, owner *user_mo repoLabels, err := issues_model.GetLabelsByRepoID(ctx, repoID, "", db.ListOptions{}) if err != nil { ctx.ServerError("GetLabelsByRepoID", err) - return nil + return ret } allLabels = append(allLabels, repoLabels...) } @@ -41,14 +45,14 @@ func PrepareFilterIssueLabels(ctx *context.Context, repoID int64, owner *user_mo orgLabels, err := issues_model.GetLabelsByOrgID(ctx, owner.ID, "", db.ListOptions{}) if err != nil { ctx.ServerError("GetLabelsByOrgID", err) - return nil + return ret } allLabels = append(allLabels, orgLabels...) } // Get the exclusive scope for every label ID - labelExclusiveScopes := make([]string, 0, len(labelIDs)) - for _, labelID := range labelIDs { + labelExclusiveScopes := make([]string, 0, len(ret.SelectedLabelIDs)) + for _, labelID := range ret.SelectedLabelIDs { foundExclusiveScope := false for _, label := range allLabels { if label.ID == labelID || label.ID == -labelID { @@ -63,9 +67,10 @@ func PrepareFilterIssueLabels(ctx *context.Context, repoID int64, owner *user_mo } for _, l := range allLabels { - l.LoadSelectedLabelsAfterClick(labelIDs, labelExclusiveScopes) + l.LoadSelectedLabelsAfterClick(ret.SelectedLabelIDs, labelExclusiveScopes) } ctx.Data["Labels"] = allLabels ctx.Data["SelectLabels"] = selectLabels - return labelIDs + ret.AllLabels = allLabels + return ret } diff --git a/routers/web/shared/user/header.go b/routers/web/shared/user/header.go index 62b146c7f3..48a5d58ea4 100644 --- a/routers/web/shared/user/header.go +++ b/routers/web/shared/user/header.go @@ -24,19 +24,8 @@ import ( "code.gitea.io/gitea/services/context" ) -// prepareContextForCommonProfile store some common data into context data for user's profile related pages (including the nav menu) -// It is designed to be fast and safe to be called multiple times in one request -func prepareContextForCommonProfile(ctx *context.Context) { - ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled - ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled - ctx.Data["EnableFeed"] = setting.Other.EnableFeed - ctx.Data["FeedURL"] = ctx.ContextUser.HomeLink() -} - -// PrepareContextForProfileBigAvatar set the context for big avatar view on the profile page -func PrepareContextForProfileBigAvatar(ctx *context.Context) { - prepareContextForCommonProfile(ctx) - +// prepareContextForProfileBigAvatar set the context for big avatar view on the profile page +func prepareContextForProfileBigAvatar(ctx *context.Context) { ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID) ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate if setting.Service.UserLocationMapURL != "" { @@ -138,16 +127,44 @@ func FindOwnerProfileReadme(ctx *context.Context, doer *user_model.User, optProf return profileDbRepo, profileReadmeBlob } -func RenderUserHeader(ctx *context.Context) { - prepareContextForCommonProfile(ctx) - - _, profileReadmeBlob := FindOwnerProfileReadme(ctx, ctx.Doer) - ctx.Data["HasUserProfileReadme"] = profileReadmeBlob != nil +type PrepareOwnerHeaderResult struct { + ProfilePublicRepo *repo_model.Repository + ProfilePublicReadmeBlob *git.Blob + ProfilePrivateRepo *repo_model.Repository + ProfilePrivateReadmeBlob *git.Blob + HasOrgProfileReadme bool } -func LoadHeaderCount(ctx *context.Context) error { - prepareContextForCommonProfile(ctx) +const ( + RepoNameProfilePrivate = ".profile-private" + RepoNameProfile = ".profile" +) +func RenderUserOrgHeader(ctx *context.Context) (result *PrepareOwnerHeaderResult, err error) { + ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled + ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled + ctx.Data["EnableFeed"] = setting.Other.EnableFeed + ctx.Data["FeedURL"] = ctx.ContextUser.HomeLink() + + if err := loadHeaderCount(ctx); err != nil { + return nil, err + } + + result = &PrepareOwnerHeaderResult{} + if ctx.ContextUser.IsOrganization() { + result.ProfilePublicRepo, result.ProfilePublicReadmeBlob = FindOwnerProfileReadme(ctx, ctx.Doer) + result.ProfilePrivateRepo, result.ProfilePrivateReadmeBlob = FindOwnerProfileReadme(ctx, ctx.Doer, RepoNameProfilePrivate) + result.HasOrgProfileReadme = result.ProfilePublicReadmeBlob != nil || result.ProfilePrivateReadmeBlob != nil + ctx.Data["HasOrgProfileReadme"] = result.HasOrgProfileReadme // many pages need it to show the "overview" tab + } else { + _, profileReadmeBlob := FindOwnerProfileReadme(ctx, ctx.Doer) + ctx.Data["HasUserProfileReadme"] = profileReadmeBlob != nil + prepareContextForProfileBigAvatar(ctx) + } + return result, nil +} + +func loadHeaderCount(ctx *context.Context) error { repoCount, err := repo_model.CountRepository(ctx, &repo_model.SearchRepoOptions{ Actor: ctx.Doer, OwnerID: ctx.ContextUser.ID, @@ -178,29 +195,3 @@ func LoadHeaderCount(ctx *context.Context) error { return nil } - -const ( - RepoNameProfilePrivate = ".profile-private" - RepoNameProfile = ".profile" -) - -type PrepareOrgHeaderResult struct { - ProfilePublicRepo *repo_model.Repository - ProfilePublicReadmeBlob *git.Blob - ProfilePrivateRepo *repo_model.Repository - ProfilePrivateReadmeBlob *git.Blob - HasOrgProfileReadme bool -} - -func PrepareOrgHeader(ctx *context.Context) (result *PrepareOrgHeaderResult, err error) { - if err = LoadHeaderCount(ctx); err != nil { - return nil, err - } - - result = &PrepareOrgHeaderResult{} - result.ProfilePublicRepo, result.ProfilePublicReadmeBlob = FindOwnerProfileReadme(ctx, ctx.Doer) - result.ProfilePrivateRepo, result.ProfilePrivateReadmeBlob = FindOwnerProfileReadme(ctx, ctx.Doer, RepoNameProfilePrivate) - result.HasOrgProfileReadme = result.ProfilePublicReadmeBlob != nil || result.ProfilePrivateReadmeBlob != nil - ctx.Data["HasOrgProfileReadme"] = result.HasOrgProfileReadme // many pages need it to show the "overview" tab - return result, nil -} diff --git a/routers/web/user/code.go b/routers/web/user/code.go index f9aa58b877..f2153c6d54 100644 --- a/routers/web/user/code.go +++ b/routers/web/user/code.go @@ -26,11 +26,8 @@ func CodeSearch(ctx *context.Context) { ctx.Redirect(ctx.ContextUser.HomeLink()) return } - shared_user.PrepareContextForProfileBigAvatar(ctx) - shared_user.RenderUserHeader(ctx) - - if err := shared_user.LoadHeaderCount(ctx); err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 44e2a5ec71..77f9cb8cca 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -617,9 +617,10 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { return 0 } reviewTyp := issues_model.ReviewTypeApprove - if typ == "reject" { + switch typ { + case "reject": reviewTyp = issues_model.ReviewTypeReject - } else if typ == "waiting" { + case "waiting": reviewTyp = issues_model.ReviewTypeRequest } for _, count := range counts { @@ -698,7 +699,7 @@ func ShowGPGKeys(ctx *context.Context) { headers := make(map[string]string) if len(failedEntitiesID) > 0 { // If some key need re-import to be exported - headers["Note"] = fmt.Sprintf("The keys with the following IDs couldn't be exported and need to be reuploaded %s", strings.Join(failedEntitiesID, ", ")) + headers["Note"] = "The keys with the following IDs couldn't be exported and need to be reuploaded " + strings.Join(failedEntitiesID, ", ") } else if len(entities) == 0 { headers["Note"] = "This user hasn't uploaded any GPG keys." } diff --git a/routers/web/user/home_test.go b/routers/web/user/home_test.go index b2c8ad98ba..2a8a8812e3 100644 --- a/routers/web/user/home_test.go +++ b/routers/web/user/home_test.go @@ -37,15 +37,15 @@ func TestArchivedIssues(t *testing.T) { NumIssues[repo.ID] = repo.NumIssues } assert.False(t, IsArchived[50]) - assert.EqualValues(t, 1, NumIssues[50]) + assert.Equal(t, 1, NumIssues[50]) assert.True(t, IsArchived[51]) - assert.EqualValues(t, 1, NumIssues[51]) + assert.Equal(t, 1, NumIssues[51]) // Act Issues(ctx) // Assert: One Issue (ID 30) from one Repo (ID 50) is retrieved, while nothing from archived Repo 51 is retrieved - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.Len(t, ctx.Data["Issues"], 1) } @@ -58,7 +58,7 @@ func TestIssues(t *testing.T) { contexttest.LoadUser(t, ctx, 2) ctx.Req.Form.Set("state", "closed") Issues(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.EqualValues(t, true, ctx.Data["IsShowClosed"]) assert.Len(t, ctx.Data["Issues"], 1) @@ -72,7 +72,7 @@ func TestPulls(t *testing.T) { contexttest.LoadUser(t, ctx, 2) ctx.Req.Form.Set("state", "open") Pulls(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.Len(t, ctx.Data["Issues"], 5) } @@ -87,7 +87,7 @@ func TestMilestones(t *testing.T) { ctx.Req.Form.Set("state", "closed") ctx.Req.Form.Set("sort", "furthestduedate") Milestones(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.EqualValues(t, map[int64]int64{1: 1}, ctx.Data["Counts"]) assert.EqualValues(t, true, ctx.Data["IsShowClosed"]) assert.EqualValues(t, "furthestduedate", ctx.Data["SortType"]) @@ -107,7 +107,7 @@ func TestMilestonesForSpecificRepo(t *testing.T) { ctx.Req.Form.Set("state", "closed") ctx.Req.Form.Set("sort", "furthestduedate") Milestones(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.EqualValues(t, map[int64]int64{1: 1}, ctx.Data["Counts"]) assert.EqualValues(t, true, ctx.Data["IsShowClosed"]) assert.EqualValues(t, "furthestduedate", ctx.Data["SortType"]) diff --git a/routers/web/user/notification.go b/routers/web/user/notification.go index f0c4390852..89f3c6956f 100644 --- a/routers/web/user/notification.go +++ b/routers/web/user/notification.go @@ -308,9 +308,10 @@ func NotificationSubscriptions(ctx *context.Context) { return 0 } reviewTyp := issues_model.ReviewTypeApprove - if typ == "reject" { + switch typ { + case "reject": reviewTyp = issues_model.ReviewTypeReject - } else if typ == "waiting" { + case "waiting": reviewTyp = issues_model.ReviewTypeRequest } for _, count := range counts { diff --git a/routers/web/user/package.go b/routers/web/user/package.go index c01bc96e2b..e96f5a04ea 100644 --- a/routers/web/user/package.go +++ b/routers/web/user/package.go @@ -42,7 +42,10 @@ const ( // ListPackages displays a list of all packages of the context user func ListPackages(ctx *context.Context) { - shared_user.PrepareContextForProfileBigAvatar(ctx) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } page := ctx.FormInt("page") if page <= 1 { page = 1 @@ -94,8 +97,6 @@ func ListPackages(ctx *context.Context) { return } - shared_user.RenderUserHeader(ctx) - ctx.Data["Title"] = ctx.Tr("packages.title") ctx.Data["IsPackagesPage"] = true ctx.Data["Query"] = query @@ -106,9 +107,8 @@ func ListPackages(ctx *context.Context) { ctx.Data["Total"] = total ctx.Data["RepositoryAccessMap"] = repositoryAccessMap - err = shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } @@ -126,11 +126,9 @@ func ListPackages(ctx *context.Context) { ctx.Data["IsOrganizationOwner"] = false } } - pager := context.NewPagination(int(total), setting.UI.PackagesPagingNum, page, 5) pager.AddParamFromRequest(ctx.Req) ctx.Data["Page"] = pager - ctx.HTML(http.StatusOK, tplPackagesList) } @@ -164,16 +162,17 @@ func RedirectToLastVersion(ctx *context.Context) { ctx.ServerError("GetPackageDescriptor", err) return } - ctx.Redirect(pd.VersionWebLink()) } // ViewPackageVersion displays a single package version func ViewPackageVersion(ctx *context.Context) { + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } + pd := ctx.Package.Descriptor - - shared_user.RenderUserHeader(ctx) - ctx.Data["Title"] = pd.Package.Name ctx.Data["IsPackagesPage"] = true ctx.Data["PackageDescriptor"] = pd @@ -301,19 +300,16 @@ func ViewPackageVersion(ctx *context.Context) { hasRepositoryAccess = permission.HasAnyUnitAccess() } ctx.Data["HasRepositoryAccess"] = hasRepositoryAccess - - err = shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) - return - } - ctx.HTML(http.StatusOK, tplPackagesView) } // ListPackageVersions lists all versions of a package func ListPackageVersions(ctx *context.Context) { - shared_user.PrepareContextForProfileBigAvatar(ctx) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } + p, err := packages_model.GetPackageByName(ctx, ctx.Package.Owner.ID, packages_model.Type(ctx.PathParam("type")), ctx.PathParam("name")) if err != nil { if err == packages_model.ErrPackageNotExist { @@ -336,8 +332,6 @@ func ListPackageVersions(ctx *context.Context) { query := ctx.FormTrim("q") sort := ctx.FormTrim("sort") - shared_user.RenderUserHeader(ctx) - ctx.Data["Title"] = ctx.Tr("packages.title") ctx.Data["IsPackagesPage"] = true ctx.Data["PackageDescriptor"] = &packages_model.PackageDescriptor{ @@ -393,12 +387,6 @@ func ListPackageVersions(ctx *context.Context) { ctx.Data["Total"] = total - err = shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) - return - } - pager := context.NewPagination(int(total), setting.UI.PackagesPagingNum, page, 5) pager.AddParamFromRequest(ctx.Req) ctx.Data["Page"] = pager @@ -410,7 +398,10 @@ func ListPackageVersions(ctx *context.Context) { func PackageSettings(ctx *context.Context) { pd := ctx.Package.Descriptor - shared_user.RenderUserHeader(ctx) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } ctx.Data["Title"] = pd.Package.Name ctx.Data["IsPackagesPage"] = true @@ -423,12 +414,6 @@ func PackageSettings(ctx *context.Context) { ctx.Data["Repos"] = repos ctx.Data["CanWritePackages"] = ctx.Package.AccessMode >= perm.AccessModeWrite || ctx.IsUserSiteAdmin() - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) - return - } - ctx.HTML(http.StatusOK, tplPackagesSettings) } diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 39f066a53c..ee19665109 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -78,8 +78,15 @@ func userProfile(ctx *context.Context) { showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) prepareUserProfileTabData(ctx, showPrivate, profileDbRepo, profileReadmeBlob) - // call PrepareContextForProfileBigAvatar later to avoid re-querying the NumFollowers & NumFollowing - shared_user.PrepareContextForProfileBigAvatar(ctx) + + // prepare the user nav header data after "prepareUserProfileTabData" to avoid re-querying the NumFollowers & NumFollowing + // because ctx.Data["NumFollowers"] and "NumFollowing" logic duplicates in both of them + // and the "profile readme" related logic also duplicates in both of FindOwnerProfileReadme and RenderUserOrgHeader + // TODO: it is a bad design and should be refactored later, + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } ctx.HTML(http.StatusOK, tplProfile) } @@ -302,9 +309,8 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb ctx.Data["Repos"] = repos ctx.Data["Total"] = total - err = shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } @@ -328,9 +334,11 @@ func ActionUserFollow(ctx *context.Context) { ctx.HTTPError(http.StatusBadRequest, fmt.Sprintf("Action %q failed", ctx.FormString("action"))) return } - + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } if ctx.ContextUser.IsIndividual() { - shared_user.PrepareContextForProfileBigAvatar(ctx) ctx.HTML(http.StatusOK, tplProfileBigAvatar) return } else if ctx.ContextUser.IsOrganization() { diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index 94577832a9..b124d5e1de 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -6,7 +6,6 @@ package setting import ( "errors" - "fmt" "net/http" "time" @@ -37,7 +36,7 @@ const ( // Account renders change user's password, user's email and user suicide page func Account(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials, setting.UserFeatureDeletion) && !setting.Service.EnableNotifyMail { - ctx.NotFound(fmt.Errorf("account setting are not allowed to be changed")) + ctx.NotFound(errors.New("account setting are not allowed to be changed")) return } @@ -54,7 +53,7 @@ func Account(ctx *context.Context) { // AccountPost response for change user's password func AccountPost(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { - ctx.NotFound(fmt.Errorf("password setting is not allowed to be changed")) + ctx.NotFound(errors.New("password setting is not allowed to be changed")) return } @@ -105,7 +104,7 @@ func AccountPost(ctx *context.Context) { // EmailPost response for change user's email func EmailPost(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { - ctx.NotFound(fmt.Errorf("emails are not allowed to be changed")) + ctx.NotFound(errors.New("emails are not allowed to be changed")) return } @@ -239,7 +238,7 @@ func EmailPost(ctx *context.Context) { // DeleteEmail response for delete user's email func DeleteEmail(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { - ctx.NotFound(fmt.Errorf("emails are not allowed to be changed")) + ctx.NotFound(errors.New("emails are not allowed to be changed")) return } email, err := user_model.GetEmailAddressByID(ctx, ctx.Doer.ID, ctx.FormInt64("id")) diff --git a/routers/web/user/setting/account_test.go b/routers/web/user/setting/account_test.go index 13caa33771..9b8cffc868 100644 --- a/routers/web/user/setting/account_test.go +++ b/routers/web/user/setting/account_test.go @@ -95,7 +95,7 @@ func TestChangePassword(t *testing.T) { AccountPost(ctx) assert.Contains(t, ctx.Flash.ErrorMsg, req.Message) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) }) } } diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index 1f6c97a5cc..9c43ddd3ea 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -43,8 +43,9 @@ func ApplicationsPost(ctx *context.Context) { _ = ctx.Req.ParseForm() var scopeNames []string + const accessTokenScopePrefix = "scope-" for k, v := range ctx.Req.Form { - if strings.HasPrefix(k, "scope-") { + if strings.HasPrefix(k, accessTokenScopePrefix) { scopeNames = append(scopeNames, v...) } } @@ -54,7 +55,7 @@ func ApplicationsPost(ctx *context.Context) { ctx.ServerError("GetScope", err) return } - if scope == "" || scope == auth_model.AccessTokenScopePublicOnly { + if !scope.HasPermissionScope() { ctx.Flash.Error(ctx.Tr("settings.at_least_one_permission"), true) } diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go index 17e32f5403..6b5a7a2e2a 100644 --- a/routers/web/user/setting/keys.go +++ b/routers/web/user/setting/keys.go @@ -5,7 +5,7 @@ package setting import ( - "fmt" + "errors" "net/http" asymkey_model "code.gitea.io/gitea/models/asymkey" @@ -26,7 +26,7 @@ const ( // Keys render user's SSH/GPG public keys page func Keys(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys, setting.UserFeatureManageGPGKeys) { - ctx.NotFound(fmt.Errorf("keys setting is not allowed to be changed")) + ctx.NotFound(errors.New("keys setting is not allowed to be changed")) return } @@ -87,7 +87,7 @@ func KeysPost(ctx *context.Context) { ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "gpg": if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { - ctx.NotFound(fmt.Errorf("gpg keys setting is not allowed to be visited")) + ctx.NotFound(errors.New("gpg keys setting is not allowed to be visited")) return } @@ -168,7 +168,7 @@ func KeysPost(ctx *context.Context) { ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "ssh": if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { - ctx.NotFound(fmt.Errorf("ssh keys setting is not allowed to be visited")) + ctx.NotFound(errors.New("ssh keys setting is not allowed to be visited")) return } @@ -212,7 +212,7 @@ func KeysPost(ctx *context.Context) { ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "verify_ssh": if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { - ctx.NotFound(fmt.Errorf("ssh keys setting is not allowed to be visited")) + ctx.NotFound(errors.New("ssh keys setting is not allowed to be visited")) return } @@ -249,7 +249,7 @@ func DeleteKey(ctx *context.Context) { switch ctx.FormString("type") { case "gpg": if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { - ctx.NotFound(fmt.Errorf("gpg keys setting is not allowed to be visited")) + ctx.NotFound(errors.New("gpg keys setting is not allowed to be visited")) return } if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, ctx.FormInt64("id")); err != nil { @@ -259,7 +259,7 @@ func DeleteKey(ctx *context.Context) { } case "ssh": if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { - ctx.NotFound(fmt.Errorf("ssh keys setting is not allowed to be visited")) + ctx.NotFound(errors.New("ssh keys setting is not allowed to be visited")) return } diff --git a/routers/web/user/setting/oauth2_common.go b/routers/web/user/setting/oauth2_common.go index d4da468a85..f460acce10 100644 --- a/routers/web/user/setting/oauth2_common.go +++ b/routers/web/user/setting/oauth2_common.go @@ -28,8 +28,8 @@ func (oa *OAuth2CommonHandlers) renderEditPage(ctx *context.Context) { ctx.Data["FormActionPath"] = fmt.Sprintf("%s/%d", oa.BasePathEditPrefix, app.ID) if ctx.ContextUser != nil && ctx.ContextUser.IsOrganization() { - if err := shared_user.LoadHeaderCount(ctx); err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } } diff --git a/routers/web/web.go b/routers/web/web.go index 455d0a3a0d..fcddcad1b1 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -178,7 +178,7 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Cont return } - if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == "POST" { + if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == http.MethodPost { ctx.Csrf.Validate(ctx) if ctx.Written() { return @@ -283,23 +283,23 @@ func Routes() *web.Router { mid = append(mid, goGet) mid = append(mid, common.PageTmplFunctions) - others := web.NewRouter() - others.Use(mid...) - registerRoutes(others) - routes.Mount("", others) + webRoutes := web.NewRouter() + webRoutes.Use(mid...) + webRoutes.Group("", func() { registerWebRoutes(webRoutes) }, common.BlockExpensive()) + routes.Mount("", webRoutes) return routes } var optSignInIgnoreCsrf = verifyAuthWithOptions(&common.VerifyOptions{DisableCSRF: true}) -// registerRoutes register routes -func registerRoutes(m *web.Router) { +// registerWebRoutes register routes +func registerWebRoutes(m *web.Router) { // required to be signed in or signed out reqSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true}) reqSignOut := verifyAuthWithOptions(&common.VerifyOptions{SignOutRequired: true}) // optional sign in (if signed in, use the user as doer, if not, no doer) - optSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView}) - optExploreSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView}) + optSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInViewStrict}) + optExploreSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInViewStrict || setting.Service.Explore.RequireSigninView}) validation.AddBindingRules() @@ -854,13 +854,13 @@ func registerRoutes(m *web.Router) { 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: + switch ctx.ContextUser.Visibility { + case structs.VisibleTypePrivate: if ctx.Doer == nil || (ctx.ContextUser.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin) { ctx.NotFound(nil) return } - case ctx.ContextUser.Visibility == structs.VisibleTypeLimited: + case structs.VisibleTypeLimited: if ctx.Doer == nil { ctx.NotFound(nil) return @@ -1078,6 +1078,8 @@ func registerRoutes(m *web.Router) { m.Post("/avatar", web.Bind(forms.AvatarForm{}), repo_setting.SettingsAvatar) m.Post("/avatar/delete", repo_setting.SettingsDeleteAvatar) + m.Combo("/public_access").Get(repo_setting.PublicAccess).Post(repo_setting.PublicAccessPost) + m.Group("/collaboration", func() { m.Combo("").Get(repo_setting.Collaboration).Post(repo_setting.CollaborationPost) m.Post("/access_mode", repo_setting.ChangeCollaborationAccessMode) @@ -1209,7 +1211,7 @@ func registerRoutes(m *web.Router) { m.Get("/comments/{id}/attachments", repo.GetCommentAttachments) m.Get("/labels", repo.RetrieveLabelsForList, repo.Labels) m.Get("/milestones", repo.Milestones) - m.Get("/milestone/{id}", context.RepoRef(), repo.MilestoneIssuesAndPulls) + m.Get("/milestone/{id}", repo.MilestoneIssuesAndPulls) m.Get("/issues/suggestions", repo.IssueSuggestions) }, optSignIn, context.RepoAssignment, reqRepoIssuesOrPullsReader) // issue/pull attachments, labels, milestones // end "/{username}/{reponame}": view milestone, label, issue, pull, etc @@ -1223,9 +1225,9 @@ func registerRoutes(m *web.Router) { m.Group("/{username}/{reponame}", func() { // edit issues, pulls, labels, milestones, etc m.Group("/issues", func() { m.Group("/new", func() { - m.Combo("").Get(context.RepoRef(), repo.NewIssue). + m.Combo("").Get(repo.NewIssue). Post(web.Bind(forms.CreateIssueForm{}), repo.NewIssuePost) - m.Get("/choose", context.RepoRef(), repo.NewIssueChooseTemplate) + m.Get("/choose", repo.NewIssueChooseTemplate) }) m.Get("/search", repo.SearchRepoIssuesJSON) }, reqUnitIssuesReader) @@ -1288,7 +1290,7 @@ func registerRoutes(m *web.Router) { m.Post("/edit", web.Bind(forms.CreateLabelForm{}), repo.UpdateLabel) m.Post("/delete", repo.DeleteLabel) m.Post("/initialize", web.Bind(forms.InitializeLabelsForm{}), repo.InitializeLabels) - }, reqRepoIssuesOrPullsWriter, context.RepoRef()) + }, reqRepoIssuesOrPullsWriter) m.Group("/milestones", func() { m.Combo("/new").Get(repo.NewMilestone). @@ -1297,7 +1299,7 @@ func registerRoutes(m *web.Router) { m.Post("/{id}/edit", web.Bind(forms.CreateMilestoneForm{}), repo.EditMilestonePost) m.Post("/{id}/{action}", repo.ChangeMilestoneStatus) m.Post("/delete", repo.DeleteMilestone) - }, reqRepoIssuesOrPullsWriter, context.RepoRef()) + }, reqRepoIssuesOrPullsWriter) // FIXME: many "pulls" requests are sent to "issues" endpoints incorrectly, need to move these routes to the proper place m.Group("/issues", func() { @@ -1375,7 +1377,7 @@ func registerRoutes(m *web.Router) { m.Post("/delete", repo.DeleteRelease) m.Post("/attachments", repo.UploadReleaseAttachment) m.Post("/attachments/remove", repo.DeleteAttachment) - }, reqSignIn, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, context.RepoRef()) + }, reqSignIn, context.RepoMustNotBeArchived(), reqRepoReleaseWriter) m.Group("/releases", func() { m.Get("/edit/*", repo.EditRelease) m.Post("/edit/*", web.Bind(forms.EditReleaseForm{}), repo.EditReleasePost) @@ -1488,7 +1490,7 @@ func registerRoutes(m *web.Router) { }) m.Group("/recent-commits", func() { m.Get("", repo.RecentCommits) - m.Get("/data", repo.RecentCommitsData) + m.Get("/data", repo.CodeFrequencyData) // "recent-commits" also uses the same data as "code-frequency" }) }, reqUnitCodeReader) }, @@ -1504,19 +1506,19 @@ func registerRoutes(m *web.Router) { m.Get(".diff", repo.DownloadPullDiff) m.Get(".patch", repo.DownloadPullPatch) m.Group("/commits", func() { - m.Get("", context.RepoRef(), repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits) - m.Get("/list", context.RepoRef(), repo.GetPullCommits) - m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit) + m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits) + m.Get("/list", repo.GetPullCommits) + m.Get("/{sha:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit) }) m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), repo.MergePullRequest) m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest) m.Post("/update", repo.UpdatePullRequest) m.Post("/set_allow_maintainer_edit", web.Bind(forms.UpdateAllowEditsForm{}), repo.SetAllowEdits) - m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest) + m.Post("/cleanup", context.RepoMustNotBeArchived(), repo.CleanUpPullRequest) m.Group("/files", func() { - m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForAllCommitsOfPr) - m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit) - m.Get("/{shaFrom:[a-f0-9]{7,40}}..{shaTo:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange) + m.Get("", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForAllCommitsOfPr) + m.Get("/{sha:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit) + m.Get("/{shaFrom:[a-f0-9]{7,40}}..{shaTo:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange) m.Group("/reviews", func() { m.Get("/new_comment", repo.RenderNewCodeCommentForm) m.Post("/comments", web.Bind(forms.CodeCommentForm{}), repo.SetShowOutdatedComments, repo.CreateCodeComment) @@ -1603,7 +1605,7 @@ func registerRoutes(m *web.Router) { m.Get("/tree/*", repo.RedirectRepoTreeToSrc) // redirect "/owner/repo/tree/*" requests to "/owner/repo/src/*" m.Get("/blob/*", repo.RedirectRepoBlobToCommit) // redirect "/owner/repo/blob/*" requests to "/owner/repo/src/commit/*" - m.Get("/forks", context.RepoRef(), repo.Forks) + m.Get("/forks", repo.Forks) m.Get("/commit/{sha:([a-f0-9]{7,64})}.{ext:patch|diff}", repo.MustBeNotEmpty, repo.RawDiff) m.Post("/lastcommit/*", context.RepoRefByType(git.RefTypeCommit), repo.LastCommit) }, optSignIn, context.RepoAssignment, reqUnitCodeReader) diff --git a/routers/web/webfinger.go b/routers/web/webfinger.go index afcfdc8252..a4c9bf902b 100644 --- a/routers/web/webfinger.go +++ b/routers/web/webfinger.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" "net/url" + "strconv" "strings" user_model "code.gitea.io/gitea/models/user" @@ -85,10 +86,10 @@ func WebfingerQuery(ctx *context.Context) { aliases := []string{ u.HTMLURL(), - appURL.String() + "api/v1/activitypub/user-id/" + fmt.Sprint(u.ID), + appURL.String() + "api/v1/activitypub/user-id/" + strconv.FormatInt(u.ID, 10), } if !u.KeepEmailPrivate { - aliases = append(aliases, fmt.Sprintf("mailto:%s", u.Email)) + aliases = append(aliases, "mailto:"+u.Email) } links := []*webfingerLink{ @@ -104,7 +105,7 @@ func WebfingerQuery(ctx *context.Context) { { Rel: "self", Type: "application/activity+json", - Href: appURL.String() + "api/v1/activitypub/user-id/" + fmt.Sprint(u.ID), + Href: appURL.String() + "api/v1/activitypub/user-id/" + strconv.FormatInt(u.ID, 10), }, { Rel: "http://openid.net/specs/connect/1.0/issuer", diff --git a/services/actions/auth.go b/services/actions/auth.go index 1ef21f6e0e..12a8fba53f 100644 --- a/services/actions/auth.go +++ b/services/actions/auth.go @@ -4,6 +4,7 @@ package actions import ( + "errors" "fmt" "net/http" "strings" @@ -80,7 +81,7 @@ func ParseAuthorizationToken(req *http.Request) (int64, error) { parts := strings.SplitN(h, " ", 2) if len(parts) != 2 { log.Error("split token failed: %s", h) - return 0, fmt.Errorf("split token failed") + return 0, errors.New("split token failed") } return TokenToTaskID(parts[1]) @@ -100,7 +101,7 @@ func TokenToTaskID(token string) (int64, error) { c, ok := parsedToken.Claims.(*actionsClaims) if !parsedToken.Valid || !ok { - return 0, fmt.Errorf("invalid token claim") + return 0, errors.New("invalid token claim") } return c.TaskID, nil diff --git a/services/actions/auth_test.go b/services/actions/auth_test.go index 85e7409105..38d0ba7f82 100644 --- a/services/actions/auth_test.go +++ b/services/actions/auth_test.go @@ -18,7 +18,7 @@ func TestCreateAuthorizationToken(t *testing.T) { var taskID int64 = 23 token, err := CreateAuthorizationToken(taskID, 1, 2) assert.NoError(t, err) - assert.NotEqual(t, "", token) + assert.NotEmpty(t, token) claims := jwt.MapClaims{} _, err = jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) { return setting.GetGeneralTokenSigningSecret(), nil @@ -44,7 +44,7 @@ func TestParseAuthorizationToken(t *testing.T) { var taskID int64 = 23 token, err := CreateAuthorizationToken(taskID, 1, 2) assert.NoError(t, err) - assert.NotEqual(t, "", token) + assert.NotEmpty(t, token) headers := http.Header{} headers.Set("Authorization", "Bearer "+token) rTaskID, err := ParseAuthorizationToken(&http.Request{ diff --git a/services/actions/commit_status.go b/services/actions/commit_status.go index 94ab89a3b7..eb15d16061 100644 --- a/services/actions/commit_status.go +++ b/services/actions/commit_status.go @@ -5,6 +5,7 @@ package actions import ( "context" + "errors" "fmt" "path" @@ -51,7 +52,7 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er return fmt.Errorf("GetPushEventPayload: %w", err) } if payload.HeadCommit == nil { - return fmt.Errorf("head commit is missing in event payload") + return errors.New("head commit is missing in event payload") } sha = payload.HeadCommit.ID case // pull_request @@ -71,9 +72,9 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er return fmt.Errorf("GetPullRequestEventPayload: %w", err) } if payload.PullRequest == nil { - return fmt.Errorf("pull request is missing in event payload") + return errors.New("pull request is missing in event payload") } else if payload.PullRequest.Head == nil { - return fmt.Errorf("head of pull request is missing in event payload") + return errors.New("head of pull request is missing in event payload") } sha = payload.PullRequest.Head.Sha case webhook_module.HookEventRelease: diff --git a/services/actions/context.go b/services/actions/context.go index d14728fae4..2667e18337 100644 --- a/services/actions/context.go +++ b/services/actions/context.go @@ -6,6 +6,7 @@ package actions import ( "context" "fmt" + "strconv" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" @@ -68,7 +69,7 @@ func GenerateGiteaContext(run *actions_model.ActionRun, job *actions_model.Actio "repositoryUrl": run.Repo.HTMLURL(), // string, The Git URL to the repository. For example, git://github.com/codertocat/hello-world.git. "retention_days": "", // string, The number of days that workflow run logs and artifacts are kept. "run_id": "", // string, A unique number for each workflow run within a repository. This number does not change if you re-run the workflow run. - "run_number": fmt.Sprint(run.Index), // string, A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run. + "run_number": strconv.FormatInt(run.Index, 10), // string, A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run. "run_attempt": "", // string, A unique number for each attempt of a particular workflow run in a repository. This number begins at 1 for the workflow run's first attempt, and increments with each re-run. "secret_source": "Actions", // string, The source of a secret used in a workflow. Possible values are None, Actions, Dependabot, or Codespaces. "server_url": setting.AppURL, // string, The URL of the GitHub server. For example: https://github.com. @@ -83,8 +84,8 @@ func GenerateGiteaContext(run *actions_model.ActionRun, job *actions_model.Actio if job != nil { gitContext["job"] = job.JobID - gitContext["run_id"] = fmt.Sprint(job.RunID) - gitContext["run_attempt"] = fmt.Sprint(job.Attempt) + gitContext["run_id"] = strconv.FormatInt(job.RunID, 10) + gitContext["run_attempt"] = strconv.FormatInt(job.Attempt, 10) } return gitContext diff --git a/services/actions/task.go b/services/actions/task.go index 9c8198206a..6a547c1c12 100644 --- a/services/actions/task.go +++ b/services/actions/task.go @@ -5,6 +5,7 @@ package actions import ( "context" + "errors" "fmt" actions_model "code.gitea.io/gitea/models/actions" @@ -39,7 +40,7 @@ func PickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv if err != nil { return nil, false, err } - return nil, false, fmt.Errorf("runner has been removed") + return nil, false, errors.New("runner has been removed") } } diff --git a/services/asymkey/commit.go b/services/asymkey/commit.go index df29133972..105782a93a 100644 --- a/services/asymkey/commit.go +++ b/services/asymkey/commit.go @@ -5,15 +5,19 @@ package asymkey import ( "context" + "fmt" "strings" asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/cache" + "code.gitea.io/gitea/modules/cachegroup" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "github.com/42wim/sshsig" "github.com/ProtonMail/go-crypto/openpgp/packet" ) @@ -57,7 +61,7 @@ func ParseCommitWithSignatureCommitter(ctx context.Context, c *git.Commit, commi // If this a SSH signature handle it differently if strings.HasPrefix(c.Signature.Signature, "-----BEGIN SSH SIGNATURE-----") { - return asymkey_model.ParseCommitWithSSHSignature(ctx, c, committer) + return ParseCommitWithSSHSignature(ctx, c, committer) } // Parsing signature @@ -113,7 +117,7 @@ func ParseCommitWithSignatureCommitter(ctx context.Context, c *git.Commit, commi } } - committerEmailAddresses, _ := user_model.GetEmailAddresses(ctx, committer.ID) + committerEmailAddresses, _ := cache.GetWithContextCache(ctx, cachegroup.UserEmailAddresses, committer.ID, user_model.GetEmailAddresses) activated := false for _, e := range committerEmailAddresses { if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) { @@ -207,10 +211,9 @@ func checkKeyEmails(ctx context.Context, email string, keys ...*asymkey_model.GP } if key.Verified && key.OwnerID != 0 { if uid != key.OwnerID { - userEmails, _ = user_model.GetEmailAddresses(ctx, key.OwnerID) + userEmails, _ = cache.GetWithContextCache(ctx, cachegroup.UserEmailAddresses, key.OwnerID, user_model.GetEmailAddresses) uid = key.OwnerID - user = &user_model.User{ID: uid} - _, _ = user_model.GetUser(ctx, user) + user, _ = cache.GetWithContextCache(ctx, cachegroup.User, uid, user_model.GetUserByID) } for _, e := range userEmails { if e.IsActivated && (email == "" || strings.EqualFold(e.Email, email)) { @@ -229,10 +232,7 @@ func HashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload s if keyID == "" { return nil } - keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{ - KeyID: keyID, - IncludeSubKeys: true, - }) + keys, err := cache.GetWithContextCache(ctx, cachegroup.GPGKeyWithSubKeys, keyID, asymkey_model.FindGPGKeyWithSubKeys) if err != nil { log.Error("GetGPGKeysByKeyID: %v", err) return &asymkey_model.CommitVerification{ @@ -247,10 +247,7 @@ func HashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload s for _, key := range keys { var primaryKeys []*asymkey_model.GPGKey if key.PrimaryKeyID != "" { - primaryKeys, err = db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{ - KeyID: key.PrimaryKeyID, - IncludeSubKeys: true, - }) + primaryKeys, err = cache.GetWithContextCache(ctx, cachegroup.GPGKeyWithSubKeys, key.PrimaryKeyID, asymkey_model.FindGPGKeyWithSubKeys) if err != nil { log.Error("GetGPGKeysByKeyID: %v", err) return &asymkey_model.CommitVerification{ @@ -270,8 +267,8 @@ func HashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload s Name: name, Email: email, } - if key.OwnerID != 0 { - owner, err := user_model.GetUserByID(ctx, key.OwnerID) + if key.OwnerID > 0 { + owner, err := cache.GetWithContextCache(ctx, cachegroup.User, key.OwnerID, user_model.GetUserByID) if err == nil { signer = owner } else if !user_model.IsErrUserNotExist(err) { @@ -361,3 +358,65 @@ func VerifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, si } return nil } + +// ParseCommitWithSSHSignature check if signature is good against keystore. +func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *user_model.User) *asymkey_model.CommitVerification { + // Now try to associate the signature with the committer, if present + if committer.ID != 0 { + keys, err := db.Find[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{ + OwnerID: committer.ID, + NotKeytype: asymkey_model.KeyTypePrincipal, + }) + if err != nil { // Skipping failed to get ssh keys of user + log.Error("ListPublicKeys: %v", err) + return &asymkey_model.CommitVerification{ + CommittingUser: committer, + Verified: false, + Reason: "gpg.error.failed_retrieval_gpg_keys", + } + } + + committerEmailAddresses, err := cache.GetWithContextCache(ctx, cachegroup.UserEmailAddresses, committer.ID, user_model.GetEmailAddresses) + if err != nil { + log.Error("GetEmailAddresses: %v", err) + } + + activated := false + for _, e := range committerEmailAddresses { + if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) { + activated = true + break + } + } + + for _, k := range keys { + if k.Verified && activated { + commitVerification := verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, k, committer, committer, c.Committer.Email) + if commitVerification != nil { + return commitVerification + } + } + } + } + + return &asymkey_model.CommitVerification{ + CommittingUser: committer, + Verified: false, + Reason: asymkey_model.NoKeyFound, + } +} + +func verifySSHCommitVerification(sig, payload string, k *asymkey_model.PublicKey, committer, signer *user_model.User, email string) *asymkey_model.CommitVerification { + if err := sshsig.Verify(strings.NewReader(payload), []byte(sig), []byte(k.Content), "git"); err != nil { + return nil + } + + return &asymkey_model.CommitVerification{ // Everything is ok + CommittingUser: committer, + Verified: true, + Reason: fmt.Sprintf("%s / %s", signer.Name, k.Fingerprint), + SigningUser: signer, + SigningSSHKey: k, + SigningEmail: email, + } +} diff --git a/services/attachment/attachment_test.go b/services/attachment/attachment_test.go index 142bcfe629..65475836be 100644 --- a/services/attachment/attachment_test.go +++ b/services/attachment/attachment_test.go @@ -41,6 +41,6 @@ func TestUploadAttachment(t *testing.T) { attachment, err := repo_model.GetAttachmentByUUID(db.DefaultContext, attach.UUID) assert.NoError(t, err) - assert.EqualValues(t, user.ID, attachment.UploaderID) + assert.Equal(t, user.ID, attachment.UploaderID) assert.Equal(t, int64(0), attachment.DownloadCount) } diff --git a/services/auth/auth.go b/services/auth/auth.go index f7deeb4c50..fb6612290b 100644 --- a/services/auth/auth.go +++ b/services/auth/auth.go @@ -62,14 +62,14 @@ func (a *authPathDetector) isAPIPath() bool { // isAttachmentDownload check if request is a file download (GET) with URL to an attachment func (a *authPathDetector) isAttachmentDownload() bool { - return strings.HasPrefix(a.req.URL.Path, "/attachments/") && a.req.Method == "GET" + return strings.HasPrefix(a.req.URL.Path, "/attachments/") && a.req.Method == http.MethodGet } func (a *authPathDetector) isFeedRequest(req *http.Request) bool { if !setting.Other.EnableFeed { return false } - if req.Method != "GET" { + if req.Method != http.MethodGet { return false } return a.vars.feedPathRe.MatchString(req.URL.Path) || a.vars.feedRefPathRe.MatchString(req.URL.Path) diff --git a/services/auth/auth_test.go b/services/auth/auth_test.go index b8d3396163..c45f312c90 100644 --- a/services/auth/auth_test.go +++ b/services/auth/auth_test.go @@ -97,7 +97,7 @@ func Test_isGitRawOrLFSPath(t *testing.T) { defer test.MockVariableValue(&setting.LFS.StartServer)() for _, tt := range tests { t.Run(tt.path, func(t *testing.T) { - req, _ := http.NewRequest("POST", "http://localhost"+tt.path, nil) + req, _ := http.NewRequest(http.MethodPost, "http://localhost"+tt.path, nil) setting.LFS.StartServer = false assert.Equal(t, tt.want, newAuthPathDetector(req).isGitRawOrAttachOrLFSPath()) @@ -119,7 +119,7 @@ func Test_isGitRawOrLFSPath(t *testing.T) { } for _, tt := range lfsTests { t.Run(tt, func(t *testing.T) { - req, _ := http.NewRequest("POST", tt, nil) + req, _ := http.NewRequest(http.MethodPost, tt, nil) setting.LFS.StartServer = false got := newAuthPathDetector(req).isGitRawOrAttachOrLFSPath() assert.Equalf(t, setting.LFS.StartServer, got, "isGitOrLFSPath(%q) = %v, want %v, %v", tt, got, setting.LFS.StartServer, globalVars().gitRawOrAttachPathRe.MatchString(tt)) @@ -148,7 +148,7 @@ func Test_isFeedRequest(t *testing.T) { } for _, tt := range tests { t.Run(tt.path, func(t *testing.T) { - req, _ := http.NewRequest("GET", "http://localhost"+tt.path, nil) + req, _ := http.NewRequest(http.MethodGet, "http://localhost"+tt.path, nil) assert.Equal(t, tt.want, newAuthPathDetector(req).isFeedRequest(req)) }) } diff --git a/services/auth/httpsign.go b/services/auth/httpsign.go index 83a36bef23..25e96ff32d 100644 --- a/services/auth/httpsign.go +++ b/services/auth/httpsign.go @@ -134,7 +134,7 @@ func VerifyCert(r *http.Request) (*asymkey_model.PublicKey, error) { // Check if it's really a ssh certificate cert, ok := pk.(*ssh.Certificate) if !ok { - return nil, fmt.Errorf("no certificate found") + return nil, errors.New("no certificate found") } c := &ssh.CertChecker{ @@ -153,7 +153,7 @@ func VerifyCert(r *http.Request) (*asymkey_model.PublicKey, error) { // check the CA of the cert if !c.IsUserAuthority(cert.SignatureKey) { - return nil, fmt.Errorf("CA check failed") + return nil, errors.New("CA check failed") } // Create a verifier @@ -191,7 +191,7 @@ func VerifyCert(r *http.Request) (*asymkey_model.PublicKey, error) { } // No public key matching a principal in the certificate is registered in gitea - return nil, fmt.Errorf("no valid principal found") + return nil, errors.New("no valid principal found") } // doVerify iterates across the provided public keys attempting the verify the current request against each key in turn diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index 6a6c60cd40..7aee761d0d 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -5,7 +5,6 @@ package ldap import ( "context" - "fmt" "strings" asymkey_model "code.gitea.io/gitea/models/asymkey" @@ -41,7 +40,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u sr.Username = userName } if sr.Mail == "" { - sr.Mail = fmt.Sprintf("%s@localhost.local", sr.Username) + sr.Mail = sr.Username + "@localhost.local" } isAttributeSSHPublicKeySet := strings.TrimSpace(source.AttributeSSHPublicKey) != "" diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index e817bf1fa9..ff36db2955 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -5,7 +5,6 @@ package ldap import ( "context" - "fmt" "strings" asymkey_model "code.gitea.io/gitea/models/asymkey" @@ -106,7 +105,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { } if su.Mail == "" { - su.Mail = fmt.Sprintf("%s@localhost.local", su.Username) + su.Mail = su.Username + "@localhost.local" } fullName := composeFullName(su.Name, su.Surname, su.Username) diff --git a/services/auth/source/oauth2/source_sync_test.go b/services/auth/source/oauth2/source_sync_test.go index aacb4286a1..08d841cc90 100644 --- a/services/auth/source/oauth2/source_sync_test.go +++ b/services/auth/source/oauth2/source_sync_test.go @@ -88,8 +88,8 @@ func TestSource(t *testing.T) { ok, err := user_model.GetExternalLogin(t.Context(), e) assert.NoError(t, err) assert.True(t, ok) - assert.Equal(t, "", e.RefreshToken) - assert.Equal(t, "", e.AccessToken) + assert.Empty(t, e.RefreshToken) + assert.Empty(t, e.AccessToken) u, err := user_model.GetUserByID(t.Context(), user.ID) assert.NoError(t, err) diff --git a/services/automerge/automerge.go b/services/automerge/automerge.go index 62d560ff94..9d2f7f4857 100644 --- a/services/automerge/automerge.go +++ b/services/automerge/automerge.go @@ -36,7 +36,7 @@ func Init() error { prAutoMergeQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "pr_auto_merge", handler) if prAutoMergeQueue == nil { - return fmt.Errorf("unable to create pr_auto_merge queue") + return errors.New("unable to create pr_auto_merge queue") } go graceful.GetManager().RunWithCancel(prAutoMergeQueue) return nil diff --git a/services/context/access_log.go b/services/context/access_log.go index 925e4a3056..caade113a7 100644 --- a/services/context/access_log.go +++ b/services/context/access_log.go @@ -5,7 +5,6 @@ package context import ( "bytes" - "fmt" "net" "net/http" "strings" @@ -47,7 +46,7 @@ func parseRequestIDFromRequestHeader(req *http.Request) string { } } if len(requestID) > maxRequestIDByteLength { - requestID = fmt.Sprintf("%s...", requestID[:maxRequestIDByteLength]) + requestID = requestID[:maxRequestIDByteLength] + "..." } return requestID } diff --git a/services/context/access_log_test.go b/services/context/access_log_test.go index c40ef9acd1..139a6eb217 100644 --- a/services/context/access_log_test.go +++ b/services/context/access_log_test.go @@ -59,7 +59,7 @@ func TestAccessLogger(t *testing.T) { recorder.logger = mockLogger req := &http.Request{ RemoteAddr: "remote-addr", - Method: "GET", + Method: http.MethodGet, Proto: "https", URL: &url.URL{Path: "/path"}, } diff --git a/services/context/api.go b/services/context/api.go index 10fad419ba..6e3b635ce3 100644 --- a/services/context/api.go +++ b/services/context/api.go @@ -9,6 +9,7 @@ import ( "fmt" "net/http" "net/url" + "strconv" "strings" "code.gitea.io/gitea/models/unit" @@ -168,7 +169,7 @@ func genAPILinks(curURL *url.URL, total, pageSize, curPage int) []string { if paginater.HasNext() { u := *curURL queries := u.Query() - queries.Set("page", fmt.Sprintf("%d", paginater.Next())) + queries.Set("page", strconv.Itoa(paginater.Next())) u.RawQuery = queries.Encode() links = append(links, fmt.Sprintf("<%s%s>; rel=\"next\"", setting.AppURL, u.RequestURI()[1:])) @@ -176,7 +177,7 @@ func genAPILinks(curURL *url.URL, total, pageSize, curPage int) []string { if !paginater.IsLast() { u := *curURL queries := u.Query() - queries.Set("page", fmt.Sprintf("%d", paginater.TotalPages())) + queries.Set("page", strconv.Itoa(paginater.TotalPages())) u.RawQuery = queries.Encode() links = append(links, fmt.Sprintf("<%s%s>; rel=\"last\"", setting.AppURL, u.RequestURI()[1:])) @@ -192,7 +193,7 @@ func genAPILinks(curURL *url.URL, total, pageSize, curPage int) []string { if paginater.HasPrevious() { u := *curURL queries := u.Query() - queries.Set("page", fmt.Sprintf("%d", paginater.Previous())) + queries.Set("page", strconv.Itoa(paginater.Previous())) u.RawQuery = queries.Encode() links = append(links, fmt.Sprintf("<%s%s>; rel=\"prev\"", setting.AppURL, u.RequestURI()[1:])) @@ -225,7 +226,7 @@ func APIContexter() func(http.Handler) http.Handler { ctx.SetContextValue(apiContextKey, ctx) // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid. - if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") { + if ctx.Req.Method == http.MethodPost && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") { if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size ctx.APIErrorInternal(err) return @@ -297,7 +298,7 @@ func RepoRefForAPI(next http.Handler) http.Handler { } if ctx.Repo.GitRepo == nil { - ctx.APIErrorInternal(fmt.Errorf("no open git repo")) + ctx.APIErrorInternal(errors.New("no open git repo")) return } diff --git a/services/context/api_test.go b/services/context/api_test.go index 911a49949e..87d74004db 100644 --- a/services/context/api_test.go +++ b/services/context/api_test.go @@ -45,6 +45,6 @@ func TestGenAPILinks(t *testing.T) { links := genAPILinks(u, 100, 20, curPage) - assert.EqualValues(t, links, response) + assert.Equal(t, links, response) } } diff --git a/services/context/base.go b/services/context/base.go index 3701668bf6..f3f92b7eeb 100644 --- a/services/context/base.go +++ b/services/context/base.go @@ -8,6 +8,7 @@ import ( "html/template" "io" "net/http" + "strconv" "strings" "code.gitea.io/gitea/modules/httplib" @@ -53,7 +54,7 @@ func (b *Base) AppendAccessControlExposeHeaders(names ...string) { // SetTotalCountHeader set "X-Total-Count" header func (b *Base) SetTotalCountHeader(total int64) { - b.RespHeader().Set("X-Total-Count", fmt.Sprint(total)) + b.RespHeader().Set("X-Total-Count", strconv.FormatInt(total, 10)) b.AppendAccessControlExposeHeaders("X-Total-Count") } diff --git a/services/context/base_test.go b/services/context/base_test.go index b936b76f58..2a4f86dddf 100644 --- a/services/context/base_test.go +++ b/services/context/base_test.go @@ -15,7 +15,7 @@ import ( func TestRedirect(t *testing.T) { setting.IsInTesting = true - req, _ := http.NewRequest("GET", "/", nil) + req, _ := http.NewRequest(http.MethodGet, "/", nil) cases := []struct { url string @@ -36,7 +36,7 @@ func TestRedirect(t *testing.T) { assert.Equal(t, c.keep, has, "url = %q", c.url) } - req, _ = http.NewRequest("GET", "/", nil) + req, _ = http.NewRequest(http.MethodGet, "/", nil) resp := httptest.NewRecorder() req.Header.Add("HX-Request", "true") b := NewBaseContextForTest(resp, req) diff --git a/services/context/context.go b/services/context/context.go index 79bc5da920..3c0ac54fc1 100644 --- a/services/context/context.go +++ b/services/context/context.go @@ -184,7 +184,7 @@ func Contexter() func(next http.Handler) http.Handler { }) // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid. - if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") { + if ctx.Req.Method == http.MethodPost && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") { if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size ctx.ServerError("ParseMultipartForm", err) return diff --git a/services/context/context_response.go b/services/context/context_response.go index 61b432395a..4e11e29b69 100644 --- a/services/context/context_response.go +++ b/services/context/context_response.go @@ -150,7 +150,7 @@ func (ctx *Context) notFoundInternal(logMsg string, logErr error) { ctx.Data["IsRepo"] = ctx.Repo.Repository != nil ctx.Data["Title"] = "Page Not Found" - ctx.HTML(http.StatusNotFound, templates.TplName("status/404")) + ctx.HTML(http.StatusNotFound, "status/404") } // ServerError displays a 500 (Internal Server Error) page and prints the given error, if any. diff --git a/services/context/org.go b/services/context/org.go index 992a48afa0..c8b6ed09b7 100644 --- a/services/context/org.go +++ b/services/context/org.go @@ -182,7 +182,7 @@ func OrgAssignment(opts OrgAssignmentOptions) func(ctx *Context) { return } for _, team := range teams { - if team.IncludesAllRepositories && team.AccessMode >= perm.AccessModeAdmin { + if team.IncludesAllRepositories && team.HasAdminAccess() { shouldSeeAllTeams = true break } @@ -228,7 +228,7 @@ func OrgAssignment(opts OrgAssignmentOptions) func(ctx *Context) { return } - ctx.Org.IsTeamAdmin = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.AccessMode >= perm.AccessModeAdmin + ctx.Org.IsTeamAdmin = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.HasAdminAccess() ctx.Data["IsTeamAdmin"] = ctx.Org.IsTeamAdmin if opts.RequireTeamAdmin && !ctx.Org.IsTeamAdmin { ctx.NotFound(err) diff --git a/services/context/package.go b/services/context/package.go index 64bb4f3ecd..8b722932b1 100644 --- a/services/context/package.go +++ b/services/context/package.go @@ -93,7 +93,7 @@ func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, any)) *Package } func determineAccessMode(ctx *Base, pkg *Package, doer *user_model.User) (perm.AccessMode, error) { - if setting.Service.RequireSignInView && (doer == nil || doer.IsGhost()) { + if setting.Service.RequireSignInViewStrict && (doer == nil || doer.IsGhost()) { return perm.AccessModeNone, nil } diff --git a/services/context/private.go b/services/context/private.go index 51857da8fe..3f7637518b 100644 --- a/services/context/private.go +++ b/services/context/private.go @@ -5,7 +5,6 @@ package context import ( "context" - "fmt" "net/http" "time" @@ -82,7 +81,7 @@ func OverrideContext() func(http.Handler) http.Handler { // We now need to override the request context as the base for our work because even if the request is cancelled we have to continue this work ctx := GetPrivateContext(req) var finished func() - ctx.Override, _, finished = process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), fmt.Sprintf("PrivateContext: %s", ctx.Req.RequestURI), process.RequestProcessType, true) + ctx.Override, _, finished = process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "PrivateContext: "+ctx.Req.RequestURI, process.RequestProcessType, true) defer finished() next.ServeHTTP(ctx.Resp, ctx.Req) }) diff --git a/services/context/repo.go b/services/context/repo.go index 7d0b44c42f..6f5c772f5e 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -669,12 +669,6 @@ func RepoAssignment(ctx *Context) { const headRefName = "HEAD" -func RepoRef() func(*Context) { - // old code does: return RepoRefByType(git.RefTypeBranch) - // in most cases, it is an abuse, so we just disable it completely and fix the abuses one by one (if there is anything wrong) - return nil -} - func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool) string { refName := "" parts := strings.Split(path, "/") @@ -817,9 +811,9 @@ func RepoRefByType(detectRefType git.RefType) func(*Context) { if reqPath == "" { refShortName = ctx.Repo.Repository.DefaultBranch if !gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, refShortName) { - brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 1) + brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 1) if err == nil && len(brs) != 0 { - refShortName = brs[0].Name + refShortName = brs[0] } else if len(brs) == 0 { log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path) } else { diff --git a/services/context/upload/upload.go b/services/context/upload/upload.go index da4370a433..12aa485aa7 100644 --- a/services/context/upload/upload.go +++ b/services/context/upload/upload.go @@ -87,14 +87,15 @@ func Verify(buf []byte, fileName, allowedTypesStr string) error { // AddUploadContext renders template values for dropzone func AddUploadContext(ctx *context.Context, uploadType string) { - if uploadType == "release" { + switch uploadType { + case "release": ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/releases/attachments" ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/releases/attachments/remove" ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/releases/attachments" ctx.Data["UploadAccepts"] = strings.ReplaceAll(setting.Repository.Release.AllowedTypes, "|", ",") ctx.Data["UploadMaxFiles"] = setting.Attachment.MaxFiles ctx.Data["UploadMaxSize"] = setting.Attachment.MaxSize - } else if uploadType == "comment" { + case "comment": ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/issues/attachments" ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/issues/attachments/remove" if len(ctx.PathParam("index")) > 0 { @@ -105,7 +106,7 @@ func AddUploadContext(ctx *context.Context, uploadType string) { ctx.Data["UploadAccepts"] = strings.ReplaceAll(setting.Attachment.AllowedTypes, "|", ",") ctx.Data["UploadMaxFiles"] = setting.Attachment.MaxFiles ctx.Data["UploadMaxSize"] = setting.Attachment.MaxSize - } else if uploadType == "repo" { + case "repo": ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/upload-file" ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/upload-remove" ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/upload-file" diff --git a/services/convert/git_commit_test.go b/services/convert/git_commit_test.go index 73cb5e8c71..ad1cc0eca3 100644 --- a/services/convert/git_commit_test.go +++ b/services/convert/git_commit_test.go @@ -33,7 +33,7 @@ func TestToCommitMeta(t *testing.T) { commitMeta := ToCommitMeta(headRepo, tag) assert.NotNil(t, commitMeta) - assert.EqualValues(t, &api.CommitMeta{ + assert.Equal(t, &api.CommitMeta{ SHA: sha1.EmptyObjectID().String(), URL: util.URLJoin(headRepo.APIURL(), "git/commits", sha1.EmptyObjectID().String()), Created: time.Unix(0, 0), diff --git a/services/convert/pull.go b/services/convert/pull.go index 928534ce5e..7798bebb08 100644 --- a/services/convert/pull.go +++ b/services/convert/pull.go @@ -14,6 +14,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/cache" + "code.gitea.io/gitea/modules/cachegroup" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" @@ -28,8 +29,8 @@ import ( // Optional - Merger func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User) *api.PullRequest { var ( - baseBranch *git.Branch - headBranch *git.Branch + baseBranch string + headBranch string baseCommit *git.Commit err error ) @@ -60,14 +61,14 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u doerID = doer.ID } - const repoDoerPermCacheKey = "repo_doer_perm_cache" - p, err := cache.GetWithContextCache(ctx, repoDoerPermCacheKey, fmt.Sprintf("%d_%d", pr.BaseRepoID, doerID), - func() (access_model.Permission, error) { + repoUserPerm, err := cache.GetWithContextCache(ctx, cachegroup.RepoUserPermission, fmt.Sprintf("%d-%d", pr.BaseRepoID, doerID), + func(ctx context.Context, _ string) (access_model.Permission, error) { return access_model.GetUserRepoPermission(ctx, pr.BaseRepo, doer) - }) + }, + ) if err != nil { log.Error("GetUserRepoPermission[%d]: %v", pr.BaseRepoID, err) - p.AccessMode = perm.AccessModeNone + repoUserPerm.AccessMode = perm.AccessModeNone } apiPullRequest := &api.PullRequest{ @@ -107,7 +108,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u Name: pr.BaseBranch, Ref: pr.BaseBranch, RepoID: pr.BaseRepoID, - Repository: ToRepo(ctx, pr.BaseRepo, p), + Repository: ToRepo(ctx, pr.BaseRepo, repoUserPerm), }, Head: &api.PRBranchInfo{ Name: pr.HeadBranch, @@ -150,16 +151,16 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u } defer gitRepo.Close() - baseBranch, err = gitRepo.GetBranch(pr.BaseBranch) - if err != nil && !git.IsErrBranchNotExist(err) { + exist, err := git_model.IsBranchExist(ctx, pr.BaseRepoID, pr.BaseBranch) + if err != nil { log.Error("GetBranch[%s]: %v", pr.BaseBranch, err) return nil } - if err == nil { - baseCommit, err = baseBranch.GetCommit() + if exist { + baseCommit, err = gitRepo.GetBranchCommit(pr.BaseBranch) if err != nil && !git.IsErrNotExist(err) { - log.Error("GetCommit[%s]: %v", baseBranch.Name, err) + log.Error("GetCommit[%s]: %v", baseBranch, err) return nil } @@ -169,13 +170,6 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u } if pr.Flow == issues_model.PullRequestFlowAGit { - gitRepo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo) - if err != nil { - log.Error("OpenRepository[%s]: %v", pr.GetGitRefName(), err) - return nil - } - defer gitRepo.Close() - apiPullRequest.Head.Sha, err = gitRepo.GetRefCommitID(pr.GetGitRefName()) if err != nil { log.Error("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err) @@ -203,8 +197,8 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u } defer headGitRepo.Close() - headBranch, err = headGitRepo.GetBranch(pr.HeadBranch) - if err != nil && !git.IsErrBranchNotExist(err) { + exist, err = git_model.IsBranchExist(ctx, pr.HeadRepoID, pr.HeadBranch) + if err != nil { log.Error("GetBranch[%s]: %v", pr.HeadBranch, err) return nil } @@ -215,7 +209,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u endCommitID string ) - if git.IsErrBranchNotExist(err) { + if !exist { headCommitID, err := headGitRepo.GetRefCommitID(apiPullRequest.Head.Ref) if err != nil && !git.IsErrNotExist(err) { log.Error("GetCommit[%s]: %v", pr.HeadBranch, err) @@ -226,9 +220,9 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u endCommitID = headCommitID } } else { - commit, err := headBranch.GetCommit() + commit, err := headGitRepo.GetBranchCommit(pr.HeadBranch) if err != nil && !git.IsErrNotExist(err) { - log.Error("GetCommit[%s]: %v", headBranch.Name, err) + log.Error("GetCommit[%s]: %v", headBranch, err) return nil } if err == nil { @@ -389,7 +383,7 @@ func ToAPIPullRequests(ctx context.Context, baseRepo *repo_model.Repository, prs }, Head: &api.PRBranchInfo{ Name: pr.HeadBranch, - Ref: fmt.Sprintf("%s%d/head", git.PullPrefix, pr.Index), + Ref: pr.GetGitRefName(), RepoID: -1, }, } @@ -422,72 +416,43 @@ func ToAPIPullRequests(ctx context.Context, baseRepo *repo_model.Repository, prs return nil, err } } - if baseBranch != nil { apiPullRequest.Base.Sha = baseBranch.CommitID } - if pr.Flow == issues_model.PullRequestFlowAGit { - apiPullRequest.Head.Sha, err = gitRepo.GetRefCommitID(pr.GetGitRefName()) + // pull request head branch, both repository and branch could not exist + if pr.HeadRepo != nil { + apiPullRequest.Head.RepoID = pr.HeadRepo.ID + exist, err := git_model.IsBranchExist(ctx, pr.HeadRepo.ID, pr.HeadBranch) if err != nil { - log.Error("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err) + log.Error("IsBranchExist[%d]: %v", pr.HeadRepo.ID, err) return nil, err } - apiPullRequest.Head.RepoID = pr.BaseRepoID - apiPullRequest.Head.Repository = apiPullRequest.Base.Repository - apiPullRequest.Head.Name = "" + if exist { + apiPullRequest.Head.Ref = pr.HeadBranch + } + } + if apiPullRequest.Head.Ref == "" { + apiPullRequest.Head.Ref = pr.GetGitRefName() } - var headGitRepo *git.Repository - if pr.HeadRepo != nil && pr.Flow == issues_model.PullRequestFlowGithub { - if pr.HeadRepoID == pr.BaseRepoID { - apiPullRequest.Head.RepoID = pr.HeadRepo.ID - apiPullRequest.Head.Repository = apiRepo - headGitRepo = gitRepo - } else { - p, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, doer) - if err != nil { - log.Error("GetUserRepoPermission[%d]: %v", pr.HeadRepoID, err) - p.AccessMode = perm.AccessModeNone - } - - apiPullRequest.Head.RepoID = pr.HeadRepo.ID - apiPullRequest.Head.Repository = ToRepo(ctx, pr.HeadRepo, p) - - headGitRepo, err = gitrepo.OpenRepository(ctx, pr.HeadRepo) - if err != nil { - log.Error("OpenRepository[%s]: %v", pr.HeadRepo.RepoPath(), err) - return nil, err - } - defer headGitRepo.Close() + if pr.HeadRepoID == pr.BaseRepoID { + apiPullRequest.Head.Repository = apiPullRequest.Base.Repository + } else { + p, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, doer) + if err != nil { + log.Error("GetUserRepoPermission[%d]: %v", pr.HeadRepoID, err) + p.AccessMode = perm.AccessModeNone } + apiPullRequest.Head.Repository = ToRepo(ctx, pr.HeadRepo, p) + } - headBranch, err := headGitRepo.GetBranch(pr.HeadBranch) - if err != nil && !git.IsErrBranchNotExist(err) { - log.Error("GetBranch[%s]: %v", pr.HeadBranch, err) - return nil, err - } - - if git.IsErrBranchNotExist(err) { - headCommitID, err := headGitRepo.GetRefCommitID(apiPullRequest.Head.Ref) - if err != nil && !git.IsErrNotExist(err) { - log.Error("GetCommit[%s]: %v", pr.HeadBranch, err) - return nil, err - } - if err == nil { - apiPullRequest.Head.Sha = headCommitID - } - } else { - commit, err := headBranch.GetCommit() - if err != nil && !git.IsErrNotExist(err) { - log.Error("GetCommit[%s]: %v", headBranch.Name, err) - return nil, err - } - if err == nil { - apiPullRequest.Head.Ref = pr.HeadBranch - apiPullRequest.Head.Sha = commit.ID.String() - } - } + if pr.Flow == issues_model.PullRequestFlowAGit { + apiPullRequest.Head.Name = "" + } + apiPullRequest.Head.Sha, err = gitRepo.GetRefCommitID(pr.GetGitRefName()) + if err != nil { + log.Error("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err) } if len(apiPullRequest.Head.Sha) == 0 && len(apiPullRequest.Head.Ref) != 0 { diff --git a/services/convert/pull_review_test.go b/services/convert/pull_review_test.go index a1296fafd4..d0a077ab24 100644 --- a/services/convert/pull_review_test.go +++ b/services/convert/pull_review_test.go @@ -19,8 +19,8 @@ func Test_ToPullReview(t *testing.T) { reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) review := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 6}) - assert.EqualValues(t, reviewer.ID, review.ReviewerID) - assert.EqualValues(t, issues_model.ReviewTypePending, review.Type) + assert.Equal(t, reviewer.ID, review.ReviewerID) + assert.Equal(t, issues_model.ReviewTypePending, review.Type) reviewList := []*issues_model.Review{review} diff --git a/services/convert/pull_test.go b/services/convert/pull_test.go index e069fa4a68..cd86283c8a 100644 --- a/services/convert/pull_test.go +++ b/services/convert/pull_test.go @@ -27,7 +27,7 @@ func TestPullRequest_APIFormat(t *testing.T) { assert.NoError(t, pr.LoadIssue(db.DefaultContext)) apiPullRequest := ToAPIPullRequest(git.DefaultContext, pr, nil) assert.NotNil(t, apiPullRequest) - assert.EqualValues(t, &structs.PRBranchInfo{ + assert.Equal(t, &structs.PRBranchInfo{ Name: "branch1", Ref: "refs/pull/2/head", Sha: "4a357436d925b5c974181ff12a994538ddc5a269", diff --git a/services/convert/release_test.go b/services/convert/release_test.go index 201b27e16d..bb618c9ca3 100644 --- a/services/convert/release_test.go +++ b/services/convert/release_test.go @@ -23,6 +23,6 @@ func TestRelease_ToRelease(t *testing.T) { apiRelease := ToAPIRelease(db.DefaultContext, repo1, release1) assert.NotNil(t, apiRelease) assert.EqualValues(t, 1, apiRelease.ID) - assert.EqualValues(t, "https://try.gitea.io/api/v1/repos/user2/repo1/releases/1", apiRelease.URL) - assert.EqualValues(t, "https://try.gitea.io/api/v1/repos/user2/repo1/releases/1/assets", apiRelease.UploadURL) + assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/releases/1", apiRelease.URL) + assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/releases/1/assets", apiRelease.UploadURL) } diff --git a/services/convert/user_test.go b/services/convert/user_test.go index 4b1effc7aa..199d500732 100644 --- a/services/convert/user_test.go +++ b/services/convert/user_test.go @@ -30,11 +30,11 @@ func TestUser_ToUser(t *testing.T) { apiUser = toUser(db.DefaultContext, user1, false, false) assert.False(t, apiUser.IsAdmin) - assert.EqualValues(t, api.VisibleTypePublic.String(), apiUser.Visibility) + assert.Equal(t, api.VisibleTypePublic.String(), apiUser.Visibility) user31 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 31, IsAdmin: false, Visibility: api.VisibleTypePrivate}) apiUser = toUser(db.DefaultContext, user31, true, true) assert.False(t, apiUser.IsAdmin) - assert.EqualValues(t, api.VisibleTypePrivate.String(), apiUser.Visibility) + assert.Equal(t, api.VisibleTypePrivate.String(), apiUser.Visibility) } diff --git a/services/convert/utils_test.go b/services/convert/utils_test.go index a8363ec6bd..7965624e2b 100644 --- a/services/convert/utils_test.go +++ b/services/convert/utils_test.go @@ -10,10 +10,10 @@ import ( ) func TestToCorrectPageSize(t *testing.T) { - assert.EqualValues(t, 30, ToCorrectPageSize(0)) - assert.EqualValues(t, 30, ToCorrectPageSize(-10)) - assert.EqualValues(t, 20, ToCorrectPageSize(20)) - assert.EqualValues(t, 50, ToCorrectPageSize(100)) + assert.Equal(t, 30, ToCorrectPageSize(0)) + assert.Equal(t, 30, ToCorrectPageSize(-10)) + assert.Equal(t, 20, ToCorrectPageSize(20)) + assert.Equal(t, 50, ToCorrectPageSize(100)) } func TestToGitServiceType(t *testing.T) { diff --git a/services/doctor/authorizedkeys.go b/services/doctor/authorizedkeys.go index 8d6fc9cb5e..46e7099dce 100644 --- a/services/doctor/authorizedkeys.go +++ b/services/doctor/authorizedkeys.go @@ -7,6 +7,7 @@ import ( "bufio" "bytes" "context" + "errors" "fmt" "os" "path/filepath" @@ -78,7 +79,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e fPath, "gitea admin regenerate keys", "gitea doctor --run authorized-keys --fix") - return fmt.Errorf(`authorized_keys is out of date and should be regenerated with "gitea admin regenerate keys" or "gitea doctor --run authorized-keys --fix"`) + return errors.New(`authorized_keys is out of date and should be regenerated with "gitea admin regenerate keys" or "gitea doctor --run authorized-keys --fix"`) } logger.Warn("authorized_keys is out of date. Attempting rewrite...") err = asymkey_service.RewriteAllPublicKeys(ctx) diff --git a/services/doctor/fix16961_test.go b/services/doctor/fix16961_test.go index d5eded1117..11a128620c 100644 --- a/services/doctor/fix16961_test.go +++ b/services/doctor/fix16961_test.go @@ -215,7 +215,7 @@ func Test_fixPullRequestsConfig_16961(t *testing.T) { if gotFixed != tt.wantFixed { t.Errorf("fixPullRequestsConfig_16961() = %v, want %v", gotFixed, tt.wantFixed) } - assert.EqualValues(t, &tt.expected, cfg) + assert.Equal(t, &tt.expected, cfg) }) } } @@ -259,7 +259,7 @@ func Test_fixIssuesConfig_16961(t *testing.T) { if gotFixed != tt.wantFixed { t.Errorf("fixIssuesConfig_16961() = %v, want %v", gotFixed, tt.wantFixed) } - assert.EqualValues(t, &tt.expected, cfg) + assert.Equal(t, &tt.expected, cfg) }) } } diff --git a/services/doctor/lfs.go b/services/doctor/lfs.go index 5f110b8f97..a90f394450 100644 --- a/services/doctor/lfs.go +++ b/services/doctor/lfs.go @@ -5,7 +5,7 @@ package doctor import ( "context" - "fmt" + "errors" "time" "code.gitea.io/gitea/modules/log" @@ -27,7 +27,7 @@ func init() { func garbageCollectLFSCheck(ctx context.Context, logger log.Logger, autofix bool) error { if !setting.LFS.StartServer { - return fmt.Errorf("LFS support is disabled") + return errors.New("LFS support is disabled") } if err := repository.GarbageCollectLFSMetaObjects(ctx, repository.GarbageCollectLFSMetaObjectsOptions{ diff --git a/services/doctor/storage.go b/services/doctor/storage.go index 3f3b562c37..77fc6d65df 100644 --- a/services/doctor/storage.go +++ b/services/doctor/storage.go @@ -121,7 +121,7 @@ func checkStorage(opts *checkStorageOptions) func(ctx context.Context, logger lo storer: storage.LFS, isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) { // The oid of an LFS stored object is the name but with all the path.Separators removed - oid := strings.ReplaceAll(path, "/", "") + oid := strings.ReplaceAll(strings.ReplaceAll(path, "\\", ""), "/", "") exists, err := git.ExistsLFSObject(ctx, oid) return !exists, err }, diff --git a/services/externalaccount/link.go b/services/externalaccount/link.go index d6e2ea7e94..ab853140cb 100644 --- a/services/externalaccount/link.go +++ b/services/externalaccount/link.go @@ -5,7 +5,7 @@ package externalaccount import ( "context" - "fmt" + "errors" user_model "code.gitea.io/gitea/models/user" @@ -23,7 +23,7 @@ type Store interface { func LinkAccountFromStore(ctx context.Context, store Store, user *user_model.User) error { gothUser := store.Get("linkAccountGothUser") if gothUser == nil { - return fmt.Errorf("not in LinkAccount session") + return errors.New("not in LinkAccount session") } return LinkAccountToUser(ctx, user, gothUser.(goth.User)) diff --git a/services/feed/feed.go b/services/feed/feed.go index 214e9b5765..38a4e25308 100644 --- a/services/feed/feed.go +++ b/services/feed/feed.go @@ -6,6 +6,7 @@ package feed import ( "context" "fmt" + "strconv" activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" @@ -86,7 +87,7 @@ func notifyWatchers(ctx context.Context, act *activities_model.Action, watchers return fmt.Errorf("count user feeds: %w", err) } - _ = cache.GetCache().Put(userFeedCacheKey(act.UserID), fmt.Sprintf("%d", total), setting.CacheService.TTLSeconds()) + _ = cache.GetCache().Put(userFeedCacheKey(act.UserID), strconv.FormatInt(total, 10), setting.CacheService.TTLSeconds()) } return nil diff --git a/services/feed/feed_test.go b/services/feed/feed_test.go index 243bc046b0..a3492938c8 100644 --- a/services/feed/feed_test.go +++ b/services/feed/feed_test.go @@ -30,7 +30,7 @@ func TestGetFeeds(t *testing.T) { assert.NoError(t, err) if assert.Len(t, actions, 1) { assert.EqualValues(t, 1, actions[0].ID) - assert.EqualValues(t, user.ID, actions[0].UserID) + assert.Equal(t, user.ID, actions[0].UserID) } assert.Equal(t, int64(1), count) @@ -107,7 +107,7 @@ func TestGetFeeds2(t *testing.T) { assert.Len(t, actions, 1) if assert.Len(t, actions, 1) { assert.EqualValues(t, 2, actions[0].ID) - assert.EqualValues(t, org.ID, actions[0].UserID) + assert.Equal(t, org.ID, actions[0].UserID) } assert.Equal(t, int64(1), count) diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 1366d30b1f..434274c174 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -110,17 +110,14 @@ type RepoSettingForm struct { EnablePrune bool // Advanced settings - EnableCode bool - DefaultCodeEveryoneAccess string + EnableCode bool - EnableWiki bool - EnableExternalWiki bool - DefaultWikiBranch string - DefaultWikiEveryoneAccess string - ExternalWikiURL string + EnableWiki bool + EnableExternalWiki bool + DefaultWikiBranch string + ExternalWikiURL string EnableIssues bool - DefaultIssuesEveryoneAccess string EnableExternalTracker bool ExternalTrackerURL string TrackerURLFormat string @@ -522,12 +519,13 @@ func (f *CreateMilestoneForm) Validate(req *http.Request, errs binding.Errors) b // CreateLabelForm form for creating label type CreateLabelForm struct { - ID int64 - Title string `binding:"Required;MaxSize(50)" locale:"repo.issues.label_title"` - Exclusive bool `form:"exclusive"` - IsArchived bool `form:"is_archived"` - Description string `binding:"MaxSize(200)" locale:"repo.issues.label_description"` - Color string `binding:"Required;MaxSize(7)" locale:"repo.issues.label_color"` + ID int64 + Title string `binding:"Required;MaxSize(50)" locale:"repo.issues.label_title"` + Exclusive bool `form:"exclusive"` + ExclusiveOrder int `form:"exclusive_order"` + IsArchived bool `form:"is_archived"` + Description string `binding:"MaxSize(200)" locale:"repo.issues.label_description"` + Color string `binding:"Required;MaxSize(7)" locale:"repo.issues.label_color"` } // Validate validates the fields diff --git a/services/git/commit.go b/services/git/commit.go index 8ab8f3d369..3faef76782 100644 --- a/services/git/commit.go +++ b/services/git/commit.go @@ -17,7 +17,7 @@ import ( ) // ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys. -func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error)) ([]*asymkey_model.SignCommit, error) { +func ParseCommitsWithSignature(ctx context.Context, repo *repo_model.Repository, oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType) ([]*asymkey_model.SignCommit, error) { newCommits := make([]*asymkey_model.SignCommit, 0, len(oldCommits)) keyMap := map[string]bool{} @@ -47,6 +47,10 @@ func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.Use Verification: asymkey_service.ParseCommitWithSignatureCommitter(ctx, c.Commit, committer), } + isOwnerMemberCollaborator := func(user *user_model.User) (bool, error) { + return repo_model.IsOwnerMemberCollaborator(ctx, repo, user.ID) + } + _ = asymkey_model.CalculateTrustStatus(signCommit.Verification, repoTrustModel, isOwnerMemberCollaborator, &keyMap) newCommits = append(newCommits, signCommit) @@ -62,11 +66,9 @@ func ConvertFromGitCommit(ctx context.Context, commits []*git.Commit, repo *repo } signedCommits, err := ParseCommitsWithSignature( ctx, + repo, validatedCommits, repo.GetTrustModel(), - func(user *user_model.User) (bool, error) { - return repo_model.IsOwnerMemberCollaborator(ctx, repo, user.ID) - }, ) if err != nil { return nil, err diff --git a/services/gitdiff/git_diff_tree.go b/services/gitdiff/git_diff_tree.go index 035210a31d..ed94bfbfe4 100644 --- a/services/gitdiff/git_diff_tree.go +++ b/services/gitdiff/git_diff_tree.go @@ -6,6 +6,7 @@ package gitdiff import ( "bufio" "context" + "errors" "fmt" "io" "strconv" @@ -71,7 +72,7 @@ func runGitDiffTree(ctx context.Context, gitRepo *git.Repository, useMergeBase b func validateGitDiffTreeArguments(gitRepo *git.Repository, useMergeBase bool, baseSha, headSha string) (shouldUseMergeBase bool, resolvedBaseSha, resolvedHeadSha string, err error) { // if the head is empty its an error if headSha == "" { - return false, "", "", fmt.Errorf("headSha is empty") + return false, "", "", errors.New("headSha is empty") } // if the head commit doesn't exist its and error @@ -207,7 +208,7 @@ func parseGitDiffTreeLine(line string) (*DiffTreeRecord, error) { func statusFromLetter(rawStatus string) (status string, score uint8, err error) { if len(rawStatus) < 1 { - return "", 0, fmt.Errorf("empty status letter") + return "", 0, errors.New("empty status letter") } switch rawStatus[0] { case 'A': @@ -235,7 +236,7 @@ func statusFromLetter(rawStatus string) (status string, score uint8, err error) func tryParseStatusScore(rawStatus string) (uint8, error) { if len(rawStatus) < 2 { - return 0, fmt.Errorf("status score missing") + return 0, errors.New("status score missing") } score, err := strconv.ParseUint(rawStatus[1:], 10, 8) diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index b9781cf8d0..9ee86d9dfc 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -25,6 +25,7 @@ import ( "code.gitea.io/gitea/modules/analyze" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/attribute" "code.gitea.io/gitea/modules/highlight" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" @@ -1237,24 +1238,21 @@ func GetDiffForRender(ctx context.Context, gitRepo *git.Repository, opts *DiffOp return nil, err } - checker, deferrable := gitRepo.CheckAttributeReader(opts.AfterCommitID) - defer deferrable() + checker, err := attribute.NewBatchChecker(gitRepo, opts.AfterCommitID, []string{attribute.LinguistVendored, attribute.LinguistGenerated, attribute.LinguistLanguage, attribute.GitlabLanguage}) + if err != nil { + return nil, err + } + defer checker.Close() for _, diffFile := range diff.Files { isVendored := optional.None[bool]() isGenerated := optional.None[bool]() - if checker != nil { - attrs, err := checker.CheckPath(diffFile.Name) - if err == nil { - isVendored = git.AttributeToBool(attrs, git.AttributeLinguistVendored) - isGenerated = git.AttributeToBool(attrs, git.AttributeLinguistGenerated) - - language := git.TryReadLanguageAttribute(attrs) - if language.Has() { - diffFile.Language = language.Value() - } - } else { - checker = nil // CheckPath fails, it's not impossible to "check" anymore + attrs, err := checker.CheckPath(diffFile.Name) + if err == nil { + isVendored, isGenerated = attrs.GetVendored(), attrs.GetGenerated() + language := attrs.GetLanguage() + if language.Has() { + diffFile.Language = language.Value() } } diff --git a/services/gitdiff/highlightdiff.go b/services/gitdiff/highlightdiff.go index 6e18651d83..e8be063e69 100644 --- a/services/gitdiff/highlightdiff.go +++ b/services/gitdiff/highlightdiff.go @@ -14,13 +14,14 @@ import ( // token is a html tag or entity, eg: "", "", "<" func extractHTMLToken(s string) (before, token, after string, valid bool) { for pos1 := 0; pos1 < len(s); pos1++ { - if s[pos1] == '<' { + switch s[pos1] { + case '<': pos2 := strings.IndexByte(s[pos1:], '>') if pos2 == -1 { return "", "", s, false } return s[:pos1], s[pos1 : pos1+pos2+1], s[pos1+pos2+1:], true - } else if s[pos1] == '&' { + case '&': pos2 := strings.IndexByte(s[pos1:], ';') if pos2 == -1 { return "", "", s, false diff --git a/services/gitdiff/highlightdiff_test.go b/services/gitdiff/highlightdiff_test.go index c2584dc622..aebe38ae7c 100644 --- a/services/gitdiff/highlightdiff_test.go +++ b/services/gitdiff/highlightdiff_test.go @@ -38,15 +38,15 @@ func TestDiffWithHighlight(t *testing.T) { hcd.placeholderTokenMap['O'], hcd.placeholderTokenMap['C'] = "", "" assert.Equal(t, "", string(hcd.recoverOneDiff("OC"))) assert.Equal(t, "", string(hcd.recoverOneDiff("O"))) - assert.Equal(t, "", string(hcd.recoverOneDiff("C"))) + assert.Empty(t, string(hcd.recoverOneDiff("C"))) }) } func TestDiffWithHighlightPlaceholder(t *testing.T) { hcd := newHighlightCodeDiff() output := hcd.diffLineWithHighlight(DiffLineDel, "a='\U00100000'", "a='\U0010FFFD''") - assert.Equal(t, "", hcd.placeholderTokenMap[0x00100000]) - assert.Equal(t, "", hcd.placeholderTokenMap[0x0010FFFD]) + assert.Empty(t, hcd.placeholderTokenMap[0x00100000]) + assert.Empty(t, hcd.placeholderTokenMap[0x0010FFFD]) expected := fmt.Sprintf(`a='%s'`, "\U00100000") assert.Equal(t, expected, string(output)) diff --git a/services/issue/comments.go b/services/issue/comments.go index 46f92f7cd2..10c81198d5 100644 --- a/services/issue/comments.go +++ b/services/issue/comments.go @@ -5,6 +5,7 @@ package issue import ( "context" + "errors" "fmt" "code.gitea.io/gitea/models/db" @@ -22,7 +23,7 @@ import ( // CreateRefComment creates a commit reference comment to issue. func CreateRefComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, issue *issues_model.Issue, content, commitSHA string) error { if len(commitSHA) == 0 { - return fmt.Errorf("cannot create reference with empty commit SHA") + return errors.New("cannot create reference with empty commit SHA") } if user_model.IsUserBlockedBy(ctx, doer, issue.PosterID, repo.OwnerID) { diff --git a/services/issue/issue_test.go b/services/issue/issue_test.go index 8806cec0e7..b3df8191e1 100644 --- a/services/issue/issue_test.go +++ b/services/issue/issue_test.go @@ -24,8 +24,8 @@ func TestGetRefEndNamesAndURLs(t *testing.T) { repoLink := "/foo/bar" endNames, urls := GetRefEndNamesAndURLs(issues, repoLink) - assert.EqualValues(t, map[int64]string{1: "branch1", 2: "tag1", 3: "c0ffee"}, endNames) - assert.EqualValues(t, map[int64]string{ + assert.Equal(t, map[int64]string{1: "branch1", 2: "tag1", 3: "c0ffee"}, endNames) + assert.Equal(t, map[int64]string{ 1: repoLink + "/src/branch/branch1", 2: repoLink + "/src/tag/tag1", 3: repoLink + "/src/commit/c0ffee", diff --git a/services/issue/milestone.go b/services/issue/milestone.go index beb6f131a9..afca70794d 100644 --- a/services/issue/milestone.go +++ b/services/issue/milestone.go @@ -5,6 +5,7 @@ package issue import ( "context" + "errors" "fmt" "code.gitea.io/gitea/models/db" @@ -21,7 +22,7 @@ func changeMilestoneAssign(ctx context.Context, doer *user_model.User, issue *is return fmt.Errorf("HasMilestoneByRepoID: %w", err) } if !has { - return fmt.Errorf("HasMilestoneByRepoID: issue doesn't exist") + return errors.New("HasMilestoneByRepoID: issue doesn't exist") } } diff --git a/services/issue/suggestion_test.go b/services/issue/suggestion_test.go index 84cfd520ac..a5b39d27bb 100644 --- a/services/issue/suggestion_test.go +++ b/services/issue/suggestion_test.go @@ -51,7 +51,7 @@ func Test_Suggestion(t *testing.T) { for _, issue := range issues { issueIndexes = append(issueIndexes, issue.Index) } - assert.EqualValues(t, testCase.expectedIndexes, issueIndexes) + assert.Equal(t, testCase.expectedIndexes, issueIndexes) }) } } diff --git a/services/lfs/server.go b/services/lfs/server.go index c4866edaab..0a99287ed9 100644 --- a/services/lfs/server.go +++ b/services/lfs/server.go @@ -164,11 +164,12 @@ func BatchHandler(ctx *context.Context) { } var isUpload bool - if br.Operation == "upload" { + switch br.Operation { + case "upload": isUpload = true - } else if br.Operation == "download" { + case "download": isUpload = false - } else { + default: log.Trace("Attempt to BATCH with invalid operation: %s", br.Operation) writeStatus(ctx, http.StatusBadRequest) return @@ -571,15 +572,15 @@ func handleLFSToken(ctx stdCtx.Context, tokenSHA string, target *repo_model.Repo claims, claimsOk := token.Claims.(*Claims) if !token.Valid || !claimsOk { - return nil, fmt.Errorf("invalid token claim") + return nil, errors.New("invalid token claim") } if claims.RepoID != target.ID { - return nil, fmt.Errorf("invalid token claim") + return nil, errors.New("invalid token claim") } if mode == perm_model.AccessModeWrite && claims.Op != "upload" { - return nil, fmt.Errorf("invalid token claim") + return nil, errors.New("invalid token claim") } u, err := user_model.GetUserByID(ctx, claims.UserID) @@ -592,12 +593,12 @@ func handleLFSToken(ctx stdCtx.Context, tokenSHA string, target *repo_model.Repo func parseToken(ctx stdCtx.Context, authorization string, target *repo_model.Repository, mode perm_model.AccessMode) (*user_model.User, error) { if authorization == "" { - return nil, fmt.Errorf("no token") + return nil, errors.New("no token") } parts := strings.SplitN(authorization, " ", 2) if len(parts) != 2 { - return nil, fmt.Errorf("no token") + return nil, errors.New("no token") } tokenSHA := parts[1] switch strings.ToLower(parts[0]) { @@ -606,7 +607,7 @@ func parseToken(ctx stdCtx.Context, authorization string, target *repo_model.Rep case "token": return handleLFSToken(ctx, tokenSHA, target, mode) } - return nil, fmt.Errorf("token not found") + return nil, errors.New("token not found") } func requireAuth(ctx *context.Context) { diff --git a/services/mailer/mail.go b/services/mailer/mail.go index f7e5b0c9f0..aa51cbdbcf 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -8,6 +8,7 @@ import ( "bytes" "context" "encoding/base64" + "errors" "fmt" "html/template" "io" @@ -117,7 +118,7 @@ func (b64embedder *mailAttachmentBase64Embedder) AttachmentSrcToBase64DataURI(ct attachmentUUID, ok = strings.CutPrefix(parsedSrc.RepoSubPath, "/attachments/") } if !ok { - return "", fmt.Errorf("not an attachment") + return "", errors.New("not an attachment") } } attachment, err := repo_model.GetAttachmentByUUID(ctx, attachmentUUID) @@ -126,10 +127,10 @@ func (b64embedder *mailAttachmentBase64Embedder) AttachmentSrcToBase64DataURI(ct } if attachment.RepoID != b64embedder.repo.ID { - return "", fmt.Errorf("attachment does not belong to the repository") + return "", errors.New("attachment does not belong to the repository") } if attachment.Size+b64embedder.estimateSize > b64embedder.maxSize { - return "", fmt.Errorf("total embedded images exceed max limit") + return "", errors.New("total embedded images exceed max limit") } fr, err := storage.Attachments.Open(attachment.RelativePath()) @@ -146,7 +147,7 @@ func (b64embedder *mailAttachmentBase64Embedder) AttachmentSrcToBase64DataURI(ct mimeType := typesniffer.DetectContentType(content) if !mimeType.IsImage() { - return "", fmt.Errorf("not an image") + return "", errors.New("not an image") } encoded := base64.StdEncoding.EncodeToString(content) diff --git a/services/mailer/mail_team_invite.go b/services/mailer/mail_team_invite.go index 1fbade7e23..f4aa788dec 100644 --- a/services/mailer/mail_team_invite.go +++ b/services/mailer/mail_team_invite.go @@ -6,6 +6,7 @@ package mailer import ( "bytes" "context" + "errors" "fmt" "net/url" @@ -38,10 +39,10 @@ func MailTeamInvite(ctx context.Context, inviter *user_model.User, team *org_mod if err != nil && !user_model.IsErrUserNotExist(err) { return err } else if user != nil && user.ProhibitLogin { - return fmt.Errorf("login is prohibited for the invited user") + return errors.New("login is prohibited for the invited user") } - inviteRedirect := url.QueryEscape(fmt.Sprintf("/org/invite/%s", invite.Token)) + inviteRedirect := url.QueryEscape("/org/invite/" + invite.Token) inviteURL := fmt.Sprintf("%suser/sign_up?redirect_to=%s", setting.AppURL, inviteRedirect) if (err == nil && user != nil) || setting.Service.DisableRegistration || setting.Service.AllowOnlyExternalRegistration { diff --git a/services/mailer/mail_test.go b/services/mailer/mail_test.go index 6aced705f3..7a47cf3876 100644 --- a/services/mailer/mail_test.go +++ b/services/mailer/mail_test.go @@ -467,7 +467,7 @@ func TestFromDisplayName(t *testing.T) { t.Run(tc.userDisplayName, func(t *testing.T) { user := &user_model.User{FullName: tc.userDisplayName, Name: "tmp"} got := fromDisplayName(user) - assert.EqualValues(t, tc.fromDisplayName, got) + assert.Equal(t, tc.fromDisplayName, got) }) } @@ -484,7 +484,7 @@ func TestFromDisplayName(t *testing.T) { setting.Domain = oldDomain }() - assert.EqualValues(t, "Mister X (by Code IT on [code.it])", fromDisplayName(&user_model.User{FullName: "Mister X", Name: "tmp"})) + assert.Equal(t, "Mister X (by Code IT on [code.it])", fromDisplayName(&user_model.User{FullName: "Mister X", Name: "tmp"})) }) } diff --git a/services/mailer/notify.go b/services/mailer/notify.go index a27177e8f5..77c366fe31 100644 --- a/services/mailer/notify.go +++ b/services/mailer/notify.go @@ -31,15 +31,16 @@ func (m *mailNotifier) CreateIssueComment(ctx context.Context, doer *user_model. issue *issues_model.Issue, comment *issues_model.Comment, mentions []*user_model.User, ) { var act activities_model.ActionType - if comment.Type == issues_model.CommentTypeClose { + switch comment.Type { + case issues_model.CommentTypeClose: act = activities_model.ActionCloseIssue - } else if comment.Type == issues_model.CommentTypeReopen { + case issues_model.CommentTypeReopen: act = activities_model.ActionReopenIssue - } else if comment.Type == issues_model.CommentTypeComment { + case issues_model.CommentTypeComment: act = activities_model.ActionCommentIssue - } else if comment.Type == issues_model.CommentTypeCode { + case issues_model.CommentTypeCode: act = activities_model.ActionCommentIssue - } else if comment.Type == issues_model.CommentTypePullRequestPush { + case issues_model.CommentTypePullRequestPush: act = 0 } @@ -95,11 +96,12 @@ func (m *mailNotifier) NewPullRequest(ctx context.Context, pr *issues_model.Pull func (m *mailNotifier) PullRequestReview(ctx context.Context, pr *issues_model.PullRequest, r *issues_model.Review, comment *issues_model.Comment, mentions []*user_model.User) { var act activities_model.ActionType - if comment.Type == issues_model.CommentTypeClose { + switch comment.Type { + case issues_model.CommentTypeClose: act = activities_model.ActionCloseIssue - } else if comment.Type == issues_model.CommentTypeReopen { + case issues_model.CommentTypeReopen: act = activities_model.ActionReopenIssue - } else if comment.Type == issues_model.CommentTypeComment { + case issues_model.CommentTypeComment: act = activities_model.ActionCommentPull } if err := MailParticipantsComment(ctx, comment, act, pr.Issue, mentions); err != nil { diff --git a/services/mailer/sender/smtp.go b/services/mailer/sender/smtp.go index c53c3da997..8dc1b40b74 100644 --- a/services/mailer/sender/smtp.go +++ b/services/mailer/sender/smtp.go @@ -5,6 +5,7 @@ package sender import ( "crypto/tls" + "errors" "fmt" "io" "net" @@ -99,7 +100,7 @@ func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error { canAuth, options := client.Extension("AUTH") if len(opts.User) > 0 { if !canAuth { - return fmt.Errorf("SMTP server does not support AUTH, but credentials provided") + return errors.New("SMTP server does not support AUTH, but credentials provided") } var auth smtp.Auth diff --git a/services/mailer/sender/smtp_auth.go b/services/mailer/sender/smtp_auth.go index 260b12437b..c60e0dbfbb 100644 --- a/services/mailer/sender/smtp_auth.go +++ b/services/mailer/sender/smtp_auth.go @@ -4,6 +4,7 @@ package sender import ( + "errors" "fmt" "github.com/Azure/go-ntlmssp" @@ -60,7 +61,7 @@ func (a *ntlmAuth) Start(server *smtp.ServerInfo) (string, []byte, error) { func (a *ntlmAuth) Next(fromServer []byte, more bool) ([]byte, error) { if more { if len(fromServer) == 0 { - return nil, fmt.Errorf("ntlm ChallengeMessage is empty") + return nil, errors.New("ntlm ChallengeMessage is empty") } authenticateMessage, err := ntlmssp.ProcessChallenge(fromServer, a.username, a.password, a.domainNeeded) return authenticateMessage, err diff --git a/services/markup/renderhelper_codepreview.go b/services/markup/renderhelper_codepreview.go index d638af7ff0..fa1eb824a2 100644 --- a/services/markup/renderhelper_codepreview.go +++ b/services/markup/renderhelper_codepreview.go @@ -6,7 +6,7 @@ package markup import ( "bufio" "context" - "fmt" + "errors" "html/template" "strings" @@ -14,13 +14,13 @@ import ( "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/charset" + "code.gitea.io/gitea/modules/git/languagestats" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/indexer/code" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" gitea_context "code.gitea.io/gitea/services/context" - "code.gitea.io/gitea/services/repository/files" ) func renderRepoFileCodePreview(ctx context.Context, opts markup.RenderCodePreviewOptions) (template.HTML, error) { @@ -38,7 +38,7 @@ func renderRepoFileCodePreview(ctx context.Context, opts markup.RenderCodePrevie webCtx := gitea_context.GetWebContext(ctx) if webCtx == nil { - return "", fmt.Errorf("context is not a web context") + return "", errors.New("context is not a web context") } doer := webCtx.Doer @@ -61,14 +61,14 @@ func renderRepoFileCodePreview(ctx context.Context, opts markup.RenderCodePrevie return "", err } - language, _ := files.TryGetContentLanguage(gitRepo, opts.CommitID, opts.FilePath) + language, _ := languagestats.GetFileLanguage(ctx, gitRepo, opts.CommitID, opts.FilePath) blob, err := commit.GetBlobByPath(opts.FilePath) if err != nil { return "", err } if blob.Size() > setting.UI.MaxDisplayFileSize { - return "", fmt.Errorf("file is too large") + return "", errors.New("file is too large") } dataRc, err := blob.DataAsync() diff --git a/services/markup/renderhelper_issueicontitle.go b/services/markup/renderhelper_issueicontitle.go index fd8f9d43fa..27b5595fa9 100644 --- a/services/markup/renderhelper_issueicontitle.go +++ b/services/markup/renderhelper_issueicontitle.go @@ -5,6 +5,7 @@ package markup import ( "context" + "errors" "fmt" "html/template" @@ -20,7 +21,7 @@ import ( func renderRepoIssueIconTitle(ctx context.Context, opts markup.RenderIssueIconTitleOptions) (_ template.HTML, err error) { webCtx := gitea_context.GetWebContext(ctx) if webCtx == nil { - return "", fmt.Errorf("context is not a web context") + return "", errors.New("context is not a web context") } textIssueIndex := fmt.Sprintf("(#%d)", opts.IssueIndex) diff --git a/services/markup/renderhelper_mention_test.go b/services/markup/renderhelper_mention_test.go index d05fbb6fba..d54ab13a48 100644 --- a/services/markup/renderhelper_mention_test.go +++ b/services/markup/renderhelper_mention_test.go @@ -37,7 +37,7 @@ func TestRenderHelperMention(t *testing.T) { assert.False(t, FormalRenderHelperFuncs().IsUsernameMentionable(t.Context(), userNoSuch)) // when using web context, use user.IsUserVisibleToViewer to check - req, err := http.NewRequest("GET", "/", nil) + req, err := http.NewRequest(http.MethodGet, "/", nil) assert.NoError(t, err) base := gitea_context.NewBaseContextForTest(httptest.NewRecorder(), req) giteaCtx := gitea_context.NewWebContext(base, &contexttest.MockRender{}, nil) diff --git a/services/migrations/codebase.go b/services/migrations/codebase.go index 880dd21497..240c7bcdc9 100644 --- a/services/migrations/codebase.go +++ b/services/migrations/codebase.go @@ -134,7 +134,7 @@ func (d *CodebaseDownloader) callAPI(ctx context.Context, endpoint string, param u.RawQuery = query.Encode() } - req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) if err != nil { return err } diff --git a/services/migrations/codecommit.go b/services/migrations/codecommit.go index c45f9e5943..4b2634ef8a 100644 --- a/services/migrations/codecommit.go +++ b/services/migrations/codecommit.go @@ -5,7 +5,7 @@ package migrations import ( "context" - "fmt" + "errors" "net/url" "strconv" "strings" @@ -42,13 +42,13 @@ func (c *CodeCommitDownloaderFactory) New(ctx context.Context, opts base.Migrate hostElems := strings.Split(u.Host, ".") if len(hostElems) != 4 { - return nil, fmt.Errorf("cannot get the region from clone URL") + return nil, errors.New("cannot get the region from clone URL") } region := hostElems[1] pathElems := strings.Split(u.Path, "/") if len(pathElems) == 0 { - return nil, fmt.Errorf("cannot get the repo name from clone URL") + return nil, errors.New("cannot get the repo name from clone URL") } repoName := pathElems[len(pathElems)-1] diff --git a/services/migrations/gitea_downloader.go b/services/migrations/gitea_downloader.go index f92f318293..5d48d2f003 100644 --- a/services/migrations/gitea_downloader.go +++ b/services/migrations/gitea_downloader.go @@ -298,7 +298,7 @@ func (g *GiteaDownloader) convertGiteaRelease(rel *gitea_sdk.Release) *base.Rele } // FIXME: for a private download? - req, err := http.NewRequest("GET", assetDownloadURL, nil) + req, err := http.NewRequest(http.MethodGet, assetDownloadURL, nil) if err != nil { return nil, err } diff --git a/services/migrations/gitea_downloader_test.go b/services/migrations/gitea_downloader_test.go index da56120065..bb1760e889 100644 --- a/services/migrations/gitea_downloader_test.go +++ b/services/migrations/gitea_downloader_test.go @@ -47,7 +47,7 @@ func TestGiteaDownloadRepo(t *testing.T) { topics, err := downloader.GetTopics(ctx) assert.NoError(t, err) sort.Strings(topics) - assert.EqualValues(t, []string{"ci", "gitea", "migration", "test"}, topics) + assert.Equal(t, []string{"ci", "gitea", "migration", "test"}, topics) labels, err := downloader.GetLabels(ctx) assert.NoError(t, err) @@ -134,7 +134,7 @@ func TestGiteaDownloadRepo(t *testing.T) { assert.NoError(t, err) assert.True(t, isEnd) assert.Len(t, issues, 7) - assert.EqualValues(t, "open", issues[0].State) + assert.Equal(t, "open", issues[0].State) issues, isEnd, err = downloader.GetIssues(ctx, 3, 2) assert.NoError(t, err) diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index b17cc3ce41..cd5af39910 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -1017,7 +1017,7 @@ func (g *GiteaLocalUploader) remapLocalUser(ctx context.Context, source user_mod func (g *GiteaLocalUploader) remapExternalUser(ctx context.Context, source user_model.ExternalUserMigrated) (userid int64, err error) { userid, ok := g.userMap[source.GetExternalID()] if !ok { - userid, err = user_model.GetUserIDByExternalUserID(ctx, g.gitServiceType.Name(), fmt.Sprintf("%d", source.GetExternalID())) + userid, err = user_model.GetUserIDByExternalUserID(ctx, g.gitServiceType.Name(), strconv.FormatInt(source.GetExternalID(), 10)) if err != nil { log.Error("GetUserIDByExternalUserID: %v", err) return 0, err diff --git a/services/migrations/gitea_uploader_test.go b/services/migrations/gitea_uploader_test.go index f52d4157c8..1970c0550c 100644 --- a/services/migrations/gitea_uploader_test.go +++ b/services/migrations/gitea_uploader_test.go @@ -64,7 +64,7 @@ func TestGiteaUploadRepo(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, Name: repoName}) assert.True(t, repo.HasWiki()) - assert.EqualValues(t, repo_model.RepositoryReady, repo.Status) + assert.Equal(t, repo_model.RepositoryReady, repo.Status) milestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{ RepoID: repo.ID, @@ -152,7 +152,7 @@ func TestGiteaUploadRemapLocalUser(t *testing.T) { uploader.userMap = make(map[int64]int64) err := uploader.remapUser(ctx, &source, &target) assert.NoError(t, err) - assert.EqualValues(t, doer.ID, target.GetUserID()) + assert.Equal(t, doer.ID, target.GetUserID()) // // The externalID matches a known user but the name does not match, @@ -163,7 +163,7 @@ func TestGiteaUploadRemapLocalUser(t *testing.T) { uploader.userMap = make(map[int64]int64) err = uploader.remapUser(ctx, &source, &target) assert.NoError(t, err) - assert.EqualValues(t, doer.ID, target.GetUserID()) + assert.Equal(t, doer.ID, target.GetUserID()) // // The externalID and externalName match an existing user, everything @@ -174,7 +174,7 @@ func TestGiteaUploadRemapLocalUser(t *testing.T) { uploader.userMap = make(map[int64]int64) err = uploader.remapUser(ctx, &source, &target) assert.NoError(t, err) - assert.EqualValues(t, user.ID, target.GetUserID()) + assert.Equal(t, user.ID, target.GetUserID()) } func TestGiteaUploadRemapExternalUser(t *testing.T) { @@ -202,7 +202,7 @@ func TestGiteaUploadRemapExternalUser(t *testing.T) { target := repo_model.Release{} err := uploader.remapUser(ctx, &source, &target) assert.NoError(t, err) - assert.EqualValues(t, doer.ID, target.GetUserID()) + assert.Equal(t, doer.ID, target.GetUserID()) // // Link the external ID to an existing user @@ -225,7 +225,7 @@ func TestGiteaUploadRemapExternalUser(t *testing.T) { target = repo_model.Release{} err = uploader.remapUser(ctx, &source, &target) assert.NoError(t, err) - assert.EqualValues(t, linkedUser.ID, target.GetUserID()) + assert.Equal(t, linkedUser.ID, target.GetUserID()) } func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { @@ -239,7 +239,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { assert.NoError(t, git.InitRepository(git.DefaultContext, fromRepo.RepoPath(), false, fromRepo.ObjectFormatName)) err := git.NewCommand("symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseRef).Run(git.DefaultContext, &git.RunOpts{Dir: fromRepo.RepoPath()}) assert.NoError(t, err) - assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s", fromRepo.RepoPath())), 0o644)) + assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte("# Testing Repository\n\nOriginally created in: "+fromRepo.RepoPath()), 0o644)) assert.NoError(t, git.AddChanges(fromRepo.RepoPath(), true)) signature := git.Signature{ Email: "test@example.com", @@ -287,7 +287,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { })) _, _, err = git.NewCommand("checkout", "-b").AddDynamicArguments(forkHeadRef).RunStdString(git.DefaultContext, &git.RunOpts{Dir: forkRepo.RepoPath()}) assert.NoError(t, err) - assert.NoError(t, os.WriteFile(filepath.Join(forkRepo.RepoPath(), "README.md"), []byte(fmt.Sprintf("# branch2 %s", forkRepo.RepoPath())), 0o644)) + assert.NoError(t, os.WriteFile(filepath.Join(forkRepo.RepoPath(), "README.md"), []byte("# branch2 "+forkRepo.RepoPath()), 0o644)) assert.NoError(t, git.AddChanges(forkRepo.RepoPath(), true)) assert.NoError(t, git.CommitChanges(forkRepo.RepoPath(), git.CommitChangesOptions{ Committer: &signature, @@ -508,14 +508,14 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { head, err := uploader.updateGitForPullRequest(ctx, &testCase.pr) assert.NoError(t, err) - assert.EqualValues(t, testCase.head, head) + assert.Equal(t, testCase.head, head) log.Info(stopMark) logFiltered, logStopped := logChecker.Check(5 * time.Second) assert.True(t, logStopped) if len(testCase.logFilter) > 0 { - assert.EqualValues(t, testCase.logFiltered, logFiltered, "for log message filters: %v", testCase.logFilter) + assert.Equal(t, testCase.logFiltered, logFiltered, "for log message filters: %v", testCase.logFilter) } }) } diff --git a/services/migrations/github.go b/services/migrations/github.go index b00d6ed27f..ce3e5ced9e 100644 --- a/services/migrations/github.go +++ b/services/migrations/github.go @@ -133,7 +133,7 @@ func (g *GithubDownloaderV3) LogString() string { func (g *GithubDownloaderV3) addClient(client *http.Client, baseURL string) { githubClient := github.NewClient(client) if baseURL != "https://github.com" { - githubClient, _ = github.NewClient(client).WithEnterpriseURLs(baseURL, baseURL) + githubClient, _ = githubClient.WithEnterpriseURLs(baseURL, baseURL) } g.clients = append(g.clients, githubClient) g.rates = append(g.rates, nil) @@ -358,7 +358,7 @@ func (g *GithubDownloaderV3) convertGithubRelease(ctx context.Context, rel *gith } g.waitAndPickClient(ctx) - req, err := http.NewRequestWithContext(ctx, "GET", redirectURL, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, redirectURL, nil) if err != nil { return nil, err } @@ -872,3 +872,18 @@ func (g *GithubDownloaderV3) GetReviews(ctx context.Context, reviewable base.Rev } return allReviews, nil } + +// FormatCloneURL add authentication into remote URLs +func (g *GithubDownloaderV3) FormatCloneURL(opts MigrateOptions, remoteAddr string) (string, error) { + u, err := url.Parse(remoteAddr) + if err != nil { + return "", err + } + if len(opts.AuthToken) > 0 { + // "multiple tokens" are used to benefit more "API rate limit quota" + // git clone doesn't count for rate limits, so only use the first token. + // source: https://github.com/orgs/community/discussions/44515 + u.User = url.UserPassword("oauth2", strings.Split(opts.AuthToken, ",")[0]) + } + return u.String(), nil +} diff --git a/services/migrations/github_test.go b/services/migrations/github_test.go index 2625fb62ec..6d1a5378b9 100644 --- a/services/migrations/github_test.go +++ b/services/migrations/github_test.go @@ -12,6 +12,7 @@ import ( base "code.gitea.io/gitea/modules/migration" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestGitHubDownloadRepo(t *testing.T) { @@ -429,3 +430,36 @@ func TestGitHubDownloadRepo(t *testing.T) { }, }, reviews) } + +func TestGithubMultiToken(t *testing.T) { + testCases := []struct { + desc string + token string + expectedCloneURL string + }{ + { + desc: "Single Token", + token: "single_token", + expectedCloneURL: "https://oauth2:single_token@github.com", + }, + { + desc: "Multi Token", + token: "token1,token2", + expectedCloneURL: "https://oauth2:token1@github.com", + }, + } + factory := GithubDownloaderV3Factory{} + + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + opts := base.MigrateOptions{CloneAddr: "https://github.com/go-gitea/gitea", AuthToken: tC.token} + client, err := factory.New(t.Context(), opts) + require.NoError(t, err) + + cloneURL, err := client.FormatCloneURL(opts, "https://github.com") + require.NoError(t, err) + + assert.Equal(t, tC.expectedCloneURL, cloneURL) + }) + } +} diff --git a/services/migrations/gitlab.go b/services/migrations/gitlab.go index 4bed8e2f6c..a19a04bc44 100644 --- a/services/migrations/gitlab.go +++ b/services/migrations/gitlab.go @@ -341,7 +341,7 @@ func (g *GitlabDownloader) convertGitlabRelease(ctx context.Context, rel *gitlab return io.NopCloser(strings.NewReader(link.URL)), nil } - req, err := http.NewRequest("GET", link.URL, nil) + req, err := http.NewRequest(http.MethodGet, link.URL, nil) if err != nil { return nil, err } diff --git a/services/migrations/gitlab_test.go b/services/migrations/gitlab_test.go index 52f5827dfe..73a1b6a276 100644 --- a/services/migrations/gitlab_test.go +++ b/services/migrations/gitlab_test.go @@ -50,7 +50,7 @@ func TestGitlabDownloadRepo(t *testing.T) { topics, err := downloader.GetTopics(ctx) assert.NoError(t, err) assert.Len(t, topics, 2) - assert.EqualValues(t, []string{"migration", "test"}, topics) + assert.Equal(t, []string{"migration", "test"}, topics) milestones, err := downloader.GetMilestones(ctx) assert.NoError(t, err) @@ -501,7 +501,7 @@ func TestAwardsToReactions(t *testing.T) { assert.NoError(t, json.Unmarshal([]byte(testResponse), &awards)) reactions := downloader.awardsToReactions(awards) - assert.EqualValues(t, []*base.Reaction{ + assert.Equal(t, []*base.Reaction{ { UserName: "lafriks", UserID: 1241334, @@ -593,7 +593,7 @@ func TestNoteToComment(t *testing.T) { for i, note := range notes { actualComment := *downloader.convertNoteToComment(17, ¬e) - assert.EqualValues(t, actualComment, comments[i]) + assert.Equal(t, actualComment, comments[i]) } } diff --git a/services/migrations/migrate.go b/services/migrations/migrate.go index 5dda12286f..961abe16f4 100644 --- a/services/migrations/migrate.go +++ b/services/migrations/migrate.go @@ -6,6 +6,7 @@ package migrations import ( "context" + "errors" "fmt" "net" "net/url" @@ -211,7 +212,7 @@ func migrateRepository(ctx context.Context, doer *user_model.User, downloader ba if cloneURL.Scheme == "file" || cloneURL.Scheme == "" { if cloneAddrURL.Scheme != "file" && cloneAddrURL.Scheme != "" { - return fmt.Errorf("repo info has changed from external to local filesystem") + return errors.New("repo info has changed from external to local filesystem") } } diff --git a/services/migrations/onedev.go b/services/migrations/onedev.go index 4ce35dd12e..e052cba0cc 100644 --- a/services/migrations/onedev.go +++ b/services/migrations/onedev.go @@ -128,7 +128,7 @@ func (d *OneDevDownloader) callAPI(ctx context.Context, endpoint string, paramet u.RawQuery = query.Encode() } - req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) if err != nil { return err } diff --git a/services/mirror/mirror.go b/services/mirror/mirror.go index e029bbb1d6..7fb7fabb75 100644 --- a/services/mirror/mirror.go +++ b/services/mirror/mirror.go @@ -5,7 +5,7 @@ package mirror import ( "context" - "fmt" + "errors" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/log" @@ -29,7 +29,7 @@ func doMirrorSync(ctx context.Context, req *SyncRequest) { } } -var errLimit = fmt.Errorf("reached limit") +var errLimit = errors.New("reached limit") // Update checks and updates mirror repositories. func Update(ctx context.Context, pullLimit, pushLimit int) error { @@ -68,7 +68,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error { // Check we've not been cancelled select { case <-ctx.Done(): - return fmt.Errorf("aborted") + return errors.New("aborted") default: } diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index fa5b9934ec..c43a4ef04a 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -437,7 +437,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo } for _, branch := range branches { - cache.Remove(m.Repo.GetCommitsCountCacheKey(branch.Name, true)) + cache.Remove(m.Repo.GetCommitsCountCacheKey(branch, true)) } m.UpdatedUnix = timeutil.TimeStampNow() diff --git a/services/mirror/mirror_pull_test.go b/services/mirror/mirror_pull_test.go index 67ef37c954..97859be5b0 100644 --- a/services/mirror/mirror_pull_test.go +++ b/services/mirror/mirror_pull_test.go @@ -24,45 +24,45 @@ func Test_parseRemoteUpdateOutput(t *testing.T) { ` results := parseRemoteUpdateOutput(output, "origin") assert.Len(t, results, 10) - assert.EqualValues(t, "refs/tags/v0.1.8", results[0].refName.String()) - assert.EqualValues(t, gitShortEmptySha, results[0].oldCommitID) - assert.EqualValues(t, "", results[0].newCommitID) + assert.Equal(t, "refs/tags/v0.1.8", results[0].refName.String()) + assert.Equal(t, gitShortEmptySha, results[0].oldCommitID) + assert.Empty(t, results[0].newCommitID) - assert.EqualValues(t, "refs/heads/master", results[1].refName.String()) - assert.EqualValues(t, gitShortEmptySha, results[1].oldCommitID) - assert.EqualValues(t, "", results[1].newCommitID) + assert.Equal(t, "refs/heads/master", results[1].refName.String()) + assert.Equal(t, gitShortEmptySha, results[1].oldCommitID) + assert.Empty(t, results[1].newCommitID) - assert.EqualValues(t, "refs/heads/test1", results[2].refName.String()) - assert.EqualValues(t, "", results[2].oldCommitID) - assert.EqualValues(t, gitShortEmptySha, results[2].newCommitID) + assert.Equal(t, "refs/heads/test1", results[2].refName.String()) + assert.Empty(t, results[2].oldCommitID) + assert.Equal(t, gitShortEmptySha, results[2].newCommitID) - assert.EqualValues(t, "refs/tags/tag1", results[3].refName.String()) - assert.EqualValues(t, "", results[3].oldCommitID) - assert.EqualValues(t, gitShortEmptySha, results[3].newCommitID) + assert.Equal(t, "refs/tags/tag1", results[3].refName.String()) + assert.Empty(t, results[3].oldCommitID) + assert.Equal(t, gitShortEmptySha, results[3].newCommitID) - assert.EqualValues(t, "refs/heads/test2", results[4].refName.String()) - assert.EqualValues(t, "f895a1e", results[4].oldCommitID) - assert.EqualValues(t, "957a993", results[4].newCommitID) + assert.Equal(t, "refs/heads/test2", results[4].refName.String()) + assert.Equal(t, "f895a1e", results[4].oldCommitID) + assert.Equal(t, "957a993", results[4].newCommitID) - assert.EqualValues(t, "refs/heads/test3", results[5].refName.String()) - assert.EqualValues(t, "957a993", results[5].oldCommitID) - assert.EqualValues(t, "a87ba5f", results[5].newCommitID) + assert.Equal(t, "refs/heads/test3", results[5].refName.String()) + assert.Equal(t, "957a993", results[5].oldCommitID) + assert.Equal(t, "a87ba5f", results[5].newCommitID) - assert.EqualValues(t, "refs/pull/26595/head", results[6].refName.String()) - assert.EqualValues(t, gitShortEmptySha, results[6].oldCommitID) - assert.EqualValues(t, "", results[6].newCommitID) + assert.Equal(t, "refs/pull/26595/head", results[6].refName.String()) + assert.Equal(t, gitShortEmptySha, results[6].oldCommitID) + assert.Empty(t, results[6].newCommitID) - assert.EqualValues(t, "refs/pull/26595/merge", results[7].refName.String()) - assert.EqualValues(t, gitShortEmptySha, results[7].oldCommitID) - assert.EqualValues(t, "", results[7].newCommitID) + assert.Equal(t, "refs/pull/26595/merge", results[7].refName.String()) + assert.Equal(t, gitShortEmptySha, results[7].oldCommitID) + assert.Empty(t, results[7].newCommitID) - assert.EqualValues(t, "refs/pull/25873/head", results[8].refName.String()) - assert.EqualValues(t, "e0639e38fb", results[8].oldCommitID) - assert.EqualValues(t, "6db2410489", results[8].newCommitID) + assert.Equal(t, "refs/pull/25873/head", results[8].refName.String()) + assert.Equal(t, "e0639e38fb", results[8].oldCommitID) + assert.Equal(t, "6db2410489", results[8].newCommitID) - assert.EqualValues(t, "refs/pull/25873/merge", results[9].refName.String()) - assert.EqualValues(t, "1c97ebc746", results[9].oldCommitID) - assert.EqualValues(t, "976d27d52f", results[9].newCommitID) + assert.Equal(t, "refs/pull/25873/merge", results[9].refName.String()) + assert.Equal(t, "1c97ebc746", results[9].oldCommitID) + assert.Equal(t, "976d27d52f", results[9].newCommitID) } func Test_checkRecoverableSyncError(t *testing.T) { diff --git a/services/oauth2_provider/access_token.go b/services/oauth2_provider/access_token.go index 5cb6fb64c5..52a73c9572 100644 --- a/services/oauth2_provider/access_token.go +++ b/services/oauth2_provider/access_token.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "slices" + "strconv" "strings" auth "code.gitea.io/gitea/models/auth" @@ -177,7 +178,7 @@ func NewAccessTokenResponse(ctx context.Context, grant *auth.OAuth2Grant, server ExpiresAt: jwt.NewNumericDate(expirationDate.AsTime()), Issuer: setting.AppURL, Audience: []string{app.ClientID}, - Subject: fmt.Sprint(grant.UserID), + Subject: strconv.FormatInt(grant.UserID, 10), }, Nonce: grant.Nonce, } diff --git a/services/oauth2_provider/jwtsigningkey.go b/services/oauth2_provider/jwtsigningkey.go index 6c668db463..3bc4f49410 100644 --- a/services/oauth2_provider/jwtsigningkey.go +++ b/services/oauth2_provider/jwtsigningkey.go @@ -31,7 +31,7 @@ type ErrInvalidAlgorithmType struct { } func (err ErrInvalidAlgorithmType) Error() string { - return fmt.Sprintf("JWT signing algorithm is not supported: %s", err.Algorithm) + return "JWT signing algorithm is not supported: " + err.Algorithm } // JWTSigningKey represents a algorithm/key pair to sign JWTs diff --git a/services/oauth2_provider/token.go b/services/oauth2_provider/token.go index b71b11906e..383bcdb3eb 100644 --- a/services/oauth2_provider/token.go +++ b/services/oauth2_provider/token.go @@ -4,6 +4,7 @@ package oauth2_provider //nolint import ( + "errors" "fmt" "time" @@ -44,12 +45,12 @@ func ParseToken(jwtToken string, signingKey JWTSigningKey) (*Token, error) { return nil, err } if !parsedToken.Valid { - return nil, fmt.Errorf("invalid token") + return nil, errors.New("invalid token") } var token *Token var ok bool if token, ok = parsedToken.Claims.(*Token); !ok || !parsedToken.Valid { - return nil, fmt.Errorf("invalid token") + return nil, errors.New("invalid token") } return token, nil } diff --git a/services/org/team.go b/services/org/team.go index ee3bd898ea..6890dafd90 100644 --- a/services/org/team.go +++ b/services/org/team.go @@ -259,37 +259,6 @@ func AddTeamMember(ctx context.Context, team *organization.Team, user *user_mode } team.NumMembers++ - - // Give access to team repositories. - // update exist access if mode become bigger - subQuery := builder.Select("repo_id").From("team_repo"). - Where(builder.Eq{"team_id": team.ID}) - - if _, err := sess.Where("user_id=?", user.ID). - In("repo_id", subQuery). - And("mode < ?", team.AccessMode). - SetExpr("mode", team.AccessMode). - Update(new(access_model.Access)); err != nil { - return fmt.Errorf("update user accesses: %w", err) - } - - // for not exist access - var repoIDs []int64 - accessSubQuery := builder.Select("repo_id").From("access").Where(builder.Eq{"user_id": user.ID}) - if err := sess.SQL(subQuery.And(builder.NotIn("repo_id", accessSubQuery))).Find(&repoIDs); err != nil { - return fmt.Errorf("select id accesses: %w", err) - } - - accesses := make([]*access_model.Access, 0, 100) - for i, repoID := range repoIDs { - accesses = append(accesses, &access_model.Access{RepoID: repoID, UserID: user.ID, Mode: team.AccessMode}) - if (i%100 == 0 || i == len(repoIDs)-1) && len(accesses) > 0 { - if err = db.Insert(ctx, accesses); err != nil { - return fmt.Errorf("insert new user accesses: %w", err) - } - accesses = accesses[:0] - } - } return nil }) if err != nil { diff --git a/services/org/team_test.go b/services/org/team_test.go index 3791776e46..831fe1669f 100644 --- a/services/org/team_test.go +++ b/services/org/team_test.go @@ -88,7 +88,7 @@ func TestUpdateTeam(t *testing.T) { assert.True(t, strings.HasPrefix(team.Description, "A long description!")) access := unittest.AssertExistsAndLoadBean(t, &access_model.Access{UserID: 4, RepoID: 3}) - assert.EqualValues(t, perm.AccessModeAdmin, access.Mode) + assert.Equal(t, perm.AccessModeAdmin, access.Mode) unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID}) } @@ -166,24 +166,6 @@ func TestRemoveTeamMember(t *testing.T) { assert.True(t, organization.IsErrLastOrgOwner(err)) } -func TestRepository_RecalculateAccesses3(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - team5 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}) - user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29}) - - has, err := db.GetEngine(db.DefaultContext).Get(&access_model.Access{UserID: user29.ID, RepoID: 23}) - assert.NoError(t, err) - assert.False(t, has) - - // adding user29 to team5 should add an explicit access row for repo 23 - // even though repo 23 is public - assert.NoError(t, AddTeamMember(db.DefaultContext, team5, user29)) - - has, err = db.GetEngine(db.DefaultContext).Get(&access_model.Access{UserID: user29.ID, RepoID: 23}) - assert.NoError(t, err) - assert.True(t, has) -} - func TestIncludesAllRepositoriesTeams(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) diff --git a/services/org/user_test.go b/services/org/user_test.go index 96d1a1c8ca..c61d600d90 100644 --- a/services/org/user_test.go +++ b/services/org/user_test.go @@ -53,7 +53,7 @@ func TestRemoveOrgUser(t *testing.T) { assert.NoError(t, RemoveOrgUser(db.DefaultContext, org, user)) unittest.AssertNotExistsBean(t, &organization.OrgUser{OrgID: org.ID, UID: user.ID}) org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: org.ID}) - assert.EqualValues(t, expectedNumMembers, org.NumMembers) + assert.Equal(t, expectedNumMembers, org.NumMembers) } org3 := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) diff --git a/services/packages/alpine/repository.go b/services/packages/alpine/repository.go index 27e6391980..277c188874 100644 --- a/services/packages/alpine/repository.go +++ b/services/packages/alpine/repository.go @@ -290,7 +290,7 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package privPem, _ := pem.Decode([]byte(priv)) if privPem == nil { - return fmt.Errorf("failed to decode private key pem") + return errors.New("failed to decode private key pem") } privKey, err := x509.ParsePKCS1PrivateKey(privPem.Bytes) diff --git a/services/packages/arch/repository.go b/services/packages/arch/repository.go index a12af82ba5..438bb10837 100644 --- a/services/packages/arch/repository.go +++ b/services/packages/arch/repository.go @@ -13,6 +13,7 @@ import ( "fmt" "io" "os" + "strconv" "strings" packages_model "code.gitea.io/gitea/models/packages" @@ -372,8 +373,8 @@ func writeDescription(tw *tar.Writer, opts *entryOptions) error { {"MD5SUM", opts.Blob.HashMD5}, {"SHA256SUM", opts.Blob.HashSHA256}, {"PGPSIG", opts.Signature}, - {"CSIZE", fmt.Sprintf("%d", opts.Blob.Size)}, - {"ISIZE", fmt.Sprintf("%d", opts.FileMetadata.InstalledSize)}, + {"CSIZE", strconv.FormatInt(opts.Blob.Size, 10)}, + {"ISIZE", strconv.FormatInt(opts.FileMetadata.InstalledSize, 10)}, {"NAME", opts.Package.Name}, {"BASE", opts.FileMetadata.Base}, {"ARCH", opts.FileMetadata.Architecture}, @@ -382,7 +383,7 @@ func writeDescription(tw *tar.Writer, opts *entryOptions) error { {"URL", opts.VersionMetadata.ProjectURL}, {"LICENSE", strings.Join(opts.VersionMetadata.Licenses, "\n")}, {"GROUPS", strings.Join(opts.FileMetadata.Groups, "\n")}, - {"BUILDDATE", fmt.Sprintf("%d", opts.FileMetadata.BuildDate)}, + {"BUILDDATE", strconv.FormatInt(opts.FileMetadata.BuildDate, 10)}, {"PACKAGER", opts.FileMetadata.Packager}, {"PROVIDES", strings.Join(opts.FileMetadata.Provides, "\n")}, {"REPLACES", strings.Join(opts.FileMetadata.Replaces, "\n")}, diff --git a/services/packages/auth.go b/services/packages/auth.go index 4526a8e303..6e87643e29 100644 --- a/services/packages/auth.go +++ b/services/packages/auth.go @@ -4,6 +4,7 @@ package packages import ( + "errors" "fmt" "net/http" "strings" @@ -58,7 +59,7 @@ func ParseAuthorizationRequest(req *http.Request) (*PackageMeta, error) { parts := strings.SplitN(h, " ", 2) if len(parts) != 2 { log.Error("split token failed: %s", h) - return nil, fmt.Errorf("split token failed") + return nil, errors.New("split token failed") } return ParseAuthorizationToken(parts[1]) @@ -77,7 +78,7 @@ func ParseAuthorizationToken(tokenStr string) (*PackageMeta, error) { c, ok := token.Claims.(*packageClaims) if !token.Valid || !ok { - return nil, fmt.Errorf("invalid token claim") + return nil, errors.New("invalid token claim") } return &c.PackageMeta, nil diff --git a/services/packages/cargo/index.go b/services/packages/cargo/index.go index 0c8a98c40f..decb224b85 100644 --- a/services/packages/cargo/index.go +++ b/services/packages/cargo/index.go @@ -247,7 +247,7 @@ func createOrUpdateConfigFile(ctx context.Context, repo *repo_model.Repository, "Initialize Cargo Config", func(t *files_service.TemporaryUploadRepository) error { var b bytes.Buffer - err := json.NewEncoder(&b).Encode(BuildConfig(owner, setting.Service.RequireSignInView || owner.Visibility != structs.VisibleTypePublic || repo.IsPrivate)) + err := json.NewEncoder(&b).Encode(BuildConfig(owner, setting.Service.RequireSignInViewStrict || owner.Visibility != structs.VisibleTypePublic || repo.IsPrivate)) if err != nil { return err } diff --git a/services/packages/rpm/repository.go b/services/packages/rpm/repository.go index a7d196c15c..5027021c52 100644 --- a/services/packages/rpm/repository.go +++ b/services/packages/rpm/repository.go @@ -408,7 +408,6 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs [] files = append(files, f) } } - packageVersion := fmt.Sprintf("%s-%s", pd.FileMetadata.Version, pd.FileMetadata.Release) packages = append(packages, &Package{ Type: "rpm", Name: pd.Package.Name, @@ -437,7 +436,7 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs [] Archive: pd.FileMetadata.ArchiveSize, }, Location: Location{ - Href: fmt.Sprintf("package/%s/%s/%s/%s-%s.%s.rpm", pd.Package.Name, packageVersion, pd.FileMetadata.Architecture, pd.Package.Name, packageVersion, pd.FileMetadata.Architecture), + Href: fmt.Sprintf("package/%s/%s/%s/%s-%s.%s.rpm", pd.Package.Name, pd.Version.Version, pd.FileMetadata.Architecture, pd.Package.Name, pd.Version.Version, pd.FileMetadata.Architecture), }, Format: Format{ License: pd.VersionMetadata.License, diff --git a/services/projects/issue.go b/services/projects/issue.go index 090d19d2f4..590fe960d5 100644 --- a/services/projects/issue.go +++ b/services/projects/issue.go @@ -5,7 +5,7 @@ package project import ( "context" - "fmt" + "errors" "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" @@ -29,7 +29,7 @@ func MoveIssuesOnProjectColumn(ctx context.Context, doer *user_model.User, colum return err } if int(count) != len(sortedIssueIDs) { - return fmt.Errorf("all issues have to be added to a project first") + return errors.New("all issues have to be added to a project first") } issues, err := issues_model.GetIssuesByIDs(ctx, issueIDs) diff --git a/services/projects/issue_test.go b/services/projects/issue_test.go index b6f0b1dae1..e76d31e757 100644 --- a/services/projects/issue_test.go +++ b/services/projects/issue_test.go @@ -130,7 +130,7 @@ func Test_Projects(t *testing.T) { }) assert.NoError(t, err) assert.Len(t, projects, 1) - assert.EqualValues(t, project1.ID, projects[0].ID) + assert.Equal(t, project1.ID, projects[0].ID) t.Run("Authenticated user", func(t *testing.T) { columnIssues, err := LoadIssuesFromProject(db.DefaultContext, projects[0], &issues_model.IssuesOptions{ diff --git a/services/pull/check.go b/services/pull/check.go index 9b159891d7..b036970fbf 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -400,7 +400,7 @@ func Init() error { prPatchCheckerQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "pr_patch_checker", handler) if prPatchCheckerQueue == nil { - return fmt.Errorf("unable to create pr_patch_checker queue") + return errors.New("unable to create pr_patch_checker queue") } go graceful.GetManager().RunWithCancel(prPatchCheckerQueue) diff --git a/services/pull/check_test.go b/services/pull/check_test.go index 5508a70f45..6d85ac158e 100644 --- a/services/pull/check_test.go +++ b/services/pull/check_test.go @@ -51,7 +51,7 @@ func TestPullRequest_AddToTaskQueue(t *testing.T) { select { case id := <-idChan: - assert.EqualValues(t, pr.ID, id) + assert.Equal(t, pr.ID, id) case <-time.After(time.Second): assert.FailNow(t, "Timeout: nothing was added to pullRequestQueue") } diff --git a/services/pull/merge.go b/services/pull/merge.go index 1e1ca55bc1..9804d8aac1 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -6,6 +6,7 @@ package pull import ( "context" + "errors" "fmt" "os" "path/filepath" @@ -59,7 +60,7 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue issueReference = "!" } - reviewedOn := fmt.Sprintf("Reviewed-on: %s", httplib.MakeAbsoluteURL(ctx, pr.Issue.Link())) + reviewedOn := "Reviewed-on: " + httplib.MakeAbsoluteURL(ctx, pr.Issue.Link()) reviewedBy := pr.GetApprovers(ctx) if mergeStyle != "" { @@ -621,13 +622,13 @@ func MergedManually(ctx context.Context, pr *issues_model.PullRequest, doer *use objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName) if len(commitID) != objectFormat.FullLength() { - return fmt.Errorf("Wrong commit ID") + return errors.New("Wrong commit ID") } commit, err := baseGitRepo.GetCommit(commitID) if err != nil { if git.IsErrNotExist(err) { - return fmt.Errorf("Wrong commit ID") + return errors.New("Wrong commit ID") } return err } @@ -638,14 +639,14 @@ func MergedManually(ctx context.Context, pr *issues_model.PullRequest, doer *use return err } if !ok { - return fmt.Errorf("Wrong commit ID") + return errors.New("Wrong commit ID") } var merged bool if merged, err = SetMerged(ctx, pr, commitID, timeutil.TimeStamp(commit.Author.When.Unix()), doer, issues_model.PullRequestStatusManuallyMerged); err != nil { return err } else if !merged { - return fmt.Errorf("SetMerged failed") + return errors.New("SetMerged failed") } return nil }) diff --git a/services/pull/merge_squash.go b/services/pull/merge_squash.go index 076189fd7a..72660cd3c5 100644 --- a/services/pull/merge_squash.go +++ b/services/pull/merge_squash.go @@ -66,8 +66,8 @@ func doMergeStyleSquash(ctx *mergeContext, message string) error { if setting.Repository.PullRequest.AddCoCommitterTrailers && ctx.committer.String() != sig.String() { // add trailer - if !strings.Contains(message, fmt.Sprintf("Co-authored-by: %s", sig.String())) { - message += fmt.Sprintf("\nCo-authored-by: %s", sig.String()) + if !strings.Contains(message, "Co-authored-by: "+sig.String()) { + message += "\nCo-authored-by: " + sig.String() } message += fmt.Sprintf("\nCo-committed-by: %s\n", sig.String()) } diff --git a/services/pull/patch.go b/services/pull/patch.go index 29f2f992ab..7a24237724 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -134,7 +134,7 @@ type errMergeConflict struct { } func (e *errMergeConflict) Error() string { - return fmt.Sprintf("conflict detected at: %s", e.filename) + return "conflict detected at: " + e.filename } func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, filesToRemove *[]string, filesToAdd *[]git.IndexObjectInfo) error { @@ -355,23 +355,19 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * } // 3b. Create a plain patch from head to base - tmpPatchFile, err := os.CreateTemp("", "patch") + tmpPatchFile, cleanup, err := setting.AppDataTempDir("git-repo-content").CreateTempFileRandom("patch") if err != nil { log.Error("Unable to create temporary patch file! Error: %v", err) return false, fmt.Errorf("unable to create temporary patch file! Error: %w", err) } - defer func() { - _ = util.Remove(tmpPatchFile.Name()) - }() + defer cleanup() if err := gitRepo.GetDiffBinary(pr.MergeBase+"...tracking", tmpPatchFile); err != nil { - tmpPatchFile.Close() log.Error("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err) return false, fmt.Errorf("unable to get patch file from %s to %s in %s Error: %w", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err) } stat, err := tmpPatchFile.Stat() if err != nil { - tmpPatchFile.Close() return false, fmt.Errorf("unable to stat patch file: %w", err) } patchPath := tmpPatchFile.Name() diff --git a/services/pull/pull.go b/services/pull/pull.go index 4641d4ac40..13cbb40110 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -763,7 +763,7 @@ func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *re var errs []error for _, branch := range branches { - prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repo.ID, branch.Name) + prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repo.ID, branch) if err != nil { return err } diff --git a/services/pull/review.go b/services/pull/review.go index 78723a58ae..5c80e7b338 100644 --- a/services/pull/review.go +++ b/services/pull/review.go @@ -395,7 +395,7 @@ func DismissReview(ctx context.Context, reviewID, repoID int64, message string, } if review.Type != issues_model.ReviewTypeApprove && review.Type != issues_model.ReviewTypeReject { - return nil, fmt.Errorf("not need to dismiss this review because it's type is not Approve or change request") + return nil, errors.New("not need to dismiss this review because it's type is not Approve or change request") } // load data for notify @@ -405,7 +405,7 @@ func DismissReview(ctx context.Context, reviewID, repoID int64, message string, // Check if the review's repoID is the one we're currently expecting. if review.Issue.RepoID != repoID { - return nil, fmt.Errorf("reviews's repository is not the same as the one we expect") + return nil, errors.New("reviews's repository is not the same as the one we expect") } issue := review.Issue diff --git a/services/pull/temp_repo.go b/services/pull/temp_repo.go index 3f33370798..d543e3d4a3 100644 --- a/services/pull/temp_repo.go +++ b/services/pull/temp_repo.go @@ -74,11 +74,13 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) } // Clone base repo. - tmpBasePath, err := repo_module.CreateTemporaryPath("pull") + tmpBasePath, cleanup, err := repo_module.CreateTemporaryPath("pull") if err != nil { log.Error("CreateTemporaryPath[%-v]: %v", pr, err) return nil, nil, err } + cancel = cleanup + prCtx = &prContext{ Context: ctx, tmpBasePath: tmpBasePath, @@ -86,11 +88,6 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) outbuf: &strings.Builder{}, errbuf: &strings.Builder{}, } - cancel = func() { - if err := repo_module.RemoveTemporaryPath(tmpBasePath); err != nil { - log.Error("Error whilst removing removing temporary repo for %-v: %v", pr, err) - } - } baseRepoPath := pr.BaseRepo.RepoPath() headRepoPath := pr.HeadRepo.RepoPath() diff --git a/services/pull/update.go b/services/pull/update.go index 3e00dd4e65..5cc5e2b134 100644 --- a/services/pull/update.go +++ b/services/pull/update.go @@ -5,6 +5,7 @@ package pull import ( "context" + "errors" "fmt" git_model "code.gitea.io/gitea/models/git" @@ -23,7 +24,7 @@ import ( func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, message string, rebase bool) error { if pr.Flow == issues_model.PullRequestFlowAGit { // TODO: update of agit flow pull request's head branch is unsupported - return fmt.Errorf("update of agit flow pull request's head branch is unsupported") + return errors.New("update of agit flow pull request's head branch is unsupported") } releaser, err := globallock.Lock(ctx, getPullWorkingLockKey(pr.ID)) diff --git a/services/release/release_test.go b/services/release/release_test.go index 95a54832b9..36a9f667d6 100644 --- a/services/release/release_test.go +++ b/services/release/release_test.go @@ -250,9 +250,9 @@ func TestRelease_Update(t *testing.T) { assert.NoError(t, UpdateRelease(db.DefaultContext, user, gitRepo, release, []string{attach.UUID}, nil, nil)) assert.NoError(t, repo_model.GetReleaseAttachments(db.DefaultContext, release)) assert.Len(t, release.Attachments, 1) - assert.EqualValues(t, attach.UUID, release.Attachments[0].UUID) - assert.EqualValues(t, release.ID, release.Attachments[0].ReleaseID) - assert.EqualValues(t, attach.Name, release.Attachments[0].Name) + assert.Equal(t, attach.UUID, release.Attachments[0].UUID) + assert.Equal(t, release.ID, release.Attachments[0].ReleaseID) + assert.Equal(t, attach.Name, release.Attachments[0].Name) // update the attachment name assert.NoError(t, UpdateRelease(db.DefaultContext, user, gitRepo, release, nil, nil, map[string]string{ @@ -261,9 +261,9 @@ func TestRelease_Update(t *testing.T) { release.Attachments = nil assert.NoError(t, repo_model.GetReleaseAttachments(db.DefaultContext, release)) assert.Len(t, release.Attachments, 1) - assert.EqualValues(t, attach.UUID, release.Attachments[0].UUID) - assert.EqualValues(t, release.ID, release.Attachments[0].ReleaseID) - assert.EqualValues(t, "test2.txt", release.Attachments[0].Name) + assert.Equal(t, attach.UUID, release.Attachments[0].UUID) + assert.Equal(t, release.ID, release.Attachments[0].ReleaseID) + assert.Equal(t, "test2.txt", release.Attachments[0].Name) // delete the attachment assert.NoError(t, UpdateRelease(db.DefaultContext, user, gitRepo, release, nil, []string{attach.UUID}, nil)) diff --git a/services/repository/adopt.go b/services/repository/adopt.go index b7321156d9..7f1954145c 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -16,7 +16,6 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" @@ -28,18 +27,30 @@ import ( "github.com/gobwas/glob" ) +func deleteFailedAdoptRepository(repoID int64) error { + return db.WithTx(db.DefaultContext, func(ctx context.Context) error { + if err := deleteDBRepository(ctx, repoID); err != nil { + return fmt.Errorf("deleteDBRepository: %w", err) + } + if err := git_model.DeleteRepoBranches(ctx, repoID); err != nil { + return fmt.Errorf("deleteRepoBranches: %w", err) + } + return repo_model.DeleteRepoReleases(ctx, repoID) + }) +} + // AdoptRepository adopts pre-existing repository files for the user/organization. -func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { - if !doer.IsAdmin && !u.CanCreateRepo() { +func AdoptRepository(ctx context.Context, doer, owner *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { + if !doer.CanCreateRepoIn(owner) { return nil, repo_model.ErrReachLimitOfRepo{ - Limit: u.MaxRepoCreation, + Limit: owner.MaxRepoCreation, } } repo := &repo_model.Repository{ - OwnerID: u.ID, - Owner: u, - OwnerName: u.Name, + OwnerID: owner.ID, + Owner: owner, + OwnerName: owner.Name, Name: opts.Name, LowerName: strings.ToLower(opts.Name), Description: opts.Description, @@ -48,59 +59,52 @@ func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateR IsPrivate: opts.IsPrivate, IsFsckEnabled: !opts.IsMirror, CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, - Status: opts.Status, + Status: repo_model.RepositoryBeingMigrated, IsEmpty: !opts.AutoInit, } - if err := db.WithTx(ctx, func(ctx context.Context) error { - isExist, err := gitrepo.IsRepositoryExist(ctx, repo) + // 1 - create the repository database operations first + err := db.WithTx(ctx, func(ctx context.Context) error { + return createRepositoryInDB(ctx, doer, owner, repo, false) + }) + if err != nil { + return nil, err + } + + // last - clean up if something goes wrong + // WARNING: Don't override all later err with local variables + defer func() { if err != nil { - log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err) - return err - } - if !isExist { - return repo_model.ErrRepoNotExist{ - OwnerName: u.Name, - Name: repo.Name, + // we can not use the ctx because it maybe canceled or timeout + if errDel := deleteFailedAdoptRepository(repo.ID); errDel != nil { + log.Error("Failed to delete repository %s that could not be adopted: %v", repo.FullName(), errDel) } } + }() - if err := CreateRepositoryByExample(ctx, doer, u, repo, true, false); err != nil { - return err - } - - // Re-fetch the repository from database before updating it (else it would - // override changes that were done earlier with sql) - if repo, err = repo_model.GetRepositoryByID(ctx, repo.ID); err != nil { - return fmt.Errorf("getRepositoryByID: %w", err) - } - return nil - }); err != nil { - return nil, err + // Re-fetch the repository from database before updating it (else it would + // override changes that were done earlier with sql) + if repo, err = repo_model.GetRepositoryByID(ctx, repo.ID); err != nil { + return nil, fmt.Errorf("getRepositoryByID: %w", err) } - if err := func() error { - if err := adoptRepository(ctx, repo, opts.DefaultBranch); err != nil { - return fmt.Errorf("adoptRepository: %w", err) - } - - if err := repo_module.CheckDaemonExportOK(ctx, repo); err != nil { - return fmt.Errorf("checkDaemonExportOK: %w", err) - } - - if stdout, _, err := git.NewCommand("update-server-info"). - RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}); err != nil { - log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) - return fmt.Errorf("CreateRepository(git update-server-info): %w", err) - } - return nil - }(); err != nil { - if errDel := DeleteRepository(ctx, doer, repo, false /* no notify */); errDel != nil { - log.Error("Failed to delete repository %s that could not be adopted: %v", repo.FullName(), errDel) - } - return nil, err + // 2 - adopt the repository from disk + if err = adoptRepository(ctx, repo, opts.DefaultBranch); err != nil { + return nil, fmt.Errorf("adoptRepository: %w", err) } - notify_service.AdoptRepository(ctx, doer, u, repo) + + // 3 - Update the git repository + if err = updateGitRepoAfterCreate(ctx, repo); err != nil { + return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err) + } + + // 4 - update repository status + repo.Status = repo_model.RepositoryReady + if err = repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil { + return nil, fmt.Errorf("UpdateRepositoryCols: %w", err) + } + + notify_service.AdoptRepository(ctx, doer, owner, repo) return repo, nil } @@ -192,7 +196,7 @@ func adoptRepository(ctx context.Context, repo *repo_model.Repository, defaultBr return fmt.Errorf("setDefaultBranch: %w", err) } } - if err = repo_module.UpdateRepository(ctx, repo, false); err != nil { + if err = updateRepository(ctx, repo, false); err != nil { return fmt.Errorf("updateRepository: %w", err) } diff --git a/services/repository/adopt_test.go b/services/repository/adopt_test.go index 294185ea1f..6e1dc417b3 100644 --- a/services/repository/adopt_test.go +++ b/services/repository/adopt_test.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) @@ -89,10 +90,36 @@ func TestListUnadoptedRepositories_ListOptions(t *testing.T) { func TestAdoptRepository(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - assert.NoError(t, unittest.SyncDirs(filepath.Join(setting.RepoRootPath, "user2", "repo1.git"), filepath.Join(setting.RepoRootPath, "user2", "test-adopt.git"))) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - _, err := AdoptRepository(db.DefaultContext, user2, user2, CreateRepoOptions{Name: "test-adopt"}) + + // a successful adopt + destDir := filepath.Join(setting.RepoRootPath, user2.Name, "test-adopt.git") + assert.NoError(t, unittest.SyncDirs(filepath.Join(setting.RepoRootPath, user2.Name, "repo1.git"), destDir)) + + adoptedRepo, err := AdoptRepository(db.DefaultContext, user2, user2, CreateRepoOptions{Name: "test-adopt"}) assert.NoError(t, err) repoTestAdopt := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "test-adopt"}) assert.Equal(t, "sha1", repoTestAdopt.ObjectFormatName) + + // just delete the adopted repo's db records + err = deleteFailedAdoptRepository(adoptedRepo.ID) + assert.NoError(t, err) + + unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: "test-adopt"}) + + // a failed adopt because some mock data + // remove the hooks directory and create a file so that we cannot create the hooks successfully + _ = os.RemoveAll(filepath.Join(destDir, "hooks", "update.d")) + assert.NoError(t, os.WriteFile(filepath.Join(destDir, "hooks", "update.d"), []byte("tests"), os.ModePerm)) + + adoptedRepo, err = AdoptRepository(db.DefaultContext, user2, user2, CreateRepoOptions{Name: "test-adopt"}) + assert.Error(t, err) + assert.Nil(t, adoptedRepo) + + unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: "test-adopt"}) + + exist, err := util.IsExist(repo_model.RepoPath(user2.Name, "test-adopt")) + assert.NoError(t, err) + assert.True(t, exist) // the repository should be still in the disk } diff --git a/services/repository/archiver/archiver.go b/services/repository/archiver/archiver.go index d39acc080d..a657e3884c 100644 --- a/services/repository/archiver/archiver.go +++ b/services/repository/archiver/archiver.go @@ -44,7 +44,7 @@ type ErrUnknownArchiveFormat struct { // Error implements error func (err ErrUnknownArchiveFormat) Error() string { - return fmt.Sprintf("unknown format: %s", err.RequestNameType) + return "unknown format: " + err.RequestNameType } // Is implements error @@ -60,7 +60,7 @@ type RepoRefNotFoundError struct { // Error implements error. func (e RepoRefNotFoundError) Error() string { - return fmt.Sprintf("unrecognized repository reference: %s", e.RefShortName) + return "unrecognized repository reference: " + e.RefShortName } func (e RepoRefNotFoundError) Is(err error) bool { diff --git a/services/repository/archiver/archiver_test.go b/services/repository/archiver/archiver_test.go index 522f90558a..87324ad38c 100644 --- a/services/repository/archiver/archiver_test.go +++ b/services/repository/archiver/archiver_test.go @@ -33,7 +33,7 @@ func TestArchive_Basic(t *testing.T) { bogusReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") assert.NoError(t, err) assert.NotNil(t, bogusReq) - assert.EqualValues(t, firstCommit+".zip", bogusReq.GetArchiveName()) + assert.Equal(t, firstCommit+".zip", bogusReq.GetArchiveName()) // Check a series of bogus requests. // Step 1, valid commit with a bad extension. @@ -54,12 +54,12 @@ func TestArchive_Basic(t *testing.T) { bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "master.zip") assert.NoError(t, err) assert.NotNil(t, bogusReq) - assert.EqualValues(t, "master.zip", bogusReq.GetArchiveName()) + assert.Equal(t, "master.zip", bogusReq.GetArchiveName()) bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "test/archive.zip") assert.NoError(t, err) assert.NotNil(t, bogusReq) - assert.EqualValues(t, "test-archive.zip", bogusReq.GetArchiveName()) + assert.Equal(t, "test-archive.zip", bogusReq.GetArchiveName()) // Now two valid requests, firstCommit with valid extensions. zipReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") diff --git a/services/repository/avatar_test.go b/services/repository/avatar_test.go index bea820e85f..2dc5173eec 100644 --- a/services/repository/avatar_test.go +++ b/services/repository/avatar_test.go @@ -59,7 +59,7 @@ func TestDeleteAvatar(t *testing.T) { err = DeleteAvatar(db.DefaultContext, repo) assert.NoError(t, err) - assert.Equal(t, "", repo.Avatar) + assert.Empty(t, repo.Avatar) } func TestGenerateAvatar(t *testing.T) { diff --git a/services/repository/branch.go b/services/repository/branch.go index 8804778bd5..94c47ffdc4 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -303,7 +303,7 @@ func SyncBranchesToDB(ctx context.Context, repoID, pusherID int64, branchNames, // For other batches, it will hit optimization 4. if len(branchNames) != len(commitIDs) { - return fmt.Errorf("branchNames and commitIDs length not match") + return errors.New("branchNames and commitIDs length not match") } return db.WithTx(ctx, func(ctx context.Context) error { diff --git a/services/repository/contributors_graph_test.go b/services/repository/contributors_graph_test.go index 6db93f6a64..7d32b1c931 100644 --- a/services/repository/contributors_graph_test.go +++ b/services/repository/contributors_graph_test.go @@ -38,14 +38,14 @@ func TestRepository_ContributorsGraph(t *testing.T) { keys = append(keys, k) } slices.Sort(keys) - assert.EqualValues(t, []string{ + assert.Equal(t, []string{ "ethantkoenig@gmail.com", "jimmy.praet@telenet.be", "jon@allspice.io", "total", // generated summary }, keys) - assert.EqualValues(t, &ContributorData{ + assert.Equal(t, &ContributorData{ Name: "Ethan Koenig", AvatarLink: "/assets/img/avatar_default.png", TotalCommits: 1, @@ -58,7 +58,7 @@ func TestRepository_ContributorsGraph(t *testing.T) { }, }, }, data["ethantkoenig@gmail.com"]) - assert.EqualValues(t, &ContributorData{ + assert.Equal(t, &ContributorData{ Name: "Total", AvatarLink: "", TotalCommits: 3, diff --git a/services/repository/create.go b/services/repository/create.go index 1a6a68b35a..897f2a1d14 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" + system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/models/webhook" @@ -28,7 +29,6 @@ import ( "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/templates/vars" - "code.gitea.io/gitea/modules/util" ) // CreateRepoOptions contains the create repository options @@ -140,21 +140,20 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir // InitRepository initializes README and .gitignore if needed. func initRepository(ctx context.Context, u *user_model.User, repo *repo_model.Repository, opts CreateRepoOptions) (err error) { - if err = repo_module.CheckInitRepository(ctx, repo); err != nil { - return err + // Init git bare new repository. + if err = git.InitRepository(ctx, repo.RepoPath(), true, repo.ObjectFormatName); err != nil { + return fmt.Errorf("git.InitRepository: %w", err) + } else if err = gitrepo.CreateDelegateHooks(ctx, repo); err != nil { + return fmt.Errorf("createDelegateHooks: %w", err) } // Initialize repository according to user's choice. if opts.AutoInit { - tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-"+repo.Name) + tmpDir, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("repos-" + repo.Name) if err != nil { - return fmt.Errorf("Failed to create temp dir for repository %s: %w", repo.FullName(), err) + return fmt.Errorf("failed to create temp dir for repository %s: %w", repo.FullName(), err) } - defer func() { - if err := util.RemoveAll(tmpDir); err != nil { - log.Warn("Unable to remove temporary directory: %s: Error: %v", tmpDir, err) - } - }() + defer cleanup() if err = prepareRepoCommit(ctx, repo, tmpDir, opts); err != nil { return fmt.Errorf("prepareRepoCommit: %w", err) @@ -200,10 +199,10 @@ func initRepository(ctx context.Context, u *user_model.User, repo *repo_model.Re } // CreateRepositoryDirectly creates a repository for the user/organization. -func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { - if !doer.IsAdmin && !u.CanCreateRepo() { +func CreateRepositoryDirectly(ctx context.Context, doer, owner *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { + if !doer.CanCreateRepoIn(owner) { return nil, repo_model.ErrReachLimitOfRepo{ - Limit: u.MaxRepoCreation, + Limit: owner.MaxRepoCreation, } } @@ -223,9 +222,9 @@ func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opt } repo := &repo_model.Repository{ - OwnerID: u.ID, - Owner: u, - OwnerName: u.Name, + OwnerID: owner.ID, + Owner: owner, + OwnerName: owner.Name, Name: opts.Name, LowerName: strings.ToLower(opts.Name), Description: opts.Description, @@ -244,100 +243,93 @@ func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opt ObjectFormatName: opts.ObjectFormatName, } - var rollbackRepo *repo_model.Repository - - if err := db.WithTx(ctx, func(ctx context.Context) error { - if err := CreateRepositoryByExample(ctx, doer, u, repo, false, false); err != nil { - return err - } - - // No need for init mirror. - if opts.IsMirror { - return nil - } - - isExist, err := gitrepo.IsRepositoryExist(ctx, repo) - if err != nil { - log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err) - return err - } - if isExist { - // repo already exists - We have two or three options. - // 1. We fail stating that the directory exists - // 2. We create the db repository to go with this data and adopt the git repo - // 3. We delete it and start afresh - // - // Previously Gitea would just delete and start afresh - this was naughty. - // So we will now fail and delegate to other functionality to adopt or delete - log.Error("Files already exist in %s and we are not going to adopt or delete.", repo.FullName()) - return repo_model.ErrRepoFilesAlreadyExist{ - Uname: u.Name, - Name: repo.Name, - } - } - - if err = initRepository(ctx, doer, repo, opts); err != nil { - if err2 := gitrepo.DeleteRepository(ctx, repo); err2 != nil { - log.Error("initRepository: %v", err) - return fmt.Errorf( - "delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2) - } - return fmt.Errorf("initRepository: %w", err) - } - - // Initialize Issue Labels if selected - if len(opts.IssueLabels) > 0 { - if err = repo_module.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil { - rollbackRepo = repo - rollbackRepo.OwnerID = u.ID - return fmt.Errorf("InitializeLabels: %w", err) - } - } - - if err := repo_module.CheckDaemonExportOK(ctx, repo); err != nil { - return fmt.Errorf("checkDaemonExportOK: %w", err) - } - - if stdout, _, err := git.NewCommand("update-server-info"). - RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}); err != nil { - log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) - rollbackRepo = repo - rollbackRepo.OwnerID = u.ID - return fmt.Errorf("CreateRepository(git update-server-info): %w", err) - } - - // update licenses - var licenses []string - if len(opts.License) > 0 { - licenses = append(licenses, opts.License) - - stdout, _, err := git.NewCommand("rev-parse", "HEAD").RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}) - if err != nil { - log.Error("CreateRepository(git rev-parse HEAD) in %v: Stdout: %s\nError: %v", repo, stdout, err) - rollbackRepo = repo - rollbackRepo.OwnerID = u.ID - return fmt.Errorf("CreateRepository(git rev-parse HEAD): %w", err) - } - if err := repo_model.UpdateRepoLicenses(ctx, repo, stdout, licenses); err != nil { - return err - } - } - return nil - }); err != nil { - if rollbackRepo != nil { - if errDelete := DeleteRepositoryDirectly(ctx, doer, rollbackRepo.ID); errDelete != nil { - log.Error("Rollback deleteRepository: %v", errDelete) - } - } + needsUpdateStatus := opts.Status != repo_model.RepositoryReady + // 1 - create the repository database operations first + err := db.WithTx(ctx, func(ctx context.Context) error { + return createRepositoryInDB(ctx, doer, owner, repo, false) + }) + if err != nil { return nil, err } + // last - clean up if something goes wrong + // WARNING: Don't override all later err with local variables + defer func() { + if err != nil { + // we can not use the ctx because it maybe canceled or timeout + cleanupRepository(doer, repo.ID) + } + }() + + // No need for init mirror. + if opts.IsMirror { + return repo, nil + } + + // 2 - check whether the repository with the same storage exists + var isExist bool + isExist, err = gitrepo.IsRepositoryExist(ctx, repo) + if err != nil { + log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err) + return nil, err + } + if isExist { + log.Error("Files already exist in %s and we are not going to adopt or delete.", repo.FullName()) + // Don't return directly, we need err in defer to cleanupRepository + err = repo_model.ErrRepoFilesAlreadyExist{ + Uname: repo.OwnerName, + Name: repo.Name, + } + return nil, err + } + + // 3 - init git repository in storage + if err = initRepository(ctx, doer, repo, opts); err != nil { + return nil, fmt.Errorf("initRepository: %w", err) + } + + // 4 - Initialize Issue Labels if selected + if len(opts.IssueLabels) > 0 { + if err = repo_module.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil { + return nil, fmt.Errorf("InitializeLabels: %w", err) + } + } + + // 5 - Update the git repository + if err = updateGitRepoAfterCreate(ctx, repo); err != nil { + return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err) + } + + // 6 - update licenses + var licenses []string + if len(opts.License) > 0 { + licenses = append(licenses, opts.License) + + var stdout string + stdout, _, err = git.NewCommand("rev-parse", "HEAD").RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}) + if err != nil { + log.Error("CreateRepository(git rev-parse HEAD) in %v: Stdout: %s\nError: %v", repo, stdout, err) + return nil, fmt.Errorf("CreateRepository(git rev-parse HEAD): %w", err) + } + if err = repo_model.UpdateRepoLicenses(ctx, repo, stdout, licenses); err != nil { + return nil, err + } + } + + // 7 - update repository status to be ready + if needsUpdateStatus { + repo.Status = repo_model.RepositoryReady + if err = repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil { + return nil, fmt.Errorf("UpdateRepositoryCols: %w", err) + } + } + return repo, nil } -// CreateRepositoryByExample creates a repository for the user/organization. -func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository, overwriteOrAdopt, isFork bool) (err error) { +// createRepositoryInDB creates a repository for the user/organization. +func createRepositoryInDB(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository, isFork bool) (err error) { if err = repo_model.IsUsableRepoName(repo.Name); err != nil { return err } @@ -352,19 +344,6 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re } } - isExist, err := gitrepo.IsRepositoryExist(ctx, repo) - if err != nil { - log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err) - return err - } - if !overwriteOrAdopt && isExist { - log.Error("Files already exist in %s and we are not going to adopt or delete.", repo.FullName()) - return repo_model.ErrRepoFilesAlreadyExist{ - Uname: u.Name, - Name: repo.Name, - } - } - if err = db.Insert(ctx, repo); err != nil { return err } @@ -384,7 +363,8 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re } units := make([]repo_model.RepoUnit, 0, len(defaultUnits)) for _, tp := range defaultUnits { - if tp == unit.TypeIssues { + switch tp { + case unit.TypeIssues: units = append(units, repo_model.RepoUnit{ RepoID: repo.ID, Type: tp, @@ -394,7 +374,7 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re EnableDependencies: setting.Service.DefaultEnableDependencies, }, }) - } else if tp == unit.TypePullRequests { + case unit.TypePullRequests: units = append(units, repo_model.RepoUnit{ RepoID: repo.ID, Type: tp, @@ -404,13 +384,13 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re AllowRebaseUpdate: true, }, }) - } else if tp == unit.TypeProjects { + case unit.TypeProjects: units = append(units, repo_model.RepoUnit{ RepoID: repo.ID, Type: tp, Config: &repo_model.ProjectsConfig{ProjectsMode: repo_model.ProjectsModeAll}, }) - } else { + default: units = append(units, repo_model.RepoUnit{ RepoID: repo.ID, Type: tp, @@ -472,3 +452,26 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re return nil } + +func cleanupRepository(doer *user_model.User, repoID int64) { + if errDelete := DeleteRepositoryDirectly(db.DefaultContext, doer, repoID); errDelete != nil { + log.Error("cleanupRepository failed: %v", errDelete) + // add system notice + if err := system_model.CreateRepositoryNotice("DeleteRepositoryDirectly failed when cleanup repository: %v", errDelete); err != nil { + log.Error("CreateRepositoryNotice: %v", err) + } + } +} + +func updateGitRepoAfterCreate(ctx context.Context, repo *repo_model.Repository) error { + if err := checkDaemonExportOK(ctx, repo); err != nil { + return fmt.Errorf("checkDaemonExportOK: %w", err) + } + + if stdout, _, err := git.NewCommand("update-server-info"). + RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}); err != nil { + log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) + return fmt.Errorf("CreateRepository(git update-server-info): %w", err) + } + return nil +} diff --git a/services/repository/create_test.go b/services/repository/create_test.go new file mode 100644 index 0000000000..9ecf404e8b --- /dev/null +++ b/services/repository/create_test.go @@ -0,0 +1,57 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repository + +import ( + "os" + "testing" + + "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" + "code.gitea.io/gitea/modules/util" + + "github.com/stretchr/testify/assert" +) + +func TestCreateRepositoryDirectly(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + // a successful creating repository + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + createdRepo, err := CreateRepositoryDirectly(git.DefaultContext, user2, user2, CreateRepoOptions{ + Name: "created-repo", + }) + assert.NoError(t, err) + assert.NotNil(t, createdRepo) + + exist, err := util.IsExist(repo_model.RepoPath(user2.Name, createdRepo.Name)) + assert.NoError(t, err) + assert.True(t, exist) + + unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: createdRepo.Name}) + + err = DeleteRepositoryDirectly(db.DefaultContext, user2, createdRepo.ID) + assert.NoError(t, err) + + // a failed creating because some mock data + // create the repository directory so that the creation will fail after database record created. + assert.NoError(t, os.MkdirAll(repo_model.RepoPath(user2.Name, createdRepo.Name), os.ModePerm)) + + createdRepo2, err := CreateRepositoryDirectly(db.DefaultContext, user2, user2, CreateRepoOptions{ + Name: "created-repo", + }) + assert.Nil(t, createdRepo2) + assert.Error(t, err) + + // assert the cleanup is successful + unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: createdRepo.Name}) + + exist, err = util.IsExist(repo_model.RepoPath(user2.Name, createdRepo.Name)) + assert.NoError(t, err) + assert.False(t, exist) +} diff --git a/services/repository/delete.go b/services/repository/delete.go index ff74a20817..cf960af8cf 100644 --- a/services/repository/delete.go +++ b/services/repository/delete.go @@ -32,6 +32,19 @@ import ( "xorm.io/builder" ) +func deleteDBRepository(ctx context.Context, repoID int64) error { + if cnt, err := db.GetEngine(ctx).ID(repoID).Delete(&repo_model.Repository{}); err != nil { + return err + } else if cnt != 1 { + return repo_model.ErrRepoNotExist{ + ID: repoID, + OwnerName: "", + Name: "", + } + } + return nil +} + // DeleteRepository deletes a repository for a user or organization. // make sure if you call this func to close open sessions (sqlite will otherwise get a deadlock) func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID int64, ignoreOrgTeams ...bool) error { @@ -82,14 +95,8 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID } needRewriteKeysFile := deleted > 0 - if cnt, err := sess.ID(repoID).Delete(&repo_model.Repository{}); err != nil { + if err := deleteDBRepository(ctx, repoID); err != nil { return err - } else if cnt != 1 { - return repo_model.ErrRepoNotExist{ - ID: repoID, - OwnerName: "", - Name: "", - } } if org != nil && org.IsOrganization() { diff --git a/services/repository/files/cherry_pick.go b/services/repository/files/cherry_pick.go index 0e069fb2ce..6818bb343d 100644 --- a/services/repository/files/cherry_pick.go +++ b/services/repository/files/cherry_pick.go @@ -5,6 +5,7 @@ package files import ( "context" + "errors" "fmt" "strings" @@ -100,7 +101,7 @@ func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_mod } if conflict { - return nil, fmt.Errorf("failed to merge due to conflicts") + return nil, errors.New("failed to merge due to conflicts") } treeHash, err := t.WriteTree(ctx) diff --git a/services/repository/files/content.go b/services/repository/files/content.go index 0ab7422ce2..0327e7f2ce 100644 --- a/services/repository/files/content.go +++ b/services/repository/files/content.go @@ -188,6 +188,14 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, treePath, ref }, } + // GitHub doesn't have these fields in the response, but we could follow other similar APIs to name them + // https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#list-commits + if lastCommit.Committer != nil { + contentsResponse.LastCommitterDate = lastCommit.Committer.When + } + if lastCommit.Author != nil { + contentsResponse.LastAuthorDate = lastCommit.Author.When + } // Now populate the rest of the ContentsResponse based on entry type if entry.IsRegular() || entry.IsExecutable() { contentsResponse.Type = string(ContentTypeRegular) @@ -269,28 +277,3 @@ func GetBlobBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git Content: content, }, nil } - -// TryGetContentLanguage tries to get the (linguist) language of the file content -func TryGetContentLanguage(gitRepo *git.Repository, commitID, treePath string) (string, error) { - indexFilename, worktree, deleteTemporaryFile, err := gitRepo.ReadTreeToTemporaryIndex(commitID) - if err != nil { - return "", err - } - - defer deleteTemporaryFile() - - filename2attribute2info, err := gitRepo.CheckAttribute(git.CheckAttributeOpts{ - CachedOnly: true, - Attributes: []string{git.AttributeLinguistLanguage, git.AttributeGitlabLanguage}, - Filenames: []string{treePath}, - IndexFile: indexFilename, - WorkTree: worktree, - }) - if err != nil { - return "", err - } - - language := git.TryReadLanguageAttribute(filename2attribute2info[treePath]) - - return language.Value(), nil -} diff --git a/services/repository/files/content_test.go b/services/repository/files/content_test.go index 7cb46c0bb6..e066f30cba 100644 --- a/services/repository/files/content_test.go +++ b/services/repository/files/content_test.go @@ -5,6 +5,7 @@ package files import ( "testing" + "time" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/gitrepo" @@ -30,18 +31,20 @@ func getExpectedReadmeContentsResponse() *api.ContentsResponse { gitURL := "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/" + sha downloadURL := "https://try.gitea.io/user2/repo1/raw/branch/master/" + treePath return &api.ContentsResponse{ - Name: treePath, - Path: treePath, - SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f", - LastCommitSHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", - Type: "file", - Size: 30, - Encoding: &encoding, - Content: &content, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: treePath, + Path: treePath, + SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f", + LastCommitSHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", + LastCommitterDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)), + LastAuthorDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)), + Type: "file", + Size: 30, + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, Links: &api.FileLinksResponse{ Self: &selfURL, GitURL: &gitURL, @@ -67,13 +70,13 @@ func TestGetContents(t *testing.T) { t.Run("Get README.md contents with GetContents(ctx, )", func(t *testing.T) { fileContentResponse, err := GetContents(ctx, ctx.Repo.Repository, treePath, ref, false) - assert.EqualValues(t, expectedContentsResponse, fileContentResponse) + assert.Equal(t, expectedContentsResponse, fileContentResponse) assert.NoError(t, err) }) t.Run("Get README.md contents with ref as empty string (should then use the repo's default branch) with GetContents(ctx, )", func(t *testing.T) { fileContentResponse, err := GetContents(ctx, ctx.Repo.Repository, treePath, "", false) - assert.EqualValues(t, expectedContentsResponse, fileContentResponse) + assert.Equal(t, expectedContentsResponse, fileContentResponse) assert.NoError(t, err) }) } diff --git a/services/repository/files/diff_test.go b/services/repository/files/diff_test.go index a8514791cc..ae702e4189 100644 --- a/services/repository/files/diff_test.go +++ b/services/repository/files/diff_test.go @@ -118,7 +118,7 @@ func TestGetDiffPreview(t *testing.T) { assert.NoError(t, err) bs, err := json.Marshal(diff) assert.NoError(t, err) - assert.EqualValues(t, string(expectedBs), string(bs)) + assert.Equal(t, string(expectedBs), string(bs)) }) t.Run("empty branch, same results", func(t *testing.T) { @@ -128,7 +128,7 @@ func TestGetDiffPreview(t *testing.T) { assert.NoError(t, err) bs, err := json.Marshal(diff) assert.NoError(t, err) - assert.EqualValues(t, expectedBs, bs) + assert.Equal(t, expectedBs, bs) }) } diff --git a/services/repository/files/file.go b/services/repository/files/file.go index 2caa1b4946..a8ad5889cb 100644 --- a/services/repository/files/file.go +++ b/services/repository/files/file.go @@ -5,6 +5,7 @@ package files import ( "context" + "errors" "fmt" "net/url" "strings" @@ -62,10 +63,10 @@ func GetFileResponseFromFilesResponse(filesResponse *api.FilesResponse, index in // GetFileCommitResponse Constructs a FileCommitResponse from a Commit object func GetFileCommitResponse(repo *repo_model.Repository, commit *git.Commit) (*api.FileCommitResponse, error) { if repo == nil { - return nil, fmt.Errorf("repo cannot be nil") + return nil, errors.New("repo cannot be nil") } if commit == nil { - return nil, fmt.Errorf("commit cannot be nil") + return nil, errors.New("commit cannot be nil") } commitURL, _ := url.Parse(repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String())) commitTreeURL, _ := url.Parse(repo.APIURL() + "/git/trees/" + url.PathEscape(commit.Tree.ID.String())) diff --git a/services/repository/files/file_test.go b/services/repository/files/file_test.go index 52c0574883..7cb79d7ebf 100644 --- a/services/repository/files/file_test.go +++ b/services/repository/files/file_test.go @@ -5,6 +5,7 @@ package files import ( "testing" + "time" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/gitrepo" @@ -20,14 +21,14 @@ func TestCleanUploadFileName(t *testing.T) { name := "this/is/test" cleanName := CleanUploadFileName(name) expectedCleanName := name - assert.EqualValues(t, expectedCleanName, cleanName) + assert.Equal(t, expectedCleanName, cleanName) }) t.Run("Clean a .git path", func(t *testing.T) { name := "this/is/test/.git" cleanName := CleanUploadFileName(name) expectedCleanName := "" - assert.EqualValues(t, expectedCleanName, cleanName) + assert.Equal(t, expectedCleanName, cleanName) }) } @@ -42,18 +43,20 @@ func getExpectedFileResponse() *api.FileResponse { downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath return &api.FileResponse{ Content: &api.ContentsResponse{ - Name: treePath, - Path: treePath, - SHA: sha, - LastCommitSHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", - Type: "file", - Size: 30, - Encoding: &encoding, - Content: &content, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: treePath, + Path: treePath, + SHA: sha, + LastCommitSHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", + LastCommitterDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)), + LastAuthorDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)), + Type: "file", + Size: 30, + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, Links: &api.FileLinksResponse{ Self: &selfURL, GitURL: &gitURL, @@ -116,5 +119,5 @@ func TestGetFileResponseFromCommit(t *testing.T) { fileResponse, err := GetFileResponseFromCommit(ctx, repo, commit, branch, treePath) assert.NoError(t, err) - assert.EqualValues(t, expectedFileResponse, fileResponse) + assert.Equal(t, expectedFileResponse, fileResponse) } diff --git a/services/repository/files/patch.go b/services/repository/files/patch.go index 1941adb86a..5fbf748206 100644 --- a/services/repository/files/patch.go +++ b/services/repository/files/patch.go @@ -12,7 +12,6 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -62,29 +61,26 @@ func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_mode opts.NewBranch = opts.OldBranch } - gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo) - if err != nil { - return err - } - defer closer.Close() - // oldBranch must exist for this operation - if _, err := gitRepo.GetBranch(opts.OldBranch); err != nil { + if exist, err := git_model.IsBranchExist(ctx, repo.ID, opts.OldBranch); err != nil { return err + } else if !exist { + return git_model.ErrBranchNotExist{ + BranchName: opts.OldBranch, + } } // A NewBranch can be specified for the patch to be applied to. // Check to make sure the branch does not already exist, otherwise we can't proceed. // If we aren't branching to a new branch, make sure user can commit to the given branch if opts.NewBranch != opts.OldBranch { - existingBranch, err := gitRepo.GetBranch(opts.NewBranch) - if existingBranch != nil { + exist, err := git_model.IsBranchExist(ctx, repo.ID, opts.NewBranch) + if err != nil { + return err + } else if exist { return git_model.ErrBranchAlreadyExists{ BranchName: opts.NewBranch, } } - if err != nil && !git.IsErrBranchNotExist(err) { - return err - } } else { protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, opts.OldBranch) if err != nil { diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go index d2c70a7a34..493ff9998d 100644 --- a/services/repository/files/temp_repo.go +++ b/services/repository/files/temp_repo.go @@ -6,6 +6,7 @@ package files import ( "bytes" "context" + "errors" "fmt" "io" "os" @@ -29,23 +30,24 @@ type TemporaryUploadRepository struct { repo *repo_model.Repository gitRepo *git.Repository basePath string + cleanup func() } // NewTemporaryUploadRepository creates a new temporary upload repository func NewTemporaryUploadRepository(repo *repo_model.Repository) (*TemporaryUploadRepository, error) { - basePath, err := repo_module.CreateTemporaryPath("upload") + basePath, cleanup, err := repo_module.CreateTemporaryPath("upload") if err != nil { return nil, err } - t := &TemporaryUploadRepository{repo: repo, basePath: basePath} + t := &TemporaryUploadRepository{repo: repo, basePath: basePath, cleanup: cleanup} return t, nil } // Close the repository cleaning up all files func (t *TemporaryUploadRepository) Close() { defer t.gitRepo.Close() - if err := repo_module.RemoveTemporaryPath(t.basePath); err != nil { - log.Error("Failed to remove temporary path %s: %v", t.basePath, err) + if t.cleanup != nil { + t.cleanup() } } @@ -414,7 +416,7 @@ func (t *TemporaryUploadRepository) DiffIndex(ctx context.Context) (*gitdiff.Dif // GetBranchCommit Gets the commit object of the given branch func (t *TemporaryUploadRepository) GetBranchCommit(branch string) (*git.Commit, error) { if t.gitRepo == nil { - return nil, fmt.Errorf("repository has not been cloned") + return nil, errors.New("repository has not been cloned") } return t.gitRepo.GetBranchCommit(branch) } @@ -422,7 +424,7 @@ func (t *TemporaryUploadRepository) GetBranchCommit(branch string) (*git.Commit, // GetCommit Gets the commit object of the given commit ID func (t *TemporaryUploadRepository) GetCommit(commitID string) (*git.Commit, error) { if t.gitRepo == nil { - return nil, fmt.Errorf("repository has not been cloned") + return nil, errors.New("repository has not been cloned") } return t.gitRepo.GetCommit(commitID) } diff --git a/services/repository/files/tree.go b/services/repository/files/tree.go index 9142416347..faeb85a046 100644 --- a/services/repository/files/tree.go +++ b/services/repository/files/tree.go @@ -6,12 +6,14 @@ package files import ( "context" "fmt" + "html/template" "net/url" "path" "sort" "strings" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/fileicon" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -140,8 +142,13 @@ func entryModeString(entryMode git.EntryMode) string { } type TreeViewNode struct { - EntryName string `json:"entryName"` - EntryMode string `json:"entryMode"` + EntryName string `json:"entryName"` + EntryMode string `json:"entryMode"` + EntryIcon template.HTML `json:"entryIcon"` + EntryIconOpen template.HTML `json:"entryIconOpen,omitempty"` + + SymLinkedToMode string `json:"symLinkedToMode,omitempty"` // TODO: for the EntryMode="symlink" + FullPath string `json:"fullPath"` SubmoduleURL string `json:"submoduleUrl,omitempty"` Children []*TreeViewNode `json:"children,omitempty"` @@ -151,13 +158,28 @@ func (node *TreeViewNode) sortLevel() int { return util.Iif(node.EntryMode == "tree" || node.EntryMode == "commit", 0, 1) } -func newTreeViewNodeFromEntry(ctx context.Context, commit *git.Commit, parentDir string, entry *git.TreeEntry) *TreeViewNode { +func newTreeViewNodeFromEntry(ctx context.Context, renderedIconPool *fileicon.RenderedIconPool, commit *git.Commit, parentDir string, entry *git.TreeEntry) *TreeViewNode { node := &TreeViewNode{ EntryName: entry.Name(), EntryMode: entryModeString(entry.Mode()), FullPath: path.Join(parentDir, entry.Name()), } + if entry.IsLink() { + // TODO: symlink to a folder or a file, the icon differs + target, err := entry.FollowLink() + if err == nil { + _ = target.IsDir() + // if target.IsDir() { } else { } + } + } + + if node.EntryIcon == "" { + node.EntryIcon = fileicon.RenderEntryIcon(renderedIconPool, entry) + // TODO: no open icon support yet + // node.EntryIconOpen = fileicon.RenderEntryIconOpen(renderedIconPool, entry) + } + if node.EntryMode == "commit" { if subModule, err := commit.GetSubModule(node.FullPath); err != nil { log.Error("GetSubModule: %v", err) @@ -182,7 +204,7 @@ func sortTreeViewNodes(nodes []*TreeViewNode) { }) } -func listTreeNodes(ctx context.Context, commit *git.Commit, tree *git.Tree, treePath, subPath string) ([]*TreeViewNode, error) { +func listTreeNodes(ctx context.Context, renderedIconPool *fileicon.RenderedIconPool, commit *git.Commit, tree *git.Tree, treePath, subPath string) ([]*TreeViewNode, error) { entries, err := tree.ListEntries() if err != nil { return nil, err @@ -191,14 +213,14 @@ func listTreeNodes(ctx context.Context, commit *git.Commit, tree *git.Tree, tree subPathDirName, subPathRemaining, _ := strings.Cut(subPath, "/") nodes := make([]*TreeViewNode, 0, len(entries)) for _, entry := range entries { - node := newTreeViewNodeFromEntry(ctx, commit, treePath, entry) + node := newTreeViewNodeFromEntry(ctx, renderedIconPool, commit, treePath, entry) nodes = append(nodes, node) if entry.IsDir() && subPathDirName == entry.Name() { subTreePath := treePath + "/" + node.EntryName if subTreePath[0] == '/' { subTreePath = subTreePath[1:] } - subNodes, err := listTreeNodes(ctx, commit, entry.Tree(), subTreePath, subPathRemaining) + subNodes, err := listTreeNodes(ctx, renderedIconPool, commit, entry.Tree(), subTreePath, subPathRemaining) if err != nil { log.Error("listTreeNodes: %v", err) } else { @@ -210,10 +232,10 @@ func listTreeNodes(ctx context.Context, commit *git.Commit, tree *git.Tree, tree return nodes, nil } -func GetTreeViewNodes(ctx context.Context, commit *git.Commit, treePath, subPath string) ([]*TreeViewNode, error) { +func GetTreeViewNodes(ctx context.Context, renderedIconPool *fileicon.RenderedIconPool, commit *git.Commit, treePath, subPath string) ([]*TreeViewNode, error) { entry, err := commit.GetTreeEntryByPath(treePath) if err != nil { return nil, err } - return listTreeNodes(ctx, commit, entry.Tree(), treePath, subPath) + return listTreeNodes(ctx, renderedIconPool, commit, entry.Tree(), treePath, subPath) } diff --git a/services/repository/files/tree_test.go b/services/repository/files/tree_test.go index 8ea54969ce..2657c49977 100644 --- a/services/repository/files/tree_test.go +++ b/services/repository/files/tree_test.go @@ -4,9 +4,11 @@ package files import ( + "html/template" "testing" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/fileicon" "code.gitea.io/gitea/modules/git" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/services/contexttest" @@ -49,7 +51,7 @@ func TestGetTreeBySHA(t *testing.T) { TotalCount: 1, } - assert.EqualValues(t, expectedTree, tree) + assert.Equal(t, expectedTree, tree) } func TestGetTreeViewNodes(t *testing.T) { @@ -62,40 +64,51 @@ func TestGetTreeViewNodes(t *testing.T) { contexttest.LoadGitRepo(t, ctx) defer ctx.Repo.GitRepo.Close() - treeNodes, err := GetTreeViewNodes(ctx, ctx.Repo.Commit, "", "") + renderedIconPool := fileicon.NewRenderedIconPool() + mockIconForFile := func(id string) template.HTML { + return template.HTML(``) + } + mockIconForFolder := func(id string) template.HTML { + return template.HTML(``) + } + treeNodes, err := GetTreeViewNodes(ctx, renderedIconPool, ctx.Repo.Commit, "", "") assert.NoError(t, err) assert.Equal(t, []*TreeViewNode{ { EntryName: "docs", EntryMode: "tree", FullPath: "docs", + EntryIcon: mockIconForFolder(`svg-mfi-folder-docs`), }, }, treeNodes) - treeNodes, err = GetTreeViewNodes(ctx, ctx.Repo.Commit, "", "docs/README.md") + treeNodes, err = GetTreeViewNodes(ctx, renderedIconPool, ctx.Repo.Commit, "", "docs/README.md") assert.NoError(t, err) assert.Equal(t, []*TreeViewNode{ { EntryName: "docs", EntryMode: "tree", FullPath: "docs", + EntryIcon: mockIconForFolder(`svg-mfi-folder-docs`), Children: []*TreeViewNode{ { EntryName: "README.md", EntryMode: "blob", FullPath: "docs/README.md", + EntryIcon: mockIconForFile(`svg-mfi-readme`), }, }, }, }, treeNodes) - treeNodes, err = GetTreeViewNodes(ctx, ctx.Repo.Commit, "docs", "README.md") + treeNodes, err = GetTreeViewNodes(ctx, renderedIconPool, ctx.Repo.Commit, "docs", "README.md") assert.NoError(t, err) assert.Equal(t, []*TreeViewNode{ { EntryName: "README.md", EntryMode: "blob", FullPath: "docs/README.md", + EntryIcon: mockIconForFile(`svg-mfi-readme`), }, }, treeNodes) } diff --git a/services/repository/files/update.go b/services/repository/files/update.go index cade7ba2bf..75ede4976f 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -15,6 +15,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/attribute" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" @@ -107,8 +108,13 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use defer closer.Close() // oldBranch must exist for this operation - if _, err := gitRepo.GetBranch(opts.OldBranch); err != nil && !repo.IsEmpty { + if exist, err := git_model.IsBranchExist(ctx, repo.ID, opts.OldBranch); err != nil { return nil, err + } else if !exist && !repo.IsEmpty { + return nil, git_model.ErrBranchNotExist{ + RepoID: repo.ID, + BranchName: opts.OldBranch, + } } var treePaths []string @@ -145,15 +151,15 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use // Check to make sure the branch does not already exist, otherwise we can't proceed. // If we aren't branching to a new branch, make sure user can commit to the given branch if opts.NewBranch != opts.OldBranch { - existingBranch, err := gitRepo.GetBranch(opts.NewBranch) - if existingBranch != nil { + exist, err := git_model.IsBranchExist(ctx, repo.ID, opts.NewBranch) + if err != nil { + return nil, err + } + if exist { return nil, git_model.ErrBranchAlreadyExists{ BranchName: opts.NewBranch, } } - if err != nil && !git.IsErrBranchNotExist(err) { - return nil, err - } } else if err := VerifyBranchProtection(ctx, repo, doer, opts.OldBranch, treePaths); err != nil { return nil, err } @@ -483,16 +489,15 @@ func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file var lfsMetaObject *git_model.LFSMetaObject if setting.LFS.StartServer && hasOldBranch { // Check there is no way this can return multiple infos - filename2attribute2info, err := t.gitRepo.CheckAttribute(git.CheckAttributeOpts{ - Attributes: []string{"filter"}, + attributesMap, err := attribute.CheckAttributes(ctx, t.gitRepo, "" /* use temp repo's working dir */, attribute.CheckAttributeOpts{ + Attributes: []string{attribute.Filter}, Filenames: []string{file.Options.treePath}, - CachedOnly: true, }) if err != nil { return err } - if filename2attribute2info[file.Options.treePath] != nil && filename2attribute2info[file.Options.treePath]["filter"] == "lfs" { + if attributesMap[file.Options.treePath] != nil && attributesMap[file.Options.treePath].Get(attribute.Filter).ToString().Value() == "lfs" { // OK so we are supposed to LFS this data! pointer, err := lfs.GeneratePointer(treeObjectContentReader) if err != nil { diff --git a/services/repository/files/upload.go b/services/repository/files/upload.go index 2e4ed1744e..f348cb68ab 100644 --- a/services/repository/files/upload.go +++ b/services/repository/files/upload.go @@ -14,6 +14,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/attribute" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/setting" ) @@ -105,12 +106,11 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use } } - var filename2attribute2info map[string]map[string]string + var attributesMap map[string]*attribute.Attributes if setting.LFS.StartServer { - filename2attribute2info, err = t.gitRepo.CheckAttribute(git.CheckAttributeOpts{ - Attributes: []string{"filter"}, + attributesMap, err = attribute.CheckAttributes(ctx, t.gitRepo, "" /* use temp repo's working dir */, attribute.CheckAttributeOpts{ + Attributes: []string{attribute.Filter}, Filenames: names, - CachedOnly: true, }) if err != nil { return err @@ -119,7 +119,7 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use // Copy uploaded files into repository. for i := range infos { - if err := copyUploadedLFSFileIntoRepository(ctx, &infos[i], filename2attribute2info, t, opts.TreePath); err != nil { + if err := copyUploadedLFSFileIntoRepository(ctx, &infos[i], attributesMap, t, opts.TreePath); err != nil { return err } } @@ -176,7 +176,7 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use return repo_model.DeleteUploads(ctx, uploads...) } -func copyUploadedLFSFileIntoRepository(ctx context.Context, info *uploadInfo, filename2attribute2info map[string]map[string]string, t *TemporaryUploadRepository, treePath string) error { +func copyUploadedLFSFileIntoRepository(ctx context.Context, info *uploadInfo, attributesMap map[string]*attribute.Attributes, t *TemporaryUploadRepository, treePath string) error { file, err := os.Open(info.upload.LocalPath()) if err != nil { return err @@ -184,7 +184,7 @@ func copyUploadedLFSFileIntoRepository(ctx context.Context, info *uploadInfo, fi defer file.Close() var objectHash string - if setting.LFS.StartServer && filename2attribute2info[info.upload.Name] != nil && filename2attribute2info[info.upload.Name]["filter"] == "lfs" { + if setting.LFS.StartServer && attributesMap[info.upload.Name] != nil && attributesMap[info.upload.Name].Get(attribute.Filter).ToString().Value() == "lfs" { // Handle LFS // FIXME: Inefficient! this should probably happen in models.Upload pointer, err := lfs.GeneratePointer(file) diff --git a/services/repository/fork.go b/services/repository/fork.go index 5b1ba7a418..c16c3d598a 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -65,7 +65,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork } // Fork is prohibited, if user has reached maximum limit of repositories - if !owner.CanForkRepo() { + if !doer.CanForkRepoIn(owner) { return nil, repo_model.ErrReachLimitOfRepo{ Limit: owner.MaxRepoCreation, } @@ -100,114 +100,106 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork IsFork: true, ForkID: opts.BaseRepo.ID, ObjectFormatName: opts.BaseRepo.ObjectFormatName, + Status: repo_model.RepositoryBeingMigrated, } - oldRepoPath := opts.BaseRepo.RepoPath() - - needsRollback := false - rollbackFn := func() { - if !needsRollback { - return - } - - if exists, _ := gitrepo.IsRepositoryExist(ctx, repo); !exists { - return - } - - // As the transaction will be failed and hence database changes will be destroyed we only need - // to delete the related repository on the filesystem - if errDelete := gitrepo.DeleteRepository(ctx, repo); errDelete != nil { - log.Error("Failed to remove fork repo") - } - } - - needsRollbackInPanic := true - defer func() { - panicErr := recover() - if panicErr == nil { - return - } - - if needsRollbackInPanic { - rollbackFn() - } - panic(panicErr) - }() - - err = db.WithTx(ctx, func(txCtx context.Context) error { - if err = CreateRepositoryByExample(txCtx, doer, owner, repo, false, true); err != nil { + // 1 - Create the repository in the database + err = db.WithTx(ctx, func(ctx context.Context) error { + if err = createRepositoryInDB(ctx, doer, owner, repo, true); err != nil { return err } - - if err = repo_model.IncrementRepoForkNum(txCtx, opts.BaseRepo.ID); err != nil { + if err = repo_model.IncrementRepoForkNum(ctx, opts.BaseRepo.ID); err != nil { return err } // copy lfs files failure should not be ignored - if err = git_model.CopyLFS(txCtx, repo, opts.BaseRepo); err != nil { - return err - } - - needsRollback = true - - cloneCmd := git.NewCommand("clone", "--bare") - if opts.SingleBranch != "" { - cloneCmd.AddArguments("--single-branch", "--branch").AddDynamicArguments(opts.SingleBranch) - } - if stdout, _, err := cloneCmd.AddDynamicArguments(oldRepoPath, repo.RepoPath()). - RunStdBytes(txCtx, &git.RunOpts{Timeout: 10 * time.Minute}); err != nil { - log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, opts.BaseRepo, stdout, err) - return fmt.Errorf("git clone: %w", err) - } - - if err := repo_module.CheckDaemonExportOK(txCtx, repo); err != nil { - return fmt.Errorf("checkDaemonExportOK: %w", err) - } - - if stdout, _, err := git.NewCommand("update-server-info"). - RunStdString(txCtx, &git.RunOpts{Dir: repo.RepoPath()}); err != nil { - log.Error("Fork Repository (git update-server-info) failed for %v:\nStdout: %s\nError: %v", repo, stdout, err) - return fmt.Errorf("git update-server-info: %w", err) - } - - if err = gitrepo.CreateDelegateHooks(ctx, repo); err != nil { - return fmt.Errorf("createDelegateHooks: %w", err) - } - - gitRepo, err := gitrepo.OpenRepository(txCtx, repo) - if err != nil { - return fmt.Errorf("OpenRepository: %w", err) - } - defer gitRepo.Close() - - _, err = repo_module.SyncRepoBranchesWithRepo(txCtx, repo, gitRepo, doer.ID) - return err + return git_model.CopyLFS(ctx, repo, opts.BaseRepo) }) - needsRollbackInPanic = false if err != nil { - rollbackFn() return nil, err } - // even if below operations failed, it could be ignored. And they will be retried - if err := repo_module.UpdateRepoSize(ctx, repo); err != nil { - log.Error("Failed to update size for repository: %v", err) - } - if err := repo_model.CopyLanguageStat(ctx, opts.BaseRepo, repo); err != nil { - log.Error("Copy language stat from oldRepo failed: %v", err) - } - if err := repo_model.CopyLicense(ctx, opts.BaseRepo, repo); err != nil { - return nil, err - } - - gitRepo, err := gitrepo.OpenRepository(ctx, repo) - if err != nil { - log.Error("Open created git repository failed: %v", err) - } else { - defer gitRepo.Close() - if err := repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { - log.Error("Sync releases from git tags failed: %v", err) + // last - clean up if something goes wrong + // WARNING: Don't override all later err with local variables + defer func() { + if err != nil { + // we can not use the ctx because it maybe canceled or timeout + cleanupRepository(doer, repo.ID) } + }() + + // 2 - check whether the repository with the same storage exists + var isExist bool + isExist, err = gitrepo.IsRepositoryExist(ctx, repo) + if err != nil { + log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err) + return nil, err + } + if isExist { + log.Error("Files already exist in %s and we are not going to adopt or delete.", repo.FullName()) + // Don't return directly, we need err in defer to cleanupRepository + err = repo_model.ErrRepoFilesAlreadyExist{ + Uname: repo.OwnerName, + Name: repo.Name, + } + return nil, err + } + + // 3 - Clone the repository + cloneCmd := git.NewCommand("clone", "--bare") + if opts.SingleBranch != "" { + cloneCmd.AddArguments("--single-branch", "--branch").AddDynamicArguments(opts.SingleBranch) + } + var stdout []byte + if stdout, _, err = cloneCmd.AddDynamicArguments(opts.BaseRepo.RepoPath(), repo.RepoPath()). + RunStdBytes(ctx, &git.RunOpts{Timeout: 10 * time.Minute}); err != nil { + log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, opts.BaseRepo, stdout, err) + return nil, fmt.Errorf("git clone: %w", err) + } + + // 4 - Update the git repository + if err = updateGitRepoAfterCreate(ctx, repo); err != nil { + return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err) + } + + // 5 - Create hooks + if err = gitrepo.CreateDelegateHooks(ctx, repo); err != nil { + return nil, fmt.Errorf("createDelegateHooks: %w", err) + } + + // 6 - Sync the repository branches and tags + var gitRepo *git.Repository + gitRepo, err = gitrepo.OpenRepository(ctx, repo) + if err != nil { + return nil, fmt.Errorf("OpenRepository: %w", err) + } + defer gitRepo.Close() + + if _, err = repo_module.SyncRepoBranchesWithRepo(ctx, repo, gitRepo, doer.ID); err != nil { + return nil, fmt.Errorf("SyncRepoBranchesWithRepo: %w", err) + } + if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { + return nil, fmt.Errorf("Sync releases from git tags failed: %v", err) + } + + // 7 - Update the repository + // even if below operations failed, it could be ignored. And they will be retried + if err = repo_module.UpdateRepoSize(ctx, repo); err != nil { + log.Error("Failed to update size for repository: %v", err) + err = nil + } + if err = repo_model.CopyLanguageStat(ctx, opts.BaseRepo, repo); err != nil { + log.Error("Copy language stat from oldRepo failed: %v", err) + err = nil + } + if err = repo_model.CopyLicense(ctx, opts.BaseRepo, repo); err != nil { + return nil, err + } + + // 8 - update repository status to be ready + repo.Status = repo_model.RepositoryReady + if err = repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil { + return nil, fmt.Errorf("UpdateRepositoryCols: %w", err) } notify_service.ForkRepository(ctx, doer, opts.BaseRepo, repo) @@ -235,7 +227,7 @@ func ConvertForkToNormalRepository(ctx context.Context, repo *repo_model.Reposit repo.IsFork = false repo.ForkID = 0 - if err := repo_module.UpdateRepository(ctx, repo, false); err != nil { + if err := updateRepository(ctx, repo, false); err != nil { log.Error("Unable to update repository %-v whilst converting from fork. Error: %v", repo, err) return err } diff --git a/services/repository/fork_test.go b/services/repository/fork_test.go index 452798b25b..9edc0aa39f 100644 --- a/services/repository/fork_test.go +++ b/services/repository/fork_test.go @@ -4,13 +4,16 @@ package repository import ( + "os" "testing" + "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" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) @@ -46,3 +49,43 @@ func TestForkRepository(t *testing.T) { assert.Nil(t, fork2) assert.True(t, repo_model.IsErrReachLimitOfRepo(err)) } + +func TestForkRepositoryCleanup(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + // a successful fork + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) + + fork, err := ForkRepository(git.DefaultContext, user2, user2, ForkRepoOptions{ + BaseRepo: repo10, + Name: "test", + }) + assert.NoError(t, err) + assert.NotNil(t, fork) + + exist, err := util.IsExist(repo_model.RepoPath(user2.Name, "test")) + assert.NoError(t, err) + assert.True(t, exist) + + err = DeleteRepositoryDirectly(db.DefaultContext, user2, fork.ID) + assert.NoError(t, err) + + // a failed creating because some mock data + // create the repository directory so that the creation will fail after database record created. + assert.NoError(t, os.MkdirAll(repo_model.RepoPath(user2.Name, "test"), os.ModePerm)) + + fork2, err := ForkRepository(db.DefaultContext, user2, user2, ForkRepoOptions{ + BaseRepo: repo10, + Name: "test", + }) + assert.Nil(t, fork2) + assert.Error(t, err) + + // assert the cleanup is successful + unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: "test"}) + + exist, err = util.IsExist(repo_model.RepoPath(user2.Name, "test")) + assert.NoError(t, err) + assert.False(t, exist) +} diff --git a/services/repository/generate.go b/services/repository/generate.go index 9d2bbb1f7f..b02f7c9482 100644 --- a/services/repository/generate.go +++ b/services/repository/generate.go @@ -17,11 +17,11 @@ import ( git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" - user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "github.com/gobwas/glob" @@ -256,16 +256,11 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r } func generateGitContent(ctx context.Context, repo, templateRepo, generateRepo *repo_model.Repository) (err error) { - tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-"+repo.Name) + tmpDir, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("gitea-" + repo.Name) if err != nil { - return fmt.Errorf("Failed to create temp dir for repository %s: %w", repo.FullName(), err) + return fmt.Errorf("failed to create temp dir for repository %s: %w", repo.FullName(), err) } - - defer func() { - if err := util.RemoveAll(tmpDir); err != nil { - log.Error("RemoveAll: %v", err) - } - }() + defer cleanup() if err = generateRepoCommit(ctx, repo, templateRepo, generateRepo, tmpDir); err != nil { return fmt.Errorf("generateRepoCommit: %w", err) @@ -328,57 +323,6 @@ func (gro GenerateRepoOptions) IsValid() bool { gro.IssueLabels || gro.ProtectedBranch // or other items as they are added } -// generateRepository generates a repository from a template -func generateRepository(ctx context.Context, doer, owner *user_model.User, templateRepo *repo_model.Repository, opts GenerateRepoOptions) (_ *repo_model.Repository, err error) { - generateRepo := &repo_model.Repository{ - OwnerID: owner.ID, - Owner: owner, - OwnerName: owner.Name, - Name: opts.Name, - LowerName: strings.ToLower(opts.Name), - Description: opts.Description, - DefaultBranch: opts.DefaultBranch, - IsPrivate: opts.Private, - IsEmpty: !opts.GitContent || templateRepo.IsEmpty, - IsFsckEnabled: templateRepo.IsFsckEnabled, - TemplateID: templateRepo.ID, - TrustModel: templateRepo.TrustModel, - ObjectFormatName: templateRepo.ObjectFormatName, - } - - if err = CreateRepositoryByExample(ctx, doer, owner, generateRepo, false, false); err != nil { - return nil, err - } - - isExist, err := gitrepo.IsRepositoryExist(ctx, generateRepo) - if err != nil { - log.Error("Unable to check if %s exists. Error: %v", generateRepo.FullName(), err) - return nil, err - } - if isExist { - return nil, repo_model.ErrRepoFilesAlreadyExist{ - Uname: generateRepo.OwnerName, - Name: generateRepo.Name, - } - } - - if err = repo_module.CheckInitRepository(ctx, generateRepo); err != nil { - return generateRepo, err - } - - if err = repo_module.CheckDaemonExportOK(ctx, generateRepo); err != nil { - return generateRepo, fmt.Errorf("checkDaemonExportOK: %w", err) - } - - if stdout, _, err := git.NewCommand("update-server-info"). - RunStdString(ctx, &git.RunOpts{Dir: generateRepo.RepoPath()}); err != nil { - log.Error("GenerateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", generateRepo, stdout, err) - return generateRepo, fmt.Errorf("error in GenerateRepository(git update-server-info): %w", err) - } - - return generateRepo, nil -} - var fileNameSanitizeRegexp = regexp.MustCompile(`(?i)\.\.|[<>:\"/\\|?*\x{0000}-\x{001F}]|^(con|prn|aux|nul|com\d|lpt\d)$`) // Sanitize user input to valid OS filenames diff --git a/services/repository/license_test.go b/services/repository/license_test.go index 9e74a268f5..eb897f3c03 100644 --- a/services/repository/license_test.go +++ b/services/repository/license_test.go @@ -4,7 +4,6 @@ package repository import ( - "fmt" "strings" "testing" @@ -45,7 +44,7 @@ func Test_detectLicense(t *testing.T) { assert.NoError(t, err) tests = append(tests, DetectLicenseTest{ - name: fmt.Sprintf("single license test: %s", licenseName), + name: "single license test: " + licenseName, arg: string(license), want: []string{licenseName}, }) diff --git a/services/repository/migrate.go b/services/repository/migrate.go index 9a5c6ffb0f..003be1a9ab 100644 --- a/services/repository/migrate.go +++ b/services/repository/migrate.go @@ -118,14 +118,8 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, repo.Owner = u } - if err := repo_module.CheckDaemonExportOK(ctx, repo); err != nil { - return repo, fmt.Errorf("checkDaemonExportOK: %w", err) - } - - if stdout, _, err := git.NewCommand("update-server-info"). - RunStdString(ctx, &git.RunOpts{Dir: repoPath}); err != nil { - log.Error("MigrateRepositoryGitData(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) - return repo, fmt.Errorf("error in MigrateRepositoryGitData(git update-server-info): %w", err) + if err := updateGitRepoAfterCreate(ctx, repo); err != nil { + return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err) } gitRepo, err := git.OpenRepository(ctx, repoPath) @@ -142,12 +136,12 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, if !repo.IsEmpty { if len(repo.DefaultBranch) == 0 { // Try to get HEAD branch and set it as default branch. - headBranch, err := gitRepo.GetHEADBranch() + headBranchName, err := git.GetDefaultBranch(ctx, repoPath) if err != nil { return repo, fmt.Errorf("GetHEADBranch: %w", err) } - if headBranch != nil { - repo.DefaultBranch = headBranch.Name + if headBranchName != "" { + repo.DefaultBranch = headBranchName } } diff --git a/services/repository/push.go b/services/repository/push.go index 6d3b9dd252..ba801ad019 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -66,7 +66,7 @@ func PushUpdates(opts []*repo_module.PushUpdateOptions) error { for _, opt := range opts { if opt.IsNewRef() && opt.IsDelRef() { - return fmt.Errorf("Old and new revisions are both NULL") + return errors.New("Old and new revisions are both NULL") } } @@ -385,7 +385,7 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo if err != nil { return fmt.Errorf("GetTag: %w", err) } - commit, err := tag.Commit(gitRepo) + commit, err := gitRepo.GetTagCommit(tag.Name) if err != nil { return fmt.Errorf("Commit: %w", err) } diff --git a/services/repository/repository.go b/services/repository/repository.go index fcc617979e..90aad4f9d8 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -5,22 +5,29 @@ package repository import ( "context" + "errors" "fmt" + "os" + "path/filepath" + "strings" + activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" + access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" - system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/graceful" + issue_indexer "code.gitea.io/gitea/modules/indexer/issues" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/queue" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" notify_service "code.gitea.io/gitea/services/notify" pull_service "code.gitea.io/gitea/services/pull" ) @@ -72,10 +79,10 @@ func PushCreateRepo(ctx context.Context, authUser, owner *user_model.User, repoN if ok, err := organization.CanCreateOrgRepo(ctx, owner.ID, authUser.ID); err != nil { return nil, err } else if !ok { - return nil, fmt.Errorf("cannot push-create repository for org") + return nil, errors.New("cannot push-create repository for org") } } else if authUser.ID != owner.ID { - return nil, fmt.Errorf("cannot push-create repository for another user") + return nil, errors.New("cannot push-create repository for another user") } } @@ -94,15 +101,13 @@ func PushCreateRepo(ctx context.Context, authUser, owner *user_model.User, repoN func Init(ctx context.Context) error { licenseUpdaterQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "repo_license_updater", repoLicenseUpdater) if licenseUpdaterQueue == nil { - return fmt.Errorf("unable to create repo_license_updater queue") + return errors.New("unable to create repo_license_updater queue") } go graceful.GetManager().RunWithCancel(licenseUpdaterQueue) if err := repo_module.LoadRepoConfig(); err != nil { return err } - system_model.RemoveAllWithNotice(ctx, "Clean up temporary repository uploads", setting.Repository.Upload.TempPath) - system_model.RemoveAllWithNotice(ctx, "Clean up temporary repositories", repo_module.LocalCopyPath()) if err := initPushQueue(); err != nil { return err } @@ -111,42 +116,32 @@ func Init(ctx context.Context) error { // UpdateRepository updates a repository func UpdateRepository(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err = repo_module.UpdateRepository(ctx, repo, visibilityChanged); err != nil { - return fmt.Errorf("updateRepository: %w", err) - } - - return committer.Commit() -} - -func UpdateRepositoryVisibility(ctx context.Context, repo *repo_model.Repository, isPrivate bool) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - - defer committer.Close() - - repo.IsPrivate = isPrivate - - if err = repo_module.UpdateRepository(ctx, repo, true); err != nil { - return fmt.Errorf("UpdateRepositoryVisibility: %w", err) - } - - return committer.Commit() + return db.WithTx(ctx, func(ctx context.Context) error { + if err = updateRepository(ctx, repo, visibilityChanged); err != nil { + return fmt.Errorf("updateRepository: %w", err) + } + return nil + }) } func MakeRepoPublic(ctx context.Context, repo *repo_model.Repository) (err error) { - return UpdateRepositoryVisibility(ctx, repo, false) + return db.WithTx(ctx, func(ctx context.Context) error { + repo.IsPrivate = false + if err = updateRepository(ctx, repo, true); err != nil { + return fmt.Errorf("MakeRepoPublic: %w", err) + } + return nil + }) } func MakeRepoPrivate(ctx context.Context, repo *repo_model.Repository) (err error) { - return UpdateRepositoryVisibility(ctx, repo, true) + return db.WithTx(ctx, func(ctx context.Context) error { + repo.IsPrivate = true + if err = updateRepository(ctx, repo, true); err != nil { + return fmt.Errorf("MakeRepoPrivate: %w", err) + } + return nil + }) } // LinkedRepository returns the linked repo if any @@ -172,3 +167,97 @@ func LinkedRepository(ctx context.Context, a *repo_model.Attachment) (*repo_mode } return nil, -1, nil } + +// checkDaemonExportOK creates/removes git-daemon-export-ok for git-daemon... +func checkDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error { + if err := repo.LoadOwner(ctx); err != nil { + return err + } + + // Create/Remove git-daemon-export-ok for git-daemon... + daemonExportFile := filepath.Join(repo.RepoPath(), `git-daemon-export-ok`) + + isExist, err := util.IsExist(daemonExportFile) + if err != nil { + log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err) + return err + } + + isPublic := !repo.IsPrivate && repo.Owner.Visibility == structs.VisibleTypePublic + if !isPublic && isExist { + if err = util.Remove(daemonExportFile); err != nil { + log.Error("Failed to remove %s: %v", daemonExportFile, err) + } + } else if isPublic && !isExist { + if f, err := os.Create(daemonExportFile); err != nil { + log.Error("Failed to create %s: %v", daemonExportFile, err) + } else { + f.Close() + } + } + + return nil +} + +// updateRepository updates a repository with db context +func updateRepository(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) { + repo.LowerName = strings.ToLower(repo.Name) + + e := db.GetEngine(ctx) + + if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil { + return fmt.Errorf("update: %w", err) + } + + if err = repo_module.UpdateRepoSize(ctx, repo); err != nil { + log.Error("Failed to update size for repository: %v", err) + } + + if visibilityChanged { + if err = repo.LoadOwner(ctx); err != nil { + return fmt.Errorf("LoadOwner: %w", err) + } + if repo.Owner.IsOrganization() { + // Organization repository need to recalculate access table when visibility is changed. + if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil { + return fmt.Errorf("recalculateTeamAccesses: %w", err) + } + } + + // If repo has become private, we need to set its actions to private. + if repo.IsPrivate { + _, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&activities_model.Action{ + IsPrivate: true, + }) + if err != nil { + return err + } + + if err = repo_model.ClearRepoStars(ctx, repo.ID); err != nil { + return err + } + } + + // Create/Remove git-daemon-export-ok for git-daemon... + if err := checkDaemonExportOK(ctx, repo); err != nil { + return err + } + + forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID) + if err != nil { + return fmt.Errorf("getRepositoriesByForkID: %w", err) + } + for i := range forkRepos { + forkRepos[i].IsPrivate = repo.IsPrivate || repo.Owner.Visibility == structs.VisibleTypePrivate + if err = updateRepository(ctx, forkRepos[i], true); err != nil { + return fmt.Errorf("updateRepository[%d]: %w", forkRepos[i].ID, err) + } + } + + // If visibility is changed, we need to update the issue indexer. + // Since the data in the issue indexer have field to indicate if the repo is public or not. + issue_indexer.UpdateRepoIndexer(ctx, repo.ID) + } + + return nil +} diff --git a/services/repository/repository_test.go b/services/repository/repository_test.go index 892a11a23e..8f9fdf8fa1 100644 --- a/services/repository/repository_test.go +++ b/services/repository/repository_test.go @@ -6,6 +6,7 @@ package repository import ( "testing" + activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" @@ -40,3 +41,23 @@ func TestLinkedRepository(t *testing.T) { }) } } + +func TestUpdateRepositoryVisibilityChanged(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + // Get sample repo and change visibility + repo, err := repo_model.GetRepositoryByID(db.DefaultContext, 9) + assert.NoError(t, err) + repo.IsPrivate = true + + // Update it + err = updateRepository(db.DefaultContext, repo, true) + assert.NoError(t, err) + + // Check visibility of action has become private + act := activities_model.Action{} + _, err = db.GetEngine(db.DefaultContext).ID(3).Get(&act) + + assert.NoError(t, err) + assert.True(t, act.IsPrivate) +} diff --git a/services/repository/template.go b/services/repository/template.go index 36a680c8e2..95f585cead 100644 --- a/services/repository/template.go +++ b/services/repository/template.go @@ -5,12 +5,17 @@ package repository import ( "context" + "fmt" + "strings" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" + "code.gitea.io/gitea/modules/log" notify_service "code.gitea.io/gitea/services/notify" ) @@ -63,72 +68,126 @@ func GenerateProtectedBranch(ctx context.Context, templateRepo, generateRepo *re // GenerateRepository generates a repository from a template func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templateRepo *repo_model.Repository, opts GenerateRepoOptions) (_ *repo_model.Repository, err error) { - if !doer.IsAdmin && !owner.CanCreateRepo() { + if !doer.CanCreateRepoIn(owner) { return nil, repo_model.ErrReachLimitOfRepo{ Limit: owner.MaxRepoCreation, } } - var generateRepo *repo_model.Repository - if err = db.WithTx(ctx, func(ctx context.Context) error { - generateRepo, err = generateRepository(ctx, doer, owner, templateRepo, opts) - if err != nil { - return err - } + generateRepo := &repo_model.Repository{ + OwnerID: owner.ID, + Owner: owner, + OwnerName: owner.Name, + Name: opts.Name, + LowerName: strings.ToLower(opts.Name), + Description: opts.Description, + DefaultBranch: opts.DefaultBranch, + IsPrivate: opts.Private, + IsEmpty: !opts.GitContent || templateRepo.IsEmpty, + IsFsckEnabled: templateRepo.IsFsckEnabled, + TemplateID: templateRepo.ID, + TrustModel: templateRepo.TrustModel, + ObjectFormatName: templateRepo.ObjectFormatName, + Status: repo_model.RepositoryBeingMigrated, + } - // Git Content - if opts.GitContent && !templateRepo.IsEmpty { - if err = GenerateGitContent(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - // Topics - if opts.Topics { - if err = repo_model.GenerateTopics(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - // Git Hooks - if opts.GitHooks { - if err = GenerateGitHooks(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - // Webhooks - if opts.Webhooks { - if err = GenerateWebhooks(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - // Avatar - if opts.Avatar && len(templateRepo.Avatar) > 0 { - if err = generateAvatar(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - // Issue Labels - if opts.IssueLabels { - if err = GenerateIssueLabels(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - if opts.ProtectedBranch { - if err = GenerateProtectedBranch(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - return nil + // 1 - Create the repository in the database + if err := db.WithTx(ctx, func(ctx context.Context) error { + return createRepositoryInDB(ctx, doer, owner, generateRepo, false) }); err != nil { return nil, err } + // last - clean up the repository if something goes wrong + defer func() { + if err != nil { + // we can not use the ctx because it maybe canceled or timeout + cleanupRepository(doer, generateRepo.ID) + } + }() + + // 2 - check whether the repository with the same storage exists + isExist, err := gitrepo.IsRepositoryExist(ctx, generateRepo) + if err != nil { + log.Error("Unable to check if %s exists. Error: %v", generateRepo.FullName(), err) + return nil, err + } + if isExist { + // Don't return directly, we need err in defer to cleanupRepository + err = repo_model.ErrRepoFilesAlreadyExist{ + Uname: generateRepo.OwnerName, + Name: generateRepo.Name, + } + return nil, err + } + + // 3 -Init git bare new repository. + if err = git.InitRepository(ctx, generateRepo.RepoPath(), true, generateRepo.ObjectFormatName); err != nil { + return nil, fmt.Errorf("git.InitRepository: %w", err) + } else if err = gitrepo.CreateDelegateHooks(ctx, generateRepo); err != nil { + return nil, fmt.Errorf("createDelegateHooks: %w", err) + } + + // 4 - Update the git repository + if err = updateGitRepoAfterCreate(ctx, generateRepo); err != nil { + return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err) + } + + // 5 - generate the repository contents according to the template + // Git Content + if opts.GitContent && !templateRepo.IsEmpty { + if err = GenerateGitContent(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + // Topics + if opts.Topics { + if err = repo_model.GenerateTopics(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + // Git Hooks + if opts.GitHooks { + if err = GenerateGitHooks(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + // Webhooks + if opts.Webhooks { + if err = GenerateWebhooks(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + // Avatar + if opts.Avatar && len(templateRepo.Avatar) > 0 { + if err = generateAvatar(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + // Issue Labels + if opts.IssueLabels { + if err = GenerateIssueLabels(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + if opts.ProtectedBranch { + if err = GenerateProtectedBranch(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + // 6 - update repository status to be ready + generateRepo.Status = repo_model.RepositoryReady + if err = repo_model.UpdateRepositoryCols(ctx, generateRepo, "status"); err != nil { + return nil, fmt.Errorf("UpdateRepositoryCols: %w", err) + } + notify_service.CreateRepository(ctx, doer, owner, generateRepo) return generateRepo, nil diff --git a/services/repository/transfer.go b/services/repository/transfer.go index a589bc469d..86917ad285 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -20,10 +20,22 @@ import ( "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/globallock" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" notify_service "code.gitea.io/gitea/services/notify" ) +type LimitReachedError struct{ Limit int } + +func (LimitReachedError) Error() string { + return "Repository limit has been reached" +} + +func IsRepositoryLimitReached(err error) bool { + _, ok := err.(LimitReachedError) + return ok +} + func getRepoWorkingLockKey(repoID int64) string { return fmt.Sprintf("repo_working_%d", repoID) } @@ -49,6 +61,11 @@ func AcceptTransferOwnership(ctx context.Context, repo *repo_model.Repository, d return err } + if !doer.CanCreateRepoIn(repoTransfer.Recipient) { + limit := util.Iif(repoTransfer.Recipient.MaxRepoCreation >= 0, repoTransfer.Recipient.MaxRepoCreation, setting.Repository.MaxCreationLimit) + return LimitReachedError{Limit: limit} + } + if !repoTransfer.CanUserAcceptOrRejectTransfer(ctx, doer) { return util.ErrPermissionDenied } @@ -399,6 +416,11 @@ func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.Use return err } + if !doer.CanForkRepoIn(newOwner) { + limit := util.Iif(newOwner.MaxRepoCreation >= 0, newOwner.MaxRepoCreation, setting.Repository.MaxCreationLimit) + return LimitReachedError{Limit: limit} + } + var isDirectTransfer bool oldOwnerName := repo.OwnerName diff --git a/services/repository/transfer_test.go b/services/repository/transfer_test.go index 16a8fb6e1e..80a073e9f9 100644 --- a/services/repository/transfer_test.go +++ b/services/repository/transfer_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2025 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package repository @@ -14,11 +14,14 @@ import ( 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/setting" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/feed" notify_service "code.gitea.io/gitea/services/notify" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var notifySync sync.Once @@ -125,3 +128,40 @@ func TestRepositoryTransfer(t *testing.T) { err = RejectRepositoryTransfer(db.DefaultContext, repo2, doer) assert.True(t, repo_model.IsErrNoPendingTransfer(err)) } + +// Test transfer rejections +func TestRepositoryTransferRejection(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + // Set limit to 0 repositories so no repositories can be transferred + defer test.MockVariableValue(&setting.Repository.MaxCreationLimit, 0)() + + // Admin case + doerAdmin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 5}) + + transfer, err := repo_model.GetPendingRepositoryTransfer(db.DefaultContext, repo) + require.NoError(t, err) + require.NotNil(t, transfer) + require.NoError(t, transfer.LoadRecipient(db.DefaultContext)) + + require.True(t, doerAdmin.CanCreateRepoIn(transfer.Recipient)) // admin is not subject to limits + + // Administrator should not be affected by the limits so transfer should be successful + assert.NoError(t, AcceptTransferOwnership(db.DefaultContext, repo, doerAdmin)) + + // Non admin user case + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10}) + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21}) + + transfer, err = repo_model.GetPendingRepositoryTransfer(db.DefaultContext, repo) + require.NoError(t, err) + require.NotNil(t, transfer) + require.NoError(t, transfer.LoadRecipient(db.DefaultContext)) + + require.False(t, doer.CanCreateRepoIn(transfer.Recipient)) // regular user is subject to limits + + // Cannot accept because of the limit + err = AcceptTransferOwnership(db.DefaultContext, repo, doer) + assert.Error(t, err) + assert.True(t, IsRepositoryLimitReached(err)) +} diff --git a/services/task/task.go b/services/task/task.go index c90ee91270..105aee2a25 100644 --- a/services/task/task.go +++ b/services/task/task.go @@ -5,6 +5,7 @@ package task import ( "context" + "errors" "fmt" admin_model "code.gitea.io/gitea/models/admin" @@ -41,7 +42,7 @@ func Run(ctx context.Context, t *admin_model.Task) error { func Init() error { taskQueue = queue.CreateSimpleQueue(graceful.GetManager().ShutdownContext(), "task", handler) if taskQueue == nil { - return fmt.Errorf("unable to create task queue") + return errors.New("unable to create task queue") } go graceful.GetManager().RunWithCancel(taskQueue) return nil diff --git a/services/user/user_test.go b/services/user/user_test.go index 162a735cd4..28a0df8628 100644 --- a/services/user/user_test.go +++ b/services/user/user_test.go @@ -150,7 +150,7 @@ func TestRenameUser(t *testing.T) { redirectUID, err := user_model.LookupUserRedirect(db.DefaultContext, oldUsername) assert.NoError(t, err) - assert.EqualValues(t, user.ID, redirectUID) + assert.Equal(t, user.ID, redirectUID) unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, OwnerName: user.Name}) }) diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go index df32d5741e..e8e6ed19c1 100644 --- a/services/webhook/deliver.go +++ b/services/webhook/deliver.go @@ -10,6 +10,7 @@ import ( "crypto/sha256" "crypto/tls" "encoding/hex" + "errors" "fmt" "io" "net/http" @@ -41,7 +42,7 @@ func newDefaultRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook case http.MethodPost: switch w.ContentType { case webhook_model.ContentTypeJSON: - req, err = http.NewRequest("POST", w.URL, strings.NewReader(t.PayloadContent)) + req, err = http.NewRequest(http.MethodPost, w.URL, strings.NewReader(t.PayloadContent)) if err != nil { return nil, nil, err } @@ -52,7 +53,7 @@ func newDefaultRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook "payload": []string{t.PayloadContent}, } - req, err = http.NewRequest("POST", w.URL, strings.NewReader(forms.Encode())) + req, err = http.NewRequest(http.MethodPost, w.URL, strings.NewReader(forms.Encode())) if err != nil { return nil, nil, err } @@ -69,7 +70,7 @@ func newDefaultRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook vals := u.Query() vals["payload"] = []string{t.PayloadContent} u.RawQuery = vals.Encode() - req, err = http.NewRequest("GET", u.String(), nil) + req, err = http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { return nil, nil, err } @@ -81,7 +82,7 @@ func newDefaultRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook return nil, nil, err } url := fmt.Sprintf("%s/%s", w.URL, url.PathEscape(txnID)) - req, err = http.NewRequest("PUT", url, strings.NewReader(t.PayloadContent)) + req, err = http.NewRequest(http.MethodPut, url, strings.NewReader(t.PayloadContent)) if err != nil { return nil, nil, err } @@ -328,7 +329,7 @@ func Init() error { hookQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "webhook_sender", handler) if hookQueue == nil { - return fmt.Errorf("unable to create webhook_sender queue") + return errors.New("unable to create webhook_sender queue") } go graceful.GetManager().RunWithCancel(hookQueue) diff --git a/services/webhook/deliver_test.go b/services/webhook/deliver_test.go index 6a74b1455c..1d32d7b772 100644 --- a/services/webhook/deliver_test.go +++ b/services/webhook/deliver_test.go @@ -64,7 +64,7 @@ func TestWebhookProxy(t *testing.T) { } for _, tt := range tests { t.Run(tt.req, func(t *testing.T) { - req, err := http.NewRequest("POST", tt.req, nil) + req, err := http.NewRequest(http.MethodPost, tt.req, nil) require.NoError(t, err) u, err := webhookProxy(allowedHostMatcher)(req) @@ -91,7 +91,7 @@ func TestWebhookDeliverAuthorizationHeader(t *testing.T) { s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/webhook", r.URL.Path) assert.Equal(t, "Bearer s3cr3t-t0ken", r.Header.Get("Authorization")) - w.WriteHeader(200) + w.WriteHeader(http.StatusOK) done <- struct{}{} })) t.Cleanup(s.Close) @@ -138,7 +138,7 @@ func TestWebhookDeliverHookTask(t *testing.T) { case "/webhook/66d222a5d6349e1311f551e50722d837e30fce98": // Version 1 assert.Equal(t, "push", r.Header.Get("X-GitHub-Event")) - assert.Equal(t, "", r.Header.Get("Content-Type")) + assert.Empty(t, r.Header.Get("Content-Type")) body, err := io.ReadAll(r.Body) assert.NoError(t, err) assert.Equal(t, `{"data": 42}`, string(body)) @@ -152,11 +152,11 @@ func TestWebhookDeliverHookTask(t *testing.T) { assert.Len(t, body, 2147) default: - w.WriteHeader(404) + w.WriteHeader(http.StatusNotFound) t.Fatalf("unexpected url path %s", r.URL.Path) return } - w.WriteHeader(200) + w.WriteHeader(http.StatusOK) done <- struct{}{} })) t.Cleanup(s.Close) diff --git a/services/webhook/dingtalk.go b/services/webhook/dingtalk.go index 5afca8d65a..ce907bf0cb 100644 --- a/services/webhook/dingtalk.go +++ b/services/webhook/dingtalk.go @@ -30,7 +30,7 @@ func (dc dingtalkConvertor) Create(p *api.CreatePayload) (DingtalkPayload, error refName := git.RefName(p.Ref).ShortName() title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName) - return createDingtalkPayload(title, title, fmt.Sprintf("view ref %s", refName), p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName)), nil + return createDingtalkPayload(title, title, "view ref "+refName, p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName)), nil } // Delete implements PayloadConvertor Delete method @@ -39,14 +39,14 @@ func (dc dingtalkConvertor) Delete(p *api.DeletePayload) (DingtalkPayload, error refName := git.RefName(p.Ref).ShortName() title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) - return createDingtalkPayload(title, title, fmt.Sprintf("view ref %s", refName), p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName)), nil + return createDingtalkPayload(title, title, "view ref "+refName, p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName)), nil } // Fork implements PayloadConvertor Fork method func (dc dingtalkConvertor) Fork(p *api.ForkPayload) (DingtalkPayload, error) { title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName) - return createDingtalkPayload(title, title, fmt.Sprintf("view forked repo %s", p.Repo.FullName), p.Repo.HTMLURL), nil + return createDingtalkPayload(title, title, "view forked repo "+p.Repo.FullName, p.Repo.HTMLURL), nil } // Push implements PayloadConvertor Push method diff --git a/services/webhook/discord.go b/services/webhook/discord.go index 0a7eb0b166..0e8a9aa67c 100644 --- a/services/webhook/discord.go +++ b/services/webhook/discord.go @@ -101,6 +101,13 @@ var ( redColor = color("ff3232") ) +// https://discord.com/developers/docs/resources/message#embed-object-embed-limits +// Discord has some limits in place for the embeds. +// According to some tests, there is no consistent limit for different character sets. +// For example: 4096 ASCII letters are allowed, but only 2490 emoji characters are allowed. +// To keep it simple, we currently truncate at 2000. +const discordDescriptionCharactersLimit = 2000 + type discordConvertor struct { Username string AvatarURL string @@ -313,7 +320,7 @@ func (d discordConvertor) createPayload(s *api.User, title, text, url string, co Embeds: []DiscordEmbed{ { Title: title, - Description: text, + Description: util.TruncateRunes(text, discordDescriptionCharactersLimit), URL: url, Color: color, Author: DiscordEmbedAuthor{ diff --git a/services/webhook/general.go b/services/webhook/general.go index ea75038faf..251659e75e 100644 --- a/services/webhook/general.go +++ b/services/webhook/general.go @@ -39,19 +39,20 @@ func getPullRequestInfo(p *api.PullRequestPayload) (title, link, by, operator, o for i, user := range assignList { assignStringList[i] = user.UserName } - if p.Action == api.HookIssueAssigned { + switch p.Action { + case api.HookIssueAssigned: operateResult = fmt.Sprintf("%s assign this to %s", p.Sender.UserName, assignList[len(assignList)-1].UserName) - } else if p.Action == api.HookIssueUnassigned { - operateResult = fmt.Sprintf("%s unassigned this for someone", p.Sender.UserName) - } else if p.Action == api.HookIssueMilestoned { + case api.HookIssueUnassigned: + operateResult = p.Sender.UserName + " unassigned this for someone" + case api.HookIssueMilestoned: operateResult = fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.PullRequest.Milestone.ID) } link = p.PullRequest.HTMLURL - by = fmt.Sprintf("PullRequest by %s", p.PullRequest.Poster.UserName) + by = "PullRequest by " + p.PullRequest.Poster.UserName if len(assignStringList) > 0 { - assignees = fmt.Sprintf("Assignees: %s", strings.Join(assignStringList, ", ")) + assignees = "Assignees: " + strings.Join(assignStringList, ", ") } - operator = fmt.Sprintf("Operator: %s", p.Sender.UserName) + operator = "Operator: " + p.Sender.UserName return title, link, by, operator, operateResult, assignees } @@ -64,19 +65,20 @@ func getIssuesInfo(p *api.IssuePayload) (issueTitle, link, by, operator, operate for i, user := range assignList { assignStringList[i] = user.UserName } - if p.Action == api.HookIssueAssigned { + switch p.Action { + case api.HookIssueAssigned: operateResult = fmt.Sprintf("%s assign this to %s", p.Sender.UserName, assignList[len(assignList)-1].UserName) - } else if p.Action == api.HookIssueUnassigned { - operateResult = fmt.Sprintf("%s unassigned this for someone", p.Sender.UserName) - } else if p.Action == api.HookIssueMilestoned { + case api.HookIssueUnassigned: + operateResult = p.Sender.UserName + " unassigned this for someone" + case api.HookIssueMilestoned: operateResult = fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.Issue.Milestone.ID) } link = p.Issue.HTMLURL - by = fmt.Sprintf("Issue by %s", p.Issue.Poster.UserName) + by = "Issue by " + p.Issue.Poster.UserName if len(assignStringList) > 0 { - assignees = fmt.Sprintf("Assignees: %s", strings.Join(assignStringList, ", ")) + assignees = "Assignees: " + strings.Join(assignStringList, ", ") } - operator = fmt.Sprintf("Operator: %s", p.Sender.UserName) + operator = "Operator: " + p.Sender.UserName return issueTitle, link, by, operator, operateResult, assignees } @@ -85,11 +87,11 @@ func getIssuesCommentInfo(p *api.IssueCommentPayload) (title, link, by, operator title = fmt.Sprintf("[Comment-%s #%d]: %s\n%s", p.Repository.FullName, p.Issue.Index, p.Action, p.Issue.Title) link = p.Issue.HTMLURL if p.IsPull { - by = fmt.Sprintf("PullRequest by %s", p.Issue.Poster.UserName) + by = "PullRequest by " + p.Issue.Poster.UserName } else { - by = fmt.Sprintf("Issue by %s", p.Issue.Poster.UserName) + by = "Issue by " + p.Issue.Poster.UserName } - operator = fmt.Sprintf("Operator: %s", p.Sender.UserName) + operator = "Operator: " + p.Sender.UserName return title, link, by, operator } @@ -133,7 +135,7 @@ func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter, with text = fmt.Sprintf("[%s] Issue milestone cleared: %s", repoLink, titleLink) } if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) + text += " by " + linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName) } if p.Action == api.HookIssueOpened || p.Action == api.HookIssueEdited { @@ -198,7 +200,7 @@ func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkForm text = fmt.Sprintf("[%s] Pull request review request removed: %s", repoLink, titleLink) } if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)) + text += " by " + linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) } return text, issueTitle, extraMarkdown, color @@ -220,7 +222,7 @@ func getReleasePayloadInfo(p *api.ReleasePayload, linkFormatter linkFormatter, w color = redColor } if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) + text += " by " + linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName) } return text, color @@ -249,7 +251,7 @@ func getWikiPayloadInfo(p *api.WikiPayload, linkFormatter linkFormatter, withSen } if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) + text += " by " + linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName) } return text, color, pageLink @@ -285,7 +287,7 @@ func getIssueCommentPayloadInfo(p *api.IssueCommentPayload, linkFormatter linkFo color = redColor } if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) + text += " by " + linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName) } return text, issueTitle, color @@ -296,14 +298,14 @@ func getPackagePayloadInfo(p *api.PackagePayload, linkFormatter linkFormatter, w switch p.Action { case api.HookPackageCreated: - text = fmt.Sprintf("Package created: %s", refLink) + text = "Package created: " + refLink color = greenColor case api.HookPackageDeleted: - text = fmt.Sprintf("Package deleted: %s", refLink) + text = "Package deleted: " + refLink color = redColor } if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) + text += " by " + linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName) } return text, color @@ -316,9 +318,9 @@ func getStatusPayloadInfo(p *api.CommitStatusPayload, linkFormatter linkFormatte color = greenColor if withSender { if user_model.IsGiteaActionsUserName(p.Sender.UserName) { - text += fmt.Sprintf(" by %s", p.Sender.FullName) + text += " by " + p.Sender.FullName } else { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) + text += " by " + linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName) } } @@ -350,7 +352,7 @@ func getWorkflowJobPayloadInfo(p *api.WorkflowJobPayload, linkFormatter linkForm color = greyColor } if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) + text += " by " + linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName) } return text, color diff --git a/services/webhook/msteams.go b/services/webhook/msteams.go index f70e235f20..07d28c3867 100644 --- a/services/webhook/msteams.go +++ b/services/webhook/msteams.go @@ -8,6 +8,7 @@ import ( "fmt" "net/http" "net/url" + "strconv" "strings" webhook_model "code.gitea.io/gitea/models/webhook" @@ -73,7 +74,7 @@ func (m msteamsConvertor) Create(p *api.CreatePayload) (MSTeamsPayload, error) { "", p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName), greenColor, - &MSTeamsFact{fmt.Sprintf("%s:", p.RefType), refName}, + &MSTeamsFact{p.RefType + ":", refName}, ), nil } @@ -90,7 +91,7 @@ func (m msteamsConvertor) Delete(p *api.DeletePayload) (MSTeamsPayload, error) { "", p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName), yellowColor, - &MSTeamsFact{fmt.Sprintf("%s:", p.RefType), refName}, + &MSTeamsFact{p.RefType + ":", refName}, ), nil } @@ -148,7 +149,7 @@ func (m msteamsConvertor) Push(p *api.PushPayload) (MSTeamsPayload, error) { text, titleLink, greenColor, - &MSTeamsFact{"Commit count:", fmt.Sprintf("%d", p.TotalCommits)}, + &MSTeamsFact{"Commit count:", strconv.Itoa(p.TotalCommits)}, ), nil } @@ -163,7 +164,7 @@ func (m msteamsConvertor) Issue(p *api.IssuePayload) (MSTeamsPayload, error) { extraMarkdown, p.Issue.HTMLURL, color, - &MSTeamsFact{"Issue #:", fmt.Sprintf("%d", p.Issue.ID)}, + &MSTeamsFact{"Issue #:", strconv.FormatInt(p.Issue.ID, 10)}, ), nil } @@ -178,7 +179,7 @@ func (m msteamsConvertor) IssueComment(p *api.IssueCommentPayload) (MSTeamsPaylo p.Comment.Body, p.Comment.HTMLURL, color, - &MSTeamsFact{"Issue #:", fmt.Sprintf("%d", p.Issue.ID)}, + &MSTeamsFact{"Issue #:", strconv.FormatInt(p.Issue.ID, 10)}, ), nil } @@ -193,7 +194,7 @@ func (m msteamsConvertor) PullRequest(p *api.PullRequestPayload) (MSTeamsPayload extraMarkdown, p.PullRequest.HTMLURL, color, - &MSTeamsFact{"Pull request #:", fmt.Sprintf("%d", p.PullRequest.ID)}, + &MSTeamsFact{"Pull request #:", strconv.FormatInt(p.PullRequest.ID, 10)}, ), nil } @@ -230,7 +231,7 @@ func (m msteamsConvertor) Review(p *api.PullRequestPayload, event webhook_module text, p.PullRequest.HTMLURL, color, - &MSTeamsFact{"Pull request #:", fmt.Sprintf("%d", p.PullRequest.ID)}, + &MSTeamsFact{"Pull request #:", strconv.FormatInt(p.PullRequest.ID, 10)}, ), nil } diff --git a/services/webhook/msteams_test.go b/services/webhook/msteams_test.go index d5020d3ff5..0d98b94bad 100644 --- a/services/webhook/msteams_test.go +++ b/services/webhook/msteams_test.go @@ -335,7 +335,7 @@ func TestMSTeamsPayload(t *testing.T) { assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment)", pl.Summary) assert.Len(t, pl.Sections, 1) assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Equal(t, "", pl.Sections[0].Text) + assert.Empty(t, pl.Sections[0].Text) assert.Len(t, pl.Sections[0].Facts, 2) for _, fact := range pl.Sections[0].Facts { if fact.Name == "Repository:" { @@ -356,7 +356,7 @@ func TestMSTeamsPayload(t *testing.T) { assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment)", pl.Summary) assert.Len(t, pl.Sections, 1) assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Equal(t, "", pl.Sections[0].Text) + assert.Empty(t, pl.Sections[0].Text) assert.Len(t, pl.Sections[0].Facts, 2) for _, fact := range pl.Sections[0].Facts { if fact.Name == "Repository:" { diff --git a/services/webhook/packagist_test.go b/services/webhook/packagist_test.go index 638dcfd302..4e77f29edc 100644 --- a/services/webhook/packagist_test.go +++ b/services/webhook/packagist_test.go @@ -210,5 +210,5 @@ func TestPackagistEmptyPayload(t *testing.T) { var body PackagistPayload err = json.NewDecoder(req.Body).Decode(&body) assert.NoError(t, err) - assert.Equal(t, "", body.PackagistRepository.URL) + assert.Empty(t, body.PackagistRepository.URL) } diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index 4cc372c70d..84cb9ced4d 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -102,15 +102,11 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model hasDefaultBranch := gitrepo.IsBranchExist(ctx, repo.WikiStorageRepo(), repo.DefaultWikiBranch) - basePath, err := repo_module.CreateTemporaryPath("update-wiki") + basePath, cleanup, err := repo_module.CreateTemporaryPath("update-wiki") if err != nil { return err } - defer func() { - if err := repo_module.RemoveTemporaryPath(basePath); err != nil { - log.Error("Merge: RemoveTemporaryPath: %s", err) - } - }() + defer cleanup() cloneOpts := git.CloneRepoOptions{ Bare: true, @@ -264,15 +260,11 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model return fmt.Errorf("InitWiki: %w", err) } - basePath, err := repo_module.CreateTemporaryPath("update-wiki") + basePath, cleanup, err := repo_module.CreateTemporaryPath("update-wiki") if err != nil { return err } - defer func() { - if err := repo_module.RemoveTemporaryPath(basePath); err != nil { - log.Error("Merge: RemoveTemporaryPath: %s", err) - } - }() + defer cleanup() if err := git.Clone(ctx, repo.WikiPath(), basePath, git.CloneRepoOptions{ Bare: true, diff --git a/services/wiki/wiki_test.go b/services/wiki/wiki_test.go index 288d258279..f441c2939b 100644 --- a/services/wiki/wiki_test.go +++ b/services/wiki/wiki_test.go @@ -26,7 +26,7 @@ func TestMain(m *testing.M) { func TestWebPathSegments(t *testing.T) { a := WebPathSegments("a%2Fa/b+c/d-e/f-g.-") - assert.EqualValues(t, []string{"a/a", "b c", "d e", "f-g"}, a) + assert.Equal(t, []string{"a/a", "b c", "d e", "f-g"}, a) } func TestUserTitleToWebPath(t *testing.T) { @@ -63,7 +63,7 @@ func TestWebPathToDisplayName(t *testing.T) { {"a b", "a%20b.md"}, } { _, displayName := WebPathToUserTitle(test.WebPath) - assert.EqualValues(t, test.Expected, displayName) + assert.Equal(t, test.Expected, displayName) } } @@ -80,7 +80,7 @@ func TestWebPathToGitPath(t *testing.T) { {"2000-01-02-meeting.md", "2000-01-02+meeting"}, {"2000-01-02 meeting.-.md", "2000-01-02%20meeting.-"}, } { - assert.EqualValues(t, test.Expected, WebPathToGitPath(test.WikiName)) + assert.Equal(t, test.Expected, WebPathToGitPath(test.WikiName)) } } @@ -134,9 +134,9 @@ func TestUserWebGitPathConsistency(t *testing.T) { _, userTitle1 := WebPathToUserTitle(webPath1) gitPath1 := WebPathToGitPath(webPath1) - assert.EqualValues(t, userTitle, userTitle1, "UserTitle for userTitle: %q", userTitle) - assert.EqualValues(t, webPath, webPath1, "WebPath for userTitle: %q", userTitle) - assert.EqualValues(t, gitPath, gitPath1, "GitPath for userTitle: %q", userTitle) + assert.Equal(t, userTitle, userTitle1, "UserTitle for userTitle: %q", userTitle) + assert.Equal(t, webPath, webPath1, "WebPath for userTitle: %q", userTitle) + assert.Equal(t, gitPath, gitPath1, "GitPath for userTitle: %q", userTitle) } } @@ -175,7 +175,7 @@ func TestRepository_AddWikiPage(t *testing.T) { gitPath := WebPathToGitPath(webPath) entry, err := masterTree.GetTreeEntryByPath(gitPath) assert.NoError(t, err) - assert.EqualValues(t, gitPath, entry.Name(), "%s not added correctly", userTitle) + assert.Equal(t, gitPath, entry.Name(), "%s not added correctly", userTitle) }) } @@ -220,7 +220,7 @@ func TestRepository_EditWikiPage(t *testing.T) { gitPath := WebPathToGitPath(webPath) entry, err := masterTree.GetTreeEntryByPath(gitPath) assert.NoError(t, err) - assert.EqualValues(t, gitPath, entry.Name(), "%s not edited correctly", newWikiName) + assert.Equal(t, gitPath, entry.Name(), "%s not edited correctly", newWikiName) if newWikiName != "Home" { _, err := masterTree.GetTreeEntryByPath("Home.md") @@ -290,7 +290,7 @@ func TestPrepareWikiFileName(t *testing.T) { t.Errorf("expect to find an escaped file but we could not detect one") } } - assert.EqualValues(t, tt.wikiPath, newWikiPath) + assert.Equal(t, tt.wikiPath, newWikiPath) }) } } @@ -312,13 +312,13 @@ func TestPrepareWikiFileName_FirstPage(t *testing.T) { existence, newWikiPath, err := prepareGitPath(gitRepo, "master", "Home") assert.False(t, existence) assert.NoError(t, err) - assert.EqualValues(t, "Home.md", newWikiPath) + assert.Equal(t, "Home.md", newWikiPath) } func TestWebPathConversion(t *testing.T) { assert.Equal(t, "path/wiki", WebPathToURLPath(WebPath("path/wiki"))) assert.Equal(t, "wiki", WebPathToURLPath(WebPath("wiki"))) - assert.Equal(t, "", WebPathToURLPath(WebPath(""))) + assert.Empty(t, WebPathToURLPath(WebPath(""))) } func TestWebPathFromRequest(t *testing.T) { diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 29a5e1b473..806347c720 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -69,10 +69,6 @@ {{if not .SSH.StartBuiltinServer}} {{ctx.Locale.Tr "admin.config.ssh_root_path"}} {{.SSH.RootPath}} - {{ctx.Locale.Tr "admin.config.ssh_key_test_path"}} - {{.SSH.KeyTestPath}} - {{ctx.Locale.Tr "admin.config.ssh_keygen_path"}} - {{.SSH.KeygenPath}} {{ctx.Locale.Tr "admin.config.ssh_minimum_key_size_check"}} {{svg (Iif .SSH.MinimumKeySizeCheck "octicon-check" "octicon-x")}} {{if .SSH.MinimumKeySizeCheck}} @@ -148,7 +144,7 @@ {{ctx.Locale.Tr "admin.config.enable_openid_signin"}} {{svg (Iif .Service.EnableOpenIDSignIn "octicon-check" "octicon-x")}} {{ctx.Locale.Tr "admin.config.require_sign_in_view"}} - {{svg (Iif .Service.RequireSignInView "octicon-check" "octicon-x")}} + {{svg (Iif .Service.RequireSignInViewStrict "octicon-check" "octicon-x")}} {{ctx.Locale.Tr "admin.config.mail_notify"}} {{svg (Iif .Service.EnableNotifyMail "octicon-check" "octicon-x")}} {{ctx.Locale.Tr "admin.config.enable_captcha"}} diff --git a/templates/devtest/badge-actions-svg.tmpl b/templates/devtest/badge-actions-svg.tmpl index 8125793bb3..5be4fb3131 100644 --- a/templates/devtest/badge-actions-svg.tmpl +++ b/templates/devtest/badge-actions-svg.tmpl @@ -3,9 +3,16 @@ Actions SVG - {{range $fontName := .BadgeFontFamilyNames}} - {{$fontName}} - {{end}} + + {{range $fontName := .BadgeFontFamilyNames}} + {{$fontName}} + {{end}} + + + {{range $style := .BadgeStyles}} + {{$style}} + {{end}} + submit diff --git a/templates/devtest/fomantic-dropdown.tmpl b/templates/devtest/fomantic-dropdown.tmpl index d41a161e86..a10dc890ce 100644 --- a/templates/devtest/fomantic-dropdown.tmpl +++ b/templates/devtest/fomantic-dropdown.tmpl @@ -75,12 +75,12 @@ Selection {{/* the "selection" class is optional, it will be added by JS automatically */}} - + a abcdefuvwxyz loooooooooooooooooooooooooooooooooooooooooooooooooooooooooong - + loooooooooooooooooooooooooooooooooooooooooooooooooooooooooong abcdefuvwxyz a diff --git a/templates/explore/repo_list.tmpl b/templates/explore/repo_list.tmpl index 219b1255c0..ad190b89b2 100644 --- a/templates/explore/repo_list.tmpl +++ b/templates/explore/repo_list.tmpl @@ -39,12 +39,12 @@ {{end}} {{if not $.DisableStars}} - {{svg "octicon-star" 16}} + {{svg "octicon-star" 16}} {{CountFmt .NumStars}} {{end}} - {{svg "octicon-git-branch" 16}} + {{svg "octicon-git-branch" 16}} {{CountFmt .NumForks}} diff --git a/templates/org/menu.tmpl b/templates/org/menu.tmpl index 2d3af2d559..d876dabb44 100644 --- a/templates/org/menu.tmpl +++ b/templates/org/menu.tmpl @@ -44,7 +44,7 @@ {{end}} {{end}} - {{if .IsOrganizationOwner}} + {{if and EnableTimetracking .IsOrganizationOwner}} {{svg "octicon-clock"}} {{ctx.Locale.Tr "org.worktime"}} diff --git a/templates/org/projects/new.tmpl b/templates/org/projects/new.tmpl index fc52130f68..c021c5a0fe 100644 --- a/templates/org/projects/new.tmpl +++ b/templates/org/projects/new.tmpl @@ -1,9 +1,24 @@ {{template "base/head" .}} - - {{template "shared/user/org_profile_avatar" .}} +{{if .ContextUser.IsOrganization}} + + {{template "org/header" .}} - {{template "user/overview/header" .}} - {{template "projects/new" .}} + {{template "projects/new" .}} +{{else}} + + + + + {{template "shared/user/profile_big_avatar" .}} + + + {{template "user/overview/header" .}} + {{template "projects/new" .}} + + + + +{{end}} {{template "base/footer" .}} diff --git a/templates/org/team/new.tmpl b/templates/org/team/new.tmpl index b67c18dd7d..67529ddfba 100644 --- a/templates/org/team/new.tmpl +++ b/templates/org/team/new.tmpl @@ -56,7 +56,7 @@ - + {{ctx.Locale.Tr "org.teams.general_access"}} {{ctx.Locale.Tr "org.teams.general_access_helper"}} diff --git a/templates/org/team/sidebar.tmpl b/templates/org/team/sidebar.tmpl index c4acd8da24..8390bf0acd 100644 --- a/templates/org/team/sidebar.tmpl +++ b/templates/org/team/sidebar.tmpl @@ -42,10 +42,12 @@ {{ctx.Locale.Tr "org.teams.can_create_org_repo"}} {{end}} - {{if (eq .Team.AccessMode 2)}} + {{/* the AccessMode should be either none or admin/owner, the real permissions are provided by each team unit */}} + {{if false}}{{/*(eq .Team.AccessMode 2)*/}} {{ctx.Locale.Tr "org.settings.permission"}} {{ctx.Locale.Tr "org.teams.write_permission_desc"}} {{else if (eq .Team.AccessMode 3)}} + {{/* FIXME: here might not right, see "FIXME: TEAM-UNIT-PERMISSION", new units might not have correct admin permission*/}} {{ctx.Locale.Tr "org.settings.permission"}} {{ctx.Locale.Tr "org.teams.admin_permission_desc"}} {{else}} diff --git a/templates/package/shared/view.tmpl b/templates/package/shared/view.tmpl new file mode 100644 index 0000000000..713e1bbfc5 --- /dev/null +++ b/templates/package/shared/view.tmpl @@ -0,0 +1,106 @@ + + {{.PackageDescriptor.Package.Name}} ({{.PackageDescriptor.Version.Version}}) + + {{$timeStr := DateUtils.TimeSince .PackageDescriptor.Version.CreatedUnix}} + {{if .HasRepositoryAccess}} + {{ctx.Locale.Tr "packages.published_by_in" $timeStr .PackageDescriptor.Creator.HomeLink .PackageDescriptor.Creator.GetDisplayName .PackageDescriptor.Repository.Link .PackageDescriptor.Repository.FullName}} + {{else}} + {{ctx.Locale.Tr "packages.published_by" $timeStr .PackageDescriptor.Creator.HomeLink .PackageDescriptor.Creator.GetDisplayName}} + {{end}} + + + + + {{template "package/content/alpine" .}} + {{template "package/content/arch" .}} + {{template "package/content/cargo" .}} + {{template "package/content/chef" .}} + {{template "package/content/composer" .}} + {{template "package/content/conan" .}} + {{template "package/content/conda" .}} + {{template "package/content/container" .}} + {{template "package/content/cran" .}} + {{template "package/content/debian" .}} + {{template "package/content/generic" .}} + {{template "package/content/go" .}} + {{template "package/content/helm" .}} + {{template "package/content/maven" .}} + {{template "package/content/npm" .}} + {{template "package/content/nuget" .}} + {{template "package/content/pub" .}} + {{template "package/content/pypi" .}} + {{template "package/content/rpm" .}} + {{template "package/content/rubygems" .}} + {{template "package/content/swift" .}} + {{template "package/content/vagrant" .}} + + + {{ctx.Locale.Tr "packages.details"}} + + {{svg .PackageDescriptor.Package.Type.SVGName}} {{.PackageDescriptor.Package.Type.Name}} + {{if .HasRepositoryAccess}} + {{svg "octicon-repo"}} {{.PackageDescriptor.Repository.FullName}} + {{end}} + {{svg "octicon-calendar"}} {{DateUtils.TimeSince .PackageDescriptor.Version.CreatedUnix}} + {{svg "octicon-download"}} {{.PackageDescriptor.Version.DownloadCount}} + {{template "package/metadata/alpine" .}} + {{template "package/metadata/arch" .}} + {{template "package/metadata/cargo" .}} + {{template "package/metadata/chef" .}} + {{template "package/metadata/composer" .}} + {{template "package/metadata/conan" .}} + {{template "package/metadata/conda" .}} + {{template "package/metadata/container" .}} + {{template "package/metadata/cran" .}} + {{template "package/metadata/debian" .}} + {{template "package/metadata/generic" .}} + {{template "package/metadata/helm" .}} + {{template "package/metadata/maven" .}} + {{template "package/metadata/npm" .}} + {{template "package/metadata/nuget" .}} + {{template "package/metadata/pub" .}} + {{template "package/metadata/pypi" .}} + {{template "package/metadata/rpm" .}} + {{template "package/metadata/rubygems" .}} + {{template "package/metadata/swift" .}} + {{template "package/metadata/vagrant" .}} + {{if not (and (eq .PackageDescriptor.Package.Type "container") .PackageDescriptor.Metadata.Manifests)}} + {{svg "octicon-database"}} {{FileSize .PackageDescriptor.CalculateBlobSize}} + {{end}} + + {{if not (eq .PackageDescriptor.Package.Type "container")}} + + {{ctx.Locale.Tr "packages.assets"}} ({{len .PackageDescriptor.Files}}) + + {{range .PackageDescriptor.Files}} + + {{.File.Name}} + {{FileSize .Blob.Size}} + + {{end}} + + {{end}} + + {{ctx.Locale.Tr "packages.versions"}} ({{.TotalVersionCount}}) + {{ctx.Locale.Tr "packages.versions.view_all"}} + + {{range .LatestVersions}} + + {{.Version}} + {{DateUtils.AbsoluteShort .CreatedUnix}} + + {{end}} + + {{if or .CanWritePackages .HasRepositoryAccess}} + + + {{if .HasRepositoryAccess}} + {{svg "octicon-issue-opened"}} {{ctx.Locale.Tr "repo.issues"}} + {{end}} + {{if .CanWritePackages}} + {{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}} + {{end}} + + {{end}} + + diff --git a/templates/package/view.tmpl b/templates/package/view.tmpl index 9e92207466..9067f44296 100644 --- a/templates/package/view.tmpl +++ b/templates/package/view.tmpl @@ -1,114 +1,24 @@ {{template "base/head" .}} - - {{template "shared/user/org_profile_avatar" .}} +{{if .ContextUser.IsOrganization}} + + {{template "org/header" .}} - {{template "user/overview/header" .}} - - {{.PackageDescriptor.Package.Name}} ({{.PackageDescriptor.Version.Version}}) - - {{$timeStr := DateUtils.TimeSince .PackageDescriptor.Version.CreatedUnix}} - {{if .HasRepositoryAccess}} - {{ctx.Locale.Tr "packages.published_by_in" $timeStr .PackageDescriptor.Creator.HomeLink .PackageDescriptor.Creator.GetDisplayName .PackageDescriptor.Repository.Link .PackageDescriptor.Repository.FullName}} - {{else}} - {{ctx.Locale.Tr "packages.published_by" $timeStr .PackageDescriptor.Creator.HomeLink .PackageDescriptor.Creator.GetDisplayName}} - {{end}} + {{template "package/shared/view" .}} + + +{{else}} + + + + + {{template "shared/user/profile_big_avatar" .}} - - - - {{template "package/content/alpine" .}} - {{template "package/content/arch" .}} - {{template "package/content/cargo" .}} - {{template "package/content/chef" .}} - {{template "package/content/composer" .}} - {{template "package/content/conan" .}} - {{template "package/content/conda" .}} - {{template "package/content/container" .}} - {{template "package/content/cran" .}} - {{template "package/content/debian" .}} - {{template "package/content/generic" .}} - {{template "package/content/go" .}} - {{template "package/content/helm" .}} - {{template "package/content/maven" .}} - {{template "package/content/npm" .}} - {{template "package/content/nuget" .}} - {{template "package/content/pub" .}} - {{template "package/content/pypi" .}} - {{template "package/content/rpm" .}} - {{template "package/content/rubygems" .}} - {{template "package/content/swift" .}} - {{template "package/content/vagrant" .}} - - - {{ctx.Locale.Tr "packages.details"}} - - {{svg .PackageDescriptor.Package.Type.SVGName}} {{.PackageDescriptor.Package.Type.Name}} - {{if .HasRepositoryAccess}} - {{svg "octicon-repo"}} {{.PackageDescriptor.Repository.FullName}} - {{end}} - {{svg "octicon-calendar"}} {{DateUtils.TimeSince .PackageDescriptor.Version.CreatedUnix}} - {{svg "octicon-download"}} {{.PackageDescriptor.Version.DownloadCount}} - {{template "package/metadata/alpine" .}} - {{template "package/metadata/arch" .}} - {{template "package/metadata/cargo" .}} - {{template "package/metadata/chef" .}} - {{template "package/metadata/composer" .}} - {{template "package/metadata/conan" .}} - {{template "package/metadata/conda" .}} - {{template "package/metadata/container" .}} - {{template "package/metadata/cran" .}} - {{template "package/metadata/debian" .}} - {{template "package/metadata/generic" .}} - {{template "package/metadata/helm" .}} - {{template "package/metadata/maven" .}} - {{template "package/metadata/npm" .}} - {{template "package/metadata/nuget" .}} - {{template "package/metadata/pub" .}} - {{template "package/metadata/pypi" .}} - {{template "package/metadata/rpm" .}} - {{template "package/metadata/rubygems" .}} - {{template "package/metadata/swift" .}} - {{template "package/metadata/vagrant" .}} - {{if not (and (eq .PackageDescriptor.Package.Type "container") .PackageDescriptor.Metadata.Manifests)}} - {{svg "octicon-database"}} {{FileSize .PackageDescriptor.CalculateBlobSize}} - {{end}} - - {{if not (eq .PackageDescriptor.Package.Type "container")}} - - {{ctx.Locale.Tr "packages.assets"}} ({{len .PackageDescriptor.Files}}) - - {{range .PackageDescriptor.Files}} - - {{.File.Name}} - {{FileSize .Blob.Size}} - - {{end}} - - {{end}} - - {{ctx.Locale.Tr "packages.versions"}} ({{.TotalVersionCount}}) - {{ctx.Locale.Tr "packages.versions.view_all"}} - - {{range .LatestVersions}} - - {{.Version}} - {{DateUtils.AbsoluteShort .CreatedUnix}} - - {{end}} - - {{if or .CanWritePackages .HasRepositoryAccess}} - - - {{if .HasRepositoryAccess}} - {{svg "octicon-issue-opened"}} {{ctx.Locale.Tr "repo.issues"}} - {{end}} - {{if .CanWritePackages}} - {{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings"}} - {{end}} - - {{end}} + + {{template "user/overview/header" .}} + {{template "package/shared/view" .}} +{{end}} {{template "base/footer" .}} diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl index 79925e69cc..d6a335ef4b 100644 --- a/templates/projects/view.tmpl +++ b/templates/projects/view.tmpl @@ -13,7 +13,7 @@ "UserSearchList" $.Assignees "SelectedUserId" $.AssigneeID "TextFilterTitle" (ctx.Locale.Tr "repo.issues.filter_assignee") - "TextFilterMatchNone" (ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee") + "TextFilterMatchNone" (ctx.Locale.Tr "repo.issues.filter_assignee_no_assignee") "TextFilterMatchAny" (ctx.Locale.Tr "repo.issues.filter_assignee_any_assignee") }} diff --git a/templates/repo/actions/status.tmpl b/templates/repo/actions/status.tmpl index 64c2543302..f2020bc160 100644 --- a/templates/repo/actions/status.tmpl +++ b/templates/repo/actions/status.tmpl @@ -16,7 +16,7 @@ {{else if eq .status "blocked"}} {{svg "octicon-blocked" $size (printf "text yellow %s" $className)}} {{else if eq .status "running"}} - {{svg "octicon-meter" $size (printf "text yellow job-status-rotate %s" $className)}} + {{svg "octicon-meter" $size (printf "text yellow circular-spin %s" $className)}} {{else}}{{/*failure, unknown*/}} {{svg "octicon-x-circle-fill" $size (printf "text red %s" $className)}} {{end}} diff --git a/templates/repo/actions/workflow_dispatch.tmpl b/templates/repo/actions/workflow_dispatch.tmpl index 55fe122419..540bbe9162 100644 --- a/templates/repo/actions/workflow_dispatch.tmpl +++ b/templates/repo/actions/workflow_dispatch.tmpl @@ -10,7 +10,7 @@ {{ctx.Locale.Tr "actions.workflow.from_ref"}}: - + {{svg "octicon-git-branch" 14}} {{index .Branches 0}} diff --git a/templates/repo/blame.tmpl b/templates/repo/blame.tmpl index 05f79612bd..9596fe837a 100644 --- a/templates/repo/blame.tmpl +++ b/templates/repo/blame.tmpl @@ -10,7 +10,7 @@ {{end}} {{end}} - + {{template "repo/file_info" .}} diff --git a/templates/repo/branch/list.tmpl b/templates/repo/branch/list.tmpl index c0accf16fa..19797229bf 100644 --- a/templates/repo/branch/list.tmpl +++ b/templates/repo/branch/list.tmpl @@ -27,7 +27,7 @@ {{svg "octicon-copy" 14}} {{template "repo/commit_statuses" dict "Status" (index $.CommitStatus .DefaultBranchBranch.DBBranch.CommitID) "Statuses" (index $.CommitStatuses .DefaultBranchBranch.DBBranch.CommitID)}} - {{svg "octicon-git-commit" 16 "tw-mr-1"}}{{ShortSha .DefaultBranchBranch.DBBranch.CommitID}} · {{ctx.RenderUtils.RenderCommitMessage .DefaultBranchBranch.DBBranch.CommitMessage (.Repository.ComposeMetas ctx)}} · {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DefaultBranchBranch.DBBranch.CommitTime}}{{if .DefaultBranchBranch.DBBranch.Pusher}} {{template "shared/user/avatarlink" dict "user" .DefaultBranchBranch.DBBranch.Pusher}}{{template "shared/user/namelink" .DefaultBranchBranch.DBBranch.Pusher}}{{end}} + {{svg "octicon-git-commit" 16 "tw-mr-1"}}{{ShortSha .DefaultBranchBranch.DBBranch.CommitID}} · {{ctx.RenderUtils.RenderCommitMessage .DefaultBranchBranch.DBBranch.CommitMessage (.Repository.ComposeCommentMetas ctx)}} · {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DefaultBranchBranch.DBBranch.CommitTime}}{{if .DefaultBranchBranch.DBBranch.Pusher}} {{template "shared/user/avatarlink" dict "user" .DefaultBranchBranch.DBBranch.Pusher}}{{template "shared/user/namelink" .DefaultBranchBranch.DBBranch.Pusher}}{{end}} {{/* FIXME: here and below, the tw-overflow-visible is not quite right but it is still needed the moment: to show the important buttons when the width is narrow */}} @@ -103,7 +103,7 @@ {{svg "octicon-copy" 14}} {{template "repo/commit_statuses" dict "Status" (index $.CommitStatus .DBBranch.CommitID) "Statuses" (index $.CommitStatuses .DBBranch.CommitID)}} - {{svg "octicon-git-commit" 16 "tw-mr-1"}}{{ShortSha .DBBranch.CommitID}} · {{ctx.RenderUtils.RenderCommitMessage .DBBranch.CommitMessage ($.Repository.ComposeMetas ctx)}} · {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DBBranch.CommitTime}}{{if .DBBranch.Pusher}} {{template "shared/user/avatarlink" dict "user" .DBBranch.Pusher}} {{template "shared/user/namelink" .DBBranch.Pusher}}{{end}} + {{svg "octicon-git-commit" 16 "tw-mr-1"}}{{ShortSha .DBBranch.CommitID}} · {{ctx.RenderUtils.RenderCommitMessage .DBBranch.CommitMessage ($.Repository.ComposeCommentMetas ctx)}} · {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DBBranch.CommitTime}}{{if .DBBranch.Pusher}} {{template "shared/user/avatarlink" dict "user" .DBBranch.Pusher}} {{template "shared/user/namelink" .DBBranch.Pusher}}{{end}} {{end}} diff --git a/templates/repo/branch_dropdown.tmpl b/templates/repo/branch_dropdown.tmpl index f679b8744b..261d0ea409 100644 --- a/templates/repo/branch_dropdown.tmpl +++ b/templates/repo/branch_dropdown.tmpl @@ -46,7 +46,7 @@ Search "repo/branch_dropdown" in the template directory to find all occurrences. data-enable-feed="{{ctx.RootData.EnableFeed}}" > {{/* show dummy elements before Vue componment is mounted, this code must match the code in BranchTagSelector.vue */}} - + {{if not .DropdownFixedText}} diff --git a/templates/repo/clone_panel.tmpl b/templates/repo/clone_panel.tmpl index 2ed8f52fbe..0e3c13eaa2 100644 --- a/templates/repo/clone_panel.tmpl +++ b/templates/repo/clone_panel.tmpl @@ -1,6 +1,6 @@ {{svg "octicon-code" 16}} - Code + {{ctx.Locale.Tr "repo.code"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}} diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl index ff3dccd534..5639c87a82 100644 --- a/templates/repo/commit_page.tmpl +++ b/templates/repo/commit_page.tmpl @@ -5,7 +5,7 @@ - {{ctx.RenderUtils.RenderCommitMessage .Commit.Message ($.Repository.ComposeMetas ctx)}}{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses}} + {{ctx.RenderUtils.RenderCommitMessage .Commit.Message ($.Repository.ComposeCommentMetas ctx)}}{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses}} {{if not $.PageIsWiki}} @@ -122,7 +122,7 @@ {{end}} {{if IsMultilineCommitMessage .Commit.Message}} - {{ctx.RenderUtils.RenderCommitBody .Commit.Message ($.Repository.ComposeMetas ctx)}} + {{ctx.RenderUtils.RenderCommitBody .Commit.Message ($.Repository.ComposeCommentMetas ctx)}} {{end}} {{template "repo/commit_load_branches_and_tags" .}} diff --git a/templates/repo/commit_statuses.tmpl b/templates/repo/commit_statuses.tmpl index a6f75584a3..1bbfb33105 100644 --- a/templates/repo/commit_statuses.tmpl +++ b/templates/repo/commit_statuses.tmpl @@ -1,10 +1,10 @@ {{if .Statuses}} {{if and (eq (len .Statuses) 1) .Status.TargetURL}} - + {{template "repo/commit_status" .Status}} {{else}} - + {{template "repo/commit_status" .Status}} {{end}} diff --git a/templates/repo/commits_list.tmpl b/templates/repo/commits_list.tmpl index e1bd6b73ae..17c7240ee4 100644 --- a/templates/repo/commits_list.tmpl +++ b/templates/repo/commits_list.tmpl @@ -44,7 +44,7 @@ {{.Summary | ctx.RenderUtils.RenderEmoji}} {{else}} {{$commitLink:= printf "%s/commit/%s" $commitRepoLink (PathEscape .ID.String)}} - {{ctx.RenderUtils.RenderCommitMessageLinkSubject .Message $commitLink ($.Repository.ComposeMetas ctx)}} + {{ctx.RenderUtils.RenderCommitMessageLinkSubject .Message $commitLink ($.Repository.ComposeCommentMetas ctx)}} {{end}} {{if IsMultilineCommitMessage .Message}} @@ -52,7 +52,7 @@ {{end}} {{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}} {{if IsMultilineCommitMessage .Message}} - {{ctx.RenderUtils.RenderCommitBody .Message ($.Repository.ComposeMetas ctx)}} + {{ctx.RenderUtils.RenderCommitBody .Message ($.Repository.ComposeCommentMetas ctx)}} {{end}} {{if $.CommitsTagsMap}} {{range (index $.CommitsTagsMap .ID.String)}} @@ -70,15 +70,15 @@ {{/* at the moment, wiki doesn't support these "view" links like "view at history point" */}} {{if not $.PageIsWiki}} {{/* view single file diff */}} - {{if $.FileName}} + {{if $.FileTreePath}} {{svg "octicon-file-diff"}} {{end}} {{/* view at history point */}} {{$viewCommitLink := printf "%s/src/commit/%s" $commitRepoLink (PathEscape .ID.String)}} - {{if $.FileName}}{{$viewCommitLink = printf "%s/%s" $viewCommitLink (PathEscapeSegments $.FileName)}}{{end}} + {{if $.FileTreePath}}{{$viewCommitLink = printf "%s/%s" $viewCommitLink (PathEscapeSegments $.FileTreePath)}}{{end}} {{svg "octicon-file-code"}} {{end}} diff --git a/templates/repo/commits_list_small.tmpl b/templates/repo/commits_list_small.tmpl index 2acf7c58b8..b054ce19a5 100644 --- a/templates/repo/commits_list_small.tmpl +++ b/templates/repo/commits_list_small.tmpl @@ -15,7 +15,7 @@ {{$commitLink:= printf "%s/%s" $commitBaseLink (PathEscape .ID.String)}} - {{- ctx.RenderUtils.RenderCommitMessageLinkSubject .Message $commitLink ($.comment.Issue.PullRequest.BaseRepo.ComposeMetas ctx) -}} + {{- ctx.RenderUtils.RenderCommitMessageLinkSubject .Message $commitLink ($.comment.Issue.PullRequest.BaseRepo.ComposeCommentMetas ctx) -}} {{if IsMultilineCommitMessage .Message}} @@ -29,7 +29,7 @@ {{if IsMultilineCommitMessage .Message}} - {{- ctx.RenderUtils.RenderCommitBody .Message ($.comment.Issue.PullRequest.BaseRepo.ComposeMetas ctx) -}} + {{- ctx.RenderUtils.RenderCommitBody .Message ($.comment.Issue.PullRequest.BaseRepo.ComposeCommentMetas ctx) -}} {{end}} {{end}} diff --git a/templates/repo/create.tmpl b/templates/repo/create.tmpl index a90c26b423..ada7e0c092 100644 --- a/templates/repo/create.tmpl +++ b/templates/repo/create.tmpl @@ -12,23 +12,23 @@ {{ctx.Locale.Tr "repo.owner"}} - + - + {{svg "octicon-triangle-down" 14 "dropdown icon"}} - {{ctx.AvatarUtils.Avatar .SignedUser 28 "mini"}} - {{.SignedUser.ShortName 40}} + {{.SignedUser.ShortName 40}} {{range .Orgs}} - + {{ctx.AvatarUtils.Avatar . 28 "mini"}} - {{.ShortName 40}} + {{.ShortName 40}} {{end}} diff --git a/templates/repo/diff/compare.tmpl b/templates/repo/diff/compare.tmpl index 05cfffd2b7..0c9e3f0698 100644 --- a/templates/repo/diff/compare.tmpl +++ b/templates/repo/diff/compare.tmpl @@ -189,7 +189,7 @@ {{template "shared/issueicon" .}} - {{ctx.RenderUtils.RenderIssueTitle .PullRequest.Issue.Title ($.Repository.ComposeMetas ctx)}} + {{ctx.RenderUtils.RenderIssueTitle .PullRequest.Issue.Title ($.Repository.ComposeCommentMetas ctx)}} #{{.PullRequest.Issue.Index}} diff --git a/templates/repo/graph/commits.tmpl b/templates/repo/graph/commits.tmpl index 6af0ba1f0f..55a91ac195 100644 --- a/templates/repo/graph/commits.tmpl +++ b/templates/repo/graph/commits.tmpl @@ -8,7 +8,7 @@ {{template "repo/commit_sign_badge" dict "Commit" $commit.Commit "CommitBaseLink" (print $.RepoLink "/commit") "CommitSignVerification" $commit.Verification}} - {{ctx.RenderUtils.RenderCommitMessage $commit.Subject ($.Repository.ComposeMetas ctx)}} + {{ctx.RenderUtils.RenderCommitMessage $commit.Subject ($.Repository.ComposeCommentMetas ctx)}} diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index 8c2a0da8d0..c7c53b4f5d 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -25,6 +25,9 @@ {{svg "octicon-shield-lock" 18}} {{end}} {{end}} + {{if $.Permission.HasAnyUnitPublicAccess}} + {{ctx.Locale.Tr "repo.desc.public_access"}} + {{end}} {{if .IsTemplate}} {{ctx.Locale.Tr "repo.desc.template"}} {{svg "octicon-repo-template" 18}} @@ -208,7 +211,7 @@ {{end}} - {{if and (.Permission.CanReadAny ctx.Consts.RepoUnitTypePullRequests ctx.Consts.RepoUnitTypeIssues ctx.Consts.RepoUnitTypeReleases) (not .IsEmptyRepo)}} + {{if and (.Permission.CanReadAny ctx.Consts.RepoUnitTypePullRequests ctx.Consts.RepoUnitTypeIssues ctx.Consts.RepoUnitTypeReleases ctx.Consts.RepoUnitTypeCode) (not .IsEmptyRepo)}} {{svg "octicon-pulse"}} {{ctx.Locale.Tr "repo.activity"}} diff --git a/templates/repo/home_sidebar_top.tmpl b/templates/repo/home_sidebar_top.tmpl index 8d3d7c6f86..8c2089c839 100644 --- a/templates/repo/home_sidebar_top.tmpl +++ b/templates/repo/home_sidebar_top.tmpl @@ -45,7 +45,7 @@ {{end}} {{if .ReadmeExist}} - + {{svg "octicon-book"}} {{ctx.Locale.Tr "readme"}} {{end}} diff --git a/templates/repo/issue/branch_selector_field.tmpl b/templates/repo/issue/branch_selector_field.tmpl index 9183b7b46a..c8b67490ac 100644 --- a/templates/repo/issue/branch_selector_field.tmpl +++ b/templates/repo/issue/branch_selector_field.tmpl @@ -14,7 +14,7 @@ Still needs to figure out: */}} {{if and (not .Issue.IsPull) (not .PageIsComparePull)}} - diff --git a/templates/repo/issue/filter_list.tmpl b/templates/repo/issue/filter_list.tmpl index 58ca4a7c00..048b5f37b7 100644 --- a/templates/repo/issue/filter_list.tmpl +++ b/templates/repo/issue/filter_list.tmpl @@ -94,7 +94,7 @@ "UserSearchList" $.Assignees "SelectedUserId" $.AssigneeID "TextFilterTitle" (ctx.Locale.Tr "repo.issues.filter_assignee") - "TextFilterMatchNone" (ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee") + "TextFilterMatchNone" (ctx.Locale.Tr "repo.issues.filter_assignee_no_assignee") "TextFilterMatchAny" (ctx.Locale.Tr "repo.issues.filter_assignee_any_assignee") }} @@ -133,5 +133,11 @@ {{ctx.Locale.Tr "repo.issues.filter_sort.leastcomment"}} {{ctx.Locale.Tr "repo.issues.filter_sort.nearduedate"}} {{ctx.Locale.Tr "repo.issues.filter_sort.farduedate"}} + + {{ctx.Locale.Tr "repo.issues.filter_label"}} + {{range $scope := .ExclusiveLabelScopes}} + {{$sortType := (printf "scope-%s" $scope)}} + {{$scope}} + {{end}} diff --git a/templates/repo/issue/labels/label_edit_modal.tmpl b/templates/repo/issue/labels/label_edit_modal.tmpl index 527b7ff900..06c397ba8d 100644 --- a/templates/repo/issue/labels/label_edit_modal.tmpl +++ b/templates/repo/issue/labels/label_edit_modal.tmpl @@ -24,7 +24,13 @@ {{svg "octicon-alert"}} {{ctx.Locale.Tr "repo.issues.label_exclusive_warning"}} - + + + {{ctx.Locale.Tr "repo.issues.label_exclusive_order"}} + {{svg "octicon-info"}} + + + diff --git a/templates/repo/issue/labels/label_list.tmpl b/templates/repo/issue/labels/label_list.tmpl index cdbcc456f0..cc231971e0 100644 --- a/templates/repo/issue/labels/label_list.tmpl +++ b/templates/repo/issue/labels/label_list.tmpl @@ -50,6 +50,7 @@ data-label-id="{{.ID}}" data-label-name="{{.Name}}" data-label-color="{{.Color}}" data-label-exclusive="{{.Exclusive}}" data-label-is-archived="{{gt .ArchivedUnix 0}}" data-label-num-issues="{{.NumIssues}}" data-label-description="{{.Description}}" + data-label-exclusive-order="{{.ExclusiveOrder}}" >{{svg "octicon-pencil"}} {{ctx.Locale.Tr "repo.issues.label_edit"}} {{ctx.Locale.Tr "repository"}} - + {{.Repository.FullName}} diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl index dcc1f48c2c..a4be598540 100644 --- a/templates/repo/issue/view_title.tmpl +++ b/templates/repo/issue/view_title.tmpl @@ -13,7 +13,7 @@ {{$canEditIssueTitle := and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .Repository.IsArchived)}} - {{ctx.RenderUtils.RenderIssueTitle .Issue.Title ($.Repository.ComposeMetas ctx)}} + {{ctx.RenderUtils.RenderIssueTitle .Issue.Title ($.Repository.ComposeCommentMetas ctx)}} #{{.Issue.Index}} diff --git a/templates/repo/latest_commit.tmpl b/templates/repo/latest_commit.tmpl index 0341d60eb2..da457e423a 100644 --- a/templates/repo/latest_commit.tmpl +++ b/templates/repo/latest_commit.tmpl @@ -21,10 +21,10 @@ {{template "repo/commit_statuses" dict "Status" .LatestCommitStatus "Statuses" .LatestCommitStatuses}} {{$commitLink:= printf "%s/commit/%s" .RepoLink (PathEscape .LatestCommit.ID.String)}} - {{ctx.RenderUtils.RenderCommitMessageLinkSubject .LatestCommit.Message $commitLink ($.Repository.ComposeMetas ctx)}} + {{ctx.RenderUtils.RenderCommitMessageLinkSubject .LatestCommit.Message $commitLink ($.Repository.ComposeCommentMetas ctx)}} {{if IsMultilineCommitMessage .LatestCommit.Message}} ... - {{ctx.RenderUtils.RenderCommitBody .LatestCommit.Message ($.Repository.ComposeMetas ctx)}} + {{ctx.RenderUtils.RenderCommitBody .LatestCommit.Message ($.Repository.ComposeCommentMetas ctx)}} {{end}} {{end}} diff --git a/templates/repo/migrate/codebase.tmpl b/templates/repo/migrate/codebase.tmpl index d4ca269f02..f4de8ff236 100644 --- a/templates/repo/migrate/codebase.tmpl +++ b/templates/repo/migrate/codebase.tmpl @@ -60,22 +60,22 @@ {{ctx.Locale.Tr "repo.owner"}} - + - + {{ctx.AvatarUtils.Avatar .ContextUser 28 "mini"}} - {{.ContextUser.ShortName 40}} + {{.ContextUser.ShortName 40}} {{svg "octicon-triangle-down" 14 "dropdown icon"}} - + {{ctx.AvatarUtils.Avatar .SignedUser 28 "mini"}} - {{.SignedUser.ShortName 40}} + {{.SignedUser.ShortName 40}} {{range .Orgs}} - + {{ctx.AvatarUtils.Avatar . 28 "mini"}} - {{.ShortName 40}} + {{.ShortName 40}} {{end}} diff --git a/templates/repo/migrate/codecommit.tmpl b/templates/repo/migrate/codecommit.tmpl index 53ca7dda3d..275a2aef09 100644 --- a/templates/repo/migrate/codecommit.tmpl +++ b/templates/repo/migrate/codecommit.tmpl @@ -61,22 +61,22 @@ {{ctx.Locale.Tr "repo.owner"}} - + - + {{ctx.AvatarUtils.Avatar .ContextUser 28 "mini"}} - {{.ContextUser.ShortName 40}} + {{.ContextUser.ShortName 40}} {{svg "octicon-triangle-down" 14 "dropdown icon"}} - + {{ctx.AvatarUtils.Avatar .SignedUser 28 "mini"}} - {{.SignedUser.ShortName 40}} + {{.SignedUser.ShortName 40}} {{range .Orgs}} - + {{ctx.AvatarUtils.Avatar . 28 "mini"}} - {{.ShortName 40}} + {{.ShortName 40}} {{end}} diff --git a/templates/repo/migrate/git.tmpl b/templates/repo/migrate/git.tmpl index 3f447f76eb..41139d4fd6 100644 --- a/templates/repo/migrate/git.tmpl +++ b/templates/repo/migrate/git.tmpl @@ -34,22 +34,22 @@ {{ctx.Locale.Tr "repo.owner"}} - + - + {{ctx.AvatarUtils.Avatar .ContextUser}} - {{.ContextUser.ShortName 40}} + {{.ContextUser.ShortName 40}} {{svg "octicon-triangle-down" 14 "dropdown icon"}} - + {{ctx.AvatarUtils.Avatar .SignedUser}} - {{.SignedUser.ShortName 40}} + {{.SignedUser.ShortName 40}} {{range .Orgs}} - + {{ctx.AvatarUtils.Avatar .}} - {{.ShortName 40}} + {{.ShortName 40}} {{end}} diff --git a/templates/repo/migrate/gitbucket.tmpl b/templates/repo/migrate/gitbucket.tmpl index 559988951b..c89aa6c744 100644 --- a/templates/repo/migrate/gitbucket.tmpl +++ b/templates/repo/migrate/gitbucket.tmpl @@ -76,22 +76,22 @@ {{ctx.Locale.Tr "repo.owner"}} - + - + {{ctx.AvatarUtils.Avatar .ContextUser 28 "mini"}} - {{.ContextUser.ShortName 40}} + {{.ContextUser.ShortName 40}} {{svg "octicon-triangle-down" 14 "dropdown icon"}} - + {{ctx.AvatarUtils.Avatar .SignedUser 28 "mini"}} - {{.SignedUser.ShortName 40}} + {{.SignedUser.ShortName 40}} {{range .Orgs}} - + {{ctx.AvatarUtils.Avatar . 28 "mini"}} - {{.ShortName 40}} + {{.ShortName 40}} {{end}} diff --git a/templates/repo/migrate/gitea.tmpl b/templates/repo/migrate/gitea.tmpl index 3d692129d5..55cf61a77f 100644 --- a/templates/repo/migrate/gitea.tmpl +++ b/templates/repo/migrate/gitea.tmpl @@ -72,22 +72,22 @@ {{ctx.Locale.Tr "repo.owner"}} - + - + {{ctx.AvatarUtils.Avatar .ContextUser}} - {{.ContextUser.ShortName 40}} + {{.ContextUser.ShortName 40}} {{svg "octicon-triangle-down" 14 "dropdown icon"}} - + {{ctx.AvatarUtils.Avatar .SignedUser}} - {{.SignedUser.ShortName 40}} + {{.SignedUser.ShortName 40}} {{range .Orgs}} - + {{ctx.AvatarUtils.Avatar .}} - {{.ShortName 40}} + {{.ShortName 40}} {{end}} diff --git a/templates/repo/migrate/github.tmpl b/templates/repo/migrate/github.tmpl index 850a2b3c71..86b585b861 100644 --- a/templates/repo/migrate/github.tmpl +++ b/templates/repo/migrate/github.tmpl @@ -74,22 +74,22 @@ {{ctx.Locale.Tr "repo.owner"}} - + - + {{ctx.AvatarUtils.Avatar .ContextUser 28 "mini"}} - {{.ContextUser.ShortName 40}} + {{.ContextUser.ShortName 40}} {{svg "octicon-triangle-down" 14 "dropdown icon"}} - + {{ctx.AvatarUtils.Avatar .SignedUser 28 "mini"}} - {{.SignedUser.ShortName 40}} + {{.SignedUser.ShortName 40}} {{range .Orgs}} - + {{ctx.AvatarUtils.Avatar . 28 "mini"}} - {{.ShortName 40}} + {{.ShortName 40}} {{end}} diff --git a/templates/repo/migrate/gitlab.tmpl b/templates/repo/migrate/gitlab.tmpl index 9bafa122f1..edd69f0027 100644 --- a/templates/repo/migrate/gitlab.tmpl +++ b/templates/repo/migrate/gitlab.tmpl @@ -71,22 +71,22 @@ {{ctx.Locale.Tr "repo.owner"}} - + - + {{ctx.AvatarUtils.Avatar .ContextUser 28 "mini"}} - {{.ContextUser.ShortName 40}} + {{.ContextUser.ShortName 40}} {{svg "octicon-triangle-down" 14 "dropdown icon"}} - + {{ctx.AvatarUtils.Avatar .SignedUser 28 "mini"}} - {{.SignedUser.ShortName 40}} + {{.SignedUser.ShortName 40}} {{range .Orgs}} - + {{ctx.AvatarUtils.Avatar . 28 "mini"}} - {{.ShortName 40}} + {{.ShortName 40}} {{end}} diff --git a/templates/repo/migrate/gogs.tmpl b/templates/repo/migrate/gogs.tmpl index 0495ce67fb..9149c43239 100644 --- a/templates/repo/migrate/gogs.tmpl +++ b/templates/repo/migrate/gogs.tmpl @@ -74,22 +74,22 @@ {{ctx.Locale.Tr "repo.owner"}} - + - + {{ctx.AvatarUtils.Avatar .ContextUser}} - {{.ContextUser.ShortName 40}} + {{.ContextUser.ShortName 40}} {{svg "octicon-triangle-down" 14 "dropdown icon"}} - + {{ctx.AvatarUtils.Avatar .SignedUser}} - {{.SignedUser.ShortName 40}} + {{.SignedUser.ShortName 40}} {{range .Orgs}} - + {{ctx.AvatarUtils.Avatar .}} - {{.ShortName 40}} + {{.ShortName 40}} {{end}} diff --git a/templates/repo/migrate/onedev.tmpl b/templates/repo/migrate/onedev.tmpl index 55945154ef..a25ccf9438 100644 --- a/templates/repo/migrate/onedev.tmpl +++ b/templates/repo/migrate/onedev.tmpl @@ -61,22 +61,22 @@ {{ctx.Locale.Tr "repo.owner"}} - + - + {{ctx.AvatarUtils.Avatar .ContextUser 28 "mini"}} - {{.ContextUser.ShortName 40}} + {{.ContextUser.ShortName 40}} {{svg "octicon-triangle-down" 14 "dropdown icon"}} - + {{ctx.AvatarUtils.Avatar .SignedUser 28 "mini"}} - {{.SignedUser.ShortName 40}} + {{.SignedUser.ShortName 40}} {{range .Orgs}} - + {{ctx.AvatarUtils.Avatar . 28 "mini"}} - {{.ShortName 40}} + {{.ShortName 40}} {{end}} diff --git a/templates/repo/navbar.tmpl b/templates/repo/navbar.tmpl index b2471dc17e..e004c5254b 100644 --- a/templates/repo/navbar.tmpl +++ b/templates/repo/navbar.tmpl @@ -1,14 +1,19 @@ +{{$canReadCode := $.Permission.CanRead ctx.Consts.RepoUnitTypeCode}} + + {{/* the default activity page "pulse" could work with any permission: code, issue, pr, release*/}} {{ctx.Locale.Tr "repo.activity.navbar.pulse"}} - - {{ctx.Locale.Tr "repo.activity.navbar.contributors"}} - - - {{ctx.Locale.Tr "repo.activity.navbar.code_frequency"}} - - - {{ctx.Locale.Tr "repo.activity.navbar.recent_commits"}} - + {{if $canReadCode}} + + {{ctx.Locale.Tr "repo.activity.navbar.contributors"}} + + + {{ctx.Locale.Tr "repo.activity.navbar.code_frequency"}} + + + {{ctx.Locale.Tr "repo.activity.navbar.recent_commits"}} + + {{end}} diff --git a/templates/repo/pulls/fork.tmpl b/templates/repo/pulls/fork.tmpl index 879e5328f2..adf9e250c1 100644 --- a/templates/repo/pulls/fork.tmpl +++ b/templates/repo/pulls/fork.tmpl @@ -10,24 +10,24 @@ {{.CsrfTokenHtml}} {{ctx.Locale.Tr "repo.owner"}} - + - + {{ctx.AvatarUtils.Avatar .ContextUser 28 "mini"}} - {{.ContextUser.ShortName 40}} + {{.ContextUser.ShortName 40}} {{svg "octicon-triangle-down" 14 "dropdown icon"}} {{if .CanForkToUser}} - + {{ctx.AvatarUtils.Avatar .SignedUser 28 "mini"}} - {{.SignedUser.ShortName 40}} + {{.SignedUser.ShortName 40}} {{end}} {{range .Orgs}} - + {{ctx.AvatarUtils.Avatar . 28 "mini"}} - {{.ShortName 40}} + {{.ShortName 40}} {{end}} @@ -52,10 +52,10 @@ {{ctx.Locale.Tr "repo.fork_branch"}} - + - {{ctx.Locale.Tr "repo.all_branches"}} + {{ctx.Locale.Tr "repo.all_branches"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}} @@ -75,7 +75,7 @@ - + {{ctx.Locale.Tr "repo.fork_repo"}} diff --git a/templates/repo/settings/githooks.tmpl b/templates/repo/settings/githooks.tmpl index 1a603f9fe8..9b17af1406 100644 --- a/templates/repo/settings/githooks.tmpl +++ b/templates/repo/settings/githooks.tmpl @@ -4,18 +4,14 @@ {{ctx.Locale.Tr "repo.settings.githooks"}} - - - {{ctx.Locale.Tr "repo.settings.githooks_desc"}} - + + {{ctx.Locale.Tr "repo.settings.githooks_desc"}} {{range .Hooks}} - - {{svg "octicon-dot-fill" 22}} - {{.Name}} - - {{svg "octicon-pencil"}} - - + + {{svg "octicon-dot-fill" 22}} + {{.Name}} + {{svg "octicon-pencil"}} + {{end}} diff --git a/templates/repo/settings/navbar.tmpl b/templates/repo/settings/navbar.tmpl index 3e127ccbb3..3dd86d1f6a 100644 --- a/templates/repo/settings/navbar.tmpl +++ b/templates/repo/settings/navbar.tmpl @@ -4,6 +4,11 @@ {{ctx.Locale.Tr "repo.settings.options"}} + {{if or .Repository.IsPrivate .Permission.HasAnyUnitPublicAccess}} + + {{ctx.Locale.Tr "repo.settings.public_access"}} + + {{end}} {{ctx.Locale.Tr "repo.settings.collaboration"}} diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index aade734a1d..4d61604612 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -310,15 +310,6 @@ {{ctx.Locale.Tr "repo.code.desc"}} - - {{$unitCode := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeCode}} - {{ctx.Locale.Tr "repo.settings.default_permission_everyone_access"}} - - {{/* everyone access mode is different from others, none means it is unset and won't be applied */}} - {{ctx.Locale.Tr "settings.permission_not_set"}} - {{ctx.Locale.Tr "settings.permission_read"}} - - {{$isInternalWikiEnabled := .Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeWiki}} @@ -346,16 +337,6 @@ {{ctx.Locale.Tr "repo.settings.default_wiki_branch_name"}} - - {{$unitInternalWiki := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeWiki}} - {{ctx.Locale.Tr "repo.settings.default_permission_everyone_access"}} - - {{/* everyone access mode is different from others, none means it is unset and won't be applied */}} - {{ctx.Locale.Tr "settings.permission_not_set"}} - {{ctx.Locale.Tr "settings.permission_read"}} - {{ctx.Locale.Tr "settings.permission_write"}} - - @@ -391,15 +372,6 @@ - - {{$unitIssue := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeIssues}} - {{ctx.Locale.Tr "repo.settings.default_permission_everyone_access"}} - - {{/* everyone access mode is different from others, none means it is unset and won't be applied */}} - {{ctx.Locale.Tr "settings.permission_not_set"}} - {{ctx.Locale.Tr "settings.permission_read"}} - - {{if .Repository.CanEnableTimetracker}} @@ -830,7 +802,7 @@ {{end}} - {{if and .Repository.IsFork .Repository.Owner.CanCreateRepo}} + {{if .CanConvertFork}} {{ctx.Locale.Tr "repo.settings.convert_fork"}} @@ -944,7 +916,7 @@ {{end}} - {{if and .Repository.IsFork .Repository.Owner.CanCreateRepo}} + {{if .CanConvertFork}} {{ctx.Locale.Tr "repo.settings.convert_fork"}} diff --git a/templates/repo/settings/public_access.tmpl b/templates/repo/settings/public_access.tmpl new file mode 100644 index 0000000000..c1c198bcce --- /dev/null +++ b/templates/repo/settings/public_access.tmpl @@ -0,0 +1,54 @@ +{{template "repo/settings/layout_head" (dict "ctxData" . "pageClass" "repository settings")}} + + + {{ctx.Locale.Tr "repo.settings.public_access"}} + + + + {{ctx.Locale.Tr "repo.settings.public_access_desc"}} + + {{$paNotSet := "not-set"}} + {{$paAnonymousRead := "anonymous-read"}} + {{$paEveryoneRead := "everyone-read"}} + {{$paEveryoneWrite := "everyone-write"}} + + {{.CsrfTokenHtml}} + + + + {{ctx.Locale.Tr "units.unit"}} + {{ctx.Locale.Tr "settings.permission_not_set"}} + {{ctx.Locale.Tr "settings.permission_anonymous_read"}} + {{ctx.Locale.Tr "settings.permission_everyone_read"}} + {{ctx.Locale.Tr "settings.permission_everyone_write"}} + + + + {{range $ua := .RepoUnitPublicAccesses}} + + {{$ua.DisplayName}} + + + + + {{if SliceUtils.Contains $ua.PublicAccessTypes $paEveryoneWrite}} + + {{else}} + - + {{end}} + + + {{end}} + + + + {{ctx.Locale.Tr "repo.settings.public_access.docs.not_set"}} + {{ctx.Locale.Tr "repo.settings.public_access.docs.anonymous_read"}} + {{ctx.Locale.Tr "repo.settings.public_access.docs.everyone_read"}} + {{ctx.Locale.Tr "repo.settings.public_access.docs.everyone_write"}} + + {{ctx.Locale.Tr "repo.settings.update_settings"}} + + + +{{template "repo/settings/layout_footer" .}} diff --git a/templates/repo/settings/webhook/base_list.tmpl b/templates/repo/settings/webhook/base_list.tmpl index 36e75a7eb5..a63771b97e 100644 --- a/templates/repo/settings/webhook/base_list.tmpl +++ b/templates/repo/settings/webhook/base_list.tmpl @@ -8,14 +8,12 @@ - - - {{.Description}} - + + {{.Description}} {{range .Webhooks}} - - {{svg "octicon-dot-fill" 22}} - + + {{svg "octicon-dot-fill" 22}} + {{.URL}} {{svg "octicon-pencil"}} diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl index 51a8f7d501..f01adccadc 100644 --- a/templates/repo/view_file.tmpl +++ b/templates/repo/view_file.tmpl @@ -1,4 +1,4 @@ - + {{- if .FileError}} {{.FileError}} @@ -27,7 +27,7 @@ {{if .ReadmeInList}} {{svg "octicon-book" 16 "tw-mr-2"}} - {{.FileName}} + {{.FileTreePath}} {{else}} {{template "repo/file_info" .}} {{end}} @@ -78,7 +78,7 @@ {{ctx.Locale.Tr "repo.escape_control_characters"}} {{end}} {{if and .ReadmeInList .CanEditReadmeFile}} - {{svg "octicon-pencil"}} + {{svg "octicon-pencil"}} {{end}} diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl index 4745110dd2..572987a986 100644 --- a/templates/repo/view_list.tmpl +++ b/templates/repo/view_list.tmpl @@ -9,13 +9,14 @@ {{svg "octicon-file-directory-fill"}} .. {{end}} + {{$.FileIconPoolHTML}} {{range $item := .Files}} {{$entry := $item.Entry}} {{$commit := $item.Commit}} {{$submoduleFile := $item.SubmoduleFile}} - {{ctx.RenderUtils.RenderFileIcon $entry}} + {{index $.FileIcons $entry.Name}} {{if $entry.IsSubModule}} {{$submoduleLink := $submoduleFile.SubmoduleWebLink ctx}} {{if $submoduleLink}} @@ -46,7 +47,7 @@ {{if $commit}} {{$commitLink := printf "%s/commit/%s" $.RepoLink (PathEscape $commit.ID.String)}} - {{ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.Message $commitLink ($.Repository.ComposeMetas ctx)}} + {{ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.Message $commitLink ($.Repository.ComposeCommentMetas ctx)}} {{else}} … {{/* will be loaded again by LastCommitLoaderURL */}} {{end}} diff --git a/templates/shared/actions/runner_badge_flat-square.tmpl b/templates/shared/actions/runner_badge_flat-square.tmpl new file mode 100644 index 0000000000..cc1dc1e8f3 --- /dev/null +++ b/templates/shared/actions/runner_badge_flat-square.tmpl @@ -0,0 +1,15 @@ + + {{.Badge.Label.Text}}: {{.Badge.Message.Text}} + + + + + + {{.Badge.Label.Text}} + {{.Badge.Message.Text}} + + diff --git a/templates/shared/actions/runner_badge.tmpl b/templates/shared/actions/runner_badge_flat.tmpl similarity index 100% rename from templates/shared/actions/runner_badge.tmpl rename to templates/shared/actions/runner_badge_flat.tmpl diff --git a/templates/status/500.tmpl b/templates/status/500.tmpl index 198f1ea898..6dfa2d8a8c 100644 --- a/templates/status/500.tmpl +++ b/templates/status/500.tmpl @@ -38,7 +38,7 @@ {{if .ErrorMsg}} {{ctx.Locale.Tr "error.occurred"}}: - {{.ErrorMsg}} + {{.ErrorMsg}} {{end}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index de7c8dc6f0..244bc9f9c0 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -3339,6 +3339,104 @@ } } }, + "/packages/{owner}/{type}/{name}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "package" + ], + "summary": "Gets all versions of a package", + "operationId": "listPackageVersions", + "parameters": [ + { + "type": "string", + "description": "owner of the package", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "type of the package", + "name": "type", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the package", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "page number of results to return (1-based)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size of results", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "$ref": "#/responses/PackageList" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, + "/packages/{owner}/{type}/{name}/-/latest": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "package" + ], + "summary": "Gets the latest version of a package", + "operationId": "getLatestPackageVersion", + "parameters": [ + { + "type": "string", + "description": "owner of the package", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "type of the package", + "name": "type", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the package", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/Package" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, "/packages/{owner}/{type}/{name}/-/link/{repo_name}": { "post": { "tags": [ @@ -20411,10 +20509,20 @@ "type": "string", "x-go-name": "HTMLURL" }, + "last_author_date": { + "type": "string", + "format": "date-time", + "x-go-name": "LastAuthorDate" + }, "last_commit_sha": { "type": "string", "x-go-name": "LastCommitSHA" }, + "last_committer_date": { + "type": "string", + "format": "date-time", + "x-go-name": "LastCommitterDate" + }, "name": { "type": "string", "x-go-name": "Name" @@ -24376,10 +24484,6 @@ "description": "PackageFile represents a package file", "type": "object", "properties": { - "Size": { - "type": "integer", - "format": "int64" - }, "id": { "type": "integer", "format": "int64", @@ -24404,6 +24508,11 @@ "sha512": { "type": "string", "x-go-name": "HashSHA512" + }, + "size": { + "type": "integer", + "format": "int64", + "x-go-name": "Size" } }, "x-go-package": "code.gitea.io/gitea/modules/structs" diff --git a/templates/user/dashboard/feeds.tmpl b/templates/user/dashboard/feeds.tmpl index 47686dd442..366bb80b42 100644 --- a/templates/user/dashboard/feeds.tmpl +++ b/templates/user/dashboard/feeds.tmpl @@ -94,7 +94,7 @@ {{ShortSha .Sha1}} - {{ctx.RenderUtils.RenderCommitMessage .Message ($repo.ComposeMetas ctx)}} + {{ctx.RenderUtils.RenderCommitMessage .Message ($repo.ComposeCommentMetas ctx)}} {{end}} diff --git a/templates/user/dashboard/milestones.tmpl b/templates/user/dashboard/milestones.tmpl index d0fe0abbc9..15b1b9e30b 100644 --- a/templates/user/dashboard/milestones.tmpl +++ b/templates/user/dashboard/milestones.tmpl @@ -116,7 +116,7 @@ {{ctx.Locale.Tr "repo.milestones.closed" $closedDate}} {{else}} {{if .DeadlineString}} - + {{svg "octicon-calendar" 14}} {{DateUtils.AbsoluteShort (.DeadlineString|DateUtils.ParseLegacy)}} diff --git a/templates/user/dashboard/navbar.tmpl b/templates/user/dashboard/navbar.tmpl index a828bc90e4..2774ec5da2 100644 --- a/templates/user/dashboard/navbar.tmpl +++ b/templates/user/dashboard/navbar.tmpl @@ -2,32 +2,32 @@ - + {{ctx.AvatarUtils.Avatar .ContextUser 24 "tw-mr-1"}} - {{.ContextUser.ShortName 40}} + {{.ContextUser.ShortName 40}} {{if .ContextUser.Visibility.IsLimited}}{{ctx.Locale.Tr "org.settings.visibility.limited_shortname"}}{{end}} {{if .ContextUser.Visibility.IsPrivate}}{{ctx.Locale.Tr "org.settings.visibility.private_shortname"}}{{end}} {{svg "octicon-triangle-down" 14 "dropdown icon tw-ml-1"}} - + {{ctx.Locale.Tr "home.switch_dashboard_context"}} - - + + {{ctx.AvatarUtils.Avatar .SignedUser}} - {{.SignedUser.ShortName 40}} + {{.SignedUser.ShortName 40}} {{if .SignedUser.Visibility.IsLimited}}{{ctx.Locale.Tr "org.settings.visibility.limited_shortname"}}{{end}} {{if .SignedUser.Visibility.IsPrivate}}{{ctx.Locale.Tr "org.settings.visibility.private_shortname"}}{{end}} {{range .Orgs}} - + {{ctx.AvatarUtils.Avatar .}} - {{.ShortName 40}} + {{.ShortName 40}} {{if .Visibility.IsLimited}}{{ctx.Locale.Tr "org.settings.visibility.limited_shortname"}}{{end}} {{if .Visibility.IsPrivate}}{{ctx.Locale.Tr "org.settings.visibility.private_shortname"}}{{end}} diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index ece136be50..af631ca8fd 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -94,7 +94,7 @@ func TestE2e(t *testing.T) { onGiteaRun(t, func(*testing.T, *url.URL) { cmd := exec.Command(runArgs[0], runArgs...) cmd.Env = os.Environ() - cmd.Env = append(cmd.Env, fmt.Sprintf("GITEA_URL=%s", setting.AppURL)) + cmd.Env = append(cmd.Env, "GITEA_URL="+setting.AppURL) var stdout, stderr bytes.Buffer cmd.Stdout = &stdout diff --git a/tests/integration/actions_job_test.go b/tests/integration/actions_job_test.go index 89c93e7a75..4f4456a4e5 100644 --- a/tests/integration/actions_job_test.go +++ b/tests/integration/actions_job_test.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "reflect" + "strconv" "testing" "time" @@ -137,9 +138,9 @@ jobs: runner.registerAsRepoRunner(t, user2.Name, apiRepo.Name, "mock-runner", []string{"ubuntu-latest"}, false) for _, tc := range testCases { - t.Run(fmt.Sprintf("test %s", tc.treePath), func(t *testing.T) { + t.Run("test "+tc.treePath, func(t *testing.T) { // create the workflow file - opts := getWorkflowCreateFileOptions(user2, apiRepo.DefaultBranch, fmt.Sprintf("create %s", tc.treePath), tc.fileContent) + opts := getWorkflowCreateFileOptions(user2, apiRepo.DefaultBranch, "create "+tc.treePath, tc.fileContent) fileResp := createWorkflowFile(t, token, user2.Name, apiRepo.Name, tc.treePath, opts) // fetch and execute task @@ -320,8 +321,8 @@ jobs: runner.registerAsRepoRunner(t, user2.Name, apiRepo.Name, "mock-runner", []string{"ubuntu-latest"}, false) for _, tc := range testCases { - t.Run(fmt.Sprintf("test %s", tc.treePath), func(t *testing.T) { - opts := getWorkflowCreateFileOptions(user2, apiRepo.DefaultBranch, fmt.Sprintf("create %s", tc.treePath), tc.fileContent) + t.Run("test "+tc.treePath, func(t *testing.T) { + opts := getWorkflowCreateFileOptions(user2, apiRepo.DefaultBranch, "create "+tc.treePath, tc.fileContent) createWorkflowFile(t, token, user2.Name, apiRepo.Name, tc.treePath, opts) for i := 0; i < len(tc.outcomes); i++ { @@ -371,7 +372,7 @@ jobs: steps: - run: echo 'test the pull' ` - opts := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, fmt.Sprintf("create %s", wfTreePath), wfFileContent) + opts := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create "+wfTreePath, wfFileContent) createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, opts) // user2 creates a pull request doAPICreateFile(user2APICtx, "user2-patch.txt", &api.CreateFileOptions{ @@ -418,9 +419,9 @@ jobs: assert.Equal(t, actionRun.Repo.OwnerName+"/"+actionRun.Repo.Name, gtCtx["repository"].GetStringValue()) assert.Equal(t, actionRun.Repo.OwnerName, gtCtx["repository_owner"].GetStringValue()) assert.Equal(t, actionRun.Repo.HTMLURL(), gtCtx["repositoryUrl"].GetStringValue()) - assert.Equal(t, fmt.Sprint(actionRunJob.RunID), gtCtx["run_id"].GetStringValue()) - assert.Equal(t, fmt.Sprint(actionRun.Index), gtCtx["run_number"].GetStringValue()) - assert.Equal(t, fmt.Sprint(actionRunJob.Attempt), gtCtx["run_attempt"].GetStringValue()) + assert.Equal(t, strconv.FormatInt(actionRunJob.RunID, 10), gtCtx["run_id"].GetStringValue()) + assert.Equal(t, strconv.FormatInt(actionRun.Index, 10), gtCtx["run_number"].GetStringValue()) + assert.Equal(t, strconv.FormatInt(actionRunJob.Attempt, 10), gtCtx["run_attempt"].GetStringValue()) assert.Equal(t, "Actions", gtCtx["secret_source"].GetStringValue()) assert.Equal(t, setting.AppURL, gtCtx["server_url"].GetStringValue()) assert.Equal(t, actionRun.CommitSHA, gtCtx["sha"].GetStringValue()) @@ -463,7 +464,7 @@ jobs: steps: - run: echo 'test the pull' ` - opts := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, fmt.Sprintf("create %s", wfTreePath), wfFileContent) + opts := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create "+wfTreePath, wfFileContent) createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, opts) // user2 creates a pull request doAPICreateFile(user2APICtx, "user2-patch.txt", &api.CreateFileOptions{ @@ -510,9 +511,9 @@ jobs: assert.Equal(t, actionRun.Repo.OwnerName+"/"+actionRun.Repo.Name, gtCtx["repository"].GetStringValue()) assert.Equal(t, actionRun.Repo.OwnerName, gtCtx["repository_owner"].GetStringValue()) assert.Equal(t, actionRun.Repo.HTMLURL(), gtCtx["repositoryUrl"].GetStringValue()) - assert.Equal(t, fmt.Sprint(actionRunJob.RunID), gtCtx["run_id"].GetStringValue()) - assert.Equal(t, fmt.Sprint(actionRun.Index), gtCtx["run_number"].GetStringValue()) - assert.Equal(t, fmt.Sprint(actionRunJob.Attempt), gtCtx["run_attempt"].GetStringValue()) + assert.Equal(t, strconv.FormatInt(actionRunJob.RunID, 10), gtCtx["run_id"].GetStringValue()) + assert.Equal(t, strconv.FormatInt(actionRun.Index, 10), gtCtx["run_number"].GetStringValue()) + assert.Equal(t, strconv.FormatInt(actionRunJob.Attempt, 10), gtCtx["run_attempt"].GetStringValue()) assert.Equal(t, "Actions", gtCtx["secret_source"].GetStringValue()) assert.Equal(t, setting.AppURL, gtCtx["server_url"].GetStringValue()) assert.Equal(t, actionRun.CommitSHA, gtCtx["sha"].GetStringValue()) diff --git a/tests/integration/actions_log_test.go b/tests/integration/actions_log_test.go index cd20604b84..503bda97c9 100644 --- a/tests/integration/actions_log_test.go +++ b/tests/integration/actions_log_test.go @@ -156,7 +156,7 @@ jobs: runner.registerAsRepoRunner(t, user2.Name, repo.Name, "mock-runner", []string{"ubuntu-latest"}, false) for _, tc := range testCases { - t.Run(fmt.Sprintf("test %s", tc.treePath), func(t *testing.T) { + t.Run("test "+tc.treePath, func(t *testing.T) { var resetFunc func() if tc.zstdEnabled { resetFunc = test.MockVariableValue(&setting.Actions.LogCompression, "zstd") @@ -167,7 +167,7 @@ jobs: } // create the workflow file - opts := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, fmt.Sprintf("create %s", tc.treePath), tc.fileContent) + opts := getWorkflowCreateFileOptions(user2, repo.DefaultBranch, "create "+tc.treePath, tc.fileContent) createWorkflowFile(t, token, user2.Name, repo.Name, tc.treePath, opts) // fetch and execute tasks diff --git a/tests/integration/actions_runner_test.go b/tests/integration/actions_runner_test.go index ee92032e9f..75402929a1 100644 --- a/tests/integration/actions_runner_test.go +++ b/tests/integration/actions_runner_test.go @@ -37,7 +37,7 @@ func newMockRunner() *mockRunner { } func newMockRunnerClient(uuid, token string) *mockRunnerClient { - baseURL := fmt.Sprintf("%sapi/actions", setting.AppURL) + baseURL := setting.AppURL + "api/actions" opt := connect.WithInterceptors(connect.UnaryInterceptorFunc(func(next connect.UnaryFunc) connect.UnaryFunc { return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) { diff --git a/tests/integration/actions_trigger_test.go b/tests/integration/actions_trigger_test.go index 096f51dfc0..f576dc38ab 100644 --- a/tests/integration/actions_trigger_test.go +++ b/tests/integration/actions_trigger_test.go @@ -4,6 +4,7 @@ package integration import ( + "encoding/base64" "fmt" "net/http" "net/url" @@ -28,6 +29,7 @@ import ( api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" issue_service "code.gitea.io/gitea/services/issue" pull_service "code.gitea.io/gitea/services/pull" release_service "code.gitea.io/gitea/services/release" @@ -286,7 +288,7 @@ jobs: ContentReader: strings.NewReader("bar"), }, }, - Message: fmt.Sprintf("%s add bar", setting.Actions.SkipWorkflowStrings[0]), + Message: setting.Actions.SkipWorkflowStrings[0] + " add bar", OldBranch: "master", NewBranch: "master", Author: &files_service.IdentityOptions{ @@ -1368,3 +1370,81 @@ jobs: assert.Equal(t, "true", dispatchPayload.Inputs["myinput3"]) }) } + +func TestClosePullRequestWithPath(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + // user2 is the owner of the base repo + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + user2Token := getTokenForLoggedInUser(t, loginUser(t, user2.Name), auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + // user4 is the owner of the fork repo + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + user4Token := getTokenForLoggedInUser(t, loginUser(t, user4.Name), auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + // create the base repo + apiBaseRepo := createActionsTestRepo(t, user2Token, "close-pull-request-with-path", false) + baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID}) + user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository) + + // init the workflow + wfTreePath := ".gitea/workflows/pull.yml" + wfFileContent := `name: Pull Request +on: + pull_request: + types: + - closed + paths: + - 'app/**' +jobs: + echo: + runs-on: ubuntu-latest + steps: + - run: echo 'Hello World' +` + opts1 := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, "create "+wfTreePath, wfFileContent) + createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, opts1) + + // user4 forks the repo + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/forks", baseRepo.OwnerName, baseRepo.Name), + &api.CreateForkOption{ + Name: util.ToPointer("close-pull-request-with-path-fork"), + }).AddTokenAuth(user4Token) + resp := MakeRequest(t, req, http.StatusAccepted) + var apiForkRepo api.Repository + DecodeJSON(t, resp, &apiForkRepo) + forkRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiForkRepo.ID}) + user4APICtx := NewAPITestContext(t, user4.Name, forkRepo.Name, auth_model.AccessTokenScopeWriteRepository) + + // user4 creates a pull request to add file "app/main.go" + doAPICreateFile(user4APICtx, "app/main.go", &api.CreateFileOptions{ + FileOptions: api.FileOptions{ + NewBranchName: "user4/add-main", + Message: "create main.go", + Author: api.Identity{ + Name: user4.Name, + Email: user4.Email, + }, + Committer: api.Identity{ + Name: user4.Name, + Email: user4.Email, + }, + Dates: api.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }, + ContentBase64: base64.StdEncoding.EncodeToString([]byte("// main.go")), + })(t) + apiPull, err := doAPICreatePullRequest(user4APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, user4.Name+":user4/add-main")(t) + assert.NoError(t, err) + + doAPIMergePullRequest(user2APICtx, baseRepo.OwnerName, baseRepo.Name, apiPull.Index)(t) + + pullRequest := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID}) + + // load and compare ActionRun + assert.Equal(t, 1, unittest.GetCount(t, &actions_model.ActionRun{RepoID: baseRepo.ID})) + actionRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{RepoID: baseRepo.ID}) + assert.Equal(t, actions_module.GithubEventPullRequest, actionRun.TriggerEvent) + assert.Equal(t, pullRequest.MergedCommitID, actionRun.CommitSHA) + }) +} diff --git a/tests/integration/api_activitypub_person_test.go b/tests/integration/api_activitypub_person_test.go index 17d628a483..9bb1f2736e 100644 --- a/tests/integration/api_activitypub_person_test.go +++ b/tests/integration/api_activitypub_person_test.go @@ -72,10 +72,10 @@ func TestActivityPubPerson(t *testing.T) { ctx := t.Context() user1, err := user_model.GetUserByName(ctx, username1) assert.NoError(t, err) - user1url := fmt.Sprintf("%s/api/v1/activitypub/user-id/1#main-key", srv.URL) + user1url := srv.URL + "/api/v1/activitypub/user-id/1#main-key" c, err := activitypub.NewClient(db.DefaultContext, user1, user1url) assert.NoError(t, err) - user2inboxurl := fmt.Sprintf("%s/api/v1/activitypub/user-id/2/inbox", srv.URL) + user2inboxurl := srv.URL + "/api/v1/activitypub/user-id/2/inbox" // Signed request succeeds resp, err := c.Post([]byte{}, user2inboxurl) diff --git a/tests/integration/api_admin_test.go b/tests/integration/api_admin_test.go index b42f05fc55..d28a103e59 100644 --- a/tests/integration/api_admin_test.go +++ b/tests/integration/api_admin_test.go @@ -88,7 +88,7 @@ func TestAPISudoUser(t *testing.T) { normalUsername := "user2" token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadUser) - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user?sudo=%s", normalUsername)). + req := NewRequest(t, "GET", "/api/v1/user?sudo="+normalUsername). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) var user api.User @@ -103,7 +103,7 @@ func TestAPISudoUserForbidden(t *testing.T) { normalUsername := "user2" token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadAdmin) - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user?sudo=%s", adminUsername)). + req := NewRequest(t, "GET", "/api/v1/user?sudo="+adminUsername). AddTokenAuth(token) MakeRequest(t, req, http.StatusForbidden) } @@ -193,7 +193,7 @@ func TestAPIEditUser(t *testing.T) { defer tests.PrepareTestEnv(t)() adminUsername := "user1" token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) - urlStr := fmt.Sprintf("/api/v1/admin/users/%s", "user2") + urlStr := "/api/v1/admin/users/" + "user2" fullNameToChange := "Full Name User 2" req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{ @@ -217,7 +217,7 @@ func TestAPIEditUser(t *testing.T) { errMap := make(map[string]any) json.Unmarshal(resp.Body.Bytes(), &errMap) - assert.EqualValues(t, "e-mail invalid [email: ]", errMap["message"].(string)) + assert.Equal(t, "e-mail invalid [email: ]", errMap["message"].(string)) user2 = unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: "user2"}) assert.False(t, user2.IsRestricted) @@ -374,7 +374,7 @@ func TestAPIEditUser_NotAllowedEmailDomain(t *testing.T) { adminUsername := "user1" token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) - urlStr := fmt.Sprintf("/api/v1/admin/users/%s", "user2") + urlStr := "/api/v1/admin/users/" + "user2" newEmail := "user2@example1.com" req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ diff --git a/tests/integration/api_branch_test.go b/tests/integration/api_branch_test.go index d64dd97f93..16e1f2812e 100644 --- a/tests/integration/api_branch_test.go +++ b/tests/integration/api_branch_test.go @@ -24,13 +24,13 @@ func testAPIGetBranch(t *testing.T, branchName string, exists bool) { AddTokenAuth(token) resp := MakeRequest(t, req, NoExpectedStatus) if !exists { - assert.EqualValues(t, http.StatusNotFound, resp.Code) + assert.Equal(t, http.StatusNotFound, resp.Code) return } - assert.EqualValues(t, http.StatusOK, resp.Code) + assert.Equal(t, http.StatusOK, resp.Code) var branch api.Branch DecodeJSON(t, resp, &branch) - assert.EqualValues(t, branchName, branch.Name) + assert.Equal(t, branchName, branch.Name) assert.True(t, branch.UserCanPush) assert.True(t, branch.UserCanMerge) } @@ -44,7 +44,7 @@ func testAPIGetBranchProtection(t *testing.T, branchName string, expectedHTTPSta if resp.Code == http.StatusOK { var branchProtection api.BranchProtection DecodeJSON(t, resp, &branchProtection) - assert.EqualValues(t, branchName, branchProtection.RuleName) + assert.Equal(t, branchName, branchProtection.RuleName) return &branchProtection } return nil @@ -60,7 +60,7 @@ func testAPICreateBranchProtection(t *testing.T, branchName string, expectedPrio if resp.Code == http.StatusCreated { var branchProtection api.BranchProtection DecodeJSON(t, resp, &branchProtection) - assert.EqualValues(t, branchName, branchProtection.RuleName) + assert.Equal(t, branchName, branchProtection.RuleName) assert.EqualValues(t, expectedPriority, branchProtection.Priority) } } @@ -74,7 +74,7 @@ func testAPIEditBranchProtection(t *testing.T, branchName string, body *api.Bran if resp.Code == http.StatusOK { var branchProtection api.BranchProtection DecodeJSON(t, resp, &branchProtection) - assert.EqualValues(t, branchName, branchProtection.RuleName) + assert.Equal(t, branchName, branchProtection.RuleName) } } @@ -181,7 +181,7 @@ func testAPICreateBranch(t testing.TB, session *TestSession, user, repo, oldBran DecodeJSON(t, resp, &branch) if resp.Result().StatusCode == http.StatusCreated { - assert.EqualValues(t, newBranch, branch.Name) + assert.Equal(t, newBranch, branch.Name) } return resp.Result().StatusCode == status @@ -303,7 +303,7 @@ func TestAPICreateBranchWithSyncBranches(t *testing.T) { RepoID: 1, }) assert.NoError(t, err) - assert.Len(t, branches, 4) + assert.Len(t, branches, 6) // make a broke repository with no branch on database _, err = db.DeleteByBean(db.DefaultContext, git_model.Branch{RepoID: 1}) @@ -320,7 +320,7 @@ func TestAPICreateBranchWithSyncBranches(t *testing.T) { RepoID: 1, }) assert.NoError(t, err) - assert.Len(t, branches, 5) + assert.Len(t, branches, 7) branches, err = db.Find[git_model.Branch](db.DefaultContext, git_model.FindBranchOptions{ RepoID: 1, diff --git a/tests/integration/api_comment_test.go b/tests/integration/api_comment_test.go index 255b8332b2..9842c358f6 100644 --- a/tests/integration/api_comment_test.go +++ b/tests/integration/api_comment_test.go @@ -106,7 +106,7 @@ func TestAPICreateComment(t *testing.T) { var updatedComment api.Comment DecodeJSON(t, resp, &updatedComment) - assert.EqualValues(t, commentBody, updatedComment.Body) + assert.Equal(t, commentBody, updatedComment.Body) unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: updatedComment.ID, IssueID: issue.ID, Content: commentBody}) t.Run("BlockedByRepoOwner", func(t *testing.T) { @@ -174,7 +174,7 @@ func TestAPIGetSystemUserComment(t *testing.T) { user_model.NewGhostUser(), user_model.NewActionsUser(), } { - body := fmt.Sprintf("Hello %s", systemUser.Name) + body := "Hello " + systemUser.Name comment, err := issues_model.CreateComment(db.DefaultContext, &issues_model.CreateCommentOptions{ Type: issues_model.CommentTypeComment, Doer: systemUser, @@ -233,8 +233,8 @@ func TestAPIEditComment(t *testing.T) { var updatedComment api.Comment DecodeJSON(t, resp, &updatedComment) - assert.EqualValues(t, comment.ID, updatedComment.ID) - assert.EqualValues(t, newCommentBody, updatedComment.Body) + assert.Equal(t, comment.ID, updatedComment.ID) + assert.Equal(t, newCommentBody, updatedComment.Body) unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: comment.ID, IssueID: issue.ID, Content: newCommentBody}) } diff --git a/tests/integration/api_fork_test.go b/tests/integration/api_fork_test.go index 69f37f4574..2837c3b93e 100644 --- a/tests/integration/api_fork_test.go +++ b/tests/integration/api_fork_test.go @@ -34,7 +34,7 @@ func TestAPIForkListLimitedAndPrivateRepos(t *testing.T) { // fork into a limited org limitedOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 22}) - assert.EqualValues(t, api.VisibleTypeLimited, limitedOrg.Visibility) + assert.Equal(t, api.VisibleTypeLimited, limitedOrg.Visibility) ownerTeam1, err := org_model.OrgFromUser(limitedOrg).GetOwnerTeam(db.DefaultContext) assert.NoError(t, err) @@ -49,7 +49,7 @@ func TestAPIForkListLimitedAndPrivateRepos(t *testing.T) { user4Sess := loginUser(t, "user4") user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user4"}) privateOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 23}) - assert.EqualValues(t, api.VisibleTypePrivate, privateOrg.Visibility) + assert.Equal(t, api.VisibleTypePrivate, privateOrg.Visibility) ownerTeam2, err := org_model.OrgFromUser(privateOrg).GetOwnerTeam(db.DefaultContext) assert.NoError(t, err) @@ -70,7 +70,7 @@ func TestAPIForkListLimitedAndPrivateRepos(t *testing.T) { DecodeJSON(t, resp, &forks) assert.Empty(t, forks) - assert.EqualValues(t, "0", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "0", resp.Header().Get("X-Total-Count")) }) t.Run("Logged in", func(t *testing.T) { @@ -83,7 +83,7 @@ func TestAPIForkListLimitedAndPrivateRepos(t *testing.T) { DecodeJSON(t, resp, &forks) assert.Len(t, forks, 2) - assert.EqualValues(t, "2", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "2", resp.Header().Get("X-Total-Count")) assert.NoError(t, org_service.AddTeamMember(db.DefaultContext, ownerTeam2, user1)) @@ -94,7 +94,7 @@ func TestAPIForkListLimitedAndPrivateRepos(t *testing.T) { DecodeJSON(t, resp, &forks) assert.Len(t, forks, 2) - assert.EqualValues(t, "2", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "2", resp.Header().Get("X-Total-Count")) }) } @@ -121,7 +121,7 @@ func TestGetPrivateReposForks(t *testing.T) { forks := []*api.Repository{} DecodeJSON(t, resp, &forks) assert.Len(t, forks, 1) - assert.EqualValues(t, "1", resp.Header().Get("X-Total-Count")) - assert.EqualValues(t, "forked-repo", forks[0].Name) - assert.EqualValues(t, privateOrg.Name, forks[0].Owner.UserName) + assert.Equal(t, "1", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "forked-repo", forks[0].Name) + assert.Equal(t, privateOrg.Name, forks[0].Owner.UserName) } diff --git a/tests/integration/api_gitignore_templates_test.go b/tests/integration/api_gitignore_templates_test.go index c58f5eebfe..1c56d51344 100644 --- a/tests/integration/api_gitignore_templates_test.go +++ b/tests/integration/api_gitignore_templates_test.go @@ -4,7 +4,6 @@ package integration import ( - "fmt" "net/http" "testing" @@ -38,7 +37,7 @@ func TestAPIGetGitignoreTemplateInfo(t *testing.T) { // Use the first template for the test templateName := repo_module.Gitignores[0] - urlStr := fmt.Sprintf("/api/v1/gitignore/templates/%s", templateName) + urlStr := "/api/v1/gitignore/templates/" + templateName req := NewRequest(t, "GET", urlStr) resp := MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/api_gpg_keys_test.go b/tests/integration/api_gpg_keys_test.go index ec0dafc2d6..edfb9e6eca 100644 --- a/tests/integration/api_gpg_keys_test.go +++ b/tests/integration/api_gpg_keys_test.go @@ -86,13 +86,13 @@ func TestGPGKeys(t *testing.T) { assert.Len(t, keys, 1) primaryKey1 := keys[0] // Primary key 1 - assert.EqualValues(t, "38EA3BCED732982C", primaryKey1.KeyID) + assert.Equal(t, "38EA3BCED732982C", primaryKey1.KeyID) assert.Len(t, primaryKey1.Emails, 1) - assert.EqualValues(t, "user2@example.com", primaryKey1.Emails[0].Email) + assert.Equal(t, "user2@example.com", primaryKey1.Emails[0].Email) assert.True(t, primaryKey1.Emails[0].Verified) subKey := primaryKey1.SubsKey[0] // Subkey of 38EA3BCED732982C - assert.EqualValues(t, "70D7C694D17D03AD", subKey.KeyID) + assert.Equal(t, "70D7C694D17D03AD", subKey.KeyID) assert.Empty(t, subKey.Emails) var key api.GPGKey @@ -100,16 +100,16 @@ func TestGPGKeys(t *testing.T) { AddTokenAuth(tokenWithGPGKeyScope) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &key) - assert.EqualValues(t, "38EA3BCED732982C", key.KeyID) + assert.Equal(t, "38EA3BCED732982C", key.KeyID) assert.Len(t, key.Emails, 1) - assert.EqualValues(t, "user2@example.com", key.Emails[0].Email) + assert.Equal(t, "user2@example.com", key.Emails[0].Email) assert.True(t, key.Emails[0].Verified) req = NewRequest(t, "GET", "/api/v1/user/gpg_keys/"+strconv.FormatInt(subKey.ID, 10)). // Subkey of 38EA3BCED732982C AddTokenAuth(tokenWithGPGKeyScope) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &key) - assert.EqualValues(t, "70D7C694D17D03AD", key.KeyID) + assert.Equal(t, "70D7C694D17D03AD", key.KeyID) assert.Empty(t, key.Emails) }) diff --git a/tests/integration/api_helper_for_declarative_test.go b/tests/integration/api_helper_for_declarative_test.go index f3a595540f..083535a9a5 100644 --- a/tests/integration/api_helper_for_declarative_test.go +++ b/tests/integration/api_helper_for_declarative_test.go @@ -276,7 +276,7 @@ func doAPIMergePullRequest(ctx APITestContext, owner, repo string, index int64) } err := api.APIError{} DecodeJSON(t, resp, &err) - assert.EqualValues(t, "Please try again later", err.Message) + assert.Equal(t, "Please try again later", err.Message) queue.GetManager().FlushAll(t.Context(), 5*time.Second) <-time.After(1 * time.Second) } @@ -286,7 +286,7 @@ func doAPIMergePullRequest(ctx APITestContext, owner, repo string, index int64) expected = http.StatusOK } - if !assert.EqualValues(t, expected, resp.Code, + if !assert.Equal(t, expected, resp.Code, "Request: %s %s", req.Method, req.URL.String()) { logUnexpectedResponse(t, resp) } diff --git a/tests/integration/api_issue_label_test.go b/tests/integration/api_issue_label_test.go index c9cdd46b9a..4324fd37d9 100644 --- a/tests/integration/api_issue_label_test.go +++ b/tests/integration/api_issue_label_test.go @@ -38,8 +38,8 @@ func TestAPIModifyLabels(t *testing.T) { apiLabel := new(api.Label) DecodeJSON(t, resp, &apiLabel) dbLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: apiLabel.ID, RepoID: repo.ID}) - assert.EqualValues(t, dbLabel.Name, apiLabel.Name) - assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) + assert.Equal(t, dbLabel.Name, apiLabel.Name) + assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ Name: "TestL 2", @@ -67,7 +67,7 @@ func TestAPIModifyLabels(t *testing.T) { AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiLabel) - assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) + assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) // EditLabel newName := "LabelNewName" @@ -79,7 +79,7 @@ func TestAPIModifyLabels(t *testing.T) { }).AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiLabel) - assert.EqualValues(t, newColor, apiLabel.Color) + assert.Equal(t, newColor, apiLabel.Color) req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ Color: &newColorWrong, }).AddTokenAuth(token) @@ -165,7 +165,7 @@ func TestAPIReplaceIssueLabels(t *testing.T) { var apiLabels []*api.Label DecodeJSON(t, resp, &apiLabels) if assert.Len(t, apiLabels, 1) { - assert.EqualValues(t, label.ID, apiLabels[0].ID) + assert.Equal(t, label.ID, apiLabels[0].ID) } unittest.AssertCount(t, &issues_model.IssueLabel{IssueID: issue.ID}, 1) @@ -191,7 +191,7 @@ func TestAPIReplaceIssueLabelsWithLabelNames(t *testing.T) { var apiLabels []*api.Label DecodeJSON(t, resp, &apiLabels) if assert.Len(t, apiLabels, 1) { - assert.EqualValues(t, label.Name, apiLabels[0].Name) + assert.Equal(t, label.Name, apiLabels[0].Name) } } @@ -215,8 +215,8 @@ func TestAPIModifyOrgLabels(t *testing.T) { apiLabel := new(api.Label) DecodeJSON(t, resp, &apiLabel) dbLabel := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: apiLabel.ID, OrgID: owner.ID}) - assert.EqualValues(t, dbLabel.Name, apiLabel.Name) - assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) + assert.Equal(t, dbLabel.Name, apiLabel.Name) + assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{ Name: "TestL 2", @@ -244,7 +244,7 @@ func TestAPIModifyOrgLabels(t *testing.T) { AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiLabel) - assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) + assert.Equal(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color) // EditLabel newName := "LabelNewName" @@ -256,7 +256,7 @@ func TestAPIModifyOrgLabels(t *testing.T) { }).AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiLabel) - assert.EqualValues(t, newColor, apiLabel.Color) + assert.Equal(t, newColor, apiLabel.Color) req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{ Color: &newColorWrong, }).AddTokenAuth(token) diff --git a/tests/integration/api_issue_milestone_test.go b/tests/integration/api_issue_milestone_test.go index 2d00752302..1196c8d358 100644 --- a/tests/integration/api_issue_milestone_test.go +++ b/tests/integration/api_issue_milestone_test.go @@ -73,7 +73,7 @@ func TestAPIIssuesMilestone(t *testing.T) { AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiMilestone) - assert.EqualValues(t, apiMilestones[2], apiMilestone) + assert.Equal(t, apiMilestones[2], apiMilestone) req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/%s/milestones?state=%s&name=%s", owner.Name, repo.Name, "all", "milestone2")). AddTokenAuth(token) diff --git a/tests/integration/api_issue_stopwatch_test.go b/tests/integration/api_issue_stopwatch_test.go index 4765787e6f..3606d9a228 100644 --- a/tests/integration/api_issue_stopwatch_test.go +++ b/tests/integration/api_issue_stopwatch_test.go @@ -35,11 +35,11 @@ func TestAPIListStopWatches(t *testing.T) { stopwatch := unittest.AssertExistsAndLoadBean(t, &issues_model.Stopwatch{UserID: owner.ID}) issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: stopwatch.IssueID}) if assert.Len(t, apiWatches, 1) { - assert.EqualValues(t, stopwatch.CreatedUnix.AsTime().Unix(), apiWatches[0].Created.Unix()) - assert.EqualValues(t, issue.Index, apiWatches[0].IssueIndex) - assert.EqualValues(t, issue.Title, apiWatches[0].IssueTitle) - assert.EqualValues(t, repo.Name, apiWatches[0].RepoName) - assert.EqualValues(t, repo.OwnerName, apiWatches[0].RepoOwnerName) + assert.Equal(t, stopwatch.CreatedUnix.AsTime().Unix(), apiWatches[0].Created.Unix()) + assert.Equal(t, issue.Index, apiWatches[0].IssueIndex) + assert.Equal(t, issue.Title, apiWatches[0].IssueTitle) + assert.Equal(t, repo.Name, apiWatches[0].RepoName) + assert.Equal(t, repo.OwnerName, apiWatches[0].RepoOwnerName) assert.Positive(t, apiWatches[0].Seconds) } } diff --git a/tests/integration/api_issue_subscription_test.go b/tests/integration/api_issue_subscription_test.go index 7a716301c4..74ba171c01 100644 --- a/tests/integration/api_issue_subscription_test.go +++ b/tests/integration/api_issue_subscription_test.go @@ -43,11 +43,11 @@ func TestAPIIssueSubscriptions(t *testing.T) { wi := new(api.WatchInfo) DecodeJSON(t, resp, wi) - assert.EqualValues(t, isWatching, wi.Subscribed) - assert.EqualValues(t, !isWatching, wi.Ignored) - assert.EqualValues(t, issue.APIURL(db.DefaultContext)+"/subscriptions", wi.URL) + assert.Equal(t, isWatching, wi.Subscribed) + assert.Equal(t, !isWatching, wi.Ignored) + assert.Equal(t, issue.APIURL(db.DefaultContext)+"/subscriptions", wi.URL) assert.EqualValues(t, issue.CreatedUnix, wi.CreatedAt.Unix()) - assert.EqualValues(t, issueRepo.APIURL(), wi.RepositoryURL) + assert.Equal(t, issueRepo.APIURL(), wi.RepositoryURL) } testSubscription(issue1, true) diff --git a/tests/integration/api_issue_test.go b/tests/integration/api_issue_test.go index d8394a33d9..e035f7200b 100644 --- a/tests/integration/api_issue_test.go +++ b/tests/integration/api_issue_test.go @@ -313,7 +313,7 @@ func TestAPISearchIssues(t *testing.T) { req = NewRequest(t, "GET", link.String()).AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiIssues) - assert.EqualValues(t, "22", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "22", resp.Header().Get("X-Total-Count")) assert.Len(t, apiIssues, 20) query.Add("limit", "10") @@ -321,7 +321,7 @@ func TestAPISearchIssues(t *testing.T) { req = NewRequest(t, "GET", link.String()).AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiIssues) - assert.EqualValues(t, "22", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "22", resp.Header().Get("X-Total-Count")) assert.Len(t, apiIssues, 10) query = url.Values{"assigned": {"true"}, "state": {"all"}} diff --git a/tests/integration/api_issue_tracked_time_test.go b/tests/integration/api_issue_tracked_time_test.go index fd2c452b20..bd562b602e 100644 --- a/tests/integration/api_issue_tracked_time_test.go +++ b/tests/integration/api_issue_tracked_time_test.go @@ -41,8 +41,8 @@ func TestAPIGetTrackedTimes(t *testing.T) { for i, time := range expect { assert.Equal(t, time.ID, apiTimes[i].ID) - assert.EqualValues(t, issue2.Title, apiTimes[i].Issue.Title) - assert.EqualValues(t, issue2.ID, apiTimes[i].IssueID) + assert.Equal(t, issue2.Title, apiTimes[i].Issue.Title) + assert.Equal(t, issue2.ID, apiTimes[i].IssueID) assert.Equal(t, time.Created.Unix(), apiTimes[i].Created.Unix()) assert.Equal(t, time.Time, apiTimes[i].Time) user, err := user_model.GetUserByID(db.DefaultContext, time.UserID) @@ -125,6 +125,6 @@ func TestAPIAddTrackedTimes(t *testing.T) { DecodeJSON(t, resp, &apiNewTime) assert.EqualValues(t, 33, apiNewTime.Time) - assert.EqualValues(t, user2.ID, apiNewTime.UserID) + assert.Equal(t, user2.ID, apiNewTime.UserID) assert.EqualValues(t, 947688818, apiNewTime.Created.Unix()) } diff --git a/tests/integration/api_keys_test.go b/tests/integration/api_keys_test.go index 2276b955cf..3162051acc 100644 --- a/tests/integration/api_keys_test.go +++ b/tests/integration/api_keys_test.go @@ -143,7 +143,7 @@ func TestCreateUserKey(t *testing.T) { }) // Search by fingerprint - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/keys?fingerprint=%s", newPublicKey.Fingerprint)). + req = NewRequest(t, "GET", "/api/v1/user/keys?fingerprint="+newPublicKey.Fingerprint). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) @@ -183,7 +183,7 @@ func TestCreateUserKey(t *testing.T) { token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeWriteUser) // Should find key even though not ours, but we shouldn't know whose it is - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/keys?fingerprint=%s", newPublicKey.Fingerprint)). + req = NewRequest(t, "GET", "/api/v1/user/keys?fingerprint="+newPublicKey.Fingerprint). AddTokenAuth(token2) resp = MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/api_label_templates_test.go b/tests/integration/api_label_templates_test.go index 007e979011..3e637daba6 100644 --- a/tests/integration/api_label_templates_test.go +++ b/tests/integration/api_label_templates_test.go @@ -4,7 +4,6 @@ package integration import ( - "fmt" "net/http" "net/url" "strings" @@ -42,7 +41,7 @@ func TestAPIGetLabelTemplateInfo(t *testing.T) { // Use the first template for the test templateName := repo_module.LabelTemplateFiles[0].DisplayName - urlStr := fmt.Sprintf("/api/v1/label/templates/%s", url.PathEscape(templateName)) + urlStr := "/api/v1/label/templates/" + url.PathEscape(templateName) req := NewRequest(t, "GET", urlStr) resp := MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/api_license_templates_test.go b/tests/integration/api_license_templates_test.go index e12aab7c2c..52e240f9a7 100644 --- a/tests/integration/api_license_templates_test.go +++ b/tests/integration/api_license_templates_test.go @@ -4,7 +4,6 @@ package integration import ( - "fmt" "net/http" "net/url" "testing" @@ -39,7 +38,7 @@ func TestAPIGetLicenseTemplateInfo(t *testing.T) { // Use the first template for the test licenseName := repo_module.Licenses[0] - urlStr := fmt.Sprintf("/api/v1/licenses/%s", url.PathEscape(licenseName)) + urlStr := "/api/v1/licenses/" + url.PathEscape(licenseName) req := NewRequest(t, "GET", urlStr) resp := MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/api_notification_test.go b/tests/integration/api_notification_test.go index dc4ba83ecc..e6bc142476 100644 --- a/tests/integration/api_notification_test.go +++ b/tests/integration/api_notification_test.go @@ -35,7 +35,7 @@ func TestAPINotification(t *testing.T) { // -- GET /notifications -- // test filter since := "2000-01-01T00%3A50%3A01%2B00%3A00" // 946687801 - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications?since=%s", since)). + req := NewRequest(t, "GET", "/api/v1/notifications?since="+since). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) var apiNL []api.NotificationThread @@ -104,10 +104,10 @@ func TestAPINotification(t *testing.T) { assert.EqualValues(t, 5, apiN.ID) assert.False(t, apiN.Pinned) assert.True(t, apiN.Unread) - assert.EqualValues(t, "issue4", apiN.Subject.Title) + assert.Equal(t, "issue4", apiN.Subject.Title) assert.EqualValues(t, "Issue", apiN.Subject.Type) - assert.EqualValues(t, thread5.Issue.APIURL(db.DefaultContext), apiN.Subject.URL) - assert.EqualValues(t, thread5.Repository.HTMLURL(), apiN.Repository.HTMLURL) + assert.Equal(t, thread5.Issue.APIURL(db.DefaultContext), apiN.Subject.URL) + assert.Equal(t, thread5.Repository.HTMLURL(), apiN.Repository.HTMLURL) MakeRequest(t, NewRequest(t, "GET", "/api/v1/notifications/new"), http.StatusUnauthorized) diff --git a/tests/integration/api_oauth2_apps_test.go b/tests/integration/api_oauth2_apps_test.go index 7a17b4ca88..13f64fd69e 100644 --- a/tests/integration/api_oauth2_apps_test.go +++ b/tests/integration/api_oauth2_apps_test.go @@ -43,12 +43,12 @@ func testAPICreateOAuth2Application(t *testing.T) { var createdApp *api.OAuth2Application DecodeJSON(t, resp, &createdApp) - assert.EqualValues(t, appBody.Name, createdApp.Name) + assert.Equal(t, appBody.Name, createdApp.Name) assert.Len(t, createdApp.ClientSecret, 56) assert.Len(t, createdApp.ClientID, 36) assert.True(t, createdApp.ConfidentialClient) assert.NotEmpty(t, createdApp.Created) - assert.EqualValues(t, appBody.RedirectURIs[0], createdApp.RedirectURIs[0]) + assert.Equal(t, appBody.RedirectURIs[0], createdApp.RedirectURIs[0]) unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{UID: user.ID, Name: createdApp.Name}) } @@ -74,12 +74,12 @@ func testAPIListOAuth2Applications(t *testing.T) { DecodeJSON(t, resp, &appList) expectedApp := appList[0] - assert.EqualValues(t, expectedApp.Name, existApp.Name) - assert.EqualValues(t, expectedApp.ClientID, existApp.ClientID) + assert.Equal(t, expectedApp.Name, existApp.Name) + assert.Equal(t, expectedApp.ClientID, existApp.ClientID) assert.Equal(t, expectedApp.ConfidentialClient, existApp.ConfidentialClient) assert.Len(t, expectedApp.ClientID, 36) assert.Empty(t, expectedApp.ClientSecret) - assert.EqualValues(t, existApp.RedirectURIs[0], expectedApp.RedirectURIs[0]) + assert.Equal(t, existApp.RedirectURIs[0], expectedApp.RedirectURIs[0]) unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: expectedApp.ID, Name: expectedApp.Name}) } @@ -128,13 +128,13 @@ func testAPIGetOAuth2Application(t *testing.T) { DecodeJSON(t, resp, &app) expectedApp := app - assert.EqualValues(t, expectedApp.Name, existApp.Name) - assert.EqualValues(t, expectedApp.ClientID, existApp.ClientID) + assert.Equal(t, expectedApp.Name, existApp.Name) + assert.Equal(t, expectedApp.ClientID, existApp.ClientID) assert.Equal(t, expectedApp.ConfidentialClient, existApp.ConfidentialClient) assert.Len(t, expectedApp.ClientID, 36) assert.Empty(t, expectedApp.ClientSecret) assert.Len(t, expectedApp.RedirectURIs, 1) - assert.EqualValues(t, expectedApp.RedirectURIs[0], existApp.RedirectURIs[0]) + assert.Equal(t, expectedApp.RedirectURIs[0], existApp.RedirectURIs[0]) unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: expectedApp.ID, Name: expectedApp.Name}) } @@ -168,8 +168,8 @@ func testAPIUpdateOAuth2Application(t *testing.T) { expectedApp := app assert.Len(t, expectedApp.RedirectURIs, 2) - assert.EqualValues(t, expectedApp.RedirectURIs[0], appBody.RedirectURIs[0]) - assert.EqualValues(t, expectedApp.RedirectURIs[1], appBody.RedirectURIs[1]) + assert.Equal(t, expectedApp.RedirectURIs[0], appBody.RedirectURIs[0]) + assert.Equal(t, expectedApp.RedirectURIs[1], appBody.RedirectURIs[1]) assert.Equal(t, expectedApp.ConfidentialClient, appBody.ConfidentialClient) unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: expectedApp.ID, Name: expectedApp.Name}) } diff --git a/tests/integration/api_org_test.go b/tests/integration/api_org_test.go index d766b1e8be..6577bd1684 100644 --- a/tests/integration/api_org_test.go +++ b/tests/integration/api_org_test.go @@ -22,6 +22,7 @@ import ( "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestAPIOrgCreateRename(t *testing.T) { @@ -59,7 +60,7 @@ func TestAPIOrgCreateRename(t *testing.T) { req = NewRequestf(t, "GET", "/api/v1/orgs/%s", org.UserName).AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiOrg) - assert.EqualValues(t, org.UserName, apiOrg.Name) + assert.Equal(t, org.UserName, apiOrg.Name) t.Run("CheckPermission", func(t *testing.T) { // Check owner team permission @@ -86,7 +87,7 @@ func TestAPIOrgCreateRename(t *testing.T) { var users []*api.User DecodeJSON(t, resp, &users) assert.Len(t, users, 1) - assert.EqualValues(t, "user1", users[0].UserName) + assert.Equal(t, "user1", users[0].UserName) }) t.Run("RenameOrg", func(t *testing.T) { @@ -110,121 +111,142 @@ func TestAPIOrgCreateRename(t *testing.T) { }) } -func TestAPIOrgEdit(t *testing.T) { +func TestAPIOrgGeneral(t *testing.T) { defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user1") + user1Session := loginUser(t, "user1") + user1Token := getTokenForLoggedInUser(t, user1Session, auth_model.AccessTokenScopeWriteOrganization) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) - org := api.EditOrgOption{ - FullName: "Org3 organization new full name", - Description: "A new description", - Website: "https://try.gitea.io/new", - Location: "Beijing", - Visibility: "private", - } - req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) + t.Run("OrgGetAll", func(t *testing.T) { + // accessing with a token will return all orgs + req := NewRequest(t, "GET", "/api/v1/orgs").AddTokenAuth(user1Token) + resp := MakeRequest(t, req, http.StatusOK) + var apiOrgList []*api.Organization - var apiOrg api.Organization - DecodeJSON(t, resp, &apiOrg) + DecodeJSON(t, resp, &apiOrgList) + assert.Len(t, apiOrgList, 13) + assert.Equal(t, "Limited Org 36", apiOrgList[1].FullName) + assert.Equal(t, "limited", apiOrgList[1].Visibility) - assert.Equal(t, "org3", apiOrg.Name) - assert.Equal(t, org.FullName, apiOrg.FullName) - assert.Equal(t, org.Description, apiOrg.Description) - assert.Equal(t, org.Website, apiOrg.Website) - assert.Equal(t, org.Location, apiOrg.Location) - assert.Equal(t, org.Visibility, apiOrg.Visibility) -} - -func TestAPIOrgEditBadVisibility(t *testing.T) { - defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user1") - - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) - org := api.EditOrgOption{ - FullName: "Org3 organization new full name", - Description: "A new description", - Website: "https://try.gitea.io/new", - Location: "Beijing", - Visibility: "badvisibility", - } - req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusUnprocessableEntity) -} - -func TestAPIOrgDeny(t *testing.T) { - defer tests.PrepareTestEnv(t)() - defer test.MockVariableValue(&setting.Service.RequireSignInView, true)() - - orgName := "user1_org" - req := NewRequestf(t, "GET", "/api/v1/orgs/%s", orgName) - MakeRequest(t, req, http.StatusNotFound) - - req = NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", orgName) - MakeRequest(t, req, http.StatusNotFound) - - req = NewRequestf(t, "GET", "/api/v1/orgs/%s/members", orgName) - MakeRequest(t, req, http.StatusNotFound) -} - -func TestAPIGetAll(t *testing.T) { - defer tests.PrepareTestEnv(t)() - token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadOrganization) - - // accessing with a token will return all orgs - req := NewRequest(t, "GET", "/api/v1/orgs"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - var apiOrgList []*api.Organization - - DecodeJSON(t, resp, &apiOrgList) - assert.Len(t, apiOrgList, 13) - assert.Equal(t, "Limited Org 36", apiOrgList[1].FullName) - assert.Equal(t, "limited", apiOrgList[1].Visibility) - - // accessing without a token will return only public orgs - req = NewRequest(t, "GET", "/api/v1/orgs") - resp = MakeRequest(t, req, http.StatusOK) - - DecodeJSON(t, resp, &apiOrgList) - assert.Len(t, apiOrgList, 9) - assert.Equal(t, "org 17", apiOrgList[0].FullName) - assert.Equal(t, "public", apiOrgList[0].Visibility) -} - -func TestAPIOrgSearchEmptyTeam(t *testing.T) { - defer tests.PrepareTestEnv(t)() - token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteOrganization) - orgName := "org_with_empty_team" - - // create org - req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &api.CreateOrgOption{ - UserName: orgName, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - - // create team with no member - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams", orgName), &api.CreateTeamOption{ - Name: "Empty", - IncludesAllRepositories: true, - Permission: "read", - Units: []string{"repo.code", "repo.issues", "repo.ext_issues", "repo.wiki", "repo.pulls"}, - }).AddTokenAuth(token) - MakeRequest(t, req, http.StatusCreated) - - // case-insensitive search for teams that have no members - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/%s/teams/search?q=%s", orgName, "empty")). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - data := struct { - Ok bool - Data []*api.Team - }{} - DecodeJSON(t, resp, &data) - assert.True(t, data.Ok) - if assert.Len(t, data.Data, 1) { - assert.EqualValues(t, "Empty", data.Data[0].Name) - } + // accessing without a token will return only public orgs + req = NewRequest(t, "GET", "/api/v1/orgs") + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &apiOrgList) + assert.Len(t, apiOrgList, 9) + assert.Equal(t, "org 17", apiOrgList[0].FullName) + assert.Equal(t, "public", apiOrgList[0].Visibility) + }) + + t.Run("OrgEdit", func(t *testing.T) { + org := api.EditOrgOption{ + FullName: "Org3 organization new full name", + Description: "A new description", + Website: "https://try.gitea.io/new", + Location: "Beijing", + Visibility: "private", + } + req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org).AddTokenAuth(user1Token) + resp := MakeRequest(t, req, http.StatusOK) + + var apiOrg api.Organization + DecodeJSON(t, resp, &apiOrg) + + assert.Equal(t, "org3", apiOrg.Name) + assert.Equal(t, org.FullName, apiOrg.FullName) + assert.Equal(t, org.Description, apiOrg.Description) + assert.Equal(t, org.Website, apiOrg.Website) + assert.Equal(t, org.Location, apiOrg.Location) + assert.Equal(t, org.Visibility, apiOrg.Visibility) + }) + + t.Run("OrgEditBadVisibility", func(t *testing.T) { + org := api.EditOrgOption{ + FullName: "Org3 organization new full name", + Description: "A new description", + Website: "https://try.gitea.io/new", + Location: "Beijing", + Visibility: "badvisibility", + } + req := NewRequestWithJSON(t, "PATCH", "/api/v1/orgs/org3", &org).AddTokenAuth(user1Token) + MakeRequest(t, req, http.StatusUnprocessableEntity) + }) + + t.Run("OrgDeny", func(t *testing.T) { + defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)() + + orgName := "user1_org" + req := NewRequestf(t, "GET", "/api/v1/orgs/%s", orgName) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequestf(t, "GET", "/api/v1/orgs/%s/repos", orgName) + MakeRequest(t, req, http.StatusNotFound) + + req = NewRequestf(t, "GET", "/api/v1/orgs/%s/members", orgName) + MakeRequest(t, req, http.StatusNotFound) + }) + + t.Run("OrgSearchEmptyTeam", func(t *testing.T) { + orgName := "org_with_empty_team" + // create org + req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &api.CreateOrgOption{ + UserName: orgName, + }).AddTokenAuth(user1Token) + MakeRequest(t, req, http.StatusCreated) + + // create team with no member + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams", orgName), &api.CreateTeamOption{ + Name: "Empty", + IncludesAllRepositories: true, + Permission: "read", + Units: []string{"repo.code", "repo.issues", "repo.ext_issues", "repo.wiki", "repo.pulls"}, + }).AddTokenAuth(user1Token) + MakeRequest(t, req, http.StatusCreated) + + // case-insensitive search for teams that have no members + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/%s/teams/search?q=%s", orgName, "empty")). + AddTokenAuth(user1Token) + resp := MakeRequest(t, req, http.StatusOK) + data := struct { + Ok bool + Data []*api.Team + }{} + DecodeJSON(t, resp, &data) + assert.True(t, data.Ok) + if assert.Len(t, data.Data, 1) { + assert.Equal(t, "Empty", data.Data[0].Name) + } + }) + + t.Run("User2ChangeStatus", func(t *testing.T) { + user2Session := loginUser(t, "user2") + user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteOrganization) + + req := NewRequest(t, "PUT", "/api/v1/orgs/org3/public_members/user2").AddTokenAuth(user2Token) + MakeRequest(t, req, http.StatusNoContent) + req = NewRequest(t, "DELETE", "/api/v1/orgs/org3/public_members/user2").AddTokenAuth(user2Token) + MakeRequest(t, req, http.StatusNoContent) + + // non admin but org owner could also change other member's status + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + require.False(t, user2.IsAdmin) + req = NewRequest(t, "PUT", "/api/v1/orgs/org3/public_members/user1").AddTokenAuth(user2Token) + MakeRequest(t, req, http.StatusNoContent) + req = NewRequest(t, "DELETE", "/api/v1/orgs/org3/public_members/user1").AddTokenAuth(user2Token) + MakeRequest(t, req, http.StatusNoContent) + }) + + t.Run("User4ChangeStatus", func(t *testing.T) { + user4Session := loginUser(t, "user4") + user4Token := getTokenForLoggedInUser(t, user4Session, auth_model.AccessTokenScopeWriteOrganization) + + // user4 is a normal team member, they could change their own status + req := NewRequest(t, "PUT", "/api/v1/orgs/org3/public_members/user4").AddTokenAuth(user4Token) + MakeRequest(t, req, http.StatusNoContent) + req = NewRequest(t, "DELETE", "/api/v1/orgs/org3/public_members/user4").AddTokenAuth(user4Token) + MakeRequest(t, req, http.StatusNoContent) + req = NewRequest(t, "PUT", "/api/v1/orgs/org3/public_members/user1").AddTokenAuth(user4Token) + MakeRequest(t, req, http.StatusForbidden) + req = NewRequest(t, "DELETE", "/api/v1/orgs/org3/public_members/user1").AddTokenAuth(user4Token) + MakeRequest(t, req, http.StatusForbidden) + }) } diff --git a/tests/integration/api_packages_arch_test.go b/tests/integration/api_packages_arch_test.go index b9064ca3d1..e5778b4203 100644 --- a/tests/integration/api_packages_arch_test.go +++ b/tests/integration/api_packages_arch_test.go @@ -163,7 +163,7 @@ license = MIT`) assert.Condition(t, func() bool { seen := false expectedFilename := fmt.Sprintf("%s-%s-aarch64.pkg.tar.%s", packageName, packageVersion, compression) - expectedCompositeKey := fmt.Sprintf("%s|aarch64", repository) + expectedCompositeKey := repository + "|aarch64" for _, pf := range pfs { if pf.Name == expectedFilename && pf.CompositeKey == expectedCompositeKey { if seen { @@ -321,7 +321,7 @@ license = MIT`) _, has = content["gitea-test-1.0.1/desc"] assert.True(t, has) - req = NewRequest(t, "DELETE", fmt.Sprintf("%s/gitea-test/1.0.1/aarch64", rootURL)). + req = NewRequest(t, "DELETE", rootURL+"/gitea-test/1.0.1/aarch64"). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNoContent) diff --git a/tests/integration/api_packages_cargo_test.go b/tests/integration/api_packages_cargo_test.go index 3fb9687653..8b5caa7ea7 100644 --- a/tests/integration/api_packages_cargo_test.go +++ b/tests/integration/api_packages_cargo_test.go @@ -94,7 +94,7 @@ func testPackageCargo(t *testing.T, _ *neturl.URL) { } root := fmt.Sprintf("%sapi/packages/%s/cargo", setting.AppURL, user.Name) - url := fmt.Sprintf("%s/api/v1/crates", root) + url := root + "/api/v1/crates" t.Run("Index", func(t *testing.T) { t.Run("Git/Config", func(t *testing.T) { diff --git a/tests/integration/api_packages_chef_test.go b/tests/integration/api_packages_chef_test.go index 6efb2708af..86b3be9d0c 100644 --- a/tests/integration/api_packages_chef_test.go +++ b/tests/integration/api_packages_chef_test.go @@ -274,7 +274,7 @@ nwIDAQAB uploadPackage := func(t *testing.T, version string, expectedStatus int) { var body bytes.Buffer mpw := multipart.NewWriter(&body) - part, _ := mpw.CreateFormFile("tarball", fmt.Sprintf("%s.tar.gz", version)) + part, _ := mpw.CreateFormFile("tarball", version+".tar.gz") zw := gzip.NewWriter(part) tw := tar.NewWriter(zw) @@ -320,7 +320,7 @@ nwIDAQAB pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) assert.NoError(t, err) assert.Len(t, pfs, 1) - assert.Equal(t, fmt.Sprintf("%s.tar.gz", packageVersion), pfs[0].Name) + assert.Equal(t, packageVersion+".tar.gz", pfs[0].Name) assert.True(t, pfs[0].IsLead) uploadPackage(t, packageVersion, http.StatusConflict) diff --git a/tests/integration/api_packages_composer_test.go b/tests/integration/api_packages_composer_test.go index bc858c7476..54f61d91d9 100644 --- a/tests/integration/api_packages_composer_test.go +++ b/tests/integration/api_packages_composer_test.go @@ -64,7 +64,7 @@ func TestPackageComposer(t *testing.T) { t.Run("ServiceIndex", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/packages.json", url)). + req := NewRequest(t, "GET", url+"/packages.json"). AddBasicAuth(user.Name) resp := MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/api_packages_conan_test.go b/tests/integration/api_packages_conan_test.go index 3055e57a2e..4e83c998b8 100644 --- a/tests/integration/api_packages_conan_test.go +++ b/tests/integration/api_packages_conan_test.go @@ -91,18 +91,18 @@ func uploadConanPackageV1(t *testing.T, baseURL, token, name, version, user, cha AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) - req = NewRequest(t, "GET", fmt.Sprintf("%s/digest", recipeURL)). + req = NewRequest(t, "GET", recipeURL+"/digest"). AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) - req = NewRequest(t, "GET", fmt.Sprintf("%s/download_urls", recipeURL)). + req = NewRequest(t, "GET", recipeURL+"/download_urls"). AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) - req = NewRequest(t, "POST", fmt.Sprintf("%s/upload_urls", recipeURL)) + req = NewRequest(t, "POST", recipeURL+"/upload_urls") MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("%s/upload_urls", recipeURL), map[string]int64{ + req = NewRequestWithJSON(t, "POST", recipeURL+"/upload_urls", map[string]int64{ conanfileName: int64(len(contentConanfile)), "removed.txt": 0, }).AddTokenAuth(token) @@ -127,18 +127,18 @@ func uploadConanPackageV1(t *testing.T, baseURL, token, name, version, user, cha AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) - req = NewRequest(t, "GET", fmt.Sprintf("%s/digest", packageURL)). + req = NewRequest(t, "GET", packageURL+"/digest"). AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) - req = NewRequest(t, "GET", fmt.Sprintf("%s/download_urls", packageURL)). + req = NewRequest(t, "GET", packageURL+"/download_urls"). AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) - req = NewRequest(t, "POST", fmt.Sprintf("%s/upload_urls", packageURL)) + req = NewRequest(t, "POST", packageURL+"/upload_urls") MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("%s/upload_urls", packageURL), map[string]int64{ + req = NewRequestWithJSON(t, "POST", packageURL+"/upload_urls", map[string]int64{ conaninfoName: int64(len(contentConaninfo)), "removed.txt": 0, }).AddTokenAuth(token) @@ -167,7 +167,7 @@ func uploadConanPackageV2(t *testing.T, baseURL, token, name, version, user, cha AddTokenAuth(token) MakeRequest(t, req, http.StatusCreated) - req = NewRequest(t, "GET", fmt.Sprintf("%s/files", recipeURL)). + req = NewRequest(t, "GET", recipeURL+"/files"). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) @@ -180,7 +180,7 @@ func uploadConanPackageV2(t *testing.T, baseURL, token, name, version, user, cha packageURL := fmt.Sprintf("%s/packages/%s/revisions/%s", recipeURL, conanPackageReference, packageRevision) - req = NewRequest(t, "GET", fmt.Sprintf("%s/files", packageURL)). + req = NewRequest(t, "GET", packageURL+"/files"). AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) @@ -188,7 +188,7 @@ func uploadConanPackageV2(t *testing.T, baseURL, token, name, version, user, cha AddTokenAuth(token) MakeRequest(t, req, http.StatusCreated) - req = NewRequest(t, "GET", fmt.Sprintf("%s/files", packageURL)). + req = NewRequest(t, "GET", packageURL+"/files"). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) @@ -219,7 +219,7 @@ func TestPackageConan(t *testing.T) { t.Run("Ping", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/ping", url)) + req := NewRequest(t, "GET", url+"/v1/ping") resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, "revisions", resp.Header().Get("X-Conan-Server-Capabilities")) @@ -230,7 +230,7 @@ func TestPackageConan(t *testing.T) { t.Run("UserName/Password Authenticate", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/users/authenticate", url)). + req := NewRequest(t, "GET", url+"/v1/users/authenticate"). AddBasicAuth(user.Name) resp := MakeRequest(t, req, http.StatusOK) @@ -256,7 +256,7 @@ func TestPackageConan(t *testing.T) { token := getTokenForLoggedInUser(t, session, scope) - req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/users/authenticate", url)). + req := NewRequest(t, "GET", url+"/v1/users/authenticate"). AddTokenAuth(token) resp := MakeRequest(t, req, expectedAuthStatusCode) if expectedAuthStatusCode != http.StatusOK { @@ -273,7 +273,7 @@ func TestPackageConan(t *testing.T) { recipeURL := fmt.Sprintf("%s/v1/conans/%s/%s/%s/%s", url, "TestScope", version1, "testing", channel1) - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("%s/upload_urls", recipeURL), map[string]int64{ + req = NewRequestWithJSON(t, "POST", recipeURL+"/upload_urls", map[string]int64{ conanfileName: 64, "removed.txt": 0, }).AddTokenAuth(token) @@ -308,7 +308,7 @@ func TestPackageConan(t *testing.T) { t.Run("CheckCredentials", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/v1/users/check_credentials", url)). + req := NewRequest(t, "GET", url+"/v1/users/check_credentials"). AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) }) @@ -356,7 +356,7 @@ func TestPackageConan(t *testing.T) { assert.Equal(t, int64(len(contentConaninfo)), pb.Size) } else { - assert.FailNow(t, "unknown file: %s", pf.Name) + assert.FailNow(t, "unknown file", "unknown file: %s", pf.Name) } } }) @@ -376,14 +376,14 @@ func TestPackageConan(t *testing.T) { assert.Contains(t, fileHashes, conanfileName) assert.Equal(t, "7abc52241c22090782c54731371847a8", fileHashes[conanfileName]) - req = NewRequest(t, "GET", fmt.Sprintf("%s/digest", recipeURL)) + req = NewRequest(t, "GET", recipeURL+"/digest") resp = MakeRequest(t, req, http.StatusOK) downloadURLs := make(map[string]string) DecodeJSON(t, resp, &downloadURLs) assert.Contains(t, downloadURLs, conanfileName) - req = NewRequest(t, "GET", fmt.Sprintf("%s/download_urls", recipeURL)) + req = NewRequest(t, "GET", recipeURL+"/download_urls") resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &downloadURLs) @@ -404,14 +404,14 @@ func TestPackageConan(t *testing.T) { assert.Contains(t, fileHashes, conaninfoName) assert.Equal(t, "7628bfcc5b17f1470c468621a78df394", fileHashes[conaninfoName]) - req = NewRequest(t, "GET", fmt.Sprintf("%s/digest", packageURL)) + req = NewRequest(t, "GET", packageURL+"/digest") resp = MakeRequest(t, req, http.StatusOK) downloadURLs = make(map[string]string) DecodeJSON(t, resp, &downloadURLs) assert.Contains(t, downloadURLs, conaninfoName) - req = NewRequest(t, "GET", fmt.Sprintf("%s/download_urls", packageURL)) + req = NewRequest(t, "GET", packageURL+"/download_urls") resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &downloadURLs) @@ -550,7 +550,7 @@ func TestPackageConan(t *testing.T) { t.Run("Ping", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/ping", url)) + req := NewRequest(t, "GET", url+"/v2/ping") resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, "revisions", resp.Header().Get("X-Conan-Server-Capabilities")) @@ -561,7 +561,7 @@ func TestPackageConan(t *testing.T) { t.Run("UserName/Password Authenticate", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/users/authenticate", url)). + req := NewRequest(t, "GET", url+"/v2/users/authenticate"). AddBasicAuth(user.Name) resp := MakeRequest(t, req, http.StatusOK) @@ -573,7 +573,7 @@ func TestPackageConan(t *testing.T) { assert.Equal(t, user.ID, pkgMeta.UserID) assert.Equal(t, auth_model.AccessTokenScopeAll, pkgMeta.Scope) - token = fmt.Sprintf("Bearer %s", body) + token = "Bearer " + body }) badToken := "" @@ -590,7 +590,7 @@ func TestPackageConan(t *testing.T) { token := getTokenForLoggedInUser(t, session, scope) - req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/users/authenticate", url)). + req := NewRequest(t, "GET", url+"/v2/users/authenticate"). AddTokenAuth(token) resp := MakeRequest(t, req, expectedAuthStatusCode) if expectedAuthStatusCode != http.StatusOK { @@ -640,7 +640,7 @@ func TestPackageConan(t *testing.T) { t.Run("CheckCredentials", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/v2/users/check_credentials", url)). + req := NewRequest(t, "GET", url+"/v2/users/check_credentials"). AddTokenAuth(token) MakeRequest(t, req, http.StatusOK) }) @@ -664,7 +664,7 @@ func TestPackageConan(t *testing.T) { recipeURL := fmt.Sprintf("%s/v2/conans/%s/%s/%s/%s", url, name, version1, user1, channel1) - req := NewRequest(t, "GET", fmt.Sprintf("%s/latest", recipeURL)) + req := NewRequest(t, "GET", recipeURL+"/latest") resp := MakeRequest(t, req, http.StatusOK) obj := make(map[string]string) diff --git a/tests/integration/api_packages_conda_test.go b/tests/integration/api_packages_conda_test.go index 272a660d45..32f55e5435 100644 --- a/tests/integration/api_packages_conda_test.go +++ b/tests/integration/api_packages_conda_test.go @@ -193,19 +193,19 @@ func TestPackageConda(t *testing.T) { Removed map[string]*PackageInfo `json:"removed"` } - req := NewRequest(t, "GET", fmt.Sprintf("%s/noarch/repodata.json", root)) + req := NewRequest(t, "GET", root+"/noarch/repodata.json") resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, "application/json", resp.Header().Get("Content-Type")) - req = NewRequest(t, "GET", fmt.Sprintf("%s/noarch/repodata.json.bz2", root)) + req = NewRequest(t, "GET", root+"/noarch/repodata.json.bz2") resp = MakeRequest(t, req, http.StatusOK) assert.Equal(t, "application/x-bzip2", resp.Header().Get("Content-Type")) - req = NewRequest(t, "GET", fmt.Sprintf("%s/noarch/current_repodata.json", root)) + req = NewRequest(t, "GET", root+"/noarch/current_repodata.json") resp = MakeRequest(t, req, http.StatusOK) assert.Equal(t, "application/json", resp.Header().Get("Content-Type")) - req = NewRequest(t, "GET", fmt.Sprintf("%s/noarch/current_repodata.json.bz2", root)) + req = NewRequest(t, "GET", root+"/noarch/current_repodata.json.bz2") resp = MakeRequest(t, req, http.StatusOK) assert.Equal(t, "application/x-bzip2", resp.Header().Get("Content-Type")) @@ -218,7 +218,7 @@ func TestPackageConda(t *testing.T) { pd, err := packages.GetPackageDescriptor(db.DefaultContext, pv) assert.NoError(t, err) - req := NewRequest(t, "GET", fmt.Sprintf("%s/noarch/repodata.json", root)) + req := NewRequest(t, "GET", root+"/noarch/repodata.json") resp := MakeRequest(t, req, http.StatusOK) var result RepoData diff --git a/tests/integration/api_packages_container_test.go b/tests/integration/api_packages_container_test.go index 3905ad1b70..b2db77685d 100644 --- a/tests/integration/api_packages_container_test.go +++ b/tests/integration/api_packages_container_test.go @@ -9,6 +9,7 @@ import ( "encoding/base64" "fmt" "net/http" + "strconv" "strings" "sync" "testing" @@ -92,12 +93,12 @@ func TestPackageContainer(t *testing.T) { t.Run("Anonymous", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)) + req := NewRequest(t, "GET", setting.AppURL+"v2") resp := MakeRequest(t, req, http.StatusUnauthorized) assert.ElementsMatch(t, defaultAuthenticateValues, resp.Header().Values("WWW-Authenticate")) - req = NewRequest(t, "GET", fmt.Sprintf("%sv2/token", setting.AppURL)) + req = NewRequest(t, "GET", setting.AppURL+"v2/token") resp = MakeRequest(t, req, http.StatusOK) tokenResponse := &TokenResponse{} @@ -105,18 +106,18 @@ func TestPackageContainer(t *testing.T) { assert.NotEmpty(t, tokenResponse.Token) - anonymousToken = fmt.Sprintf("Bearer %s", tokenResponse.Token) + anonymousToken = "Bearer " + tokenResponse.Token - req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)). + req = NewRequest(t, "GET", setting.AppURL+"v2"). AddTokenAuth(anonymousToken) MakeRequest(t, req, http.StatusOK) - defer test.MockVariableValue(&setting.Service.RequireSignInView, true)() + defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)() - req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)) + req = NewRequest(t, "GET", setting.AppURL+"v2") MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequest(t, "GET", fmt.Sprintf("%sv2/token", setting.AppURL)) + req = NewRequest(t, "GET", setting.AppURL+"v2/token") MakeRequest(t, req, http.StatusUnauthorized) defer test.MockVariableValue(&setting.AppURL, "https://domain:8443/sub-path/")() @@ -129,12 +130,12 @@ func TestPackageContainer(t *testing.T) { t.Run("UserName/Password", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)) + req := NewRequest(t, "GET", setting.AppURL+"v2") resp := MakeRequest(t, req, http.StatusUnauthorized) assert.ElementsMatch(t, defaultAuthenticateValues, resp.Header().Values("WWW-Authenticate")) - req = NewRequest(t, "GET", fmt.Sprintf("%sv2/token", setting.AppURL)). + req = NewRequest(t, "GET", setting.AppURL+"v2/token"). AddBasicAuth(user.Name) resp = MakeRequest(t, req, http.StatusOK) @@ -147,9 +148,9 @@ func TestPackageContainer(t *testing.T) { assert.Equal(t, user.ID, pkgMeta.UserID) assert.Equal(t, auth_model.AccessTokenScopeAll, pkgMeta.Scope) - userToken = fmt.Sprintf("Bearer %s", tokenResponse.Token) + userToken = "Bearer " + tokenResponse.Token - req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)). + req = NewRequest(t, "GET", setting.AppURL+"v2"). AddTokenAuth(userToken) MakeRequest(t, req, http.StatusOK) }) @@ -161,23 +162,23 @@ func TestPackageContainer(t *testing.T) { session := loginUser(t, user.Name) readToken = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadPackage) - req := NewRequest(t, "GET", fmt.Sprintf("%sv2/token", setting.AppURL)) + req := NewRequest(t, "GET", setting.AppURL+"v2/token") req.Request.SetBasicAuth(user.Name, readToken) resp := MakeRequest(t, req, http.StatusOK) tokenResponse := &TokenResponse{} DecodeJSON(t, resp, &tokenResponse) - readToken = fmt.Sprintf("Bearer %s", tokenResponse.Token) + readToken = "Bearer " + tokenResponse.Token badToken = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadNotification) - req = NewRequest(t, "GET", fmt.Sprintf("%sv2/token", setting.AppURL)) + req = NewRequest(t, "GET", setting.AppURL+"v2/token") req.Request.SetBasicAuth(user.Name, badToken) MakeRequest(t, req, http.StatusUnauthorized) testCase := func(scope auth_model.AccessTokenScope, expectedAuthStatus, expectedStatus int) { token := getTokenForLoggedInUser(t, session, scope) - req := NewRequest(t, "GET", fmt.Sprintf("%sv2/token", setting.AppURL)) + req := NewRequest(t, "GET", setting.AppURL+"v2/token") req.SetBasicAuth(user.Name, token) resp := MakeRequest(t, req, expectedAuthStatus) @@ -190,8 +191,8 @@ func TestPackageContainer(t *testing.T) { assert.NotEmpty(t, tokenResponse.Token) - req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)). - AddTokenAuth(fmt.Sprintf("Bearer %s", tokenResponse.Token)) + req = NewRequest(t, "GET", setting.AppURL+"v2"). + AddTokenAuth("Bearer " + tokenResponse.Token) MakeRequest(t, req, expectedStatus) } testCase(auth_model.AccessTokenScopeReadPackage, http.StatusOK, http.StatusOK) @@ -204,17 +205,17 @@ func TestPackageContainer(t *testing.T) { t.Run("DetermineSupport", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)). + req := NewRequest(t, "GET", setting.AppURL+"v2"). AddTokenAuth(userToken) resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, "registry/2.0", resp.Header().Get("Docker-Distribution-Api-Version")) - req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)). + req = NewRequest(t, "GET", setting.AppURL+"v2"). AddTokenAuth(readToken) resp = MakeRequest(t, req, http.StatusOK) assert.Equal(t, "registry/2.0", resp.Header().Get("Docker-Distribution-Api-Version")) - req = NewRequest(t, "GET", fmt.Sprintf("%sv2", setting.AppURL)). + req = NewRequest(t, "GET", setting.AppURL+"v2"). AddTokenAuth(badToken) MakeRequest(t, req, http.StatusUnauthorized) }) @@ -226,15 +227,15 @@ func TestPackageContainer(t *testing.T) { t.Run("UploadBlob/Monolithic", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)). + req := NewRequest(t, "POST", url+"/blobs/uploads"). AddTokenAuth(anonymousToken) MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)). + req = NewRequest(t, "POST", url+"/blobs/uploads"). AddTokenAuth(readToken) MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)). + req = NewRequest(t, "POST", url+"/blobs/uploads"). AddTokenAuth(badToken) MakeRequest(t, req, http.StatusUnauthorized) @@ -264,15 +265,15 @@ func TestPackageContainer(t *testing.T) { t.Run("UploadBlob/Chunked", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)). + req := NewRequest(t, "POST", url+"/blobs/uploads"). AddTokenAuth(readToken) MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)). + req = NewRequest(t, "POST", url+"/blobs/uploads"). AddTokenAuth(badToken) MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)). + req = NewRequest(t, "POST", url+"/blobs/uploads"). AddTokenAuth(userToken) resp := MakeRequest(t, req, http.StatusAccepted) @@ -325,7 +326,7 @@ func TestPackageContainer(t *testing.T) { t.Run("Cancel", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)). + req := NewRequest(t, "POST", url+"/blobs/uploads"). AddTokenAuth(userToken) resp := MakeRequest(t, req, http.StatusAccepted) @@ -441,7 +442,7 @@ func TestPackageContainer(t *testing.T) { assert.Equal(t, "application/vnd.docker.image.rootfs.diff.tar.gzip", pfd.Properties.GetByName(container_module.PropertyMediaType)) assert.Equal(t, blobDigest, pfd.Properties.GetByName(container_module.PropertyDigest)) default: - assert.FailNow(t, "unknown file: %s", pfd.File.Name) + assert.FailNow(t, "unknown file", "unknown file: %s", pfd.File.Name) } } @@ -467,7 +468,7 @@ func TestPackageContainer(t *testing.T) { t.Run("HeadManifest", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "HEAD", fmt.Sprintf("%s/manifests/unknown-tag", url)). + req := NewRequest(t, "HEAD", url+"/manifests/unknown-tag"). AddTokenAuth(userToken) MakeRequest(t, req, http.StatusNotFound) @@ -475,14 +476,14 @@ func TestPackageContainer(t *testing.T) { AddTokenAuth(userToken) resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, fmt.Sprintf("%d", len(manifestContent)), resp.Header().Get("Content-Length")) + assert.Equal(t, strconv.Itoa(len(manifestContent)), resp.Header().Get("Content-Length")) assert.Equal(t, manifestDigest, resp.Header().Get("Docker-Content-Digest")) }) t.Run("GetManifest", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/manifests/unknown-tag", url)). + req := NewRequest(t, "GET", url+"/manifests/unknown-tag"). AddTokenAuth(userToken) MakeRequest(t, req, http.StatusNotFound) @@ -490,7 +491,7 @@ func TestPackageContainer(t *testing.T) { AddTokenAuth(userToken) resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, fmt.Sprintf("%d", len(manifestContent)), resp.Header().Get("Content-Length")) + assert.Equal(t, strconv.Itoa(len(manifestContent)), resp.Header().Get("Content-Length")) assert.Equal(t, oci.MediaTypeImageManifest, resp.Header().Get("Content-Type")) assert.Equal(t, manifestDigest, resp.Header().Get("Docker-Content-Digest")) assert.Equal(t, manifestContent, resp.Body.String()) @@ -512,7 +513,7 @@ func TestPackageContainer(t *testing.T) { AddTokenAuth(userToken) resp = MakeRequest(t, req, http.StatusOK) - assert.Equal(t, fmt.Sprintf("%d", len(untaggedManifestContent)), resp.Header().Get("Content-Length")) + assert.Equal(t, strconv.Itoa(len(untaggedManifestContent)), resp.Header().Get("Content-Length")) assert.Equal(t, untaggedManifestDigest, resp.Header().Get("Docker-Content-Digest")) pv, err := packages_model.GetVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, untaggedManifestDigest) @@ -598,7 +599,7 @@ func TestPackageContainer(t *testing.T) { AddTokenAuth(userToken) resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, fmt.Sprintf("%d", len(blobContent)), resp.Header().Get("Content-Length")) + assert.Equal(t, strconv.Itoa(len(blobContent)), resp.Header().Get("Content-Length")) assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest")) req = NewRequest(t, "HEAD", fmt.Sprintf("%s/blobs/%s", url, blobDigest)). @@ -617,7 +618,7 @@ func TestPackageContainer(t *testing.T) { AddTokenAuth(userToken) resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, fmt.Sprintf("%d", len(blobContent)), resp.Header().Get("Content-Length")) + assert.Equal(t, strconv.Itoa(len(blobContent)), resp.Header().Get("Content-Length")) assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest")) assert.Equal(t, blobContent, resp.Body.Bytes()) }) @@ -631,27 +632,27 @@ func TestPackageContainer(t *testing.T) { ExpectedLink string }{ { - URL: fmt.Sprintf("%s/tags/list", url), + URL: url + "/tags/list", ExpectedTags: []string{"latest", "main", "multi"}, ExpectedLink: fmt.Sprintf(`; rel="next"`, user.Name, image), }, { - URL: fmt.Sprintf("%s/tags/list?n=0", url), + URL: url + "/tags/list?n=0", ExpectedTags: []string{}, ExpectedLink: "", }, { - URL: fmt.Sprintf("%s/tags/list?n=2", url), + URL: url + "/tags/list?n=2", ExpectedTags: []string{"latest", "main"}, ExpectedLink: fmt.Sprintf(`; rel="next"`, user.Name, image), }, { - URL: fmt.Sprintf("%s/tags/list?last=main", url), + URL: url + "/tags/list?last=main", ExpectedTags: []string{"multi"}, ExpectedLink: fmt.Sprintf(`; rel="next"`, user.Name, image), }, { - URL: fmt.Sprintf("%s/tags/list?n=1&last=latest", url), + URL: url + "/tags/list?n=1&last=latest", ExpectedTags: []string{"main"}, ExpectedLink: fmt.Sprintf(`; rel="next"`, user.Name, image), }, @@ -757,7 +758,7 @@ func TestPackageContainer(t *testing.T) { return func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%sv2/_catalog", setting.AppURL)). + req := NewRequest(t, "GET", setting.AppURL+"v2/_catalog"). AddTokenAuth(userToken) resp := MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/api_packages_cran_test.go b/tests/integration/api_packages_cran_test.go index 667ba0908c..bd4a99f331 100644 --- a/tests/integration/api_packages_cran_test.go +++ b/tests/integration/api_packages_cran_test.go @@ -133,8 +133,8 @@ func TestPackageCran(t *testing.T) { assert.Contains(t, resp.Header().Get("Content-Type"), "text/plain") body := resp.Body.String() - assert.Contains(t, body, fmt.Sprintf("Package: %s", packageName)) - assert.Contains(t, body, fmt.Sprintf("Version: %s", packageVersion)) + assert.Contains(t, body, "Package: "+packageName) + assert.Contains(t, body, "Version: "+packageVersion) req = NewRequest(t, "GET", url+"/src/contrib/PACKAGES.gz"). AddBasicAuth(user.Name) @@ -230,8 +230,8 @@ func TestPackageCran(t *testing.T) { assert.Contains(t, resp.Header().Get("Content-Type"), "text/plain") body := resp.Body.String() - assert.Contains(t, body, fmt.Sprintf("Package: %s", packageName)) - assert.Contains(t, body, fmt.Sprintf("Version: %s", packageVersion)) + assert.Contains(t, body, "Package: "+packageName) + assert.Contains(t, body, "Version: "+packageVersion) req = NewRequest(t, "GET", url+"/bin/windows/contrib/4.2/PACKAGES.gz"). AddBasicAuth(user.Name) diff --git a/tests/integration/api_packages_generic_test.go b/tests/integration/api_packages_generic_test.go index baa8dd66c8..5f410fc470 100644 --- a/tests/integration/api_packages_generic_test.go +++ b/tests/integration/api_packages_generic_test.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" @@ -131,11 +132,7 @@ func TestPackageGeneric(t *testing.T) { t.Run("RequireSignInView", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - - setting.Service.RequireSignInView = true - defer func() { - setting.Service.RequireSignInView = false - }() + defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)() req = NewRequest(t, "GET", url+"/dummy.bin") MakeRequest(t, req, http.StatusUnauthorized) diff --git a/tests/integration/api_packages_helm_test.go b/tests/integration/api_packages_helm_test.go index 76285add11..8f5c6ac571 100644 --- a/tests/integration/api_packages_helm_test.go +++ b/tests/integration/api_packages_helm_test.go @@ -52,7 +52,7 @@ dependencies: zw := gzip.NewWriter(&buf) archive := tar.NewWriter(zw) archive.WriteHeader(&tar.Header{ - Name: fmt.Sprintf("%s/Chart.yaml", packageName), + Name: packageName + "/Chart.yaml", Mode: 0o600, Size: int64(len(chartContent)), }) @@ -122,7 +122,7 @@ dependencies: t.Run("Index", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/index.yaml", url)). + req := NewRequest(t, "GET", url+"/index.yaml"). AddBasicAuth(user.Name) resp := MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/api_packages_npm_test.go b/tests/integration/api_packages_npm_test.go index ae1dd876f7..a190ed679d 100644 --- a/tests/integration/api_packages_npm_test.go +++ b/tests/integration/api_packages_npm_test.go @@ -28,7 +28,7 @@ func TestPackageNpm(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - token := fmt.Sprintf("Bearer %s", getTokenForLoggedInUser(t, loginUser(t, user.Name), auth_model.AccessTokenScopeWritePackage)) + token := "Bearer " + getTokenForLoggedInUser(t, loginUser(t, user.Name), auth_model.AccessTokenScopeWritePackage) packageName := "@scope/test-package" packageVersion := "1.0.1-pre" diff --git a/tests/integration/api_packages_nuget_test.go b/tests/integration/api_packages_nuget_test.go index 622c2c4394..c0e69a82cd 100644 --- a/tests/integration/api_packages_nuget_test.go +++ b/tests/integration/api_packages_nuget_test.go @@ -198,7 +198,7 @@ func TestPackageNuGet(t *testing.T) { t.Run(c.Owner, func(t *testing.T) { url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner) - req := NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url)) + req := NewRequest(t, "GET", url+"/index.json") if c.UseBasicAuth { req.AddBasicAuth(user.Name) } else if c.token != "" { @@ -273,10 +273,10 @@ func TestPackageNuGet(t *testing.T) { pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID) assert.NoError(t, err) assert.Equal(t, int64(len(content)), pb.Size) - case fmt.Sprintf("%s.nuspec", packageName): + case packageName + ".nuspec": assert.False(t, pf.IsLead) default: - assert.Fail(t, "unexpected filename: %v", pf.Name) + assert.Fail(t, "unexpected filename", "unexpected filename: %v", pf.Name) } } @@ -319,10 +319,10 @@ func TestPackageNuGet(t *testing.T) { pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID) assert.NoError(t, err) assert.Equal(t, int64(len(content)), pb.Size) - case fmt.Sprintf("%s.nuspec", packageName): + case packageName + ".nuspec": assert.False(t, pf.IsLead) default: - assert.Fail(t, "unexpected filename: %v", pf.Name) + assert.Fail(t, "unexpected filename", "unexpected filename: %v", pf.Name) } } @@ -360,15 +360,15 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) return &buf } - req := NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage("unknown-package", "SymbolsPackage")). + req := NewRequestWithBody(t, "PUT", url+"/symbolpackage", createSymbolPackage("unknown-package", "SymbolsPackage")). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusNotFound) - req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage(packageName, "DummyPackage")). + req = NewRequestWithBody(t, "PUT", url+"/symbolpackage", createSymbolPackage(packageName, "DummyPackage")). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusBadRequest) - req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage(packageName, "SymbolsPackage")). + req = NewRequestWithBody(t, "PUT", url+"/symbolpackage", createSymbolPackage(packageName, "SymbolsPackage")). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusCreated) @@ -400,7 +400,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID) assert.NoError(t, err) assert.Equal(t, int64(616), pb.Size) - case fmt.Sprintf("%s.nuspec", packageName): + case packageName + ".nuspec": assert.False(t, pf.IsLead) pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID) @@ -419,11 +419,11 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) assert.Equal(t, nuget_module.PropertySymbolID, pps[0].Name) assert.Equal(t, symbolID, pps[0].Value) default: - assert.FailNow(t, "unexpected file: %v", pf.Name) + assert.FailNow(t, "unexpected file", "unexpected file: %v", pf.Name) } } - req = NewRequestWithBody(t, "PUT", fmt.Sprintf("%s/symbolpackage", url), createSymbolPackage(packageName, "SymbolsPackage")). + req = NewRequestWithBody(t, "PUT", url+"/symbolpackage", createSymbolPackage(packageName, "SymbolsPackage")). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusConflict) }) @@ -631,7 +631,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) }) t.Run("Next", func(t *testing.T) { - req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?searchTerm='test'&$skip=0&$top=1", url)). + req := NewRequest(t, "GET", url+"/Search()?searchTerm='test'&$skip=0&$top=1"). AddBasicAuth(user.Name) resp := MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/api_packages_pub_test.go b/tests/integration/api_packages_pub_test.go index 11da894ddf..3c1bca908e 100644 --- a/tests/integration/api_packages_pub_test.go +++ b/tests/integration/api_packages_pub_test.go @@ -37,7 +37,7 @@ func TestPackagePub(t *testing.T) { packageVersion := "1.0.1" packageDescription := "Test Description" - filename := fmt.Sprintf("%s.tar.gz", packageVersion) + filename := packageVersion + ".tar.gz" pubspecContent := `name: ` + packageName + ` version: ` + packageVersion + ` diff --git a/tests/integration/api_packages_pypi_test.go b/tests/integration/api_packages_pypi_test.go index 2dabb5005b..54db45f1ac 100644 --- a/tests/integration/api_packages_pypi_test.go +++ b/tests/integration/api_packages_pypi_test.go @@ -67,7 +67,7 @@ func TestPackagePyPI(t *testing.T) { body, writer, closeFunc := createBasicMultipartFile(filename, packageName, content) writer.WriteField("project_urls", "DOCUMENTATION , https://readthedocs.org") - writer.WriteField("project_urls", fmt.Sprintf("Home-page, %s", projectURL)) + writer.WriteField("project_urls", "Home-page, "+projectURL) _ = closeFunc() diff --git a/tests/integration/api_packages_rpm_test.go b/tests/integration/api_packages_rpm_test.go index 6feceaeb78..469bd1fc6c 100644 --- a/tests/integration/api_packages_rpm_test.go +++ b/tests/integration/api_packages_rpm_test.go @@ -317,7 +317,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, var result Metadata decodeGzipXML(t, resp, &result) - assert.EqualValues(t, 1, result.PackageCount) + assert.Equal(t, 1, result.PackageCount) assert.Len(t, result.Packages, 1) p := result.Packages[0] assert.Equal(t, "rpm", p.Type) @@ -366,7 +366,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, var result Filelists decodeGzipXML(t, resp, &result) - assert.EqualValues(t, 1, result.PackageCount) + assert.Equal(t, 1, result.PackageCount) assert.Len(t, result.Packages, 1) p := result.Packages[0] assert.NotEmpty(t, p.Pkgid) @@ -403,7 +403,7 @@ gpgkey=%sapi/packages/%s/rpm/repository.key`, var result Other decodeGzipXML(t, resp, &result) - assert.EqualValues(t, 1, result.PackageCount) + assert.Equal(t, 1, result.PackageCount) assert.Len(t, result.Packages, 1) p := result.Packages[0] assert.NotEmpty(t, p.Pkgid) diff --git a/tests/integration/api_packages_rubygems_test.go b/tests/integration/api_packages_rubygems_test.go index fe9283df4d..ab76c52440 100644 --- a/tests/integration/api_packages_rubygems_test.go +++ b/tests/integration/api_packages_rubygems_test.go @@ -185,7 +185,7 @@ func TestPackageRubyGems(t *testing.T) { root := fmt.Sprintf("/api/packages/%s/rubygems", user.Name) uploadFile := func(t *testing.T, content []byte, expectedStatus int) { - req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/api/v1/gems", root), bytes.NewReader(content)). + req := NewRequestWithBody(t, "POST", root+"/api/v1/gems", bytes.NewReader(content)). AddBasicAuth(user.Name) MakeRequest(t, req, expectedStatus) } @@ -293,7 +293,7 @@ gAAAAP//MS06Gw==`) t.Run("Versions", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/versions", root)).AddBasicAuth(user.Name) + req := NewRequest(t, "GET", root+"/versions").AddBasicAuth(user.Name) resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, `--- gitea 1.0.5 08843c2dd0ea19910e6b056b98e38f1c @@ -307,7 +307,7 @@ gitea-another 0.99 8b639e4048d282941485368ec42609be _ = writer.WriteField("gem_name", packageName) _ = writer.WriteField("version", packageVersion) _ = writer.Close() - req := NewRequestWithBody(t, "DELETE", fmt.Sprintf("%s/api/v1/gems/yank", root), &body). + req := NewRequestWithBody(t, "DELETE", root+"/api/v1/gems/yank", &body). SetHeader("Content-Type", writer.FormDataContentType()). AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusOK) @@ -330,7 +330,7 @@ gitea-another 0.99 8b639e4048d282941485368ec42609be t.Run("VersionsAfterDelete", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("%s/versions", root)).AddBasicAuth(user.Name) + req := NewRequest(t, "GET", root+"/versions").AddBasicAuth(user.Name) resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, "---\n", resp.Body.String()) }) diff --git a/tests/integration/api_packages_test.go b/tests/integration/api_packages_test.go index 978a690302..786addbd76 100644 --- a/tests/integration/api_packages_test.go +++ b/tests/integration/api_packages_test.go @@ -49,7 +49,7 @@ func TestPackageAPI(t *testing.T) { t.Run("ListPackages", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s", user.Name)). + req := NewRequest(t, "GET", "/api/v1/packages/"+user.Name). AddTokenAuth(tokenReadPackage) resp := MakeRequest(t, req, http.StatusOK) @@ -83,70 +83,101 @@ func TestPackageAPI(t *testing.T) { assert.Equal(t, packageVersion, p.Version) assert.NotNil(t, p.Creator) assert.Equal(t, user.Name, p.Creator.UserName) + }) - t.Run("RepositoryLink", func(t *testing.T) { - defer tests.PrintCurrentTest(t)() + t.Run("ListPackageVersions", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - _, err := packages_model.GetPackageByName(db.DefaultContext, user.ID, packages_model.TypeGeneric, packageName) - assert.NoError(t, err) + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s", user.Name, packageName)). + AddTokenAuth(tokenReadPackage) + resp := MakeRequest(t, req, http.StatusOK) - // no repository link - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)). - AddTokenAuth(tokenReadPackage) - resp := MakeRequest(t, req, http.StatusOK) + var apiPackages []*api.Package + DecodeJSON(t, resp, &apiPackages) - var ap1 *api.Package - DecodeJSON(t, resp, &ap1) - assert.Nil(t, ap1.Repository) + assert.Len(t, apiPackages, 1) + assert.Equal(t, string(packages_model.TypeGeneric), apiPackages[0].Type) + assert.Equal(t, packageName, apiPackages[0].Name) + assert.Equal(t, packageVersion, apiPackages[0].Version) + }) - // create a repository - newRepo, err := repo_service.CreateRepository(db.DefaultContext, user, user, repo_service.CreateRepoOptions{ - Name: "repo4", - }) - assert.NoError(t, err) + t.Run("LatestPackageVersion", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - // link to public repository - req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/link/%s", user.Name, packageName, newRepo.Name)).AddTokenAuth(tokenWritePackage) - MakeRequest(t, req, http.StatusCreated) + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/latest", user.Name, packageName)). + AddTokenAuth(tokenReadPackage) + resp := MakeRequest(t, req, http.StatusOK) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)). - AddTokenAuth(tokenReadPackage) - resp = MakeRequest(t, req, http.StatusOK) + var apiPackage *api.Package + DecodeJSON(t, resp, &apiPackage) - var ap2 *api.Package - DecodeJSON(t, resp, &ap2) - assert.NotNil(t, ap2.Repository) - assert.EqualValues(t, newRepo.ID, ap2.Repository.ID) + assert.Equal(t, string(packages_model.TypeGeneric), apiPackage.Type) + assert.Equal(t, packageName, apiPackage.Name) + assert.Equal(t, packageVersion, apiPackage.Version) + }) - // link to repository without write access, should fail - req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/link/%s", user.Name, packageName, "repo3")).AddTokenAuth(tokenWritePackage) - MakeRequest(t, req, http.StatusNotFound) + t.Run("RepositoryLink", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - // remove link - req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/unlink", user.Name, packageName)).AddTokenAuth(tokenWritePackage) - MakeRequest(t, req, http.StatusNoContent) + _, err := packages_model.GetPackageByName(db.DefaultContext, user.ID, packages_model.TypeGeneric, packageName) + assert.NoError(t, err) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)). - AddTokenAuth(tokenReadPackage) - resp = MakeRequest(t, req, http.StatusOK) + // no repository link + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)). + AddTokenAuth(tokenReadPackage) + resp := MakeRequest(t, req, http.StatusOK) - var ap3 *api.Package - DecodeJSON(t, resp, &ap3) - assert.Nil(t, ap3.Repository) + var ap1 *api.Package + DecodeJSON(t, resp, &ap1) + assert.Nil(t, ap1.Repository) - // force link to a repository the currently logged-in user doesn't have access to - privateRepoID := int64(6) - assert.NoError(t, packages_model.SetRepositoryLink(db.DefaultContext, p.ID, privateRepoID)) - - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)).AddTokenAuth(tokenReadPackage) - resp = MakeRequest(t, req, http.StatusOK) - - var ap4 *api.Package - DecodeJSON(t, resp, &ap4) - assert.Nil(t, ap4.Repository) - - assert.NoError(t, packages_model.UnlinkRepositoryFromAllPackages(db.DefaultContext, privateRepoID)) + // create a repository + newRepo, err := repo_service.CreateRepository(db.DefaultContext, user, user, repo_service.CreateRepoOptions{ + Name: "repo4", }) + assert.NoError(t, err) + + // link to public repository + req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/link/%s", user.Name, packageName, newRepo.Name)).AddTokenAuth(tokenWritePackage) + MakeRequest(t, req, http.StatusCreated) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)). + AddTokenAuth(tokenReadPackage) + resp = MakeRequest(t, req, http.StatusOK) + + var ap2 *api.Package + DecodeJSON(t, resp, &ap2) + assert.NotNil(t, ap2.Repository) + assert.Equal(t, newRepo.ID, ap2.Repository.ID) + + // link to repository without write access, should fail + req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/link/%s", user.Name, packageName, "repo3")).AddTokenAuth(tokenWritePackage) + MakeRequest(t, req, http.StatusNotFound) + + // remove link + req = NewRequest(t, "POST", fmt.Sprintf("/api/v1/packages/%s/generic/%s/-/unlink", user.Name, packageName)).AddTokenAuth(tokenWritePackage) + MakeRequest(t, req, http.StatusNoContent) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)). + AddTokenAuth(tokenReadPackage) + resp = MakeRequest(t, req, http.StatusOK) + + var ap3 *api.Package + DecodeJSON(t, resp, &ap3) + assert.Nil(t, ap3.Repository) + + // force link to a repository the currently logged-in user doesn't have access to + privateRepoID := int64(6) + assert.NoError(t, packages_model.SetRepositoryLink(db.DefaultContext, ap1.ID, privateRepoID)) + + req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s/generic/%s/%s", user.Name, packageName, packageVersion)).AddTokenAuth(tokenReadPackage) + resp = MakeRequest(t, req, http.StatusOK) + + var ap4 *api.Package + DecodeJSON(t, resp, &ap4) + assert.Nil(t, ap4.Repository) + + assert.NoError(t, packages_model.UnlinkRepositoryFromAllPackages(db.DefaultContext, privateRepoID)) }) t.Run("ListPackageFiles", func(t *testing.T) { @@ -408,7 +439,7 @@ func TestPackageAccess(t *testing.T) { {limitedOrgNoMember, http.StatusOK}, {publicOrgNoMember, http.StatusOK}, } { - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/packages/%s", target.Owner.Name)). + req := NewRequest(t, "GET", "/api/v1/packages/"+target.Owner.Name). AddTokenAuth(tokenReadPackage) MakeRequest(t, req, target.ExpectedStatus) } diff --git a/tests/integration/api_packages_vagrant_test.go b/tests/integration/api_packages_vagrant_test.go index a5e954f3a2..1743e37222 100644 --- a/tests/integration/api_packages_vagrant_test.go +++ b/tests/integration/api_packages_vagrant_test.go @@ -35,7 +35,7 @@ func TestPackageVagrant(t *testing.T) { packageDescription := "Test Description" packageProvider := "virtualbox" - filename := fmt.Sprintf("%s.box", packageProvider) + filename := packageProvider + ".box" infoContent, _ := json.Marshal(map[string]string{ "description": packageDescription, @@ -59,7 +59,7 @@ func TestPackageVagrant(t *testing.T) { t.Run("Authenticate", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - authenticateURL := fmt.Sprintf("%s/authenticate", root) + authenticateURL := root + "/authenticate" req := NewRequest(t, "GET", authenticateURL) MakeRequest(t, req, http.StatusUnauthorized) diff --git a/tests/integration/api_pull_review_test.go b/tests/integration/api_pull_review_test.go index b85882a510..1fc65ddea8 100644 --- a/tests/integration/api_pull_review_test.go +++ b/tests/integration/api_pull_review_test.go @@ -43,17 +43,17 @@ func TestAPIPullReview(t *testing.T) { require.Len(t, reviews, 8) for _, r := range reviews { - assert.EqualValues(t, pullIssue.HTMLURL(), r.HTMLPullURL) + assert.Equal(t, pullIssue.HTMLURL(), r.HTMLPullURL) } assert.EqualValues(t, 8, reviews[3].ID) assert.EqualValues(t, "APPROVED", reviews[3].State) - assert.EqualValues(t, 0, reviews[3].CodeCommentsCount) + assert.Equal(t, 0, reviews[3].CodeCommentsCount) assert.True(t, reviews[3].Stale) assert.False(t, reviews[3].Official) assert.EqualValues(t, 10, reviews[5].ID) assert.EqualValues(t, "REQUEST_CHANGES", reviews[5].State) - assert.EqualValues(t, 1, reviews[5].CodeCommentsCount) + assert.Equal(t, 1, reviews[5].CodeCommentsCount) assert.EqualValues(t, -1, reviews[5].Reviewer.ID) // ghost user assert.False(t, reviews[5].Stale) assert.True(t, reviews[5].Official) @@ -64,13 +64,13 @@ func TestAPIPullReview(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) var review api.PullReview DecodeJSON(t, resp, &review) - assert.EqualValues(t, *reviews[3], review) + assert.Equal(t, *reviews[3], review) req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.Name, pullIssue.Index, reviews[5].ID). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &review) - assert.EqualValues(t, *reviews[5], review) + assert.Equal(t, *reviews[5], review) // test GetPullReviewComments comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 7}) @@ -80,11 +80,11 @@ func TestAPIPullReview(t *testing.T) { var reviewComments []*api.PullReviewComment DecodeJSON(t, resp, &reviewComments) assert.Len(t, reviewComments, 1) - assert.EqualValues(t, "Ghost", reviewComments[0].Poster.UserName) - assert.EqualValues(t, "a review from a deleted user", reviewComments[0].Body) - assert.EqualValues(t, comment.ID, reviewComments[0].ID) + assert.Equal(t, "Ghost", reviewComments[0].Poster.UserName) + assert.Equal(t, "a review from a deleted user", reviewComments[0].Body) + assert.Equal(t, comment.ID, reviewComments[0].ID) assert.EqualValues(t, comment.UpdatedUnix, reviewComments[0].Updated.Unix()) - assert.EqualValues(t, comment.HTMLURL(db.DefaultContext), reviewComments[0].HTMLURL) + assert.Equal(t, comment.HTMLURL(db.DefaultContext), reviewComments[0].HTMLURL) // test CreatePullReview req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{ @@ -113,7 +113,7 @@ func TestAPIPullReview(t *testing.T) { DecodeJSON(t, resp, &review) assert.EqualValues(t, 6, review.ID) assert.EqualValues(t, "PENDING", review.State) - assert.EqualValues(t, 3, review.CodeCommentsCount) + assert.Equal(t, 3, review.CodeCommentsCount) // test SubmitPullReview req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.Name, pullIssue.Index, review.ID), &api.SubmitPullReviewOptions{ @@ -124,7 +124,7 @@ func TestAPIPullReview(t *testing.T) { DecodeJSON(t, resp, &review) assert.EqualValues(t, 6, review.ID) assert.EqualValues(t, "APPROVED", review.State) - assert.EqualValues(t, 3, review.CodeCommentsCount) + assert.Equal(t, 3, review.CodeCommentsCount) // test dismiss review req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d/dismissals", repo.OwnerName, repo.Name, pullIssue.Index, review.ID), &api.DismissPullReviewOptions{ @@ -151,7 +151,7 @@ func TestAPIPullReview(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &review) assert.EqualValues(t, "COMMENT", review.State) - assert.EqualValues(t, 0, review.CodeCommentsCount) + assert.Equal(t, 0, review.CodeCommentsCount) req = NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.Name, pullIssue.Index, review.ID). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) @@ -179,7 +179,7 @@ func TestAPIPullReview(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &commentReview) assert.EqualValues(t, "COMMENT", commentReview.State) - assert.EqualValues(t, 2, commentReview.CodeCommentsCount) + assert.Equal(t, 2, commentReview.CodeCommentsCount) assert.Empty(t, commentReview.Body) assert.False(t, commentReview.Dismissed) @@ -194,8 +194,8 @@ func TestAPIPullReview(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &commentReview) assert.EqualValues(t, "COMMENT", commentReview.State) - assert.EqualValues(t, 0, commentReview.CodeCommentsCount) - assert.EqualValues(t, commentBody, commentReview.Body) + assert.Equal(t, 0, commentReview.CodeCommentsCount) + assert.Equal(t, commentBody, commentReview.Body) assert.False(t, commentReview.Dismissed) // test CreatePullReview Comment without body and no comments @@ -207,7 +207,7 @@ func TestAPIPullReview(t *testing.T) { resp = MakeRequest(t, req, http.StatusUnprocessableEntity) errMap := make(map[string]any) json.Unmarshal(resp.Body.Bytes(), &errMap) - assert.EqualValues(t, "review event COMMENT requires a body or a comment", errMap["message"].(string)) + assert.Equal(t, "review event COMMENT requires a body or a comment", errMap["message"].(string)) // test get review requests // to make it simple, use same api with get review @@ -221,14 +221,14 @@ func TestAPIPullReview(t *testing.T) { DecodeJSON(t, resp, &reviews) assert.EqualValues(t, 11, reviews[0].ID) assert.EqualValues(t, "REQUEST_REVIEW", reviews[0].State) - assert.EqualValues(t, 0, reviews[0].CodeCommentsCount) + assert.Equal(t, 0, reviews[0].CodeCommentsCount) assert.False(t, reviews[0].Stale) assert.True(t, reviews[0].Official) - assert.EqualValues(t, "test_team", reviews[0].ReviewerTeam.Name) + assert.Equal(t, "test_team", reviews[0].ReviewerTeam.Name) assert.EqualValues(t, 12, reviews[1].ID) assert.EqualValues(t, "REQUEST_REVIEW", reviews[1].State) - assert.EqualValues(t, 0, reviews[0].CodeCommentsCount) + assert.Equal(t, 0, reviews[0].CodeCommentsCount) assert.False(t, reviews[1].Stale) assert.True(t, reviews[1].Official) assert.EqualValues(t, 1, reviews[1].Reviewer.ID) diff --git a/tests/integration/api_pull_test.go b/tests/integration/api_pull_test.go index 39c6c34a30..f3165c6fc5 100644 --- a/tests/integration/api_pull_test.go +++ b/tests/integration/api_pull_test.go @@ -61,8 +61,8 @@ func TestAPIViewPulls(t *testing.T) { assert.Equal(t, "File-WoW", patch.Files[0].Name) // FIXME: The old name should be empty if it's a file add type assert.Equal(t, "File-WoW", patch.Files[0].OldName) - assert.EqualValues(t, 1, patch.Files[0].Addition) - assert.EqualValues(t, 0, patch.Files[0].Deletion) + assert.Equal(t, 1, patch.Files[0].Addition) + assert.Equal(t, 0, patch.Files[0].Deletion) assert.Equal(t, gitdiff.DiffFileAdd, patch.Files[0].Type) } @@ -71,9 +71,9 @@ func TestAPIViewPulls(t *testing.T) { if assert.Len(t, files, 1) { assert.Equal(t, "File-WoW", files[0].Filename) assert.Empty(t, files[0].PreviousFilename) - assert.EqualValues(t, 1, files[0].Additions) - assert.EqualValues(t, 1, files[0].Changes) - assert.EqualValues(t, 0, files[0].Deletions) + assert.Equal(t, 1, files[0].Additions) + assert.Equal(t, 1, files[0].Changes) + assert.Equal(t, 0, files[0].Deletions) assert.Equal(t, "added", files[0].Status) } })) @@ -97,8 +97,8 @@ func TestAPIViewPulls(t *testing.T) { if assert.Len(t, patch.Files, 1) { assert.Equal(t, "README.md", patch.Files[0].Name) assert.Equal(t, "README.md", patch.Files[0].OldName) - assert.EqualValues(t, 4, patch.Files[0].Addition) - assert.EqualValues(t, 1, patch.Files[0].Deletion) + assert.Equal(t, 4, patch.Files[0].Addition) + assert.Equal(t, 1, patch.Files[0].Deletion) assert.Equal(t, gitdiff.DiffFileChange, patch.Files[0].Type) } @@ -107,9 +107,9 @@ func TestAPIViewPulls(t *testing.T) { if assert.Len(t, files, 1) { assert.Equal(t, "README.md", files[0].Filename) // FIXME: The PreviousFilename name should be the same as Filename if it's a file change - assert.Equal(t, "", files[0].PreviousFilename) - assert.EqualValues(t, 4, files[0].Additions) - assert.EqualValues(t, 1, files[0].Deletions) + assert.Empty(t, files[0].PreviousFilename) + assert.Equal(t, 4, files[0].Additions) + assert.Equal(t, 1, files[0].Deletions) assert.Equal(t, "changed", files[0].Status) } })) @@ -193,7 +193,7 @@ func TestAPICreatePullSuccess(t *testing.T) { session := loginUser(t, owner11.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner10.Name, repo10.Name), &api.CreatePullRequestOption{ - Head: fmt.Sprintf("%s:master", owner11.Name), + Head: owner11.Name + ":master", Base: "master", Title: "create a failure pr", }).AddTokenAuth(token) @@ -213,7 +213,7 @@ func TestAPICreatePullBasePermission(t *testing.T) { session := loginUser(t, user4.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) opts := &api.CreatePullRequestOption{ - Head: fmt.Sprintf("%s:master", repo11.OwnerName), + Head: repo11.OwnerName + ":master", Base: "master", Title: "create a failure pr", } @@ -241,7 +241,7 @@ func TestAPICreatePullHeadPermission(t *testing.T) { session := loginUser(t, user4.Name) token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) opts := &api.CreatePullRequestOption{ - Head: fmt.Sprintf("%s:master", repo11.OwnerName), + Head: repo11.OwnerName + ":master", Base: "master", Title: "create a failure pr", } @@ -269,7 +269,7 @@ func TestAPICreatePullSameRepoSuccess(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls", owner.Name, repo.Name), &api.CreatePullRequestOption{ - Head: fmt.Sprintf("%s:pr-to-update", owner.Name), + Head: owner.Name + ":pr-to-update", Base: "master", Title: "successfully create a PR between branches of the same repository", }).AddTokenAuth(token) @@ -290,7 +290,7 @@ func TestAPICreatePullWithFieldsSuccess(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) opts := &api.CreatePullRequestOption{ - Head: fmt.Sprintf("%s:master", owner11.Name), + Head: owner11.Name + ":master", Base: "master", Title: "create a failure pr", Body: "foobaaar", @@ -307,12 +307,12 @@ func TestAPICreatePullWithFieldsSuccess(t *testing.T) { DecodeJSON(t, res, pull) assert.NotNil(t, pull.Milestone) - assert.EqualValues(t, opts.Milestone, pull.Milestone.ID) + assert.Equal(t, opts.Milestone, pull.Milestone.ID) if assert.Len(t, pull.Assignees, 1) { - assert.EqualValues(t, opts.Assignees[0], owner10.Name) + assert.Equal(t, opts.Assignees[0], owner10.Name) } assert.NotNil(t, pull.Labels) - assert.EqualValues(t, opts.Labels[0], pull.Labels[0].ID) + assert.Equal(t, opts.Labels[0], pull.Labels[0].ID) } func TestAPICreatePullWithFieldsFailure(t *testing.T) { @@ -328,7 +328,7 @@ func TestAPICreatePullWithFieldsFailure(t *testing.T) { token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) opts := &api.CreatePullRequestOption{ - Head: fmt.Sprintf("%s:master", owner11.Name), + Head: owner11.Name + ":master", Base: "master", } @@ -366,7 +366,7 @@ func TestAPIEditPull(t *testing.T) { apiPull := new(api.PullRequest) resp := MakeRequest(t, req, http.StatusCreated) DecodeJSON(t, resp, apiPull) - assert.EqualValues(t, "master", apiPull.Base.Name) + assert.Equal(t, "master", apiPull.Base.Name) newTitle := "edit a this pr" newBody := "edited body" @@ -377,7 +377,7 @@ func TestAPIEditPull(t *testing.T) { }).AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusCreated) DecodeJSON(t, resp, apiPull) - assert.EqualValues(t, "feature/1", apiPull.Base.Name) + assert.Equal(t, "feature/1", apiPull.Base.Name) // check comment history pull := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: apiPull.ID}) err := pull.LoadIssue(db.DefaultContext) diff --git a/tests/integration/api_releases_test.go b/tests/integration/api_releases_test.go index b3d4928b7b..a3dbc0363b 100644 --- a/tests/integration/api_releases_test.go +++ b/tests/integration/api_releases_test.go @@ -97,7 +97,7 @@ func createNewReleaseUsingAPI(t *testing.T, token string, owner *user_model.User Title: newRelease.Title, } unittest.AssertExistsAndLoadBean(t, rel) - assert.EqualValues(t, newRelease.Note, rel.Note) + assert.Equal(t, newRelease.Note, rel.Note) return &newRelease } @@ -151,7 +151,7 @@ func TestAPICreateAndUpdateRelease(t *testing.T) { Title: newRelease.Title, } unittest.AssertExistsAndLoadBean(t, rel) - assert.EqualValues(t, rel.Note, newRelease.Note) + assert.Equal(t, rel.Note, newRelease.Note) } func TestAPICreateProtectedTagRelease(t *testing.T) { @@ -329,7 +329,7 @@ func TestAPIUploadAssetRelease(t *testing.T) { var attachment *api.Attachment DecodeJSON(t, resp, &attachment) - assert.EqualValues(t, filename, attachment.Name) + assert.Equal(t, filename, attachment.Name) assert.EqualValues(t, 104, attachment.Size) req = NewRequestWithBody(t, http.MethodPost, assetURL+"?name=test-asset", bytes.NewReader(body.Bytes())). @@ -340,7 +340,7 @@ func TestAPIUploadAssetRelease(t *testing.T) { var attachment2 *api.Attachment DecodeJSON(t, resp, &attachment2) - assert.EqualValues(t, "test-asset", attachment2.Name) + assert.Equal(t, "test-asset", attachment2.Name) assert.EqualValues(t, 104, attachment2.Size) }) @@ -358,7 +358,7 @@ func TestAPIUploadAssetRelease(t *testing.T) { var attachment *api.Attachment DecodeJSON(t, resp, &attachment) - assert.EqualValues(t, "stream.bin", attachment.Name) + assert.Equal(t, "stream.bin", attachment.Name) assert.EqualValues(t, 104, attachment.Size) }) } diff --git a/tests/integration/api_repo_archive_test.go b/tests/integration/api_repo_archive_test.go index 8589199da3..e698148d84 100644 --- a/tests/integration/api_repo_archive_test.go +++ b/tests/integration/api_repo_archive_test.go @@ -48,7 +48,7 @@ func TestAPIDownloadArchive(t *testing.T) { bs2, err := io.ReadAll(resp.Body) assert.NoError(t, err) // The locked URL should give the same bytes as the non-locked one - assert.EqualValues(t, bs, bs2) + assert.Equal(t, bs, bs2) link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master.bundle", user2.Name, repo.Name)) resp = MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) @@ -88,7 +88,7 @@ func TestAPIDownloadArchive2(t *testing.T) { bs2, err := io.ReadAll(resp.Body) assert.NoError(t, err) // The locked URL should give the same bytes as the non-locked one - assert.EqualValues(t, bs, bs2) + assert.Equal(t, bs, bs2) link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/bundle/master", user2.Name, repo.Name)) resp = MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) diff --git a/tests/integration/api_repo_branch_test.go b/tests/integration/api_repo_branch_test.go index 63080b308c..066eb366b1 100644 --- a/tests/integration/api_repo_branch_test.go +++ b/tests/integration/api_repo_branch_test.go @@ -4,11 +4,11 @@ package integration import ( - "bytes" "fmt" "io" "net/http" "net/url" + "strings" "testing" auth_model "code.gitea.io/gitea/models/auth" @@ -42,8 +42,8 @@ func TestAPIRepoBranchesPlain(t *testing.T) { var branches []*api.Branch assert.NoError(t, json.Unmarshal(bs, &branches)) assert.Len(t, branches, 2) - assert.EqualValues(t, "test_branch", branches[0].Name) - assert.EqualValues(t, "master", branches[1].Name) + assert.Equal(t, "test_branch", branches[0].Name) + assert.Equal(t, "master", branches[1].Name) link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch", repo3.Name)) MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden) @@ -53,20 +53,20 @@ func TestAPIRepoBranchesPlain(t *testing.T) { assert.NoError(t, err) var branch api.Branch assert.NoError(t, json.Unmarshal(bs, &branch)) - assert.EqualValues(t, "test_branch", branch.Name) + assert.Equal(t, "test_branch", branch.Name) MakeRequest(t, NewRequest(t, "POST", link.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden) req := NewRequest(t, "POST", link.String()).AddTokenAuth(token) req.Header.Add("Content-Type", "application/json") - req.Body = io.NopCloser(bytes.NewBufferString(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`)) + req.Body = io.NopCloser(strings.NewReader(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`)) resp = MakeRequest(t, req, http.StatusCreated) bs, err = io.ReadAll(resp.Body) assert.NoError(t, err) var branch2 api.Branch assert.NoError(t, json.Unmarshal(bs, &branch2)) - assert.EqualValues(t, "test_branch2", branch2.Name) - assert.EqualValues(t, branch.Commit.ID, branch2.Commit.ID) + assert.Equal(t, "test_branch2", branch2.Name) + assert.Equal(t, branch.Commit.ID, branch2.Commit.ID) resp = MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) bs, err = io.ReadAll(resp.Body) @@ -75,9 +75,9 @@ func TestAPIRepoBranchesPlain(t *testing.T) { branches = []*api.Branch{} assert.NoError(t, json.Unmarshal(bs, &branches)) assert.Len(t, branches, 3) - assert.EqualValues(t, "test_branch", branches[0].Name) - assert.EqualValues(t, "test_branch2", branches[1].Name) - assert.EqualValues(t, "master", branches[2].Name) + assert.Equal(t, "test_branch", branches[0].Name) + assert.Equal(t, "test_branch2", branches[1].Name) + assert.Equal(t, "master", branches[2].Name) link3, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch2", repo3.Name)) MakeRequest(t, NewRequest(t, "DELETE", link3.String()), http.StatusNotFound) @@ -104,8 +104,8 @@ func TestAPIRepoBranchesMirror(t *testing.T) { var branches []*api.Branch assert.NoError(t, json.Unmarshal(bs, &branches)) assert.Len(t, branches, 2) - assert.EqualValues(t, "test_branch", branches[0].Name) - assert.EqualValues(t, "master", branches[1].Name) + assert.Equal(t, "test_branch", branches[0].Name) + assert.Equal(t, "master", branches[1].Name) link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch", repo5.Name)) resp = MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(token), http.StatusOK) @@ -113,18 +113,18 @@ func TestAPIRepoBranchesMirror(t *testing.T) { assert.NoError(t, err) var branch api.Branch assert.NoError(t, json.Unmarshal(bs, &branch)) - assert.EqualValues(t, "test_branch", branch.Name) + assert.Equal(t, "test_branch", branch.Name) req := NewRequest(t, "POST", link.String()).AddTokenAuth(token) req.Header.Add("Content-Type", "application/json") - req.Body = io.NopCloser(bytes.NewBufferString(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`)) + req.Body = io.NopCloser(strings.NewReader(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`)) resp = MakeRequest(t, req, http.StatusForbidden) bs, err = io.ReadAll(resp.Body) assert.NoError(t, err) - assert.EqualValues(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}\n", string(bs)) + assert.Equal(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}\n", string(bs)) resp = MakeRequest(t, NewRequest(t, "DELETE", link2.String()).AddTokenAuth(token), http.StatusForbidden) bs, err = io.ReadAll(resp.Body) assert.NoError(t, err) - assert.EqualValues(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}\n", string(bs)) + assert.Equal(t, "{\"message\":\"Git Repository is a mirror.\",\"url\":\""+setting.AppURL+"api/swagger\"}\n", string(bs)) } diff --git a/tests/integration/api_repo_file_create_test.go b/tests/integration/api_repo_file_create_test.go index 2bf4a81280..0a7f37facb 100644 --- a/tests/integration/api_repo_file_create_test.go +++ b/tests/integration/api_repo_file_create_test.go @@ -8,7 +8,7 @@ import ( "fmt" "net/http" "net/url" - "path/filepath" + "path" "testing" "time" @@ -49,28 +49,42 @@ func getCreateFileOptions() api.CreateFileOptions { } } -func getExpectedFileResponseForCreate(repoFullName, commitID, treePath, latestCommitSHA string) *api.FileResponse { +func normalizeFileContentResponseCommitTime(c *api.ContentsResponse) { + // decoded JSON response may contain different timezone from the one parsed by git commit + // so we need to normalize the time to UTC to make "assert.Equal" pass + c.LastCommitterDate = c.LastCommitterDate.UTC() + c.LastAuthorDate = c.LastAuthorDate.UTC() +} + +type apiFileResponseInfo struct { + repoFullName, commitID, treePath, lastCommitSHA string + lastCommitterWhen, lastAuthorWhen time.Time +} + +func getExpectedFileResponseForCreate(info apiFileResponseInfo) *api.FileResponse { sha := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" encoding := "base64" content := "VGhpcyBpcyBuZXcgdGV4dA==" - selfURL := setting.AppURL + "api/v1/repos/" + repoFullName + "/contents/" + treePath + "?ref=master" - htmlURL := setting.AppURL + repoFullName + "/src/branch/master/" + treePath - gitURL := setting.AppURL + "api/v1/repos/" + repoFullName + "/git/blobs/" + sha - downloadURL := setting.AppURL + repoFullName + "/raw/branch/master/" + treePath - return &api.FileResponse{ + selfURL := setting.AppURL + "api/v1/repos/" + info.repoFullName + "/contents/" + info.treePath + "?ref=master" + htmlURL := setting.AppURL + info.repoFullName + "/src/branch/master/" + info.treePath + gitURL := setting.AppURL + "api/v1/repos/" + info.repoFullName + "/git/blobs/" + sha + downloadURL := setting.AppURL + info.repoFullName + "/raw/branch/master/" + info.treePath + ret := &api.FileResponse{ Content: &api.ContentsResponse{ - Name: filepath.Base(treePath), - Path: treePath, - SHA: sha, - LastCommitSHA: latestCommitSHA, - Size: 16, - Type: "file", - Encoding: &encoding, - Content: &content, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: path.Base(info.treePath), + Path: info.treePath, + SHA: sha, + LastCommitSHA: info.lastCommitSHA, + LastCommitterDate: info.lastCommitterWhen, + LastAuthorDate: info.lastAuthorWhen, + Size: 16, + Type: "file", + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, Links: &api.FileLinksResponse{ Self: &selfURL, GitURL: &gitURL, @@ -79,10 +93,10 @@ func getExpectedFileResponseForCreate(repoFullName, commitID, treePath, latestCo }, Commit: &api.FileCommitResponse{ CommitMeta: api.CommitMeta{ - URL: setting.AppURL + "api/v1/repos/" + repoFullName + "/git/commits/" + commitID, - SHA: commitID, + URL: setting.AppURL + "api/v1/repos/" + info.repoFullName + "/git/commits/" + info.commitID, + SHA: info.commitID, }, - HTMLURL: setting.AppURL + repoFullName + "/commit/" + commitID, + HTMLURL: setting.AppURL + info.repoFullName + "/commit/" + info.commitID, Author: &api.CommitUser{ Identity: api.Identity{ Name: "Anne Doe", @@ -106,6 +120,8 @@ func getExpectedFileResponseForCreate(repoFullName, commitID, treePath, latestCo Payload: "", }, } + normalizeFileContentResponseCommitTime(ret.Content) + return ret } func BenchmarkAPICreateFileSmall(b *testing.B) { @@ -167,21 +183,29 @@ func TestAPICreateFile(t *testing.T) { AddTokenAuth(token2) resp := MakeRequest(t, req, http.StatusCreated) gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo1) + defer gitRepo.Close() commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName) - latestCommit, _ := gitRepo.GetCommitByPath(treePath) - expectedFileResponse := getExpectedFileResponseForCreate("user2/repo1", commitID, treePath, latestCommit.ID.String()) + lastCommit, _ := gitRepo.GetCommitByPath(treePath) + expectedFileResponse := getExpectedFileResponseForCreate(apiFileResponseInfo{ + repoFullName: "user2/repo1", + commitID: commitID, + treePath: treePath, + lastCommitSHA: lastCommit.ID.String(), + lastCommitterWhen: lastCommit.Committer.When, + lastAuthorWhen: lastCommit.Author.When, + }) var fileResponse api.FileResponse DecodeJSON(t, resp, &fileResponse) - assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) - assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) - assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date) - assert.EqualValues(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email) - assert.EqualValues(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name) - assert.EqualValues(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date) - gitRepo.Close() + normalizeFileContentResponseCommitTime(fileResponse.Content) + assert.Equal(t, expectedFileResponse.Content, fileResponse.Content) + assert.Equal(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) + assert.Equal(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + assert.Equal(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) + assert.Equal(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) + assert.Equal(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date) + assert.Equal(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email) + assert.Equal(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name) + assert.Equal(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date) } // Test creating a file in a new branch @@ -198,10 +222,10 @@ func TestAPICreateFile(t *testing.T) { expectedSHA := "a635aa942442ddfdba07468cf9661c08fbdf0ebf" expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/new/file%d.txt", fileID) expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/new/file%d.txt", fileID) - assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) - assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) - assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) - assert.EqualValues(t, createFileOptions.Message+"\n", fileResponse.Commit.Message) + assert.Equal(t, expectedSHA, fileResponse.Content.SHA) + assert.Equal(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) + assert.Equal(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) + assert.Equal(t, createFileOptions.Message+"\n", fileResponse.Commit.Message) // Test creating a file without a message createFileOptions = getCreateFileOptions() @@ -213,7 +237,7 @@ func TestAPICreateFile(t *testing.T) { resp = MakeRequest(t, req, http.StatusCreated) DecodeJSON(t, resp, &fileResponse) expectedMessage := "Add " + treePath + "\n" - assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message) + assert.Equal(t, expectedMessage, fileResponse.Commit.Message) // Test trying to create a file that already exists, should fail createFileOptions = getCreateFileOptions() @@ -285,19 +309,27 @@ func TestAPICreateFile(t *testing.T) { resp = MakeRequest(t, req, http.StatusCreated) emptyRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user2", Name: "empty-repo"}) // public repo gitRepo, _ := gitrepo.OpenRepository(t.Context(), emptyRepo) + defer gitRepo.Close() commitID, _ := gitRepo.GetBranchCommitID(createFileOptions.NewBranchName) latestCommit, _ := gitRepo.GetCommitByPath(treePath) - expectedFileResponse := getExpectedFileResponseForCreate("user2/empty-repo", commitID, treePath, latestCommit.ID.String()) + expectedFileResponse := getExpectedFileResponseForCreate(apiFileResponseInfo{ + repoFullName: "user2/empty-repo", + commitID: commitID, + treePath: treePath, + lastCommitSHA: latestCommit.ID.String(), + lastCommitterWhen: latestCommit.Committer.When, + lastAuthorWhen: latestCommit.Author.When, + }) DecodeJSON(t, resp, &fileResponse) - assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) - assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) - assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date) - assert.EqualValues(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email) - assert.EqualValues(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name) - assert.EqualValues(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date) - gitRepo.Close() + normalizeFileContentResponseCommitTime(fileResponse.Content) + assert.Equal(t, expectedFileResponse.Content, fileResponse.Content) + assert.Equal(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) + assert.Equal(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + assert.Equal(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) + assert.Equal(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) + assert.Equal(t, expectedFileResponse.Commit.Author.Date, fileResponse.Commit.Author.Date) + assert.Equal(t, expectedFileResponse.Commit.Committer.Email, fileResponse.Commit.Committer.Email) + assert.Equal(t, expectedFileResponse.Commit.Committer.Name, fileResponse.Commit.Committer.Name) + assert.Equal(t, expectedFileResponse.Commit.Committer.Date, fileResponse.Commit.Committer.Date) }) } diff --git a/tests/integration/api_repo_file_delete_test.go b/tests/integration/api_repo_file_delete_test.go index 7c93307e19..47730cd933 100644 --- a/tests/integration/api_repo_file_delete_test.go +++ b/tests/integration/api_repo_file_delete_test.go @@ -87,7 +87,7 @@ func TestAPIDeleteFile(t *testing.T) { DecodeJSON(t, resp, &fileResponse) assert.NotNil(t, fileResponse) assert.Nil(t, fileResponse.Content) - assert.EqualValues(t, deleteFileOptions.Message+"\n", fileResponse.Commit.Message) + assert.Equal(t, deleteFileOptions.Message+"\n", fileResponse.Commit.Message) // Test deleting file without a message fileID++ @@ -100,7 +100,7 @@ func TestAPIDeleteFile(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &fileResponse) expectedMessage := "Delete " + treePath + "\n" - assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message) + assert.Equal(t, expectedMessage, fileResponse.Commit.Message) // Test deleting a file with the wrong SHA fileID++ diff --git a/tests/integration/api_repo_file_update_test.go b/tests/integration/api_repo_file_update_test.go index c8ce94a3f5..1605cfbd0b 100644 --- a/tests/integration/api_repo_file_update_test.go +++ b/tests/integration/api_repo_file_update_test.go @@ -8,7 +8,7 @@ import ( "fmt" "net/http" "net/url" - "path/filepath" + "path" "testing" auth_model "code.gitea.io/gitea/models/auth" @@ -47,28 +47,30 @@ func getUpdateFileOptions() *api.UpdateFileOptions { } } -func getExpectedFileResponseForUpdate(commitID, treePath, lastCommitSHA string) *api.FileResponse { +func getExpectedFileResponseForUpdate(info apiFileResponseInfo) *api.FileResponse { sha := "08bd14b2e2852529157324de9c226b3364e76136" encoding := "base64" content := "VGhpcyBpcyB1cGRhdGVkIHRleHQ=" - selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + treePath + "?ref=master" - htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + treePath + selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + info.treePath + "?ref=master" + htmlURL := setting.AppURL + "user2/repo1/src/branch/master/" + info.treePath gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha - downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath - return &api.FileResponse{ + downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + info.treePath + ret := &api.FileResponse{ Content: &api.ContentsResponse{ - Name: filepath.Base(treePath), - Path: treePath, - SHA: sha, - LastCommitSHA: lastCommitSHA, - Type: "file", - Size: 20, - Encoding: &encoding, - Content: &content, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: path.Base(info.treePath), + Path: info.treePath, + SHA: sha, + LastCommitSHA: info.lastCommitSHA, + LastCommitterDate: info.lastCommitterWhen, + LastAuthorDate: info.lastAuthorWhen, + Type: "file", + Size: 20, + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, Links: &api.FileLinksResponse{ Self: &selfURL, GitURL: &gitURL, @@ -77,10 +79,10 @@ func getExpectedFileResponseForUpdate(commitID, treePath, lastCommitSHA string) }, Commit: &api.FileCommitResponse{ CommitMeta: api.CommitMeta{ - URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/" + commitID, - SHA: commitID, + URL: setting.AppURL + "api/v1/repos/user2/repo1/git/commits/" + info.commitID, + SHA: info.commitID, }, - HTMLURL: setting.AppURL + "user2/repo1/commit/" + commitID, + HTMLURL: setting.AppURL + "user2/repo1/commit/" + info.commitID, Author: &api.CommitUser{ Identity: api.Identity{ Name: "John Doe", @@ -102,6 +104,8 @@ func getExpectedFileResponseForUpdate(commitID, treePath, lastCommitSHA string) Payload: "", }, } + normalizeFileContentResponseCommitTime(ret.Content) + return ret } func TestAPIUpdateFile(t *testing.T) { @@ -135,17 +139,24 @@ func TestAPIUpdateFile(t *testing.T) { AddTokenAuth(token2) resp := MakeRequest(t, req, http.StatusOK) gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo1) + defer gitRepo.Close() commitID, _ := gitRepo.GetBranchCommitID(updateFileOptions.NewBranchName) lasCommit, _ := gitRepo.GetCommitByPath(treePath) - expectedFileResponse := getExpectedFileResponseForUpdate(commitID, treePath, lasCommit.ID.String()) + expectedFileResponse := getExpectedFileResponseForUpdate(apiFileResponseInfo{ + commitID: commitID, + treePath: treePath, + lastCommitSHA: lasCommit.ID.String(), + lastCommitterWhen: lasCommit.Committer.When, + lastAuthorWhen: lasCommit.Author.When, + }) var fileResponse api.FileResponse DecodeJSON(t, resp, &fileResponse) - assert.EqualValues(t, expectedFileResponse.Content, fileResponse.Content) - assert.EqualValues(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) - assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) - gitRepo.Close() + normalizeFileContentResponseCommitTime(fileResponse.Content) + assert.Equal(t, expectedFileResponse.Content, fileResponse.Content) + assert.Equal(t, expectedFileResponse.Commit.SHA, fileResponse.Commit.SHA) + assert.Equal(t, expectedFileResponse.Commit.HTMLURL, fileResponse.Commit.HTMLURL) + assert.Equal(t, expectedFileResponse.Commit.Author.Email, fileResponse.Commit.Author.Email) + assert.Equal(t, expectedFileResponse.Commit.Author.Name, fileResponse.Commit.Author.Name) } // Test updating a file in a new branch @@ -163,10 +174,10 @@ func TestAPIUpdateFile(t *testing.T) { expectedSHA := "08bd14b2e2852529157324de9c226b3364e76136" expectedHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/update/file%d.txt", fileID) expectedDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID) - assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) - assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) - assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) - assert.EqualValues(t, updateFileOptions.Message+"\n", fileResponse.Commit.Message) + assert.Equal(t, expectedSHA, fileResponse.Content.SHA) + assert.Equal(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) + assert.Equal(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) + assert.Equal(t, updateFileOptions.Message+"\n", fileResponse.Commit.Message) // Test updating a file and renaming it updateFileOptions = getUpdateFileOptions() @@ -183,9 +194,9 @@ func TestAPIUpdateFile(t *testing.T) { expectedSHA = "08bd14b2e2852529157324de9c226b3364e76136" expectedHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/master/rename/update/file%d.txt", fileID) expectedDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID) - assert.EqualValues(t, expectedSHA, fileResponse.Content.SHA) - assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) - assert.EqualValues(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) + assert.Equal(t, expectedSHA, fileResponse.Content.SHA) + assert.Equal(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) + assert.Equal(t, expectedDownloadURL, *fileResponse.Content.DownloadURL) // Test updating a file without a message updateFileOptions = getUpdateFileOptions() @@ -199,7 +210,7 @@ func TestAPIUpdateFile(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &fileResponse) expectedMessage := "Update " + treePath + "\n" - assert.EqualValues(t, expectedMessage, fileResponse.Commit.Message) + assert.Equal(t, expectedMessage, fileResponse.Commit.Message) // Test updating a file with the wrong SHA fileID++ diff --git a/tests/integration/api_repo_files_change_test.go b/tests/integration/api_repo_files_change_test.go index aca58025d2..999bcdc680 100644 --- a/tests/integration/api_repo_files_change_test.go +++ b/tests/integration/api_repo_files_change_test.go @@ -77,51 +77,56 @@ func TestAPIChangeFiles(t *testing.T) { token4 := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) // Test changing files in repo1 which user2 owns, try both with branch and empty branch - for _, branch := range [...]string{ - "master", // Branch - "", // Empty branch - } { - fileID++ - createTreePath := fmt.Sprintf("new/file%d.txt", fileID) - updateTreePath := fmt.Sprintf("update/file%d.txt", fileID) - deleteTreePath := fmt.Sprintf("delete/file%d.txt", fileID) - createFile(user2, repo1, updateTreePath) - createFile(user2, repo1, deleteTreePath) - changeFilesOptions := getChangeFilesOptions() - changeFilesOptions.BranchName = branch - changeFilesOptions.Files[0].Path = createTreePath - changeFilesOptions.Files[1].Path = updateTreePath - changeFilesOptions.Files[2].Path = deleteTreePath - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name), &changeFilesOptions). - AddTokenAuth(token2) - resp := MakeRequest(t, req, http.StatusCreated) - gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo1) - commitID, _ := gitRepo.GetBranchCommitID(changeFilesOptions.NewBranchName) - createLasCommit, _ := gitRepo.GetCommitByPath(createTreePath) - updateLastCommit, _ := gitRepo.GetCommitByPath(updateTreePath) - expectedCreateFileResponse := getExpectedFileResponseForCreate(fmt.Sprintf("%v/%v", user2.Name, repo1.Name), commitID, createTreePath, createLasCommit.ID.String()) - expectedUpdateFileResponse := getExpectedFileResponseForUpdate(commitID, updateTreePath, updateLastCommit.ID.String()) - var filesResponse api.FilesResponse - DecodeJSON(t, resp, &filesResponse) - - // check create file - assert.EqualValues(t, expectedCreateFileResponse.Content, filesResponse.Files[0]) - - // check update file - assert.EqualValues(t, expectedUpdateFileResponse.Content, filesResponse.Files[1]) - - // test commit info - assert.EqualValues(t, expectedCreateFileResponse.Commit.SHA, filesResponse.Commit.SHA) - assert.EqualValues(t, expectedCreateFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) - assert.EqualValues(t, expectedCreateFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email) - assert.EqualValues(t, expectedCreateFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name) - assert.EqualValues(t, expectedCreateFileResponse.Commit.Committer.Email, filesResponse.Commit.Committer.Email) - assert.EqualValues(t, expectedCreateFileResponse.Commit.Committer.Name, filesResponse.Commit.Committer.Name) - - // test delete file - assert.Nil(t, filesResponse.Files[2]) - - gitRepo.Close() + for _, branch := range []string{"master", ""} { + t.Run("Branch-"+branch, func(t *testing.T) { + fileID++ + createTreePath := fmt.Sprintf("new/file%d.txt", fileID) + updateTreePath := fmt.Sprintf("update/file%d.txt", fileID) + deleteTreePath := fmt.Sprintf("delete/file%d.txt", fileID) + _, _ = createFile(user2, repo1, updateTreePath) + _, _ = createFile(user2, repo1, deleteTreePath) + changeFilesOptions := getChangeFilesOptions() + changeFilesOptions.BranchName = branch + changeFilesOptions.Files[0].Path = createTreePath + changeFilesOptions.Files[1].Path = updateTreePath + changeFilesOptions.Files[2].Path = deleteTreePath + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents", user2.Name, repo1.Name), &changeFilesOptions). + AddTokenAuth(token2) + resp := MakeRequest(t, req, http.StatusCreated) + gitRepo, _ := gitrepo.OpenRepository(t.Context(), repo1) + defer gitRepo.Close() + commitID, _ := gitRepo.GetBranchCommitID(changeFilesOptions.NewBranchName) + createLasCommit, _ := gitRepo.GetCommitByPath(createTreePath) + updateLastCommit, _ := gitRepo.GetCommitByPath(updateTreePath) + expectedCreateFileResponse := getExpectedFileResponseForCreate(apiFileResponseInfo{ + repoFullName: fmt.Sprintf("%s/%s", user2.Name, repo1.Name), + commitID: commitID, + treePath: createTreePath, + lastCommitSHA: createLasCommit.ID.String(), + lastCommitterWhen: createLasCommit.Committer.When, + lastAuthorWhen: createLasCommit.Author.When, + }) + expectedUpdateFileResponse := getExpectedFileResponseForUpdate(apiFileResponseInfo{ + commitID: commitID, + treePath: updateTreePath, + lastCommitSHA: updateLastCommit.ID.String(), + lastCommitterWhen: updateLastCommit.Committer.When, + lastAuthorWhen: updateLastCommit.Author.When, + }) + var filesResponse api.FilesResponse + DecodeJSON(t, resp, &filesResponse) + normalizeFileContentResponseCommitTime(filesResponse.Files[0]) + normalizeFileContentResponseCommitTime(filesResponse.Files[1]) + assert.Equal(t, expectedCreateFileResponse.Content, filesResponse.Files[0]) // check create file + assert.Equal(t, expectedUpdateFileResponse.Content, filesResponse.Files[1]) // check update file + assert.Equal(t, expectedCreateFileResponse.Commit.SHA, filesResponse.Commit.SHA) + assert.Equal(t, expectedCreateFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) + assert.Equal(t, expectedCreateFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email) + assert.Equal(t, expectedCreateFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name) + assert.Equal(t, expectedCreateFileResponse.Commit.Committer.Email, filesResponse.Commit.Committer.Email) + assert.Equal(t, expectedCreateFileResponse.Commit.Committer.Name, filesResponse.Commit.Committer.Name) + assert.Nil(t, filesResponse.Files[2]) // test delete file + }) } // Test changing files in a new branch @@ -149,15 +154,15 @@ func TestAPIChangeFiles(t *testing.T) { expectedUpdateSHA := "08bd14b2e2852529157324de9c226b3364e76136" expectedUpdateHTMLURL := fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/new_branch/update/file%d.txt", fileID) expectedUpdateDownloadURL := fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/new_branch/update/file%d.txt", fileID) - assert.EqualValues(t, expectedCreateSHA, filesResponse.Files[0].SHA) - assert.EqualValues(t, expectedCreateHTMLURL, *filesResponse.Files[0].HTMLURL) - assert.EqualValues(t, expectedCreateDownloadURL, *filesResponse.Files[0].DownloadURL) - assert.EqualValues(t, expectedUpdateSHA, filesResponse.Files[1].SHA) - assert.EqualValues(t, expectedUpdateHTMLURL, *filesResponse.Files[1].HTMLURL) - assert.EqualValues(t, expectedUpdateDownloadURL, *filesResponse.Files[1].DownloadURL) + assert.Equal(t, expectedCreateSHA, filesResponse.Files[0].SHA) + assert.Equal(t, expectedCreateHTMLURL, *filesResponse.Files[0].HTMLURL) + assert.Equal(t, expectedCreateDownloadURL, *filesResponse.Files[0].DownloadURL) + assert.Equal(t, expectedUpdateSHA, filesResponse.Files[1].SHA) + assert.Equal(t, expectedUpdateHTMLURL, *filesResponse.Files[1].HTMLURL) + assert.Equal(t, expectedUpdateDownloadURL, *filesResponse.Files[1].DownloadURL) assert.Nil(t, filesResponse.Files[2]) - assert.EqualValues(t, changeFilesOptions.Message+"\n", filesResponse.Commit.Message) + assert.Equal(t, changeFilesOptions.Message+"\n", filesResponse.Commit.Message) // Test updating a file and renaming it changeFilesOptions = getChangeFilesOptions() @@ -175,9 +180,9 @@ func TestAPIChangeFiles(t *testing.T) { expectedUpdateSHA = "08bd14b2e2852529157324de9c226b3364e76136" expectedUpdateHTMLURL = fmt.Sprintf(setting.AppURL+"user2/repo1/src/branch/master/rename/update/file%d.txt", fileID) expectedUpdateDownloadURL = fmt.Sprintf(setting.AppURL+"user2/repo1/raw/branch/master/rename/update/file%d.txt", fileID) - assert.EqualValues(t, expectedUpdateSHA, filesResponse.Files[0].SHA) - assert.EqualValues(t, expectedUpdateHTMLURL, *filesResponse.Files[0].HTMLURL) - assert.EqualValues(t, expectedUpdateDownloadURL, *filesResponse.Files[0].DownloadURL) + assert.Equal(t, expectedUpdateSHA, filesResponse.Files[0].SHA) + assert.Equal(t, expectedUpdateHTMLURL, *filesResponse.Files[0].HTMLURL) + assert.Equal(t, expectedUpdateDownloadURL, *filesResponse.Files[0].DownloadURL) // Test updating a file without a message changeFilesOptions = getChangeFilesOptions() @@ -197,7 +202,7 @@ func TestAPIChangeFiles(t *testing.T) { resp = MakeRequest(t, req, http.StatusCreated) DecodeJSON(t, resp, &filesResponse) expectedMessage := fmt.Sprintf("Add %v\nUpdate %v\nDelete %v\n", createTreePath, updateTreePath, deleteTreePath) - assert.EqualValues(t, expectedMessage, filesResponse.Commit.Message) + assert.Equal(t, expectedMessage, filesResponse.Commit.Message) // Test updating a file with the wrong SHA fileID++ diff --git a/tests/integration/api_repo_get_contents_list_test.go b/tests/integration/api_repo_get_contents_list_test.go index 1ba74490a3..9b192c6304 100644 --- a/tests/integration/api_repo_get_contents_list_test.go +++ b/tests/integration/api_repo_get_contents_list_test.go @@ -6,8 +6,9 @@ package integration import ( "net/http" "net/url" - "path/filepath" + "path" "testing" + "time" auth_model "code.gitea.io/gitea/models/auth" repo_model "code.gitea.io/gitea/models/repo" @@ -31,16 +32,18 @@ func getExpectedContentsListResponseForContents(ref, refType, lastCommitSHA stri downloadURL := setting.AppURL + "user2/repo1/raw/" + refType + "/" + ref + "/" + treePath return []*api.ContentsResponse{ { - Name: filepath.Base(treePath), - Path: treePath, - SHA: sha, - LastCommitSHA: lastCommitSHA, - Type: "file", - Size: 30, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: path.Base(treePath), + Path: treePath, + SHA: sha, + LastCommitSHA: lastCommitSHA, + LastCommitterDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)), + LastAuthorDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)), + Type: "file", + Size: 30, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, Links: &api.FileLinksResponse{ Self: &selfURL, GitURL: &gitURL, @@ -99,7 +102,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { lastCommit, err := gitRepo.GetCommitByPath("README.md") assert.NoError(t, err) expectedContentsListResponse := getExpectedContentsListResponseForContents(ref, refType, lastCommit.ID.String()) - assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) + assert.Equal(t, expectedContentsListResponse, contentsListResponse) // No ref refType = "branch" @@ -109,7 +112,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { assert.NotNil(t, contentsListResponse) expectedContentsListResponse = getExpectedContentsListResponseForContents(repo1.DefaultBranch, refType, lastCommit.ID.String()) - assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) + assert.Equal(t, expectedContentsListResponse, contentsListResponse) // ref is the branch we created above in setup ref = newBranch @@ -123,7 +126,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { lastCommit, err = branchCommit.GetCommitByPath("README.md") assert.NoError(t, err) expectedContentsListResponse = getExpectedContentsListResponseForContents(ref, refType, lastCommit.ID.String()) - assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) + assert.Equal(t, expectedContentsListResponse, contentsListResponse) // ref is the new tag we created above in setup ref = newTag @@ -137,7 +140,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { lastCommit, err = tagCommit.GetCommitByPath("README.md") assert.NoError(t, err) expectedContentsListResponse = getExpectedContentsListResponseForContents(ref, refType, lastCommit.ID.String()) - assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) + assert.Equal(t, expectedContentsListResponse, contentsListResponse) // ref is a commit ref = commitID @@ -147,7 +150,7 @@ func testAPIGetContentsList(t *testing.T, u *url.URL) { DecodeJSON(t, resp, &contentsListResponse) assert.NotNil(t, contentsListResponse) expectedContentsListResponse = getExpectedContentsListResponseForContents(ref, refType, commitID) - assert.EqualValues(t, expectedContentsListResponse, contentsListResponse) + assert.Equal(t, expectedContentsListResponse, contentsListResponse) // Test file contents a file with a bad ref ref = "badref" diff --git a/tests/integration/api_repo_get_contents_test.go b/tests/integration/api_repo_get_contents_test.go index d0f61da0c0..1571787886 100644 --- a/tests/integration/api_repo_get_contents_test.go +++ b/tests/integration/api_repo_get_contents_test.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" "testing" + "time" auth_model "code.gitea.io/gitea/models/auth" repo_model "code.gitea.io/gitea/models/repo" @@ -33,18 +34,20 @@ func getExpectedContentsResponseForContents(ref, refType, lastCommitSHA string) gitURL := setting.AppURL + "api/v1/repos/user2/repo1/git/blobs/" + sha downloadURL := setting.AppURL + "user2/repo1/raw/" + refType + "/" + ref + "/" + treePath return &api.ContentsResponse{ - Name: treePath, - Path: treePath, - SHA: sha, - LastCommitSHA: lastCommitSHA, - Type: "file", - Size: 30, - Encoding: &encoding, - Content: &content, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: treePath, + Path: treePath, + SHA: sha, + LastCommitSHA: lastCommitSHA, + LastCommitterDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)), + LastAuthorDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)), + Type: "file", + Size: 30, + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, Links: &api.FileLinksResponse{ Self: &selfURL, GitURL: &gitURL, @@ -102,7 +105,7 @@ func testAPIGetContents(t *testing.T, u *url.URL) { assert.NotNil(t, contentsResponse) lastCommit, _ := gitRepo.GetCommitByPath("README.md") expectedContentsResponse := getExpectedContentsResponseForContents(ref, refType, lastCommit.ID.String()) - assert.EqualValues(t, *expectedContentsResponse, contentsResponse) + assert.Equal(t, *expectedContentsResponse, contentsResponse) // No ref refType = "branch" @@ -111,7 +114,7 @@ func testAPIGetContents(t *testing.T, u *url.URL) { DecodeJSON(t, resp, &contentsResponse) assert.NotNil(t, contentsResponse) expectedContentsResponse = getExpectedContentsResponseForContents(repo1.DefaultBranch, refType, lastCommit.ID.String()) - assert.EqualValues(t, *expectedContentsResponse, contentsResponse) + assert.Equal(t, *expectedContentsResponse, contentsResponse) // ref is the branch we created above in setup ref = newBranch @@ -123,7 +126,7 @@ func testAPIGetContents(t *testing.T, u *url.URL) { branchCommit, _ := gitRepo.GetBranchCommit(ref) lastCommit, _ = branchCommit.GetCommitByPath("README.md") expectedContentsResponse = getExpectedContentsResponseForContents(ref, refType, lastCommit.ID.String()) - assert.EqualValues(t, *expectedContentsResponse, contentsResponse) + assert.Equal(t, *expectedContentsResponse, contentsResponse) // ref is the new tag we created above in setup ref = newTag @@ -135,7 +138,7 @@ func testAPIGetContents(t *testing.T, u *url.URL) { tagCommit, _ := gitRepo.GetTagCommit(ref) lastCommit, _ = tagCommit.GetCommitByPath("README.md") expectedContentsResponse = getExpectedContentsResponseForContents(ref, refType, lastCommit.ID.String()) - assert.EqualValues(t, *expectedContentsResponse, contentsResponse) + assert.Equal(t, *expectedContentsResponse, contentsResponse) // ref is a commit ref = commitID @@ -145,7 +148,7 @@ func testAPIGetContents(t *testing.T, u *url.URL) { DecodeJSON(t, resp, &contentsResponse) assert.NotNil(t, contentsResponse) expectedContentsResponse = getExpectedContentsResponseForContents(ref, refType, commitID) - assert.EqualValues(t, *expectedContentsResponse, contentsResponse) + assert.Equal(t, *expectedContentsResponse, contentsResponse) // Test file contents a file with a bad ref ref = "badref" @@ -178,22 +181,22 @@ func TestAPIGetContentsRefFormats(t *testing.T) { resp := MakeRequest(t, NewRequest(t, http.MethodGet, "/api/v1/repos/user2/repo1/raw/"+file), http.StatusOK) raw, err := io.ReadAll(resp.Body) assert.NoError(t, err) - assert.EqualValues(t, content, string(raw)) + assert.Equal(t, content, string(raw)) resp = MakeRequest(t, NewRequest(t, http.MethodGet, "/api/v1/repos/user2/repo1/raw/"+sha+"/"+file), http.StatusOK) raw, err = io.ReadAll(resp.Body) assert.NoError(t, err) - assert.EqualValues(t, content, string(raw)) + assert.Equal(t, content, string(raw)) resp = MakeRequest(t, NewRequest(t, http.MethodGet, "/api/v1/repos/user2/repo1/raw/"+file+"?ref="+sha), http.StatusOK) raw, err = io.ReadAll(resp.Body) assert.NoError(t, err) - assert.EqualValues(t, content, string(raw)) + assert.Equal(t, content, string(raw)) resp = MakeRequest(t, NewRequest(t, http.MethodGet, "/api/v1/repos/user2/repo1/raw/"+file+"?ref=master"), http.StatusOK) raw, err = io.ReadAll(resp.Body) assert.NoError(t, err) - assert.EqualValues(t, content, string(raw)) + assert.Equal(t, content, string(raw)) _ = MakeRequest(t, NewRequest(t, http.MethodGet, "/api/v1/repos/user2/repo1/raw/docs/README.md?ref=main"), http.StatusNotFound) _ = MakeRequest(t, NewRequest(t, http.MethodGet, "/api/v1/repos/user2/repo1/raw/README.md?ref=main"), http.StatusOK) diff --git a/tests/integration/api_repo_git_commits_test.go b/tests/integration/api_repo_git_commits_test.go index c4c626eb49..a1584d4629 100644 --- a/tests/integration/api_repo_git_commits_test.go +++ b/tests/integration/api_repo_git_commits_test.go @@ -72,12 +72,12 @@ func TestAPIReposGitCommitList(t *testing.T) { DecodeJSON(t, resp, &apiData) assert.Len(t, apiData, 2) - assert.EqualValues(t, "cfe3b3c1fd36fba04f9183287b106497e1afe986", apiData[0].CommitMeta.SHA) + assert.Equal(t, "cfe3b3c1fd36fba04f9183287b106497e1afe986", apiData[0].CommitMeta.SHA) compareCommitFiles(t, []string{"link_hi", "test.csv"}, apiData[0].Files) - assert.EqualValues(t, "c8e31bc7688741a5287fcde4fbb8fc129ca07027", apiData[1].CommitMeta.SHA) + assert.Equal(t, "c8e31bc7688741a5287fcde4fbb8fc129ca07027", apiData[1].CommitMeta.SHA) compareCommitFiles(t, []string{"test.csv"}, apiData[1].Files) - assert.EqualValues(t, "2", resp.Header().Get("X-Total")) + assert.Equal(t, "2", resp.Header().Get("X-Total")) } func TestAPIReposGitCommitListNotMaster(t *testing.T) { @@ -96,14 +96,14 @@ func TestAPIReposGitCommitListNotMaster(t *testing.T) { DecodeJSON(t, resp, &apiData) assert.Len(t, apiData, 3) - assert.EqualValues(t, "69554a64c1e6030f051e5c3f94bfbd773cd6a324", apiData[0].CommitMeta.SHA) + assert.Equal(t, "69554a64c1e6030f051e5c3f94bfbd773cd6a324", apiData[0].CommitMeta.SHA) compareCommitFiles(t, []string{"readme.md"}, apiData[0].Files) - assert.EqualValues(t, "27566bd5738fc8b4e3fef3c5e72cce608537bd95", apiData[1].CommitMeta.SHA) + assert.Equal(t, "27566bd5738fc8b4e3fef3c5e72cce608537bd95", apiData[1].CommitMeta.SHA) compareCommitFiles(t, []string{"readme.md"}, apiData[1].Files) - assert.EqualValues(t, "5099b81332712fe655e34e8dd63574f503f61811", apiData[2].CommitMeta.SHA) + assert.Equal(t, "5099b81332712fe655e34e8dd63574f503f61811", apiData[2].CommitMeta.SHA) compareCommitFiles(t, []string{"readme.md"}, apiData[2].Files) - assert.EqualValues(t, "3", resp.Header().Get("X-Total")) + assert.Equal(t, "3", resp.Header().Get("X-Total")) } func TestAPIReposGitCommitListPage2Empty(t *testing.T) { @@ -177,7 +177,7 @@ func TestDownloadCommitDiffOrPatch(t *testing.T) { reqDiff := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/git/commits/f27c2b2b03dcab38beaf89b0ab4ff61f6de63441.diff", user.Name). AddTokenAuth(token) resp := MakeRequest(t, reqDiff, http.StatusOK) - assert.EqualValues(t, + assert.Equal(t, "commit f27c2b2b03dcab38beaf89b0ab4ff61f6de63441\nAuthor: User2 \nDate: Sun Aug 6 19:55:01 2017 +0200\n\n good signed commit\n\ndiff --git a/readme.md b/readme.md\nnew file mode 100644\nindex 0000000..458121c\n--- /dev/null\n+++ b/readme.md\n@@ -0,0 +1 @@\n+good sign\n", resp.Body.String()) @@ -185,7 +185,7 @@ func TestDownloadCommitDiffOrPatch(t *testing.T) { reqPatch := NewRequestf(t, "GET", "/api/v1/repos/%s/repo16/git/commits/f27c2b2b03dcab38beaf89b0ab4ff61f6de63441.patch", user.Name). AddTokenAuth(token) resp = MakeRequest(t, reqPatch, http.StatusOK) - assert.EqualValues(t, + assert.Equal(t, "From f27c2b2b03dcab38beaf89b0ab4ff61f6de63441 Mon Sep 17 00:00:00 2001\nFrom: User2 \nDate: Sun, 6 Aug 2017 19:55:01 +0200\nSubject: [PATCH] good signed commit\n\n---\n readme.md | 1 +\n 1 file changed, 1 insertion(+)\n create mode 100644 readme.md\n\ndiff --git a/readme.md b/readme.md\nnew file mode 100644\nindex 0000000..458121c\n--- /dev/null\n+++ b/readme.md\n@@ -0,0 +1 @@\n+good sign\n", resp.Body.String()) } @@ -208,7 +208,7 @@ func TestGetFileHistory(t *testing.T) { assert.Equal(t, "f27c2b2b03dcab38beaf89b0ab4ff61f6de63441", apiData[0].CommitMeta.SHA) compareCommitFiles(t, []string{"readme.md"}, apiData[0].Files) - assert.EqualValues(t, "1", resp.Header().Get("X-Total")) + assert.Equal(t, "1", resp.Header().Get("X-Total")) } func TestGetFileHistoryNotOnMaster(t *testing.T) { @@ -229,5 +229,5 @@ func TestGetFileHistoryNotOnMaster(t *testing.T) { assert.Equal(t, "c8e31bc7688741a5287fcde4fbb8fc129ca07027", apiData[0].CommitMeta.SHA) compareCommitFiles(t, []string{"test.csv"}, apiData[0].Files) - assert.EqualValues(t, "1", resp.Header().Get("X-Total")) + assert.Equal(t, "1", resp.Header().Get("X-Total")) } diff --git a/tests/integration/api_repo_lfs_locks_test.go b/tests/integration/api_repo_lfs_locks_test.go index 4ba01e6d9b..161fa45dc6 100644 --- a/tests/integration/api_repo_lfs_locks_test.go +++ b/tests/integration/api_repo_lfs_locks_test.go @@ -112,7 +112,7 @@ func TestAPILFSLocksLogged(t *testing.T) { var lfsLock api.LFSLockResponse DecodeJSON(t, resp, &lfsLock) assert.Equal(t, test.user.Name, lfsLock.Lock.Owner.Name) - assert.EqualValues(t, lfsLock.Lock.LockedAt.Format(time.RFC3339), lfsLock.Lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second + assert.Equal(t, lfsLock.Lock.LockedAt.Format(time.RFC3339), lfsLock.Lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second for _, id := range test.addTime { resultsTests[id].locksTimes = append(resultsTests[id].locksTimes, time.Now()) } @@ -129,9 +129,9 @@ func TestAPILFSLocksLogged(t *testing.T) { DecodeJSON(t, resp, &lfsLocks) assert.Len(t, lfsLocks.Locks, test.totalCount) for i, lock := range lfsLocks.Locks { - assert.EqualValues(t, test.locksOwners[i].Name, lock.Owner.Name) + assert.Equal(t, test.locksOwners[i].Name, lock.Owner.Name) assert.WithinDuration(t, test.locksTimes[i], lock.LockedAt, 10*time.Second) - assert.EqualValues(t, lock.LockedAt.Format(time.RFC3339), lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second + assert.Equal(t, lock.LockedAt.Format(time.RFC3339), lock.LockedAt.Format(time.RFC3339Nano)) // locked at should be rounded to second } req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/%s.git/info/lfs/locks/verify", test.repo.FullName()), map[string]string{}) @@ -143,7 +143,7 @@ func TestAPILFSLocksLogged(t *testing.T) { assert.Len(t, lfsLocksVerify.Ours, test.oursCount) assert.Len(t, lfsLocksVerify.Theirs, test.theirsCount) for _, lock := range lfsLocksVerify.Ours { - assert.EqualValues(t, test.user.Name, lock.Owner.Name) + assert.Equal(t, test.user.Name, lock.Owner.Name) deleteTests = append(deleteTests, struct { user *user_model.User repo *repo_model.Repository diff --git a/tests/integration/api_repo_lfs_migrate_test.go b/tests/integration/api_repo_lfs_migrate_test.go index 8b4d79db02..6ca6f9afab 100644 --- a/tests/integration/api_repo_lfs_migrate_test.go +++ b/tests/integration/api_repo_lfs_migrate_test.go @@ -40,7 +40,7 @@ func TestAPIRepoLFSMigrateLocal(t *testing.T) { LFS: true, }).AddTokenAuth(token) resp := MakeRequest(t, req, NoExpectedStatus) - assert.EqualValues(t, http.StatusCreated, resp.Code) + assert.Equal(t, http.StatusCreated, resp.Code) store := lfs.NewContentStore() ok, _ := store.Verify(lfs.Pointer{Oid: "fb8f7d8435968c4f82a726a92395be4d16f2f63116caf36c8ad35c60831ab041", Size: 6}) diff --git a/tests/integration/api_repo_raw_test.go b/tests/integration/api_repo_raw_test.go index e5f83d1c80..e9d741925f 100644 --- a/tests/integration/api_repo_raw_test.go +++ b/tests/integration/api_repo_raw_test.go @@ -30,11 +30,11 @@ func TestAPIReposRaw(t *testing.T) { req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/raw/%s/README.md", user.Name, ref). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) - assert.EqualValues(t, "file", resp.Header().Get("x-gitea-object-type")) + assert.Equal(t, "file", resp.Header().Get("x-gitea-object-type")) } // Test default branch req := NewRequestf(t, "GET", "/api/v1/repos/%s/repo1/raw/README.md", user.Name). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) - assert.EqualValues(t, "file", resp.Header().Get("x-gitea-object-type")) + assert.Equal(t, "file", resp.Header().Get("x-gitea-object-type")) } diff --git a/tests/integration/api_repo_tags_test.go b/tests/integration/api_repo_tags_test.go index a7f021ca4f..3932a8ba2b 100644 --- a/tests/integration/api_repo_tags_test.go +++ b/tests/integration/api_repo_tags_test.go @@ -48,10 +48,10 @@ func TestAPIRepoTags(t *testing.T) { assert.Len(t, tags, 2) for _, tag := range tags { if tag.Name != "v1.1" { - assert.EqualValues(t, newTag.Name, tag.Name) - assert.EqualValues(t, newTag.Message, tag.Message) - assert.EqualValues(t, "nice!\nand some text", tag.Message) - assert.EqualValues(t, newTag.Commit.SHA, tag.Commit.SHA) + assert.Equal(t, newTag.Name, tag.Name) + assert.Equal(t, newTag.Message, tag.Message) + assert.Equal(t, "nice!\nand some text", tag.Message) + assert.Equal(t, newTag.Commit.SHA, tag.Commit.SHA) } } @@ -61,7 +61,7 @@ func TestAPIRepoTags(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) var tag *api.Tag DecodeJSON(t, resp, &tag) - assert.EqualValues(t, newTag, tag) + assert.Equal(t, newTag, tag) // delete tag delReq := NewRequestf(t, "DELETE", "/api/v1/repos/%s/%s/tags/%s", user.Name, repoName, newTag.Name). diff --git a/tests/integration/api_repo_teams_test.go b/tests/integration/api_repo_teams_test.go index 07d065b02b..143e3dd29f 100644 --- a/tests/integration/api_repo_teams_test.go +++ b/tests/integration/api_repo_teams_test.go @@ -37,15 +37,15 @@ func TestAPIRepoTeams(t *testing.T) { var teams []*api.Team DecodeJSON(t, res, &teams) if assert.Len(t, teams, 2) { - assert.EqualValues(t, "Owners", teams[0].Name) + assert.Equal(t, "Owners", teams[0].Name) assert.True(t, teams[0].CanCreateOrgRepo) assert.True(t, util.SliceSortedEqual(unit.AllUnitKeyNames(), teams[0].Units), "%v == %v", unit.AllUnitKeyNames(), teams[0].Units) - assert.EqualValues(t, "owner", teams[0].Permission) + assert.Equal(t, "owner", teams[0].Permission) - assert.EqualValues(t, "test_team", teams[1].Name) + assert.Equal(t, "test_team", teams[1].Name) assert.False(t, teams[1].CanCreateOrgRepo) - assert.EqualValues(t, []string{"repo.issues"}, teams[1].Units) - assert.EqualValues(t, "write", teams[1].Permission) + assert.Equal(t, []string{"repo.issues"}, teams[1].Units) + assert.Equal(t, "write", teams[1].Permission) } // IsTeam @@ -54,7 +54,7 @@ func TestAPIRepoTeams(t *testing.T) { res = MakeRequest(t, req, http.StatusOK) var team *api.Team DecodeJSON(t, res, &team) - assert.EqualValues(t, teams[1], team) + assert.Equal(t, teams[1], team) req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/teams/%s", publicOrgRepo.FullName(), "NonExistingTeam")). AddTokenAuth(token) diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go index 22f26d87d4..672c2a2c8b 100644 --- a/tests/integration/api_repo_test.go +++ b/tests/integration/api_repo_test.go @@ -37,7 +37,7 @@ func TestAPIUserReposNotLogin(t *testing.T) { unittest.Cond("is_private = ?", false)) assert.Len(t, apiRepos, expectedLen) for _, repo := range apiRepos { - assert.EqualValues(t, user.ID, repo.Owner.ID) + assert.Equal(t, user.ID, repo.Owner.ID) assert.False(t, repo.Private) } } @@ -45,7 +45,7 @@ func TestAPIUserReposNotLogin(t *testing.T) { func TestAPIUserReposWithWrongToken(t *testing.T) { defer tests.PrepareTestEnv(t)() user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - wrongToken := fmt.Sprintf("Bearer %s", "wrong_token") + wrongToken := "Bearer " + "wrong_token" req := NewRequestf(t, "GET", "/api/v1/users/%s/repos", user.Name). AddTokenAuth(wrongToken) resp := MakeRequest(t, req, http.StatusUnauthorized) @@ -266,25 +266,25 @@ func TestAPIViewRepo(t *testing.T) { resp := MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &repo) assert.EqualValues(t, 1, repo.ID) - assert.EqualValues(t, "repo1", repo.Name) - assert.EqualValues(t, 2, repo.Releases) - assert.EqualValues(t, 1, repo.OpenIssues) - assert.EqualValues(t, 3, repo.OpenPulls) + assert.Equal(t, "repo1", repo.Name) + assert.Equal(t, 2, repo.Releases) + assert.Equal(t, 1, repo.OpenIssues) + assert.Equal(t, 3, repo.OpenPulls) req = NewRequest(t, "GET", "/api/v1/repos/user12/repo10") resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &repo) assert.EqualValues(t, 10, repo.ID) - assert.EqualValues(t, "repo10", repo.Name) - assert.EqualValues(t, 1, repo.OpenPulls) - assert.EqualValues(t, 1, repo.Forks) + assert.Equal(t, "repo10", repo.Name) + assert.Equal(t, 1, repo.OpenPulls) + assert.Equal(t, 1, repo.Forks) req = NewRequest(t, "GET", "/api/v1/repos/user5/repo4") resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &repo) assert.EqualValues(t, 4, repo.ID) - assert.EqualValues(t, "repo4", repo.Name) - assert.EqualValues(t, 1, repo.Stars) + assert.Equal(t, "repo4", repo.Name) + assert.Equal(t, 1, repo.Stars) } func TestAPIOrgRepos(t *testing.T) { @@ -337,9 +337,9 @@ func TestAPIOrgReposWithCodeUnitDisabled(t *testing.T) { var units []unit_model.Type units = append(units, unit_model.TypeCode) - if err := repo_service.UpdateRepositoryUnits(db.DefaultContext, repo21, nil, units); err != nil { - assert.Fail(t, "should have been able to delete code repository unit; failed to %v", err) - } + err := repo_service.UpdateRepositoryUnits(db.DefaultContext, repo21, nil, units) + assert.NoError(t, err, "should have been able to delete code repository unit") + assert.False(t, repo21.UnitEnabled(db.DefaultContext, unit_model.TypeCode)) session := loginUser(t, "user2") @@ -403,12 +403,12 @@ func TestAPIRepoMigrate(t *testing.T) { case "Remote visit addressed rate limitation.": t.Log("test hit github rate limitation") case "You can not import from disallowed hosts.": - assert.EqualValues(t, "private-ip", testCase.repoName) + assert.Equal(t, "private-ip", testCase.repoName) default: - assert.FailNow(t, "unexpected error '%v' on url '%s'", respJSON["message"], testCase.cloneURL) + assert.FailNow(t, "unexpected error", "unexpected error '%v' on url '%s'", respJSON["message"], testCase.cloneURL) } } else { - assert.EqualValues(t, testCase.expectedStatus, resp.Code) + assert.Equal(t, testCase.expectedStatus, resp.Code) } } } diff --git a/tests/integration/api_repo_topic_test.go b/tests/integration/api_repo_topic_test.go index a10e159b78..82d0c54ca8 100644 --- a/tests/integration/api_repo_topic_test.go +++ b/tests/integration/api_repo_topic_test.go @@ -30,7 +30,7 @@ func TestAPITopicSearch(t *testing.T) { res := MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) DecodeJSON(t, res, &topics) assert.Len(t, topics.TopicNames, 6) - assert.EqualValues(t, "6", res.Header().Get("x-total-count")) + assert.Equal(t, "6", res.Header().Get("x-total-count")) // pagination search topics first page topics.TopicNames = nil @@ -40,7 +40,7 @@ func TestAPITopicSearch(t *testing.T) { res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) DecodeJSON(t, res, &topics) assert.Len(t, topics.TopicNames, 4) - assert.EqualValues(t, "6", res.Header().Get("x-total-count")) + assert.Equal(t, "6", res.Header().Get("x-total-count")) // pagination search topics second page topics.TopicNames = nil @@ -50,7 +50,7 @@ func TestAPITopicSearch(t *testing.T) { res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) DecodeJSON(t, res, &topics) assert.Len(t, topics.TopicNames, 2) - assert.EqualValues(t, "6", res.Header().Get("x-total-count")) + assert.Equal(t, "6", res.Header().Get("x-total-count")) // add keyword search query = url.Values{"page": []string{"1"}, "limit": []string{"4"}} @@ -66,8 +66,8 @@ func TestAPITopicSearch(t *testing.T) { DecodeJSON(t, res, &topics) if assert.Len(t, topics.TopicNames, 1) { assert.EqualValues(t, 2, topics.TopicNames[0].ID) - assert.EqualValues(t, "database", topics.TopicNames[0].Name) - assert.EqualValues(t, 1, topics.TopicNames[0].RepoCount) + assert.Equal(t, "database", topics.TopicNames[0].Name) + assert.Equal(t, 1, topics.TopicNames[0].RepoCount) } } diff --git a/tests/integration/api_settings_test.go b/tests/integration/api_settings_test.go index 9881578fba..6f433148f5 100644 --- a/tests/integration/api_settings_test.go +++ b/tests/integration/api_settings_test.go @@ -30,7 +30,7 @@ func TestAPIExposedSettings(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiSettings) - assert.EqualValues(t, &api.GeneralAPISettings{ + assert.Equal(t, &api.GeneralAPISettings{ MaxResponseItems: setting.API.MaxResponseItems, DefaultPagingNum: setting.API.DefaultPagingNum, DefaultGitTreesPerPage: setting.API.DefaultGitTreesPerPage, @@ -42,7 +42,7 @@ func TestAPIExposedSettings(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &repo) - assert.EqualValues(t, &api.GeneralRepoSettings{ + assert.Equal(t, &api.GeneralRepoSettings{ MirrorsDisabled: !setting.Mirror.Enabled, HTTPGitDisabled: setting.Repository.DisableHTTPGit, MigrationsDisabled: setting.Repository.DisableMigrations, @@ -55,7 +55,7 @@ func TestAPIExposedSettings(t *testing.T) { resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &attachment) - assert.EqualValues(t, &api.GeneralAttachmentSettings{ + assert.Equal(t, &api.GeneralAttachmentSettings{ Enabled: setting.Attachment.Enabled, AllowedTypes: setting.Attachment.AllowedTypes, MaxFiles: setting.Attachment.MaxFiles, diff --git a/tests/integration/api_team_test.go b/tests/integration/api_team_test.go index d14c66ff2c..e54203ed50 100644 --- a/tests/integration/api_team_test.go +++ b/tests/integration/api_team_test.go @@ -40,9 +40,9 @@ func TestAPITeam(t *testing.T) { var apiTeam api.Team DecodeJSON(t, resp, &apiTeam) - assert.EqualValues(t, team.ID, apiTeam.ID) + assert.Equal(t, team.ID, apiTeam.ID) assert.Equal(t, team.Name, apiTeam.Name) - assert.EqualValues(t, convert.ToOrganization(db.DefaultContext, org), apiTeam.Organization) + assert.Equal(t, convert.ToOrganization(db.DefaultContext, org), apiTeam.Organization) // non team member user will not access the teams details teamUser2 := unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{ID: 3}) @@ -78,9 +78,9 @@ func TestAPITeam(t *testing.T) { apiTeam = api.Team{} DecodeJSON(t, resp, &apiTeam) checkTeamResponse(t, "CreateTeam1", &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, - teamToCreate.Permission, teamToCreate.Units, nil) + "none", teamToCreate.Units, nil) checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, - teamToCreate.Permission, teamToCreate.Units, nil) + "none", teamToCreate.Units, nil) teamID := apiTeam.ID // Edit team. @@ -149,9 +149,9 @@ func TestAPITeam(t *testing.T) { apiTeam = api.Team{} DecodeJSON(t, resp, &apiTeam) checkTeamResponse(t, "CreateTeam2", &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, - "read", nil, teamToCreate.UnitsMap) + "none", nil, teamToCreate.UnitsMap) checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, - "read", nil, teamToCreate.UnitsMap) + "none", nil, teamToCreate.UnitsMap) teamID = apiTeam.ID // Edit team. @@ -171,9 +171,9 @@ func TestAPITeam(t *testing.T) { apiTeam = api.Team{} DecodeJSON(t, resp, &apiTeam) checkTeamResponse(t, "EditTeam2", &apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, - "read", nil, teamToEdit.UnitsMap) + "none", nil, teamToEdit.UnitsMap) checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories, - "read", nil, teamToEdit.UnitsMap) + "none", nil, teamToEdit.UnitsMap) // Edit team Description only editDescription = "second team" @@ -184,9 +184,9 @@ func TestAPITeam(t *testing.T) { apiTeam = api.Team{} DecodeJSON(t, resp, &apiTeam) checkTeamResponse(t, "EditTeam2_DescOnly", &apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, - "read", nil, teamToEdit.UnitsMap) + "none", nil, teamToEdit.UnitsMap) checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories, - "read", nil, teamToEdit.UnitsMap) + "none", nil, teamToEdit.UnitsMap) // Read team. teamRead = unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) @@ -247,10 +247,10 @@ func checkTeamResponse(t *testing.T, testName string, apiTeam *api.Team, name, d if units != nil { sort.StringSlice(units).Sort() sort.StringSlice(apiTeam.Units).Sort() - assert.EqualValues(t, units, apiTeam.Units, "units") + assert.Equal(t, units, apiTeam.Units, "units") } if unitsMap != nil { - assert.EqualValues(t, unitsMap, apiTeam.UnitsMap, "unitsMap") + assert.Equal(t, unitsMap, apiTeam.UnitsMap, "unitsMap") } }) } diff --git a/tests/integration/api_team_user_test.go b/tests/integration/api_team_user_test.go index 6c80bc9f80..cbbbe00a9e 100644 --- a/tests/integration/api_team_user_test.go +++ b/tests/integration/api_team_user_test.go @@ -21,29 +21,31 @@ import ( func TestAPITeamUser(t *testing.T) { defer tests.PrepareTestEnv(t)() + user2Session := loginUser(t, "user2") + user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteOrganization) - normalUsername := "user2" - session := loginUser(t, normalUsername) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization) - req := NewRequest(t, "GET", "/api/v1/teams/1/members/user1"). - AddTokenAuth(token) - MakeRequest(t, req, http.StatusNotFound) + t.Run("User2ReadUser1", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/teams/1/members/user1").AddTokenAuth(user2Token) + MakeRequest(t, req, http.StatusNotFound) + }) - req = NewRequest(t, "GET", "/api/v1/teams/1/members/user2"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - var user2 *api.User - DecodeJSON(t, resp, &user2) - user2.Created = user2.Created.In(time.Local) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + t.Run("User2ReadSelf", func(t *testing.T) { + // read self user + req := NewRequest(t, "GET", "/api/v1/teams/1/members/user2").AddTokenAuth(user2Token) + resp := MakeRequest(t, req, http.StatusOK) + var user2 *api.User + DecodeJSON(t, resp, &user2) + user2.Created = user2.Created.In(time.Local) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) - expectedUser := convert.ToUser(db.DefaultContext, user, user) + expectedUser := convert.ToUser(db.DefaultContext, user, user) - // test time via unix timestamp - assert.EqualValues(t, expectedUser.LastLogin.Unix(), user2.LastLogin.Unix()) - assert.EqualValues(t, expectedUser.Created.Unix(), user2.Created.Unix()) - expectedUser.LastLogin = user2.LastLogin - expectedUser.Created = user2.Created + // test time via unix timestamp + assert.Equal(t, expectedUser.LastLogin.Unix(), user2.LastLogin.Unix()) + assert.Equal(t, expectedUser.Created.Unix(), user2.Created.Unix()) + expectedUser.LastLogin = user2.LastLogin + expectedUser.Created = user2.Created - assert.Equal(t, expectedUser, user2) + assert.Equal(t, expectedUser, user2) + }) } diff --git a/tests/integration/api_token_test.go b/tests/integration/api_token_test.go index 01d18ef6f1..1770358d21 100644 --- a/tests/integration/api_token_test.go +++ b/tests/integration/api_token_test.go @@ -507,7 +507,7 @@ func runTestCase(t *testing.T, testCase *requiredScopeTestCase, user *user_model } else if minRequiredLevel == auth_model.Write { unauthorizedLevel = auth_model.Read } else { - assert.FailNow(t, "Invalid test case: Unknown access token scope level: %v", minRequiredLevel) + assert.FailNow(t, "Invalid test case", "Unknown access token scope level: %v", minRequiredLevel) } } diff --git a/tests/integration/api_twofa_test.go b/tests/integration/api_twofa_test.go index 18e6fa91b7..b8052c0812 100644 --- a/tests/integration/api_twofa_test.go +++ b/tests/integration/api_twofa_test.go @@ -94,7 +94,7 @@ func TestBasicAuthWithWebAuthn(t *testing.T) { } var userParsed userResponse DecodeJSON(t, resp, &userParsed) - assert.EqualValues(t, "Basic authorization is not allowed while webAuthn enrolled", userParsed.Message) + assert.Equal(t, "Basic authorization is not allowed while webAuthn enrolled", userParsed.Message) // user32 has webauthn enrolled, he can't request git protocol with basic auth req = NewRequest(t, "GET", "/user2/repo1/info/refs") diff --git a/tests/integration/api_user_block_test.go b/tests/integration/api_user_block_test.go index ae6b9eb849..6f73b089df 100644 --- a/tests/integration/api_user_block_test.go +++ b/tests/integration/api_user_block_test.go @@ -76,7 +76,7 @@ func TestBlockUser(t *testing.T) { blockeeName := "user10" t.Run("Block", func(t *testing.T) { - req := NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/blocks/%s", blockeeName)) + req := NewRequest(t, "PUT", "/api/v1/user/blocks/"+blockeeName) MakeRequest(t, req, http.StatusUnauthorized) assert.EqualValues(t, 1, countStars(t, blockerID, blockeeID)) @@ -84,7 +84,7 @@ func TestBlockUser(t *testing.T) { assert.EqualValues(t, 1, countRepositoryTransfers(t, blockerID, blockeeID)) assert.EqualValues(t, 1, countCollaborations(t, blockerID, blockeeID)) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/blocks/%s", blockeeName)). + req = NewRequest(t, "GET", "/api/v1/user/blocks/"+blockeeName). AddTokenAuth(blockerToken) MakeRequest(t, req, http.StatusNotFound) @@ -97,15 +97,15 @@ func TestBlockUser(t *testing.T) { assert.EqualValues(t, 0, countRepositoryTransfers(t, blockerID, blockeeID)) assert.EqualValues(t, 0, countCollaborations(t, blockerID, blockeeID)) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/blocks/%s", blockeeName)). + req = NewRequest(t, "GET", "/api/v1/user/blocks/"+blockeeName). AddTokenAuth(blockerToken) MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/blocks/%s", blockeeName)). + req = NewRequest(t, "PUT", "/api/v1/user/blocks/"+blockeeName). AddTokenAuth(blockerToken) MakeRequest(t, req, http.StatusBadRequest) // can't block blocked user - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/blocks/%s", "org3")). + req = NewRequest(t, "PUT", "/api/v1/user/blocks/"+"org3"). AddTokenAuth(blockerToken) MakeRequest(t, req, http.StatusBadRequest) // can't block organization @@ -124,18 +124,18 @@ func TestBlockUser(t *testing.T) { }) t.Run("Unblock", func(t *testing.T) { - req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/blocks/%s", blockeeName)) + req := NewRequest(t, "DELETE", "/api/v1/user/blocks/"+blockeeName) MakeRequest(t, req, http.StatusUnauthorized) - req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/blocks/%s", blockeeName)). + req = NewRequest(t, "DELETE", "/api/v1/user/blocks/"+blockeeName). AddTokenAuth(blockerToken) MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/blocks/%s", blockeeName)). + req = NewRequest(t, "DELETE", "/api/v1/user/blocks/"+blockeeName). AddTokenAuth(blockerToken) MakeRequest(t, req, http.StatusBadRequest) - req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/blocks/%s", "org3")). + req = NewRequest(t, "DELETE", "/api/v1/user/blocks/"+"org3"). AddTokenAuth(blockerToken) MakeRequest(t, req, http.StatusBadRequest) diff --git a/tests/integration/api_user_email_test.go b/tests/integration/api_user_email_test.go index 6441e2ed8e..5b6f0708ea 100644 --- a/tests/integration/api_user_email_test.go +++ b/tests/integration/api_user_email_test.go @@ -28,7 +28,7 @@ func TestAPIListEmails(t *testing.T) { var emails []*api.Email DecodeJSON(t, resp, &emails) - assert.EqualValues(t, []*api.Email{ + assert.Equal(t, []*api.Email{ { Email: "user2@example.com", Verified: true, @@ -66,7 +66,7 @@ func TestAPIAddEmail(t *testing.T) { var emails []*api.Email DecodeJSON(t, resp, &emails) - assert.EqualValues(t, []*api.Email{ + assert.Equal(t, []*api.Email{ { Email: "user2@example.com", Verified: true, @@ -119,7 +119,7 @@ func TestAPIDeleteEmail(t *testing.T) { var emails []*api.Email DecodeJSON(t, resp, &emails) - assert.EqualValues(t, []*api.Email{ + assert.Equal(t, []*api.Email{ { Email: "user2@example.com", Verified: true, diff --git a/tests/integration/api_user_follow_test.go b/tests/integration/api_user_follow_test.go index fe20af6769..6cb31a6802 100644 --- a/tests/integration/api_user_follow_test.go +++ b/tests/integration/api_user_follow_test.go @@ -32,7 +32,7 @@ func TestAPIFollow(t *testing.T) { t.Run("Follow", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/following/%s", user1)). + req := NewRequest(t, "PUT", "/api/v1/user/following/"+user1). AddTokenAuth(token2) MakeRequest(t, req, http.StatusNoContent) @@ -110,11 +110,11 @@ func TestAPIFollow(t *testing.T) { t.Run("CheckMyFollowing", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/following/%s", user1)). + req := NewRequest(t, "GET", "/api/v1/user/following/"+user1). AddTokenAuth(token2) MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/following/%s", user2)). + req = NewRequest(t, "GET", "/api/v1/user/following/"+user2). AddTokenAuth(token1) MakeRequest(t, req, http.StatusNotFound) }) @@ -122,7 +122,7 @@ func TestAPIFollow(t *testing.T) { t.Run("Unfollow", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/following/%s", user1)). + req := NewRequest(t, "DELETE", "/api/v1/user/following/"+user1). AddTokenAuth(token2) MakeRequest(t, req, http.StatusNoContent) }) diff --git a/tests/integration/api_user_info_test.go b/tests/integration/api_user_info_test.go index 89f7266859..06353eabe0 100644 --- a/tests/integration/api_user_info_test.go +++ b/tests/integration/api_user_info_test.go @@ -4,7 +4,6 @@ package integration import ( - "fmt" "net/http" "testing" @@ -31,7 +30,7 @@ func TestAPIUserInfo(t *testing.T) { t.Run("GetInfo", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s", user2)). + req := NewRequest(t, "GET", "/api/v1/users/"+user2). AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) @@ -39,17 +38,17 @@ func TestAPIUserInfo(t *testing.T) { DecodeJSON(t, resp, &u) assert.Equal(t, user2, u.UserName) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s", user2)) + req = NewRequest(t, "GET", "/api/v1/users/"+user2) MakeRequest(t, req, http.StatusNotFound) // test if the placaholder Mail is returned if a User is not logged in - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s", org3.Name)) + req = NewRequest(t, "GET", "/api/v1/users/"+org3.Name) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &u) assert.Equal(t, org3.GetPlaceholderEmail(), u.Email) // Test if the correct Mail is returned if a User is logged in - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s", org3.Name)). + req = NewRequest(t, "GET", "/api/v1/users/"+org3.Name). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &u) diff --git a/tests/integration/api_user_search_test.go b/tests/integration/api_user_search_test.go index 5604a14259..97264969eb 100644 --- a/tests/integration/api_user_search_test.go +++ b/tests/integration/api_user_search_test.go @@ -66,7 +66,7 @@ func TestAPIUserSearchNotLoggedIn(t *testing.T) { for _, user := range results.Data { assert.Contains(t, user.UserName, query) modelUser = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: user.ID}) - assert.EqualValues(t, modelUser.GetPlaceholderEmail(), user.Email) + assert.Equal(t, modelUser.GetPlaceholderEmail(), user.Email) } } @@ -85,8 +85,8 @@ func TestAPIUserSearchSystemUsers(t *testing.T) { assert.NotEmpty(t, results.Data) if assert.Len(t, results.Data, 1) { user := results.Data[0] - assert.EqualValues(t, user.UserName, systemUser.Name) - assert.EqualValues(t, user.ID, systemUser.ID) + assert.Equal(t, user.UserName, systemUser.Name) + assert.Equal(t, user.ID, systemUser.ID) } }) } @@ -108,7 +108,7 @@ func TestAPIUserSearchAdminLoggedInUserHidden(t *testing.T) { for _, user := range results.Data { assert.Contains(t, user.UserName, query) assert.NotEmpty(t, user.Email) - assert.EqualValues(t, "private", user.Visibility) + assert.Equal(t, "private", user.Visibility) } } diff --git a/tests/integration/api_user_secrets_test.go b/tests/integration/api_user_secrets_test.go index 56bf30e804..10024ac090 100644 --- a/tests/integration/api_user_secrets_test.go +++ b/tests/integration/api_user_secrets_test.go @@ -4,7 +4,6 @@ package integration import ( - "fmt" "net/http" "testing" @@ -55,7 +54,7 @@ func TestAPIUserSecrets(t *testing.T) { } for _, c := range cases { - req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/user/actions/secrets/%s", c.Name), api.CreateOrUpdateSecretOption{ + req := NewRequestWithJSON(t, "PUT", "/api/v1/user/actions/secrets/"+c.Name, api.CreateOrUpdateSecretOption{ Data: "data", }).AddTokenAuth(token) MakeRequest(t, req, c.ExpectedStatus) @@ -64,7 +63,7 @@ func TestAPIUserSecrets(t *testing.T) { t.Run("Update", func(t *testing.T) { name := "update_secret" - url := fmt.Sprintf("/api/v1/user/actions/secrets/%s", name) + url := "/api/v1/user/actions/secrets/" + name req := NewRequestWithJSON(t, "PUT", url, api.CreateOrUpdateSecretOption{ Data: "initial", @@ -79,7 +78,7 @@ func TestAPIUserSecrets(t *testing.T) { t.Run("Delete", func(t *testing.T) { name := "delete_secret" - url := fmt.Sprintf("/api/v1/user/actions/secrets/%s", name) + url := "/api/v1/user/actions/secrets/" + name req := NewRequestWithJSON(t, "PUT", url, api.CreateOrUpdateSecretOption{ Data: "initial", diff --git a/tests/integration/api_user_star_test.go b/tests/integration/api_user_star_test.go index 368756528a..989e7ab1d1 100644 --- a/tests/integration/api_user_star_test.go +++ b/tests/integration/api_user_star_test.go @@ -32,13 +32,13 @@ func TestAPIStar(t *testing.T) { t.Run("Star", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/starred/%s", repo)). + req := NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). AddTokenAuth(tokenWithUserScope) MakeRequest(t, req, http.StatusNoContent) // blocked user can't star a repo user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/starred/%s", repo)). + req = NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteRepository)) MakeRequest(t, req, http.StatusForbidden) }) @@ -76,11 +76,11 @@ func TestAPIStar(t *testing.T) { t.Run("IsStarring", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/starred/%s", repo)). + req := NewRequest(t, "GET", "/api/v1/user/starred/"+repo). AddTokenAuth(tokenWithUserScope) MakeRequest(t, req, http.StatusNoContent) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/starred/%s", repo+"notexisting")). + req = NewRequest(t, "GET", "/api/v1/user/starred/"+repo+"notexisting"). AddTokenAuth(tokenWithUserScope) MakeRequest(t, req, http.StatusNotFound) }) @@ -88,7 +88,7 @@ func TestAPIStar(t *testing.T) { t.Run("Unstar", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/starred/%s", repo)). + req := NewRequest(t, "DELETE", "/api/v1/user/starred/"+repo). AddTokenAuth(tokenWithUserScope) MakeRequest(t, req, http.StatusNoContent) }) @@ -109,12 +109,12 @@ func TestAPIStarDisabled(t *testing.T) { t.Run("Star", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/starred/%s", repo)). + req := NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). AddTokenAuth(tokenWithUserScope) MakeRequest(t, req, http.StatusForbidden) user34 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 34}) - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/starred/%s", repo)). + req = NewRequest(t, "PUT", "/api/v1/user/starred/"+repo). AddTokenAuth(getUserToken(t, user34.Name, auth_model.AccessTokenScopeWriteRepository)) MakeRequest(t, req, http.StatusForbidden) }) @@ -138,11 +138,11 @@ func TestAPIStarDisabled(t *testing.T) { t.Run("IsStarring", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/starred/%s", repo)). + req := NewRequest(t, "GET", "/api/v1/user/starred/"+repo). AddTokenAuth(tokenWithUserScope) MakeRequest(t, req, http.StatusForbidden) - req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/starred/%s", repo+"notexisting")). + req = NewRequest(t, "GET", "/api/v1/user/starred/"+repo+"notexisting"). AddTokenAuth(tokenWithUserScope) MakeRequest(t, req, http.StatusForbidden) }) @@ -150,7 +150,7 @@ func TestAPIStarDisabled(t *testing.T) { t.Run("Unstar", func(t *testing.T) { defer tests.PrintCurrentTest(t)() - req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/starred/%s", repo)). + req := NewRequest(t, "DELETE", "/api/v1/user/starred/"+repo). AddTokenAuth(tokenWithUserScope) MakeRequest(t, req, http.StatusForbidden) }) diff --git a/tests/integration/api_user_variables_test.go b/tests/integration/api_user_variables_test.go index 9fd84ddf81..367b83e7d4 100644 --- a/tests/integration/api_user_variables_test.go +++ b/tests/integration/api_user_variables_test.go @@ -4,7 +4,6 @@ package integration import ( - "fmt" "net/http" "testing" @@ -63,7 +62,7 @@ func TestAPIUserVariables(t *testing.T) { } for _, c := range cases { - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/user/actions/variables/%s", c.Name), api.CreateVariableOption{ + req := NewRequestWithJSON(t, "POST", "/api/v1/user/actions/variables/"+c.Name, api.CreateVariableOption{ Value: "value", }).AddTokenAuth(token) MakeRequest(t, req, c.ExpectedStatus) @@ -72,7 +71,7 @@ func TestAPIUserVariables(t *testing.T) { t.Run("UpdateUserVariable", func(t *testing.T) { variableName := "test_update_var" - url := fmt.Sprintf("/api/v1/user/actions/variables/%s", variableName) + url := "/api/v1/user/actions/variables/" + variableName req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ Value: "initial_val", }).AddTokenAuth(token) @@ -118,7 +117,7 @@ func TestAPIUserVariables(t *testing.T) { } for _, c := range cases { - req := NewRequestWithJSON(t, "PUT", fmt.Sprintf("/api/v1/user/actions/variables/%s", c.Name), api.UpdateVariableOption{ + req := NewRequestWithJSON(t, "PUT", "/api/v1/user/actions/variables/"+c.Name, api.UpdateVariableOption{ Name: c.UpdateName, Value: "updated_val", }).AddTokenAuth(token) @@ -128,7 +127,7 @@ func TestAPIUserVariables(t *testing.T) { t.Run("DeleteRepoVariable", func(t *testing.T) { variableName := "test_delete_var" - url := fmt.Sprintf("/api/v1/user/actions/variables/%s", variableName) + url := "/api/v1/user/actions/variables/" + variableName req := NewRequestWithJSON(t, "POST", url, api.CreateVariableOption{ Value: "initial_val", diff --git a/tests/integration/db_collation_test.go b/tests/integration/db_collation_test.go index acec4aa5d1..339bfce71c 100644 --- a/tests/integration/db_collation_test.go +++ b/tests/integration/db_collation_test.go @@ -75,7 +75,7 @@ func TestDatabaseCollation(t *testing.T) { defer test.MockVariableValue(&setting.Database.CharsetCollation, "utf8mb4_bin")() r, err := db.CheckCollations(x) assert.NoError(t, err) - assert.EqualValues(t, "utf8mb4_bin", r.ExpectedCollation) + assert.Equal(t, "utf8mb4_bin", r.ExpectedCollation) assert.NoError(t, db.ConvertDatabaseTable()) r, err = db.CheckCollations(x) assert.NoError(t, err) diff --git a/tests/integration/delete_user_test.go b/tests/integration/delete_user_test.go index ad3c882882..4b02c4725a 100644 --- a/tests/integration/delete_user_test.go +++ b/tests/integration/delete_user_test.go @@ -4,7 +4,6 @@ package integration import ( - "fmt" "net/http" "testing" @@ -34,7 +33,7 @@ func TestUserDeleteAccount(t *testing.T) { session := loginUser(t, "user8") csrf := GetUserCSRFToken(t, session) - urlStr := fmt.Sprintf("/user/settings/account/delete?password=%s", userPassword) + urlStr := "/user/settings/account/delete?password=" + userPassword req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ "_csrf": csrf, }) @@ -49,7 +48,7 @@ func TestUserDeleteAccountStillOwnRepos(t *testing.T) { session := loginUser(t, "user2") csrf := GetUserCSRFToken(t, session) - urlStr := fmt.Sprintf("/user/settings/account/delete?password=%s", userPassword) + urlStr := "/user/settings/account/delete?password=" + userPassword req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ "_csrf": csrf, }) diff --git a/tests/integration/dump_restore_test.go b/tests/integration/dump_restore_test.go index 54683becaa..d2d43075c3 100644 --- a/tests/integration/dump_restore_test.go +++ b/tests/integration/dump_restore_test.go @@ -178,7 +178,7 @@ func (c *compareDump) assertEquals(repoBefore, repoAfter *repo_model.Repository) }).([]*base.Comment) assert.True(c.t, ok) for _, comment := range comments { - assert.EqualValues(c.t, issue.Number, comment.IssueIndex) + assert.Equal(c.t, issue.Number, comment.IssueIndex) } } @@ -205,7 +205,7 @@ func (c *compareDump) assertEquals(repoBefore, repoAfter *repo_model.Repository) comments, ok := c.assertEqual(filename, []base.Comment{}, compareFields{}).([]*base.Comment) assert.True(c.t, ok) for _, comment := range comments { - assert.EqualValues(c.t, pr.Number, comment.IssueIndex) + assert.Equal(c.t, pr.Number, comment.IssueIndex) } } } @@ -213,7 +213,7 @@ func (c *compareDump) assertEquals(repoBefore, repoAfter *repo_model.Repository) func (c *compareDump) assertLoadYAMLFiles(beforeFilename, afterFilename string, before, after any) { _, beforeErr := os.Stat(beforeFilename) _, afterErr := os.Stat(afterFilename) - assert.EqualValues(c.t, errors.Is(beforeErr, os.ErrNotExist), errors.Is(afterErr, os.ErrNotExist)) + assert.Equal(c.t, errors.Is(beforeErr, os.ErrNotExist), errors.Is(afterErr, os.ErrNotExist)) if errors.Is(beforeErr, os.ErrNotExist) { return } @@ -265,7 +265,7 @@ func (c *compareDump) assertEqual(filename string, kind any, fields compareField } func (c *compareDump) assertEqualSlices(before, after reflect.Value, fields compareFields) any { - assert.EqualValues(c.t, before.Len(), after.Len()) + assert.Equal(c.t, before.Len(), after.Len()) if before.Len() == after.Len() { for i := 0; i < before.Len(); i++ { _ = c.assertEqualValues( @@ -298,15 +298,15 @@ func (c *compareDump) assertEqualValues(before, after reflect.Value, fields comp assert.True(c.t, ok) as, ok := ai.(string) assert.True(c.t, ok) - assert.EqualValues(c.t, compare.transform(bs), compare.transform(as)) + assert.Equal(c.t, compare.transform(bs), compare.transform(as)) continue } if compare.before != nil && compare.after != nil { // // The fields are expected to have different values // - assert.EqualValues(c.t, compare.before, bi) - assert.EqualValues(c.t, compare.after, ai) + assert.Equal(c.t, compare.before, bi) + assert.Equal(c.t, compare.after, ai) continue } if compare.nested != nil { @@ -317,7 +317,7 @@ func (c *compareDump) assertEqualValues(before, after reflect.Value, fields comp continue } } - assert.EqualValues(c.t, bi, ai) + assert.Equal(c.t, bi, ai) } return after.Interface() } diff --git a/tests/integration/editor_test.go b/tests/integration/editor_test.go index fa58b8df42..a5936d86de 100644 --- a/tests/integration/editor_test.go +++ b/tests/integration/editor_test.go @@ -12,6 +12,7 @@ import ( "net/http/httptest" "net/url" "path" + "strings" "testing" repo_model "code.gitea.io/gitea/models/repo" @@ -68,7 +69,7 @@ func TestCreateFileOnProtectedBranch(t *testing.T) { session.MakeRequest(t, req, http.StatusSeeOther) // Check if master branch has been locked successfully flashMsg := session.GetCookieFlashMessage() - assert.EqualValues(t, `Branch protection for rule "master" has been updated.`, flashMsg.SuccessMsg) + assert.Equal(t, `Branch protection for rule "master" has been updated.`, flashMsg.SuccessMsg) // Request editor page req = NewRequest(t, "GET", "/user2/repo1/_new/master/") @@ -103,11 +104,11 @@ func TestCreateFileOnProtectedBranch(t *testing.T) { res := make(map[string]string) assert.NoError(t, json.NewDecoder(resp.Body).Decode(&res)) - assert.EqualValues(t, "/user2/repo1/settings/branches", res["redirect"]) + assert.Equal(t, "/user2/repo1/settings/branches", res["redirect"]) // Check if master branch has been locked successfully flashMsg = session.GetCookieFlashMessage() - assert.EqualValues(t, `Removing branch protection rule "1" failed.`, flashMsg.ErrorMsg) + assert.Equal(t, `Removing branch protection rule "1" failed.`, flashMsg.ErrorMsg) }) } @@ -135,7 +136,7 @@ func testEditFile(t *testing.T, session *TestSession, user, repo, branch, filePa // Verify the change req = NewRequest(t, "GET", path.Join(user, repo, "raw/branch", branch, filePath)) resp = session.MakeRequest(t, req, http.StatusOK) - assert.EqualValues(t, newContent, resp.Body.String()) + assert.Equal(t, newContent, resp.Body.String()) return resp } @@ -165,7 +166,7 @@ func testEditFileToNewBranch(t *testing.T, session *TestSession, user, repo, bra // Verify the change req = NewRequest(t, "GET", path.Join(user, repo, "raw/branch", targetBranch, filePath)) resp = session.MakeRequest(t, req, http.StatusOK) - assert.EqualValues(t, newContent, resp.Body.String()) + assert.Equal(t, newContent, resp.Body.String()) return resp } @@ -215,10 +216,10 @@ func TestWebGitCommitEmail(t *testing.T) { assert.Contains(t, errMsg, translation.NewLocale("en-US").Tr("repo.editor.invalid_commit_email")) } else { require.NotEqual(t, lastCommit.ID.String(), newCommit.ID.String()) - assert.EqualValues(t, expectedUserName, newCommit.Author.Name) - assert.EqualValues(t, expectedEmail, newCommit.Author.Email) - assert.EqualValues(t, expectedUserName, newCommit.Committer.Name) - assert.EqualValues(t, expectedEmail, newCommit.Committer.Email) + assert.Equal(t, expectedUserName, newCommit.Author.Name) + assert.Equal(t, expectedEmail, newCommit.Author.Email) + assert.Equal(t, expectedUserName, newCommit.Committer.Name) + assert.Equal(t, expectedEmail, newCommit.Committer.Email) } return resp } @@ -227,7 +228,7 @@ func TestWebGitCommitEmail(t *testing.T) { body := &bytes.Buffer{} uploadForm := multipart.NewWriter(body) file, _ := uploadForm.CreateFormFile("file", name) - _, _ = io.Copy(file, bytes.NewBufferString(content)) + _, _ = io.Copy(file, strings.NewReader(content)) _ = uploadForm.WriteField("_csrf", GetUserCSRFToken(t, session)) _ = uploadForm.Close() @@ -254,7 +255,7 @@ func TestWebGitCommitEmail(t *testing.T) { t.Run("EmailInvalid", func(t *testing.T) { defer tests.PrintCurrentTest(t)() email := unittest.AssertExistsAndLoadBean(t, &user_model.EmailAddress{ID: 1, IsActivated: true}) - require.NotEqualValues(t, email.UID, user.ID) + require.NotEqual(t, email.UID, user.ID) makeReq(t, "/user2/repo1/_edit/master/README.md", map[string]string{ "tree_path": "README.md", "content": "test content", @@ -332,7 +333,7 @@ index 0000000000..bbbbbbbbbb ) // By the way, test the "cherrypick" page: a successful revert redirects to the main branch - assert.EqualValues(t, "/user2/repo1/src/branch/master", resp1.Header().Get("Location")) + assert.Equal(t, "/user2/repo1/src/branch/master", resp1.Header().Get("Location")) }) }) } diff --git a/tests/integration/empty_repo_test.go b/tests/integration/empty_repo_test.go index e122531dc9..f85d883cc7 100644 --- a/tests/integration/empty_repo_test.go +++ b/tests/integration/empty_repo_test.go @@ -11,6 +11,7 @@ import ( "mime/multipart" "net/http" "net/http/httptest" + "strings" "testing" auth_model "code.gitea.io/gitea/models/auth" @@ -130,7 +131,7 @@ func TestEmptyRepoUploadFile(t *testing.T) { mpForm := multipart.NewWriter(body) _ = mpForm.WriteField("_csrf", GetUserCSRFToken(t, session)) file, _ := mpForm.CreateFormFile("file", "uploaded-file.txt") - _, _ = io.Copy(file, bytes.NewBufferString("newly-uploaded-test-file")) + _, _ = io.Copy(file, strings.NewReader("newly-uploaded-test-file")) _ = mpForm.Close() req = NewRequestWithBody(t, "POST", "/user30/empty/upload-file", body) @@ -172,7 +173,7 @@ func TestEmptyRepoAddFileByAPI(t *testing.T) { var fileResponse api.FileResponse DecodeJSON(t, resp, &fileResponse) expectedHTMLURL := setting.AppURL + "user30/empty/src/branch/new_branch/new-file.txt" - assert.EqualValues(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) + assert.Equal(t, expectedHTMLURL, *fileResponse.Content.HTMLURL) req = NewRequest(t, "GET", "/user30/empty/src/branch/new_branch/new-file.txt") resp = session.MakeRequest(t, req, http.StatusOK) diff --git a/tests/integration/feed_repo_test.go b/tests/integration/feed_repo_test.go index 132ed32ced..2915b9b3f4 100644 --- a/tests/integration/feed_repo_test.go +++ b/tests/integration/feed_repo_test.go @@ -29,7 +29,7 @@ func TestFeedRepo(t *testing.T) { assert.Contains(t, rss.Channel.Link, "/user2/repo1") assert.NotEmpty(t, rss.Channel.PubDate) assert.Len(t, rss.Channel.Items, 1) - assert.EqualValues(t, "issue5", rss.Channel.Items[0].Description) + assert.Equal(t, "issue5", rss.Channel.Items[0].Description) assert.NotEmpty(t, rss.Channel.Items[0].PubDate) }) } diff --git a/tests/integration/git_general_test.go b/tests/integration/git_general_test.go index 95ffe20c35..34fe212d50 100644 --- a/tests/integration/git_general_test.go +++ b/tests/integration/git_general_test.go @@ -483,7 +483,7 @@ func doProtectBranch(ctx APITestContext, branch, userToWhitelistPush, userToWhit // Check if master branch has been locked successfully flashMsg := ctx.Session.GetCookieFlashMessage() - assert.EqualValues(t, `Branch protection for rule "`+branch+`" has been updated.`, flashMsg.SuccessMsg) + assert.Equal(t, `Branch protection for rule "`+branch+`" has been updated.`, flashMsg.SuccessMsg) } } @@ -592,7 +592,7 @@ func doPushCreate(ctx APITestContext, u *url.URL) func(t *testing.T) { defer tests.PrintCurrentTest(t)() // create a context for a currently non-existent repository - ctx.Reponame = fmt.Sprintf("repo-tmp-push-create-%s", u.Scheme) + ctx.Reponame = "repo-tmp-push-create-" + u.Scheme u.Path = ctx.GitPath() // Create a temporary directory @@ -623,7 +623,7 @@ func doPushCreate(ctx APITestContext, u *url.URL) func(t *testing.T) { // Now add a remote that is invalid to "Push To Create" invalidCtx := ctx - invalidCtx.Reponame = fmt.Sprintf("invalid/repo-tmp-push-create-%s", u.Scheme) + invalidCtx.Reponame = "invalid/repo-tmp-push-create-" + u.Scheme u.Path = invalidCtx.GitPath() t.Run("AddInvalidRemote", doGitAddRemote(tmpDir, "invalid", u)) diff --git a/tests/integration/git_helper_for_declarative_test.go b/tests/integration/git_helper_for_declarative_test.go index fce9b8f247..7d42508bfe 100644 --- a/tests/integration/git_helper_for_declarative_test.go +++ b/tests/integration/git_helper_for_declarative_test.go @@ -123,7 +123,7 @@ func doGitInitTestRepository(dstPath string) func(*testing.T) { // forcibly set default branch to master _, _, err := git.NewCommand("symbolic-ref", "HEAD", git.BranchPrefix+"master").RunStdString(git.DefaultContext, &git.RunOpts{Dir: dstPath}) assert.NoError(t, err) - assert.NoError(t, os.WriteFile(filepath.Join(dstPath, "README.md"), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s", dstPath)), 0o644)) + assert.NoError(t, os.WriteFile(filepath.Join(dstPath, "README.md"), []byte("# Testing Repository\n\nOriginally created in: "+dstPath), 0o644)) assert.NoError(t, git.AddChanges(dstPath, true)) signature := git.Signature{ Email: "test@example.com", @@ -163,7 +163,7 @@ func doGitAddSomeCommits(dstPath, branch string) func(*testing.T) { return func(t *testing.T) { doGitCheckoutBranch(dstPath, branch)(t) - assert.NoError(t, os.WriteFile(filepath.Join(dstPath, fmt.Sprintf("file-%s.txt", branch)), []byte(fmt.Sprintf("file %s", branch)), 0o644)) + assert.NoError(t, os.WriteFile(filepath.Join(dstPath, fmt.Sprintf("file-%s.txt", branch)), []byte("file "+branch), 0o644)) assert.NoError(t, git.AddChanges(dstPath, true)) signature := git.Signature{ Email: "test@test.test", @@ -172,7 +172,7 @@ func doGitAddSomeCommits(dstPath, branch string) func(*testing.T) { assert.NoError(t, git.CommitChanges(dstPath, git.CommitChangesOptions{ Committer: &signature, Author: &signature, - Message: fmt.Sprintf("update %s", branch), + Message: "update " + branch, })) } } diff --git a/tests/integration/git_push_test.go b/tests/integration/git_push_test.go index ee4f203dbe..bac7b4f48b 100644 --- a/tests/integration/git_push_test.go +++ b/tests/integration/git_push_test.go @@ -170,7 +170,7 @@ func runTestGitPush(t *testing.T, u *url.URL, gitOperation func(t *testing.T, gi dbBranches := make([]*git_model.Branch, 0) require.NoError(t, db.GetEngine(db.DefaultContext).Where("repo_id=?", repo.ID).Find(&dbBranches)) - assert.Equalf(t, len(pushedBranches), len(dbBranches), "mismatched number of branches in db") + assert.Lenf(t, dbBranches, len(pushedBranches), "mismatched number of branches in db") dbBranchesMap := make(map[string]*git_model.Branch, len(dbBranches)) for _, branch := range dbBranches { dbBranchesMap[branch.Name] = branch diff --git a/tests/integration/git_smart_http_test.go b/tests/integration/git_smart_http_test.go index 1cbda5a673..e984fd3aad 100644 --- a/tests/integration/git_smart_http_test.go +++ b/tests/integration/git_smart_http_test.go @@ -72,7 +72,7 @@ func testGitSmartHTTP(t *testing.T, u *url.URL) { resp, err := http.DefaultClient.Do(req) require.NoError(t, err) defer resp.Body.Close() - assert.EqualValues(t, kase.code, resp.StatusCode) + assert.Equal(t, kase.code, resp.StatusCode) _, err = io.ReadAll(resp.Body) require.NoError(t, err) }) @@ -80,7 +80,7 @@ func testGitSmartHTTP(t *testing.T, u *url.URL) { } func testRenamedRepoRedirect(t *testing.T) { - defer test.MockVariableValue(&setting.Service.RequireSignInView, true)() + defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)() // git client requires to get a 301 redirect response before 401 unauthorized response req := NewRequest(t, "GET", "/user2/oldrepo1/info/refs") diff --git a/tests/integration/gpg_git_test.go b/tests/integration/gpg_git_test.go index 31695fb2e1..32de200f63 100644 --- a/tests/integration/gpg_git_test.go +++ b/tests/integration/gpg_git_test.go @@ -99,26 +99,14 @@ func TestGPGGit(t *testing.T) { testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) t.Run("CreateCRUDFile-Always", crudActionCreateFile( t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) { - assert.NotNil(t, response.Verification) - if response.Verification == nil { - assert.FailNow(t, "no verification provided with response! %v", response) - } - assert.True(t, response.Verification.Verified) - if !response.Verification.Verified { - t.FailNow() - } + require.NotNil(t, response.Verification, "no verification provided with response! %v", response) + require.True(t, response.Verification.Verified) assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) })) t.Run("CreateCRUDFile-ParentSigned-always", crudActionCreateFile( t, testCtx, user, "parentsigned", "parentsigned-always", "signed-parent2.txt", func(t *testing.T, response api.FileResponse) { - assert.NotNil(t, response.Verification) - if response.Verification == nil { - assert.FailNow(t, "no verification provided with response! %v", response) - } - assert.True(t, response.Verification.Verified) - if !response.Verification.Verified { - t.FailNow() - } + require.NotNil(t, response.Verification, "no verification provided with response! %v", response) + require.True(t, response.Verification.Verified) assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) })) }) @@ -129,14 +117,8 @@ func TestGPGGit(t *testing.T) { testCtx := NewAPITestContext(t, username, "initial-unsigned", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) t.Run("CreateCRUDFile-Always-ParentSigned", crudActionCreateFile( t, testCtx, user, "always", "always-parentsigned", "signed-always-parentsigned.txt", func(t *testing.T, response api.FileResponse) { - assert.NotNil(t, response.Verification) - if response.Verification == nil { - assert.FailNow(t, "no verification provided with response! %v", response) - } - assert.True(t, response.Verification.Verified) - if !response.Verification.Verified { - t.FailNow() - } + require.NotNil(t, response.Verification, "no verification provided with response! %v", response) + require.True(t, response.Verification.Verified) assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) })) }) @@ -147,18 +129,9 @@ func TestGPGGit(t *testing.T) { testCtx := NewAPITestContext(t, username, "initial-always", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { - assert.NotNil(t, branch.Commit) - if branch.Commit == nil { - assert.FailNow(t, "no commit provided with branch! %v", branch) - } - assert.NotNil(t, branch.Commit.Verification) - if branch.Commit.Verification == nil { - assert.FailNow(t, "no verification provided with branch commit! %v", branch.Commit) - } - assert.True(t, branch.Commit.Verification.Verified) - if !branch.Commit.Verification.Verified { - t.FailNow() - } + require.NotNil(t, branch.Commit, "no commit provided with branch! %v", branch) + require.NotNil(t, branch.Commit.Verification, "no verification provided with branch commit! %v", branch.Commit) + require.True(t, branch.Commit.Verification.Verified) assert.Equal(t, "gitea@fake.local", branch.Commit.Verification.Signer.Email) })) }) @@ -181,11 +154,7 @@ func TestGPGGit(t *testing.T) { t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile( t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) { - assert.True(t, response.Verification.Verified) - if !response.Verification.Verified { - t.FailNow() - return - } + require.True(t, response.Verification.Verified) assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) })) }) @@ -197,11 +166,7 @@ func TestGPGGit(t *testing.T) { t.Run("CreateRepository", doAPICreateRepository(testCtx, false)) t.Run("CreateCRUDFile-Always", crudActionCreateFile( t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) { - assert.True(t, response.Verification.Verified) - if !response.Verification.Verified { - t.FailNow() - return - } + require.True(t, response.Verification.Verified) assert.Equal(t, "gitea@fake.local", response.Verification.Signer.Email) })) }) @@ -273,7 +238,7 @@ func crudActionCreateFile(_ *testing.T, ctx APITestContext, user *user_model.Use Email: user.Email, }, }, - ContentBase64: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("This is new text for %s", path))), + ContentBase64: base64.StdEncoding.EncodeToString([]byte("This is new text for " + path)), }, callback...) } diff --git a/tests/integration/incoming_email_test.go b/tests/integration/incoming_email_test.go index e968a2956e..6ccf82a9eb 100644 --- a/tests/integration/incoming_email_test.go +++ b/tests/integration/incoming_email_test.go @@ -50,12 +50,12 @@ func TestIncomingEmail(t *testing.T) { ref, err := incoming_payload.GetReferenceFromPayload(db.DefaultContext, issuePayload) assert.NoError(t, err) assert.IsType(t, ref, new(issues_model.Issue)) - assert.EqualValues(t, issue.ID, ref.(*issues_model.Issue).ID) + assert.Equal(t, issue.ID, ref.(*issues_model.Issue).ID) ref, err = incoming_payload.GetReferenceFromPayload(db.DefaultContext, commentPayload) assert.NoError(t, err) assert.IsType(t, ref, new(issues_model.Comment)) - assert.EqualValues(t, comment.ID, ref.(*issues_model.Comment).ID) + assert.Equal(t, comment.ID, ref.(*issues_model.Comment).ID) }) t.Run("Token", func(t *testing.T) { diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index 2f6b7eae31..d5b7bb7a3e 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -310,7 +310,7 @@ func NewRequestWithValues(t testing.TB, method, urlStr string, values map[string func NewRequestWithURLValues(t testing.TB, method, urlStr string, urlValues url.Values) *RequestWrapper { t.Helper() - return NewRequestWithBody(t, method, urlStr, bytes.NewBufferString(urlValues.Encode())). + return NewRequestWithBody(t, method, urlStr, strings.NewReader(urlValues.Encode())). SetHeader("Content-Type", "application/x-www-form-urlencoded") } @@ -360,7 +360,7 @@ func MakeRequestNilResponseRecorder(t testing.TB, rw *RequestWrapper, expectedSt recorder := NewNilResponseRecorder() testWebRoutes.ServeHTTP(recorder, req) if expectedStatus != NoExpectedStatus { - if !assert.EqualValues(t, expectedStatus, recorder.Code, + if !assert.Equal(t, expectedStatus, recorder.Code, "Request: %s %s", req.Method, req.URL.String()) { logUnexpectedResponse(t, &recorder.ResponseRecorder) } @@ -374,7 +374,7 @@ func MakeRequestNilResponseHashSumRecorder(t testing.TB, rw *RequestWrapper, exp recorder := NewNilResponseHashSumRecorder() testWebRoutes.ServeHTTP(recorder, req) if expectedStatus != NoExpectedStatus { - if !assert.EqualValues(t, expectedStatus, recorder.Code, + if !assert.Equal(t, expectedStatus, recorder.Code, "Request: %s %s", req.Method, req.URL.String()) { logUnexpectedResponse(t, &recorder.ResponseRecorder) } diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go index dc0c9b1350..f0a5e4f519 100644 --- a/tests/integration/issue_test.go +++ b/tests/integration/issue_test.go @@ -32,7 +32,7 @@ import ( func getIssuesSelection(t testing.TB, htmlDoc *HTMLDoc) *goquery.Selection { issueList := htmlDoc.doc.Find("#issue-list") - assert.EqualValues(t, 1, issueList.Length()) + assert.Equal(t, 1, issueList.Length()) return issueList.Find(".flex-item").Find(".issue-title") } @@ -84,11 +84,11 @@ func TestViewIssuesSortByType(t *testing.T) { if expectedNumIssues > setting.UI.IssuePagingNum { expectedNumIssues = setting.UI.IssuePagingNum } - assert.EqualValues(t, expectedNumIssues, issuesSelection.Length()) + assert.Equal(t, expectedNumIssues, issuesSelection.Length()) issuesSelection.Each(func(_ int, selection *goquery.Selection) { issue := getIssue(t, repo.ID, selection) - assert.EqualValues(t, user.ID, issue.PosterID) + assert.Equal(t, user.ID, issue.PosterID) }) } @@ -108,7 +108,7 @@ func TestViewIssuesKeyword(t *testing.T) { htmlDoc := NewHTMLParser(t, resp.Body) issuesSelection := getIssuesSelection(t, htmlDoc) - assert.EqualValues(t, 1, issuesSelection.Length()) + assert.Equal(t, 1, issuesSelection.Length()) issuesSelection.Each(func(_ int, selection *goquery.Selection) { issue := getIssue(t, repo.ID, selection) assert.False(t, issue.IsClosed) @@ -195,21 +195,21 @@ func TestEditIssue(t *testing.T) { session := loginUser(t, "user2") issueURL := testNewIssue(t, session, "user2", "repo1", "Title", "Description") - req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/content", issueURL), map[string]string{ + req := NewRequestWithValues(t, "POST", issueURL+"/content", map[string]string{ "_csrf": GetUserCSRFToken(t, session), "content": "modified content", "context": fmt.Sprintf("/%s/%s", "user2", "repo1"), }) session.MakeRequest(t, req, http.StatusOK) - req = NewRequestWithValues(t, "POST", fmt.Sprintf("%s/content", issueURL), map[string]string{ + req = NewRequestWithValues(t, "POST", issueURL+"/content", map[string]string{ "_csrf": GetUserCSRFToken(t, session), "content": "modified content", "context": fmt.Sprintf("/%s/%s", "user2", "repo1"), }) session.MakeRequest(t, req, http.StatusBadRequest) - req = NewRequestWithValues(t, "POST", fmt.Sprintf("%s/content", issueURL), map[string]string{ + req = NewRequestWithValues(t, "POST", issueURL+"/content", map[string]string{ "_csrf": GetUserCSRFToken(t, session), "content": "modified content", "content_version": "1", @@ -510,7 +510,7 @@ func TestSearchIssues(t *testing.T) { req = NewRequest(t, "GET", link.String()) resp = session.MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiIssues) - assert.EqualValues(t, "22", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "22", resp.Header().Get("X-Total-Count")) assert.Len(t, apiIssues, 20) query.Add("limit", "5") @@ -518,7 +518,7 @@ func TestSearchIssues(t *testing.T) { req = NewRequest(t, "GET", link.String()) resp = session.MakeRequest(t, req, http.StatusOK) DecodeJSON(t, resp, &apiIssues) - assert.EqualValues(t, "22", resp.Header().Get("X-Total-Count")) + assert.Equal(t, "22", resp.Header().Get("X-Total-Count")) assert.Len(t, apiIssues, 5) query = url.Values{"assigned": {"true"}, "state": {"all"}} @@ -645,7 +645,7 @@ func TestGetIssueInfo(t *testing.T) { } DecodeJSON(t, resp, &respStruct) - assert.EqualValues(t, issue.ID, respStruct.ConvertedIssue.ID) + assert.Equal(t, issue.ID, respStruct.ConvertedIssue.ID) assert.Contains(t, string(respStruct.RenderedLabels), `"labels-list"`) } @@ -665,7 +665,7 @@ func TestUpdateIssueDeadline(t *testing.T) { req := NewRequestWithValues(t, "POST", urlStr, map[string]string{"deadline": "2022-04-06"}) session.MakeRequest(t, req, http.StatusOK) issueAfter := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 10}) - assert.EqualValues(t, "2022-04-06", issueAfter.DeadlineUnix.FormatDate()) + assert.Equal(t, "2022-04-06", issueAfter.DeadlineUnix.FormatDate()) req = NewRequestWithValues(t, "POST", urlStr, map[string]string{"deadline": ""}) session.MakeRequest(t, req, http.StatusOK) @@ -686,8 +686,8 @@ func TestIssueReferenceURL(t *testing.T) { // the "reference" uses relative URLs, then JS code will convert them to absolute URLs for current origin, in case users are using multiple domains ref, _ := htmlDoc.Find(`.timeline-item.comment.first .reference-issue`).Attr("data-reference") - assert.EqualValues(t, "/user2/repo1/issues/1#issue-1", ref) + assert.Equal(t, "/user2/repo1/issues/1#issue-1", ref) ref, _ = htmlDoc.Find(`.timeline-item.comment:not(.first) .reference-issue`).Attr("data-reference") - assert.EqualValues(t, "/user2/repo1/issues/1#issuecomment-2", ref) + assert.Equal(t, "/user2/repo1/issues/1#issuecomment-2", ref) } diff --git a/tests/integration/lfs_local_endpoint_test.go b/tests/integration/lfs_local_endpoint_test.go index d42888bbe1..e67f0712a3 100644 --- a/tests/integration/lfs_local_endpoint_test.go +++ b/tests/integration/lfs_local_endpoint_test.go @@ -4,7 +4,6 @@ package integration import ( - "fmt" "net/url" "os" "path/filepath" @@ -41,55 +40,55 @@ func TestDetermineLocalEndpoint(t *testing.T) { { cloneurl: root, lfsurl: "", - expected: str2url(fmt.Sprintf("file://%s", root)), + expected: str2url("file://" + root), }, // case 1 { cloneurl: root, lfsurl: lfsroot, - expected: str2url(fmt.Sprintf("file://%s", lfsroot)), + expected: str2url("file://" + lfsroot), }, // case 2 { cloneurl: "https://git.com/repo.git", lfsurl: lfsroot, - expected: str2url(fmt.Sprintf("file://%s", lfsroot)), + expected: str2url("file://" + lfsroot), }, // case 3 { cloneurl: rootdotgit, lfsurl: "", - expected: str2url(fmt.Sprintf("file://%s", filepath.Join(rootdotgit, ".git"))), + expected: str2url("file://" + filepath.Join(rootdotgit, ".git")), }, // case 4 { cloneurl: "", lfsurl: rootdotgit, - expected: str2url(fmt.Sprintf("file://%s", filepath.Join(rootdotgit, ".git"))), + expected: str2url("file://" + filepath.Join(rootdotgit, ".git")), }, // case 5 { cloneurl: rootdotgit, lfsurl: rootdotgit, - expected: str2url(fmt.Sprintf("file://%s", filepath.Join(rootdotgit, ".git"))), + expected: str2url("file://" + filepath.Join(rootdotgit, ".git")), }, // case 6 { - cloneurl: fmt.Sprintf("file://%s", root), + cloneurl: "file://" + root, lfsurl: "", - expected: str2url(fmt.Sprintf("file://%s", root)), + expected: str2url("file://" + root), }, // case 7 { - cloneurl: fmt.Sprintf("file://%s", root), - lfsurl: fmt.Sprintf("file://%s", lfsroot), - expected: str2url(fmt.Sprintf("file://%s", lfsroot)), + cloneurl: "file://" + root, + lfsurl: "file://" + lfsroot, + expected: str2url("file://" + lfsroot), }, // case 8 { cloneurl: root, - lfsurl: fmt.Sprintf("file://%s", lfsroot), - expected: str2url(fmt.Sprintf("file://%s", lfsroot)), + lfsurl: "file://" + lfsroot, + expected: str2url("file://" + lfsroot), }, // case 9 { diff --git a/tests/integration/links_test.go b/tests/integration/links_test.go index 6573a47ccc..ec59e147d2 100644 --- a/tests/integration/links_test.go +++ b/tests/integration/links_test.go @@ -54,6 +54,7 @@ func TestRedirectsNoLogin(t *testing.T) { {"/user2/repo1/src/master", "/user2/repo1/src/branch/master"}, {"/user2/repo1/src/master/a%2fb.txt", "/user2/repo1/src/branch/master/a%2fb.txt"}, {"/user2/repo1/src/master/directory/file.txt?a=1", "/user2/repo1/src/branch/master/directory/file.txt?a=1"}, + {"/user2/repo1/src/branch/master/directory/file.txt?raw=1&other=2", "/user2/repo1/raw/branch/master/directory/file.txt"}, {"/user2/repo1/tree/a%2fb?a=1", "/user2/repo1/src/a%2fb?a=1"}, {"/user2/repo1/blob/123456/%20?a=1", "/user2/repo1/src/commit/123456/%20?a=1"}, {"/user/avatar/GhosT/-1", "/assets/img/avatar_default.png"}, @@ -63,7 +64,7 @@ func TestRedirectsNoLogin(t *testing.T) { for _, c := range redirects { req := NewRequest(t, "GET", c.from) resp := MakeRequest(t, req, http.StatusSeeOther) - assert.EqualValues(t, path.Join(setting.AppSubURL, c.to), test.RedirectURL(resp)) + assert.Equal(t, path.Join(setting.AppSubURL, c.to), test.RedirectURL(resp)) } } diff --git a/tests/integration/markup_external_test.go b/tests/integration/markup_external_test.go index 2d713b0eb9..47d143a29e 100644 --- a/tests/integration/markup_external_test.go +++ b/tests/integration/markup_external_test.go @@ -27,7 +27,7 @@ func TestExternalMarkupRenderer(t *testing.T) { req := NewRequest(t, "GET", "/user30/renderer/src/branch/master/README.html") resp := MakeRequest(t, req, http.StatusOK) - assert.EqualValues(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type")) bs, err := io.ReadAll(resp.Body) assert.NoError(t, err) @@ -36,25 +36,25 @@ func TestExternalMarkupRenderer(t *testing.T) { div := doc.Find("div.file-view") data, err := div.Html() assert.NoError(t, err) - assert.EqualValues(t, "\n\ttest external renderer\n", strings.TrimSpace(data)) + assert.Equal(t, "\n\ttest external renderer\n", strings.TrimSpace(data)) r := markup.GetRendererByFileName("a.html").(*external.Renderer) r.RenderContentMode = setting.RenderContentModeIframe req = NewRequest(t, "GET", "/user30/renderer/src/branch/master/README.html") resp = MakeRequest(t, req, http.StatusOK) - assert.EqualValues(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type")) bs, err = io.ReadAll(resp.Body) assert.NoError(t, err) doc = NewHTMLParser(t, bytes.NewBuffer(bs)) iframe := doc.Find("iframe") - assert.EqualValues(t, "/user30/renderer/render/branch/master/README.html", iframe.AttrOr("src", "")) + assert.Equal(t, "/user30/renderer/render/branch/master/README.html", iframe.AttrOr("src", "")) req = NewRequest(t, "GET", "/user30/renderer/render/branch/master/README.html") resp = MakeRequest(t, req, http.StatusOK) - assert.EqualValues(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type")) + assert.Equal(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type")) bs, err = io.ReadAll(resp.Body) assert.NoError(t, err) - assert.EqualValues(t, "frame-src 'self'; sandbox allow-scripts", resp.Header().Get("Content-Security-Policy")) - assert.EqualValues(t, "\n\ttest external renderer\n\n", string(bs)) + assert.Equal(t, "frame-src 'self'; sandbox allow-scripts", resp.Header().Get("Content-Security-Policy")) + assert.Equal(t, "\n\ttest external renderer\n\n", string(bs)) } diff --git a/tests/integration/migrate_test.go b/tests/integration/migrate_test.go index 59dd6907db..a89dc8b85c 100644 --- a/tests/integration/migrate_test.go +++ b/tests/integration/migrate_test.go @@ -9,6 +9,7 @@ import ( "net/url" "os" "path/filepath" + "strconv" "testing" auth_model "code.gitea.io/gitea/models/auth" @@ -84,7 +85,7 @@ func TestMigrateGiteaForm(t *testing.T) { assert.True(t, exists, "The template has changed") serviceInput, exists := form.Find(`input[name="service"]`).Attr("value") assert.True(t, exists) - assert.EqualValues(t, fmt.Sprintf("%d", structs.GiteaService), serviceInput) + assert.Equal(t, fmt.Sprintf("%d", structs.GiteaService), serviceInput) // Step 4: submit the migration to only migrate issues migratedRepoName := "otherrepo" req = NewRequestWithValues(t, "POST", link, map[string]string{ @@ -95,12 +96,12 @@ func TestMigrateGiteaForm(t *testing.T) { "issues": "on", "repo_name": migratedRepoName, "description": "", - "uid": fmt.Sprintf("%d", repoOwner.ID), + "uid": strconv.FormatInt(repoOwner.ID, 10), }) resp = session.MakeRequest(t, req, http.StatusSeeOther) // Step 5: a redirection displays the migrated repository loc := resp.Header().Get("Location") - assert.EqualValues(t, fmt.Sprintf("/%s/%s", ownerName, migratedRepoName), loc) + assert.Equal(t, fmt.Sprintf("/%s/%s", ownerName, migratedRepoName), loc) // Step 6: check the repo was created unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: migratedRepoName}) }) diff --git a/tests/integration/migration-test/migration_test.go b/tests/integration/migration-test/migration_test.go index c319fd20a7..0fc8a6e24d 100644 --- a/tests/integration/migration-test/migration_test.go +++ b/tests/integration/migration-test/migration_test.go @@ -52,7 +52,7 @@ func initMigrationTest(t *testing.T) func() { setting.CustomConf = giteaConf } - unittest.InitSettings() + unittest.InitSettingsForTesting() assert.NotEmpty(t, setting.RepoRootPath) assert.NoError(t, unittest.SyncDirs(filepath.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath)) @@ -140,10 +140,10 @@ func restoreOldDB(t *testing.T, version string) { assert.NoError(t, err) defer db.Close() - _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)) + _, err = db.Exec("DROP DATABASE IF EXISTS " + setting.Database.Name) assert.NoError(t, err) - _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", setting.Database.Name)) + _, err = db.Exec("CREATE DATABASE IF NOT EXISTS " + setting.Database.Name) assert.NoError(t, err) db.Close() @@ -170,10 +170,10 @@ func restoreOldDB(t *testing.T, version string) { } defer db.Close() - _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)) + _, err = db.Exec("DROP DATABASE IF EXISTS " + setting.Database.Name) assert.NoError(t, err) - _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", setting.Database.Name)) + _, err = db.Exec("CREATE DATABASE " + setting.Database.Name) assert.NoError(t, err) db.Close() @@ -195,7 +195,7 @@ func restoreOldDB(t *testing.T, version string) { if !schrows.Next() { // Create and setup a DB schema - _, err = db.Exec(fmt.Sprintf("CREATE SCHEMA %s", setting.Database.Schema)) + _, err = db.Exec("CREATE SCHEMA " + setting.Database.Schema) assert.NoError(t, err) } schrows.Close() diff --git a/tests/integration/mirror_pull_test.go b/tests/integration/mirror_pull_test.go index 7ab8f72b4a..fdf399f2bc 100644 --- a/tests/integration/mirror_pull_test.go +++ b/tests/integration/mirror_pull_test.go @@ -99,7 +99,7 @@ func TestMirrorPull(t *testing.T) { count, err := db.Count[repo_model.Release](db.DefaultContext, findOptions) assert.NoError(t, err) - assert.EqualValues(t, initCount+1, count) + assert.Equal(t, initCount+1, count) release, err := repo_model.GetRelease(db.DefaultContext, repo.ID, "v0.2") assert.NoError(t, err) @@ -110,5 +110,5 @@ func TestMirrorPull(t *testing.T) { count, err = db.Count[repo_model.Release](db.DefaultContext, findOptions) assert.NoError(t, err) - assert.EqualValues(t, initCount, count) + assert.Equal(t, initCount, count) } diff --git a/tests/integration/mirror_push_test.go b/tests/integration/mirror_push_test.go index a8046cdc59..40d100cd27 100644 --- a/tests/integration/mirror_push_test.go +++ b/tests/integration/mirror_push_test.go @@ -128,18 +128,18 @@ func TestRepoSettingPushMirrorUpdate(t *testing.T) { pushMirrors, cnt, err := repo_model.GetPushMirrorsByRepoID(db.DefaultContext, repo2.ID, db.ListOptions{}) assert.NoError(t, err) assert.EqualValues(t, 1, cnt) - assert.EqualValues(t, 24*time.Hour, pushMirrors[0].Interval) + assert.Equal(t, 24*time.Hour, pushMirrors[0].Interval) repo2PushMirrorID := pushMirrors[0].ID // update repo2 push mirror assert.True(t, doUpdatePushMirror(t, session, "user2", "repo2", repo2PushMirrorID, "10m0s")) pushMirror := unittest.AssertExistsAndLoadBean(t, &repo_model.PushMirror{ID: repo2PushMirrorID}) - assert.EqualValues(t, 10*time.Minute, pushMirror.Interval) + assert.Equal(t, 10*time.Minute, pushMirror.Interval) // avoid updating repo2 push mirror from repo1 assert.False(t, doUpdatePushMirror(t, session, "user2", "repo1", repo2PushMirrorID, "20m0s")) pushMirror = unittest.AssertExistsAndLoadBean(t, &repo_model.PushMirror{ID: repo2PushMirrorID}) - assert.EqualValues(t, 10*time.Minute, pushMirror.Interval) // not changed + assert.Equal(t, 10*time.Minute, pushMirror.Interval) // not changed // avoid deleting repo2 push mirror from repo1 assert.False(t, doRemovePushMirror(t, session, "user2", "repo1", repo2PushMirrorID)) diff --git a/tests/integration/org_team_invite_test.go b/tests/integration/org_team_invite_test.go index 4c1053702e..7444980ea8 100644 --- a/tests/integration/org_team_invite_test.go +++ b/tests/integration/org_team_invite_test.go @@ -58,7 +58,7 @@ func TestOrgTeamEmailInvite(t *testing.T) { session = loginUser(t, user.Name) // join the team - inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token) + inviteURL := "/org/invite/" + invites[0].Token csrf = GetUserCSRFToken(t, session) req = NewRequestWithValues(t, "POST", inviteURL, map[string]string{ "_csrf": csrf, @@ -108,8 +108,8 @@ func TestOrgTeamEmailInviteRedirectsExistingUser(t *testing.T) { assert.Len(t, invites, 1) // accept the invite - inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token) - req = NewRequest(t, "GET", fmt.Sprintf("/user/login?redirect_to=%s", url.QueryEscape(inviteURL))) + inviteURL := "/org/invite/" + invites[0].Token + req = NewRequest(t, "GET", "/user/login?redirect_to="+url.QueryEscape(inviteURL)) resp = MakeRequest(t, req, http.StatusOK) doc := NewHTMLParser(t, resp.Body) @@ -179,8 +179,8 @@ func TestOrgTeamEmailInviteRedirectsNewUser(t *testing.T) { assert.Len(t, invites, 1) // accept the invite - inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token) - req = NewRequest(t, "GET", fmt.Sprintf("/user/sign_up?redirect_to=%s", url.QueryEscape(inviteURL))) + inviteURL := "/org/invite/" + invites[0].Token + req = NewRequest(t, "GET", "/user/sign_up?redirect_to="+url.QueryEscape(inviteURL)) resp = MakeRequest(t, req, http.StatusOK) doc := NewHTMLParser(t, resp.Body) @@ -260,8 +260,8 @@ func TestOrgTeamEmailInviteRedirectsNewUserWithActivation(t *testing.T) { // new user: accept the invite session = emptyTestSession(t) - inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token) - req = NewRequest(t, "GET", fmt.Sprintf("/user/sign_up?redirect_to=%s", url.QueryEscape(inviteURL))) + inviteURL := "/org/invite/" + invites[0].Token + req = NewRequest(t, "GET", "/user/sign_up?redirect_to="+url.QueryEscape(inviteURL)) session.MakeRequest(t, req, http.StatusOK) req = NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{ "user_name": "doesnotexist", @@ -275,7 +275,7 @@ func TestOrgTeamEmailInviteRedirectsNewUserWithActivation(t *testing.T) { assert.NoError(t, err) activationCode := user_model.GenerateUserTimeLimitCode(&user_model.TimeLimitCodeOptions{Purpose: user_model.TimeLimitCodeActivateAccount}, user) - activateURL := fmt.Sprintf("/user/activate?code=%s", activationCode) + activateURL := "/user/activate?code=" + activationCode req = NewRequestWithValues(t, "POST", activateURL, map[string]string{ "password": "examplePassword!1", }) @@ -337,8 +337,8 @@ func TestOrgTeamEmailInviteRedirectsExistingUserWithLogin(t *testing.T) { session = loginUser(t, "user5") // accept the invite (note: this uses the sign_up url) - inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token) - req = NewRequest(t, "GET", fmt.Sprintf("/user/sign_up?redirect_to=%s", url.QueryEscape(inviteURL))) + inviteURL := "/org/invite/" + invites[0].Token + req = NewRequest(t, "GET", "/user/sign_up?redirect_to="+url.QueryEscape(inviteURL)) resp = session.MakeRequest(t, req, http.StatusSeeOther) assert.Equal(t, inviteURL, test.RedirectURL(resp)) diff --git a/tests/integration/org_test.go b/tests/integration/org_test.go index ef4ef2bb9b..9a93455858 100644 --- a/tests/integration/org_test.go +++ b/tests/integration/org_test.go @@ -41,7 +41,7 @@ func TestOrgRepos(t *testing.T) { sel := htmlDoc.doc.Find("a.name") assert.Len(t, repos, len(sel.Nodes)) for i := 0; i < len(repos); i++ { - assert.EqualValues(t, repos[i], strings.TrimSpace(sel.Eq(i).Text())) + assert.Equal(t, repos[i], strings.TrimSpace(sel.Eq(i).Text())) } } }) @@ -151,7 +151,7 @@ func TestOrgRestrictedUser(t *testing.T) { // assert restrictedUser cannot see the org or the public repo restrictedSession := loginUser(t, restrictedUser) - req := NewRequest(t, "GET", fmt.Sprintf("/%s", orgName)) + req := NewRequest(t, "GET", "/"+orgName) restrictedSession.MakeRequest(t, req, http.StatusNotFound) req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s", orgName, repoName)) @@ -177,9 +177,9 @@ func TestOrgRestrictedUser(t *testing.T) { resp := adminSession.MakeRequest(t, req, http.StatusCreated) DecodeJSON(t, resp, &apiTeam) checkTeamResponse(t, "CreateTeam_codereader", &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, - teamToCreate.Permission, teamToCreate.Units, nil) + "none", teamToCreate.Units, nil) checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories, - teamToCreate.Permission, teamToCreate.Units, nil) + "none", teamToCreate.Units, nil) // teamID := apiTeam.ID // Now we need to add the restricted user to the team @@ -188,7 +188,7 @@ func TestOrgRestrictedUser(t *testing.T) { _ = adminSession.MakeRequest(t, req, http.StatusNoContent) // Now we need to check if the restrictedUser can access the repo - req = NewRequest(t, "GET", fmt.Sprintf("/%s", orgName)) + req = NewRequest(t, "GET", "/"+orgName) restrictedSession.MakeRequest(t, req, http.StatusOK) req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s", orgName, repoName)) diff --git a/tests/integration/project_test.go b/tests/integration/project_test.go index 111356b1da..13213c254d 100644 --- a/tests/integration/project_test.go +++ b/tests/integration/project_test.go @@ -79,9 +79,9 @@ func TestMoveRepoProjectColumns(t *testing.T) { columnsAfter, err := project1.GetColumns(db.DefaultContext) assert.NoError(t, err) assert.Len(t, columnsAfter, 3) - assert.EqualValues(t, columns[1].ID, columnsAfter[0].ID) - assert.EqualValues(t, columns[2].ID, columnsAfter[1].ID) - assert.EqualValues(t, columns[0].ID, columnsAfter[2].ID) + assert.Equal(t, columns[1].ID, columnsAfter[0].ID) + assert.Equal(t, columns[2].ID, columnsAfter[1].ID) + assert.Equal(t, columns[0].ID, columnsAfter[2].ID) assert.NoError(t, project_model.DeleteProjectByID(db.DefaultContext, project1.ID)) } diff --git a/tests/integration/pull_compare_test.go b/tests/integration/pull_compare_test.go index 4ac5be18be..86bdd1b9e3 100644 --- a/tests/integration/pull_compare_test.go +++ b/tests/integration/pull_compare_test.go @@ -47,7 +47,7 @@ func TestPullCompare(t *testing.T) { assert.True(t, exists, "The template has changed") req = NewRequest(t, "GET", link) resp = session.MakeRequest(t, req, http.StatusOK) - assert.EqualValues(t, http.StatusOK, resp.Code) + assert.Equal(t, http.StatusOK, resp.Code) // test the edit button in the PR diff view req = NewRequest(t, "GET", "/user2/repo1/pulls/3/files") @@ -86,7 +86,7 @@ func TestPullCompare(t *testing.T) { resp = session.MakeRequest(t, req, http.StatusOK) doc = NewHTMLParser(t, resp.Body) editButtonCount = doc.doc.Find(".diff-file-header-actions a[href*='/_edit/']").Length() - assert.EqualValues(t, 0, editButtonCount, "Expected not to find a button to edit a file in the PR diff view because head repository has been deleted") + assert.Equal(t, 0, editButtonCount, "Expected not to find a button to edit a file in the PR diff view because head repository has been deleted") }) } @@ -110,7 +110,7 @@ func TestPullCompare_EnableAllowEditsFromMaintainer(t *testing.T) { // user2 (admin of repo3) goes to the PR files page user2Session := loginUser(t, "user2") - resp = user2Session.MakeRequest(t, NewRequest(t, "GET", fmt.Sprintf("%s/files", prURL)), http.StatusOK) + resp = user2Session.MakeRequest(t, NewRequest(t, "GET", prURL+"/files"), http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) nodes := htmlDoc.doc.Find(".diff-file-box[data-new-filename=\"README.md\"] .diff-file-header-actions .tippy-target a") if assert.Equal(t, 1, nodes.Length()) { @@ -127,14 +127,14 @@ func TestPullCompare_EnableAllowEditsFromMaintainer(t *testing.T) { htmlDoc = NewHTMLParser(t, resp.Body) dataURL, exists := htmlDoc.doc.Find("#allow-edits-from-maintainers").Attr("data-url") assert.True(t, exists) - req := NewRequestWithValues(t, "POST", fmt.Sprintf("%s/set_allow_maintainer_edit", dataURL), map[string]string{ + req := NewRequestWithValues(t, "POST", dataURL+"/set_allow_maintainer_edit", map[string]string{ "_csrf": htmlDoc.GetCSRF(), "allow_maintainer_edit": "true", }) user4Session.MakeRequest(t, req, http.StatusOK) // user2 (admin of repo3) goes to the PR files page again - resp = user2Session.MakeRequest(t, NewRequest(t, "GET", fmt.Sprintf("%s/files", prURL)), http.StatusOK) + resp = user2Session.MakeRequest(t, NewRequest(t, "GET", prURL+"/files"), http.StatusOK) htmlDoc = NewHTMLParser(t, resp.Body) nodes = htmlDoc.doc.Find(".diff-file-box[data-new-filename=\"README.md\"] .diff-file-header-actions .tippy-target a") if assert.Equal(t, 2, nodes.Length()) { diff --git a/tests/integration/pull_create_test.go b/tests/integration/pull_create_test.go index 461a1400f7..179c84e673 100644 --- a/tests/integration/pull_create_test.go +++ b/tests/integration/pull_create_test.go @@ -293,10 +293,10 @@ func TestCreatePullWhenBlocked(t *testing.T) { // sessionBase := loginUser(t, "user2") token := getUserToken(t, RepoOwner, auth_model.AccessTokenScopeWriteUser) - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/blocks/%s", ForkOwner)). + req := NewRequest(t, "GET", "/api/v1/user/blocks/"+ForkOwner). AddTokenAuth(token) MakeRequest(t, req, http.StatusNotFound) - req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/user/blocks/%s", ForkOwner)). + req = NewRequest(t, "PUT", "/api/v1/user/blocks/"+ForkOwner). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) @@ -308,7 +308,7 @@ func TestCreatePullWhenBlocked(t *testing.T) { // Teardown // Unblock user - req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/blocks/%s", ForkOwner)). + req = NewRequest(t, "DELETE", "/api/v1/user/blocks/"+ForkOwner). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) }) diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index 1c8318db0d..cf50d5e639 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -66,7 +66,7 @@ func testPullMerge(t *testing.T, session *TestSession, user, repo, pullnum strin }{} DecodeJSON(t, resp, &respJSON) - assert.EqualValues(t, fmt.Sprintf("/%s/%s/pulls/%s", user, repo, pullnum), respJSON.Redirect) + assert.Equal(t, fmt.Sprintf("/%s/%s/pulls/%s", user, repo, pullnum), respJSON.Redirect) return resp } @@ -100,7 +100,7 @@ func TestPullMerge(t *testing.T) { resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false) hookTasks, err = webhook.HookTasks(db.DefaultContext, 1, 1) @@ -122,7 +122,7 @@ func TestPullRebase(t *testing.T) { resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleRebase, false) hookTasks, err = webhook.HookTasks(db.DefaultContext, 1, 1) @@ -144,7 +144,7 @@ func TestPullRebaseMerge(t *testing.T) { resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleRebaseMerge, false) hookTasks, err = webhook.HookTasks(db.DefaultContext, 1, 1) @@ -167,7 +167,7 @@ func TestPullSquash(t *testing.T) { resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleSquash, false) hookTasks, err = webhook.HookTasks(db.DefaultContext, 1, 1) @@ -185,7 +185,7 @@ func TestPullCleanUpAfterMerge(t *testing.T) { resp := testPullCreate(t, session, "user1", "repo1", false, "master", "feature/test", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false) // Check PR branch deletion @@ -198,7 +198,7 @@ func TestPullCleanUpAfterMerge(t *testing.T) { assert.NotEmpty(t, respJSON.Redirect, "Redirected URL is not found") elem = strings.Split(respJSON.Redirect, "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) // Check branch deletion result req := NewRequest(t, "GET", respJSON.Redirect) @@ -207,7 +207,7 @@ func TestPullCleanUpAfterMerge(t *testing.T) { htmlDoc := NewHTMLParser(t, resp.Body) resultMsg := htmlDoc.doc.Find(".ui.message>p").Text() - assert.EqualValues(t, "Branch \"user1/repo1:feature/test\" has been deleted.", resultMsg) + assert.Equal(t, "Branch \"user1/repo1:feature/test\" has been deleted.", resultMsg) }) } @@ -296,7 +296,7 @@ func TestCantMergeUnrelated(t *testing.T) { err := git.NewCommand("read-tree", "--empty").Run(git.DefaultContext, &git.RunOpts{Dir: path}) assert.NoError(t, err) - stdin := bytes.NewBufferString("Unrelated File") + stdin := strings.NewReader("Unrelated File") var stdout strings.Builder err = git.NewCommand("hash-object", "-w", "--stdin").Run(git.DefaultContext, &git.RunOpts{ Dir: path, @@ -544,11 +544,11 @@ func TestPullRetargetChildOnBranchDelete(t *testing.T) { respBasePR := testPullCreate(t, session, "user2", "repo1", true, "master", "base-pr", "Base Pull Request") elemBasePR := strings.Split(test.RedirectURL(respBasePR), "/") - assert.EqualValues(t, "pulls", elemBasePR[3]) + assert.Equal(t, "pulls", elemBasePR[3]) respChildPR := testPullCreate(t, session, "user1", "repo1", false, "base-pr", "child-pr", "Child Pull Request") elemChildPR := strings.Split(test.RedirectURL(respChildPR), "/") - assert.EqualValues(t, "pulls", elemChildPR[3]) + assert.Equal(t, "pulls", elemChildPR[3]) testPullMerge(t, session, elemBasePR[1], elemBasePR[2], elemBasePR[4], repo_model.MergeStyleMerge, true) @@ -564,8 +564,8 @@ func TestPullRetargetChildOnBranchDelete(t *testing.T) { targetBranch := htmlDoc.doc.Find("#branch_target>a").Text() prStatus := strings.TrimSpace(htmlDoc.doc.Find(".issue-title-meta>.issue-state-label").Text()) - assert.EqualValues(t, "master", targetBranch) - assert.EqualValues(t, "Open", prStatus) + assert.Equal(t, "master", targetBranch) + assert.Equal(t, "Open", prStatus) }) } @@ -578,11 +578,11 @@ func TestPullDontRetargetChildOnWrongRepo(t *testing.T) { respBasePR := testPullCreate(t, session, "user1", "repo1", false, "master", "base-pr", "Base Pull Request") elemBasePR := strings.Split(test.RedirectURL(respBasePR), "/") - assert.EqualValues(t, "pulls", elemBasePR[3]) + assert.Equal(t, "pulls", elemBasePR[3]) respChildPR := testPullCreate(t, session, "user1", "repo1", true, "base-pr", "child-pr", "Child Pull Request") elemChildPR := strings.Split(test.RedirectURL(respChildPR), "/") - assert.EqualValues(t, "pulls", elemChildPR[3]) + assert.Equal(t, "pulls", elemChildPR[3]) defer test.MockVariableValue(&setting.Repository.PullRequest.RetargetChildrenOnMerge, false)() @@ -601,8 +601,8 @@ func TestPullDontRetargetChildOnWrongRepo(t *testing.T) { targetBranch := htmlDoc.doc.Find("#branch_target>span").Text() prStatus := strings.TrimSpace(htmlDoc.doc.Find(".issue-title-meta>.issue-state-label").Text()) - assert.EqualValues(t, "base-pr", targetBranch) - assert.EqualValues(t, "Closed", prStatus) + assert.Equal(t, "base-pr", targetBranch) + assert.Equal(t, "Closed", prStatus) }) } @@ -614,7 +614,7 @@ func TestPullRequestMergedWithNoPermissionDeleteBranch(t *testing.T) { respBasePR := testPullCreate(t, session, "user4", "repo1", false, "master", "base-pr", "Base Pull Request") elemBasePR := strings.Split(test.RedirectURL(respBasePR), "/") - assert.EqualValues(t, "pulls", elemBasePR[3]) + assert.Equal(t, "pulls", elemBasePR[3]) // user2 has no permission to delete branch of repo user1/repo1 session2 := loginUser(t, "user2") @@ -665,7 +665,7 @@ func TestPullMergeIndexerNotifier(t *testing.T) { // merge the pull request elem := strings.Split(test.RedirectURL(createPullResp), "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false) // check if the issue is closed @@ -699,7 +699,7 @@ func testResetRepo(t *testing.T, repoPath, branch, commitID string) { defer repo.Close() id, err := repo.GetBranchCommitID(branch) assert.NoError(t, err) - assert.EqualValues(t, commitID, id) + assert.Equal(t, commitID, id) } func TestPullAutoMergeAfterCommitStatusSucceed(t *testing.T) { diff --git a/tests/integration/pull_review_test.go b/tests/integration/pull_review_test.go index cd354e1b6c..3c96e4e6c4 100644 --- a/tests/integration/pull_review_test.go +++ b/tests/integration/pull_review_test.go @@ -119,7 +119,7 @@ func TestPullView_CodeOwner(t *testing.T) { assert.Len(t, reviewNotifiers, 2) reviewerIDs := []int64{reviewNotifiers[0].Reviewer.ID, reviewNotifiers[1].Reviewer.ID} sort.Slice(reviewerIDs, func(i, j int) bool { return reviewerIDs[i] < reviewerIDs[j] }) - assert.EqualValues(t, []int64{5, 8}, reviewerIDs) + assert.Equal(t, []int64{5, 8}, reviewerIDs) reviewNotifiers, err = issue_service.PullRequestCodeOwnersReviewSpecialCommits(db.DefaultContext, pr, resp1.Commit.SHA, resp2.Commit.SHA) assert.NoError(t, err) @@ -130,13 +130,13 @@ func TestPullView_CodeOwner(t *testing.T) { assert.NoError(t, err) prUpdated1 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID}) assert.NoError(t, prUpdated1.LoadIssue(db.DefaultContext)) - assert.EqualValues(t, "[WIP] Test Pull Request", prUpdated1.Issue.Title) + assert.Equal(t, "[WIP] Test Pull Request", prUpdated1.Issue.Title) err = issue_service.ChangeTitle(db.DefaultContext, prUpdated1.Issue, user2, "Test Pull Request2") assert.NoError(t, err) prUpdated2 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID}) assert.NoError(t, prUpdated2.LoadIssue(db.DefaultContext)) - assert.EqualValues(t, "Test Pull Request2", prUpdated2.Issue.Title) + assert.Equal(t, "Test Pull Request2", prUpdated2.Issue.Title) }) // change the default branch CODEOWNERS file to change README.md's codeowner @@ -229,7 +229,7 @@ func TestPullView_GivenApproveOrRejectReviewOnClosedPR(t *testing.T) { testEditFile(t, user1Session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") resp := testPullCreate(t, user1Session, "user1", "repo1", false, "master", "master", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) testPullMerge(t, user1Session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false) // Grab the CSRF token. @@ -249,7 +249,7 @@ func TestPullView_GivenApproveOrRejectReviewOnClosedPR(t *testing.T) { testEditFileToNewBranch(t, user1Session, "user1", "repo1", "master", "a-test-branch", "README.md", "Hello, World (Editied...again)\n") resp := testPullCreate(t, user1Session, "user1", "repo1", false, "master", "a-test-branch", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) testIssueClose(t, user1Session, elem[1], elem[2], elem[4]) // Grab the CSRF token. diff --git a/tests/integration/pull_status_test.go b/tests/integration/pull_status_test.go index ac9036ca96..63ffe94320 100644 --- a/tests/integration/pull_status_test.go +++ b/tests/integration/pull_status_test.go @@ -86,7 +86,7 @@ func TestPullCreate_CommitStatus(t *testing.T) { commitURL, exists = doc.doc.Find("#commits-table tbody tr td.sha a").Last().Attr("href") assert.True(t, exists) assert.NotEmpty(t, commitURL) - assert.EqualValues(t, commitID, path.Base(commitURL)) + assert.Equal(t, commitID, path.Base(commitURL)) cls, ok := doc.doc.Find("#commits-table tbody tr td.message .commit-status").Last().Attr("class") assert.True(t, ok) @@ -95,7 +95,7 @@ func TestPullCreate_CommitStatus(t *testing.T) { repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: "user1", Name: "repo1"}) css := unittest.AssertExistsAndLoadBean(t, &git_model.CommitStatusSummary{RepoID: repo1.ID, SHA: commitID}) - assert.EqualValues(t, api.CommitStatusWarning, css.State) + assert.Equal(t, api.CommitStatusWarning, css.State) }) } diff --git a/tests/integration/pull_update_test.go b/tests/integration/pull_update_test.go index 7ede84127c..d28537112d 100644 --- a/tests/integration/pull_update_test.go +++ b/tests/integration/pull_update_test.go @@ -33,8 +33,8 @@ func TestAPIPullUpdate(t *testing.T) { // Test GetDiverging diffCount, err := pull_service.GetDiverging(git.DefaultContext, pr) assert.NoError(t, err) - assert.EqualValues(t, 1, diffCount.Behind) - assert.EqualValues(t, 1, diffCount.Ahead) + assert.Equal(t, 1, diffCount.Behind) + assert.Equal(t, 1, diffCount.Ahead) assert.NoError(t, pr.LoadBaseRepo(db.DefaultContext)) assert.NoError(t, pr.LoadIssue(db.DefaultContext)) @@ -47,8 +47,8 @@ func TestAPIPullUpdate(t *testing.T) { // Test GetDiverging after update diffCount, err = pull_service.GetDiverging(git.DefaultContext, pr) assert.NoError(t, err) - assert.EqualValues(t, 0, diffCount.Behind) - assert.EqualValues(t, 2, diffCount.Ahead) + assert.Equal(t, 0, diffCount.Behind) + assert.Equal(t, 2, diffCount.Ahead) }) } @@ -62,8 +62,8 @@ func TestAPIPullUpdateByRebase(t *testing.T) { // Test GetDiverging diffCount, err := pull_service.GetDiverging(git.DefaultContext, pr) assert.NoError(t, err) - assert.EqualValues(t, 1, diffCount.Behind) - assert.EqualValues(t, 1, diffCount.Ahead) + assert.Equal(t, 1, diffCount.Behind) + assert.Equal(t, 1, diffCount.Ahead) assert.NoError(t, pr.LoadBaseRepo(db.DefaultContext)) assert.NoError(t, pr.LoadIssue(db.DefaultContext)) @@ -76,8 +76,8 @@ func TestAPIPullUpdateByRebase(t *testing.T) { // Test GetDiverging after update diffCount, err = pull_service.GetDiverging(git.DefaultContext, pr) assert.NoError(t, err) - assert.EqualValues(t, 0, diffCount.Behind) - assert.EqualValues(t, 1, diffCount.Ahead) + assert.Equal(t, 0, diffCount.Behind) + assert.Equal(t, 1, diffCount.Ahead) }) } diff --git a/tests/integration/release_test.go b/tests/integration/release_test.go index 1a7f9e38b5..125f0810a7 100644 --- a/tests/integration/release_test.go +++ b/tests/integration/release_test.go @@ -54,12 +54,12 @@ func checkLatestReleaseAndCount(t *testing.T, session *TestSession, repoURL, ver htmlDoc := NewHTMLParser(t, resp.Body) labelText := htmlDoc.doc.Find("#release-list > li .detail .label").First().Text() - assert.EqualValues(t, label, labelText) + assert.Equal(t, label, labelText) titleText := htmlDoc.doc.Find("#release-list > li .detail h4 a").First().Text() - assert.EqualValues(t, version, titleText) + assert.Equal(t, version, titleText) releaseList := htmlDoc.doc.Find("#release-list > li") - assert.EqualValues(t, count, releaseList.Length()) + assert.Equal(t, count, releaseList.Length()) } func TestViewReleases(t *testing.T) { @@ -157,14 +157,14 @@ func TestViewReleaseListNoLogin(t *testing.T) { commitsToMain = append(commitsToMain, s.Find(".ahead > a").Text()) }) - assert.EqualValues(t, []string{ + assert.Equal(t, []string{ "/user2/repo-release/releases/tag/empty-target-branch", "/user2/repo-release/releases/tag/non-existing-target-branch", "/user2/repo-release/releases/tag/v2.0", "/user2/repo-release/releases/tag/v1.1", "/user2/repo-release/releases/tag/v1.0", }, links) - assert.EqualValues(t, []string{ + assert.Equal(t, []string{ "1 commits", // like v1.1 "1 commits", // like v1.1 "0 commits", @@ -182,8 +182,8 @@ func TestViewSingleRelease(t *testing.T) { htmlDoc := NewHTMLParser(t, resp.Body) // check the "number of commits to main since this release" releaseList := htmlDoc.doc.Find("#release-list .ahead > a") - assert.EqualValues(t, 1, releaseList.Length()) - assert.EqualValues(t, "3 commits", releaseList.First().Text()) + assert.Equal(t, 1, releaseList.Length()) + assert.Equal(t, "3 commits", releaseList.First().Text()) }) t.Run("Login", func(t *testing.T) { session := loginUser(t, "user1") @@ -218,7 +218,7 @@ func TestViewReleaseListLogin(t *testing.T) { links = append(links, link) }) - assert.EqualValues(t, []string{ + assert.Equal(t, []string{ "/user2/repo1/releases/tag/draft-release", "/user2/repo1/releases/tag/v1.0", "/user2/repo1/releases/tag/v1.1", @@ -245,7 +245,7 @@ func TestViewTagsList(t *testing.T) { tagNames = append(tagNames, s.Text()) }) - assert.EqualValues(t, []string{"v1.0", "delete-tag", "v1.1"}, tagNames) + assert.Equal(t, []string{"v1.0", "delete-tag", "v1.1"}, tagNames) } func TestDownloadReleaseAttachment(t *testing.T) { diff --git a/tests/integration/repo_activity_test.go b/tests/integration/repo_activity_test.go index b04560379d..d5025decba 100644 --- a/tests/integration/repo_activity_test.go +++ b/tests/integration/repo_activity_test.go @@ -9,7 +9,9 @@ import ( "strings" "testing" + "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/test" "github.com/stretchr/testify/assert" @@ -24,7 +26,7 @@ func TestRepoActivity(t *testing.T) { testEditFile(t, session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") resp := testPullCreate(t, session, "user1", "repo1", false, "master", "master", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") - assert.EqualValues(t, "pulls", elem[3]) + assert.Equal(t, "pulls", elem[3]) testPullMerge(t, session, elem[1], elem[2], elem[4], repo_model.MergeStyleMerge, false) testEditFileToNewBranch(t, session, "user1", "repo1", "master", "feat/better_readme", "README.md", "Hello, World (Edited Again)\n") @@ -61,5 +63,14 @@ func TestRepoActivity(t *testing.T) { // Should be 3 new issues list = htmlDoc.doc.Find("#new-issues").Next().Find("p.desc") assert.Len(t, list.Nodes, 3) + + // Non-existing default branch + repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "repo1"}) + repo1.DefaultBranch = "no-such-branch" + _, _ = db.GetEngine(t.Context()).Cols("default_branch").Update(repo1) + req = NewRequest(t, "GET", "/user2/repo1/activity") + req.Header.Add("Accept", "text/html") + resp = session.MakeRequest(t, req, http.StatusNotFound) + assert.Contains(t, resp.Body.String(), `Default branch "no-such-branch" does not exist.`) }) } diff --git a/tests/integration/repo_branch_test.go b/tests/integration/repo_branch_test.go index f9cf13112a..50ceb65330 100644 --- a/tests/integration/repo_branch_test.go +++ b/tests/integration/repo_branch_test.go @@ -138,7 +138,7 @@ func TestCreateBranchInvalidCSRF(t *testing.T) { } func prepareRecentlyPushedBranchTest(t *testing.T, headSession *TestSession, baseRepo, headRepo *repo_model.Repository) { - refSubURL := fmt.Sprintf("branch/%s", headRepo.DefaultBranch) + refSubURL := "branch/" + headRepo.DefaultBranch baseRepoPath := baseRepo.OwnerName + "/" + baseRepo.Name headRepoPath := headRepo.OwnerName + "/" + headRepo.Name // Case 1: Normal branch changeset to display pushed message @@ -168,7 +168,7 @@ func prepareRecentlyPushedBranchTest(t *testing.T, headSession *TestSession, bas } func prepareRecentlyPushedBranchSpecialTest(t *testing.T, session *TestSession, baseRepo, headRepo *repo_model.Repository) { - refSubURL := fmt.Sprintf("branch/%s", headRepo.DefaultBranch) + refSubURL := "branch/" + headRepo.DefaultBranch baseRepoPath := baseRepo.OwnerName + "/" + baseRepo.Name headRepoPath := headRepo.OwnerName + "/" + headRepo.Name // create branch with no new commit @@ -196,7 +196,7 @@ func testCreatePullToDefaultBranch(t *testing.T, session *TestSession, baseRepo, } func prepareRepoPR(t *testing.T, baseSession, headSession *TestSession, baseRepo, headRepo *repo_model.Repository) { - refSubURL := fmt.Sprintf("branch/%s", headRepo.DefaultBranch) + refSubURL := "branch/" + headRepo.DefaultBranch testCreateBranch(t, headSession, headRepo.OwnerName, headRepo.Name, refSubURL, "new-commit", http.StatusSeeOther) // create opening PR diff --git a/tests/integration/repo_commits_search_test.go b/tests/integration/repo_commits_search_test.go index 74ac25c0f5..9b05e36399 100644 --- a/tests/integration/repo_commits_search_test.go +++ b/tests/integration/repo_commits_search_test.go @@ -23,7 +23,7 @@ func testRepoCommitsSearch(t *testing.T, query, commit string) { doc := NewHTMLParser(t, resp.Body) sel := doc.doc.Find("#commits-table tbody tr td.sha a") - assert.EqualValues(t, commit, strings.TrimSpace(sel.Text())) + assert.Equal(t, commit, strings.TrimSpace(sel.Text())) } func TestRepoCommitsSearch(t *testing.T) { diff --git a/tests/integration/repo_commits_test.go b/tests/integration/repo_commits_test.go index fbe215eb85..dee0aa6176 100644 --- a/tests/integration/repo_commits_test.go +++ b/tests/integration/repo_commits_test.go @@ -58,9 +58,9 @@ func Test_ReposGitCommitListNotMaster(t *testing.T) { }) assert.Len(t, commits, 3) - assert.EqualValues(t, "69554a64c1e6030f051e5c3f94bfbd773cd6a324", commits[0]) - assert.EqualValues(t, "27566bd5738fc8b4e3fef3c5e72cce608537bd95", commits[1]) - assert.EqualValues(t, "5099b81332712fe655e34e8dd63574f503f61811", commits[2]) + assert.Equal(t, "69554a64c1e6030f051e5c3f94bfbd773cd6a324", commits[0]) + assert.Equal(t, "27566bd5738fc8b4e3fef3c5e72cce608537bd95", commits[1]) + assert.Equal(t, "5099b81332712fe655e34e8dd63574f503f61811", commits[2]) userNames := []string{} doc.doc.Find("#commits-table .author-wrapper").Each(func(i int, s *goquery.Selection) { @@ -71,9 +71,9 @@ func Test_ReposGitCommitListNotMaster(t *testing.T) { }) assert.Len(t, userNames, 3) - assert.EqualValues(t, "User2", userNames[0]) - assert.EqualValues(t, "user21", userNames[1]) - assert.EqualValues(t, "User2", userNames[2]) + assert.Equal(t, "User2", userNames[0]) + assert.Equal(t, "user21", userNames[1]) + assert.Equal(t, "User2", userNames[2]) } func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) { @@ -141,7 +141,7 @@ func testRepoCommitsWithStatus(t *testing.T, resp, respOne *httptest.ResponseRec assert.Equal(t, api.CommitStatusState(state), statuses[0].State) assert.Equal(t, setting.AppURL+"api/v1/repos/user2/repo1/statuses/65f1bf27bc3bf70f64657658635e66094edbcb4d", statuses[0].URL) assert.Equal(t, "http://test.ci/", statuses[0].TargetURL) - assert.Equal(t, "", statuses[0].Description) + assert.Empty(t, statuses[0].Description) assert.Equal(t, "testci", statuses[0].Context) assert.Len(t, status.Statuses, 1) @@ -240,7 +240,7 @@ func TestRepoCommitsStatusMultiple(t *testing.T) { resp = session.MakeRequest(t, req, http.StatusOK) doc = NewHTMLParser(t, resp.Body) - // Check that the data-tippy="commit-statuses" (for trigger) and commit-status (svg) are present - sel := doc.doc.Find("#commits-table .message [data-tippy=\"commit-statuses\"] .commit-status") + // Check that the data-global-init="initCommitStatuses" (for trigger) and commit-status (svg) are present + sel := doc.doc.Find(`#commits-table .message [data-global-init="initCommitStatuses"] .commit-status`) assert.Equal(t, 1, sel.Length()) } diff --git a/tests/integration/repo_fork_test.go b/tests/integration/repo_fork_test.go index cbe5e4bb3f..a7010af14a 100644 --- a/tests/integration/repo_fork_test.go +++ b/tests/integration/repo_fork_test.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "strconv" "testing" "code.gitea.io/gitea/models/db" @@ -46,7 +47,7 @@ func testRepoFork(t *testing.T, session *TestSession, ownerName, repoName, forkO assert.True(t, exists, "Fork owner '%s' is not present in select box", forkOwnerName) req = NewRequestWithValues(t, "POST", link, map[string]string{ "_csrf": htmlDoc.GetCSRF(), - "uid": fmt.Sprintf("%d", forkOwner.ID), + "uid": strconv.FormatInt(forkOwner.ID, 10), "repo_name": forkRepoName, "fork_single_branch": forkBranch, }) @@ -88,7 +89,7 @@ func TestForkListLimitedAndPrivateRepos(t *testing.T) { // fork to a limited org limitedOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 22}) - assert.EqualValues(t, structs.VisibleTypeLimited, limitedOrg.Visibility) + assert.Equal(t, structs.VisibleTypeLimited, limitedOrg.Visibility) ownerTeam1, err := org_model.OrgFromUser(limitedOrg).GetOwnerTeam(db.DefaultContext) assert.NoError(t, err) assert.NoError(t, org_service.AddTeamMember(db.DefaultContext, ownerTeam1, user1)) @@ -98,7 +99,7 @@ func TestForkListLimitedAndPrivateRepos(t *testing.T) { user4Sess := loginUser(t, "user4") user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user4"}) privateOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 23}) - assert.EqualValues(t, structs.VisibleTypePrivate, privateOrg.Visibility) + assert.Equal(t, structs.VisibleTypePrivate, privateOrg.Visibility) ownerTeam2, err := org_model.OrgFromUser(privateOrg).GetOwnerTeam(db.DefaultContext) assert.NoError(t, err) assert.NoError(t, org_service.AddTeamMember(db.DefaultContext, ownerTeam2, user4)) @@ -109,7 +110,7 @@ func TestForkListLimitedAndPrivateRepos(t *testing.T) { req := NewRequest(t, "GET", "/user2/repo1/forks") resp := MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - assert.EqualValues(t, 0, htmlDoc.Find(forkItemSelector).Length()) + assert.Equal(t, 0, htmlDoc.Find(forkItemSelector).Length()) }) t.Run("Logged in", func(t *testing.T) { @@ -119,11 +120,11 @@ func TestForkListLimitedAndPrivateRepos(t *testing.T) { resp := user1Sess.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) // since user1 is an admin, he can get both of the forked repositories - assert.EqualValues(t, 2, htmlDoc.Find(forkItemSelector).Length()) + assert.Equal(t, 2, htmlDoc.Find(forkItemSelector).Length()) assert.NoError(t, org_service.AddTeamMember(db.DefaultContext, ownerTeam2, user1)) resp = user1Sess.MakeRequest(t, req, http.StatusOK) htmlDoc = NewHTMLParser(t, resp.Body) - assert.EqualValues(t, 2, htmlDoc.Find(forkItemSelector).Length()) + assert.Equal(t, 2, htmlDoc.Find(forkItemSelector).Length()) }) } diff --git a/tests/integration/repo_generate_test.go b/tests/integration/repo_generate_test.go index f5645d62bc..fca4e92982 100644 --- a/tests/integration/repo_generate_test.go +++ b/tests/integration/repo_generate_test.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" "net/http/httptest" + "strconv" "strings" "testing" @@ -44,7 +45,7 @@ func testRepoGenerate(t *testing.T, session *TestSession, templateID, templateOw assert.True(t, exists, "Generate owner '%s' is not present in select box", generateOwnerName) req = NewRequestWithValues(t, "POST", link, map[string]string{ "_csrf": htmlDoc.GetCSRF(), - "uid": fmt.Sprintf("%d", generateOwner.ID), + "uid": strconv.FormatInt(generateOwner.ID, 10), "repo_name": generateRepoName, "repo_template": templateID, "git_content": "true", diff --git a/tests/integration/repo_search_test.go b/tests/integration/repo_search_test.go index 29d1517f4e..36a2e81f3b 100644 --- a/tests/integration/repo_search_test.go +++ b/tests/integration/repo_search_test.go @@ -57,5 +57,5 @@ func testSearch(t *testing.T, url string, expected []string) { resp := MakeRequest(t, req, http.StatusOK) filenames := resultFilenames(NewHTMLParser(t, resp.Body)) - assert.EqualValues(t, expected, filenames) + assert.Equal(t, expected, filenames) } diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index a5728ffcbd..c04d09af08 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -6,21 +6,49 @@ package integration import ( "fmt" "net/http" + "os" "path" + "strconv" "strings" "testing" "time" + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/modules/util" + repo_service "code.gitea.io/gitea/services/repository" "code.gitea.io/gitea/tests" "github.com/PuerkitoBio/goquery" "github.com/stretchr/testify/assert" ) -func TestViewRepo(t *testing.T) { +func TestRepoView(t *testing.T) { defer tests.PrepareTestEnv(t)() + t.Run("ViewRepoPublic", testViewRepoPublic) + t.Run("ViewRepoWithCache", testViewRepoWithCache) + t.Run("ViewRepoPrivate", testViewRepoPrivate) + t.Run("ViewRepo1CloneLinkAnonymous", testViewRepo1CloneLinkAnonymous) + t.Run("ViewRepo1CloneLinkAuthorized", testViewRepo1CloneLinkAuthorized) + t.Run("ViewRepoWithSymlinks", testViewRepoWithSymlinks) + t.Run("ViewFileInRepo", testViewFileInRepo) + t.Run("BlameFileInRepo", testBlameFileInRepo) + t.Run("ViewRepoDirectory", testViewRepoDirectory) + t.Run("ViewRepoDirectoryReadme", testViewRepoDirectoryReadme) + t.Run("MarkDownReadmeImage", testMarkDownReadmeImage) + t.Run("MarkDownReadmeImageSubfolder", testMarkDownReadmeImageSubfolder) + t.Run("GeneratedSourceLink", testGeneratedSourceLink) + t.Run("ViewCommit", testViewCommit) +} + +func testViewRepoPublic(t *testing.T) { + defer tests.PrintCurrentTest(t)() session := loginUser(t, "user2") @@ -41,87 +69,118 @@ func TestViewRepo(t *testing.T) { session.MakeRequest(t, req, http.StatusNotFound) } -func testViewRepo(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testViewRepoWithCache(t *testing.T) { + defer tests.PrintCurrentTest(t)() + testView := func(t *testing.T) { + req := NewRequest(t, "GET", "/org3/repo3") + session := loginUser(t, "user2") + resp := session.MakeRequest(t, req, http.StatusOK) - req := NewRequest(t, "GET", "/org3/repo3") - session := loginUser(t, "user2") - resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + files := htmlDoc.doc.Find("#repo-files-table .repo-file-item") - htmlDoc := NewHTMLParser(t, resp.Body) - files := htmlDoc.doc.Find("#repo-files-table .repo-file-item") + type file struct { + fileName string + commitID string + commitMsg string + commitTime string + } - type file struct { - fileName string - commitID string - commitMsg string - commitTime string - } + var items []file - var items []file + files.Each(func(i int, s *goquery.Selection) { + tds := s.Find(".repo-file-cell") + var f file + tds.Each(func(i int, s *goquery.Selection) { + if i == 0 { + f.fileName = strings.TrimSpace(s.Text()) + } else if i == 1 { + a := s.Find("a") + f.commitMsg = strings.TrimSpace(a.Text()) + l, _ := a.Attr("href") + f.commitID = path.Base(l) + } + }) - files.Each(func(i int, s *goquery.Selection) { - tds := s.Find(".repo-file-cell") - var f file - tds.Each(func(i int, s *goquery.Selection) { - if i == 0 { - f.fileName = strings.TrimSpace(s.Text()) - } else if i == 1 { - a := s.Find("a") - f.commitMsg = strings.TrimSpace(a.Text()) - l, _ := a.Attr("href") - f.commitID = path.Base(l) - } + // convert "2017-06-14 21:54:21 +0800" to "Wed, 14 Jun 2017 13:54:21 UTC" + htmlTimeString, _ := s.Find("relative-time").Attr("datetime") + htmlTime, _ := time.Parse(time.RFC3339, htmlTimeString) + f.commitTime = htmlTime.In(time.Local).Format(time.RFC1123) + items = append(items, f) }) - // convert "2017-06-14 21:54:21 +0800" to "Wed, 14 Jun 2017 13:54:21 UTC" - htmlTimeString, _ := s.Find("relative-time").Attr("datetime") - htmlTime, _ := time.Parse(time.RFC3339, htmlTimeString) - f.commitTime = htmlTime.In(time.Local).Format(time.RFC1123) - items = append(items, f) - }) + commitT := time.Date(2017, time.June, 14, 13, 54, 21, 0, time.UTC).In(time.Local).Format(time.RFC1123) + assert.Equal(t, []file{ + { + fileName: "doc", + commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6", + commitMsg: "init project", + commitTime: commitT, + }, + { + fileName: "README.md", + commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6", + commitMsg: "init project", + commitTime: commitT, + }, + }, items) + } - commitT := time.Date(2017, time.June, 14, 13, 54, 21, 0, time.UTC).In(time.Local).Format(time.RFC1123) - assert.EqualValues(t, []file{ - { - fileName: "doc", - commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6", - commitMsg: "init project", - commitTime: commitT, - }, - { - fileName: "README.md", - commitID: "2a47ca4b614a9f5a43abbd5ad851a54a616ffee6", - commitMsg: "init project", - commitTime: commitT, - }, - }, items) -} - -func TestViewRepo2(t *testing.T) { + // FIXME: these test don't seem quite right, no enough assert // no last commit cache - testViewRepo(t) - + testView(t) // enable last commit cache for all repositories oldCommitsCount := setting.CacheService.LastCommit.CommitsCount setting.CacheService.LastCommit.CommitsCount = 0 // first view will not hit the cache - testViewRepo(t) + testView(t) // second view will hit the cache - testViewRepo(t) + testView(t) setting.CacheService.LastCommit.CommitsCount = oldCommitsCount } -func TestViewRepo3(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testViewRepoPrivate(t *testing.T) { + defer tests.PrintCurrentTest(t)() req := NewRequest(t, "GET", "/org3/repo3") - session := loginUser(t, "user4") - session.MakeRequest(t, req, http.StatusOK) + MakeRequest(t, req, http.StatusNotFound) + + t.Run("OrgMemberAccess", func(t *testing.T) { + req = NewRequest(t, "GET", "/org3/repo3") + session := loginUser(t, "user4") + resp := session.MakeRequest(t, req, http.StatusOK) + assert.Contains(t, resp.Body.String(), `Public Access`) + + // remove "anonymous read" + req = NewRequestWithValues(t, "POST", "/org3/repo3/settings/public_access", map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // try to "anonymous read" (not found) + req = NewRequest(t, "GET", "/org3/repo3") + MakeRequest(t, req, http.StatusNotFound) + }) } -func TestViewRepo1CloneLinkAnonymous(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testViewRepo1CloneLinkAnonymous(t *testing.T) { + defer tests.PrintCurrentTest(t)() req := NewRequest(t, "GET", "/user2/repo1") resp := MakeRequest(t, req, http.StatusOK) @@ -139,8 +198,8 @@ func TestViewRepo1CloneLinkAnonymous(t *testing.T) { assert.Equal(t, "tea clone user2/repo1", link) } -func TestViewRepo1CloneLinkAuthorized(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testViewRepo1CloneLinkAuthorized(t *testing.T) { + defer tests.PrintCurrentTest(t)() session := loginUser(t, "user2") @@ -162,8 +221,8 @@ func TestViewRepo1CloneLinkAuthorized(t *testing.T) { assert.Equal(t, "tea clone user2/repo1", link) } -func TestViewRepoWithSymlinks(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testViewRepoWithSymlinks(t *testing.T) { + defer tests.PrintCurrentTest(t)() defer test.MockVariableValue(&setting.UI.FileIconTheme, "basic")() session := loginUser(t, "user2") @@ -186,8 +245,8 @@ func TestViewRepoWithSymlinks(t *testing.T) { } // TestViewFileInRepo repo description, topics and summary should not be displayed when viewing a file -func TestViewFileInRepo(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testViewFileInRepo(t *testing.T) { + defer tests.PrintCurrentTest(t)() session := loginUser(t, "user2") @@ -199,14 +258,14 @@ func TestViewFileInRepo(t *testing.T) { repoTopics := htmlDoc.doc.Find("#repo-topics") repoSummary := htmlDoc.doc.Find(".repository-summary") - assert.EqualValues(t, 0, description.Length()) - assert.EqualValues(t, 0, repoTopics.Length()) - assert.EqualValues(t, 0, repoSummary.Length()) + assert.Equal(t, 0, description.Length()) + assert.Equal(t, 0, repoTopics.Length()) + assert.Equal(t, 0, repoSummary.Length()) } // TestBlameFileInRepo repo description, topics and summary should not be displayed when running blame on a file -func TestBlameFileInRepo(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testBlameFileInRepo(t *testing.T) { + defer tests.PrintCurrentTest(t)() session := loginUser(t, "user2") @@ -218,14 +277,14 @@ func TestBlameFileInRepo(t *testing.T) { repoTopics := htmlDoc.doc.Find("#repo-topics") repoSummary := htmlDoc.doc.Find(".repository-summary") - assert.EqualValues(t, 0, description.Length()) - assert.EqualValues(t, 0, repoTopics.Length()) - assert.EqualValues(t, 0, repoSummary.Length()) + assert.Equal(t, 0, description.Length()) + assert.Equal(t, 0, repoTopics.Length()) + assert.Equal(t, 0, repoSummary.Length()) } // TestViewRepoDirectory repo description, topics and summary should not be displayed when within a directory -func TestViewRepoDirectory(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testViewRepoDirectory(t *testing.T) { + defer tests.PrintCurrentTest(t)() session := loginUser(t, "user2") @@ -246,8 +305,8 @@ func TestViewRepoDirectory(t *testing.T) { } // ensure that the all the different ways to find and render a README work -func TestViewRepoDirectoryReadme(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testViewRepoDirectoryReadme(t *testing.T) { + defer tests.PrintCurrentTest(t)() // there are many combinations: // - READMEs can be .md, .txt, or have no extension @@ -353,8 +412,8 @@ func TestViewRepoDirectoryReadme(t *testing.T) { missing("symlink-loop", "/user2/readme-test/src/branch/symlink-loop/") } -func TestMarkDownReadmeImage(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testMarkDownReadmeImage(t *testing.T) { + defer tests.PrintCurrentTest(t)() session := loginUser(t, "user2") @@ -375,8 +434,8 @@ func TestMarkDownReadmeImage(t *testing.T) { assert.Equal(t, "/user2/repo1/media/branch/home-md-img-check/test-fake-img.jpg", src) } -func TestMarkDownReadmeImageSubfolder(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testMarkDownReadmeImageSubfolder(t *testing.T) { + defer tests.PrintCurrentTest(t)() session := loginUser(t, "user2") @@ -398,8 +457,8 @@ func TestMarkDownReadmeImageSubfolder(t *testing.T) { assert.Equal(t, "/user2/repo1/media/branch/sub-home-md-img-check/docs/test-fake-img.jpg", src) } -func TestGeneratedSourceLink(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testGeneratedSourceLink(t *testing.T) { + defer tests.PrintCurrentTest(t)() t.Run("Rendered file", func(t *testing.T) { defer tests.PrintCurrentTest(t)() @@ -434,11 +493,54 @@ func TestGeneratedSourceLink(t *testing.T) { }) } -func TestViewCommit(t *testing.T) { - defer tests.PrepareTestEnv(t)() +func testViewCommit(t *testing.T) { + defer tests.PrintCurrentTest(t)() req := NewRequest(t, "GET", "/user2/repo1/commit/0123456789012345678901234567890123456789") req.Header.Add("Accept", "text/html") resp := MakeRequest(t, req, http.StatusNotFound) assert.True(t, test.IsNormalPageCompleted(resp.Body.String()), "non-existing commit should render 404 page") } + +// TestGenerateRepository the test cannot succeed when moved as a unit test +func TestGenerateRepository(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // a successful generate from template + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo44 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 44}) + + generatedRepo, err := repo_service.GenerateRepository(git.DefaultContext, user2, user2, repo44, repo_service.GenerateRepoOptions{ + Name: "generated-from-template-44", + GitContent: true, + }) + assert.NoError(t, err) + assert.NotNil(t, generatedRepo) + + exist, err := util.IsExist(repo_model.RepoPath(user2.Name, generatedRepo.Name)) + assert.NoError(t, err) + assert.True(t, exist) + + unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: generatedRepo.Name}) + + err = repo_service.DeleteRepositoryDirectly(db.DefaultContext, user2, generatedRepo.ID) + assert.NoError(t, err) + + // a failed creating because some mock data + // create the repository directory so that the creation will fail after database record created. + assert.NoError(t, os.MkdirAll(repo_model.RepoPath(user2.Name, "generated-from-template-44"), os.ModePerm)) + + generatedRepo2, err := repo_service.GenerateRepository(db.DefaultContext, user2, user2, repo44, repo_service.GenerateRepoOptions{ + Name: "generated-from-template-44", + GitContent: true, + }) + assert.Nil(t, generatedRepo2) + assert.Error(t, err) + + // assert the cleanup is successful + unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: generatedRepo.Name}) + + exist, err = util.IsExist(repo_model.RepoPath(user2.Name, generatedRepo.Name)) + assert.NoError(t, err) + assert.False(t, exist) +} diff --git a/tests/integration/repo_topic_test.go b/tests/integration/repo_topic_test.go index f198397007..7f9594b9fd 100644 --- a/tests/integration/repo_topic_test.go +++ b/tests/integration/repo_topic_test.go @@ -25,7 +25,7 @@ func TestTopicSearch(t *testing.T) { res := MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) DecodeJSON(t, res, &topics) assert.Len(t, topics.TopicNames, 6) - assert.EqualValues(t, "6", res.Header().Get("x-total-count")) + assert.Equal(t, "6", res.Header().Get("x-total-count")) // pagination search topics topics.TopicNames = nil @@ -35,7 +35,7 @@ func TestTopicSearch(t *testing.T) { res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) DecodeJSON(t, res, &topics) assert.Len(t, topics.TopicNames, 4) - assert.EqualValues(t, "6", res.Header().Get("x-total-count")) + assert.Equal(t, "6", res.Header().Get("x-total-count")) // second page topics.TopicNames = nil @@ -45,7 +45,7 @@ func TestTopicSearch(t *testing.T) { res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) DecodeJSON(t, res, &topics) assert.Len(t, topics.TopicNames, 2) - assert.EqualValues(t, "6", res.Header().Get("x-total-count")) + assert.Equal(t, "6", res.Header().Get("x-total-count")) // add keyword search topics.TopicNames = nil @@ -63,7 +63,7 @@ func TestTopicSearch(t *testing.T) { DecodeJSON(t, res, &topics) if assert.Len(t, topics.TopicNames, 1) { assert.EqualValues(t, 2, topics.TopicNames[0].ID) - assert.EqualValues(t, "database", topics.TopicNames[0].Name) - assert.EqualValues(t, 1, topics.TopicNames[0].RepoCount) + assert.Equal(t, "database", topics.TopicNames[0].Name) + assert.Equal(t, 1, topics.TopicNames[0].RepoCount) } } diff --git a/tests/integration/repo_webhook_test.go b/tests/integration/repo_webhook_test.go index 7e85c10d4b..89df15b8de 100644 --- a/tests/integration/repo_webhook_test.go +++ b/tests/integration/repo_webhook_test.go @@ -149,11 +149,11 @@ func Test_WebhookCreate(t *testing.T) { // 3. validate the webhook is triggered assert.Len(t, payloads, 1) - assert.EqualValues(t, string(webhook_module.HookEventCreate), triggeredEvent) - assert.EqualValues(t, "repo1", payloads[0].Repo.Name) - assert.EqualValues(t, "user2/repo1", payloads[0].Repo.FullName) - assert.EqualValues(t, "master2", payloads[0].Ref) - assert.EqualValues(t, "branch", payloads[0].RefType) + assert.Equal(t, string(webhook_module.HookEventCreate), triggeredEvent) + assert.Equal(t, "repo1", payloads[0].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName) + assert.Equal(t, "master2", payloads[0].Ref) + assert.Equal(t, "branch", payloads[0].RefType) }) } @@ -181,12 +181,12 @@ func Test_WebhookDelete(t *testing.T) { testAPIDeleteBranch(t, "master2", http.StatusNoContent) // 3. validate the webhook is triggered - assert.EqualValues(t, "delete", triggeredEvent) + assert.Equal(t, "delete", triggeredEvent) assert.Len(t, payloads, 1) - assert.EqualValues(t, "repo1", payloads[0].Repo.Name) - assert.EqualValues(t, "user2/repo1", payloads[0].Repo.FullName) - assert.EqualValues(t, "master2", payloads[0].Ref) - assert.EqualValues(t, "branch", payloads[0].RefType) + assert.Equal(t, "repo1", payloads[0].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName) + assert.Equal(t, "master2", payloads[0].Ref) + assert.Equal(t, "branch", payloads[0].RefType) }) } @@ -213,12 +213,12 @@ func Test_WebhookFork(t *testing.T) { testRepoFork(t, session, "user2", "repo1", "user1", "repo1-fork", "master") // 3. validate the webhook is triggered - assert.EqualValues(t, "fork", triggeredEvent) + assert.Equal(t, "fork", triggeredEvent) assert.Len(t, payloads, 1) - assert.EqualValues(t, "repo1-fork", payloads[0].Repo.Name) - assert.EqualValues(t, "user1/repo1-fork", payloads[0].Repo.FullName) - assert.EqualValues(t, "repo1", payloads[0].Forkee.Name) - assert.EqualValues(t, "user2/repo1", payloads[0].Forkee.FullName) + assert.Equal(t, "repo1-fork", payloads[0].Repo.Name) + assert.Equal(t, "user1/repo1-fork", payloads[0].Repo.FullName) + assert.Equal(t, "repo1", payloads[0].Forkee.Name) + assert.Equal(t, "user2/repo1", payloads[0].Forkee.FullName) }) } @@ -246,14 +246,14 @@ func Test_WebhookIssueComment(t *testing.T) { testIssueAddComment(t, session, issueURL, "issue title2 comment1", "") // 3. validate the webhook is triggered - assert.EqualValues(t, "issue_comment", triggeredEvent) + assert.Equal(t, "issue_comment", triggeredEvent) assert.Len(t, payloads, 1) assert.EqualValues(t, "created", payloads[0].Action) - assert.EqualValues(t, "repo1", payloads[0].Issue.Repo.Name) - assert.EqualValues(t, "user2/repo1", payloads[0].Issue.Repo.FullName) - assert.EqualValues(t, "Title2", payloads[0].Issue.Title) - assert.EqualValues(t, "Description2", payloads[0].Issue.Body) - assert.EqualValues(t, "issue title2 comment1", payloads[0].Comment.Body) + assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name) + assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName) + assert.Equal(t, "Title2", payloads[0].Issue.Title) + assert.Equal(t, "Description2", payloads[0].Issue.Body) + assert.Equal(t, "issue title2 comment1", payloads[0].Comment.Body) }) } @@ -280,11 +280,11 @@ func Test_WebhookRelease(t *testing.T) { createNewRelease(t, session, "/user2/repo1", "v0.0.99", "v0.0.99", false, false) // 3. validate the webhook is triggered - assert.EqualValues(t, "release", triggeredEvent) + assert.Equal(t, "release", triggeredEvent) assert.Len(t, payloads, 1) - assert.EqualValues(t, "repo1", payloads[0].Repository.Name) - assert.EqualValues(t, "user2/repo1", payloads[0].Repository.FullName) - assert.EqualValues(t, "v0.0.99", payloads[0].Release.TagName) + assert.Equal(t, "repo1", payloads[0].Repository.Name) + assert.Equal(t, "user2/repo1", payloads[0].Repository.FullName) + assert.Equal(t, "v0.0.99", payloads[0].Release.TagName) assert.False(t, payloads[0].Release.IsDraft) assert.False(t, payloads[0].Release.IsPrerelease) }) @@ -313,12 +313,12 @@ func Test_WebhookPush(t *testing.T) { testCreateFile(t, session, "user2", "repo1", "master", "test_webhook_push.md", "# a test file for webhook push") // 3. validate the webhook is triggered - assert.EqualValues(t, "push", triggeredEvent) + assert.Equal(t, "push", triggeredEvent) assert.Len(t, payloads, 1) - assert.EqualValues(t, "repo1", payloads[0].Repo.Name) - assert.EqualValues(t, "user2/repo1", payloads[0].Repo.FullName) + assert.Equal(t, "repo1", payloads[0].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName) assert.Len(t, payloads[0].Commits, 1) - assert.EqualValues(t, []string{"test_webhook_push.md"}, payloads[0].Commits[0].Added) + assert.Equal(t, []string{"test_webhook_push.md"}, payloads[0].Commits[0].Added) }) } @@ -345,13 +345,13 @@ func Test_WebhookIssue(t *testing.T) { testNewIssue(t, session, "user2", "repo1", "Title1", "Description1") // 3. validate the webhook is triggered - assert.EqualValues(t, "issues", triggeredEvent) + assert.Equal(t, "issues", triggeredEvent) assert.Len(t, payloads, 1) assert.EqualValues(t, "opened", payloads[0].Action) - assert.EqualValues(t, "repo1", payloads[0].Issue.Repo.Name) - assert.EqualValues(t, "user2/repo1", payloads[0].Issue.Repo.FullName) - assert.EqualValues(t, "Title1", payloads[0].Issue.Title) - assert.EqualValues(t, "Description1", payloads[0].Issue.Body) + assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name) + assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName) + assert.Equal(t, "Title1", payloads[0].Issue.Title) + assert.Equal(t, "Description1", payloads[0].Issue.Body) }) } @@ -380,15 +380,15 @@ func Test_WebhookPullRequest(t *testing.T) { testCreatePullToDefaultBranch(t, session, repo1, repo1, "master2", "first pull request") // 3. validate the webhook is triggered - assert.EqualValues(t, "pull_request", triggeredEvent) + assert.Equal(t, "pull_request", triggeredEvent) require.Len(t, payloads, 1) - assert.EqualValues(t, "repo1", payloads[0].PullRequest.Base.Repository.Name) - assert.EqualValues(t, "user2/repo1", payloads[0].PullRequest.Base.Repository.FullName) - assert.EqualValues(t, "repo1", payloads[0].PullRequest.Head.Repository.Name) - assert.EqualValues(t, "user2/repo1", payloads[0].PullRequest.Head.Repository.FullName) - assert.EqualValues(t, 0, *payloads[0].PullRequest.Additions) - assert.EqualValues(t, 0, *payloads[0].PullRequest.ChangedFiles) - assert.EqualValues(t, 0, *payloads[0].PullRequest.Deletions) + assert.Equal(t, "repo1", payloads[0].PullRequest.Base.Repository.Name) + assert.Equal(t, "user2/repo1", payloads[0].PullRequest.Base.Repository.FullName) + assert.Equal(t, "repo1", payloads[0].PullRequest.Head.Repository.Name) + assert.Equal(t, "user2/repo1", payloads[0].PullRequest.Head.Repository.FullName) + assert.Equal(t, 0, *payloads[0].PullRequest.Additions) + assert.Equal(t, 0, *payloads[0].PullRequest.ChangedFiles) + assert.Equal(t, 0, *payloads[0].PullRequest.Deletions) }) } @@ -419,14 +419,14 @@ func Test_WebhookPullRequestComment(t *testing.T) { testIssueAddComment(t, session, "/user2/repo1/pulls/"+prID, "pull title2 comment1", "") // 3. validate the webhook is triggered - assert.EqualValues(t, "pull_request_comment", triggeredEvent) + assert.Equal(t, "pull_request_comment", triggeredEvent) assert.Len(t, payloads, 1) assert.EqualValues(t, "created", payloads[0].Action) - assert.EqualValues(t, "repo1", payloads[0].Issue.Repo.Name) - assert.EqualValues(t, "user2/repo1", payloads[0].Issue.Repo.FullName) - assert.EqualValues(t, "first pull request", payloads[0].Issue.Title) - assert.EqualValues(t, "", payloads[0].Issue.Body) - assert.EqualValues(t, "pull title2 comment1", payloads[0].Comment.Body) + assert.Equal(t, "repo1", payloads[0].Issue.Repo.Name) + assert.Equal(t, "user2/repo1", payloads[0].Issue.Repo.FullName) + assert.Equal(t, "first pull request", payloads[0].Issue.Title) + assert.Empty(t, payloads[0].Issue.Body) + assert.Equal(t, "pull title2 comment1", payloads[0].Comment.Body) }) } @@ -453,12 +453,12 @@ func Test_WebhookWiki(t *testing.T) { testAPICreateWikiPage(t, session, "user2", "repo1", "Test Wiki Page", http.StatusCreated) // 3. validate the webhook is triggered - assert.EqualValues(t, "wiki", triggeredEvent) + assert.Equal(t, "wiki", triggeredEvent) assert.Len(t, payloads, 1) assert.EqualValues(t, "created", payloads[0].Action) - assert.EqualValues(t, "repo1", payloads[0].Repository.Name) - assert.EqualValues(t, "user2/repo1", payloads[0].Repository.FullName) - assert.EqualValues(t, "Test-Wiki-Page", payloads[0].Page) + assert.Equal(t, "repo1", payloads[0].Repository.Name) + assert.Equal(t, "user2/repo1", payloads[0].Repository.FullName) + assert.Equal(t, "Test-Wiki-Page", payloads[0].Page) }) } @@ -485,12 +485,12 @@ func Test_WebhookRepository(t *testing.T) { testAPIOrgCreateRepo(t, session, "org3", "repo_new", http.StatusCreated) // 3. validate the webhook is triggered - assert.EqualValues(t, "repository", triggeredEvent) + assert.Equal(t, "repository", triggeredEvent) assert.Len(t, payloads, 1) assert.EqualValues(t, "created", payloads[0].Action) - assert.EqualValues(t, "org3", payloads[0].Organization.UserName) - assert.EqualValues(t, "repo_new", payloads[0].Repository.Name) - assert.EqualValues(t, "org3/repo_new", payloads[0].Repository.FullName) + assert.Equal(t, "org3", payloads[0].Organization.UserName) + assert.Equal(t, "repo_new", payloads[0].Repository.Name) + assert.Equal(t, "org3/repo_new", payloads[0].Repository.FullName) }) } @@ -521,13 +521,13 @@ func Test_WebhookPackage(t *testing.T) { MakeRequest(t, req, http.StatusCreated) // 3. validate the webhook is triggered - assert.EqualValues(t, "package", triggeredEvent) + assert.Equal(t, "package", triggeredEvent) assert.Len(t, payloads, 1) assert.EqualValues(t, "created", payloads[0].Action) - assert.EqualValues(t, "gitea", payloads[0].Package.Name) - assert.EqualValues(t, "generic", payloads[0].Package.Type) - assert.EqualValues(t, "org3", payloads[0].Organization.UserName) - assert.EqualValues(t, "v1.24.0", payloads[0].Package.Version) + assert.Equal(t, "gitea", payloads[0].Package.Name) + assert.Equal(t, "generic", payloads[0].Package.Type) + assert.Equal(t, "org3", payloads[0].Organization.UserName) + assert.Equal(t, "v1.24.0", payloads[0].Package.Version) }) } @@ -574,13 +574,13 @@ func Test_WebhookStatus(t *testing.T) { })(t) // 3. validate the webhook is triggered - assert.EqualValues(t, "status", triggeredEvent) + assert.Equal(t, "status", triggeredEvent) assert.Len(t, payloads, 1) - assert.EqualValues(t, commitID, payloads[0].Commit.ID) - assert.EqualValues(t, "repo1", payloads[0].Repo.Name) - assert.EqualValues(t, "user2/repo1", payloads[0].Repo.FullName) - assert.EqualValues(t, "testci", payloads[0].Context) - assert.EqualValues(t, commitID, payloads[0].SHA) + assert.Equal(t, commitID, payloads[0].Commit.ID) + assert.Equal(t, "repo1", payloads[0].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName) + assert.Equal(t, "testci", payloads[0].Context) + assert.Equal(t, commitID, payloads[0].SHA) }) } @@ -605,7 +605,7 @@ func Test_WebhookStatus_NoWrongTrigger(t *testing.T) { testCreateFile(t, session, "user2", "repo1", "master", "test_webhook_push.md", "# a test file for webhook push") // 3. validate the webhook is triggered with right event - assert.EqualValues(t, "push", trigger) + assert.Equal(t, "push", trigger) }) } @@ -660,27 +660,27 @@ jobs: - run: echo 'cmd 1' - run: echo 'cmd 2' ` - opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, fmt.Sprintf("create %s", wfTreePath), wfFileContent) + opts := getWorkflowCreateFileOptions(user2, repo1.DefaultBranch, "create "+wfTreePath, wfFileContent) createWorkflowFile(t, token, "user2", "repo1", wfTreePath, opts) commitID, err := gitRepo1.GetBranchCommitID(repo1.DefaultBranch) assert.NoError(t, err) // 3. validate the webhook is triggered - assert.EqualValues(t, "workflow_job", triggeredEvent) + assert.Equal(t, "workflow_job", triggeredEvent) assert.Len(t, payloads, 2) - assert.EqualValues(t, "queued", payloads[0].Action) - assert.EqualValues(t, "queued", payloads[0].WorkflowJob.Status) - assert.EqualValues(t, []string{"ubuntu-latest"}, payloads[0].WorkflowJob.Labels) - assert.EqualValues(t, commitID, payloads[0].WorkflowJob.HeadSha) - assert.EqualValues(t, "repo1", payloads[0].Repo.Name) - assert.EqualValues(t, "user2/repo1", payloads[0].Repo.FullName) + assert.Equal(t, "queued", payloads[0].Action) + assert.Equal(t, "queued", payloads[0].WorkflowJob.Status) + assert.Equal(t, []string{"ubuntu-latest"}, payloads[0].WorkflowJob.Labels) + assert.Equal(t, commitID, payloads[0].WorkflowJob.HeadSha) + assert.Equal(t, "repo1", payloads[0].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[0].Repo.FullName) - assert.EqualValues(t, "waiting", payloads[1].Action) - assert.EqualValues(t, "waiting", payloads[1].WorkflowJob.Status) - assert.EqualValues(t, commitID, payloads[1].WorkflowJob.HeadSha) - assert.EqualValues(t, "repo1", payloads[1].Repo.Name) - assert.EqualValues(t, "user2/repo1", payloads[1].Repo.FullName) + assert.Equal(t, "waiting", payloads[1].Action) + assert.Equal(t, "waiting", payloads[1].WorkflowJob.Status) + assert.Equal(t, commitID, payloads[1].WorkflowJob.HeadSha) + assert.Equal(t, "repo1", payloads[1].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[1].Repo.FullName) // 4. Execute a single Job task := runner.fetchTask(t) @@ -691,33 +691,33 @@ jobs: runner.execTask(t, task, outcome) // 5. validate the webhook is triggered - assert.EqualValues(t, "workflow_job", triggeredEvent) + assert.Equal(t, "workflow_job", triggeredEvent) assert.Len(t, payloads, 5) - assert.EqualValues(t, "in_progress", payloads[2].Action) - assert.EqualValues(t, "in_progress", payloads[2].WorkflowJob.Status) - assert.EqualValues(t, "mock-runner", payloads[2].WorkflowJob.RunnerName) - assert.EqualValues(t, commitID, payloads[2].WorkflowJob.HeadSha) - assert.EqualValues(t, "repo1", payloads[2].Repo.Name) - assert.EqualValues(t, "user2/repo1", payloads[2].Repo.FullName) + assert.Equal(t, "in_progress", payloads[2].Action) + assert.Equal(t, "in_progress", payloads[2].WorkflowJob.Status) + assert.Equal(t, "mock-runner", payloads[2].WorkflowJob.RunnerName) + assert.Equal(t, commitID, payloads[2].WorkflowJob.HeadSha) + assert.Equal(t, "repo1", payloads[2].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[2].Repo.FullName) - assert.EqualValues(t, "completed", payloads[3].Action) - assert.EqualValues(t, "completed", payloads[3].WorkflowJob.Status) - assert.EqualValues(t, "mock-runner", payloads[3].WorkflowJob.RunnerName) - assert.EqualValues(t, "success", payloads[3].WorkflowJob.Conclusion) - assert.EqualValues(t, commitID, payloads[3].WorkflowJob.HeadSha) - assert.EqualValues(t, "repo1", payloads[3].Repo.Name) - assert.EqualValues(t, "user2/repo1", payloads[3].Repo.FullName) + assert.Equal(t, "completed", payloads[3].Action) + assert.Equal(t, "completed", payloads[3].WorkflowJob.Status) + assert.Equal(t, "mock-runner", payloads[3].WorkflowJob.RunnerName) + assert.Equal(t, "success", payloads[3].WorkflowJob.Conclusion) + assert.Equal(t, commitID, payloads[3].WorkflowJob.HeadSha) + assert.Equal(t, "repo1", payloads[3].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[3].Repo.FullName) assert.Contains(t, payloads[3].WorkflowJob.URL, fmt.Sprintf("/actions/runs/%d/jobs/%d", payloads[3].WorkflowJob.RunID, payloads[3].WorkflowJob.ID)) assert.Contains(t, payloads[3].WorkflowJob.URL, payloads[3].WorkflowJob.RunURL) assert.Contains(t, payloads[3].WorkflowJob.HTMLURL, fmt.Sprintf("/jobs/%d", 0)) assert.Len(t, payloads[3].WorkflowJob.Steps, 1) - assert.EqualValues(t, "queued", payloads[4].Action) - assert.EqualValues(t, "queued", payloads[4].WorkflowJob.Status) - assert.EqualValues(t, []string{"ubuntu-latest"}, payloads[4].WorkflowJob.Labels) - assert.EqualValues(t, commitID, payloads[4].WorkflowJob.HeadSha) - assert.EqualValues(t, "repo1", payloads[4].Repo.Name) - assert.EqualValues(t, "user2/repo1", payloads[4].Repo.FullName) + assert.Equal(t, "queued", payloads[4].Action) + assert.Equal(t, "queued", payloads[4].WorkflowJob.Status) + assert.Equal(t, []string{"ubuntu-latest"}, payloads[4].WorkflowJob.Labels) + assert.Equal(t, commitID, payloads[4].WorkflowJob.HeadSha) + assert.Equal(t, "repo1", payloads[4].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[4].Repo.FullName) // 6. Execute a single Job task = runner.fetchTask(t) @@ -728,23 +728,23 @@ jobs: runner.execTask(t, task, outcome) // 7. validate the webhook is triggered - assert.EqualValues(t, "workflow_job", triggeredEvent) + assert.Equal(t, "workflow_job", triggeredEvent) assert.Len(t, payloads, 7) - assert.EqualValues(t, "in_progress", payloads[5].Action) - assert.EqualValues(t, "in_progress", payloads[5].WorkflowJob.Status) - assert.EqualValues(t, "mock-runner", payloads[5].WorkflowJob.RunnerName) + assert.Equal(t, "in_progress", payloads[5].Action) + assert.Equal(t, "in_progress", payloads[5].WorkflowJob.Status) + assert.Equal(t, "mock-runner", payloads[5].WorkflowJob.RunnerName) - assert.EqualValues(t, commitID, payloads[5].WorkflowJob.HeadSha) - assert.EqualValues(t, "repo1", payloads[5].Repo.Name) - assert.EqualValues(t, "user2/repo1", payloads[5].Repo.FullName) + assert.Equal(t, commitID, payloads[5].WorkflowJob.HeadSha) + assert.Equal(t, "repo1", payloads[5].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[5].Repo.FullName) - assert.EqualValues(t, "completed", payloads[6].Action) - assert.EqualValues(t, "completed", payloads[6].WorkflowJob.Status) - assert.EqualValues(t, "failure", payloads[6].WorkflowJob.Conclusion) - assert.EqualValues(t, "mock-runner", payloads[6].WorkflowJob.RunnerName) - assert.EqualValues(t, commitID, payloads[6].WorkflowJob.HeadSha) - assert.EqualValues(t, "repo1", payloads[6].Repo.Name) - assert.EqualValues(t, "user2/repo1", payloads[6].Repo.FullName) + assert.Equal(t, "completed", payloads[6].Action) + assert.Equal(t, "completed", payloads[6].WorkflowJob.Status) + assert.Equal(t, "failure", payloads[6].WorkflowJob.Conclusion) + assert.Equal(t, "mock-runner", payloads[6].WorkflowJob.RunnerName) + assert.Equal(t, commitID, payloads[6].WorkflowJob.HeadSha) + assert.Equal(t, "repo1", payloads[6].Repo.Name) + assert.Equal(t, "user2/repo1", payloads[6].Repo.FullName) assert.Contains(t, payloads[6].WorkflowJob.URL, fmt.Sprintf("/actions/runs/%d/jobs/%d", payloads[6].WorkflowJob.RunID, payloads[6].WorkflowJob.ID)) assert.Contains(t, payloads[6].WorkflowJob.URL, payloads[6].WorkflowJob.RunURL) assert.Contains(t, payloads[6].WorkflowJob.HTMLURL, fmt.Sprintf("/jobs/%d", 1)) diff --git a/tests/integration/repofiles_change_test.go b/tests/integration/repofiles_change_test.go index ef520a2e51..ce55a2f943 100644 --- a/tests/integration/repofiles_change_test.go +++ b/tests/integration/repofiles_change_test.go @@ -4,8 +4,9 @@ package integration import ( + "fmt" "net/url" - "path/filepath" + "path" "strings" "testing" "time" @@ -78,7 +79,7 @@ func getDeleteRepoFilesOptions(repo *repo_model.Repository) *files_service.Chang } } -func getExpectedFileResponseForRepofilesDelete() *api.FileResponse { +func getExpectedFileResponseForRepoFilesDelete() *api.FileResponse { // Just returns fields that don't change, i.e. fields with commit SHAs and dates can't be determined return &api.FileResponse{ Content: nil, @@ -106,7 +107,7 @@ func getExpectedFileResponseForRepofilesDelete() *api.FileResponse { } } -func getExpectedFileResponseForRepofilesCreate(commitID, lastCommitSHA string) *api.FileResponse { +func getExpectedFileResponseForRepoFilesCreate(commitID string, lastCommit *git.Commit) *api.FileResponse { treePath := "new/file.txt" encoding := "base64" content := "VGhpcyBpcyBhIE5FVyBmaWxl" @@ -116,18 +117,20 @@ func getExpectedFileResponseForRepofilesCreate(commitID, lastCommitSHA string) * downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath return &api.FileResponse{ Content: &api.ContentsResponse{ - Name: filepath.Base(treePath), - Path: treePath, - SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", - LastCommitSHA: lastCommitSHA, - Type: "file", - Size: 18, - Encoding: &encoding, - Content: &content, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: path.Base(treePath), + Path: treePath, + SHA: "103ff9234cefeee5ec5361d22b49fbb04d385885", + LastCommitSHA: lastCommit.ID.String(), + LastCommitterDate: lastCommit.Committer.When, + LastAuthorDate: lastCommit.Author.When, + Type: "file", + Size: 18, + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, Links: &api.FileLinksResponse{ Self: &selfURL, GitURL: &gitURL, @@ -175,7 +178,7 @@ func getExpectedFileResponseForRepofilesCreate(commitID, lastCommitSHA string) * } } -func getExpectedFileResponseForRepofilesUpdate(commitID, filename, lastCommitSHA string) *api.FileResponse { +func getExpectedFileResponseForRepoFilesUpdate(commitID, filename, lastCommitSHA string, lastCommitterWhen, lastAuthorWhen time.Time) *api.FileResponse { encoding := "base64" content := "VGhpcyBpcyBVUERBVEVEIGNvbnRlbnQgZm9yIHRoZSBSRUFETUUgZmlsZQ==" selfURL := setting.AppURL + "api/v1/repos/user2/repo1/contents/" + filename + "?ref=master" @@ -184,18 +187,20 @@ func getExpectedFileResponseForRepofilesUpdate(commitID, filename, lastCommitSHA downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + filename return &api.FileResponse{ Content: &api.ContentsResponse{ - Name: filename, - Path: filename, - SHA: "dbf8d00e022e05b7e5cf7e535de857de57925647", - LastCommitSHA: lastCommitSHA, - Type: "file", - Size: 43, - Encoding: &encoding, - Content: &content, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: filename, + Path: filename, + SHA: "dbf8d00e022e05b7e5cf7e535de857de57925647", + LastCommitSHA: lastCommitSHA, + LastCommitterDate: lastCommitterWhen, + LastAuthorDate: lastAuthorWhen, + Type: "file", + Size: 43, + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, Links: &api.FileLinksResponse{ Self: &selfURL, GitURL: &gitURL, @@ -268,14 +273,14 @@ func TestChangeRepoFilesForCreate(t *testing.T) { commitID, _ := gitRepo.GetBranchCommitID(opts.NewBranch) lastCommit, _ := gitRepo.GetCommitByPath("new/file.txt") - expectedFileResponse := getExpectedFileResponseForRepofilesCreate(commitID, lastCommit.ID.String()) + expectedFileResponse := getExpectedFileResponseForRepoFilesCreate(commitID, lastCommit) assert.NotNil(t, expectedFileResponse) if expectedFileResponse != nil { - assert.EqualValues(t, expectedFileResponse.Content, filesResponse.Files[0]) - assert.EqualValues(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA) - assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name) + assert.Equal(t, expectedFileResponse.Content, filesResponse.Files[0]) + assert.Equal(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA) + assert.Equal(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) + assert.Equal(t, expectedFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email) + assert.Equal(t, expectedFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name) } }) } @@ -305,12 +310,12 @@ func TestChangeRepoFilesForUpdate(t *testing.T) { commit, _ := gitRepo.GetBranchCommit(opts.NewBranch) lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath) - expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String()) - assert.EqualValues(t, expectedFileResponse.Content, filesResponse.Files[0]) - assert.EqualValues(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA) - assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name) + expectedFileResponse := getExpectedFileResponseForRepoFilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String(), lastCommit.Committer.When, lastCommit.Author.When) + assert.Equal(t, expectedFileResponse.Content, filesResponse.Files[0]) + assert.Equal(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA) + assert.Equal(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) + assert.Equal(t, expectedFileResponse.Commit.Author.Email, filesResponse.Commit.Author.Email) + assert.Equal(t, expectedFileResponse.Commit.Author.Name, filesResponse.Commit.Author.Name) }) } @@ -341,7 +346,7 @@ func TestChangeRepoFilesForUpdateWithFileMove(t *testing.T) { commit, _ := gitRepo.GetBranchCommit(opts.NewBranch) lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath) - expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String()) + expectedFileResponse := getExpectedFileResponseForRepoFilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String(), lastCommit.Committer.When, lastCommit.Author.When) // assert that the old file no longer exists in the last commit of the branch fromEntry, err := commit.GetTreeEntryByPath(opts.Files[0].FromTreePath) switch err.(type) { @@ -355,12 +360,12 @@ func TestChangeRepoFilesForUpdateWithFileMove(t *testing.T) { assert.Nil(t, fromEntry) // Should no longer exist here assert.NotNil(t, toEntry) // Should exist here // assert SHA has remained the same but paths use the new file name - assert.EqualValues(t, expectedFileResponse.Content.SHA, filesResponse.Files[0].SHA) - assert.EqualValues(t, expectedFileResponse.Content.Name, filesResponse.Files[0].Name) - assert.EqualValues(t, expectedFileResponse.Content.Path, filesResponse.Files[0].Path) - assert.EqualValues(t, expectedFileResponse.Content.URL, filesResponse.Files[0].URL) - assert.EqualValues(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA) - assert.EqualValues(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) + assert.Equal(t, expectedFileResponse.Content.SHA, filesResponse.Files[0].SHA) + assert.Equal(t, expectedFileResponse.Content.Name, filesResponse.Files[0].Name) + assert.Equal(t, expectedFileResponse.Content.Path, filesResponse.Files[0].Path) + assert.Equal(t, expectedFileResponse.Content.URL, filesResponse.Files[0].URL) + assert.Equal(t, expectedFileResponse.Commit.SHA, filesResponse.Commit.SHA) + assert.Equal(t, expectedFileResponse.Commit.HTMLURL, filesResponse.Commit.HTMLURL) }) } @@ -392,8 +397,8 @@ func TestChangeRepoFilesWithoutBranchNames(t *testing.T) { commit, _ := gitRepo.GetBranchCommit(repo.DefaultBranch) lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath) - expectedFileResponse := getExpectedFileResponseForRepofilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String()) - assert.EqualValues(t, expectedFileResponse.Content, filesResponse.Files[0]) + expectedFileResponse := getExpectedFileResponseForRepoFilesUpdate(commit.ID.String(), opts.Files[0].TreePath, lastCommit.ID.String(), lastCommit.Committer.When, lastCommit.Author.When) + assert.Equal(t, expectedFileResponse.Content, filesResponse.Files[0]) }) } @@ -418,13 +423,13 @@ func testDeleteRepoFiles(t *testing.T, u *url.URL) { t.Run("Delete README.md file", func(t *testing.T) { filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts) assert.NoError(t, err) - expectedFileResponse := getExpectedFileResponseForRepofilesDelete() + expectedFileResponse := getExpectedFileResponseForRepoFilesDelete() assert.NotNil(t, filesResponse) assert.Nil(t, filesResponse.Files[0]) - assert.EqualValues(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Identity, filesResponse.Commit.Author.Identity) - assert.EqualValues(t, expectedFileResponse.Commit.Committer.Identity, filesResponse.Commit.Committer.Identity) - assert.EqualValues(t, expectedFileResponse.Verification, filesResponse.Verification) + assert.Equal(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message) + assert.Equal(t, expectedFileResponse.Commit.Author.Identity, filesResponse.Commit.Author.Identity) + assert.Equal(t, expectedFileResponse.Commit.Committer.Identity, filesResponse.Commit.Committer.Identity) + assert.Equal(t, expectedFileResponse.Verification, filesResponse.Verification) }) t.Run("Verify README.md has been deleted", func(t *testing.T) { @@ -460,13 +465,13 @@ func testDeleteRepoFilesWithoutBranchNames(t *testing.T, u *url.URL) { t.Run("Delete README.md without Branch Name", func(t *testing.T) { filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts) assert.NoError(t, err) - expectedFileResponse := getExpectedFileResponseForRepofilesDelete() + expectedFileResponse := getExpectedFileResponseForRepoFilesDelete() assert.NotNil(t, filesResponse) assert.Nil(t, filesResponse.Files[0]) - assert.EqualValues(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message) - assert.EqualValues(t, expectedFileResponse.Commit.Author.Identity, filesResponse.Commit.Author.Identity) - assert.EqualValues(t, expectedFileResponse.Commit.Committer.Identity, filesResponse.Commit.Committer.Identity) - assert.EqualValues(t, expectedFileResponse.Verification, filesResponse.Verification) + assert.Equal(t, expectedFileResponse.Commit.Message, filesResponse.Commit.Message) + assert.Equal(t, expectedFileResponse.Commit.Author.Identity, filesResponse.Commit.Author.Identity) + assert.Equal(t, expectedFileResponse.Commit.Committer.Identity, filesResponse.Commit.Committer.Identity) + assert.Equal(t, expectedFileResponse.Verification, filesResponse.Verification) }) } @@ -490,7 +495,7 @@ func TestChangeRepoFilesErrors(t *testing.T) { filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, doer, opts) assert.Error(t, err) assert.Nil(t, filesResponse) - expectedError := "branch does not exist [name: " + opts.OldBranch + "]" + expectedError := fmt.Sprintf("branch does not exist [repo_id: %d name: %s]", repo.ID, opts.OldBranch) assert.EqualError(t, err, expectedError) }) diff --git a/tests/integration/session_test.go b/tests/integration/session_test.go index b18a25827d..f72e2e24b7 100644 --- a/tests/integration/session_test.go +++ b/tests/integration/session_test.go @@ -27,11 +27,11 @@ func Test_RegenerateSession(t *testing.T) { sess, err := auth.RegenerateSession(db.DefaultContext, "", key) assert.NoError(t, err) - assert.EqualValues(t, key, sess.Key) + assert.Equal(t, key, sess.Key) assert.Empty(t, sess.Data) sess, err = auth.ReadSession(db.DefaultContext, key2) assert.NoError(t, err) - assert.EqualValues(t, key2, sess.Key) + assert.Equal(t, key2, sess.Key) assert.Empty(t, sess.Data) } diff --git a/tests/integration/signin_test.go b/tests/integration/signin_test.go index 5c7555286e..67af5b5877 100644 --- a/tests/integration/signin_test.go +++ b/tests/integration/signin_test.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/routers" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/tests" @@ -35,7 +36,7 @@ func testLoginFailed(t *testing.T, username, password, message string) { htmlDoc := NewHTMLParser(t, resp.Body) resultMsg := htmlDoc.doc.Find(".ui.message>p").Text() - assert.EqualValues(t, message, resultMsg) + assert.Equal(t, message, resultMsg) } func TestSignin(t *testing.T) { @@ -166,3 +167,32 @@ func TestEnablePasswordSignInFormAndEnablePasskeyAuth(t *testing.T) { AssertHTMLElement(t, doc, ".signin-passkey", true) }) } + +func TestRequireSignInView(t *testing.T) { + defer tests.PrepareTestEnv(t)() + t.Run("NoRequireSignInView", func(t *testing.T) { + require.False(t, setting.Service.RequireSignInViewStrict) + require.False(t, setting.Service.BlockAnonymousAccessExpensive) + req := NewRequest(t, "GET", "/user2/repo1/src/branch/master") + MakeRequest(t, req, http.StatusOK) + }) + t.Run("RequireSignInView", func(t *testing.T) { + defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)() + defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() + req := NewRequest(t, "GET", "/user2/repo1/src/branch/master") + resp := MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/user/login", resp.Header().Get("Location")) + }) + t.Run("BlockAnonymousAccessExpensive", func(t *testing.T) { + defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, false)() + defer test.MockVariableValue(&setting.Service.BlockAnonymousAccessExpensive, true)() + defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() + + req := NewRequest(t, "GET", "/user2/repo1") + MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "GET", "/user2/repo1/src/branch/master") + resp := MakeRequest(t, req, http.StatusSeeOther) + assert.Equal(t, "/user/login", resp.Header().Get("Location")) + }) +} diff --git a/tests/integration/ssh_key_test.go b/tests/integration/ssh_key_test.go index eb3a3e926a..fbdda9b3af 100644 --- a/tests/integration/ssh_key_test.go +++ b/tests/integration/ssh_key_test.go @@ -51,7 +51,7 @@ func testPushDeployKeyOnEmptyRepo(t *testing.T, u *url.URL) { ctx := NewAPITestContext(t, "user2", "deploy-key-empty-repo-1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) ctxWithDeleteRepo := NewAPITestContext(t, "user2", "deploy-key-empty-repo-1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - keyname := fmt.Sprintf("%s-push", ctx.Reponame) + keyname := ctx.Reponame + "-push" u.Path = ctx.GitPath() t.Run("CreateEmptyRepository", doAPICreateRepository(ctx, true)) @@ -89,7 +89,7 @@ func testKeyOnlyOneType(t *testing.T, u *url.URL) { reponame := "ssh-key-test-repo" username := "user2" u.Path = fmt.Sprintf("%s/%s.git", username, reponame) - keyname := fmt.Sprintf("%s-push", reponame) + keyname := reponame + "-push" // OK login ctx := NewAPITestContext(t, username, reponame, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) diff --git a/tests/integration/user_avatar_test.go b/tests/integration/user_avatar_test.go index 7b157e6e61..14ea012ac8 100644 --- a/tests/integration/user_avatar_test.go +++ b/tests/integration/user_avatar_test.go @@ -5,7 +5,6 @@ package integration import ( "bytes" - "fmt" "image/png" "io" "mime/multipart" @@ -84,9 +83,9 @@ func TestUserAvatar(t *testing.T) { } func testGetAvatarRedirect(t *testing.T, user *user_model.User) { - t.Run(fmt.Sprintf("getAvatarRedirect_%s", user.Name), func(t *testing.T) { + t.Run("getAvatarRedirect_"+user.Name, func(t *testing.T) { req := NewRequestf(t, "GET", "/%s.png", user.Name) resp := MakeRequest(t, req, http.StatusSeeOther) - assert.EqualValues(t, fmt.Sprintf("/avatars/%s", user.Avatar), resp.Header().Get("location")) + assert.Equal(t, "/avatars/"+user.Avatar, resp.Header().Get("location")) }) } diff --git a/tests/integration/user_test.go b/tests/integration/user_test.go index fd7996bf8b..2c9a916ec2 100644 --- a/tests/integration/user_test.go +++ b/tests/integration/user_test.go @@ -217,12 +217,12 @@ func TestGetUserRss(t *testing.T) { user34 := "the_34-user.with.all.allowedChars" req := NewRequestf(t, "GET", "/%s.rss", user34) resp := MakeRequest(t, req, http.StatusOK) - if assert.EqualValues(t, "application/rss+xml;charset=utf-8", resp.Header().Get("Content-Type")) { + if assert.Equal(t, "application/rss+xml;charset=utf-8", resp.Header().Get("Content-Type")) { rssDoc := NewHTMLParser(t, resp.Body).Find("channel") title, _ := rssDoc.ChildrenFiltered("title").Html() - assert.EqualValues(t, "Feed of "the_1-user.with.all.allowedChars"", title) + assert.Equal(t, "Feed of "the_1-user.with.all.allowedChars"", title) description, _ := rssDoc.ChildrenFiltered("description").Html() - assert.EqualValues(t, "<p dir="auto">some <a href="https://commonmark.org/" rel="nofollow">commonmark</a>!</p>\n", description) + assert.Equal(t, "<p dir="auto">some <a href="https://commonmark.org/" rel="nofollow">commonmark</a>!</p>\n", description) } req = NewRequestf(t, "GET", "/non-existent-user.rss") @@ -247,11 +247,11 @@ func TestListStopWatches(t *testing.T) { stopwatch := unittest.AssertExistsAndLoadBean(t, &issues_model.Stopwatch{UserID: owner.ID}) issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: stopwatch.IssueID}) if assert.Len(t, apiWatches, 1) { - assert.EqualValues(t, stopwatch.CreatedUnix.AsTime().Unix(), apiWatches[0].Created.Unix()) - assert.EqualValues(t, issue.Index, apiWatches[0].IssueIndex) - assert.EqualValues(t, issue.Title, apiWatches[0].IssueTitle) - assert.EqualValues(t, repo.Name, apiWatches[0].RepoName) - assert.EqualValues(t, repo.OwnerName, apiWatches[0].RepoOwnerName) + assert.Equal(t, stopwatch.CreatedUnix.AsTime().Unix(), apiWatches[0].Created.Unix()) + assert.Equal(t, issue.Index, apiWatches[0].IssueIndex) + assert.Equal(t, issue.Title, apiWatches[0].IssueTitle) + assert.Equal(t, repo.Name, apiWatches[0].RepoName) + assert.Equal(t, repo.OwnerName, apiWatches[0].RepoOwnerName) assert.Positive(t, apiWatches[0].Seconds) } } diff --git a/tests/integration/webfinger_test.go b/tests/integration/webfinger_test.go index a1abc8d32b..2afaed4a45 100644 --- a/tests/integration/webfinger_test.go +++ b/tests/integration/webfinger_test.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" "net/url" + "strconv" "testing" "code.gitea.io/gitea/models/unittest" @@ -52,7 +53,7 @@ func TestWebfinger(t *testing.T) { var jrd webfingerJRD DecodeJSON(t, resp, &jrd) assert.Equal(t, "acct:user2@"+appURL.Host, jrd.Subject) - assert.ElementsMatch(t, []string{user.HTMLURL(), appURL.String() + "api/v1/activitypub/user-id/" + fmt.Sprint(user.ID)}, jrd.Aliases) + assert.ElementsMatch(t, []string{user.HTMLURL(), appURL.String() + "api/v1/activitypub/user-id/" + strconv.FormatInt(user.ID, 10)}, jrd.Aliases) req = NewRequest(t, "GET", fmt.Sprintf("/.well-known/webfinger?resource=acct:%s@%s", user.LowerName, "unknown.host")) MakeRequest(t, req, http.StatusBadRequest) @@ -63,6 +64,6 @@ func TestWebfinger(t *testing.T) { req = NewRequest(t, "GET", fmt.Sprintf("/.well-known/webfinger?resource=acct:%s@%s", "user31", appURL.Host)) session.MakeRequest(t, req, http.StatusOK) - req = NewRequest(t, "GET", fmt.Sprintf("/.well-known/webfinger?resource=mailto:%s", user.Email)) + req = NewRequest(t, "GET", "/.well-known/webfinger?resource=mailto:"+user.Email) MakeRequest(t, req, http.StatusNotFound) } diff --git a/tests/integration/wiki_test.go b/tests/integration/wiki_test.go index a571c2da50..ac458af378 100644 --- a/tests/integration/wiki_test.go +++ b/tests/integration/wiki_test.go @@ -4,7 +4,6 @@ package integration import ( - "fmt" "net/http" "net/url" "os" @@ -29,7 +28,7 @@ func assertFileExist(t *testing.T, p string) { func assertFileEqual(t *testing.T, p string, content []byte) { bs, err := os.ReadFile(p) assert.NoError(t, err) - assert.EqualValues(t, content, bs) + assert.Equal(t, content, bs) } func TestRepoCloneWiki(t *testing.T) { @@ -38,7 +37,7 @@ func TestRepoCloneWiki(t *testing.T) { dstPath := t.TempDir() - r := fmt.Sprintf("%suser2/repo1.wiki.git", u.String()) + r := u.String() + "user2/repo1.wiki.git" u, _ = url.Parse(r) u.User = url.UserPassword("user2", userPassword) t.Run("Clone", func(t *testing.T) { @@ -69,6 +68,6 @@ func Test_RepoWikiPages(t *testing.T) { href, _ := firstAnchor.Attr("href") pagePath := strings.TrimPrefix(href, "/user2/repo1/wiki/") - assert.EqualValues(t, expectedPagePaths[i], pagePath) + assert.Equal(t, expectedPagePaths[i], pagePath) }) } diff --git a/tests/integration/xss_test.go b/tests/integration/xss_test.go index a8eaa5fc62..9058fc210a 100644 --- a/tests/integration/xss_test.go +++ b/tests/integration/xss_test.go @@ -32,8 +32,8 @@ func TestXSSUserFullName(t *testing.T) { req = NewRequestf(t, "GET", "/%s", user.Name) resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) - assert.EqualValues(t, 0, htmlDoc.doc.Find("script.evil").Length()) - assert.EqualValues(t, fullName, + assert.Equal(t, 0, htmlDoc.doc.Find("script.evil").Length()) + assert.Equal(t, fullName, htmlDoc.doc.Find("div.content").Find(".header.text.center").Text(), ) } diff --git a/tests/test_utils.go b/tests/test_utils.go index 96eb5731b4..4d8a8635d6 100644 --- a/tests/test_utils.go +++ b/tests/test_utils.go @@ -18,7 +18,6 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" - repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/test" @@ -67,9 +66,8 @@ func InitTest(requireGitea bool) { setting.CustomConf = giteaConf } - unittest.InitSettings() + unittest.InitSettingsForTesting() setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master" - _ = util.RemoveAll(repo_module.LocalCopyPath()) if err := git.InitFull(context.Background()); err != nil { log.Fatal("git.InitOnceWithSync: %v", err) @@ -93,7 +91,7 @@ func InitTest(requireGitea bool) { if err != nil { log.Fatal("sql.Open: %v", err) } - if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", setting.Database.Name)); err != nil { + if _, err = db.Exec("CREATE DATABASE IF NOT EXISTS " + setting.Database.Name); err != nil { log.Fatal("db.Exec: %v", err) } case setting.Database.Type.IsPostgreSQL(): @@ -118,7 +116,7 @@ func InitTest(requireGitea bool) { defer dbrows.Close() if !dbrows.Next() { - if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", setting.Database.Name)); err != nil { + if _, err = db.Exec("CREATE DATABASE " + setting.Database.Name); err != nil { log.Fatal("db.Exec: CREATE DATABASE: %v", err) } } @@ -148,7 +146,7 @@ func InitTest(requireGitea bool) { if !schrows.Next() { // Create and setup a DB schema - if _, err = db.Exec(fmt.Sprintf("CREATE SCHEMA %s", setting.Database.Schema)); err != nil { + if _, err = db.Exec("CREATE SCHEMA " + setting.Database.Schema); err != nil { log.Fatal("db.Exec: CREATE SCHEMA: %v", err) } } diff --git a/web_src/css/base.css b/web_src/css/base.css index 98eb32bc13..353ae851ad 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -347,6 +347,7 @@ a.label, .ui.dropdown .menu > .item { color: var(--color-text); + line-height: var(--line-height-default); } .ui.dropdown .menu > .item:hover { @@ -646,10 +647,11 @@ img.ui.avatar, border: 1px solid; } -.ui.floating.dropdown .overflow.menu .scrolling.menu.items { +.ui.dropdown .menu.context-user-switch .scrolling.menu { border-radius: 0 !important; box-shadow: none !important; border-bottom: 1px solid var(--color-secondary); + max-width: 80vw; } .user-menu > .item { @@ -745,6 +747,14 @@ overflow-menu .overflow-menu-button { padding: 0; } +/* match the styles of ".ui.secondary.pointing.menu .active.item" */ +overflow-menu.ui.secondary.pointing.menu .overflow-menu-button.active { + padding: 2px 0 0; + border-bottom: 2px solid currentcolor; + background-color: transparent; + font-weight: var(--font-weight-medium); +} + overflow-menu .overflow-menu-button:hover { color: var(--color-text-dark); } @@ -876,7 +886,6 @@ overflow-menu .ui.label { .code-inner { font: 12px var(--fonts-monospace); white-space: pre-wrap; - word-break: break-all; overflow-wrap: anywhere; line-height: inherit; /* needed for inline code preview in markup */ } @@ -1028,11 +1037,6 @@ table th[data-sortt-desc] .svg { text-align: left; } -.truncated-item-container { - display: flex !important; - align-items: center; -} - .ellipsis-button { padding: 0 5px 8px !important; display: inline-block !important; @@ -1041,15 +1045,6 @@ table th[data-sortt-desc] .svg { vertical-align: middle !important; } -.truncated-item-name { - line-height: 2; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - margin-top: -0.5em; - margin-bottom: -0.5em; -} - .precolors { display: flex; flex-direction: column; @@ -1140,6 +1135,7 @@ table th[data-sortt-desc] .svg { } .ui.list.flex-items-block > .item, +.ui.form .field > label.flex-text-block, /* override fomantic "block" style */ .flex-items-block > .item, .flex-text-block { display: flex; @@ -1166,14 +1162,20 @@ the "!important" is necessary to override Fomantic UI menu item styles, meanwhil margin: 0; /* use gap, but not margin */ } -.ui.dropdown.ellipsis-items-nowrap > .text { +.ui.dropdown.ellipsis-text-items { + /* reset y padding and use the line-height below instead, to avoid the "overflow: hidden" clips the larger image in the "text" element */ + padding-top: 0; + padding-bottom: 0; +} + +.ui.dropdown.ellipsis-text-items > .text { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + line-height: 2.71; /* matches fomantic dropdown's default min-height */ } -.ellipsis-items-nowrap > .item, -.ui.dropdown.ellipsis-items-nowrap .menu > .item { +.ui.dropdown.ellipsis-text-items .menu > .item { white-space: nowrap !important; overflow: hidden !important; text-overflow: ellipsis !important; @@ -1188,3 +1190,15 @@ the "!important" is necessary to override Fomantic UI menu item styles, meanwhil flex-grow: 1; justify-content: space-between; } + +.svg.octicon-file-directory-fill, +.svg.octicon-file-directory-open-fill, +.svg.octicon-file-submodule { + color: var(--color-primary); +} + +.svg.octicon-file, +.svg.octicon-file-symlink-file, +.svg.octicon-file-directory-symlink { + color: var(--color-secondary-dark-7); +} diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css index 6d985a76a7..937224a9d7 100644 --- a/web_src/css/markup/content.css +++ b/web_src/css/markup/content.css @@ -2,11 +2,7 @@ overflow: hidden; font-size: 16px; line-height: 1.5 !important; - overflow-wrap: break-word; -} - -.conversation-holder .markup { - overflow-wrap: anywhere; /* prevent overflow in code comments. TODO: properly restrict .conversation-holder width and remove this */ + overflow-wrap: anywhere; } .markup > *:first-child { @@ -451,8 +447,7 @@ margin: 0; font-size: 100%; white-space: pre-wrap; - word-break: break-all; - overflow-wrap: break-word; + overflow-wrap: anywhere; background: transparent; border: 0; } @@ -515,6 +510,18 @@ padding-left: 2em; } +.markup details.frontmatter-content summary { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + margin-bottom: 0.25em; +} + +.markup details.frontmatter-content svg { + vertical-align: middle; + margin: 0 0.25em; +} + .file-revisions-btn { display: block; float: left; diff --git a/web_src/css/modules/animations.css b/web_src/css/modules/animations.css index 481e997d4f..173ca73314 100644 --- a/web_src/css/modules/animations.css +++ b/web_src/css/modules/animations.css @@ -116,3 +116,13 @@ code.language-math.is-loading::after { animation-duration: 100ms; animation-timing-function: ease-in-out; } + +.circular-spin { + animation: circular-spin-keyframes 1s linear infinite; +} + +@keyframes circular-spin-keyframes { + 100% { + transform: rotate(-360deg); + } +} diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 87af299dad..91c1ee8607 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -1604,6 +1604,12 @@ td .commit-summary { margin-right: 0; } +.ui.label.scope-middle { + border-radius: 0; + margin-left: 0; + margin-right: 0; +} + .ui.label.scope-right { border-bottom-left-radius: 0; border-top-left-radius: 0; @@ -1724,8 +1730,7 @@ tbody.commit-list { line-height: 18px; margin: 1em; white-space: pre-wrap; - word-break: break-all; - overflow-wrap: break-word; + overflow-wrap: anywhere; } .content-history-detail-dialog .header .avatar { diff --git a/web_src/css/repo/home-file-list.css b/web_src/css/repo/home-file-list.css index 46128457ed..f2ab052a54 100644 --- a/web_src/css/repo/home-file-list.css +++ b/web_src/css/repo/home-file-list.css @@ -14,17 +14,6 @@ } } -#repo-files-table .svg.octicon-file-directory-fill, -#repo-files-table .svg.octicon-file-submodule { - color: var(--color-primary); -} - -#repo-files-table .svg.octicon-file, -#repo-files-table .svg.octicon-file-symlink-file, -#repo-files-table .svg.octicon-file-directory-symlink { - color: var(--color-secondary-dark-7); -} - #repo-files-table .repo-file-item { display: contents; } diff --git a/web_src/js/components/ActionRunStatus.vue b/web_src/js/components/ActionRunStatus.vue index 487d2460cc..bc3b99ab89 100644 --- a/web_src/js/components/ActionRunStatus.vue +++ b/web_src/js/components/ActionRunStatus.vue @@ -24,7 +24,7 @@ withDefaults(defineProps<{ - + diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index 2ef528620d..5a4c22bc90 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -574,7 +574,7 @@ export default defineComponent({ - + @@ -896,16 +896,6 @@ export default defineComponent({ ${svg}
d8a994ef24
d8a994ef24 (diff-2)
#12345
65f1bf27bc
65f1bf2
65f1bf27bc/src
65f1bf27bc...65f1bf27bc
65f1bf27bc...65f1bf27bc (L2)
commit 65f1bf27bc
/home/gitea/"+sha+"
deadbeef
d27ace93
`+sha[:14]+`.x
` + sha[:10] + `
`+expected14+`.
`+expected14+`,
[`+expected14+`]
test-owner/test-repo#12345
go-gitea/gitea#12345
/home/gitea/go-gitea/gitea#12345
783b039...da951ce
a
$a$
(a)
$$ +a +$$
+a +
[ +a +]
-
+
test
link1 +link2 +link3
https://google.com/
The Image Desc
Test
88fc37a3c0...12fc37a3c0 (hash)
88fc37a3c0
@no-such-user @mention-user @mention-user
scope/item
scope/
portée/label
âmbito/item
âmbito/
/etc/apk/repositories
/etc/apk/keys/
~/.cargo/config.toml
~/.chef/config.rb
~/.composer/config.json
Rprofile.site
Here are some links to the most important topics. You can find the full list of pages at the sidebar.
Configuration -
Link -
") for _, it := range diff { - if it.Type == diffmatchpatch.DiffInsert { + switch it.Type { + case diffmatchpatch.DiffInsert: diffHTMLBuf.WriteString("") diffHTMLBuf.WriteString(html.EscapeString(it.Text)) diffHTMLBuf.WriteString("") - } else if it.Type == diffmatchpatch.DiffDelete { + case diffmatchpatch.DiffDelete: diffHTMLBuf.WriteString("") diffHTMLBuf.WriteString(html.EscapeString(it.Text)) diffHTMLBuf.WriteString("") - } else { + default: diffHTMLBuf.WriteString(html.EscapeString(it.Text)) } } diff --git a/routers/web/repo/issue_label.go b/routers/web/repo/issue_label.go index 62c0128f19..f9c41adbcf 100644 --- a/routers/web/repo/issue_label.go +++ b/routers/web/repo/issue_label.go @@ -111,11 +111,12 @@ func NewLabel(ctx *context.Context) { } l := &issues_model.Label{ - RepoID: ctx.Repo.Repository.ID, - Name: form.Title, - Exclusive: form.Exclusive, - Description: form.Description, - Color: form.Color, + RepoID: ctx.Repo.Repository.ID, + Name: form.Title, + Exclusive: form.Exclusive, + ExclusiveOrder: form.ExclusiveOrder, + Description: form.Description, + Color: form.Color, } if err := issues_model.NewLabel(ctx, l); err != nil { ctx.ServerError("NewLabel", err) @@ -139,6 +140,7 @@ func UpdateLabel(ctx *context.Context) { } l.Name = form.Title l.Exclusive = form.Exclusive + l.ExclusiveOrder = form.ExclusiveOrder l.Description = form.Description l.Color = form.Color diff --git a/routers/web/repo/issue_label_test.go b/routers/web/repo/issue_label_test.go index 486c2e35a2..c3fba07034 100644 --- a/routers/web/repo/issue_label_test.go +++ b/routers/web/repo/issue_label_test.go @@ -38,7 +38,7 @@ func TestInitializeLabels(t *testing.T) { contexttest.LoadRepo(t, ctx, 2) web.SetForm(ctx, &forms.InitializeLabelsForm{TemplateName: "Default"}) InitializeLabels(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ RepoID: 2, Name: "enhancement", @@ -68,7 +68,7 @@ func TestRetrieveLabels(t *testing.T) { assert.True(t, ok) if assert.Len(t, labels, len(testCase.ExpectedLabelIDs)) { for i, label := range labels { - assert.EqualValues(t, testCase.ExpectedLabelIDs[i], label.ID) + assert.Equal(t, testCase.ExpectedLabelIDs[i], label.ID) } } } @@ -84,7 +84,7 @@ func TestNewLabel(t *testing.T) { Color: "#abcdef", }) NewLabel(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ Name: "newlabel", Color: "#abcdef", @@ -104,7 +104,7 @@ func TestUpdateLabel(t *testing.T) { IsArchived: true, }) UpdateLabel(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ ID: 2, Name: "newnameforlabel", @@ -120,7 +120,7 @@ func TestDeleteLabel(t *testing.T) { contexttest.LoadRepo(t, ctx, 1) ctx.Req.Form.Set("id", "2") DeleteLabel(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) unittest.AssertNotExistsBean(t, &issues_model.Label{ID: 2}) unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{LabelID: 2}) assert.EqualValues(t, ctx.Tr("repo.issues.label_deletion_success"), ctx.Flash.SuccessMsg) @@ -134,7 +134,7 @@ func TestUpdateIssueLabel_Clear(t *testing.T) { ctx.Req.Form.Set("issue_ids", "1,3") ctx.Req.Form.Set("action", "clear") UpdateIssueLabel(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{IssueID: 1}) unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{IssueID: 3}) unittest.CheckConsistencyFor(t, &issues_model.Label{}) @@ -160,7 +160,7 @@ func TestUpdateIssueLabel_Toggle(t *testing.T) { ctx.Req.Form.Set("action", testCase.Action) ctx.Req.Form.Set("id", strconv.Itoa(int(testCase.LabelID))) UpdateIssueLabel(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) for _, issueID := range testCase.IssueIDs { if testCase.ExpectedAdd { unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issueID, LabelID: testCase.LabelID}) diff --git a/routers/web/repo/issue_list.go b/routers/web/repo/issue_list.go index 69b38c81ec..35107bc585 100644 --- a/routers/web/repo/issue_list.go +++ b/routers/web/repo/issue_list.go @@ -5,8 +5,10 @@ package repo import ( "bytes" - "fmt" + "maps" "net/http" + "slices" + "sort" "strconv" "strings" @@ -18,6 +20,7 @@ import ( "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" issue_indexer "code.gitea.io/gitea/modules/indexer/issues" + db_indexer "code.gitea.io/gitea/modules/indexer/issues/db" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" @@ -30,14 +33,6 @@ import ( pull_service "code.gitea.io/gitea/services/pull" ) -func issueIDsFromSearch(ctx *context.Context, keyword string, opts *issues_model.IssuesOptions) ([]int64, error) { - ids, _, err := issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, opts)) - if err != nil { - return nil, fmt.Errorf("SearchIssues: %w", err) - } - return ids, nil -} - func retrieveProjectsForIssueList(ctx *context.Context, repo *repo_model.Repository) { ctx.Data["OpenProjects"], ctx.Data["ClosedProjects"] = retrieveProjectsInternal(ctx, repo) } @@ -459,6 +454,19 @@ func UpdateIssueStatus(ctx *context.Context) { ctx.JSONOK() } +func prepareIssueFilterExclusiveOrderScopes(ctx *context.Context, allLabels []*issues_model.Label) { + scopeSet := make(map[string]bool) + for _, label := range allLabels { + scope := label.ExclusiveScope() + if len(scope) > 0 && label.ExclusiveOrder > 0 { + scopeSet[scope] = true + } + } + scopes := slices.Collect(maps.Keys(scopeSet)) + sort.Strings(scopes) + ctx.Data["ExclusiveLabelScopes"] = scopes +} + func renderMilestones(ctx *context.Context) { // Get milestones milestones, err := db.Find[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{ @@ -481,7 +489,7 @@ func renderMilestones(ctx *context.Context) { ctx.Data["ClosedMilestones"] = closedMilestones } -func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption optional.Option[bool]) { +func prepareIssueFilterAndList(ctx *context.Context, milestoneID, projectID int64, isPullOption optional.Option[bool]) { var err error viewType := ctx.FormString("type") sortType := ctx.FormString("sort") @@ -502,7 +510,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt case "mentioned": mentionedID = ctx.Doer.ID case "assigned": - assigneeID = fmt.Sprint(ctx.Doer.ID) + assigneeID = strconv.FormatInt(ctx.Doer.ID, 10) case "review_requested": reviewRequestedID = ctx.Doer.ID case "reviewed_by": @@ -521,15 +529,18 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt mileIDs = []int64{milestoneID} } - labelIDs := issue.PrepareFilterIssueLabels(ctx, repo.ID, ctx.Repo.Owner) + preparedLabelFilter := issue.PrepareFilterIssueLabels(ctx, repo.ID, ctx.Repo.Owner) if ctx.Written() { return } + prepareIssueFilterExclusiveOrderScopes(ctx, preparedLabelFilter.AllLabels) + + var keywordMatchedIssueIDs []int64 var issueStats *issues_model.IssueStats statsOpts := &issues_model.IssuesOptions{ RepoIDs: []int64{repo.ID}, - LabelIDs: labelIDs, + LabelIDs: preparedLabelFilter.SelectedLabelIDs, MilestoneIDs: mileIDs, ProjectID: projectID, AssigneeID: assigneeID, @@ -541,7 +552,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt IssueIDs: nil, } if keyword != "" { - allIssueIDs, err := issueIDsFromSearch(ctx, keyword, statsOpts) + keywordMatchedIssueIDs, _, err = issue_indexer.SearchIssues(ctx, issue_indexer.ToSearchOptions(keyword, statsOpts)) if err != nil { if issue_indexer.IsAvailable(ctx) { ctx.ServerError("issueIDsFromSearch", err) @@ -550,14 +561,17 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt ctx.Data["IssueIndexerUnavailable"] = true return } - statsOpts.IssueIDs = allIssueIDs + if len(keywordMatchedIssueIDs) == 0 { + // It did search with the keyword, but no issue found, just set issueStats to empty, then no need to do query again. + issueStats = &issues_model.IssueStats{} + // set keywordMatchedIssueIDs to empty slice, so we can distinguish it from "nil" + keywordMatchedIssueIDs = []int64{} + } + statsOpts.IssueIDs = keywordMatchedIssueIDs } - if keyword != "" && len(statsOpts.IssueIDs) == 0 { - // So it did search with the keyword, but no issue found. - // Just set issueStats to empty. - issueStats = &issues_model.IssueStats{} - } else { - // So it did search with the keyword, and found some issues. It needs to get issueStats of these issues. + + if issueStats == nil { + // Either it did search with the keyword, and found some issues, it needs to get issueStats of these issues. // Or the keyword is empty, so it doesn't need issueIDs as filter, just get issueStats with statsOpts. issueStats, err = issues_model.GetIssueStats(ctx, statsOpts) if err != nil { @@ -589,25 +603,21 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt ctx.Data["TotalTrackedTime"] = totalTrackedTime } - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } - - var total int - switch { - case isShowClosed.Value(): - total = int(issueStats.ClosedCount) - case !isShowClosed.Has(): - total = int(issueStats.OpenCount + issueStats.ClosedCount) - default: - total = int(issueStats.OpenCount) + // prepare pager + total := int(issueStats.OpenCount + issueStats.ClosedCount) + if isShowClosed.Has() { + total = util.Iif(isShowClosed.Value(), int(issueStats.ClosedCount), int(issueStats.OpenCount)) } + page := max(ctx.FormInt("page"), 1) pager := context.NewPagination(total, setting.UI.IssuePagingNum, page, 5) + // prepare real issue list: var issues issues_model.IssueList - { - ids, err := issueIDsFromSearch(ctx, keyword, &issues_model.IssuesOptions{ + if keywordMatchedIssueIDs == nil || len(keywordMatchedIssueIDs) > 0 { + // Either it did search with the keyword, and found some issues, then keywordMatchedIssueIDs is not null, it needs to use db indexer. + // Or the keyword is empty, it also needs to usd db indexer. + // In either case, no need to use keyword anymore + searchResult, err := db_indexer.GetIndexer().FindWithIssueOptions(ctx, &issues_model.IssuesOptions{ Paginator: &db.ListOptions{ Page: pager.Paginater.Current(), PageSize: setting.UI.IssuePagingNum, @@ -622,18 +632,16 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt ProjectID: projectID, IsClosed: isShowClosed, IsPull: isPullOption, - LabelIDs: labelIDs, + LabelIDs: preparedLabelFilter.SelectedLabelIDs, SortType: sortType, + IssueIDs: keywordMatchedIssueIDs, }) if err != nil { - if issue_indexer.IsAvailable(ctx) { - ctx.ServerError("issueIDsFromSearch", err) - return - } - ctx.Data["IssueIndexerUnavailable"] = true + ctx.ServerError("DBIndexer.Search", err) return } - issues, err = issues_model.GetIssuesByIDs(ctx, ids, true) + issueIDs := issue_indexer.SearchResultToIDSlice(searchResult) + issues, err = issues_model.GetIssuesByIDs(ctx, issueIDs, true) if err != nil { ctx.ServerError("GetIssuesByIDs", err) return @@ -696,9 +704,10 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt return 0 } reviewTyp := issues_model.ReviewTypeApprove - if typ == "reject" { + switch typ { + case "reject": reviewTyp = issues_model.ReviewTypeReject - } else if typ == "waiting" { + case "waiting": reviewTyp = issues_model.ReviewTypeRequest } for _, count := range counts { @@ -727,7 +736,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt ctx.Data["IssueStats"] = issueStats ctx.Data["OpenCount"] = issueStats.OpenCount ctx.Data["ClosedCount"] = issueStats.ClosedCount - ctx.Data["SelLabelIDs"] = labelIDs + ctx.Data["SelLabelIDs"] = preparedLabelFilter.SelectedLabelIDs ctx.Data["ViewType"] = viewType ctx.Data["SortType"] = sortType ctx.Data["MilestoneID"] = milestoneID @@ -768,7 +777,7 @@ func Issues(ctx *context.Context) { ctx.Data["NewIssueChooseTemplate"] = issue_service.HasTemplatesOrContactLinks(ctx.Repo.Repository, ctx.Repo.GitRepo) } - issues(ctx, ctx.FormInt64("milestone"), ctx.FormInt64("project"), optional.Some(isPullList)) + prepareIssueFilterAndList(ctx, ctx.FormInt64("milestone"), ctx.FormInt64("project"), optional.Some(isPullList)) if ctx.Written() { return } diff --git a/routers/web/repo/issue_new.go b/routers/web/repo/issue_new.go index 9f52396414..d8863961ff 100644 --- a/routers/web/repo/issue_new.go +++ b/routers/web/repo/issue_new.go @@ -223,11 +223,11 @@ func DeleteIssue(ctx *context.Context) { } if issue.IsPull { - ctx.Redirect(fmt.Sprintf("%s/pulls", ctx.Repo.Repository.Link()), http.StatusSeeOther) + ctx.Redirect(ctx.Repo.Repository.Link()+"/pulls", http.StatusSeeOther) return } - ctx.Redirect(fmt.Sprintf("%s/issues", ctx.Repo.Repository.Link()), http.StatusSeeOther) + ctx.Redirect(ctx.Repo.Repository.Link()+"/issues", http.StatusSeeOther) } func toSet[ItemType any, KeyType comparable](slice []ItemType, keyFunc func(ItemType) KeyType) container.Set[KeyType] { diff --git a/routers/web/repo/issue_view.go b/routers/web/repo/issue_view.go index b312f1260a..3ffcdfe676 100644 --- a/routers/web/repo/issue_view.go +++ b/routers/web/repo/issue_view.go @@ -278,7 +278,7 @@ func ViewIssue(ctx *context.Context) { extIssueUnit, err := ctx.Repo.Repository.GetUnit(ctx, unit.TypeExternalTracker) if err == nil && extIssueUnit != nil { if extIssueUnit.ExternalTrackerConfig().ExternalTrackerStyle == markup.IssueNameStyleNumeric || extIssueUnit.ExternalTrackerConfig().ExternalTrackerStyle == "" { - metas := ctx.Repo.Repository.ComposeMetas(ctx) + metas := ctx.Repo.Repository.ComposeCommentMetas(ctx) metas["index"] = ctx.PathParam("index") res, err := vars.Expand(extIssueUnit.ExternalTrackerConfig().ExternalTrackerFormat, metas) if err != nil { diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go index f1d0a857ea..8a26a0dcc3 100644 --- a/routers/web/repo/milestone.go +++ b/routers/web/repo/milestone.go @@ -263,7 +263,7 @@ func MilestoneIssuesAndPulls(ctx *context.Context) { ctx.Data["Title"] = milestone.Name ctx.Data["Milestone"] = milestone - issues(ctx, milestoneID, projectID, optional.None[bool]()) + prepareIssueFilterAndList(ctx, milestoneID, projectID, optional.None[bool]()) ret := issue.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo) ctx.Data["NewIssueChooseTemplate"] = len(ret.IssueTemplates) > 0 diff --git a/routers/web/repo/patch.go b/routers/web/repo/patch.go index 120b3469f6..ca346b7e6c 100644 --- a/routers/web/repo/patch.go +++ b/routers/web/repo/patch.go @@ -4,6 +4,7 @@ package repo import ( + "net/http" "strings" git_model "code.gitea.io/gitea/models/git" @@ -39,7 +40,7 @@ func NewDiffPatch(ctx *context.Context) { ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",") ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL() - ctx.HTML(200, tplPatchFile) + ctx.HTML(http.StatusOK, tplPatchFile) } // NewDiffPatchPost response for sending patch page @@ -62,7 +63,7 @@ func NewDiffPatchPost(ctx *context.Context) { ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",") if ctx.HasError() { - ctx.HTML(200, tplPatchFile) + ctx.HTML(http.StatusOK, tplPatchFile) return } diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go index 6810025c6f..0bf1f64d09 100644 --- a/routers/web/repo/projects.go +++ b/routers/web/repo/projects.go @@ -313,13 +313,13 @@ func ViewProject(ctx *context.Context) { return } - labelIDs := issue.PrepareFilterIssueLabels(ctx, ctx.Repo.Repository.ID, ctx.Repo.Owner) + preparedLabelFilter := issue.PrepareFilterIssueLabels(ctx, ctx.Repo.Repository.ID, ctx.Repo.Owner) assigneeID := ctx.FormString("assignee") issuesMap, err := project_service.LoadIssuesFromProject(ctx, project, &issues_model.IssuesOptions{ RepoIDs: []int64{ctx.Repo.Repository.ID}, - LabelIDs: labelIDs, + LabelIDs: preparedLabelFilter.SelectedLabelIDs, AssigneeID: assigneeID, }) if err != nil { @@ -381,8 +381,8 @@ func ViewProject(ctx *context.Context) { } // Get the exclusive scope for every label ID - labelExclusiveScopes := make([]string, 0, len(labelIDs)) - for _, labelID := range labelIDs { + labelExclusiveScopes := make([]string, 0, len(preparedLabelFilter.SelectedLabelIDs)) + for _, labelID := range preparedLabelFilter.SelectedLabelIDs { foundExclusiveScope := false for _, label := range labels { if label.ID == labelID || label.ID == -labelID { @@ -397,7 +397,7 @@ func ViewProject(ctx *context.Context) { } for _, l := range labels { - l.LoadSelectedLabelsAfterClick(labelIDs, labelExclusiveScopes) + l.LoadSelectedLabelsAfterClick(preparedLabelFilter.SelectedLabelIDs, labelExclusiveScopes) } ctx.Data["Labels"] = labels ctx.Data["NumLabels"] = len(labels) diff --git a/routers/web/repo/pull_review.go b/routers/web/repo/pull_review.go index fb92d24394..929e131d61 100644 --- a/routers/web/repo/pull_review.go +++ b/routers/web/repo/pull_review.go @@ -209,11 +209,12 @@ func renderConversation(ctx *context.Context, comment *issues_model.Comment, ori return user_service.CanBlockUser(ctx, ctx.Doer, blocker, blockee) } - if origin == "diff" { + switch origin { + case "diff": ctx.HTML(http.StatusOK, tplDiffConversation) - } else if origin == "timeline" { + case "timeline": ctx.HTML(http.StatusOK, tplTimelineConversation) - } else { + default: ctx.HTTPError(http.StatusBadRequest, "Unknown origin: "+origin) } } diff --git a/routers/web/repo/recent_commits.go b/routers/web/repo/recent_commits.go index 228eb0dbac..2660116062 100644 --- a/routers/web/repo/recent_commits.go +++ b/routers/web/repo/recent_commits.go @@ -4,12 +4,10 @@ package repo import ( - "errors" "net/http" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/services/context" - contributors_service "code.gitea.io/gitea/services/repository" ) const ( @@ -26,16 +24,3 @@ func RecentCommits(ctx *context.Context) { ctx.HTML(http.StatusOK, tplRecentCommits) } - -// RecentCommitsData returns JSON of recent commits data -func RecentCommitsData(ctx *context.Context) { - if contributorStats, err := contributors_service.GetContributorStats(ctx, ctx.Cache, ctx.Repo.Repository, ctx.Repo.Repository.DefaultBranch); err != nil { - if errors.Is(err, contributors_service.ErrAwaitGeneration) { - ctx.Status(http.StatusAccepted) - return - } - ctx.ServerError("RecentCommitsData", err) - } else { - ctx.JSON(http.StatusOK, contributorStats["total"].Weeks) - } -} diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 54b7448a89..ee112b83f2 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -87,17 +87,13 @@ func checkContextUser(ctx *context.Context, uid int64) *user_model.User { return nil } - if !ctx.Doer.IsAdmin { - orgsAvailable := []*organization.Organization{} - for i := 0; i < len(orgs); i++ { - if orgs[i].CanCreateRepo() { - orgsAvailable = append(orgsAvailable, orgs[i]) - } + var orgsAvailable []*organization.Organization + for i := 0; i < len(orgs); i++ { + if ctx.Doer.CanCreateRepoIn(orgs[i].AsUser()) { + orgsAvailable = append(orgsAvailable, orgs[i]) } - ctx.Data["Orgs"] = orgsAvailable - } else { - ctx.Data["Orgs"] = orgs } + ctx.Data["Orgs"] = orgsAvailable // Not equal means current user is an organization. if uid == ctx.Doer.ID || uid == 0 { @@ -154,7 +150,7 @@ func createCommon(ctx *context.Context) { ctx.Data["Licenses"] = repo_module.Licenses ctx.Data["Readmes"] = repo_module.Readmes ctx.Data["IsForcedPrivate"] = setting.Repository.ForcePrivate - ctx.Data["CanCreateRepoInDoer"] = ctx.Doer.CanCreateRepo() + ctx.Data["CanCreateRepoInDoer"] = ctx.Doer.CanCreateRepoIn(ctx.Doer) ctx.Data["MaxCreationLimitOfDoer"] = ctx.Doer.MaxCreationLimit() ctx.Data["SupportedObjectFormats"] = git.DefaultFeatures().SupportedObjectFormats ctx.Data["DefaultObjectFormat"] = git.Sha1ObjectFormat @@ -305,11 +301,15 @@ func CreatePost(ctx *context.Context) { } func handleActionError(ctx *context.Context, err error) { - if errors.Is(err, user_model.ErrBlockedUser) { + switch { + case errors.Is(err, user_model.ErrBlockedUser): ctx.Flash.Error(ctx.Tr("repo.action.blocked_user")) - } else if errors.Is(err, util.ErrPermissionDenied) { + case repo_service.IsRepositoryLimitReached(err): + limit := err.(repo_service.LimitReachedError).Limit + ctx.Flash.Error(ctx.TrN(limit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", limit)) + case errors.Is(err, util.ErrPermissionDenied): ctx.HTTPError(http.StatusNotFound) - } else { + default: ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.PathParam("action")), err) } } diff --git a/routers/web/repo/setting/lfs.go b/routers/web/repo/setting/lfs.go index 655291d25c..a065620b2b 100644 --- a/routers/web/repo/setting/lfs.go +++ b/routers/web/repo/setting/lfs.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/attribute" "code.gitea.io/gitea/modules/git/pipeline" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" @@ -109,17 +110,13 @@ func LFSLocks(ctx *context.Context) { } // Clone base repo. - tmpBasePath, err := repo_module.CreateTemporaryPath("locks") + tmpBasePath, cleanup, err := repo_module.CreateTemporaryPath("locks") if err != nil { log.Error("Failed to create temporary path: %v", err) ctx.ServerError("LFSLocks", err) return } - defer func() { - if err := repo_module.RemoveTemporaryPath(tmpBasePath); err != nil { - log.Error("LFSLocks: RemoveTemporaryPath: %v", err) - } - }() + defer cleanup() if err := git.Clone(ctx, ctx.Repo.Repository.RepoPath(), tmpBasePath, git.CloneRepoOptions{ Bare: true, @@ -138,39 +135,24 @@ func LFSLocks(ctx *context.Context) { } defer gitRepo.Close() - filenames := make([]string, len(lfsLocks)) - - for i, lock := range lfsLocks { - filenames[i] = lock.Path - } - - if err := gitRepo.ReadTreeToIndex(ctx.Repo.Repository.DefaultBranch); err != nil { - log.Error("Unable to read the default branch to the index: %s (%v)", ctx.Repo.Repository.DefaultBranch, err) - ctx.ServerError("LFSLocks", fmt.Errorf("unable to read the default branch to the index: %s (%w)", ctx.Repo.Repository.DefaultBranch, err)) - return - } - - name2attribute2info, err := gitRepo.CheckAttribute(git.CheckAttributeOpts{ - Attributes: []string{"lockable"}, - Filenames: filenames, - CachedOnly: true, - }) + checker, err := attribute.NewBatchChecker(gitRepo, ctx.Repo.Repository.DefaultBranch, []string{attribute.Lockable}) if err != nil { log.Error("Unable to check attributes in %s (%v)", tmpBasePath, err) ctx.ServerError("LFSLocks", err) return } + defer checker.Close() lockables := make([]bool, len(lfsLocks)) + filenames := make([]string, len(lfsLocks)) for i, lock := range lfsLocks { - attribute2info, has := name2attribute2info[lock.Path] - if !has { + filenames[i] = lock.Path + attrs, err := checker.CheckPath(lock.Path) + if err != nil { + log.Error("Unable to check attributes in %s: %s (%v)", tmpBasePath, lock.Path, err) continue } - if attribute2info["lockable"] != "set" { - continue - } - lockables[i] = true + lockables[i] = attrs.Get(attribute.Lockable).ToBool().Value() } ctx.Data["Lockables"] = lockables diff --git a/routers/web/repo/setting/protected_branch.go b/routers/web/repo/setting/protected_branch.go index 75de2ba1e7..f241242f02 100644 --- a/routers/web/repo/setting/protected_branch.go +++ b/routers/web/repo/setting/protected_branch.go @@ -8,6 +8,7 @@ import ( "fmt" "net/http" "net/url" + "strconv" "strings" "time" @@ -110,7 +111,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) { var protectBranch *git_model.ProtectedBranch if f.RuleName == "" { ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_rule_name")) - ctx.Redirect(fmt.Sprintf("%s/settings/branches/edit", ctx.Repo.RepoLink)) + ctx.Redirect(ctx.Repo.RepoLink + "/settings/branches/edit") return } @@ -283,32 +284,32 @@ func SettingsProtectedBranchPost(ctx *context.Context) { func DeleteProtectedBranchRulePost(ctx *context.Context) { ruleID := ctx.PathParamInt64("id") if ruleID <= 0 { - ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID))) - ctx.JSONRedirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) + ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", strconv.FormatInt(ruleID, 10))) + ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches") return } rule, err := git_model.GetProtectedBranchRuleByID(ctx, ctx.Repo.Repository.ID, ruleID) if err != nil { - ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID))) - ctx.JSONRedirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) + ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", strconv.FormatInt(ruleID, 10))) + ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches") return } if rule == nil { - ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", fmt.Sprintf("%d", ruleID))) - ctx.JSONRedirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) + ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", strconv.FormatInt(ruleID, 10))) + ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches") return } if err := git_model.DeleteProtectedBranch(ctx, ctx.Repo.Repository, ruleID); err != nil { ctx.Flash.Error(ctx.Tr("repo.settings.remove_protected_branch_failed", rule.RuleName)) - ctx.JSONRedirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) + ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches") return } ctx.Flash.Success(ctx.Tr("repo.settings.remove_protected_branch_success", rule.RuleName)) - ctx.JSONRedirect(fmt.Sprintf("%s/settings/branches", ctx.Repo.RepoLink)) + ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/branches") } func UpdateBranchProtectionPriories(ctx *context.Context) { @@ -332,7 +333,7 @@ func RenameBranchPost(ctx *context.Context) { if ctx.HasError() { ctx.Flash.Error(ctx.GetErrMsg()) - ctx.Redirect(fmt.Sprintf("%s/branches", ctx.Repo.RepoLink)) + ctx.Redirect(ctx.Repo.RepoLink + "/branches") return } @@ -341,13 +342,13 @@ func RenameBranchPost(ctx *context.Context) { switch { case repo_model.IsErrUserDoesNotHaveAccessToRepo(err): ctx.Flash.Error(ctx.Tr("repo.branch.rename_default_or_protected_branch_error")) - ctx.Redirect(fmt.Sprintf("%s/branches", ctx.Repo.RepoLink)) + ctx.Redirect(ctx.Repo.RepoLink + "/branches") case git_model.IsErrBranchAlreadyExists(err): ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.To)) - ctx.Redirect(fmt.Sprintf("%s/branches", ctx.Repo.RepoLink)) + ctx.Redirect(ctx.Repo.RepoLink + "/branches") case errors.Is(err, git_model.ErrBranchIsProtected): ctx.Flash.Error(ctx.Tr("repo.branch.rename_protected_branch_failed")) - ctx.Redirect(fmt.Sprintf("%s/branches", ctx.Repo.RepoLink)) + ctx.Redirect(ctx.Repo.RepoLink + "/branches") default: ctx.ServerError("RenameBranch", err) } @@ -356,16 +357,16 @@ func RenameBranchPost(ctx *context.Context) { if msg == "target_exist" { ctx.Flash.Error(ctx.Tr("repo.settings.rename_branch_failed_exist", form.To)) - ctx.Redirect(fmt.Sprintf("%s/branches", ctx.Repo.RepoLink)) + ctx.Redirect(ctx.Repo.RepoLink + "/branches") return } if msg == "from_not_exist" { ctx.Flash.Error(ctx.Tr("repo.settings.rename_branch_failed_not_exist", form.From)) - ctx.Redirect(fmt.Sprintf("%s/branches", ctx.Repo.RepoLink)) + ctx.Redirect(ctx.Repo.RepoLink + "/branches") return } ctx.Flash.Success(ctx.Tr("repo.settings.rename_branch_success", form.From, form.To)) - ctx.Redirect(fmt.Sprintf("%s/branches", ctx.Repo.RepoLink)) + ctx.Redirect(ctx.Repo.RepoLink + "/branches") } diff --git a/routers/web/repo/setting/public_access.go b/routers/web/repo/setting/public_access.go new file mode 100644 index 0000000000..368d34294a --- /dev/null +++ b/routers/web/repo/setting/public_access.go @@ -0,0 +1,155 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "net/http" + "slices" + "strconv" + + "code.gitea.io/gitea/models/perm" + "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/services/context" +) + +const tplRepoSettingsPublicAccess templates.TplName = "repo/settings/public_access" + +func parsePublicAccessMode(permission string, allowed []string) (ret struct { + AnonymousAccessMode, EveryoneAccessMode perm.AccessMode +}, +) { + ret.AnonymousAccessMode = perm.AccessModeNone + ret.EveryoneAccessMode = perm.AccessModeNone + + // if site admin forces repositories to be private, then do not allow any other access mode, + // otherwise the "force private" setting would be bypassed + if setting.Repository.ForcePrivate { + return ret + } + if !slices.Contains(allowed, permission) { + return ret + } + switch permission { + case paAnonymousRead: + ret.AnonymousAccessMode = perm.AccessModeRead + case paEveryoneRead: + ret.EveryoneAccessMode = perm.AccessModeRead + case paEveryoneWrite: + ret.EveryoneAccessMode = perm.AccessModeWrite + } + return ret +} + +const ( + paNotSet = "not-set" + paAnonymousRead = "anonymous-read" + paEveryoneRead = "everyone-read" + paEveryoneWrite = "everyone-write" +) + +type repoUnitPublicAccess struct { + UnitType unit.Type + FormKey string + DisplayName string + PublicAccessTypes []string + UnitPublicAccess string +} + +func repoUnitPublicAccesses(ctx *context.Context) []*repoUnitPublicAccess { + accesses := []*repoUnitPublicAccess{ + { + UnitType: unit.TypeCode, + DisplayName: ctx.Locale.TrString("repo.code"), + PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead}, + }, + { + UnitType: unit.TypeIssues, + DisplayName: ctx.Locale.TrString("issues"), + PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead}, + }, + { + UnitType: unit.TypePullRequests, + DisplayName: ctx.Locale.TrString("pull_requests"), + PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead}, + }, + { + UnitType: unit.TypeReleases, + DisplayName: ctx.Locale.TrString("repo.releases"), + PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead}, + }, + { + UnitType: unit.TypeWiki, + DisplayName: ctx.Locale.TrString("repo.wiki"), + PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead, paEveryoneWrite}, + }, + { + UnitType: unit.TypeProjects, + DisplayName: ctx.Locale.TrString("repo.projects"), + PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead}, + }, + { + UnitType: unit.TypePackages, + DisplayName: ctx.Locale.TrString("repo.packages"), + PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead}, + }, + { + UnitType: unit.TypeActions, + DisplayName: ctx.Locale.TrString("repo.actions"), + PublicAccessTypes: []string{paAnonymousRead, paEveryoneRead}, + }, + } + for _, ua := range accesses { + ua.FormKey = "repo-unit-access-" + strconv.Itoa(int(ua.UnitType)) + for _, u := range ctx.Repo.Repository.Units { + if u.Type == ua.UnitType { + ua.UnitPublicAccess = paNotSet + switch { + case u.EveryoneAccessMode == perm.AccessModeWrite: + ua.UnitPublicAccess = paEveryoneWrite + case u.EveryoneAccessMode == perm.AccessModeRead: + ua.UnitPublicAccess = paEveryoneRead + case u.AnonymousAccessMode == perm.AccessModeRead: + ua.UnitPublicAccess = paAnonymousRead + } + break + } + } + } + return slices.DeleteFunc(accesses, func(ua *repoUnitPublicAccess) bool { + return ua.UnitPublicAccess == "" + }) +} + +func PublicAccess(ctx *context.Context) { + ctx.Data["PageIsSettingsPublicAccess"] = true + ctx.Data["RepoUnitPublicAccesses"] = repoUnitPublicAccesses(ctx) + ctx.Data["GlobalForcePrivate"] = setting.Repository.ForcePrivate + if setting.Repository.ForcePrivate { + ctx.Flash.Error(ctx.Tr("form.repository_force_private"), true) + } + ctx.HTML(http.StatusOK, tplRepoSettingsPublicAccess) +} + +func PublicAccessPost(ctx *context.Context) { + accesses := repoUnitPublicAccesses(ctx) + for _, ua := range accesses { + formVal := ctx.FormString(ua.FormKey) + parsed := parsePublicAccessMode(formVal, ua.PublicAccessTypes) + err := repo.UpdateRepoUnitPublicAccess(ctx, &repo.RepoUnit{ + RepoID: ctx.Repo.Repository.ID, + Type: ua.UnitType, + AnonymousAccessMode: parsed.AnonymousAccessMode, + EveryoneAccessMode: parsed.EveryoneAccessMode, + }) + if err != nil { + ctx.ServerError("UpdateRepoUnitPublicAccess", err) + return + } + } + ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) + ctx.Redirect(ctx.Repo.Repository.Link() + "/settings/public_access") +} diff --git a/routers/web/repo/setting/secrets.go b/routers/web/repo/setting/secrets.go index 46cb875f9b..c6e2d18249 100644 --- a/routers/web/repo/setting/secrets.go +++ b/routers/web/repo/setting/secrets.go @@ -44,9 +44,8 @@ func getSecretsCtx(ctx *context.Context) (*secretsCtx, error) { } if ctx.Data["PageIsOrgSettings"] == true { - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return nil, nil } return &secretsCtx{ diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index e8da443b67..5552a8726c 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -6,14 +6,12 @@ package setting import ( "errors" - "fmt" "net/http" "strings" "time" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" - "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" unit_model "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -37,6 +35,8 @@ import ( mirror_service "code.gitea.io/gitea/services/mirror" repo_service "code.gitea.io/gitea/services/repository" wiki_service "code.gitea.io/gitea/services/wiki" + + "xorm.io/xorm/convert" ) const ( @@ -48,15 +48,6 @@ const ( tplDeployKeys templates.TplName = "repo/settings/deploy_keys" ) -func parseEveryoneAccessMode(permission string, allowed ...perm.AccessMode) perm.AccessMode { - // if site admin forces repositories to be private, then do not allow any other access mode, - // otherwise the "force private" setting would be bypassed - if setting.Repository.ForcePrivate { - return perm.AccessModeNone - } - return perm.ParseAccessMode(permission, allowed...) -} - // SettingsCtxData is a middleware that sets all the general context data for the // settings template. func SettingsCtxData(ctx *context.Context) { @@ -68,6 +59,7 @@ func SettingsCtxData(ctx *context.Context) { ctx.Data["DisableNewPushMirrors"] = setting.Mirror.DisableNewPush ctx.Data["DefaultMirrorInterval"] = setting.Mirror.DefaultInterval ctx.Data["MinimumMirrorInterval"] = setting.Mirror.MinInterval + ctx.Data["CanConvertFork"] = ctx.Repo.Repository.IsFork && ctx.Doer.CanCreateRepoIn(ctx.Repo.Repository.Owner) signing, _ := asymkey_service.SigningKey(ctx, ctx.Repo.Repository.RepoPath()) ctx.Data["SigningKeyAvailable"] = len(signing) > 0 @@ -482,7 +474,7 @@ func handleSettingsPostPushMirrorAdd(ctx *context.Context) { m := &repo_model.PushMirror{ RepoID: repo.ID, Repo: repo, - RemoteName: fmt.Sprintf("remote_mirror_%s", remoteSuffix), + RemoteName: "remote_mirror_" + remoteSuffix, SyncOnCommit: form.PushMirrorSyncOnCommit, Interval: interval, RemoteAddress: remoteAddress, @@ -504,6 +496,17 @@ func handleSettingsPostPushMirrorAdd(ctx *context.Context) { ctx.Redirect(repo.Link() + "/settings") } +func newRepoUnit(repo *repo_model.Repository, unitType unit_model.Type, config convert.Conversion) repo_model.RepoUnit { + repoUnit := repo_model.RepoUnit{RepoID: repo.ID, Type: unitType, Config: config} + for _, u := range repo.Units { + if u.Type == unitType { + repoUnit.EveryoneAccessMode = u.EveryoneAccessMode + repoUnit.AnonymousAccessMode = u.AnonymousAccessMode + } + } + return repoUnit +} + func handleSettingsPostAdvanced(ctx *context.Context) { form := web.GetForm(ctx).(*forms.RepoSettingForm) repo := ctx.Repo.Repository @@ -521,11 +524,7 @@ func handleSettingsPostAdvanced(ctx *context.Context) { } if form.EnableCode && !unit_model.TypeCode.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeCode, - EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultCodeEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead), - }) + units = append(units, newRepoUnit(repo, unit_model.TypeCode, nil)) } else if !unit_model.TypeCode.UnitGlobalDisabled() { deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeCode) } @@ -537,21 +536,12 @@ func handleSettingsPostAdvanced(ctx *context.Context) { return } - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeExternalWiki, - Config: &repo_model.ExternalWikiConfig{ - ExternalWikiURL: form.ExternalWikiURL, - }, - }) + units = append(units, newRepoUnit(repo, unit_model.TypeExternalWiki, &repo_model.ExternalWikiConfig{ + ExternalWikiURL: form.ExternalWikiURL, + })) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki) } else if form.EnableWiki && !form.EnableExternalWiki && !unit_model.TypeWiki.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeWiki, - Config: new(repo_model.UnitConfig), - EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultWikiEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead, perm.AccessModeWrite), - }) + units = append(units, newRepoUnit(repo, unit_model.TypeWiki, new(repo_model.UnitConfig))) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki) } else { if !unit_model.TypeExternalWiki.UnitGlobalDisabled() { @@ -580,28 +570,19 @@ func handleSettingsPostAdvanced(ctx *context.Context) { ctx.Redirect(repo.Link() + "/settings") return } - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeExternalTracker, - Config: &repo_model.ExternalTrackerConfig{ - ExternalTrackerURL: form.ExternalTrackerURL, - ExternalTrackerFormat: form.TrackerURLFormat, - ExternalTrackerStyle: form.TrackerIssueStyle, - ExternalTrackerRegexpPattern: form.ExternalTrackerRegexpPattern, - }, - }) + units = append(units, newRepoUnit(repo, unit_model.TypeExternalTracker, &repo_model.ExternalTrackerConfig{ + ExternalTrackerURL: form.ExternalTrackerURL, + ExternalTrackerFormat: form.TrackerURLFormat, + ExternalTrackerStyle: form.TrackerIssueStyle, + ExternalTrackerRegexpPattern: form.ExternalTrackerRegexpPattern, + })) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeIssues) } else if form.EnableIssues && !form.EnableExternalTracker && !unit_model.TypeIssues.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeIssues, - Config: &repo_model.IssuesConfig{ - EnableTimetracker: form.EnableTimetracker, - AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, - EnableDependencies: form.EnableIssueDependencies, - }, - EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultIssuesEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead), - }) + units = append(units, newRepoUnit(repo, unit_model.TypeIssues, &repo_model.IssuesConfig{ + EnableTimetracker: form.EnableTimetracker, + AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, + EnableDependencies: form.EnableIssueDependencies, + })) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker) } else { if !unit_model.TypeExternalTracker.UnitGlobalDisabled() { @@ -613,63 +594,46 @@ func handleSettingsPostAdvanced(ctx *context.Context) { } if form.EnableProjects && !unit_model.TypeProjects.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeProjects, - Config: &repo_model.ProjectsConfig{ - ProjectsMode: repo_model.ProjectsMode(form.ProjectsMode), - }, - }) + units = append(units, newRepoUnit(repo, unit_model.TypeProjects, &repo_model.ProjectsConfig{ + ProjectsMode: repo_model.ProjectsMode(form.ProjectsMode), + })) } else if !unit_model.TypeProjects.UnitGlobalDisabled() { deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeProjects) } if form.EnableReleases && !unit_model.TypeReleases.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeReleases, - }) + units = append(units, newRepoUnit(repo, unit_model.TypeReleases, nil)) } else if !unit_model.TypeReleases.UnitGlobalDisabled() { deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeReleases) } if form.EnablePackages && !unit_model.TypePackages.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypePackages, - }) + units = append(units, newRepoUnit(repo, unit_model.TypePackages, nil)) } else if !unit_model.TypePackages.UnitGlobalDisabled() { deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePackages) } if form.EnableActions && !unit_model.TypeActions.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeActions, - }) + units = append(units, newRepoUnit(repo, unit_model.TypeActions, nil)) } else if !unit_model.TypeActions.UnitGlobalDisabled() { deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeActions) } if form.EnablePulls && !unit_model.TypePullRequests.UnitGlobalDisabled() { - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypePullRequests, - Config: &repo_model.PullRequestsConfig{ - IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace, - AllowMerge: form.PullsAllowMerge, - AllowRebase: form.PullsAllowRebase, - AllowRebaseMerge: form.PullsAllowRebaseMerge, - AllowSquash: form.PullsAllowSquash, - AllowFastForwardOnly: form.PullsAllowFastForwardOnly, - AllowManualMerge: form.PullsAllowManualMerge, - AutodetectManualMerge: form.EnableAutodetectManualMerge, - AllowRebaseUpdate: form.PullsAllowRebaseUpdate, - DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge, - DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle), - DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit, - }, - }) + units = append(units, newRepoUnit(repo, unit_model.TypePullRequests, &repo_model.PullRequestsConfig{ + IgnoreWhitespaceConflicts: form.PullsIgnoreWhitespace, + AllowMerge: form.PullsAllowMerge, + AllowRebase: form.PullsAllowRebase, + AllowRebaseMerge: form.PullsAllowRebaseMerge, + AllowSquash: form.PullsAllowSquash, + AllowFastForwardOnly: form.PullsAllowFastForwardOnly, + AllowManualMerge: form.PullsAllowManualMerge, + AutodetectManualMerge: form.EnableAutodetectManualMerge, + AllowRebaseUpdate: form.PullsAllowRebaseUpdate, + DefaultDeleteBranchAfterMerge: form.DefaultDeleteBranchAfterMerge, + DefaultMergeStyle: repo_model.MergeStyle(form.PullsDefaultMergeStyle), + DefaultAllowMaintainerEdit: form.DefaultAllowMaintainerEdit, + })) } else if !unit_model.TypePullRequests.UnitGlobalDisabled() { deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests) } @@ -823,7 +787,7 @@ func handleSettingsPostConvertFork(ctx *context.Context) { return } - if !ctx.Repo.Owner.CanCreateRepo() { + if !ctx.Doer.CanForkRepoIn(ctx.Repo.Owner) { maxCreationLimit := ctx.Repo.Owner.MaxCreationLimit() msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit) ctx.Flash.Error(msg) @@ -884,6 +848,9 @@ func handleSettingsPostTransfer(ctx *context.Context) { ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil) } else if repo_model.IsErrRepoTransferInProgress(err) { ctx.RenderWithErr(ctx.Tr("repo.settings.transfer_in_progress"), tplSettingsOptions, nil) + } else if repo_service.IsRepositoryLimitReached(err) { + limit := err.(repo_service.LimitReachedError).Limit + ctx.RenderWithErr(ctx.TrN(limit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", limit), tplSettingsOptions, nil) } else if errors.Is(err, user_model.ErrBlockedUser) { ctx.RenderWithErr(ctx.Tr("repo.settings.transfer.blocked_user"), tplSettingsOptions, nil) } else { diff --git a/routers/web/repo/setting/settings_test.go b/routers/web/repo/setting/settings_test.go index ad33dac514..ba6b0d1efc 100644 --- a/routers/web/repo/setting/settings_test.go +++ b/routers/web/repo/setting/settings_test.go @@ -54,7 +54,7 @@ func TestAddReadOnlyDeployKey(t *testing.T) { } web.SetForm(ctx, &addKeyForm) DeployKeysPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) unittest.AssertExistsAndLoadBean(t, &asymkey_model.DeployKey{ Name: addKeyForm.Title, @@ -84,7 +84,7 @@ func TestAddReadWriteOnlyDeployKey(t *testing.T) { } web.SetForm(ctx, &addKeyForm) DeployKeysPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) unittest.AssertExistsAndLoadBean(t, &asymkey_model.DeployKey{ Name: addKeyForm.Title, @@ -121,7 +121,7 @@ func TestCollaborationPost(t *testing.T) { CollaborationPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) exists, err := repo_model.IsCollaborator(ctx, re.ID, 4) assert.NoError(t, err) @@ -147,7 +147,7 @@ func TestCollaborationPost_InactiveUser(t *testing.T) { CollaborationPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assert.NotEmpty(t, ctx.Flash.ErrorMsg) } @@ -179,7 +179,7 @@ func TestCollaborationPost_AddCollaboratorTwice(t *testing.T) { CollaborationPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) exists, err := repo_model.IsCollaborator(ctx, re.ID, 4) assert.NoError(t, err) @@ -188,7 +188,7 @@ func TestCollaborationPost_AddCollaboratorTwice(t *testing.T) { // Try adding the same collaborator again CollaborationPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assert.NotEmpty(t, ctx.Flash.ErrorMsg) } @@ -210,7 +210,7 @@ func TestCollaborationPost_NonExistentUser(t *testing.T) { CollaborationPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assert.NotEmpty(t, ctx.Flash.ErrorMsg) } @@ -250,7 +250,7 @@ func TestAddTeamPost(t *testing.T) { AddTeamPost(ctx) assert.True(t, repo_service.HasRepository(db.DefaultContext, team, re.ID)) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assert.Empty(t, ctx.Flash.ErrorMsg) } @@ -290,7 +290,7 @@ func TestAddTeamPost_NotAllowed(t *testing.T) { AddTeamPost(ctx) assert.False(t, repo_service.HasRepository(db.DefaultContext, team, re.ID)) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assert.NotEmpty(t, ctx.Flash.ErrorMsg) } @@ -331,7 +331,7 @@ func TestAddTeamPost_AddTeamTwice(t *testing.T) { AddTeamPost(ctx) assert.True(t, repo_service.HasRepository(db.DefaultContext, team, re.ID)) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assert.NotEmpty(t, ctx.Flash.ErrorMsg) } @@ -364,7 +364,7 @@ func TestAddTeamPost_NonExistentTeam(t *testing.T) { ctx.Repo = repo AddTeamPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assert.NotEmpty(t, ctx.Flash.ErrorMsg) } diff --git a/routers/web/repo/treelist.go b/routers/web/repo/treelist.go index ab74741e61..9c5ec8f206 100644 --- a/routers/web/repo/treelist.go +++ b/routers/web/repo/treelist.go @@ -8,6 +8,7 @@ import ( pull_model "code.gitea.io/gitea/models/pull" "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/fileicon" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/gitdiff" @@ -87,10 +88,11 @@ func transformDiffTreeForUI(diffTree *gitdiff.DiffTree, filesViewedState map[str } func TreeViewNodes(ctx *context.Context) { - results, err := files_service.GetTreeViewNodes(ctx, ctx.Repo.Commit, ctx.Repo.TreePath, ctx.FormString("sub_path")) + renderedIconPool := fileicon.NewRenderedIconPool() + results, err := files_service.GetTreeViewNodes(ctx, renderedIconPool, ctx.Repo.Commit, ctx.Repo.TreePath, ctx.FormString("sub_path")) if err != nil { ctx.ServerError("GetTreeViewNodes", err) return } - ctx.JSON(http.StatusOK, map[string]any{"fileTreeNodes": results}) + ctx.JSON(http.StatusOK, map[string]any{"fileTreeNodes": results, "renderedIconPool": renderedIconPool.IconSVGs}) } diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 6ed5801d10..77240f0431 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -29,6 +29,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/charset" + "code.gitea.io/gitea/modules/fileicon" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" @@ -252,6 +253,16 @@ func LastCommit(ctx *context.Context) { ctx.HTML(http.StatusOK, tplRepoViewList) } +func prepareDirectoryFileIcons(ctx *context.Context, files []git.CommitInfo) { + renderedIconPool := fileicon.NewRenderedIconPool() + fileIcons := map[string]template.HTML{} + for _, f := range files { + fileIcons[f.Entry.Name()] = fileicon.RenderEntryIcon(renderedIconPool, f.Entry) + } + ctx.Data["FileIcons"] = fileIcons + ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML() +} + func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entries { tree, err := ctx.Repo.Commit.SubTree(ctx.Repo.TreePath) if err != nil { @@ -293,6 +304,7 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri return nil } ctx.Data["Files"] = files + prepareDirectoryFileIcons(ctx, files) for _, f := range files { if f.Commit == nil { ctx.Data["HasFilesWithoutLatestCommit"] = true diff --git a/routers/web/repo/view_file.go b/routers/web/repo/view_file.go index 4ce7a8e3a4..3df6051975 100644 --- a/routers/web/repo/view_file.go +++ b/routers/web/repo/view_file.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/actions" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/attribute" "code.gitea.io/gitea/modules/highlight" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" @@ -25,36 +26,34 @@ import ( "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/context" issue_service "code.gitea.io/gitea/services/issue" - files_service "code.gitea.io/gitea/services/repository/files" "github.com/nektos/act/pkg/model" ) -func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) { - ctx.Data["IsViewFile"] = true - ctx.Data["HideRepoInfo"] = true - blob := entry.Blob() - buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, blob) - if err != nil { - ctx.ServerError("getFileReader", err) - return - } - defer dataRc.Close() - - ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefFullName.ShortName()) - ctx.Data["FileIsSymlink"] = entry.IsLink() - ctx.Data["FileName"] = blob.Name() - ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) - +func prepareLatestCommitInfo(ctx *context.Context) bool { commit, err := ctx.Repo.Commit.GetCommitByPath(ctx.Repo.TreePath) if err != nil { ctx.ServerError("GetCommitByPath", err) + return false + } + + return loadLatestCommitData(ctx, commit) +} + +func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) { + ctx.Data["IsViewFile"] = true + ctx.Data["HideRepoInfo"] = true + + if !prepareLatestCommitInfo(ctx) { return } - if !loadLatestCommitData(ctx, commit) { - return - } + blob := entry.Blob() + + ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefFullName.ShortName()) + ctx.Data["FileIsSymlink"] = entry.IsLink() + ctx.Data["FileTreePath"] = ctx.Repo.TreePath + ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) if ctx.Repo.TreePath == ".editorconfig" { _, editorconfigWarning, editorconfigErr := ctx.Repo.GetEditorconfig(ctx.Repo.Commit) @@ -90,6 +89,15 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) { isDisplayingSource := ctx.FormString("display") == "source" isDisplayingRendered := !isDisplayingSource + // Don't call any other repository functions depends on git.Repository until the dataRc closed to + // avoid create unnecessary temporary cat file. + buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, blob) + if err != nil { + ctx.ServerError("getFileReader", err) + return + } + defer dataRc.Close() + if fInfo.isLFSFile { ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/media/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) } @@ -139,6 +147,23 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) { ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files") } + // read all needed attributes which will be used later + // there should be no performance different between reading 2 or 4 here + attrsMap, err := attribute.CheckAttributes(ctx, ctx.Repo.GitRepo, ctx.Repo.CommitID, attribute.CheckAttributeOpts{ + Filenames: []string{ctx.Repo.TreePath}, + Attributes: []string{attribute.LinguistGenerated, attribute.LinguistVendored, attribute.LinguistLanguage, attribute.GitlabLanguage}, + }) + if err != nil { + ctx.ServerError("attribute.CheckAttributes", err) + return + } + attrs := attrsMap[ctx.Repo.TreePath] + if attrs == nil { + // this case shouldn't happen, just in case. + setting.PanicInDevOrTesting("no attributes found for %s", ctx.Repo.TreePath) + attrs = attribute.NewAttributes() + } + switch { case isRepresentableAsText: if fInfo.fileSize >= setting.UI.MaxDisplayFileSize { @@ -168,7 +193,7 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) { if markupType != "" && !shouldRenderSource { ctx.Data["IsMarkup"] = true ctx.Data["MarkupType"] = markupType - metas := ctx.Repo.Repository.ComposeDocumentMetas(ctx) + metas := ctx.Repo.Repository.ComposeRepoFileMetas(ctx) metas["RefTypeNameSubURL"] = ctx.Repo.RefTypeNameSubURL() rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{ CurrentRefPath: ctx.Repo.RefTypeNameSubURL(), @@ -201,11 +226,7 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) { ctx.Data["NumLines"] = bytes.Count(buf, []byte{'\n'}) + 1 } - language, err := files_service.TryGetContentLanguage(ctx.Repo.GitRepo, ctx.Repo.CommitID, ctx.Repo.TreePath) - if err != nil { - log.Error("Unable to get file language for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err) - } - + language := attrs.GetLanguage().Value() fileContent, lexerName, err := highlight.File(blob.Name(), language, buf) ctx.Data["LexerName"] = lexerName if err != nil { @@ -275,17 +296,7 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) { } } - if ctx.Repo.GitRepo != nil { - checker, deferable := ctx.Repo.GitRepo.CheckAttributeReader(ctx.Repo.CommitID) - if checker != nil { - defer deferable() - attrs, err := checker.CheckPath(ctx.Repo.TreePath) - if err == nil { - ctx.Data["IsVendored"] = git.AttributeToBool(attrs, git.AttributeLinguistVendored).Value() - ctx.Data["IsGenerated"] = git.AttributeToBool(attrs, git.AttributeLinguistGenerated).Value() - } - } - } + ctx.Data["IsVendored"], ctx.Data["IsGenerated"] = attrs.GetVendored().Value(), attrs.GetGenerated().Value() if fInfo.st.IsImage() && !fInfo.st.IsSvgImage() { img, _, err := image.DecodeConfig(bytes.NewReader(buf)) diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go index d538406035..3b053821ee 100644 --- a/routers/web/repo/view_home.go +++ b/routers/web/repo/view_home.go @@ -20,6 +20,8 @@ import ( unit_model "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + giturl "code.gitea.io/gitea/modules/git/url" + "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" @@ -76,7 +78,7 @@ func prepareOpenWithEditorApps(ctx *context.Context) { schema, _, _ := strings.Cut(app.OpenURL, ":") var iconHTML template.HTML if schema == "vscode" || schema == "vscodium" || schema == "jetbrains" { - iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-%s", schema), 16) + iconHTML = svg.RenderHTML("gitea-"+schema, 16) } else { iconHTML = svg.RenderHTML("gitea-git", 16) // TODO: it could support user's customized icon in the future } @@ -269,7 +271,7 @@ func handleRepoEmptyOrBroken(ctx *context.Context) { } else if reallyEmpty { showEmpty = true // the repo is really empty updateContextRepoEmptyAndStatus(ctx, true, repo_model.RepositoryReady) - } else if branches, _, _ := ctx.Repo.GitRepo.GetBranches(0, 1); len(branches) == 0 { + } else if branches, _, _ := ctx.Repo.GitRepo.GetBranchNames(0, 1); len(branches) == 0 { showEmpty = true // it is not really empty, but there is no branch // at the moment, other repo units like "actions" are not able to handle such case, // so we just mark the repo as empty to prevent from displaying these units. @@ -302,8 +304,33 @@ func handleRepoEmptyOrBroken(ctx *context.Context) { ctx.Redirect(link) } +func handleRepoViewSubmodule(ctx *context.Context, submodule *git.SubModule) { + submoduleRepoURL, err := giturl.ParseRepositoryURL(ctx, submodule.URL) + if err != nil { + HandleGitError(ctx, "prepareToRenderDirOrFile: ParseRepositoryURL", err) + return + } + submoduleURL := giturl.MakeRepositoryWebLink(submoduleRepoURL) + if httplib.IsCurrentGiteaSiteURL(ctx, submoduleURL) { + ctx.RedirectToCurrentSite(submoduleURL) + } else { + // don't auto-redirect to external URL, to avoid open redirect or phishing + ctx.Data["NotFoundPrompt"] = submoduleURL + ctx.NotFound(nil) + } +} + func prepareToRenderDirOrFile(entry *git.TreeEntry) func(ctx *context.Context) { return func(ctx *context.Context) { + if entry.IsSubModule() { + submodule, err := ctx.Repo.Commit.GetSubModule(entry.Name()) + if err != nil { + HandleGitError(ctx, "prepareToRenderDirOrFile: GetSubModule", err) + return + } + handleRepoViewSubmodule(ctx, submodule) + return + } if entry.IsDir() { prepareToRenderDirectory(ctx) } else { @@ -343,12 +370,25 @@ func prepareHomeTreeSideBarSwitch(ctx *context.Context) { ctx.Data["UserSettingCodeViewShowFileTree"] = showFileTree } +func redirectSrcToRaw(ctx *context.Context) bool { + // GitHub redirects a tree path with "?raw=1" to the raw path + // It is useful to embed some raw contents into markdown files, + // then viewing the markdown in "src" path could embed the raw content correctly. + if ctx.Repo.TreePath != "" && ctx.FormBool("raw") { + ctx.Redirect(ctx.Repo.RepoLink + "/raw/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)) + return true + } + return false +} + // Home render repository home page func Home(ctx *context.Context) { if handleRepoHomeFeed(ctx) { return } - + if redirectSrcToRaw(ctx) { + return + } // Check whether the repo is viewable: not in migration, and the code unit should be enabled // Ideally the "feed" logic should be after this, but old code did so, so keep it as-is. checkHomeCodeViewable(ctx) diff --git a/routers/web/repo/view_home_test.go b/routers/web/repo/view_home_test.go new file mode 100644 index 0000000000..6264dba71c --- /dev/null +++ b/routers/web/repo/view_home_test.go @@ -0,0 +1,32 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/models/unittest" + git_module "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/contexttest" + + "github.com/stretchr/testify/assert" +) + +func TestViewHomeSubmoduleRedirect(t *testing.T) { + unittest.PrepareTestEnv(t) + + ctx, _ := contexttest.MockContext(t, "/user2/repo1/src/branch/master/test-submodule") + submodule := &git_module.SubModule{Path: "test-submodule", URL: setting.AppURL + "user2/repo-other.git"} + handleRepoViewSubmodule(ctx, submodule) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, "/user2/repo-other", ctx.Resp.Header().Get("Location")) + + ctx, _ = contexttest.MockContext(t, "/user2/repo1/src/branch/master/test-submodule") + submodule = &git_module.SubModule{Path: "test-submodule", URL: "https://other/user2/repo-other.git"} + handleRepoViewSubmodule(ctx, submodule) + // do not auto-redirect for external URLs, to avoid open redirect or phishing + assert.Equal(t, http.StatusNotFound, ctx.Resp.WrittenStatus()) +} diff --git a/routers/web/repo/view_readme.go b/routers/web/repo/view_readme.go index 48befe47f8..459cf0a616 100644 --- a/routers/web/repo/view_readme.go +++ b/routers/web/repo/view_readme.go @@ -69,7 +69,7 @@ func findReadmeFileInEntries(ctx *context.Context, entries []*git.TreeEntry, try if readmeFiles[i] == nil || base.NaturalSortLess(readmeFiles[i].Name(), entry.Blob().Name()) { if entry.IsLink() { target, err := entry.FollowLinks() - if err != nil && !git.IsErrBadLink(err) { + if err != nil && !git.IsErrSymlinkUnresolved(err) { return "", nil, err } else if target != nil && (target.IsExecutable() || target.IsRegular()) { readmeFiles[i] = entry @@ -162,7 +162,7 @@ func prepareToRenderReadmeFile(ctx *context.Context, subfolder string, readmeFil defer dataRc.Close() ctx.Data["FileIsText"] = fInfo.isTextFile - ctx.Data["FileName"] = path.Join(subfolder, readmeFile.Name()) + ctx.Data["FileTreePath"] = path.Join(subfolder, readmeFile.Name()) ctx.Data["FileSize"] = fInfo.fileSize ctx.Data["IsLFSFile"] = fInfo.isLFSFile diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index 20c8c2b406..d70760bc36 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -7,7 +7,6 @@ package repo import ( "bytes" gocontext "context" - "fmt" "io" "net/http" "net/url" @@ -581,7 +580,7 @@ func Wiki(ctx *context.Context) { wikiPath := entry.Name() if markup.DetectMarkupTypeByFileName(wikiPath) != markdown.MarkupName { ext := strings.ToUpper(filepath.Ext(wikiPath)) - ctx.Data["FormatWarning"] = fmt.Sprintf("%s rendering is not supported at the moment. Rendered as Markdown.", ext) + ctx.Data["FormatWarning"] = ext + " rendering is not supported at the moment. Rendered as Markdown." } // Get last change information. lastCommit, err := wikiRepo.GetCommitByPath(wikiPath) diff --git a/routers/web/repo/wiki_test.go b/routers/web/repo/wiki_test.go index e44cf46ba8..b5dfa9f856 100644 --- a/routers/web/repo/wiki_test.go +++ b/routers/web/repo/wiki_test.go @@ -71,7 +71,7 @@ func assertPagesMetas(t *testing.T, expectedNames []string, metas any) { require.Len(t, pageMetas, len(expectedNames)) for i, pageMeta := range pageMetas { - assert.EqualValues(t, expectedNames[i], pageMeta.Name) + assert.Equal(t, expectedNames[i], pageMeta.Name) } } @@ -82,7 +82,7 @@ func TestWiki(t *testing.T) { ctx.SetPathParam("*", "Home") contexttest.LoadRepo(t, ctx, 1) Wiki(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.EqualValues(t, "Home", ctx.Data["Title"]) assertPagesMetas(t, []string{"Home", "Page With Image", "Page With Spaced Name", "Unescaped File"}, ctx.Data["Pages"]) @@ -90,7 +90,7 @@ func TestWiki(t *testing.T) { ctx.SetPathParam("*", "jpeg.jpg") contexttest.LoadRepo(t, ctx, 1) Wiki(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assert.Equal(t, "/user2/repo1/wiki/raw/jpeg.jpg", ctx.Resp.Header().Get("Location")) } @@ -100,7 +100,7 @@ func TestWikiPages(t *testing.T) { ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki/?action=_pages") contexttest.LoadRepo(t, ctx, 1) WikiPages(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assertPagesMetas(t, []string{"Home", "Page With Image", "Page With Spaced Name", "Unescaped File"}, ctx.Data["Pages"]) } @@ -111,7 +111,7 @@ func TestNewWiki(t *testing.T) { contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) NewWiki(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.EqualValues(t, ctx.Tr("repo.wiki.new_page"), ctx.Data["Title"]) } @@ -131,7 +131,7 @@ func TestNewWikiPost(t *testing.T) { Message: message, }) NewWikiPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assertWikiExists(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title)) assert.Equal(t, content, wikiContent(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title))) } @@ -149,7 +149,7 @@ func TestNewWikiPost_ReservedName(t *testing.T) { Message: message, }) NewWikiPost(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.EqualValues(t, ctx.Tr("repo.wiki.reserved_page", "_edit"), ctx.Flash.ErrorMsg) assertWikiNotExists(t, ctx.Repo.Repository, "_edit") } @@ -162,7 +162,7 @@ func TestEditWiki(t *testing.T) { contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) EditWiki(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.EqualValues(t, "Home", ctx.Data["Title"]) assert.Equal(t, wikiContent(t, ctx.Repo.Repository, "Home"), ctx.Data["content"]) @@ -171,7 +171,7 @@ func TestEditWiki(t *testing.T) { contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) EditWiki(ctx) - assert.EqualValues(t, http.StatusForbidden, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusForbidden, ctx.Resp.WrittenStatus()) } func TestEditWikiPost(t *testing.T) { @@ -190,7 +190,7 @@ func TestEditWikiPost(t *testing.T) { Message: message, }) EditWikiPost(ctx) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) assertWikiExists(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title)) assert.Equal(t, content, wikiContent(t, ctx.Repo.Repository, wiki_service.UserTitleToWebPath("", title))) if title != "Home" { @@ -206,7 +206,7 @@ func TestDeleteWikiPagePost(t *testing.T) { contexttest.LoadUser(t, ctx, 2) contexttest.LoadRepo(t, ctx, 1) DeleteWikiPagePost(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assertWikiNotExists(t, ctx.Repo.Repository, "Home") } @@ -228,10 +228,10 @@ func TestWikiRaw(t *testing.T) { contexttest.LoadRepo(t, ctx, 1) WikiRaw(ctx) if filetype == "" { - assert.EqualValues(t, http.StatusNotFound, ctx.Resp.WrittenStatus(), "filepath: %s", filepath) + assert.Equal(t, http.StatusNotFound, ctx.Resp.WrittenStatus(), "filepath: %s", filepath) } else { - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus(), "filepath: %s", filepath) - assert.EqualValues(t, filetype, ctx.Resp.Header().Get("Content-Type"), "filepath: %s", filepath) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus(), "filepath: %s", filepath) + assert.Equal(t, filetype, ctx.Resp.Header().Get("Content-Type"), "filepath: %s", filepath) } } } diff --git a/routers/web/shared/actions/runners.go b/routers/web/shared/actions/runners.go index 444bd960db..a87f6ce4dc 100644 --- a/routers/web/shared/actions/runners.go +++ b/routers/web/shared/actions/runners.go @@ -57,9 +57,8 @@ func getRunnersCtx(ctx *context.Context) (*runnersCtx, error) { } if ctx.Data["PageIsOrgSettings"] == true { - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return nil, nil } return &runnersCtx{ diff --git a/routers/web/shared/actions/variables.go b/routers/web/shared/actions/variables.go index 9cc1676d7b..a43c2c2690 100644 --- a/routers/web/shared/actions/variables.go +++ b/routers/web/shared/actions/variables.go @@ -49,9 +49,8 @@ func getVariablesCtx(ctx *context.Context) (*variablesCtx, error) { } if ctx.Data["PageIsOrgSettings"] == true { - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return nil, nil } return &variablesCtx{ diff --git a/routers/web/shared/issue/issue_label.go b/routers/web/shared/issue/issue_label.go index eacea36b02..e2eeaaf0af 100644 --- a/routers/web/shared/issue/issue_label.go +++ b/routers/web/shared/issue/issue_label.go @@ -14,14 +14,18 @@ import ( ) // PrepareFilterIssueLabels reads the "labels" query parameter, sets `ctx.Data["Labels"]` and `ctx.Data["SelectLabels"]` -func PrepareFilterIssueLabels(ctx *context.Context, repoID int64, owner *user_model.User) (labelIDs []int64) { +func PrepareFilterIssueLabels(ctx *context.Context, repoID int64, owner *user_model.User) (ret struct { + AllLabels []*issues_model.Label + SelectedLabelIDs []int64 +}, +) { // 1,-2 means including label 1 and excluding label 2 // 0 means issues with no label // blank means labels will not be filtered for issues selectLabels := ctx.FormString("labels") if selectLabels != "" { var err error - labelIDs, err = base.StringsToInt64s(strings.Split(selectLabels, ",")) + ret.SelectedLabelIDs, err = base.StringsToInt64s(strings.Split(selectLabels, ",")) if err != nil { ctx.Flash.Error(ctx.Tr("invalid_data", selectLabels), true) } @@ -32,7 +36,7 @@ func PrepareFilterIssueLabels(ctx *context.Context, repoID int64, owner *user_mo repoLabels, err := issues_model.GetLabelsByRepoID(ctx, repoID, "", db.ListOptions{}) if err != nil { ctx.ServerError("GetLabelsByRepoID", err) - return nil + return ret } allLabels = append(allLabels, repoLabels...) } @@ -41,14 +45,14 @@ func PrepareFilterIssueLabels(ctx *context.Context, repoID int64, owner *user_mo orgLabels, err := issues_model.GetLabelsByOrgID(ctx, owner.ID, "", db.ListOptions{}) if err != nil { ctx.ServerError("GetLabelsByOrgID", err) - return nil + return ret } allLabels = append(allLabels, orgLabels...) } // Get the exclusive scope for every label ID - labelExclusiveScopes := make([]string, 0, len(labelIDs)) - for _, labelID := range labelIDs { + labelExclusiveScopes := make([]string, 0, len(ret.SelectedLabelIDs)) + for _, labelID := range ret.SelectedLabelIDs { foundExclusiveScope := false for _, label := range allLabels { if label.ID == labelID || label.ID == -labelID { @@ -63,9 +67,10 @@ func PrepareFilterIssueLabels(ctx *context.Context, repoID int64, owner *user_mo } for _, l := range allLabels { - l.LoadSelectedLabelsAfterClick(labelIDs, labelExclusiveScopes) + l.LoadSelectedLabelsAfterClick(ret.SelectedLabelIDs, labelExclusiveScopes) } ctx.Data["Labels"] = allLabels ctx.Data["SelectLabels"] = selectLabels - return labelIDs + ret.AllLabels = allLabels + return ret } diff --git a/routers/web/shared/user/header.go b/routers/web/shared/user/header.go index 62b146c7f3..48a5d58ea4 100644 --- a/routers/web/shared/user/header.go +++ b/routers/web/shared/user/header.go @@ -24,19 +24,8 @@ import ( "code.gitea.io/gitea/services/context" ) -// prepareContextForCommonProfile store some common data into context data for user's profile related pages (including the nav menu) -// It is designed to be fast and safe to be called multiple times in one request -func prepareContextForCommonProfile(ctx *context.Context) { - ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled - ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled - ctx.Data["EnableFeed"] = setting.Other.EnableFeed - ctx.Data["FeedURL"] = ctx.ContextUser.HomeLink() -} - -// PrepareContextForProfileBigAvatar set the context for big avatar view on the profile page -func PrepareContextForProfileBigAvatar(ctx *context.Context) { - prepareContextForCommonProfile(ctx) - +// prepareContextForProfileBigAvatar set the context for big avatar view on the profile page +func prepareContextForProfileBigAvatar(ctx *context.Context) { ctx.Data["IsFollowing"] = ctx.Doer != nil && user_model.IsFollowing(ctx, ctx.Doer.ID, ctx.ContextUser.ID) ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail && ctx.ContextUser.Email != "" && ctx.IsSigned && !ctx.ContextUser.KeepEmailPrivate if setting.Service.UserLocationMapURL != "" { @@ -138,16 +127,44 @@ func FindOwnerProfileReadme(ctx *context.Context, doer *user_model.User, optProf return profileDbRepo, profileReadmeBlob } -func RenderUserHeader(ctx *context.Context) { - prepareContextForCommonProfile(ctx) - - _, profileReadmeBlob := FindOwnerProfileReadme(ctx, ctx.Doer) - ctx.Data["HasUserProfileReadme"] = profileReadmeBlob != nil +type PrepareOwnerHeaderResult struct { + ProfilePublicRepo *repo_model.Repository + ProfilePublicReadmeBlob *git.Blob + ProfilePrivateRepo *repo_model.Repository + ProfilePrivateReadmeBlob *git.Blob + HasOrgProfileReadme bool } -func LoadHeaderCount(ctx *context.Context) error { - prepareContextForCommonProfile(ctx) +const ( + RepoNameProfilePrivate = ".profile-private" + RepoNameProfile = ".profile" +) +func RenderUserOrgHeader(ctx *context.Context) (result *PrepareOwnerHeaderResult, err error) { + ctx.Data["IsPackageEnabled"] = setting.Packages.Enabled + ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled + ctx.Data["EnableFeed"] = setting.Other.EnableFeed + ctx.Data["FeedURL"] = ctx.ContextUser.HomeLink() + + if err := loadHeaderCount(ctx); err != nil { + return nil, err + } + + result = &PrepareOwnerHeaderResult{} + if ctx.ContextUser.IsOrganization() { + result.ProfilePublicRepo, result.ProfilePublicReadmeBlob = FindOwnerProfileReadme(ctx, ctx.Doer) + result.ProfilePrivateRepo, result.ProfilePrivateReadmeBlob = FindOwnerProfileReadme(ctx, ctx.Doer, RepoNameProfilePrivate) + result.HasOrgProfileReadme = result.ProfilePublicReadmeBlob != nil || result.ProfilePrivateReadmeBlob != nil + ctx.Data["HasOrgProfileReadme"] = result.HasOrgProfileReadme // many pages need it to show the "overview" tab + } else { + _, profileReadmeBlob := FindOwnerProfileReadme(ctx, ctx.Doer) + ctx.Data["HasUserProfileReadme"] = profileReadmeBlob != nil + prepareContextForProfileBigAvatar(ctx) + } + return result, nil +} + +func loadHeaderCount(ctx *context.Context) error { repoCount, err := repo_model.CountRepository(ctx, &repo_model.SearchRepoOptions{ Actor: ctx.Doer, OwnerID: ctx.ContextUser.ID, @@ -178,29 +195,3 @@ func LoadHeaderCount(ctx *context.Context) error { return nil } - -const ( - RepoNameProfilePrivate = ".profile-private" - RepoNameProfile = ".profile" -) - -type PrepareOrgHeaderResult struct { - ProfilePublicRepo *repo_model.Repository - ProfilePublicReadmeBlob *git.Blob - ProfilePrivateRepo *repo_model.Repository - ProfilePrivateReadmeBlob *git.Blob - HasOrgProfileReadme bool -} - -func PrepareOrgHeader(ctx *context.Context) (result *PrepareOrgHeaderResult, err error) { - if err = LoadHeaderCount(ctx); err != nil { - return nil, err - } - - result = &PrepareOrgHeaderResult{} - result.ProfilePublicRepo, result.ProfilePublicReadmeBlob = FindOwnerProfileReadme(ctx, ctx.Doer) - result.ProfilePrivateRepo, result.ProfilePrivateReadmeBlob = FindOwnerProfileReadme(ctx, ctx.Doer, RepoNameProfilePrivate) - result.HasOrgProfileReadme = result.ProfilePublicReadmeBlob != nil || result.ProfilePrivateReadmeBlob != nil - ctx.Data["HasOrgProfileReadme"] = result.HasOrgProfileReadme // many pages need it to show the "overview" tab - return result, nil -} diff --git a/routers/web/user/code.go b/routers/web/user/code.go index f9aa58b877..f2153c6d54 100644 --- a/routers/web/user/code.go +++ b/routers/web/user/code.go @@ -26,11 +26,8 @@ func CodeSearch(ctx *context.Context) { ctx.Redirect(ctx.ContextUser.HomeLink()) return } - shared_user.PrepareContextForProfileBigAvatar(ctx) - shared_user.RenderUserHeader(ctx) - - if err := shared_user.LoadHeaderCount(ctx); err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 44e2a5ec71..77f9cb8cca 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -617,9 +617,10 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { return 0 } reviewTyp := issues_model.ReviewTypeApprove - if typ == "reject" { + switch typ { + case "reject": reviewTyp = issues_model.ReviewTypeReject - } else if typ == "waiting" { + case "waiting": reviewTyp = issues_model.ReviewTypeRequest } for _, count := range counts { @@ -698,7 +699,7 @@ func ShowGPGKeys(ctx *context.Context) { headers := make(map[string]string) if len(failedEntitiesID) > 0 { // If some key need re-import to be exported - headers["Note"] = fmt.Sprintf("The keys with the following IDs couldn't be exported and need to be reuploaded %s", strings.Join(failedEntitiesID, ", ")) + headers["Note"] = "The keys with the following IDs couldn't be exported and need to be reuploaded " + strings.Join(failedEntitiesID, ", ") } else if len(entities) == 0 { headers["Note"] = "This user hasn't uploaded any GPG keys." } diff --git a/routers/web/user/home_test.go b/routers/web/user/home_test.go index b2c8ad98ba..2a8a8812e3 100644 --- a/routers/web/user/home_test.go +++ b/routers/web/user/home_test.go @@ -37,15 +37,15 @@ func TestArchivedIssues(t *testing.T) { NumIssues[repo.ID] = repo.NumIssues } assert.False(t, IsArchived[50]) - assert.EqualValues(t, 1, NumIssues[50]) + assert.Equal(t, 1, NumIssues[50]) assert.True(t, IsArchived[51]) - assert.EqualValues(t, 1, NumIssues[51]) + assert.Equal(t, 1, NumIssues[51]) // Act Issues(ctx) // Assert: One Issue (ID 30) from one Repo (ID 50) is retrieved, while nothing from archived Repo 51 is retrieved - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.Len(t, ctx.Data["Issues"], 1) } @@ -58,7 +58,7 @@ func TestIssues(t *testing.T) { contexttest.LoadUser(t, ctx, 2) ctx.Req.Form.Set("state", "closed") Issues(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.EqualValues(t, true, ctx.Data["IsShowClosed"]) assert.Len(t, ctx.Data["Issues"], 1) @@ -72,7 +72,7 @@ func TestPulls(t *testing.T) { contexttest.LoadUser(t, ctx, 2) ctx.Req.Form.Set("state", "open") Pulls(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.Len(t, ctx.Data["Issues"], 5) } @@ -87,7 +87,7 @@ func TestMilestones(t *testing.T) { ctx.Req.Form.Set("state", "closed") ctx.Req.Form.Set("sort", "furthestduedate") Milestones(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.EqualValues(t, map[int64]int64{1: 1}, ctx.Data["Counts"]) assert.EqualValues(t, true, ctx.Data["IsShowClosed"]) assert.EqualValues(t, "furthestduedate", ctx.Data["SortType"]) @@ -107,7 +107,7 @@ func TestMilestonesForSpecificRepo(t *testing.T) { ctx.Req.Form.Set("state", "closed") ctx.Req.Form.Set("sort", "furthestduedate") Milestones(ctx) - assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) assert.EqualValues(t, map[int64]int64{1: 1}, ctx.Data["Counts"]) assert.EqualValues(t, true, ctx.Data["IsShowClosed"]) assert.EqualValues(t, "furthestduedate", ctx.Data["SortType"]) diff --git a/routers/web/user/notification.go b/routers/web/user/notification.go index f0c4390852..89f3c6956f 100644 --- a/routers/web/user/notification.go +++ b/routers/web/user/notification.go @@ -308,9 +308,10 @@ func NotificationSubscriptions(ctx *context.Context) { return 0 } reviewTyp := issues_model.ReviewTypeApprove - if typ == "reject" { + switch typ { + case "reject": reviewTyp = issues_model.ReviewTypeReject - } else if typ == "waiting" { + case "waiting": reviewTyp = issues_model.ReviewTypeRequest } for _, count := range counts { diff --git a/routers/web/user/package.go b/routers/web/user/package.go index c01bc96e2b..e96f5a04ea 100644 --- a/routers/web/user/package.go +++ b/routers/web/user/package.go @@ -42,7 +42,10 @@ const ( // ListPackages displays a list of all packages of the context user func ListPackages(ctx *context.Context) { - shared_user.PrepareContextForProfileBigAvatar(ctx) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } page := ctx.FormInt("page") if page <= 1 { page = 1 @@ -94,8 +97,6 @@ func ListPackages(ctx *context.Context) { return } - shared_user.RenderUserHeader(ctx) - ctx.Data["Title"] = ctx.Tr("packages.title") ctx.Data["IsPackagesPage"] = true ctx.Data["Query"] = query @@ -106,9 +107,8 @@ func ListPackages(ctx *context.Context) { ctx.Data["Total"] = total ctx.Data["RepositoryAccessMap"] = repositoryAccessMap - err = shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } @@ -126,11 +126,9 @@ func ListPackages(ctx *context.Context) { ctx.Data["IsOrganizationOwner"] = false } } - pager := context.NewPagination(int(total), setting.UI.PackagesPagingNum, page, 5) pager.AddParamFromRequest(ctx.Req) ctx.Data["Page"] = pager - ctx.HTML(http.StatusOK, tplPackagesList) } @@ -164,16 +162,17 @@ func RedirectToLastVersion(ctx *context.Context) { ctx.ServerError("GetPackageDescriptor", err) return } - ctx.Redirect(pd.VersionWebLink()) } // ViewPackageVersion displays a single package version func ViewPackageVersion(ctx *context.Context) { + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } + pd := ctx.Package.Descriptor - - shared_user.RenderUserHeader(ctx) - ctx.Data["Title"] = pd.Package.Name ctx.Data["IsPackagesPage"] = true ctx.Data["PackageDescriptor"] = pd @@ -301,19 +300,16 @@ func ViewPackageVersion(ctx *context.Context) { hasRepositoryAccess = permission.HasAnyUnitAccess() } ctx.Data["HasRepositoryAccess"] = hasRepositoryAccess - - err = shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) - return - } - ctx.HTML(http.StatusOK, tplPackagesView) } // ListPackageVersions lists all versions of a package func ListPackageVersions(ctx *context.Context) { - shared_user.PrepareContextForProfileBigAvatar(ctx) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } + p, err := packages_model.GetPackageByName(ctx, ctx.Package.Owner.ID, packages_model.Type(ctx.PathParam("type")), ctx.PathParam("name")) if err != nil { if err == packages_model.ErrPackageNotExist { @@ -336,8 +332,6 @@ func ListPackageVersions(ctx *context.Context) { query := ctx.FormTrim("q") sort := ctx.FormTrim("sort") - shared_user.RenderUserHeader(ctx) - ctx.Data["Title"] = ctx.Tr("packages.title") ctx.Data["IsPackagesPage"] = true ctx.Data["PackageDescriptor"] = &packages_model.PackageDescriptor{ @@ -393,12 +387,6 @@ func ListPackageVersions(ctx *context.Context) { ctx.Data["Total"] = total - err = shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) - return - } - pager := context.NewPagination(int(total), setting.UI.PackagesPagingNum, page, 5) pager.AddParamFromRequest(ctx.Req) ctx.Data["Page"] = pager @@ -410,7 +398,10 @@ func ListPackageVersions(ctx *context.Context) { func PackageSettings(ctx *context.Context) { pd := ctx.Package.Descriptor - shared_user.RenderUserHeader(ctx) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } ctx.Data["Title"] = pd.Package.Name ctx.Data["IsPackagesPage"] = true @@ -423,12 +414,6 @@ func PackageSettings(ctx *context.Context) { ctx.Data["Repos"] = repos ctx.Data["CanWritePackages"] = ctx.Package.AccessMode >= perm.AccessModeWrite || ctx.IsUserSiteAdmin() - err := shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) - return - } - ctx.HTML(http.StatusOK, tplPackagesSettings) } diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 39f066a53c..ee19665109 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -78,8 +78,15 @@ func userProfile(ctx *context.Context) { showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) prepareUserProfileTabData(ctx, showPrivate, profileDbRepo, profileReadmeBlob) - // call PrepareContextForProfileBigAvatar later to avoid re-querying the NumFollowers & NumFollowing - shared_user.PrepareContextForProfileBigAvatar(ctx) + + // prepare the user nav header data after "prepareUserProfileTabData" to avoid re-querying the NumFollowers & NumFollowing + // because ctx.Data["NumFollowers"] and "NumFollowing" logic duplicates in both of them + // and the "profile readme" related logic also duplicates in both of FindOwnerProfileReadme and RenderUserOrgHeader + // TODO: it is a bad design and should be refactored later, + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } ctx.HTML(http.StatusOK, tplProfile) } @@ -302,9 +309,8 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb ctx.Data["Repos"] = repos ctx.Data["Total"] = total - err = shared_user.LoadHeaderCount(ctx) - if err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } @@ -328,9 +334,11 @@ func ActionUserFollow(ctx *context.Context) { ctx.HTTPError(http.StatusBadRequest, fmt.Sprintf("Action %q failed", ctx.FormString("action"))) return } - + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } if ctx.ContextUser.IsIndividual() { - shared_user.PrepareContextForProfileBigAvatar(ctx) ctx.HTML(http.StatusOK, tplProfileBigAvatar) return } else if ctx.ContextUser.IsOrganization() { diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index 94577832a9..b124d5e1de 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -6,7 +6,6 @@ package setting import ( "errors" - "fmt" "net/http" "time" @@ -37,7 +36,7 @@ const ( // Account renders change user's password, user's email and user suicide page func Account(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials, setting.UserFeatureDeletion) && !setting.Service.EnableNotifyMail { - ctx.NotFound(fmt.Errorf("account setting are not allowed to be changed")) + ctx.NotFound(errors.New("account setting are not allowed to be changed")) return } @@ -54,7 +53,7 @@ func Account(ctx *context.Context) { // AccountPost response for change user's password func AccountPost(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { - ctx.NotFound(fmt.Errorf("password setting is not allowed to be changed")) + ctx.NotFound(errors.New("password setting is not allowed to be changed")) return } @@ -105,7 +104,7 @@ func AccountPost(ctx *context.Context) { // EmailPost response for change user's email func EmailPost(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { - ctx.NotFound(fmt.Errorf("emails are not allowed to be changed")) + ctx.NotFound(errors.New("emails are not allowed to be changed")) return } @@ -239,7 +238,7 @@ func EmailPost(ctx *context.Context) { // DeleteEmail response for delete user's email func DeleteEmail(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) { - ctx.NotFound(fmt.Errorf("emails are not allowed to be changed")) + ctx.NotFound(errors.New("emails are not allowed to be changed")) return } email, err := user_model.GetEmailAddressByID(ctx, ctx.Doer.ID, ctx.FormInt64("id")) diff --git a/routers/web/user/setting/account_test.go b/routers/web/user/setting/account_test.go index 13caa33771..9b8cffc868 100644 --- a/routers/web/user/setting/account_test.go +++ b/routers/web/user/setting/account_test.go @@ -95,7 +95,7 @@ func TestChangePassword(t *testing.T) { AccountPost(ctx) assert.Contains(t, ctx.Flash.ErrorMsg, req.Message) - assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) }) } } diff --git a/routers/web/user/setting/applications.go b/routers/web/user/setting/applications.go index 1f6c97a5cc..9c43ddd3ea 100644 --- a/routers/web/user/setting/applications.go +++ b/routers/web/user/setting/applications.go @@ -43,8 +43,9 @@ func ApplicationsPost(ctx *context.Context) { _ = ctx.Req.ParseForm() var scopeNames []string + const accessTokenScopePrefix = "scope-" for k, v := range ctx.Req.Form { - if strings.HasPrefix(k, "scope-") { + if strings.HasPrefix(k, accessTokenScopePrefix) { scopeNames = append(scopeNames, v...) } } @@ -54,7 +55,7 @@ func ApplicationsPost(ctx *context.Context) { ctx.ServerError("GetScope", err) return } - if scope == "" || scope == auth_model.AccessTokenScopePublicOnly { + if !scope.HasPermissionScope() { ctx.Flash.Error(ctx.Tr("settings.at_least_one_permission"), true) } diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go index 17e32f5403..6b5a7a2e2a 100644 --- a/routers/web/user/setting/keys.go +++ b/routers/web/user/setting/keys.go @@ -5,7 +5,7 @@ package setting import ( - "fmt" + "errors" "net/http" asymkey_model "code.gitea.io/gitea/models/asymkey" @@ -26,7 +26,7 @@ const ( // Keys render user's SSH/GPG public keys page func Keys(ctx *context.Context) { if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys, setting.UserFeatureManageGPGKeys) { - ctx.NotFound(fmt.Errorf("keys setting is not allowed to be changed")) + ctx.NotFound(errors.New("keys setting is not allowed to be changed")) return } @@ -87,7 +87,7 @@ func KeysPost(ctx *context.Context) { ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "gpg": if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { - ctx.NotFound(fmt.Errorf("gpg keys setting is not allowed to be visited")) + ctx.NotFound(errors.New("gpg keys setting is not allowed to be visited")) return } @@ -168,7 +168,7 @@ func KeysPost(ctx *context.Context) { ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "ssh": if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { - ctx.NotFound(fmt.Errorf("ssh keys setting is not allowed to be visited")) + ctx.NotFound(errors.New("ssh keys setting is not allowed to be visited")) return } @@ -212,7 +212,7 @@ func KeysPost(ctx *context.Context) { ctx.Redirect(setting.AppSubURL + "/user/settings/keys") case "verify_ssh": if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { - ctx.NotFound(fmt.Errorf("ssh keys setting is not allowed to be visited")) + ctx.NotFound(errors.New("ssh keys setting is not allowed to be visited")) return } @@ -249,7 +249,7 @@ func DeleteKey(ctx *context.Context) { switch ctx.FormString("type") { case "gpg": if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) { - ctx.NotFound(fmt.Errorf("gpg keys setting is not allowed to be visited")) + ctx.NotFound(errors.New("gpg keys setting is not allowed to be visited")) return } if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, ctx.FormInt64("id")); err != nil { @@ -259,7 +259,7 @@ func DeleteKey(ctx *context.Context) { } case "ssh": if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) { - ctx.NotFound(fmt.Errorf("ssh keys setting is not allowed to be visited")) + ctx.NotFound(errors.New("ssh keys setting is not allowed to be visited")) return } diff --git a/routers/web/user/setting/oauth2_common.go b/routers/web/user/setting/oauth2_common.go index d4da468a85..f460acce10 100644 --- a/routers/web/user/setting/oauth2_common.go +++ b/routers/web/user/setting/oauth2_common.go @@ -28,8 +28,8 @@ func (oa *OAuth2CommonHandlers) renderEditPage(ctx *context.Context) { ctx.Data["FormActionPath"] = fmt.Sprintf("%s/%d", oa.BasePathEditPrefix, app.ID) if ctx.ContextUser != nil && ctx.ContextUser.IsOrganization() { - if err := shared_user.LoadHeaderCount(ctx); err != nil { - ctx.ServerError("LoadHeaderCount", err) + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) return } } diff --git a/routers/web/web.go b/routers/web/web.go index 455d0a3a0d..fcddcad1b1 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -178,7 +178,7 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Cont return } - if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == "POST" { + if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == http.MethodPost { ctx.Csrf.Validate(ctx) if ctx.Written() { return @@ -283,23 +283,23 @@ func Routes() *web.Router { mid = append(mid, goGet) mid = append(mid, common.PageTmplFunctions) - others := web.NewRouter() - others.Use(mid...) - registerRoutes(others) - routes.Mount("", others) + webRoutes := web.NewRouter() + webRoutes.Use(mid...) + webRoutes.Group("", func() { registerWebRoutes(webRoutes) }, common.BlockExpensive()) + routes.Mount("", webRoutes) return routes } var optSignInIgnoreCsrf = verifyAuthWithOptions(&common.VerifyOptions{DisableCSRF: true}) -// registerRoutes register routes -func registerRoutes(m *web.Router) { +// registerWebRoutes register routes +func registerWebRoutes(m *web.Router) { // required to be signed in or signed out reqSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true}) reqSignOut := verifyAuthWithOptions(&common.VerifyOptions{SignOutRequired: true}) // optional sign in (if signed in, use the user as doer, if not, no doer) - optSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView}) - optExploreSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView}) + optSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInViewStrict}) + optExploreSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInViewStrict || setting.Service.Explore.RequireSigninView}) validation.AddBindingRules() @@ -854,13 +854,13 @@ func registerRoutes(m *web.Router) { 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: + switch ctx.ContextUser.Visibility { + case structs.VisibleTypePrivate: if ctx.Doer == nil || (ctx.ContextUser.ID != ctx.Doer.ID && !ctx.Doer.IsAdmin) { ctx.NotFound(nil) return } - case ctx.ContextUser.Visibility == structs.VisibleTypeLimited: + case structs.VisibleTypeLimited: if ctx.Doer == nil { ctx.NotFound(nil) return @@ -1078,6 +1078,8 @@ func registerRoutes(m *web.Router) { m.Post("/avatar", web.Bind(forms.AvatarForm{}), repo_setting.SettingsAvatar) m.Post("/avatar/delete", repo_setting.SettingsDeleteAvatar) + m.Combo("/public_access").Get(repo_setting.PublicAccess).Post(repo_setting.PublicAccessPost) + m.Group("/collaboration", func() { m.Combo("").Get(repo_setting.Collaboration).Post(repo_setting.CollaborationPost) m.Post("/access_mode", repo_setting.ChangeCollaborationAccessMode) @@ -1209,7 +1211,7 @@ func registerRoutes(m *web.Router) { m.Get("/comments/{id}/attachments", repo.GetCommentAttachments) m.Get("/labels", repo.RetrieveLabelsForList, repo.Labels) m.Get("/milestones", repo.Milestones) - m.Get("/milestone/{id}", context.RepoRef(), repo.MilestoneIssuesAndPulls) + m.Get("/milestone/{id}", repo.MilestoneIssuesAndPulls) m.Get("/issues/suggestions", repo.IssueSuggestions) }, optSignIn, context.RepoAssignment, reqRepoIssuesOrPullsReader) // issue/pull attachments, labels, milestones // end "/{username}/{reponame}": view milestone, label, issue, pull, etc @@ -1223,9 +1225,9 @@ func registerRoutes(m *web.Router) { m.Group("/{username}/{reponame}", func() { // edit issues, pulls, labels, milestones, etc m.Group("/issues", func() { m.Group("/new", func() { - m.Combo("").Get(context.RepoRef(), repo.NewIssue). + m.Combo("").Get(repo.NewIssue). Post(web.Bind(forms.CreateIssueForm{}), repo.NewIssuePost) - m.Get("/choose", context.RepoRef(), repo.NewIssueChooseTemplate) + m.Get("/choose", repo.NewIssueChooseTemplate) }) m.Get("/search", repo.SearchRepoIssuesJSON) }, reqUnitIssuesReader) @@ -1288,7 +1290,7 @@ func registerRoutes(m *web.Router) { m.Post("/edit", web.Bind(forms.CreateLabelForm{}), repo.UpdateLabel) m.Post("/delete", repo.DeleteLabel) m.Post("/initialize", web.Bind(forms.InitializeLabelsForm{}), repo.InitializeLabels) - }, reqRepoIssuesOrPullsWriter, context.RepoRef()) + }, reqRepoIssuesOrPullsWriter) m.Group("/milestones", func() { m.Combo("/new").Get(repo.NewMilestone). @@ -1297,7 +1299,7 @@ func registerRoutes(m *web.Router) { m.Post("/{id}/edit", web.Bind(forms.CreateMilestoneForm{}), repo.EditMilestonePost) m.Post("/{id}/{action}", repo.ChangeMilestoneStatus) m.Post("/delete", repo.DeleteMilestone) - }, reqRepoIssuesOrPullsWriter, context.RepoRef()) + }, reqRepoIssuesOrPullsWriter) // FIXME: many "pulls" requests are sent to "issues" endpoints incorrectly, need to move these routes to the proper place m.Group("/issues", func() { @@ -1375,7 +1377,7 @@ func registerRoutes(m *web.Router) { m.Post("/delete", repo.DeleteRelease) m.Post("/attachments", repo.UploadReleaseAttachment) m.Post("/attachments/remove", repo.DeleteAttachment) - }, reqSignIn, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, context.RepoRef()) + }, reqSignIn, context.RepoMustNotBeArchived(), reqRepoReleaseWriter) m.Group("/releases", func() { m.Get("/edit/*", repo.EditRelease) m.Post("/edit/*", web.Bind(forms.EditReleaseForm{}), repo.EditReleasePost) @@ -1488,7 +1490,7 @@ func registerRoutes(m *web.Router) { }) m.Group("/recent-commits", func() { m.Get("", repo.RecentCommits) - m.Get("/data", repo.RecentCommitsData) + m.Get("/data", repo.CodeFrequencyData) // "recent-commits" also uses the same data as "code-frequency" }) }, reqUnitCodeReader) }, @@ -1504,19 +1506,19 @@ func registerRoutes(m *web.Router) { m.Get(".diff", repo.DownloadPullDiff) m.Get(".patch", repo.DownloadPullPatch) m.Group("/commits", func() { - m.Get("", context.RepoRef(), repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits) - m.Get("/list", context.RepoRef(), repo.GetPullCommits) - m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit) + m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits) + m.Get("/list", repo.GetPullCommits) + m.Get("/{sha:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit) }) m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), repo.MergePullRequest) m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest) m.Post("/update", repo.UpdatePullRequest) m.Post("/set_allow_maintainer_edit", web.Bind(forms.UpdateAllowEditsForm{}), repo.SetAllowEdits) - m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest) + m.Post("/cleanup", context.RepoMustNotBeArchived(), repo.CleanUpPullRequest) m.Group("/files", func() { - m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForAllCommitsOfPr) - m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit) - m.Get("/{shaFrom:[a-f0-9]{7,40}}..{shaTo:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange) + m.Get("", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForAllCommitsOfPr) + m.Get("/{sha:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit) + m.Get("/{shaFrom:[a-f0-9]{7,40}}..{shaTo:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange) m.Group("/reviews", func() { m.Get("/new_comment", repo.RenderNewCodeCommentForm) m.Post("/comments", web.Bind(forms.CodeCommentForm{}), repo.SetShowOutdatedComments, repo.CreateCodeComment) @@ -1603,7 +1605,7 @@ func registerRoutes(m *web.Router) { m.Get("/tree/*", repo.RedirectRepoTreeToSrc) // redirect "/owner/repo/tree/*" requests to "/owner/repo/src/*" m.Get("/blob/*", repo.RedirectRepoBlobToCommit) // redirect "/owner/repo/blob/*" requests to "/owner/repo/src/commit/*" - m.Get("/forks", context.RepoRef(), repo.Forks) + m.Get("/forks", repo.Forks) m.Get("/commit/{sha:([a-f0-9]{7,64})}.{ext:patch|diff}", repo.MustBeNotEmpty, repo.RawDiff) m.Post("/lastcommit/*", context.RepoRefByType(git.RefTypeCommit), repo.LastCommit) }, optSignIn, context.RepoAssignment, reqUnitCodeReader) diff --git a/routers/web/webfinger.go b/routers/web/webfinger.go index afcfdc8252..a4c9bf902b 100644 --- a/routers/web/webfinger.go +++ b/routers/web/webfinger.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" "net/url" + "strconv" "strings" user_model "code.gitea.io/gitea/models/user" @@ -85,10 +86,10 @@ func WebfingerQuery(ctx *context.Context) { aliases := []string{ u.HTMLURL(), - appURL.String() + "api/v1/activitypub/user-id/" + fmt.Sprint(u.ID), + appURL.String() + "api/v1/activitypub/user-id/" + strconv.FormatInt(u.ID, 10), } if !u.KeepEmailPrivate { - aliases = append(aliases, fmt.Sprintf("mailto:%s", u.Email)) + aliases = append(aliases, "mailto:"+u.Email) } links := []*webfingerLink{ @@ -104,7 +105,7 @@ func WebfingerQuery(ctx *context.Context) { { Rel: "self", Type: "application/activity+json", - Href: appURL.String() + "api/v1/activitypub/user-id/" + fmt.Sprint(u.ID), + Href: appURL.String() + "api/v1/activitypub/user-id/" + strconv.FormatInt(u.ID, 10), }, { Rel: "http://openid.net/specs/connect/1.0/issuer", diff --git a/services/actions/auth.go b/services/actions/auth.go index 1ef21f6e0e..12a8fba53f 100644 --- a/services/actions/auth.go +++ b/services/actions/auth.go @@ -4,6 +4,7 @@ package actions import ( + "errors" "fmt" "net/http" "strings" @@ -80,7 +81,7 @@ func ParseAuthorizationToken(req *http.Request) (int64, error) { parts := strings.SplitN(h, " ", 2) if len(parts) != 2 { log.Error("split token failed: %s", h) - return 0, fmt.Errorf("split token failed") + return 0, errors.New("split token failed") } return TokenToTaskID(parts[1]) @@ -100,7 +101,7 @@ func TokenToTaskID(token string) (int64, error) { c, ok := parsedToken.Claims.(*actionsClaims) if !parsedToken.Valid || !ok { - return 0, fmt.Errorf("invalid token claim") + return 0, errors.New("invalid token claim") } return c.TaskID, nil diff --git a/services/actions/auth_test.go b/services/actions/auth_test.go index 85e7409105..38d0ba7f82 100644 --- a/services/actions/auth_test.go +++ b/services/actions/auth_test.go @@ -18,7 +18,7 @@ func TestCreateAuthorizationToken(t *testing.T) { var taskID int64 = 23 token, err := CreateAuthorizationToken(taskID, 1, 2) assert.NoError(t, err) - assert.NotEqual(t, "", token) + assert.NotEmpty(t, token) claims := jwt.MapClaims{} _, err = jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) { return setting.GetGeneralTokenSigningSecret(), nil @@ -44,7 +44,7 @@ func TestParseAuthorizationToken(t *testing.T) { var taskID int64 = 23 token, err := CreateAuthorizationToken(taskID, 1, 2) assert.NoError(t, err) - assert.NotEqual(t, "", token) + assert.NotEmpty(t, token) headers := http.Header{} headers.Set("Authorization", "Bearer "+token) rTaskID, err := ParseAuthorizationToken(&http.Request{ diff --git a/services/actions/commit_status.go b/services/actions/commit_status.go index 94ab89a3b7..eb15d16061 100644 --- a/services/actions/commit_status.go +++ b/services/actions/commit_status.go @@ -5,6 +5,7 @@ package actions import ( "context" + "errors" "fmt" "path" @@ -51,7 +52,7 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er return fmt.Errorf("GetPushEventPayload: %w", err) } if payload.HeadCommit == nil { - return fmt.Errorf("head commit is missing in event payload") + return errors.New("head commit is missing in event payload") } sha = payload.HeadCommit.ID case // pull_request @@ -71,9 +72,9 @@ func createCommitStatus(ctx context.Context, job *actions_model.ActionRunJob) er return fmt.Errorf("GetPullRequestEventPayload: %w", err) } if payload.PullRequest == nil { - return fmt.Errorf("pull request is missing in event payload") + return errors.New("pull request is missing in event payload") } else if payload.PullRequest.Head == nil { - return fmt.Errorf("head of pull request is missing in event payload") + return errors.New("head of pull request is missing in event payload") } sha = payload.PullRequest.Head.Sha case webhook_module.HookEventRelease: diff --git a/services/actions/context.go b/services/actions/context.go index d14728fae4..2667e18337 100644 --- a/services/actions/context.go +++ b/services/actions/context.go @@ -6,6 +6,7 @@ package actions import ( "context" "fmt" + "strconv" actions_model "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" @@ -68,7 +69,7 @@ func GenerateGiteaContext(run *actions_model.ActionRun, job *actions_model.Actio "repositoryUrl": run.Repo.HTMLURL(), // string, The Git URL to the repository. For example, git://github.com/codertocat/hello-world.git. "retention_days": "", // string, The number of days that workflow run logs and artifacts are kept. "run_id": "", // string, A unique number for each workflow run within a repository. This number does not change if you re-run the workflow run. - "run_number": fmt.Sprint(run.Index), // string, A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run. + "run_number": strconv.FormatInt(run.Index, 10), // string, A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run. "run_attempt": "", // string, A unique number for each attempt of a particular workflow run in a repository. This number begins at 1 for the workflow run's first attempt, and increments with each re-run. "secret_source": "Actions", // string, The source of a secret used in a workflow. Possible values are None, Actions, Dependabot, or Codespaces. "server_url": setting.AppURL, // string, The URL of the GitHub server. For example: https://github.com. @@ -83,8 +84,8 @@ func GenerateGiteaContext(run *actions_model.ActionRun, job *actions_model.Actio if job != nil { gitContext["job"] = job.JobID - gitContext["run_id"] = fmt.Sprint(job.RunID) - gitContext["run_attempt"] = fmt.Sprint(job.Attempt) + gitContext["run_id"] = strconv.FormatInt(job.RunID, 10) + gitContext["run_attempt"] = strconv.FormatInt(job.Attempt, 10) } return gitContext diff --git a/services/actions/task.go b/services/actions/task.go index 9c8198206a..6a547c1c12 100644 --- a/services/actions/task.go +++ b/services/actions/task.go @@ -5,6 +5,7 @@ package actions import ( "context" + "errors" "fmt" actions_model "code.gitea.io/gitea/models/actions" @@ -39,7 +40,7 @@ func PickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv if err != nil { return nil, false, err } - return nil, false, fmt.Errorf("runner has been removed") + return nil, false, errors.New("runner has been removed") } } diff --git a/services/asymkey/commit.go b/services/asymkey/commit.go index df29133972..105782a93a 100644 --- a/services/asymkey/commit.go +++ b/services/asymkey/commit.go @@ -5,15 +5,19 @@ package asymkey import ( "context" + "fmt" "strings" asymkey_model "code.gitea.io/gitea/models/asymkey" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/cache" + "code.gitea.io/gitea/modules/cachegroup" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "github.com/42wim/sshsig" "github.com/ProtonMail/go-crypto/openpgp/packet" ) @@ -57,7 +61,7 @@ func ParseCommitWithSignatureCommitter(ctx context.Context, c *git.Commit, commi // If this a SSH signature handle it differently if strings.HasPrefix(c.Signature.Signature, "-----BEGIN SSH SIGNATURE-----") { - return asymkey_model.ParseCommitWithSSHSignature(ctx, c, committer) + return ParseCommitWithSSHSignature(ctx, c, committer) } // Parsing signature @@ -113,7 +117,7 @@ func ParseCommitWithSignatureCommitter(ctx context.Context, c *git.Commit, commi } } - committerEmailAddresses, _ := user_model.GetEmailAddresses(ctx, committer.ID) + committerEmailAddresses, _ := cache.GetWithContextCache(ctx, cachegroup.UserEmailAddresses, committer.ID, user_model.GetEmailAddresses) activated := false for _, e := range committerEmailAddresses { if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) { @@ -207,10 +211,9 @@ func checkKeyEmails(ctx context.Context, email string, keys ...*asymkey_model.GP } if key.Verified && key.OwnerID != 0 { if uid != key.OwnerID { - userEmails, _ = user_model.GetEmailAddresses(ctx, key.OwnerID) + userEmails, _ = cache.GetWithContextCache(ctx, cachegroup.UserEmailAddresses, key.OwnerID, user_model.GetEmailAddresses) uid = key.OwnerID - user = &user_model.User{ID: uid} - _, _ = user_model.GetUser(ctx, user) + user, _ = cache.GetWithContextCache(ctx, cachegroup.User, uid, user_model.GetUserByID) } for _, e := range userEmails { if e.IsActivated && (email == "" || strings.EqualFold(e.Email, email)) { @@ -229,10 +232,7 @@ func HashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload s if keyID == "" { return nil } - keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{ - KeyID: keyID, - IncludeSubKeys: true, - }) + keys, err := cache.GetWithContextCache(ctx, cachegroup.GPGKeyWithSubKeys, keyID, asymkey_model.FindGPGKeyWithSubKeys) if err != nil { log.Error("GetGPGKeysByKeyID: %v", err) return &asymkey_model.CommitVerification{ @@ -247,10 +247,7 @@ func HashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload s for _, key := range keys { var primaryKeys []*asymkey_model.GPGKey if key.PrimaryKeyID != "" { - primaryKeys, err = db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{ - KeyID: key.PrimaryKeyID, - IncludeSubKeys: true, - }) + primaryKeys, err = cache.GetWithContextCache(ctx, cachegroup.GPGKeyWithSubKeys, key.PrimaryKeyID, asymkey_model.FindGPGKeyWithSubKeys) if err != nil { log.Error("GetGPGKeysByKeyID: %v", err) return &asymkey_model.CommitVerification{ @@ -270,8 +267,8 @@ func HashAndVerifyForKeyID(ctx context.Context, sig *packet.Signature, payload s Name: name, Email: email, } - if key.OwnerID != 0 { - owner, err := user_model.GetUserByID(ctx, key.OwnerID) + if key.OwnerID > 0 { + owner, err := cache.GetWithContextCache(ctx, cachegroup.User, key.OwnerID, user_model.GetUserByID) if err == nil { signer = owner } else if !user_model.IsErrUserNotExist(err) { @@ -361,3 +358,65 @@ func VerifyWithGPGSettings(ctx context.Context, gpgSettings *git.GPGSettings, si } return nil } + +// ParseCommitWithSSHSignature check if signature is good against keystore. +func ParseCommitWithSSHSignature(ctx context.Context, c *git.Commit, committer *user_model.User) *asymkey_model.CommitVerification { + // Now try to associate the signature with the committer, if present + if committer.ID != 0 { + keys, err := db.Find[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{ + OwnerID: committer.ID, + NotKeytype: asymkey_model.KeyTypePrincipal, + }) + if err != nil { // Skipping failed to get ssh keys of user + log.Error("ListPublicKeys: %v", err) + return &asymkey_model.CommitVerification{ + CommittingUser: committer, + Verified: false, + Reason: "gpg.error.failed_retrieval_gpg_keys", + } + } + + committerEmailAddresses, err := cache.GetWithContextCache(ctx, cachegroup.UserEmailAddresses, committer.ID, user_model.GetEmailAddresses) + if err != nil { + log.Error("GetEmailAddresses: %v", err) + } + + activated := false + for _, e := range committerEmailAddresses { + if e.IsActivated && strings.EqualFold(e.Email, c.Committer.Email) { + activated = true + break + } + } + + for _, k := range keys { + if k.Verified && activated { + commitVerification := verifySSHCommitVerification(c.Signature.Signature, c.Signature.Payload, k, committer, committer, c.Committer.Email) + if commitVerification != nil { + return commitVerification + } + } + } + } + + return &asymkey_model.CommitVerification{ + CommittingUser: committer, + Verified: false, + Reason: asymkey_model.NoKeyFound, + } +} + +func verifySSHCommitVerification(sig, payload string, k *asymkey_model.PublicKey, committer, signer *user_model.User, email string) *asymkey_model.CommitVerification { + if err := sshsig.Verify(strings.NewReader(payload), []byte(sig), []byte(k.Content), "git"); err != nil { + return nil + } + + return &asymkey_model.CommitVerification{ // Everything is ok + CommittingUser: committer, + Verified: true, + Reason: fmt.Sprintf("%s / %s", signer.Name, k.Fingerprint), + SigningUser: signer, + SigningSSHKey: k, + SigningEmail: email, + } +} diff --git a/services/attachment/attachment_test.go b/services/attachment/attachment_test.go index 142bcfe629..65475836be 100644 --- a/services/attachment/attachment_test.go +++ b/services/attachment/attachment_test.go @@ -41,6 +41,6 @@ func TestUploadAttachment(t *testing.T) { attachment, err := repo_model.GetAttachmentByUUID(db.DefaultContext, attach.UUID) assert.NoError(t, err) - assert.EqualValues(t, user.ID, attachment.UploaderID) + assert.Equal(t, user.ID, attachment.UploaderID) assert.Equal(t, int64(0), attachment.DownloadCount) } diff --git a/services/auth/auth.go b/services/auth/auth.go index f7deeb4c50..fb6612290b 100644 --- a/services/auth/auth.go +++ b/services/auth/auth.go @@ -62,14 +62,14 @@ func (a *authPathDetector) isAPIPath() bool { // isAttachmentDownload check if request is a file download (GET) with URL to an attachment func (a *authPathDetector) isAttachmentDownload() bool { - return strings.HasPrefix(a.req.URL.Path, "/attachments/") && a.req.Method == "GET" + return strings.HasPrefix(a.req.URL.Path, "/attachments/") && a.req.Method == http.MethodGet } func (a *authPathDetector) isFeedRequest(req *http.Request) bool { if !setting.Other.EnableFeed { return false } - if req.Method != "GET" { + if req.Method != http.MethodGet { return false } return a.vars.feedPathRe.MatchString(req.URL.Path) || a.vars.feedRefPathRe.MatchString(req.URL.Path) diff --git a/services/auth/auth_test.go b/services/auth/auth_test.go index b8d3396163..c45f312c90 100644 --- a/services/auth/auth_test.go +++ b/services/auth/auth_test.go @@ -97,7 +97,7 @@ func Test_isGitRawOrLFSPath(t *testing.T) { defer test.MockVariableValue(&setting.LFS.StartServer)() for _, tt := range tests { t.Run(tt.path, func(t *testing.T) { - req, _ := http.NewRequest("POST", "http://localhost"+tt.path, nil) + req, _ := http.NewRequest(http.MethodPost, "http://localhost"+tt.path, nil) setting.LFS.StartServer = false assert.Equal(t, tt.want, newAuthPathDetector(req).isGitRawOrAttachOrLFSPath()) @@ -119,7 +119,7 @@ func Test_isGitRawOrLFSPath(t *testing.T) { } for _, tt := range lfsTests { t.Run(tt, func(t *testing.T) { - req, _ := http.NewRequest("POST", tt, nil) + req, _ := http.NewRequest(http.MethodPost, tt, nil) setting.LFS.StartServer = false got := newAuthPathDetector(req).isGitRawOrAttachOrLFSPath() assert.Equalf(t, setting.LFS.StartServer, got, "isGitOrLFSPath(%q) = %v, want %v, %v", tt, got, setting.LFS.StartServer, globalVars().gitRawOrAttachPathRe.MatchString(tt)) @@ -148,7 +148,7 @@ func Test_isFeedRequest(t *testing.T) { } for _, tt := range tests { t.Run(tt.path, func(t *testing.T) { - req, _ := http.NewRequest("GET", "http://localhost"+tt.path, nil) + req, _ := http.NewRequest(http.MethodGet, "http://localhost"+tt.path, nil) assert.Equal(t, tt.want, newAuthPathDetector(req).isFeedRequest(req)) }) } diff --git a/services/auth/httpsign.go b/services/auth/httpsign.go index 83a36bef23..25e96ff32d 100644 --- a/services/auth/httpsign.go +++ b/services/auth/httpsign.go @@ -134,7 +134,7 @@ func VerifyCert(r *http.Request) (*asymkey_model.PublicKey, error) { // Check if it's really a ssh certificate cert, ok := pk.(*ssh.Certificate) if !ok { - return nil, fmt.Errorf("no certificate found") + return nil, errors.New("no certificate found") } c := &ssh.CertChecker{ @@ -153,7 +153,7 @@ func VerifyCert(r *http.Request) (*asymkey_model.PublicKey, error) { // check the CA of the cert if !c.IsUserAuthority(cert.SignatureKey) { - return nil, fmt.Errorf("CA check failed") + return nil, errors.New("CA check failed") } // Create a verifier @@ -191,7 +191,7 @@ func VerifyCert(r *http.Request) (*asymkey_model.PublicKey, error) { } // No public key matching a principal in the certificate is registered in gitea - return nil, fmt.Errorf("no valid principal found") + return nil, errors.New("no valid principal found") } // doVerify iterates across the provided public keys attempting the verify the current request against each key in turn diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index 6a6c60cd40..7aee761d0d 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -5,7 +5,6 @@ package ldap import ( "context" - "fmt" "strings" asymkey_model "code.gitea.io/gitea/models/asymkey" @@ -41,7 +40,7 @@ func (source *Source) Authenticate(ctx context.Context, user *user_model.User, u sr.Username = userName } if sr.Mail == "" { - sr.Mail = fmt.Sprintf("%s@localhost.local", sr.Username) + sr.Mail = sr.Username + "@localhost.local" } isAttributeSSHPublicKeySet := strings.TrimSpace(source.AttributeSSHPublicKey) != "" diff --git a/services/auth/source/ldap/source_sync.go b/services/auth/source/ldap/source_sync.go index e817bf1fa9..ff36db2955 100644 --- a/services/auth/source/ldap/source_sync.go +++ b/services/auth/source/ldap/source_sync.go @@ -5,7 +5,6 @@ package ldap import ( "context" - "fmt" "strings" asymkey_model "code.gitea.io/gitea/models/asymkey" @@ -106,7 +105,7 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error { } if su.Mail == "" { - su.Mail = fmt.Sprintf("%s@localhost.local", su.Username) + su.Mail = su.Username + "@localhost.local" } fullName := composeFullName(su.Name, su.Surname, su.Username) diff --git a/services/auth/source/oauth2/source_sync_test.go b/services/auth/source/oauth2/source_sync_test.go index aacb4286a1..08d841cc90 100644 --- a/services/auth/source/oauth2/source_sync_test.go +++ b/services/auth/source/oauth2/source_sync_test.go @@ -88,8 +88,8 @@ func TestSource(t *testing.T) { ok, err := user_model.GetExternalLogin(t.Context(), e) assert.NoError(t, err) assert.True(t, ok) - assert.Equal(t, "", e.RefreshToken) - assert.Equal(t, "", e.AccessToken) + assert.Empty(t, e.RefreshToken) + assert.Empty(t, e.AccessToken) u, err := user_model.GetUserByID(t.Context(), user.ID) assert.NoError(t, err) diff --git a/services/automerge/automerge.go b/services/automerge/automerge.go index 62d560ff94..9d2f7f4857 100644 --- a/services/automerge/automerge.go +++ b/services/automerge/automerge.go @@ -36,7 +36,7 @@ func Init() error { prAutoMergeQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "pr_auto_merge", handler) if prAutoMergeQueue == nil { - return fmt.Errorf("unable to create pr_auto_merge queue") + return errors.New("unable to create pr_auto_merge queue") } go graceful.GetManager().RunWithCancel(prAutoMergeQueue) return nil diff --git a/services/context/access_log.go b/services/context/access_log.go index 925e4a3056..caade113a7 100644 --- a/services/context/access_log.go +++ b/services/context/access_log.go @@ -5,7 +5,6 @@ package context import ( "bytes" - "fmt" "net" "net/http" "strings" @@ -47,7 +46,7 @@ func parseRequestIDFromRequestHeader(req *http.Request) string { } } if len(requestID) > maxRequestIDByteLength { - requestID = fmt.Sprintf("%s...", requestID[:maxRequestIDByteLength]) + requestID = requestID[:maxRequestIDByteLength] + "..." } return requestID } diff --git a/services/context/access_log_test.go b/services/context/access_log_test.go index c40ef9acd1..139a6eb217 100644 --- a/services/context/access_log_test.go +++ b/services/context/access_log_test.go @@ -59,7 +59,7 @@ func TestAccessLogger(t *testing.T) { recorder.logger = mockLogger req := &http.Request{ RemoteAddr: "remote-addr", - Method: "GET", + Method: http.MethodGet, Proto: "https", URL: &url.URL{Path: "/path"}, } diff --git a/services/context/api.go b/services/context/api.go index 10fad419ba..6e3b635ce3 100644 --- a/services/context/api.go +++ b/services/context/api.go @@ -9,6 +9,7 @@ import ( "fmt" "net/http" "net/url" + "strconv" "strings" "code.gitea.io/gitea/models/unit" @@ -168,7 +169,7 @@ func genAPILinks(curURL *url.URL, total, pageSize, curPage int) []string { if paginater.HasNext() { u := *curURL queries := u.Query() - queries.Set("page", fmt.Sprintf("%d", paginater.Next())) + queries.Set("page", strconv.Itoa(paginater.Next())) u.RawQuery = queries.Encode() links = append(links, fmt.Sprintf("<%s%s>; rel=\"next\"", setting.AppURL, u.RequestURI()[1:])) @@ -176,7 +177,7 @@ func genAPILinks(curURL *url.URL, total, pageSize, curPage int) []string { if !paginater.IsLast() { u := *curURL queries := u.Query() - queries.Set("page", fmt.Sprintf("%d", paginater.TotalPages())) + queries.Set("page", strconv.Itoa(paginater.TotalPages())) u.RawQuery = queries.Encode() links = append(links, fmt.Sprintf("<%s%s>; rel=\"last\"", setting.AppURL, u.RequestURI()[1:])) @@ -192,7 +193,7 @@ func genAPILinks(curURL *url.URL, total, pageSize, curPage int) []string { if paginater.HasPrevious() { u := *curURL queries := u.Query() - queries.Set("page", fmt.Sprintf("%d", paginater.Previous())) + queries.Set("page", strconv.Itoa(paginater.Previous())) u.RawQuery = queries.Encode() links = append(links, fmt.Sprintf("<%s%s>; rel=\"prev\"", setting.AppURL, u.RequestURI()[1:])) @@ -225,7 +226,7 @@ func APIContexter() func(http.Handler) http.Handler { ctx.SetContextValue(apiContextKey, ctx) // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid. - if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") { + if ctx.Req.Method == http.MethodPost && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") { if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size ctx.APIErrorInternal(err) return @@ -297,7 +298,7 @@ func RepoRefForAPI(next http.Handler) http.Handler { } if ctx.Repo.GitRepo == nil { - ctx.APIErrorInternal(fmt.Errorf("no open git repo")) + ctx.APIErrorInternal(errors.New("no open git repo")) return } diff --git a/services/context/api_test.go b/services/context/api_test.go index 911a49949e..87d74004db 100644 --- a/services/context/api_test.go +++ b/services/context/api_test.go @@ -45,6 +45,6 @@ func TestGenAPILinks(t *testing.T) { links := genAPILinks(u, 100, 20, curPage) - assert.EqualValues(t, links, response) + assert.Equal(t, links, response) } } diff --git a/services/context/base.go b/services/context/base.go index 3701668bf6..f3f92b7eeb 100644 --- a/services/context/base.go +++ b/services/context/base.go @@ -8,6 +8,7 @@ import ( "html/template" "io" "net/http" + "strconv" "strings" "code.gitea.io/gitea/modules/httplib" @@ -53,7 +54,7 @@ func (b *Base) AppendAccessControlExposeHeaders(names ...string) { // SetTotalCountHeader set "X-Total-Count" header func (b *Base) SetTotalCountHeader(total int64) { - b.RespHeader().Set("X-Total-Count", fmt.Sprint(total)) + b.RespHeader().Set("X-Total-Count", strconv.FormatInt(total, 10)) b.AppendAccessControlExposeHeaders("X-Total-Count") } diff --git a/services/context/base_test.go b/services/context/base_test.go index b936b76f58..2a4f86dddf 100644 --- a/services/context/base_test.go +++ b/services/context/base_test.go @@ -15,7 +15,7 @@ import ( func TestRedirect(t *testing.T) { setting.IsInTesting = true - req, _ := http.NewRequest("GET", "/", nil) + req, _ := http.NewRequest(http.MethodGet, "/", nil) cases := []struct { url string @@ -36,7 +36,7 @@ func TestRedirect(t *testing.T) { assert.Equal(t, c.keep, has, "url = %q", c.url) } - req, _ = http.NewRequest("GET", "/", nil) + req, _ = http.NewRequest(http.MethodGet, "/", nil) resp := httptest.NewRecorder() req.Header.Add("HX-Request", "true") b := NewBaseContextForTest(resp, req) diff --git a/services/context/context.go b/services/context/context.go index 79bc5da920..3c0ac54fc1 100644 --- a/services/context/context.go +++ b/services/context/context.go @@ -184,7 +184,7 @@ func Contexter() func(next http.Handler) http.Handler { }) // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid. - if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") { + if ctx.Req.Method == http.MethodPost && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") { if err := ctx.Req.ParseMultipartForm(setting.Attachment.MaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size ctx.ServerError("ParseMultipartForm", err) return diff --git a/services/context/context_response.go b/services/context/context_response.go index 61b432395a..4e11e29b69 100644 --- a/services/context/context_response.go +++ b/services/context/context_response.go @@ -150,7 +150,7 @@ func (ctx *Context) notFoundInternal(logMsg string, logErr error) { ctx.Data["IsRepo"] = ctx.Repo.Repository != nil ctx.Data["Title"] = "Page Not Found" - ctx.HTML(http.StatusNotFound, templates.TplName("status/404")) + ctx.HTML(http.StatusNotFound, "status/404") } // ServerError displays a 500 (Internal Server Error) page and prints the given error, if any. diff --git a/services/context/org.go b/services/context/org.go index 992a48afa0..c8b6ed09b7 100644 --- a/services/context/org.go +++ b/services/context/org.go @@ -182,7 +182,7 @@ func OrgAssignment(opts OrgAssignmentOptions) func(ctx *Context) { return } for _, team := range teams { - if team.IncludesAllRepositories && team.AccessMode >= perm.AccessModeAdmin { + if team.IncludesAllRepositories && team.HasAdminAccess() { shouldSeeAllTeams = true break } @@ -228,7 +228,7 @@ func OrgAssignment(opts OrgAssignmentOptions) func(ctx *Context) { return } - ctx.Org.IsTeamAdmin = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.AccessMode >= perm.AccessModeAdmin + ctx.Org.IsTeamAdmin = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.HasAdminAccess() ctx.Data["IsTeamAdmin"] = ctx.Org.IsTeamAdmin if opts.RequireTeamAdmin && !ctx.Org.IsTeamAdmin { ctx.NotFound(err) diff --git a/services/context/package.go b/services/context/package.go index 64bb4f3ecd..8b722932b1 100644 --- a/services/context/package.go +++ b/services/context/package.go @@ -93,7 +93,7 @@ func packageAssignment(ctx *packageAssignmentCtx, errCb func(int, any)) *Package } func determineAccessMode(ctx *Base, pkg *Package, doer *user_model.User) (perm.AccessMode, error) { - if setting.Service.RequireSignInView && (doer == nil || doer.IsGhost()) { + if setting.Service.RequireSignInViewStrict && (doer == nil || doer.IsGhost()) { return perm.AccessModeNone, nil } diff --git a/services/context/private.go b/services/context/private.go index 51857da8fe..3f7637518b 100644 --- a/services/context/private.go +++ b/services/context/private.go @@ -5,7 +5,6 @@ package context import ( "context" - "fmt" "net/http" "time" @@ -82,7 +81,7 @@ func OverrideContext() func(http.Handler) http.Handler { // We now need to override the request context as the base for our work because even if the request is cancelled we have to continue this work ctx := GetPrivateContext(req) var finished func() - ctx.Override, _, finished = process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), fmt.Sprintf("PrivateContext: %s", ctx.Req.RequestURI), process.RequestProcessType, true) + ctx.Override, _, finished = process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "PrivateContext: "+ctx.Req.RequestURI, process.RequestProcessType, true) defer finished() next.ServeHTTP(ctx.Resp, ctx.Req) }) diff --git a/services/context/repo.go b/services/context/repo.go index 7d0b44c42f..6f5c772f5e 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -669,12 +669,6 @@ func RepoAssignment(ctx *Context) { const headRefName = "HEAD" -func RepoRef() func(*Context) { - // old code does: return RepoRefByType(git.RefTypeBranch) - // in most cases, it is an abuse, so we just disable it completely and fix the abuses one by one (if there is anything wrong) - return nil -} - func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool) string { refName := "" parts := strings.Split(path, "/") @@ -817,9 +811,9 @@ func RepoRefByType(detectRefType git.RefType) func(*Context) { if reqPath == "" { refShortName = ctx.Repo.Repository.DefaultBranch if !gitrepo.IsBranchExist(ctx, ctx.Repo.Repository, refShortName) { - brs, _, err := ctx.Repo.GitRepo.GetBranches(0, 1) + brs, _, err := ctx.Repo.GitRepo.GetBranchNames(0, 1) if err == nil && len(brs) != 0 { - refShortName = brs[0].Name + refShortName = brs[0] } else if len(brs) == 0 { log.Error("No branches in non-empty repository %s", ctx.Repo.GitRepo.Path) } else { diff --git a/services/context/upload/upload.go b/services/context/upload/upload.go index da4370a433..12aa485aa7 100644 --- a/services/context/upload/upload.go +++ b/services/context/upload/upload.go @@ -87,14 +87,15 @@ func Verify(buf []byte, fileName, allowedTypesStr string) error { // AddUploadContext renders template values for dropzone func AddUploadContext(ctx *context.Context, uploadType string) { - if uploadType == "release" { + switch uploadType { + case "release": ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/releases/attachments" ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/releases/attachments/remove" ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/releases/attachments" ctx.Data["UploadAccepts"] = strings.ReplaceAll(setting.Repository.Release.AllowedTypes, "|", ",") ctx.Data["UploadMaxFiles"] = setting.Attachment.MaxFiles ctx.Data["UploadMaxSize"] = setting.Attachment.MaxSize - } else if uploadType == "comment" { + case "comment": ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/issues/attachments" ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/issues/attachments/remove" if len(ctx.PathParam("index")) > 0 { @@ -105,7 +106,7 @@ func AddUploadContext(ctx *context.Context, uploadType string) { ctx.Data["UploadAccepts"] = strings.ReplaceAll(setting.Attachment.AllowedTypes, "|", ",") ctx.Data["UploadMaxFiles"] = setting.Attachment.MaxFiles ctx.Data["UploadMaxSize"] = setting.Attachment.MaxSize - } else if uploadType == "repo" { + case "repo": ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/upload-file" ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/upload-remove" ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/upload-file" diff --git a/services/convert/git_commit_test.go b/services/convert/git_commit_test.go index 73cb5e8c71..ad1cc0eca3 100644 --- a/services/convert/git_commit_test.go +++ b/services/convert/git_commit_test.go @@ -33,7 +33,7 @@ func TestToCommitMeta(t *testing.T) { commitMeta := ToCommitMeta(headRepo, tag) assert.NotNil(t, commitMeta) - assert.EqualValues(t, &api.CommitMeta{ + assert.Equal(t, &api.CommitMeta{ SHA: sha1.EmptyObjectID().String(), URL: util.URLJoin(headRepo.APIURL(), "git/commits", sha1.EmptyObjectID().String()), Created: time.Unix(0, 0), diff --git a/services/convert/pull.go b/services/convert/pull.go index 928534ce5e..7798bebb08 100644 --- a/services/convert/pull.go +++ b/services/convert/pull.go @@ -14,6 +14,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/cache" + "code.gitea.io/gitea/modules/cachegroup" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" @@ -28,8 +29,8 @@ import ( // Optional - Merger func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User) *api.PullRequest { var ( - baseBranch *git.Branch - headBranch *git.Branch + baseBranch string + headBranch string baseCommit *git.Commit err error ) @@ -60,14 +61,14 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u doerID = doer.ID } - const repoDoerPermCacheKey = "repo_doer_perm_cache" - p, err := cache.GetWithContextCache(ctx, repoDoerPermCacheKey, fmt.Sprintf("%d_%d", pr.BaseRepoID, doerID), - func() (access_model.Permission, error) { + repoUserPerm, err := cache.GetWithContextCache(ctx, cachegroup.RepoUserPermission, fmt.Sprintf("%d-%d", pr.BaseRepoID, doerID), + func(ctx context.Context, _ string) (access_model.Permission, error) { return access_model.GetUserRepoPermission(ctx, pr.BaseRepo, doer) - }) + }, + ) if err != nil { log.Error("GetUserRepoPermission[%d]: %v", pr.BaseRepoID, err) - p.AccessMode = perm.AccessModeNone + repoUserPerm.AccessMode = perm.AccessModeNone } apiPullRequest := &api.PullRequest{ @@ -107,7 +108,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u Name: pr.BaseBranch, Ref: pr.BaseBranch, RepoID: pr.BaseRepoID, - Repository: ToRepo(ctx, pr.BaseRepo, p), + Repository: ToRepo(ctx, pr.BaseRepo, repoUserPerm), }, Head: &api.PRBranchInfo{ Name: pr.HeadBranch, @@ -150,16 +151,16 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u } defer gitRepo.Close() - baseBranch, err = gitRepo.GetBranch(pr.BaseBranch) - if err != nil && !git.IsErrBranchNotExist(err) { + exist, err := git_model.IsBranchExist(ctx, pr.BaseRepoID, pr.BaseBranch) + if err != nil { log.Error("GetBranch[%s]: %v", pr.BaseBranch, err) return nil } - if err == nil { - baseCommit, err = baseBranch.GetCommit() + if exist { + baseCommit, err = gitRepo.GetBranchCommit(pr.BaseBranch) if err != nil && !git.IsErrNotExist(err) { - log.Error("GetCommit[%s]: %v", baseBranch.Name, err) + log.Error("GetCommit[%s]: %v", baseBranch, err) return nil } @@ -169,13 +170,6 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u } if pr.Flow == issues_model.PullRequestFlowAGit { - gitRepo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo) - if err != nil { - log.Error("OpenRepository[%s]: %v", pr.GetGitRefName(), err) - return nil - } - defer gitRepo.Close() - apiPullRequest.Head.Sha, err = gitRepo.GetRefCommitID(pr.GetGitRefName()) if err != nil { log.Error("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err) @@ -203,8 +197,8 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u } defer headGitRepo.Close() - headBranch, err = headGitRepo.GetBranch(pr.HeadBranch) - if err != nil && !git.IsErrBranchNotExist(err) { + exist, err = git_model.IsBranchExist(ctx, pr.HeadRepoID, pr.HeadBranch) + if err != nil { log.Error("GetBranch[%s]: %v", pr.HeadBranch, err) return nil } @@ -215,7 +209,7 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u endCommitID string ) - if git.IsErrBranchNotExist(err) { + if !exist { headCommitID, err := headGitRepo.GetRefCommitID(apiPullRequest.Head.Ref) if err != nil && !git.IsErrNotExist(err) { log.Error("GetCommit[%s]: %v", pr.HeadBranch, err) @@ -226,9 +220,9 @@ func ToAPIPullRequest(ctx context.Context, pr *issues_model.PullRequest, doer *u endCommitID = headCommitID } } else { - commit, err := headBranch.GetCommit() + commit, err := headGitRepo.GetBranchCommit(pr.HeadBranch) if err != nil && !git.IsErrNotExist(err) { - log.Error("GetCommit[%s]: %v", headBranch.Name, err) + log.Error("GetCommit[%s]: %v", headBranch, err) return nil } if err == nil { @@ -389,7 +383,7 @@ func ToAPIPullRequests(ctx context.Context, baseRepo *repo_model.Repository, prs }, Head: &api.PRBranchInfo{ Name: pr.HeadBranch, - Ref: fmt.Sprintf("%s%d/head", git.PullPrefix, pr.Index), + Ref: pr.GetGitRefName(), RepoID: -1, }, } @@ -422,72 +416,43 @@ func ToAPIPullRequests(ctx context.Context, baseRepo *repo_model.Repository, prs return nil, err } } - if baseBranch != nil { apiPullRequest.Base.Sha = baseBranch.CommitID } - if pr.Flow == issues_model.PullRequestFlowAGit { - apiPullRequest.Head.Sha, err = gitRepo.GetRefCommitID(pr.GetGitRefName()) + // pull request head branch, both repository and branch could not exist + if pr.HeadRepo != nil { + apiPullRequest.Head.RepoID = pr.HeadRepo.ID + exist, err := git_model.IsBranchExist(ctx, pr.HeadRepo.ID, pr.HeadBranch) if err != nil { - log.Error("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err) + log.Error("IsBranchExist[%d]: %v", pr.HeadRepo.ID, err) return nil, err } - apiPullRequest.Head.RepoID = pr.BaseRepoID - apiPullRequest.Head.Repository = apiPullRequest.Base.Repository - apiPullRequest.Head.Name = "" + if exist { + apiPullRequest.Head.Ref = pr.HeadBranch + } + } + if apiPullRequest.Head.Ref == "" { + apiPullRequest.Head.Ref = pr.GetGitRefName() } - var headGitRepo *git.Repository - if pr.HeadRepo != nil && pr.Flow == issues_model.PullRequestFlowGithub { - if pr.HeadRepoID == pr.BaseRepoID { - apiPullRequest.Head.RepoID = pr.HeadRepo.ID - apiPullRequest.Head.Repository = apiRepo - headGitRepo = gitRepo - } else { - p, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, doer) - if err != nil { - log.Error("GetUserRepoPermission[%d]: %v", pr.HeadRepoID, err) - p.AccessMode = perm.AccessModeNone - } - - apiPullRequest.Head.RepoID = pr.HeadRepo.ID - apiPullRequest.Head.Repository = ToRepo(ctx, pr.HeadRepo, p) - - headGitRepo, err = gitrepo.OpenRepository(ctx, pr.HeadRepo) - if err != nil { - log.Error("OpenRepository[%s]: %v", pr.HeadRepo.RepoPath(), err) - return nil, err - } - defer headGitRepo.Close() + if pr.HeadRepoID == pr.BaseRepoID { + apiPullRequest.Head.Repository = apiPullRequest.Base.Repository + } else { + p, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, doer) + if err != nil { + log.Error("GetUserRepoPermission[%d]: %v", pr.HeadRepoID, err) + p.AccessMode = perm.AccessModeNone } + apiPullRequest.Head.Repository = ToRepo(ctx, pr.HeadRepo, p) + } - headBranch, err := headGitRepo.GetBranch(pr.HeadBranch) - if err != nil && !git.IsErrBranchNotExist(err) { - log.Error("GetBranch[%s]: %v", pr.HeadBranch, err) - return nil, err - } - - if git.IsErrBranchNotExist(err) { - headCommitID, err := headGitRepo.GetRefCommitID(apiPullRequest.Head.Ref) - if err != nil && !git.IsErrNotExist(err) { - log.Error("GetCommit[%s]: %v", pr.HeadBranch, err) - return nil, err - } - if err == nil { - apiPullRequest.Head.Sha = headCommitID - } - } else { - commit, err := headBranch.GetCommit() - if err != nil && !git.IsErrNotExist(err) { - log.Error("GetCommit[%s]: %v", headBranch.Name, err) - return nil, err - } - if err == nil { - apiPullRequest.Head.Ref = pr.HeadBranch - apiPullRequest.Head.Sha = commit.ID.String() - } - } + if pr.Flow == issues_model.PullRequestFlowAGit { + apiPullRequest.Head.Name = "" + } + apiPullRequest.Head.Sha, err = gitRepo.GetRefCommitID(pr.GetGitRefName()) + if err != nil { + log.Error("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err) } if len(apiPullRequest.Head.Sha) == 0 && len(apiPullRequest.Head.Ref) != 0 { diff --git a/services/convert/pull_review_test.go b/services/convert/pull_review_test.go index a1296fafd4..d0a077ab24 100644 --- a/services/convert/pull_review_test.go +++ b/services/convert/pull_review_test.go @@ -19,8 +19,8 @@ func Test_ToPullReview(t *testing.T) { reviewer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) review := unittest.AssertExistsAndLoadBean(t, &issues_model.Review{ID: 6}) - assert.EqualValues(t, reviewer.ID, review.ReviewerID) - assert.EqualValues(t, issues_model.ReviewTypePending, review.Type) + assert.Equal(t, reviewer.ID, review.ReviewerID) + assert.Equal(t, issues_model.ReviewTypePending, review.Type) reviewList := []*issues_model.Review{review} diff --git a/services/convert/pull_test.go b/services/convert/pull_test.go index e069fa4a68..cd86283c8a 100644 --- a/services/convert/pull_test.go +++ b/services/convert/pull_test.go @@ -27,7 +27,7 @@ func TestPullRequest_APIFormat(t *testing.T) { assert.NoError(t, pr.LoadIssue(db.DefaultContext)) apiPullRequest := ToAPIPullRequest(git.DefaultContext, pr, nil) assert.NotNil(t, apiPullRequest) - assert.EqualValues(t, &structs.PRBranchInfo{ + assert.Equal(t, &structs.PRBranchInfo{ Name: "branch1", Ref: "refs/pull/2/head", Sha: "4a357436d925b5c974181ff12a994538ddc5a269", diff --git a/services/convert/release_test.go b/services/convert/release_test.go index 201b27e16d..bb618c9ca3 100644 --- a/services/convert/release_test.go +++ b/services/convert/release_test.go @@ -23,6 +23,6 @@ func TestRelease_ToRelease(t *testing.T) { apiRelease := ToAPIRelease(db.DefaultContext, repo1, release1) assert.NotNil(t, apiRelease) assert.EqualValues(t, 1, apiRelease.ID) - assert.EqualValues(t, "https://try.gitea.io/api/v1/repos/user2/repo1/releases/1", apiRelease.URL) - assert.EqualValues(t, "https://try.gitea.io/api/v1/repos/user2/repo1/releases/1/assets", apiRelease.UploadURL) + assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/releases/1", apiRelease.URL) + assert.Equal(t, "https://try.gitea.io/api/v1/repos/user2/repo1/releases/1/assets", apiRelease.UploadURL) } diff --git a/services/convert/user_test.go b/services/convert/user_test.go index 4b1effc7aa..199d500732 100644 --- a/services/convert/user_test.go +++ b/services/convert/user_test.go @@ -30,11 +30,11 @@ func TestUser_ToUser(t *testing.T) { apiUser = toUser(db.DefaultContext, user1, false, false) assert.False(t, apiUser.IsAdmin) - assert.EqualValues(t, api.VisibleTypePublic.String(), apiUser.Visibility) + assert.Equal(t, api.VisibleTypePublic.String(), apiUser.Visibility) user31 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 31, IsAdmin: false, Visibility: api.VisibleTypePrivate}) apiUser = toUser(db.DefaultContext, user31, true, true) assert.False(t, apiUser.IsAdmin) - assert.EqualValues(t, api.VisibleTypePrivate.String(), apiUser.Visibility) + assert.Equal(t, api.VisibleTypePrivate.String(), apiUser.Visibility) } diff --git a/services/convert/utils_test.go b/services/convert/utils_test.go index a8363ec6bd..7965624e2b 100644 --- a/services/convert/utils_test.go +++ b/services/convert/utils_test.go @@ -10,10 +10,10 @@ import ( ) func TestToCorrectPageSize(t *testing.T) { - assert.EqualValues(t, 30, ToCorrectPageSize(0)) - assert.EqualValues(t, 30, ToCorrectPageSize(-10)) - assert.EqualValues(t, 20, ToCorrectPageSize(20)) - assert.EqualValues(t, 50, ToCorrectPageSize(100)) + assert.Equal(t, 30, ToCorrectPageSize(0)) + assert.Equal(t, 30, ToCorrectPageSize(-10)) + assert.Equal(t, 20, ToCorrectPageSize(20)) + assert.Equal(t, 50, ToCorrectPageSize(100)) } func TestToGitServiceType(t *testing.T) { diff --git a/services/doctor/authorizedkeys.go b/services/doctor/authorizedkeys.go index 8d6fc9cb5e..46e7099dce 100644 --- a/services/doctor/authorizedkeys.go +++ b/services/doctor/authorizedkeys.go @@ -7,6 +7,7 @@ import ( "bufio" "bytes" "context" + "errors" "fmt" "os" "path/filepath" @@ -78,7 +79,7 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e fPath, "gitea admin regenerate keys", "gitea doctor --run authorized-keys --fix") - return fmt.Errorf(`authorized_keys is out of date and should be regenerated with "gitea admin regenerate keys" or "gitea doctor --run authorized-keys --fix"`) + return errors.New(`authorized_keys is out of date and should be regenerated with "gitea admin regenerate keys" or "gitea doctor --run authorized-keys --fix"`) } logger.Warn("authorized_keys is out of date. Attempting rewrite...") err = asymkey_service.RewriteAllPublicKeys(ctx) diff --git a/services/doctor/fix16961_test.go b/services/doctor/fix16961_test.go index d5eded1117..11a128620c 100644 --- a/services/doctor/fix16961_test.go +++ b/services/doctor/fix16961_test.go @@ -215,7 +215,7 @@ func Test_fixPullRequestsConfig_16961(t *testing.T) { if gotFixed != tt.wantFixed { t.Errorf("fixPullRequestsConfig_16961() = %v, want %v", gotFixed, tt.wantFixed) } - assert.EqualValues(t, &tt.expected, cfg) + assert.Equal(t, &tt.expected, cfg) }) } } @@ -259,7 +259,7 @@ func Test_fixIssuesConfig_16961(t *testing.T) { if gotFixed != tt.wantFixed { t.Errorf("fixIssuesConfig_16961() = %v, want %v", gotFixed, tt.wantFixed) } - assert.EqualValues(t, &tt.expected, cfg) + assert.Equal(t, &tt.expected, cfg) }) } } diff --git a/services/doctor/lfs.go b/services/doctor/lfs.go index 5f110b8f97..a90f394450 100644 --- a/services/doctor/lfs.go +++ b/services/doctor/lfs.go @@ -5,7 +5,7 @@ package doctor import ( "context" - "fmt" + "errors" "time" "code.gitea.io/gitea/modules/log" @@ -27,7 +27,7 @@ func init() { func garbageCollectLFSCheck(ctx context.Context, logger log.Logger, autofix bool) error { if !setting.LFS.StartServer { - return fmt.Errorf("LFS support is disabled") + return errors.New("LFS support is disabled") } if err := repository.GarbageCollectLFSMetaObjects(ctx, repository.GarbageCollectLFSMetaObjectsOptions{ diff --git a/services/doctor/storage.go b/services/doctor/storage.go index 3f3b562c37..77fc6d65df 100644 --- a/services/doctor/storage.go +++ b/services/doctor/storage.go @@ -121,7 +121,7 @@ func checkStorage(opts *checkStorageOptions) func(ctx context.Context, logger lo storer: storage.LFS, isOrphaned: func(path string, obj storage.Object, stat fs.FileInfo) (bool, error) { // The oid of an LFS stored object is the name but with all the path.Separators removed - oid := strings.ReplaceAll(path, "/", "") + oid := strings.ReplaceAll(strings.ReplaceAll(path, "\\", ""), "/", "") exists, err := git.ExistsLFSObject(ctx, oid) return !exists, err }, diff --git a/services/externalaccount/link.go b/services/externalaccount/link.go index d6e2ea7e94..ab853140cb 100644 --- a/services/externalaccount/link.go +++ b/services/externalaccount/link.go @@ -5,7 +5,7 @@ package externalaccount import ( "context" - "fmt" + "errors" user_model "code.gitea.io/gitea/models/user" @@ -23,7 +23,7 @@ type Store interface { func LinkAccountFromStore(ctx context.Context, store Store, user *user_model.User) error { gothUser := store.Get("linkAccountGothUser") if gothUser == nil { - return fmt.Errorf("not in LinkAccount session") + return errors.New("not in LinkAccount session") } return LinkAccountToUser(ctx, user, gothUser.(goth.User)) diff --git a/services/feed/feed.go b/services/feed/feed.go index 214e9b5765..38a4e25308 100644 --- a/services/feed/feed.go +++ b/services/feed/feed.go @@ -6,6 +6,7 @@ package feed import ( "context" "fmt" + "strconv" activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" @@ -86,7 +87,7 @@ func notifyWatchers(ctx context.Context, act *activities_model.Action, watchers return fmt.Errorf("count user feeds: %w", err) } - _ = cache.GetCache().Put(userFeedCacheKey(act.UserID), fmt.Sprintf("%d", total), setting.CacheService.TTLSeconds()) + _ = cache.GetCache().Put(userFeedCacheKey(act.UserID), strconv.FormatInt(total, 10), setting.CacheService.TTLSeconds()) } return nil diff --git a/services/feed/feed_test.go b/services/feed/feed_test.go index 243bc046b0..a3492938c8 100644 --- a/services/feed/feed_test.go +++ b/services/feed/feed_test.go @@ -30,7 +30,7 @@ func TestGetFeeds(t *testing.T) { assert.NoError(t, err) if assert.Len(t, actions, 1) { assert.EqualValues(t, 1, actions[0].ID) - assert.EqualValues(t, user.ID, actions[0].UserID) + assert.Equal(t, user.ID, actions[0].UserID) } assert.Equal(t, int64(1), count) @@ -107,7 +107,7 @@ func TestGetFeeds2(t *testing.T) { assert.Len(t, actions, 1) if assert.Len(t, actions, 1) { assert.EqualValues(t, 2, actions[0].ID) - assert.EqualValues(t, org.ID, actions[0].UserID) + assert.Equal(t, org.ID, actions[0].UserID) } assert.Equal(t, int64(1), count) diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 1366d30b1f..434274c174 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -110,17 +110,14 @@ type RepoSettingForm struct { EnablePrune bool // Advanced settings - EnableCode bool - DefaultCodeEveryoneAccess string + EnableCode bool - EnableWiki bool - EnableExternalWiki bool - DefaultWikiBranch string - DefaultWikiEveryoneAccess string - ExternalWikiURL string + EnableWiki bool + EnableExternalWiki bool + DefaultWikiBranch string + ExternalWikiURL string EnableIssues bool - DefaultIssuesEveryoneAccess string EnableExternalTracker bool ExternalTrackerURL string TrackerURLFormat string @@ -522,12 +519,13 @@ func (f *CreateMilestoneForm) Validate(req *http.Request, errs binding.Errors) b // CreateLabelForm form for creating label type CreateLabelForm struct { - ID int64 - Title string `binding:"Required;MaxSize(50)" locale:"repo.issues.label_title"` - Exclusive bool `form:"exclusive"` - IsArchived bool `form:"is_archived"` - Description string `binding:"MaxSize(200)" locale:"repo.issues.label_description"` - Color string `binding:"Required;MaxSize(7)" locale:"repo.issues.label_color"` + ID int64 + Title string `binding:"Required;MaxSize(50)" locale:"repo.issues.label_title"` + Exclusive bool `form:"exclusive"` + ExclusiveOrder int `form:"exclusive_order"` + IsArchived bool `form:"is_archived"` + Description string `binding:"MaxSize(200)" locale:"repo.issues.label_description"` + Color string `binding:"Required;MaxSize(7)" locale:"repo.issues.label_color"` } // Validate validates the fields diff --git a/services/git/commit.go b/services/git/commit.go index 8ab8f3d369..3faef76782 100644 --- a/services/git/commit.go +++ b/services/git/commit.go @@ -17,7 +17,7 @@ import ( ) // ParseCommitsWithSignature checks if signaute of commits are corresponding to users gpg keys. -func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType, isOwnerMemberCollaborator func(*user_model.User) (bool, error)) ([]*asymkey_model.SignCommit, error) { +func ParseCommitsWithSignature(ctx context.Context, repo *repo_model.Repository, oldCommits []*user_model.UserCommit, repoTrustModel repo_model.TrustModelType) ([]*asymkey_model.SignCommit, error) { newCommits := make([]*asymkey_model.SignCommit, 0, len(oldCommits)) keyMap := map[string]bool{} @@ -47,6 +47,10 @@ func ParseCommitsWithSignature(ctx context.Context, oldCommits []*user_model.Use Verification: asymkey_service.ParseCommitWithSignatureCommitter(ctx, c.Commit, committer), } + isOwnerMemberCollaborator := func(user *user_model.User) (bool, error) { + return repo_model.IsOwnerMemberCollaborator(ctx, repo, user.ID) + } + _ = asymkey_model.CalculateTrustStatus(signCommit.Verification, repoTrustModel, isOwnerMemberCollaborator, &keyMap) newCommits = append(newCommits, signCommit) @@ -62,11 +66,9 @@ func ConvertFromGitCommit(ctx context.Context, commits []*git.Commit, repo *repo } signedCommits, err := ParseCommitsWithSignature( ctx, + repo, validatedCommits, repo.GetTrustModel(), - func(user *user_model.User) (bool, error) { - return repo_model.IsOwnerMemberCollaborator(ctx, repo, user.ID) - }, ) if err != nil { return nil, err diff --git a/services/gitdiff/git_diff_tree.go b/services/gitdiff/git_diff_tree.go index 035210a31d..ed94bfbfe4 100644 --- a/services/gitdiff/git_diff_tree.go +++ b/services/gitdiff/git_diff_tree.go @@ -6,6 +6,7 @@ package gitdiff import ( "bufio" "context" + "errors" "fmt" "io" "strconv" @@ -71,7 +72,7 @@ func runGitDiffTree(ctx context.Context, gitRepo *git.Repository, useMergeBase b func validateGitDiffTreeArguments(gitRepo *git.Repository, useMergeBase bool, baseSha, headSha string) (shouldUseMergeBase bool, resolvedBaseSha, resolvedHeadSha string, err error) { // if the head is empty its an error if headSha == "" { - return false, "", "", fmt.Errorf("headSha is empty") + return false, "", "", errors.New("headSha is empty") } // if the head commit doesn't exist its and error @@ -207,7 +208,7 @@ func parseGitDiffTreeLine(line string) (*DiffTreeRecord, error) { func statusFromLetter(rawStatus string) (status string, score uint8, err error) { if len(rawStatus) < 1 { - return "", 0, fmt.Errorf("empty status letter") + return "", 0, errors.New("empty status letter") } switch rawStatus[0] { case 'A': @@ -235,7 +236,7 @@ func statusFromLetter(rawStatus string) (status string, score uint8, err error) func tryParseStatusScore(rawStatus string) (uint8, error) { if len(rawStatus) < 2 { - return 0, fmt.Errorf("status score missing") + return 0, errors.New("status score missing") } score, err := strconv.ParseUint(rawStatus[1:], 10, 8) diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index b9781cf8d0..9ee86d9dfc 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -25,6 +25,7 @@ import ( "code.gitea.io/gitea/modules/analyze" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/attribute" "code.gitea.io/gitea/modules/highlight" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" @@ -1237,24 +1238,21 @@ func GetDiffForRender(ctx context.Context, gitRepo *git.Repository, opts *DiffOp return nil, err } - checker, deferrable := gitRepo.CheckAttributeReader(opts.AfterCommitID) - defer deferrable() + checker, err := attribute.NewBatchChecker(gitRepo, opts.AfterCommitID, []string{attribute.LinguistVendored, attribute.LinguistGenerated, attribute.LinguistLanguage, attribute.GitlabLanguage}) + if err != nil { + return nil, err + } + defer checker.Close() for _, diffFile := range diff.Files { isVendored := optional.None[bool]() isGenerated := optional.None[bool]() - if checker != nil { - attrs, err := checker.CheckPath(diffFile.Name) - if err == nil { - isVendored = git.AttributeToBool(attrs, git.AttributeLinguistVendored) - isGenerated = git.AttributeToBool(attrs, git.AttributeLinguistGenerated) - - language := git.TryReadLanguageAttribute(attrs) - if language.Has() { - diffFile.Language = language.Value() - } - } else { - checker = nil // CheckPath fails, it's not impossible to "check" anymore + attrs, err := checker.CheckPath(diffFile.Name) + if err == nil { + isVendored, isGenerated = attrs.GetVendored(), attrs.GetGenerated() + language := attrs.GetLanguage() + if language.Has() { + diffFile.Language = language.Value() } } diff --git a/services/gitdiff/highlightdiff.go b/services/gitdiff/highlightdiff.go index 6e18651d83..e8be063e69 100644 --- a/services/gitdiff/highlightdiff.go +++ b/services/gitdiff/highlightdiff.go @@ -14,13 +14,14 @@ import ( // token is a html tag or entity, eg: "", "", "<" func extractHTMLToken(s string) (before, token, after string, valid bool) { for pos1 := 0; pos1 < len(s); pos1++ { - if s[pos1] == '<' { + switch s[pos1] { + case '<': pos2 := strings.IndexByte(s[pos1:], '>') if pos2 == -1 { return "", "", s, false } return s[:pos1], s[pos1 : pos1+pos2+1], s[pos1+pos2+1:], true - } else if s[pos1] == '&' { + case '&': pos2 := strings.IndexByte(s[pos1:], ';') if pos2 == -1 { return "", "", s, false diff --git a/services/gitdiff/highlightdiff_test.go b/services/gitdiff/highlightdiff_test.go index c2584dc622..aebe38ae7c 100644 --- a/services/gitdiff/highlightdiff_test.go +++ b/services/gitdiff/highlightdiff_test.go @@ -38,15 +38,15 @@ func TestDiffWithHighlight(t *testing.T) { hcd.placeholderTokenMap['O'], hcd.placeholderTokenMap['C'] = "", "" assert.Equal(t, "", string(hcd.recoverOneDiff("OC"))) assert.Equal(t, "", string(hcd.recoverOneDiff("O"))) - assert.Equal(t, "", string(hcd.recoverOneDiff("C"))) + assert.Empty(t, string(hcd.recoverOneDiff("C"))) }) } func TestDiffWithHighlightPlaceholder(t *testing.T) { hcd := newHighlightCodeDiff() output := hcd.diffLineWithHighlight(DiffLineDel, "a='\U00100000'", "a='\U0010FFFD''") - assert.Equal(t, "", hcd.placeholderTokenMap[0x00100000]) - assert.Equal(t, "", hcd.placeholderTokenMap[0x0010FFFD]) + assert.Empty(t, hcd.placeholderTokenMap[0x00100000]) + assert.Empty(t, hcd.placeholderTokenMap[0x0010FFFD]) expected := fmt.Sprintf(`a='%s'`, "\U00100000") assert.Equal(t, expected, string(output)) diff --git a/services/issue/comments.go b/services/issue/comments.go index 46f92f7cd2..10c81198d5 100644 --- a/services/issue/comments.go +++ b/services/issue/comments.go @@ -5,6 +5,7 @@ package issue import ( "context" + "errors" "fmt" "code.gitea.io/gitea/models/db" @@ -22,7 +23,7 @@ import ( // CreateRefComment creates a commit reference comment to issue. func CreateRefComment(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, issue *issues_model.Issue, content, commitSHA string) error { if len(commitSHA) == 0 { - return fmt.Errorf("cannot create reference with empty commit SHA") + return errors.New("cannot create reference with empty commit SHA") } if user_model.IsUserBlockedBy(ctx, doer, issue.PosterID, repo.OwnerID) { diff --git a/services/issue/issue_test.go b/services/issue/issue_test.go index 8806cec0e7..b3df8191e1 100644 --- a/services/issue/issue_test.go +++ b/services/issue/issue_test.go @@ -24,8 +24,8 @@ func TestGetRefEndNamesAndURLs(t *testing.T) { repoLink := "/foo/bar" endNames, urls := GetRefEndNamesAndURLs(issues, repoLink) - assert.EqualValues(t, map[int64]string{1: "branch1", 2: "tag1", 3: "c0ffee"}, endNames) - assert.EqualValues(t, map[int64]string{ + assert.Equal(t, map[int64]string{1: "branch1", 2: "tag1", 3: "c0ffee"}, endNames) + assert.Equal(t, map[int64]string{ 1: repoLink + "/src/branch/branch1", 2: repoLink + "/src/tag/tag1", 3: repoLink + "/src/commit/c0ffee", diff --git a/services/issue/milestone.go b/services/issue/milestone.go index beb6f131a9..afca70794d 100644 --- a/services/issue/milestone.go +++ b/services/issue/milestone.go @@ -5,6 +5,7 @@ package issue import ( "context" + "errors" "fmt" "code.gitea.io/gitea/models/db" @@ -21,7 +22,7 @@ func changeMilestoneAssign(ctx context.Context, doer *user_model.User, issue *is return fmt.Errorf("HasMilestoneByRepoID: %w", err) } if !has { - return fmt.Errorf("HasMilestoneByRepoID: issue doesn't exist") + return errors.New("HasMilestoneByRepoID: issue doesn't exist") } } diff --git a/services/issue/suggestion_test.go b/services/issue/suggestion_test.go index 84cfd520ac..a5b39d27bb 100644 --- a/services/issue/suggestion_test.go +++ b/services/issue/suggestion_test.go @@ -51,7 +51,7 @@ func Test_Suggestion(t *testing.T) { for _, issue := range issues { issueIndexes = append(issueIndexes, issue.Index) } - assert.EqualValues(t, testCase.expectedIndexes, issueIndexes) + assert.Equal(t, testCase.expectedIndexes, issueIndexes) }) } } diff --git a/services/lfs/server.go b/services/lfs/server.go index c4866edaab..0a99287ed9 100644 --- a/services/lfs/server.go +++ b/services/lfs/server.go @@ -164,11 +164,12 @@ func BatchHandler(ctx *context.Context) { } var isUpload bool - if br.Operation == "upload" { + switch br.Operation { + case "upload": isUpload = true - } else if br.Operation == "download" { + case "download": isUpload = false - } else { + default: log.Trace("Attempt to BATCH with invalid operation: %s", br.Operation) writeStatus(ctx, http.StatusBadRequest) return @@ -571,15 +572,15 @@ func handleLFSToken(ctx stdCtx.Context, tokenSHA string, target *repo_model.Repo claims, claimsOk := token.Claims.(*Claims) if !token.Valid || !claimsOk { - return nil, fmt.Errorf("invalid token claim") + return nil, errors.New("invalid token claim") } if claims.RepoID != target.ID { - return nil, fmt.Errorf("invalid token claim") + return nil, errors.New("invalid token claim") } if mode == perm_model.AccessModeWrite && claims.Op != "upload" { - return nil, fmt.Errorf("invalid token claim") + return nil, errors.New("invalid token claim") } u, err := user_model.GetUserByID(ctx, claims.UserID) @@ -592,12 +593,12 @@ func handleLFSToken(ctx stdCtx.Context, tokenSHA string, target *repo_model.Repo func parseToken(ctx stdCtx.Context, authorization string, target *repo_model.Repository, mode perm_model.AccessMode) (*user_model.User, error) { if authorization == "" { - return nil, fmt.Errorf("no token") + return nil, errors.New("no token") } parts := strings.SplitN(authorization, " ", 2) if len(parts) != 2 { - return nil, fmt.Errorf("no token") + return nil, errors.New("no token") } tokenSHA := parts[1] switch strings.ToLower(parts[0]) { @@ -606,7 +607,7 @@ func parseToken(ctx stdCtx.Context, authorization string, target *repo_model.Rep case "token": return handleLFSToken(ctx, tokenSHA, target, mode) } - return nil, fmt.Errorf("token not found") + return nil, errors.New("token not found") } func requireAuth(ctx *context.Context) { diff --git a/services/mailer/mail.go b/services/mailer/mail.go index f7e5b0c9f0..aa51cbdbcf 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -8,6 +8,7 @@ import ( "bytes" "context" "encoding/base64" + "errors" "fmt" "html/template" "io" @@ -117,7 +118,7 @@ func (b64embedder *mailAttachmentBase64Embedder) AttachmentSrcToBase64DataURI(ct attachmentUUID, ok = strings.CutPrefix(parsedSrc.RepoSubPath, "/attachments/") } if !ok { - return "", fmt.Errorf("not an attachment") + return "", errors.New("not an attachment") } } attachment, err := repo_model.GetAttachmentByUUID(ctx, attachmentUUID) @@ -126,10 +127,10 @@ func (b64embedder *mailAttachmentBase64Embedder) AttachmentSrcToBase64DataURI(ct } if attachment.RepoID != b64embedder.repo.ID { - return "", fmt.Errorf("attachment does not belong to the repository") + return "", errors.New("attachment does not belong to the repository") } if attachment.Size+b64embedder.estimateSize > b64embedder.maxSize { - return "", fmt.Errorf("total embedded images exceed max limit") + return "", errors.New("total embedded images exceed max limit") } fr, err := storage.Attachments.Open(attachment.RelativePath()) @@ -146,7 +147,7 @@ func (b64embedder *mailAttachmentBase64Embedder) AttachmentSrcToBase64DataURI(ct mimeType := typesniffer.DetectContentType(content) if !mimeType.IsImage() { - return "", fmt.Errorf("not an image") + return "", errors.New("not an image") } encoded := base64.StdEncoding.EncodeToString(content) diff --git a/services/mailer/mail_team_invite.go b/services/mailer/mail_team_invite.go index 1fbade7e23..f4aa788dec 100644 --- a/services/mailer/mail_team_invite.go +++ b/services/mailer/mail_team_invite.go @@ -6,6 +6,7 @@ package mailer import ( "bytes" "context" + "errors" "fmt" "net/url" @@ -38,10 +39,10 @@ func MailTeamInvite(ctx context.Context, inviter *user_model.User, team *org_mod if err != nil && !user_model.IsErrUserNotExist(err) { return err } else if user != nil && user.ProhibitLogin { - return fmt.Errorf("login is prohibited for the invited user") + return errors.New("login is prohibited for the invited user") } - inviteRedirect := url.QueryEscape(fmt.Sprintf("/org/invite/%s", invite.Token)) + inviteRedirect := url.QueryEscape("/org/invite/" + invite.Token) inviteURL := fmt.Sprintf("%suser/sign_up?redirect_to=%s", setting.AppURL, inviteRedirect) if (err == nil && user != nil) || setting.Service.DisableRegistration || setting.Service.AllowOnlyExternalRegistration { diff --git a/services/mailer/mail_test.go b/services/mailer/mail_test.go index 6aced705f3..7a47cf3876 100644 --- a/services/mailer/mail_test.go +++ b/services/mailer/mail_test.go @@ -467,7 +467,7 @@ func TestFromDisplayName(t *testing.T) { t.Run(tc.userDisplayName, func(t *testing.T) { user := &user_model.User{FullName: tc.userDisplayName, Name: "tmp"} got := fromDisplayName(user) - assert.EqualValues(t, tc.fromDisplayName, got) + assert.Equal(t, tc.fromDisplayName, got) }) } @@ -484,7 +484,7 @@ func TestFromDisplayName(t *testing.T) { setting.Domain = oldDomain }() - assert.EqualValues(t, "Mister X (by Code IT on [code.it])", fromDisplayName(&user_model.User{FullName: "Mister X", Name: "tmp"})) + assert.Equal(t, "Mister X (by Code IT on [code.it])", fromDisplayName(&user_model.User{FullName: "Mister X", Name: "tmp"})) }) } diff --git a/services/mailer/notify.go b/services/mailer/notify.go index a27177e8f5..77c366fe31 100644 --- a/services/mailer/notify.go +++ b/services/mailer/notify.go @@ -31,15 +31,16 @@ func (m *mailNotifier) CreateIssueComment(ctx context.Context, doer *user_model. issue *issues_model.Issue, comment *issues_model.Comment, mentions []*user_model.User, ) { var act activities_model.ActionType - if comment.Type == issues_model.CommentTypeClose { + switch comment.Type { + case issues_model.CommentTypeClose: act = activities_model.ActionCloseIssue - } else if comment.Type == issues_model.CommentTypeReopen { + case issues_model.CommentTypeReopen: act = activities_model.ActionReopenIssue - } else if comment.Type == issues_model.CommentTypeComment { + case issues_model.CommentTypeComment: act = activities_model.ActionCommentIssue - } else if comment.Type == issues_model.CommentTypeCode { + case issues_model.CommentTypeCode: act = activities_model.ActionCommentIssue - } else if comment.Type == issues_model.CommentTypePullRequestPush { + case issues_model.CommentTypePullRequestPush: act = 0 } @@ -95,11 +96,12 @@ func (m *mailNotifier) NewPullRequest(ctx context.Context, pr *issues_model.Pull func (m *mailNotifier) PullRequestReview(ctx context.Context, pr *issues_model.PullRequest, r *issues_model.Review, comment *issues_model.Comment, mentions []*user_model.User) { var act activities_model.ActionType - if comment.Type == issues_model.CommentTypeClose { + switch comment.Type { + case issues_model.CommentTypeClose: act = activities_model.ActionCloseIssue - } else if comment.Type == issues_model.CommentTypeReopen { + case issues_model.CommentTypeReopen: act = activities_model.ActionReopenIssue - } else if comment.Type == issues_model.CommentTypeComment { + case issues_model.CommentTypeComment: act = activities_model.ActionCommentPull } if err := MailParticipantsComment(ctx, comment, act, pr.Issue, mentions); err != nil { diff --git a/services/mailer/sender/smtp.go b/services/mailer/sender/smtp.go index c53c3da997..8dc1b40b74 100644 --- a/services/mailer/sender/smtp.go +++ b/services/mailer/sender/smtp.go @@ -5,6 +5,7 @@ package sender import ( "crypto/tls" + "errors" "fmt" "io" "net" @@ -99,7 +100,7 @@ func (s *SMTPSender) Send(from string, to []string, msg io.WriterTo) error { canAuth, options := client.Extension("AUTH") if len(opts.User) > 0 { if !canAuth { - return fmt.Errorf("SMTP server does not support AUTH, but credentials provided") + return errors.New("SMTP server does not support AUTH, but credentials provided") } var auth smtp.Auth diff --git a/services/mailer/sender/smtp_auth.go b/services/mailer/sender/smtp_auth.go index 260b12437b..c60e0dbfbb 100644 --- a/services/mailer/sender/smtp_auth.go +++ b/services/mailer/sender/smtp_auth.go @@ -4,6 +4,7 @@ package sender import ( + "errors" "fmt" "github.com/Azure/go-ntlmssp" @@ -60,7 +61,7 @@ func (a *ntlmAuth) Start(server *smtp.ServerInfo) (string, []byte, error) { func (a *ntlmAuth) Next(fromServer []byte, more bool) ([]byte, error) { if more { if len(fromServer) == 0 { - return nil, fmt.Errorf("ntlm ChallengeMessage is empty") + return nil, errors.New("ntlm ChallengeMessage is empty") } authenticateMessage, err := ntlmssp.ProcessChallenge(fromServer, a.username, a.password, a.domainNeeded) return authenticateMessage, err diff --git a/services/markup/renderhelper_codepreview.go b/services/markup/renderhelper_codepreview.go index d638af7ff0..fa1eb824a2 100644 --- a/services/markup/renderhelper_codepreview.go +++ b/services/markup/renderhelper_codepreview.go @@ -6,7 +6,7 @@ package markup import ( "bufio" "context" - "fmt" + "errors" "html/template" "strings" @@ -14,13 +14,13 @@ import ( "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/charset" + "code.gitea.io/gitea/modules/git/languagestats" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/indexer/code" "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" gitea_context "code.gitea.io/gitea/services/context" - "code.gitea.io/gitea/services/repository/files" ) func renderRepoFileCodePreview(ctx context.Context, opts markup.RenderCodePreviewOptions) (template.HTML, error) { @@ -38,7 +38,7 @@ func renderRepoFileCodePreview(ctx context.Context, opts markup.RenderCodePrevie webCtx := gitea_context.GetWebContext(ctx) if webCtx == nil { - return "", fmt.Errorf("context is not a web context") + return "", errors.New("context is not a web context") } doer := webCtx.Doer @@ -61,14 +61,14 @@ func renderRepoFileCodePreview(ctx context.Context, opts markup.RenderCodePrevie return "", err } - language, _ := files.TryGetContentLanguage(gitRepo, opts.CommitID, opts.FilePath) + language, _ := languagestats.GetFileLanguage(ctx, gitRepo, opts.CommitID, opts.FilePath) blob, err := commit.GetBlobByPath(opts.FilePath) if err != nil { return "", err } if blob.Size() > setting.UI.MaxDisplayFileSize { - return "", fmt.Errorf("file is too large") + return "", errors.New("file is too large") } dataRc, err := blob.DataAsync() diff --git a/services/markup/renderhelper_issueicontitle.go b/services/markup/renderhelper_issueicontitle.go index fd8f9d43fa..27b5595fa9 100644 --- a/services/markup/renderhelper_issueicontitle.go +++ b/services/markup/renderhelper_issueicontitle.go @@ -5,6 +5,7 @@ package markup import ( "context" + "errors" "fmt" "html/template" @@ -20,7 +21,7 @@ import ( func renderRepoIssueIconTitle(ctx context.Context, opts markup.RenderIssueIconTitleOptions) (_ template.HTML, err error) { webCtx := gitea_context.GetWebContext(ctx) if webCtx == nil { - return "", fmt.Errorf("context is not a web context") + return "", errors.New("context is not a web context") } textIssueIndex := fmt.Sprintf("(#%d)", opts.IssueIndex) diff --git a/services/markup/renderhelper_mention_test.go b/services/markup/renderhelper_mention_test.go index d05fbb6fba..d54ab13a48 100644 --- a/services/markup/renderhelper_mention_test.go +++ b/services/markup/renderhelper_mention_test.go @@ -37,7 +37,7 @@ func TestRenderHelperMention(t *testing.T) { assert.False(t, FormalRenderHelperFuncs().IsUsernameMentionable(t.Context(), userNoSuch)) // when using web context, use user.IsUserVisibleToViewer to check - req, err := http.NewRequest("GET", "/", nil) + req, err := http.NewRequest(http.MethodGet, "/", nil) assert.NoError(t, err) base := gitea_context.NewBaseContextForTest(httptest.NewRecorder(), req) giteaCtx := gitea_context.NewWebContext(base, &contexttest.MockRender{}, nil) diff --git a/services/migrations/codebase.go b/services/migrations/codebase.go index 880dd21497..240c7bcdc9 100644 --- a/services/migrations/codebase.go +++ b/services/migrations/codebase.go @@ -134,7 +134,7 @@ func (d *CodebaseDownloader) callAPI(ctx context.Context, endpoint string, param u.RawQuery = query.Encode() } - req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) if err != nil { return err } diff --git a/services/migrations/codecommit.go b/services/migrations/codecommit.go index c45f9e5943..4b2634ef8a 100644 --- a/services/migrations/codecommit.go +++ b/services/migrations/codecommit.go @@ -5,7 +5,7 @@ package migrations import ( "context" - "fmt" + "errors" "net/url" "strconv" "strings" @@ -42,13 +42,13 @@ func (c *CodeCommitDownloaderFactory) New(ctx context.Context, opts base.Migrate hostElems := strings.Split(u.Host, ".") if len(hostElems) != 4 { - return nil, fmt.Errorf("cannot get the region from clone URL") + return nil, errors.New("cannot get the region from clone URL") } region := hostElems[1] pathElems := strings.Split(u.Path, "/") if len(pathElems) == 0 { - return nil, fmt.Errorf("cannot get the repo name from clone URL") + return nil, errors.New("cannot get the repo name from clone URL") } repoName := pathElems[len(pathElems)-1] diff --git a/services/migrations/gitea_downloader.go b/services/migrations/gitea_downloader.go index f92f318293..5d48d2f003 100644 --- a/services/migrations/gitea_downloader.go +++ b/services/migrations/gitea_downloader.go @@ -298,7 +298,7 @@ func (g *GiteaDownloader) convertGiteaRelease(rel *gitea_sdk.Release) *base.Rele } // FIXME: for a private download? - req, err := http.NewRequest("GET", assetDownloadURL, nil) + req, err := http.NewRequest(http.MethodGet, assetDownloadURL, nil) if err != nil { return nil, err } diff --git a/services/migrations/gitea_downloader_test.go b/services/migrations/gitea_downloader_test.go index da56120065..bb1760e889 100644 --- a/services/migrations/gitea_downloader_test.go +++ b/services/migrations/gitea_downloader_test.go @@ -47,7 +47,7 @@ func TestGiteaDownloadRepo(t *testing.T) { topics, err := downloader.GetTopics(ctx) assert.NoError(t, err) sort.Strings(topics) - assert.EqualValues(t, []string{"ci", "gitea", "migration", "test"}, topics) + assert.Equal(t, []string{"ci", "gitea", "migration", "test"}, topics) labels, err := downloader.GetLabels(ctx) assert.NoError(t, err) @@ -134,7 +134,7 @@ func TestGiteaDownloadRepo(t *testing.T) { assert.NoError(t, err) assert.True(t, isEnd) assert.Len(t, issues, 7) - assert.EqualValues(t, "open", issues[0].State) + assert.Equal(t, "open", issues[0].State) issues, isEnd, err = downloader.GetIssues(ctx, 3, 2) assert.NoError(t, err) diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index b17cc3ce41..cd5af39910 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -1017,7 +1017,7 @@ func (g *GiteaLocalUploader) remapLocalUser(ctx context.Context, source user_mod func (g *GiteaLocalUploader) remapExternalUser(ctx context.Context, source user_model.ExternalUserMigrated) (userid int64, err error) { userid, ok := g.userMap[source.GetExternalID()] if !ok { - userid, err = user_model.GetUserIDByExternalUserID(ctx, g.gitServiceType.Name(), fmt.Sprintf("%d", source.GetExternalID())) + userid, err = user_model.GetUserIDByExternalUserID(ctx, g.gitServiceType.Name(), strconv.FormatInt(source.GetExternalID(), 10)) if err != nil { log.Error("GetUserIDByExternalUserID: %v", err) return 0, err diff --git a/services/migrations/gitea_uploader_test.go b/services/migrations/gitea_uploader_test.go index f52d4157c8..1970c0550c 100644 --- a/services/migrations/gitea_uploader_test.go +++ b/services/migrations/gitea_uploader_test.go @@ -64,7 +64,7 @@ func TestGiteaUploadRepo(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, Name: repoName}) assert.True(t, repo.HasWiki()) - assert.EqualValues(t, repo_model.RepositoryReady, repo.Status) + assert.Equal(t, repo_model.RepositoryReady, repo.Status) milestones, err := db.Find[issues_model.Milestone](db.DefaultContext, issues_model.FindMilestoneOptions{ RepoID: repo.ID, @@ -152,7 +152,7 @@ func TestGiteaUploadRemapLocalUser(t *testing.T) { uploader.userMap = make(map[int64]int64) err := uploader.remapUser(ctx, &source, &target) assert.NoError(t, err) - assert.EqualValues(t, doer.ID, target.GetUserID()) + assert.Equal(t, doer.ID, target.GetUserID()) // // The externalID matches a known user but the name does not match, @@ -163,7 +163,7 @@ func TestGiteaUploadRemapLocalUser(t *testing.T) { uploader.userMap = make(map[int64]int64) err = uploader.remapUser(ctx, &source, &target) assert.NoError(t, err) - assert.EqualValues(t, doer.ID, target.GetUserID()) + assert.Equal(t, doer.ID, target.GetUserID()) // // The externalID and externalName match an existing user, everything @@ -174,7 +174,7 @@ func TestGiteaUploadRemapLocalUser(t *testing.T) { uploader.userMap = make(map[int64]int64) err = uploader.remapUser(ctx, &source, &target) assert.NoError(t, err) - assert.EqualValues(t, user.ID, target.GetUserID()) + assert.Equal(t, user.ID, target.GetUserID()) } func TestGiteaUploadRemapExternalUser(t *testing.T) { @@ -202,7 +202,7 @@ func TestGiteaUploadRemapExternalUser(t *testing.T) { target := repo_model.Release{} err := uploader.remapUser(ctx, &source, &target) assert.NoError(t, err) - assert.EqualValues(t, doer.ID, target.GetUserID()) + assert.Equal(t, doer.ID, target.GetUserID()) // // Link the external ID to an existing user @@ -225,7 +225,7 @@ func TestGiteaUploadRemapExternalUser(t *testing.T) { target = repo_model.Release{} err = uploader.remapUser(ctx, &source, &target) assert.NoError(t, err) - assert.EqualValues(t, linkedUser.ID, target.GetUserID()) + assert.Equal(t, linkedUser.ID, target.GetUserID()) } func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { @@ -239,7 +239,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { assert.NoError(t, git.InitRepository(git.DefaultContext, fromRepo.RepoPath(), false, fromRepo.ObjectFormatName)) err := git.NewCommand("symbolic-ref").AddDynamicArguments("HEAD", git.BranchPrefix+baseRef).Run(git.DefaultContext, &git.RunOpts{Dir: fromRepo.RepoPath()}) assert.NoError(t, err) - assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s", fromRepo.RepoPath())), 0o644)) + assert.NoError(t, os.WriteFile(filepath.Join(fromRepo.RepoPath(), "README.md"), []byte("# Testing Repository\n\nOriginally created in: "+fromRepo.RepoPath()), 0o644)) assert.NoError(t, git.AddChanges(fromRepo.RepoPath(), true)) signature := git.Signature{ Email: "test@example.com", @@ -287,7 +287,7 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { })) _, _, err = git.NewCommand("checkout", "-b").AddDynamicArguments(forkHeadRef).RunStdString(git.DefaultContext, &git.RunOpts{Dir: forkRepo.RepoPath()}) assert.NoError(t, err) - assert.NoError(t, os.WriteFile(filepath.Join(forkRepo.RepoPath(), "README.md"), []byte(fmt.Sprintf("# branch2 %s", forkRepo.RepoPath())), 0o644)) + assert.NoError(t, os.WriteFile(filepath.Join(forkRepo.RepoPath(), "README.md"), []byte("# branch2 "+forkRepo.RepoPath()), 0o644)) assert.NoError(t, git.AddChanges(forkRepo.RepoPath(), true)) assert.NoError(t, git.CommitChanges(forkRepo.RepoPath(), git.CommitChangesOptions{ Committer: &signature, @@ -508,14 +508,14 @@ func TestGiteaUploadUpdateGitForPullRequest(t *testing.T) { head, err := uploader.updateGitForPullRequest(ctx, &testCase.pr) assert.NoError(t, err) - assert.EqualValues(t, testCase.head, head) + assert.Equal(t, testCase.head, head) log.Info(stopMark) logFiltered, logStopped := logChecker.Check(5 * time.Second) assert.True(t, logStopped) if len(testCase.logFilter) > 0 { - assert.EqualValues(t, testCase.logFiltered, logFiltered, "for log message filters: %v", testCase.logFilter) + assert.Equal(t, testCase.logFiltered, logFiltered, "for log message filters: %v", testCase.logFilter) } }) } diff --git a/services/migrations/github.go b/services/migrations/github.go index b00d6ed27f..ce3e5ced9e 100644 --- a/services/migrations/github.go +++ b/services/migrations/github.go @@ -133,7 +133,7 @@ func (g *GithubDownloaderV3) LogString() string { func (g *GithubDownloaderV3) addClient(client *http.Client, baseURL string) { githubClient := github.NewClient(client) if baseURL != "https://github.com" { - githubClient, _ = github.NewClient(client).WithEnterpriseURLs(baseURL, baseURL) + githubClient, _ = githubClient.WithEnterpriseURLs(baseURL, baseURL) } g.clients = append(g.clients, githubClient) g.rates = append(g.rates, nil) @@ -358,7 +358,7 @@ func (g *GithubDownloaderV3) convertGithubRelease(ctx context.Context, rel *gith } g.waitAndPickClient(ctx) - req, err := http.NewRequestWithContext(ctx, "GET", redirectURL, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, redirectURL, nil) if err != nil { return nil, err } @@ -872,3 +872,18 @@ func (g *GithubDownloaderV3) GetReviews(ctx context.Context, reviewable base.Rev } return allReviews, nil } + +// FormatCloneURL add authentication into remote URLs +func (g *GithubDownloaderV3) FormatCloneURL(opts MigrateOptions, remoteAddr string) (string, error) { + u, err := url.Parse(remoteAddr) + if err != nil { + return "", err + } + if len(opts.AuthToken) > 0 { + // "multiple tokens" are used to benefit more "API rate limit quota" + // git clone doesn't count for rate limits, so only use the first token. + // source: https://github.com/orgs/community/discussions/44515 + u.User = url.UserPassword("oauth2", strings.Split(opts.AuthToken, ",")[0]) + } + return u.String(), nil +} diff --git a/services/migrations/github_test.go b/services/migrations/github_test.go index 2625fb62ec..6d1a5378b9 100644 --- a/services/migrations/github_test.go +++ b/services/migrations/github_test.go @@ -12,6 +12,7 @@ import ( base "code.gitea.io/gitea/modules/migration" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestGitHubDownloadRepo(t *testing.T) { @@ -429,3 +430,36 @@ func TestGitHubDownloadRepo(t *testing.T) { }, }, reviews) } + +func TestGithubMultiToken(t *testing.T) { + testCases := []struct { + desc string + token string + expectedCloneURL string + }{ + { + desc: "Single Token", + token: "single_token", + expectedCloneURL: "https://oauth2:single_token@github.com", + }, + { + desc: "Multi Token", + token: "token1,token2", + expectedCloneURL: "https://oauth2:token1@github.com", + }, + } + factory := GithubDownloaderV3Factory{} + + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + opts := base.MigrateOptions{CloneAddr: "https://github.com/go-gitea/gitea", AuthToken: tC.token} + client, err := factory.New(t.Context(), opts) + require.NoError(t, err) + + cloneURL, err := client.FormatCloneURL(opts, "https://github.com") + require.NoError(t, err) + + assert.Equal(t, tC.expectedCloneURL, cloneURL) + }) + } +} diff --git a/services/migrations/gitlab.go b/services/migrations/gitlab.go index 4bed8e2f6c..a19a04bc44 100644 --- a/services/migrations/gitlab.go +++ b/services/migrations/gitlab.go @@ -341,7 +341,7 @@ func (g *GitlabDownloader) convertGitlabRelease(ctx context.Context, rel *gitlab return io.NopCloser(strings.NewReader(link.URL)), nil } - req, err := http.NewRequest("GET", link.URL, nil) + req, err := http.NewRequest(http.MethodGet, link.URL, nil) if err != nil { return nil, err } diff --git a/services/migrations/gitlab_test.go b/services/migrations/gitlab_test.go index 52f5827dfe..73a1b6a276 100644 --- a/services/migrations/gitlab_test.go +++ b/services/migrations/gitlab_test.go @@ -50,7 +50,7 @@ func TestGitlabDownloadRepo(t *testing.T) { topics, err := downloader.GetTopics(ctx) assert.NoError(t, err) assert.Len(t, topics, 2) - assert.EqualValues(t, []string{"migration", "test"}, topics) + assert.Equal(t, []string{"migration", "test"}, topics) milestones, err := downloader.GetMilestones(ctx) assert.NoError(t, err) @@ -501,7 +501,7 @@ func TestAwardsToReactions(t *testing.T) { assert.NoError(t, json.Unmarshal([]byte(testResponse), &awards)) reactions := downloader.awardsToReactions(awards) - assert.EqualValues(t, []*base.Reaction{ + assert.Equal(t, []*base.Reaction{ { UserName: "lafriks", UserID: 1241334, @@ -593,7 +593,7 @@ func TestNoteToComment(t *testing.T) { for i, note := range notes { actualComment := *downloader.convertNoteToComment(17, ¬e) - assert.EqualValues(t, actualComment, comments[i]) + assert.Equal(t, actualComment, comments[i]) } } diff --git a/services/migrations/migrate.go b/services/migrations/migrate.go index 5dda12286f..961abe16f4 100644 --- a/services/migrations/migrate.go +++ b/services/migrations/migrate.go @@ -6,6 +6,7 @@ package migrations import ( "context" + "errors" "fmt" "net" "net/url" @@ -211,7 +212,7 @@ func migrateRepository(ctx context.Context, doer *user_model.User, downloader ba if cloneURL.Scheme == "file" || cloneURL.Scheme == "" { if cloneAddrURL.Scheme != "file" && cloneAddrURL.Scheme != "" { - return fmt.Errorf("repo info has changed from external to local filesystem") + return errors.New("repo info has changed from external to local filesystem") } } diff --git a/services/migrations/onedev.go b/services/migrations/onedev.go index 4ce35dd12e..e052cba0cc 100644 --- a/services/migrations/onedev.go +++ b/services/migrations/onedev.go @@ -128,7 +128,7 @@ func (d *OneDevDownloader) callAPI(ctx context.Context, endpoint string, paramet u.RawQuery = query.Encode() } - req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) if err != nil { return err } diff --git a/services/mirror/mirror.go b/services/mirror/mirror.go index e029bbb1d6..7fb7fabb75 100644 --- a/services/mirror/mirror.go +++ b/services/mirror/mirror.go @@ -5,7 +5,7 @@ package mirror import ( "context" - "fmt" + "errors" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/log" @@ -29,7 +29,7 @@ func doMirrorSync(ctx context.Context, req *SyncRequest) { } } -var errLimit = fmt.Errorf("reached limit") +var errLimit = errors.New("reached limit") // Update checks and updates mirror repositories. func Update(ctx context.Context, pullLimit, pushLimit int) error { @@ -68,7 +68,7 @@ func Update(ctx context.Context, pullLimit, pushLimit int) error { // Check we've not been cancelled select { case <-ctx.Done(): - return fmt.Errorf("aborted") + return errors.New("aborted") default: } diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index fa5b9934ec..c43a4ef04a 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -437,7 +437,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo } for _, branch := range branches { - cache.Remove(m.Repo.GetCommitsCountCacheKey(branch.Name, true)) + cache.Remove(m.Repo.GetCommitsCountCacheKey(branch, true)) } m.UpdatedUnix = timeutil.TimeStampNow() diff --git a/services/mirror/mirror_pull_test.go b/services/mirror/mirror_pull_test.go index 67ef37c954..97859be5b0 100644 --- a/services/mirror/mirror_pull_test.go +++ b/services/mirror/mirror_pull_test.go @@ -24,45 +24,45 @@ func Test_parseRemoteUpdateOutput(t *testing.T) { ` results := parseRemoteUpdateOutput(output, "origin") assert.Len(t, results, 10) - assert.EqualValues(t, "refs/tags/v0.1.8", results[0].refName.String()) - assert.EqualValues(t, gitShortEmptySha, results[0].oldCommitID) - assert.EqualValues(t, "", results[0].newCommitID) + assert.Equal(t, "refs/tags/v0.1.8", results[0].refName.String()) + assert.Equal(t, gitShortEmptySha, results[0].oldCommitID) + assert.Empty(t, results[0].newCommitID) - assert.EqualValues(t, "refs/heads/master", results[1].refName.String()) - assert.EqualValues(t, gitShortEmptySha, results[1].oldCommitID) - assert.EqualValues(t, "", results[1].newCommitID) + assert.Equal(t, "refs/heads/master", results[1].refName.String()) + assert.Equal(t, gitShortEmptySha, results[1].oldCommitID) + assert.Empty(t, results[1].newCommitID) - assert.EqualValues(t, "refs/heads/test1", results[2].refName.String()) - assert.EqualValues(t, "", results[2].oldCommitID) - assert.EqualValues(t, gitShortEmptySha, results[2].newCommitID) + assert.Equal(t, "refs/heads/test1", results[2].refName.String()) + assert.Empty(t, results[2].oldCommitID) + assert.Equal(t, gitShortEmptySha, results[2].newCommitID) - assert.EqualValues(t, "refs/tags/tag1", results[3].refName.String()) - assert.EqualValues(t, "", results[3].oldCommitID) - assert.EqualValues(t, gitShortEmptySha, results[3].newCommitID) + assert.Equal(t, "refs/tags/tag1", results[3].refName.String()) + assert.Empty(t, results[3].oldCommitID) + assert.Equal(t, gitShortEmptySha, results[3].newCommitID) - assert.EqualValues(t, "refs/heads/test2", results[4].refName.String()) - assert.EqualValues(t, "f895a1e", results[4].oldCommitID) - assert.EqualValues(t, "957a993", results[4].newCommitID) + assert.Equal(t, "refs/heads/test2", results[4].refName.String()) + assert.Equal(t, "f895a1e", results[4].oldCommitID) + assert.Equal(t, "957a993", results[4].newCommitID) - assert.EqualValues(t, "refs/heads/test3", results[5].refName.String()) - assert.EqualValues(t, "957a993", results[5].oldCommitID) - assert.EqualValues(t, "a87ba5f", results[5].newCommitID) + assert.Equal(t, "refs/heads/test3", results[5].refName.String()) + assert.Equal(t, "957a993", results[5].oldCommitID) + assert.Equal(t, "a87ba5f", results[5].newCommitID) - assert.EqualValues(t, "refs/pull/26595/head", results[6].refName.String()) - assert.EqualValues(t, gitShortEmptySha, results[6].oldCommitID) - assert.EqualValues(t, "", results[6].newCommitID) + assert.Equal(t, "refs/pull/26595/head", results[6].refName.String()) + assert.Equal(t, gitShortEmptySha, results[6].oldCommitID) + assert.Empty(t, results[6].newCommitID) - assert.EqualValues(t, "refs/pull/26595/merge", results[7].refName.String()) - assert.EqualValues(t, gitShortEmptySha, results[7].oldCommitID) - assert.EqualValues(t, "", results[7].newCommitID) + assert.Equal(t, "refs/pull/26595/merge", results[7].refName.String()) + assert.Equal(t, gitShortEmptySha, results[7].oldCommitID) + assert.Empty(t, results[7].newCommitID) - assert.EqualValues(t, "refs/pull/25873/head", results[8].refName.String()) - assert.EqualValues(t, "e0639e38fb", results[8].oldCommitID) - assert.EqualValues(t, "6db2410489", results[8].newCommitID) + assert.Equal(t, "refs/pull/25873/head", results[8].refName.String()) + assert.Equal(t, "e0639e38fb", results[8].oldCommitID) + assert.Equal(t, "6db2410489", results[8].newCommitID) - assert.EqualValues(t, "refs/pull/25873/merge", results[9].refName.String()) - assert.EqualValues(t, "1c97ebc746", results[9].oldCommitID) - assert.EqualValues(t, "976d27d52f", results[9].newCommitID) + assert.Equal(t, "refs/pull/25873/merge", results[9].refName.String()) + assert.Equal(t, "1c97ebc746", results[9].oldCommitID) + assert.Equal(t, "976d27d52f", results[9].newCommitID) } func Test_checkRecoverableSyncError(t *testing.T) { diff --git a/services/oauth2_provider/access_token.go b/services/oauth2_provider/access_token.go index 5cb6fb64c5..52a73c9572 100644 --- a/services/oauth2_provider/access_token.go +++ b/services/oauth2_provider/access_token.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "slices" + "strconv" "strings" auth "code.gitea.io/gitea/models/auth" @@ -177,7 +178,7 @@ func NewAccessTokenResponse(ctx context.Context, grant *auth.OAuth2Grant, server ExpiresAt: jwt.NewNumericDate(expirationDate.AsTime()), Issuer: setting.AppURL, Audience: []string{app.ClientID}, - Subject: fmt.Sprint(grant.UserID), + Subject: strconv.FormatInt(grant.UserID, 10), }, Nonce: grant.Nonce, } diff --git a/services/oauth2_provider/jwtsigningkey.go b/services/oauth2_provider/jwtsigningkey.go index 6c668db463..3bc4f49410 100644 --- a/services/oauth2_provider/jwtsigningkey.go +++ b/services/oauth2_provider/jwtsigningkey.go @@ -31,7 +31,7 @@ type ErrInvalidAlgorithmType struct { } func (err ErrInvalidAlgorithmType) Error() string { - return fmt.Sprintf("JWT signing algorithm is not supported: %s", err.Algorithm) + return "JWT signing algorithm is not supported: " + err.Algorithm } // JWTSigningKey represents a algorithm/key pair to sign JWTs diff --git a/services/oauth2_provider/token.go b/services/oauth2_provider/token.go index b71b11906e..383bcdb3eb 100644 --- a/services/oauth2_provider/token.go +++ b/services/oauth2_provider/token.go @@ -4,6 +4,7 @@ package oauth2_provider //nolint import ( + "errors" "fmt" "time" @@ -44,12 +45,12 @@ func ParseToken(jwtToken string, signingKey JWTSigningKey) (*Token, error) { return nil, err } if !parsedToken.Valid { - return nil, fmt.Errorf("invalid token") + return nil, errors.New("invalid token") } var token *Token var ok bool if token, ok = parsedToken.Claims.(*Token); !ok || !parsedToken.Valid { - return nil, fmt.Errorf("invalid token") + return nil, errors.New("invalid token") } return token, nil } diff --git a/services/org/team.go b/services/org/team.go index ee3bd898ea..6890dafd90 100644 --- a/services/org/team.go +++ b/services/org/team.go @@ -259,37 +259,6 @@ func AddTeamMember(ctx context.Context, team *organization.Team, user *user_mode } team.NumMembers++ - - // Give access to team repositories. - // update exist access if mode become bigger - subQuery := builder.Select("repo_id").From("team_repo"). - Where(builder.Eq{"team_id": team.ID}) - - if _, err := sess.Where("user_id=?", user.ID). - In("repo_id", subQuery). - And("mode < ?", team.AccessMode). - SetExpr("mode", team.AccessMode). - Update(new(access_model.Access)); err != nil { - return fmt.Errorf("update user accesses: %w", err) - } - - // for not exist access - var repoIDs []int64 - accessSubQuery := builder.Select("repo_id").From("access").Where(builder.Eq{"user_id": user.ID}) - if err := sess.SQL(subQuery.And(builder.NotIn("repo_id", accessSubQuery))).Find(&repoIDs); err != nil { - return fmt.Errorf("select id accesses: %w", err) - } - - accesses := make([]*access_model.Access, 0, 100) - for i, repoID := range repoIDs { - accesses = append(accesses, &access_model.Access{RepoID: repoID, UserID: user.ID, Mode: team.AccessMode}) - if (i%100 == 0 || i == len(repoIDs)-1) && len(accesses) > 0 { - if err = db.Insert(ctx, accesses); err != nil { - return fmt.Errorf("insert new user accesses: %w", err) - } - accesses = accesses[:0] - } - } return nil }) if err != nil { diff --git a/services/org/team_test.go b/services/org/team_test.go index 3791776e46..831fe1669f 100644 --- a/services/org/team_test.go +++ b/services/org/team_test.go @@ -88,7 +88,7 @@ func TestUpdateTeam(t *testing.T) { assert.True(t, strings.HasPrefix(team.Description, "A long description!")) access := unittest.AssertExistsAndLoadBean(t, &access_model.Access{UserID: 4, RepoID: 3}) - assert.EqualValues(t, perm.AccessModeAdmin, access.Mode) + assert.Equal(t, perm.AccessModeAdmin, access.Mode) unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID}) } @@ -166,24 +166,6 @@ func TestRemoveTeamMember(t *testing.T) { assert.True(t, organization.IsErrLastOrgOwner(err)) } -func TestRepository_RecalculateAccesses3(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - team5 := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}) - user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29}) - - has, err := db.GetEngine(db.DefaultContext).Get(&access_model.Access{UserID: user29.ID, RepoID: 23}) - assert.NoError(t, err) - assert.False(t, has) - - // adding user29 to team5 should add an explicit access row for repo 23 - // even though repo 23 is public - assert.NoError(t, AddTeamMember(db.DefaultContext, team5, user29)) - - has, err = db.GetEngine(db.DefaultContext).Get(&access_model.Access{UserID: user29.ID, RepoID: 23}) - assert.NoError(t, err) - assert.True(t, has) -} - func TestIncludesAllRepositoriesTeams(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) diff --git a/services/org/user_test.go b/services/org/user_test.go index 96d1a1c8ca..c61d600d90 100644 --- a/services/org/user_test.go +++ b/services/org/user_test.go @@ -53,7 +53,7 @@ func TestRemoveOrgUser(t *testing.T) { assert.NoError(t, RemoveOrgUser(db.DefaultContext, org, user)) unittest.AssertNotExistsBean(t, &organization.OrgUser{OrgID: org.ID, UID: user.ID}) org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: org.ID}) - assert.EqualValues(t, expectedNumMembers, org.NumMembers) + assert.Equal(t, expectedNumMembers, org.NumMembers) } org3 := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) diff --git a/services/packages/alpine/repository.go b/services/packages/alpine/repository.go index 27e6391980..277c188874 100644 --- a/services/packages/alpine/repository.go +++ b/services/packages/alpine/repository.go @@ -290,7 +290,7 @@ func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *package privPem, _ := pem.Decode([]byte(priv)) if privPem == nil { - return fmt.Errorf("failed to decode private key pem") + return errors.New("failed to decode private key pem") } privKey, err := x509.ParsePKCS1PrivateKey(privPem.Bytes) diff --git a/services/packages/arch/repository.go b/services/packages/arch/repository.go index a12af82ba5..438bb10837 100644 --- a/services/packages/arch/repository.go +++ b/services/packages/arch/repository.go @@ -13,6 +13,7 @@ import ( "fmt" "io" "os" + "strconv" "strings" packages_model "code.gitea.io/gitea/models/packages" @@ -372,8 +373,8 @@ func writeDescription(tw *tar.Writer, opts *entryOptions) error { {"MD5SUM", opts.Blob.HashMD5}, {"SHA256SUM", opts.Blob.HashSHA256}, {"PGPSIG", opts.Signature}, - {"CSIZE", fmt.Sprintf("%d", opts.Blob.Size)}, - {"ISIZE", fmt.Sprintf("%d", opts.FileMetadata.InstalledSize)}, + {"CSIZE", strconv.FormatInt(opts.Blob.Size, 10)}, + {"ISIZE", strconv.FormatInt(opts.FileMetadata.InstalledSize, 10)}, {"NAME", opts.Package.Name}, {"BASE", opts.FileMetadata.Base}, {"ARCH", opts.FileMetadata.Architecture}, @@ -382,7 +383,7 @@ func writeDescription(tw *tar.Writer, opts *entryOptions) error { {"URL", opts.VersionMetadata.ProjectURL}, {"LICENSE", strings.Join(opts.VersionMetadata.Licenses, "\n")}, {"GROUPS", strings.Join(opts.FileMetadata.Groups, "\n")}, - {"BUILDDATE", fmt.Sprintf("%d", opts.FileMetadata.BuildDate)}, + {"BUILDDATE", strconv.FormatInt(opts.FileMetadata.BuildDate, 10)}, {"PACKAGER", opts.FileMetadata.Packager}, {"PROVIDES", strings.Join(opts.FileMetadata.Provides, "\n")}, {"REPLACES", strings.Join(opts.FileMetadata.Replaces, "\n")}, diff --git a/services/packages/auth.go b/services/packages/auth.go index 4526a8e303..6e87643e29 100644 --- a/services/packages/auth.go +++ b/services/packages/auth.go @@ -4,6 +4,7 @@ package packages import ( + "errors" "fmt" "net/http" "strings" @@ -58,7 +59,7 @@ func ParseAuthorizationRequest(req *http.Request) (*PackageMeta, error) { parts := strings.SplitN(h, " ", 2) if len(parts) != 2 { log.Error("split token failed: %s", h) - return nil, fmt.Errorf("split token failed") + return nil, errors.New("split token failed") } return ParseAuthorizationToken(parts[1]) @@ -77,7 +78,7 @@ func ParseAuthorizationToken(tokenStr string) (*PackageMeta, error) { c, ok := token.Claims.(*packageClaims) if !token.Valid || !ok { - return nil, fmt.Errorf("invalid token claim") + return nil, errors.New("invalid token claim") } return &c.PackageMeta, nil diff --git a/services/packages/cargo/index.go b/services/packages/cargo/index.go index 0c8a98c40f..decb224b85 100644 --- a/services/packages/cargo/index.go +++ b/services/packages/cargo/index.go @@ -247,7 +247,7 @@ func createOrUpdateConfigFile(ctx context.Context, repo *repo_model.Repository, "Initialize Cargo Config", func(t *files_service.TemporaryUploadRepository) error { var b bytes.Buffer - err := json.NewEncoder(&b).Encode(BuildConfig(owner, setting.Service.RequireSignInView || owner.Visibility != structs.VisibleTypePublic || repo.IsPrivate)) + err := json.NewEncoder(&b).Encode(BuildConfig(owner, setting.Service.RequireSignInViewStrict || owner.Visibility != structs.VisibleTypePublic || repo.IsPrivate)) if err != nil { return err } diff --git a/services/packages/rpm/repository.go b/services/packages/rpm/repository.go index a7d196c15c..5027021c52 100644 --- a/services/packages/rpm/repository.go +++ b/services/packages/rpm/repository.go @@ -408,7 +408,6 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs [] files = append(files, f) } } - packageVersion := fmt.Sprintf("%s-%s", pd.FileMetadata.Version, pd.FileMetadata.Release) packages = append(packages, &Package{ Type: "rpm", Name: pd.Package.Name, @@ -437,7 +436,7 @@ func buildPrimary(ctx context.Context, pv *packages_model.PackageVersion, pfs [] Archive: pd.FileMetadata.ArchiveSize, }, Location: Location{ - Href: fmt.Sprintf("package/%s/%s/%s/%s-%s.%s.rpm", pd.Package.Name, packageVersion, pd.FileMetadata.Architecture, pd.Package.Name, packageVersion, pd.FileMetadata.Architecture), + Href: fmt.Sprintf("package/%s/%s/%s/%s-%s.%s.rpm", pd.Package.Name, pd.Version.Version, pd.FileMetadata.Architecture, pd.Package.Name, pd.Version.Version, pd.FileMetadata.Architecture), }, Format: Format{ License: pd.VersionMetadata.License, diff --git a/services/projects/issue.go b/services/projects/issue.go index 090d19d2f4..590fe960d5 100644 --- a/services/projects/issue.go +++ b/services/projects/issue.go @@ -5,7 +5,7 @@ package project import ( "context" - "fmt" + "errors" "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" @@ -29,7 +29,7 @@ func MoveIssuesOnProjectColumn(ctx context.Context, doer *user_model.User, colum return err } if int(count) != len(sortedIssueIDs) { - return fmt.Errorf("all issues have to be added to a project first") + return errors.New("all issues have to be added to a project first") } issues, err := issues_model.GetIssuesByIDs(ctx, issueIDs) diff --git a/services/projects/issue_test.go b/services/projects/issue_test.go index b6f0b1dae1..e76d31e757 100644 --- a/services/projects/issue_test.go +++ b/services/projects/issue_test.go @@ -130,7 +130,7 @@ func Test_Projects(t *testing.T) { }) assert.NoError(t, err) assert.Len(t, projects, 1) - assert.EqualValues(t, project1.ID, projects[0].ID) + assert.Equal(t, project1.ID, projects[0].ID) t.Run("Authenticated user", func(t *testing.T) { columnIssues, err := LoadIssuesFromProject(db.DefaultContext, projects[0], &issues_model.IssuesOptions{ diff --git a/services/pull/check.go b/services/pull/check.go index 9b159891d7..b036970fbf 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -400,7 +400,7 @@ func Init() error { prPatchCheckerQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "pr_patch_checker", handler) if prPatchCheckerQueue == nil { - return fmt.Errorf("unable to create pr_patch_checker queue") + return errors.New("unable to create pr_patch_checker queue") } go graceful.GetManager().RunWithCancel(prPatchCheckerQueue) diff --git a/services/pull/check_test.go b/services/pull/check_test.go index 5508a70f45..6d85ac158e 100644 --- a/services/pull/check_test.go +++ b/services/pull/check_test.go @@ -51,7 +51,7 @@ func TestPullRequest_AddToTaskQueue(t *testing.T) { select { case id := <-idChan: - assert.EqualValues(t, pr.ID, id) + assert.Equal(t, pr.ID, id) case <-time.After(time.Second): assert.FailNow(t, "Timeout: nothing was added to pullRequestQueue") } diff --git a/services/pull/merge.go b/services/pull/merge.go index 1e1ca55bc1..9804d8aac1 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -6,6 +6,7 @@ package pull import ( "context" + "errors" "fmt" "os" "path/filepath" @@ -59,7 +60,7 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue issueReference = "!" } - reviewedOn := fmt.Sprintf("Reviewed-on: %s", httplib.MakeAbsoluteURL(ctx, pr.Issue.Link())) + reviewedOn := "Reviewed-on: " + httplib.MakeAbsoluteURL(ctx, pr.Issue.Link()) reviewedBy := pr.GetApprovers(ctx) if mergeStyle != "" { @@ -621,13 +622,13 @@ func MergedManually(ctx context.Context, pr *issues_model.PullRequest, doer *use objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName) if len(commitID) != objectFormat.FullLength() { - return fmt.Errorf("Wrong commit ID") + return errors.New("Wrong commit ID") } commit, err := baseGitRepo.GetCommit(commitID) if err != nil { if git.IsErrNotExist(err) { - return fmt.Errorf("Wrong commit ID") + return errors.New("Wrong commit ID") } return err } @@ -638,14 +639,14 @@ func MergedManually(ctx context.Context, pr *issues_model.PullRequest, doer *use return err } if !ok { - return fmt.Errorf("Wrong commit ID") + return errors.New("Wrong commit ID") } var merged bool if merged, err = SetMerged(ctx, pr, commitID, timeutil.TimeStamp(commit.Author.When.Unix()), doer, issues_model.PullRequestStatusManuallyMerged); err != nil { return err } else if !merged { - return fmt.Errorf("SetMerged failed") + return errors.New("SetMerged failed") } return nil }) diff --git a/services/pull/merge_squash.go b/services/pull/merge_squash.go index 076189fd7a..72660cd3c5 100644 --- a/services/pull/merge_squash.go +++ b/services/pull/merge_squash.go @@ -66,8 +66,8 @@ func doMergeStyleSquash(ctx *mergeContext, message string) error { if setting.Repository.PullRequest.AddCoCommitterTrailers && ctx.committer.String() != sig.String() { // add trailer - if !strings.Contains(message, fmt.Sprintf("Co-authored-by: %s", sig.String())) { - message += fmt.Sprintf("\nCo-authored-by: %s", sig.String()) + if !strings.Contains(message, "Co-authored-by: "+sig.String()) { + message += "\nCo-authored-by: " + sig.String() } message += fmt.Sprintf("\nCo-committed-by: %s\n", sig.String()) } diff --git a/services/pull/patch.go b/services/pull/patch.go index 29f2f992ab..7a24237724 100644 --- a/services/pull/patch.go +++ b/services/pull/patch.go @@ -134,7 +134,7 @@ type errMergeConflict struct { } func (e *errMergeConflict) Error() string { - return fmt.Sprintf("conflict detected at: %s", e.filename) + return "conflict detected at: " + e.filename } func attemptMerge(ctx context.Context, file *unmergedFile, tmpBasePath string, filesToRemove *[]string, filesToAdd *[]git.IndexObjectInfo) error { @@ -355,23 +355,19 @@ func checkConflicts(ctx context.Context, pr *issues_model.PullRequest, gitRepo * } // 3b. Create a plain patch from head to base - tmpPatchFile, err := os.CreateTemp("", "patch") + tmpPatchFile, cleanup, err := setting.AppDataTempDir("git-repo-content").CreateTempFileRandom("patch") if err != nil { log.Error("Unable to create temporary patch file! Error: %v", err) return false, fmt.Errorf("unable to create temporary patch file! Error: %w", err) } - defer func() { - _ = util.Remove(tmpPatchFile.Name()) - }() + defer cleanup() if err := gitRepo.GetDiffBinary(pr.MergeBase+"...tracking", tmpPatchFile); err != nil { - tmpPatchFile.Close() log.Error("Unable to get patch file from %s to %s in %s Error: %v", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err) return false, fmt.Errorf("unable to get patch file from %s to %s in %s Error: %w", pr.MergeBase, pr.HeadBranch, pr.BaseRepo.FullName(), err) } stat, err := tmpPatchFile.Stat() if err != nil { - tmpPatchFile.Close() return false, fmt.Errorf("unable to stat patch file: %w", err) } patchPath := tmpPatchFile.Name() diff --git a/services/pull/pull.go b/services/pull/pull.go index 4641d4ac40..13cbb40110 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -763,7 +763,7 @@ func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *re var errs []error for _, branch := range branches { - prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repo.ID, branch.Name) + prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(ctx, repo.ID, branch) if err != nil { return err } diff --git a/services/pull/review.go b/services/pull/review.go index 78723a58ae..5c80e7b338 100644 --- a/services/pull/review.go +++ b/services/pull/review.go @@ -395,7 +395,7 @@ func DismissReview(ctx context.Context, reviewID, repoID int64, message string, } if review.Type != issues_model.ReviewTypeApprove && review.Type != issues_model.ReviewTypeReject { - return nil, fmt.Errorf("not need to dismiss this review because it's type is not Approve or change request") + return nil, errors.New("not need to dismiss this review because it's type is not Approve or change request") } // load data for notify @@ -405,7 +405,7 @@ func DismissReview(ctx context.Context, reviewID, repoID int64, message string, // Check if the review's repoID is the one we're currently expecting. if review.Issue.RepoID != repoID { - return nil, fmt.Errorf("reviews's repository is not the same as the one we expect") + return nil, errors.New("reviews's repository is not the same as the one we expect") } issue := review.Issue diff --git a/services/pull/temp_repo.go b/services/pull/temp_repo.go index 3f33370798..d543e3d4a3 100644 --- a/services/pull/temp_repo.go +++ b/services/pull/temp_repo.go @@ -74,11 +74,13 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) } // Clone base repo. - tmpBasePath, err := repo_module.CreateTemporaryPath("pull") + tmpBasePath, cleanup, err := repo_module.CreateTemporaryPath("pull") if err != nil { log.Error("CreateTemporaryPath[%-v]: %v", pr, err) return nil, nil, err } + cancel = cleanup + prCtx = &prContext{ Context: ctx, tmpBasePath: tmpBasePath, @@ -86,11 +88,6 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) outbuf: &strings.Builder{}, errbuf: &strings.Builder{}, } - cancel = func() { - if err := repo_module.RemoveTemporaryPath(tmpBasePath); err != nil { - log.Error("Error whilst removing removing temporary repo for %-v: %v", pr, err) - } - } baseRepoPath := pr.BaseRepo.RepoPath() headRepoPath := pr.HeadRepo.RepoPath() diff --git a/services/pull/update.go b/services/pull/update.go index 3e00dd4e65..5cc5e2b134 100644 --- a/services/pull/update.go +++ b/services/pull/update.go @@ -5,6 +5,7 @@ package pull import ( "context" + "errors" "fmt" git_model "code.gitea.io/gitea/models/git" @@ -23,7 +24,7 @@ import ( func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model.User, message string, rebase bool) error { if pr.Flow == issues_model.PullRequestFlowAGit { // TODO: update of agit flow pull request's head branch is unsupported - return fmt.Errorf("update of agit flow pull request's head branch is unsupported") + return errors.New("update of agit flow pull request's head branch is unsupported") } releaser, err := globallock.Lock(ctx, getPullWorkingLockKey(pr.ID)) diff --git a/services/release/release_test.go b/services/release/release_test.go index 95a54832b9..36a9f667d6 100644 --- a/services/release/release_test.go +++ b/services/release/release_test.go @@ -250,9 +250,9 @@ func TestRelease_Update(t *testing.T) { assert.NoError(t, UpdateRelease(db.DefaultContext, user, gitRepo, release, []string{attach.UUID}, nil, nil)) assert.NoError(t, repo_model.GetReleaseAttachments(db.DefaultContext, release)) assert.Len(t, release.Attachments, 1) - assert.EqualValues(t, attach.UUID, release.Attachments[0].UUID) - assert.EqualValues(t, release.ID, release.Attachments[0].ReleaseID) - assert.EqualValues(t, attach.Name, release.Attachments[0].Name) + assert.Equal(t, attach.UUID, release.Attachments[0].UUID) + assert.Equal(t, release.ID, release.Attachments[0].ReleaseID) + assert.Equal(t, attach.Name, release.Attachments[0].Name) // update the attachment name assert.NoError(t, UpdateRelease(db.DefaultContext, user, gitRepo, release, nil, nil, map[string]string{ @@ -261,9 +261,9 @@ func TestRelease_Update(t *testing.T) { release.Attachments = nil assert.NoError(t, repo_model.GetReleaseAttachments(db.DefaultContext, release)) assert.Len(t, release.Attachments, 1) - assert.EqualValues(t, attach.UUID, release.Attachments[0].UUID) - assert.EqualValues(t, release.ID, release.Attachments[0].ReleaseID) - assert.EqualValues(t, "test2.txt", release.Attachments[0].Name) + assert.Equal(t, attach.UUID, release.Attachments[0].UUID) + assert.Equal(t, release.ID, release.Attachments[0].ReleaseID) + assert.Equal(t, "test2.txt", release.Attachments[0].Name) // delete the attachment assert.NoError(t, UpdateRelease(db.DefaultContext, user, gitRepo, release, nil, []string{attach.UUID}, nil)) diff --git a/services/repository/adopt.go b/services/repository/adopt.go index b7321156d9..7f1954145c 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -16,7 +16,6 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" @@ -28,18 +27,30 @@ import ( "github.com/gobwas/glob" ) +func deleteFailedAdoptRepository(repoID int64) error { + return db.WithTx(db.DefaultContext, func(ctx context.Context) error { + if err := deleteDBRepository(ctx, repoID); err != nil { + return fmt.Errorf("deleteDBRepository: %w", err) + } + if err := git_model.DeleteRepoBranches(ctx, repoID); err != nil { + return fmt.Errorf("deleteRepoBranches: %w", err) + } + return repo_model.DeleteRepoReleases(ctx, repoID) + }) +} + // AdoptRepository adopts pre-existing repository files for the user/organization. -func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { - if !doer.IsAdmin && !u.CanCreateRepo() { +func AdoptRepository(ctx context.Context, doer, owner *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { + if !doer.CanCreateRepoIn(owner) { return nil, repo_model.ErrReachLimitOfRepo{ - Limit: u.MaxRepoCreation, + Limit: owner.MaxRepoCreation, } } repo := &repo_model.Repository{ - OwnerID: u.ID, - Owner: u, - OwnerName: u.Name, + OwnerID: owner.ID, + Owner: owner, + OwnerName: owner.Name, Name: opts.Name, LowerName: strings.ToLower(opts.Name), Description: opts.Description, @@ -48,59 +59,52 @@ func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts CreateR IsPrivate: opts.IsPrivate, IsFsckEnabled: !opts.IsMirror, CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, - Status: opts.Status, + Status: repo_model.RepositoryBeingMigrated, IsEmpty: !opts.AutoInit, } - if err := db.WithTx(ctx, func(ctx context.Context) error { - isExist, err := gitrepo.IsRepositoryExist(ctx, repo) + // 1 - create the repository database operations first + err := db.WithTx(ctx, func(ctx context.Context) error { + return createRepositoryInDB(ctx, doer, owner, repo, false) + }) + if err != nil { + return nil, err + } + + // last - clean up if something goes wrong + // WARNING: Don't override all later err with local variables + defer func() { if err != nil { - log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err) - return err - } - if !isExist { - return repo_model.ErrRepoNotExist{ - OwnerName: u.Name, - Name: repo.Name, + // we can not use the ctx because it maybe canceled or timeout + if errDel := deleteFailedAdoptRepository(repo.ID); errDel != nil { + log.Error("Failed to delete repository %s that could not be adopted: %v", repo.FullName(), errDel) } } + }() - if err := CreateRepositoryByExample(ctx, doer, u, repo, true, false); err != nil { - return err - } - - // Re-fetch the repository from database before updating it (else it would - // override changes that were done earlier with sql) - if repo, err = repo_model.GetRepositoryByID(ctx, repo.ID); err != nil { - return fmt.Errorf("getRepositoryByID: %w", err) - } - return nil - }); err != nil { - return nil, err + // Re-fetch the repository from database before updating it (else it would + // override changes that were done earlier with sql) + if repo, err = repo_model.GetRepositoryByID(ctx, repo.ID); err != nil { + return nil, fmt.Errorf("getRepositoryByID: %w", err) } - if err := func() error { - if err := adoptRepository(ctx, repo, opts.DefaultBranch); err != nil { - return fmt.Errorf("adoptRepository: %w", err) - } - - if err := repo_module.CheckDaemonExportOK(ctx, repo); err != nil { - return fmt.Errorf("checkDaemonExportOK: %w", err) - } - - if stdout, _, err := git.NewCommand("update-server-info"). - RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}); err != nil { - log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) - return fmt.Errorf("CreateRepository(git update-server-info): %w", err) - } - return nil - }(); err != nil { - if errDel := DeleteRepository(ctx, doer, repo, false /* no notify */); errDel != nil { - log.Error("Failed to delete repository %s that could not be adopted: %v", repo.FullName(), errDel) - } - return nil, err + // 2 - adopt the repository from disk + if err = adoptRepository(ctx, repo, opts.DefaultBranch); err != nil { + return nil, fmt.Errorf("adoptRepository: %w", err) } - notify_service.AdoptRepository(ctx, doer, u, repo) + + // 3 - Update the git repository + if err = updateGitRepoAfterCreate(ctx, repo); err != nil { + return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err) + } + + // 4 - update repository status + repo.Status = repo_model.RepositoryReady + if err = repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil { + return nil, fmt.Errorf("UpdateRepositoryCols: %w", err) + } + + notify_service.AdoptRepository(ctx, doer, owner, repo) return repo, nil } @@ -192,7 +196,7 @@ func adoptRepository(ctx context.Context, repo *repo_model.Repository, defaultBr return fmt.Errorf("setDefaultBranch: %w", err) } } - if err = repo_module.UpdateRepository(ctx, repo, false); err != nil { + if err = updateRepository(ctx, repo, false); err != nil { return fmt.Errorf("updateRepository: %w", err) } diff --git a/services/repository/adopt_test.go b/services/repository/adopt_test.go index 294185ea1f..6e1dc417b3 100644 --- a/services/repository/adopt_test.go +++ b/services/repository/adopt_test.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) @@ -89,10 +90,36 @@ func TestListUnadoptedRepositories_ListOptions(t *testing.T) { func TestAdoptRepository(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - assert.NoError(t, unittest.SyncDirs(filepath.Join(setting.RepoRootPath, "user2", "repo1.git"), filepath.Join(setting.RepoRootPath, "user2", "test-adopt.git"))) + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - _, err := AdoptRepository(db.DefaultContext, user2, user2, CreateRepoOptions{Name: "test-adopt"}) + + // a successful adopt + destDir := filepath.Join(setting.RepoRootPath, user2.Name, "test-adopt.git") + assert.NoError(t, unittest.SyncDirs(filepath.Join(setting.RepoRootPath, user2.Name, "repo1.git"), destDir)) + + adoptedRepo, err := AdoptRepository(db.DefaultContext, user2, user2, CreateRepoOptions{Name: "test-adopt"}) assert.NoError(t, err) repoTestAdopt := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "test-adopt"}) assert.Equal(t, "sha1", repoTestAdopt.ObjectFormatName) + + // just delete the adopted repo's db records + err = deleteFailedAdoptRepository(adoptedRepo.ID) + assert.NoError(t, err) + + unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: "test-adopt"}) + + // a failed adopt because some mock data + // remove the hooks directory and create a file so that we cannot create the hooks successfully + _ = os.RemoveAll(filepath.Join(destDir, "hooks", "update.d")) + assert.NoError(t, os.WriteFile(filepath.Join(destDir, "hooks", "update.d"), []byte("tests"), os.ModePerm)) + + adoptedRepo, err = AdoptRepository(db.DefaultContext, user2, user2, CreateRepoOptions{Name: "test-adopt"}) + assert.Error(t, err) + assert.Nil(t, adoptedRepo) + + unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: "test-adopt"}) + + exist, err := util.IsExist(repo_model.RepoPath(user2.Name, "test-adopt")) + assert.NoError(t, err) + assert.True(t, exist) // the repository should be still in the disk } diff --git a/services/repository/archiver/archiver.go b/services/repository/archiver/archiver.go index d39acc080d..a657e3884c 100644 --- a/services/repository/archiver/archiver.go +++ b/services/repository/archiver/archiver.go @@ -44,7 +44,7 @@ type ErrUnknownArchiveFormat struct { // Error implements error func (err ErrUnknownArchiveFormat) Error() string { - return fmt.Sprintf("unknown format: %s", err.RequestNameType) + return "unknown format: " + err.RequestNameType } // Is implements error @@ -60,7 +60,7 @@ type RepoRefNotFoundError struct { // Error implements error. func (e RepoRefNotFoundError) Error() string { - return fmt.Sprintf("unrecognized repository reference: %s", e.RefShortName) + return "unrecognized repository reference: " + e.RefShortName } func (e RepoRefNotFoundError) Is(err error) bool { diff --git a/services/repository/archiver/archiver_test.go b/services/repository/archiver/archiver_test.go index 522f90558a..87324ad38c 100644 --- a/services/repository/archiver/archiver_test.go +++ b/services/repository/archiver/archiver_test.go @@ -33,7 +33,7 @@ func TestArchive_Basic(t *testing.T) { bogusReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") assert.NoError(t, err) assert.NotNil(t, bogusReq) - assert.EqualValues(t, firstCommit+".zip", bogusReq.GetArchiveName()) + assert.Equal(t, firstCommit+".zip", bogusReq.GetArchiveName()) // Check a series of bogus requests. // Step 1, valid commit with a bad extension. @@ -54,12 +54,12 @@ func TestArchive_Basic(t *testing.T) { bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "master.zip") assert.NoError(t, err) assert.NotNil(t, bogusReq) - assert.EqualValues(t, "master.zip", bogusReq.GetArchiveName()) + assert.Equal(t, "master.zip", bogusReq.GetArchiveName()) bogusReq, err = NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "test/archive.zip") assert.NoError(t, err) assert.NotNil(t, bogusReq) - assert.EqualValues(t, "test-archive.zip", bogusReq.GetArchiveName()) + assert.Equal(t, "test-archive.zip", bogusReq.GetArchiveName()) // Now two valid requests, firstCommit with valid extensions. zipReq, err := NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip") diff --git a/services/repository/avatar_test.go b/services/repository/avatar_test.go index bea820e85f..2dc5173eec 100644 --- a/services/repository/avatar_test.go +++ b/services/repository/avatar_test.go @@ -59,7 +59,7 @@ func TestDeleteAvatar(t *testing.T) { err = DeleteAvatar(db.DefaultContext, repo) assert.NoError(t, err) - assert.Equal(t, "", repo.Avatar) + assert.Empty(t, repo.Avatar) } func TestGenerateAvatar(t *testing.T) { diff --git a/services/repository/branch.go b/services/repository/branch.go index 8804778bd5..94c47ffdc4 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -303,7 +303,7 @@ func SyncBranchesToDB(ctx context.Context, repoID, pusherID int64, branchNames, // For other batches, it will hit optimization 4. if len(branchNames) != len(commitIDs) { - return fmt.Errorf("branchNames and commitIDs length not match") + return errors.New("branchNames and commitIDs length not match") } return db.WithTx(ctx, func(ctx context.Context) error { diff --git a/services/repository/contributors_graph_test.go b/services/repository/contributors_graph_test.go index 6db93f6a64..7d32b1c931 100644 --- a/services/repository/contributors_graph_test.go +++ b/services/repository/contributors_graph_test.go @@ -38,14 +38,14 @@ func TestRepository_ContributorsGraph(t *testing.T) { keys = append(keys, k) } slices.Sort(keys) - assert.EqualValues(t, []string{ + assert.Equal(t, []string{ "ethantkoenig@gmail.com", "jimmy.praet@telenet.be", "jon@allspice.io", "total", // generated summary }, keys) - assert.EqualValues(t, &ContributorData{ + assert.Equal(t, &ContributorData{ Name: "Ethan Koenig", AvatarLink: "/assets/img/avatar_default.png", TotalCommits: 1, @@ -58,7 +58,7 @@ func TestRepository_ContributorsGraph(t *testing.T) { }, }, }, data["ethantkoenig@gmail.com"]) - assert.EqualValues(t, &ContributorData{ + assert.Equal(t, &ContributorData{ Name: "Total", AvatarLink: "", TotalCommits: 3, diff --git a/services/repository/create.go b/services/repository/create.go index 1a6a68b35a..897f2a1d14 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" + system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/models/webhook" @@ -28,7 +29,6 @@ import ( "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/templates/vars" - "code.gitea.io/gitea/modules/util" ) // CreateRepoOptions contains the create repository options @@ -140,21 +140,20 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir // InitRepository initializes README and .gitignore if needed. func initRepository(ctx context.Context, u *user_model.User, repo *repo_model.Repository, opts CreateRepoOptions) (err error) { - if err = repo_module.CheckInitRepository(ctx, repo); err != nil { - return err + // Init git bare new repository. + if err = git.InitRepository(ctx, repo.RepoPath(), true, repo.ObjectFormatName); err != nil { + return fmt.Errorf("git.InitRepository: %w", err) + } else if err = gitrepo.CreateDelegateHooks(ctx, repo); err != nil { + return fmt.Errorf("createDelegateHooks: %w", err) } // Initialize repository according to user's choice. if opts.AutoInit { - tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-"+repo.Name) + tmpDir, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("repos-" + repo.Name) if err != nil { - return fmt.Errorf("Failed to create temp dir for repository %s: %w", repo.FullName(), err) + return fmt.Errorf("failed to create temp dir for repository %s: %w", repo.FullName(), err) } - defer func() { - if err := util.RemoveAll(tmpDir); err != nil { - log.Warn("Unable to remove temporary directory: %s: Error: %v", tmpDir, err) - } - }() + defer cleanup() if err = prepareRepoCommit(ctx, repo, tmpDir, opts); err != nil { return fmt.Errorf("prepareRepoCommit: %w", err) @@ -200,10 +199,10 @@ func initRepository(ctx context.Context, u *user_model.User, repo *repo_model.Re } // CreateRepositoryDirectly creates a repository for the user/organization. -func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { - if !doer.IsAdmin && !u.CanCreateRepo() { +func CreateRepositoryDirectly(ctx context.Context, doer, owner *user_model.User, opts CreateRepoOptions) (*repo_model.Repository, error) { + if !doer.CanCreateRepoIn(owner) { return nil, repo_model.ErrReachLimitOfRepo{ - Limit: u.MaxRepoCreation, + Limit: owner.MaxRepoCreation, } } @@ -223,9 +222,9 @@ func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opt } repo := &repo_model.Repository{ - OwnerID: u.ID, - Owner: u, - OwnerName: u.Name, + OwnerID: owner.ID, + Owner: owner, + OwnerName: owner.Name, Name: opts.Name, LowerName: strings.ToLower(opts.Name), Description: opts.Description, @@ -244,100 +243,93 @@ func CreateRepositoryDirectly(ctx context.Context, doer, u *user_model.User, opt ObjectFormatName: opts.ObjectFormatName, } - var rollbackRepo *repo_model.Repository - - if err := db.WithTx(ctx, func(ctx context.Context) error { - if err := CreateRepositoryByExample(ctx, doer, u, repo, false, false); err != nil { - return err - } - - // No need for init mirror. - if opts.IsMirror { - return nil - } - - isExist, err := gitrepo.IsRepositoryExist(ctx, repo) - if err != nil { - log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err) - return err - } - if isExist { - // repo already exists - We have two or three options. - // 1. We fail stating that the directory exists - // 2. We create the db repository to go with this data and adopt the git repo - // 3. We delete it and start afresh - // - // Previously Gitea would just delete and start afresh - this was naughty. - // So we will now fail and delegate to other functionality to adopt or delete - log.Error("Files already exist in %s and we are not going to adopt or delete.", repo.FullName()) - return repo_model.ErrRepoFilesAlreadyExist{ - Uname: u.Name, - Name: repo.Name, - } - } - - if err = initRepository(ctx, doer, repo, opts); err != nil { - if err2 := gitrepo.DeleteRepository(ctx, repo); err2 != nil { - log.Error("initRepository: %v", err) - return fmt.Errorf( - "delete repo directory %s/%s failed(2): %v", u.Name, repo.Name, err2) - } - return fmt.Errorf("initRepository: %w", err) - } - - // Initialize Issue Labels if selected - if len(opts.IssueLabels) > 0 { - if err = repo_module.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil { - rollbackRepo = repo - rollbackRepo.OwnerID = u.ID - return fmt.Errorf("InitializeLabels: %w", err) - } - } - - if err := repo_module.CheckDaemonExportOK(ctx, repo); err != nil { - return fmt.Errorf("checkDaemonExportOK: %w", err) - } - - if stdout, _, err := git.NewCommand("update-server-info"). - RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}); err != nil { - log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) - rollbackRepo = repo - rollbackRepo.OwnerID = u.ID - return fmt.Errorf("CreateRepository(git update-server-info): %w", err) - } - - // update licenses - var licenses []string - if len(opts.License) > 0 { - licenses = append(licenses, opts.License) - - stdout, _, err := git.NewCommand("rev-parse", "HEAD").RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}) - if err != nil { - log.Error("CreateRepository(git rev-parse HEAD) in %v: Stdout: %s\nError: %v", repo, stdout, err) - rollbackRepo = repo - rollbackRepo.OwnerID = u.ID - return fmt.Errorf("CreateRepository(git rev-parse HEAD): %w", err) - } - if err := repo_model.UpdateRepoLicenses(ctx, repo, stdout, licenses); err != nil { - return err - } - } - return nil - }); err != nil { - if rollbackRepo != nil { - if errDelete := DeleteRepositoryDirectly(ctx, doer, rollbackRepo.ID); errDelete != nil { - log.Error("Rollback deleteRepository: %v", errDelete) - } - } + needsUpdateStatus := opts.Status != repo_model.RepositoryReady + // 1 - create the repository database operations first + err := db.WithTx(ctx, func(ctx context.Context) error { + return createRepositoryInDB(ctx, doer, owner, repo, false) + }) + if err != nil { return nil, err } + // last - clean up if something goes wrong + // WARNING: Don't override all later err with local variables + defer func() { + if err != nil { + // we can not use the ctx because it maybe canceled or timeout + cleanupRepository(doer, repo.ID) + } + }() + + // No need for init mirror. + if opts.IsMirror { + return repo, nil + } + + // 2 - check whether the repository with the same storage exists + var isExist bool + isExist, err = gitrepo.IsRepositoryExist(ctx, repo) + if err != nil { + log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err) + return nil, err + } + if isExist { + log.Error("Files already exist in %s and we are not going to adopt or delete.", repo.FullName()) + // Don't return directly, we need err in defer to cleanupRepository + err = repo_model.ErrRepoFilesAlreadyExist{ + Uname: repo.OwnerName, + Name: repo.Name, + } + return nil, err + } + + // 3 - init git repository in storage + if err = initRepository(ctx, doer, repo, opts); err != nil { + return nil, fmt.Errorf("initRepository: %w", err) + } + + // 4 - Initialize Issue Labels if selected + if len(opts.IssueLabels) > 0 { + if err = repo_module.InitializeLabels(ctx, repo.ID, opts.IssueLabels, false); err != nil { + return nil, fmt.Errorf("InitializeLabels: %w", err) + } + } + + // 5 - Update the git repository + if err = updateGitRepoAfterCreate(ctx, repo); err != nil { + return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err) + } + + // 6 - update licenses + var licenses []string + if len(opts.License) > 0 { + licenses = append(licenses, opts.License) + + var stdout string + stdout, _, err = git.NewCommand("rev-parse", "HEAD").RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}) + if err != nil { + log.Error("CreateRepository(git rev-parse HEAD) in %v: Stdout: %s\nError: %v", repo, stdout, err) + return nil, fmt.Errorf("CreateRepository(git rev-parse HEAD): %w", err) + } + if err = repo_model.UpdateRepoLicenses(ctx, repo, stdout, licenses); err != nil { + return nil, err + } + } + + // 7 - update repository status to be ready + if needsUpdateStatus { + repo.Status = repo_model.RepositoryReady + if err = repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil { + return nil, fmt.Errorf("UpdateRepositoryCols: %w", err) + } + } + return repo, nil } -// CreateRepositoryByExample creates a repository for the user/organization. -func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository, overwriteOrAdopt, isFork bool) (err error) { +// createRepositoryInDB creates a repository for the user/organization. +func createRepositoryInDB(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository, isFork bool) (err error) { if err = repo_model.IsUsableRepoName(repo.Name); err != nil { return err } @@ -352,19 +344,6 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re } } - isExist, err := gitrepo.IsRepositoryExist(ctx, repo) - if err != nil { - log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err) - return err - } - if !overwriteOrAdopt && isExist { - log.Error("Files already exist in %s and we are not going to adopt or delete.", repo.FullName()) - return repo_model.ErrRepoFilesAlreadyExist{ - Uname: u.Name, - Name: repo.Name, - } - } - if err = db.Insert(ctx, repo); err != nil { return err } @@ -384,7 +363,8 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re } units := make([]repo_model.RepoUnit, 0, len(defaultUnits)) for _, tp := range defaultUnits { - if tp == unit.TypeIssues { + switch tp { + case unit.TypeIssues: units = append(units, repo_model.RepoUnit{ RepoID: repo.ID, Type: tp, @@ -394,7 +374,7 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re EnableDependencies: setting.Service.DefaultEnableDependencies, }, }) - } else if tp == unit.TypePullRequests { + case unit.TypePullRequests: units = append(units, repo_model.RepoUnit{ RepoID: repo.ID, Type: tp, @@ -404,13 +384,13 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re AllowRebaseUpdate: true, }, }) - } else if tp == unit.TypeProjects { + case unit.TypeProjects: units = append(units, repo_model.RepoUnit{ RepoID: repo.ID, Type: tp, Config: &repo_model.ProjectsConfig{ProjectsMode: repo_model.ProjectsModeAll}, }) - } else { + default: units = append(units, repo_model.RepoUnit{ RepoID: repo.ID, Type: tp, @@ -472,3 +452,26 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re return nil } + +func cleanupRepository(doer *user_model.User, repoID int64) { + if errDelete := DeleteRepositoryDirectly(db.DefaultContext, doer, repoID); errDelete != nil { + log.Error("cleanupRepository failed: %v", errDelete) + // add system notice + if err := system_model.CreateRepositoryNotice("DeleteRepositoryDirectly failed when cleanup repository: %v", errDelete); err != nil { + log.Error("CreateRepositoryNotice: %v", err) + } + } +} + +func updateGitRepoAfterCreate(ctx context.Context, repo *repo_model.Repository) error { + if err := checkDaemonExportOK(ctx, repo); err != nil { + return fmt.Errorf("checkDaemonExportOK: %w", err) + } + + if stdout, _, err := git.NewCommand("update-server-info"). + RunStdString(ctx, &git.RunOpts{Dir: repo.RepoPath()}); err != nil { + log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) + return fmt.Errorf("CreateRepository(git update-server-info): %w", err) + } + return nil +} diff --git a/services/repository/create_test.go b/services/repository/create_test.go new file mode 100644 index 0000000000..9ecf404e8b --- /dev/null +++ b/services/repository/create_test.go @@ -0,0 +1,57 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repository + +import ( + "os" + "testing" + + "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" + "code.gitea.io/gitea/modules/util" + + "github.com/stretchr/testify/assert" +) + +func TestCreateRepositoryDirectly(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + // a successful creating repository + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + createdRepo, err := CreateRepositoryDirectly(git.DefaultContext, user2, user2, CreateRepoOptions{ + Name: "created-repo", + }) + assert.NoError(t, err) + assert.NotNil(t, createdRepo) + + exist, err := util.IsExist(repo_model.RepoPath(user2.Name, createdRepo.Name)) + assert.NoError(t, err) + assert.True(t, exist) + + unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: createdRepo.Name}) + + err = DeleteRepositoryDirectly(db.DefaultContext, user2, createdRepo.ID) + assert.NoError(t, err) + + // a failed creating because some mock data + // create the repository directory so that the creation will fail after database record created. + assert.NoError(t, os.MkdirAll(repo_model.RepoPath(user2.Name, createdRepo.Name), os.ModePerm)) + + createdRepo2, err := CreateRepositoryDirectly(db.DefaultContext, user2, user2, CreateRepoOptions{ + Name: "created-repo", + }) + assert.Nil(t, createdRepo2) + assert.Error(t, err) + + // assert the cleanup is successful + unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: createdRepo.Name}) + + exist, err = util.IsExist(repo_model.RepoPath(user2.Name, createdRepo.Name)) + assert.NoError(t, err) + assert.False(t, exist) +} diff --git a/services/repository/delete.go b/services/repository/delete.go index ff74a20817..cf960af8cf 100644 --- a/services/repository/delete.go +++ b/services/repository/delete.go @@ -32,6 +32,19 @@ import ( "xorm.io/builder" ) +func deleteDBRepository(ctx context.Context, repoID int64) error { + if cnt, err := db.GetEngine(ctx).ID(repoID).Delete(&repo_model.Repository{}); err != nil { + return err + } else if cnt != 1 { + return repo_model.ErrRepoNotExist{ + ID: repoID, + OwnerName: "", + Name: "", + } + } + return nil +} + // DeleteRepository deletes a repository for a user or organization. // make sure if you call this func to close open sessions (sqlite will otherwise get a deadlock) func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID int64, ignoreOrgTeams ...bool) error { @@ -82,14 +95,8 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID } needRewriteKeysFile := deleted > 0 - if cnt, err := sess.ID(repoID).Delete(&repo_model.Repository{}); err != nil { + if err := deleteDBRepository(ctx, repoID); err != nil { return err - } else if cnt != 1 { - return repo_model.ErrRepoNotExist{ - ID: repoID, - OwnerName: "", - Name: "", - } } if org != nil && org.IsOrganization() { diff --git a/services/repository/files/cherry_pick.go b/services/repository/files/cherry_pick.go index 0e069fb2ce..6818bb343d 100644 --- a/services/repository/files/cherry_pick.go +++ b/services/repository/files/cherry_pick.go @@ -5,6 +5,7 @@ package files import ( "context" + "errors" "fmt" "strings" @@ -100,7 +101,7 @@ func CherryPick(ctx context.Context, repo *repo_model.Repository, doer *user_mod } if conflict { - return nil, fmt.Errorf("failed to merge due to conflicts") + return nil, errors.New("failed to merge due to conflicts") } treeHash, err := t.WriteTree(ctx) diff --git a/services/repository/files/content.go b/services/repository/files/content.go index 0ab7422ce2..0327e7f2ce 100644 --- a/services/repository/files/content.go +++ b/services/repository/files/content.go @@ -188,6 +188,14 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, treePath, ref }, } + // GitHub doesn't have these fields in the response, but we could follow other similar APIs to name them + // https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28#list-commits + if lastCommit.Committer != nil { + contentsResponse.LastCommitterDate = lastCommit.Committer.When + } + if lastCommit.Author != nil { + contentsResponse.LastAuthorDate = lastCommit.Author.When + } // Now populate the rest of the ContentsResponse based on entry type if entry.IsRegular() || entry.IsExecutable() { contentsResponse.Type = string(ContentTypeRegular) @@ -269,28 +277,3 @@ func GetBlobBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git Content: content, }, nil } - -// TryGetContentLanguage tries to get the (linguist) language of the file content -func TryGetContentLanguage(gitRepo *git.Repository, commitID, treePath string) (string, error) { - indexFilename, worktree, deleteTemporaryFile, err := gitRepo.ReadTreeToTemporaryIndex(commitID) - if err != nil { - return "", err - } - - defer deleteTemporaryFile() - - filename2attribute2info, err := gitRepo.CheckAttribute(git.CheckAttributeOpts{ - CachedOnly: true, - Attributes: []string{git.AttributeLinguistLanguage, git.AttributeGitlabLanguage}, - Filenames: []string{treePath}, - IndexFile: indexFilename, - WorkTree: worktree, - }) - if err != nil { - return "", err - } - - language := git.TryReadLanguageAttribute(filename2attribute2info[treePath]) - - return language.Value(), nil -} diff --git a/services/repository/files/content_test.go b/services/repository/files/content_test.go index 7cb46c0bb6..e066f30cba 100644 --- a/services/repository/files/content_test.go +++ b/services/repository/files/content_test.go @@ -5,6 +5,7 @@ package files import ( "testing" + "time" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/gitrepo" @@ -30,18 +31,20 @@ func getExpectedReadmeContentsResponse() *api.ContentsResponse { gitURL := "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/" + sha downloadURL := "https://try.gitea.io/user2/repo1/raw/branch/master/" + treePath return &api.ContentsResponse{ - Name: treePath, - Path: treePath, - SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f", - LastCommitSHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", - Type: "file", - Size: 30, - Encoding: &encoding, - Content: &content, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: treePath, + Path: treePath, + SHA: "4b4851ad51df6a7d9f25c979345979eaeb5b349f", + LastCommitSHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", + LastCommitterDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)), + LastAuthorDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)), + Type: "file", + Size: 30, + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, Links: &api.FileLinksResponse{ Self: &selfURL, GitURL: &gitURL, @@ -67,13 +70,13 @@ func TestGetContents(t *testing.T) { t.Run("Get README.md contents with GetContents(ctx, )", func(t *testing.T) { fileContentResponse, err := GetContents(ctx, ctx.Repo.Repository, treePath, ref, false) - assert.EqualValues(t, expectedContentsResponse, fileContentResponse) + assert.Equal(t, expectedContentsResponse, fileContentResponse) assert.NoError(t, err) }) t.Run("Get README.md contents with ref as empty string (should then use the repo's default branch) with GetContents(ctx, )", func(t *testing.T) { fileContentResponse, err := GetContents(ctx, ctx.Repo.Repository, treePath, "", false) - assert.EqualValues(t, expectedContentsResponse, fileContentResponse) + assert.Equal(t, expectedContentsResponse, fileContentResponse) assert.NoError(t, err) }) } diff --git a/services/repository/files/diff_test.go b/services/repository/files/diff_test.go index a8514791cc..ae702e4189 100644 --- a/services/repository/files/diff_test.go +++ b/services/repository/files/diff_test.go @@ -118,7 +118,7 @@ func TestGetDiffPreview(t *testing.T) { assert.NoError(t, err) bs, err := json.Marshal(diff) assert.NoError(t, err) - assert.EqualValues(t, string(expectedBs), string(bs)) + assert.Equal(t, string(expectedBs), string(bs)) }) t.Run("empty branch, same results", func(t *testing.T) { @@ -128,7 +128,7 @@ func TestGetDiffPreview(t *testing.T) { assert.NoError(t, err) bs, err := json.Marshal(diff) assert.NoError(t, err) - assert.EqualValues(t, expectedBs, bs) + assert.Equal(t, expectedBs, bs) }) } diff --git a/services/repository/files/file.go b/services/repository/files/file.go index 2caa1b4946..a8ad5889cb 100644 --- a/services/repository/files/file.go +++ b/services/repository/files/file.go @@ -5,6 +5,7 @@ package files import ( "context" + "errors" "fmt" "net/url" "strings" @@ -62,10 +63,10 @@ func GetFileResponseFromFilesResponse(filesResponse *api.FilesResponse, index in // GetFileCommitResponse Constructs a FileCommitResponse from a Commit object func GetFileCommitResponse(repo *repo_model.Repository, commit *git.Commit) (*api.FileCommitResponse, error) { if repo == nil { - return nil, fmt.Errorf("repo cannot be nil") + return nil, errors.New("repo cannot be nil") } if commit == nil { - return nil, fmt.Errorf("commit cannot be nil") + return nil, errors.New("commit cannot be nil") } commitURL, _ := url.Parse(repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String())) commitTreeURL, _ := url.Parse(repo.APIURL() + "/git/trees/" + url.PathEscape(commit.Tree.ID.String())) diff --git a/services/repository/files/file_test.go b/services/repository/files/file_test.go index 52c0574883..7cb79d7ebf 100644 --- a/services/repository/files/file_test.go +++ b/services/repository/files/file_test.go @@ -5,6 +5,7 @@ package files import ( "testing" + "time" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/gitrepo" @@ -20,14 +21,14 @@ func TestCleanUploadFileName(t *testing.T) { name := "this/is/test" cleanName := CleanUploadFileName(name) expectedCleanName := name - assert.EqualValues(t, expectedCleanName, cleanName) + assert.Equal(t, expectedCleanName, cleanName) }) t.Run("Clean a .git path", func(t *testing.T) { name := "this/is/test/.git" cleanName := CleanUploadFileName(name) expectedCleanName := "" - assert.EqualValues(t, expectedCleanName, cleanName) + assert.Equal(t, expectedCleanName, cleanName) }) } @@ -42,18 +43,20 @@ func getExpectedFileResponse() *api.FileResponse { downloadURL := setting.AppURL + "user2/repo1/raw/branch/master/" + treePath return &api.FileResponse{ Content: &api.ContentsResponse{ - Name: treePath, - Path: treePath, - SHA: sha, - LastCommitSHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", - Type: "file", - Size: 30, - Encoding: &encoding, - Content: &content, - URL: &selfURL, - HTMLURL: &htmlURL, - GitURL: &gitURL, - DownloadURL: &downloadURL, + Name: treePath, + Path: treePath, + SHA: sha, + LastCommitSHA: "65f1bf27bc3bf70f64657658635e66094edbcb4d", + LastCommitterDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)), + LastAuthorDate: time.Date(2017, time.March, 19, 16, 47, 59, 0, time.FixedZone("", -14400)), + Type: "file", + Size: 30, + Encoding: &encoding, + Content: &content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, Links: &api.FileLinksResponse{ Self: &selfURL, GitURL: &gitURL, @@ -116,5 +119,5 @@ func TestGetFileResponseFromCommit(t *testing.T) { fileResponse, err := GetFileResponseFromCommit(ctx, repo, commit, branch, treePath) assert.NoError(t, err) - assert.EqualValues(t, expectedFileResponse, fileResponse) + assert.Equal(t, expectedFileResponse, fileResponse) } diff --git a/services/repository/files/patch.go b/services/repository/files/patch.go index 1941adb86a..5fbf748206 100644 --- a/services/repository/files/patch.go +++ b/services/repository/files/patch.go @@ -12,7 +12,6 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -62,29 +61,26 @@ func (opts *ApplyDiffPatchOptions) Validate(ctx context.Context, repo *repo_mode opts.NewBranch = opts.OldBranch } - gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo) - if err != nil { - return err - } - defer closer.Close() - // oldBranch must exist for this operation - if _, err := gitRepo.GetBranch(opts.OldBranch); err != nil { + if exist, err := git_model.IsBranchExist(ctx, repo.ID, opts.OldBranch); err != nil { return err + } else if !exist { + return git_model.ErrBranchNotExist{ + BranchName: opts.OldBranch, + } } // A NewBranch can be specified for the patch to be applied to. // Check to make sure the branch does not already exist, otherwise we can't proceed. // If we aren't branching to a new branch, make sure user can commit to the given branch if opts.NewBranch != opts.OldBranch { - existingBranch, err := gitRepo.GetBranch(opts.NewBranch) - if existingBranch != nil { + exist, err := git_model.IsBranchExist(ctx, repo.ID, opts.NewBranch) + if err != nil { + return err + } else if exist { return git_model.ErrBranchAlreadyExists{ BranchName: opts.NewBranch, } } - if err != nil && !git.IsErrBranchNotExist(err) { - return err - } } else { protectedBranch, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, opts.OldBranch) if err != nil { diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go index d2c70a7a34..493ff9998d 100644 --- a/services/repository/files/temp_repo.go +++ b/services/repository/files/temp_repo.go @@ -6,6 +6,7 @@ package files import ( "bytes" "context" + "errors" "fmt" "io" "os" @@ -29,23 +30,24 @@ type TemporaryUploadRepository struct { repo *repo_model.Repository gitRepo *git.Repository basePath string + cleanup func() } // NewTemporaryUploadRepository creates a new temporary upload repository func NewTemporaryUploadRepository(repo *repo_model.Repository) (*TemporaryUploadRepository, error) { - basePath, err := repo_module.CreateTemporaryPath("upload") + basePath, cleanup, err := repo_module.CreateTemporaryPath("upload") if err != nil { return nil, err } - t := &TemporaryUploadRepository{repo: repo, basePath: basePath} + t := &TemporaryUploadRepository{repo: repo, basePath: basePath, cleanup: cleanup} return t, nil } // Close the repository cleaning up all files func (t *TemporaryUploadRepository) Close() { defer t.gitRepo.Close() - if err := repo_module.RemoveTemporaryPath(t.basePath); err != nil { - log.Error("Failed to remove temporary path %s: %v", t.basePath, err) + if t.cleanup != nil { + t.cleanup() } } @@ -414,7 +416,7 @@ func (t *TemporaryUploadRepository) DiffIndex(ctx context.Context) (*gitdiff.Dif // GetBranchCommit Gets the commit object of the given branch func (t *TemporaryUploadRepository) GetBranchCommit(branch string) (*git.Commit, error) { if t.gitRepo == nil { - return nil, fmt.Errorf("repository has not been cloned") + return nil, errors.New("repository has not been cloned") } return t.gitRepo.GetBranchCommit(branch) } @@ -422,7 +424,7 @@ func (t *TemporaryUploadRepository) GetBranchCommit(branch string) (*git.Commit, // GetCommit Gets the commit object of the given commit ID func (t *TemporaryUploadRepository) GetCommit(commitID string) (*git.Commit, error) { if t.gitRepo == nil { - return nil, fmt.Errorf("repository has not been cloned") + return nil, errors.New("repository has not been cloned") } return t.gitRepo.GetCommit(commitID) } diff --git a/services/repository/files/tree.go b/services/repository/files/tree.go index 9142416347..faeb85a046 100644 --- a/services/repository/files/tree.go +++ b/services/repository/files/tree.go @@ -6,12 +6,14 @@ package files import ( "context" "fmt" + "html/template" "net/url" "path" "sort" "strings" repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/fileicon" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -140,8 +142,13 @@ func entryModeString(entryMode git.EntryMode) string { } type TreeViewNode struct { - EntryName string `json:"entryName"` - EntryMode string `json:"entryMode"` + EntryName string `json:"entryName"` + EntryMode string `json:"entryMode"` + EntryIcon template.HTML `json:"entryIcon"` + EntryIconOpen template.HTML `json:"entryIconOpen,omitempty"` + + SymLinkedToMode string `json:"symLinkedToMode,omitempty"` // TODO: for the EntryMode="symlink" + FullPath string `json:"fullPath"` SubmoduleURL string `json:"submoduleUrl,omitempty"` Children []*TreeViewNode `json:"children,omitempty"` @@ -151,13 +158,28 @@ func (node *TreeViewNode) sortLevel() int { return util.Iif(node.EntryMode == "tree" || node.EntryMode == "commit", 0, 1) } -func newTreeViewNodeFromEntry(ctx context.Context, commit *git.Commit, parentDir string, entry *git.TreeEntry) *TreeViewNode { +func newTreeViewNodeFromEntry(ctx context.Context, renderedIconPool *fileicon.RenderedIconPool, commit *git.Commit, parentDir string, entry *git.TreeEntry) *TreeViewNode { node := &TreeViewNode{ EntryName: entry.Name(), EntryMode: entryModeString(entry.Mode()), FullPath: path.Join(parentDir, entry.Name()), } + if entry.IsLink() { + // TODO: symlink to a folder or a file, the icon differs + target, err := entry.FollowLink() + if err == nil { + _ = target.IsDir() + // if target.IsDir() { } else { } + } + } + + if node.EntryIcon == "" { + node.EntryIcon = fileicon.RenderEntryIcon(renderedIconPool, entry) + // TODO: no open icon support yet + // node.EntryIconOpen = fileicon.RenderEntryIconOpen(renderedIconPool, entry) + } + if node.EntryMode == "commit" { if subModule, err := commit.GetSubModule(node.FullPath); err != nil { log.Error("GetSubModule: %v", err) @@ -182,7 +204,7 @@ func sortTreeViewNodes(nodes []*TreeViewNode) { }) } -func listTreeNodes(ctx context.Context, commit *git.Commit, tree *git.Tree, treePath, subPath string) ([]*TreeViewNode, error) { +func listTreeNodes(ctx context.Context, renderedIconPool *fileicon.RenderedIconPool, commit *git.Commit, tree *git.Tree, treePath, subPath string) ([]*TreeViewNode, error) { entries, err := tree.ListEntries() if err != nil { return nil, err @@ -191,14 +213,14 @@ func listTreeNodes(ctx context.Context, commit *git.Commit, tree *git.Tree, tree subPathDirName, subPathRemaining, _ := strings.Cut(subPath, "/") nodes := make([]*TreeViewNode, 0, len(entries)) for _, entry := range entries { - node := newTreeViewNodeFromEntry(ctx, commit, treePath, entry) + node := newTreeViewNodeFromEntry(ctx, renderedIconPool, commit, treePath, entry) nodes = append(nodes, node) if entry.IsDir() && subPathDirName == entry.Name() { subTreePath := treePath + "/" + node.EntryName if subTreePath[0] == '/' { subTreePath = subTreePath[1:] } - subNodes, err := listTreeNodes(ctx, commit, entry.Tree(), subTreePath, subPathRemaining) + subNodes, err := listTreeNodes(ctx, renderedIconPool, commit, entry.Tree(), subTreePath, subPathRemaining) if err != nil { log.Error("listTreeNodes: %v", err) } else { @@ -210,10 +232,10 @@ func listTreeNodes(ctx context.Context, commit *git.Commit, tree *git.Tree, tree return nodes, nil } -func GetTreeViewNodes(ctx context.Context, commit *git.Commit, treePath, subPath string) ([]*TreeViewNode, error) { +func GetTreeViewNodes(ctx context.Context, renderedIconPool *fileicon.RenderedIconPool, commit *git.Commit, treePath, subPath string) ([]*TreeViewNode, error) { entry, err := commit.GetTreeEntryByPath(treePath) if err != nil { return nil, err } - return listTreeNodes(ctx, commit, entry.Tree(), treePath, subPath) + return listTreeNodes(ctx, renderedIconPool, commit, entry.Tree(), treePath, subPath) } diff --git a/services/repository/files/tree_test.go b/services/repository/files/tree_test.go index 8ea54969ce..2657c49977 100644 --- a/services/repository/files/tree_test.go +++ b/services/repository/files/tree_test.go @@ -4,9 +4,11 @@ package files import ( + "html/template" "testing" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/fileicon" "code.gitea.io/gitea/modules/git" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/services/contexttest" @@ -49,7 +51,7 @@ func TestGetTreeBySHA(t *testing.T) { TotalCount: 1, } - assert.EqualValues(t, expectedTree, tree) + assert.Equal(t, expectedTree, tree) } func TestGetTreeViewNodes(t *testing.T) { @@ -62,40 +64,51 @@ func TestGetTreeViewNodes(t *testing.T) { contexttest.LoadGitRepo(t, ctx) defer ctx.Repo.GitRepo.Close() - treeNodes, err := GetTreeViewNodes(ctx, ctx.Repo.Commit, "", "") + renderedIconPool := fileicon.NewRenderedIconPool() + mockIconForFile := func(id string) template.HTML { + return template.HTML(``) + } + mockIconForFolder := func(id string) template.HTML { + return template.HTML(``) + } + treeNodes, err := GetTreeViewNodes(ctx, renderedIconPool, ctx.Repo.Commit, "", "") assert.NoError(t, err) assert.Equal(t, []*TreeViewNode{ { EntryName: "docs", EntryMode: "tree", FullPath: "docs", + EntryIcon: mockIconForFolder(`svg-mfi-folder-docs`), }, }, treeNodes) - treeNodes, err = GetTreeViewNodes(ctx, ctx.Repo.Commit, "", "docs/README.md") + treeNodes, err = GetTreeViewNodes(ctx, renderedIconPool, ctx.Repo.Commit, "", "docs/README.md") assert.NoError(t, err) assert.Equal(t, []*TreeViewNode{ { EntryName: "docs", EntryMode: "tree", FullPath: "docs", + EntryIcon: mockIconForFolder(`svg-mfi-folder-docs`), Children: []*TreeViewNode{ { EntryName: "README.md", EntryMode: "blob", FullPath: "docs/README.md", + EntryIcon: mockIconForFile(`svg-mfi-readme`), }, }, }, }, treeNodes) - treeNodes, err = GetTreeViewNodes(ctx, ctx.Repo.Commit, "docs", "README.md") + treeNodes, err = GetTreeViewNodes(ctx, renderedIconPool, ctx.Repo.Commit, "docs", "README.md") assert.NoError(t, err) assert.Equal(t, []*TreeViewNode{ { EntryName: "README.md", EntryMode: "blob", FullPath: "docs/README.md", + EntryIcon: mockIconForFile(`svg-mfi-readme`), }, }, treeNodes) } diff --git a/services/repository/files/update.go b/services/repository/files/update.go index cade7ba2bf..75ede4976f 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -15,6 +15,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/attribute" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" @@ -107,8 +108,13 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use defer closer.Close() // oldBranch must exist for this operation - if _, err := gitRepo.GetBranch(opts.OldBranch); err != nil && !repo.IsEmpty { + if exist, err := git_model.IsBranchExist(ctx, repo.ID, opts.OldBranch); err != nil { return nil, err + } else if !exist && !repo.IsEmpty { + return nil, git_model.ErrBranchNotExist{ + RepoID: repo.ID, + BranchName: opts.OldBranch, + } } var treePaths []string @@ -145,15 +151,15 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use // Check to make sure the branch does not already exist, otherwise we can't proceed. // If we aren't branching to a new branch, make sure user can commit to the given branch if opts.NewBranch != opts.OldBranch { - existingBranch, err := gitRepo.GetBranch(opts.NewBranch) - if existingBranch != nil { + exist, err := git_model.IsBranchExist(ctx, repo.ID, opts.NewBranch) + if err != nil { + return nil, err + } + if exist { return nil, git_model.ErrBranchAlreadyExists{ BranchName: opts.NewBranch, } } - if err != nil && !git.IsErrBranchNotExist(err) { - return nil, err - } } else if err := VerifyBranchProtection(ctx, repo, doer, opts.OldBranch, treePaths); err != nil { return nil, err } @@ -483,16 +489,15 @@ func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file var lfsMetaObject *git_model.LFSMetaObject if setting.LFS.StartServer && hasOldBranch { // Check there is no way this can return multiple infos - filename2attribute2info, err := t.gitRepo.CheckAttribute(git.CheckAttributeOpts{ - Attributes: []string{"filter"}, + attributesMap, err := attribute.CheckAttributes(ctx, t.gitRepo, "" /* use temp repo's working dir */, attribute.CheckAttributeOpts{ + Attributes: []string{attribute.Filter}, Filenames: []string{file.Options.treePath}, - CachedOnly: true, }) if err != nil { return err } - if filename2attribute2info[file.Options.treePath] != nil && filename2attribute2info[file.Options.treePath]["filter"] == "lfs" { + if attributesMap[file.Options.treePath] != nil && attributesMap[file.Options.treePath].Get(attribute.Filter).ToString().Value() == "lfs" { // OK so we are supposed to LFS this data! pointer, err := lfs.GeneratePointer(treeObjectContentReader) if err != nil { diff --git a/services/repository/files/upload.go b/services/repository/files/upload.go index 2e4ed1744e..f348cb68ab 100644 --- a/services/repository/files/upload.go +++ b/services/repository/files/upload.go @@ -14,6 +14,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/attribute" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/setting" ) @@ -105,12 +106,11 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use } } - var filename2attribute2info map[string]map[string]string + var attributesMap map[string]*attribute.Attributes if setting.LFS.StartServer { - filename2attribute2info, err = t.gitRepo.CheckAttribute(git.CheckAttributeOpts{ - Attributes: []string{"filter"}, + attributesMap, err = attribute.CheckAttributes(ctx, t.gitRepo, "" /* use temp repo's working dir */, attribute.CheckAttributeOpts{ + Attributes: []string{attribute.Filter}, Filenames: names, - CachedOnly: true, }) if err != nil { return err @@ -119,7 +119,7 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use // Copy uploaded files into repository. for i := range infos { - if err := copyUploadedLFSFileIntoRepository(ctx, &infos[i], filename2attribute2info, t, opts.TreePath); err != nil { + if err := copyUploadedLFSFileIntoRepository(ctx, &infos[i], attributesMap, t, opts.TreePath); err != nil { return err } } @@ -176,7 +176,7 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use return repo_model.DeleteUploads(ctx, uploads...) } -func copyUploadedLFSFileIntoRepository(ctx context.Context, info *uploadInfo, filename2attribute2info map[string]map[string]string, t *TemporaryUploadRepository, treePath string) error { +func copyUploadedLFSFileIntoRepository(ctx context.Context, info *uploadInfo, attributesMap map[string]*attribute.Attributes, t *TemporaryUploadRepository, treePath string) error { file, err := os.Open(info.upload.LocalPath()) if err != nil { return err @@ -184,7 +184,7 @@ func copyUploadedLFSFileIntoRepository(ctx context.Context, info *uploadInfo, fi defer file.Close() var objectHash string - if setting.LFS.StartServer && filename2attribute2info[info.upload.Name] != nil && filename2attribute2info[info.upload.Name]["filter"] == "lfs" { + if setting.LFS.StartServer && attributesMap[info.upload.Name] != nil && attributesMap[info.upload.Name].Get(attribute.Filter).ToString().Value() == "lfs" { // Handle LFS // FIXME: Inefficient! this should probably happen in models.Upload pointer, err := lfs.GeneratePointer(file) diff --git a/services/repository/fork.go b/services/repository/fork.go index 5b1ba7a418..c16c3d598a 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -65,7 +65,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork } // Fork is prohibited, if user has reached maximum limit of repositories - if !owner.CanForkRepo() { + if !doer.CanForkRepoIn(owner) { return nil, repo_model.ErrReachLimitOfRepo{ Limit: owner.MaxRepoCreation, } @@ -100,114 +100,106 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork IsFork: true, ForkID: opts.BaseRepo.ID, ObjectFormatName: opts.BaseRepo.ObjectFormatName, + Status: repo_model.RepositoryBeingMigrated, } - oldRepoPath := opts.BaseRepo.RepoPath() - - needsRollback := false - rollbackFn := func() { - if !needsRollback { - return - } - - if exists, _ := gitrepo.IsRepositoryExist(ctx, repo); !exists { - return - } - - // As the transaction will be failed and hence database changes will be destroyed we only need - // to delete the related repository on the filesystem - if errDelete := gitrepo.DeleteRepository(ctx, repo); errDelete != nil { - log.Error("Failed to remove fork repo") - } - } - - needsRollbackInPanic := true - defer func() { - panicErr := recover() - if panicErr == nil { - return - } - - if needsRollbackInPanic { - rollbackFn() - } - panic(panicErr) - }() - - err = db.WithTx(ctx, func(txCtx context.Context) error { - if err = CreateRepositoryByExample(txCtx, doer, owner, repo, false, true); err != nil { + // 1 - Create the repository in the database + err = db.WithTx(ctx, func(ctx context.Context) error { + if err = createRepositoryInDB(ctx, doer, owner, repo, true); err != nil { return err } - - if err = repo_model.IncrementRepoForkNum(txCtx, opts.BaseRepo.ID); err != nil { + if err = repo_model.IncrementRepoForkNum(ctx, opts.BaseRepo.ID); err != nil { return err } // copy lfs files failure should not be ignored - if err = git_model.CopyLFS(txCtx, repo, opts.BaseRepo); err != nil { - return err - } - - needsRollback = true - - cloneCmd := git.NewCommand("clone", "--bare") - if opts.SingleBranch != "" { - cloneCmd.AddArguments("--single-branch", "--branch").AddDynamicArguments(opts.SingleBranch) - } - if stdout, _, err := cloneCmd.AddDynamicArguments(oldRepoPath, repo.RepoPath()). - RunStdBytes(txCtx, &git.RunOpts{Timeout: 10 * time.Minute}); err != nil { - log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, opts.BaseRepo, stdout, err) - return fmt.Errorf("git clone: %w", err) - } - - if err := repo_module.CheckDaemonExportOK(txCtx, repo); err != nil { - return fmt.Errorf("checkDaemonExportOK: %w", err) - } - - if stdout, _, err := git.NewCommand("update-server-info"). - RunStdString(txCtx, &git.RunOpts{Dir: repo.RepoPath()}); err != nil { - log.Error("Fork Repository (git update-server-info) failed for %v:\nStdout: %s\nError: %v", repo, stdout, err) - return fmt.Errorf("git update-server-info: %w", err) - } - - if err = gitrepo.CreateDelegateHooks(ctx, repo); err != nil { - return fmt.Errorf("createDelegateHooks: %w", err) - } - - gitRepo, err := gitrepo.OpenRepository(txCtx, repo) - if err != nil { - return fmt.Errorf("OpenRepository: %w", err) - } - defer gitRepo.Close() - - _, err = repo_module.SyncRepoBranchesWithRepo(txCtx, repo, gitRepo, doer.ID) - return err + return git_model.CopyLFS(ctx, repo, opts.BaseRepo) }) - needsRollbackInPanic = false if err != nil { - rollbackFn() return nil, err } - // even if below operations failed, it could be ignored. And they will be retried - if err := repo_module.UpdateRepoSize(ctx, repo); err != nil { - log.Error("Failed to update size for repository: %v", err) - } - if err := repo_model.CopyLanguageStat(ctx, opts.BaseRepo, repo); err != nil { - log.Error("Copy language stat from oldRepo failed: %v", err) - } - if err := repo_model.CopyLicense(ctx, opts.BaseRepo, repo); err != nil { - return nil, err - } - - gitRepo, err := gitrepo.OpenRepository(ctx, repo) - if err != nil { - log.Error("Open created git repository failed: %v", err) - } else { - defer gitRepo.Close() - if err := repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { - log.Error("Sync releases from git tags failed: %v", err) + // last - clean up if something goes wrong + // WARNING: Don't override all later err with local variables + defer func() { + if err != nil { + // we can not use the ctx because it maybe canceled or timeout + cleanupRepository(doer, repo.ID) } + }() + + // 2 - check whether the repository with the same storage exists + var isExist bool + isExist, err = gitrepo.IsRepositoryExist(ctx, repo) + if err != nil { + log.Error("Unable to check if %s exists. Error: %v", repo.FullName(), err) + return nil, err + } + if isExist { + log.Error("Files already exist in %s and we are not going to adopt or delete.", repo.FullName()) + // Don't return directly, we need err in defer to cleanupRepository + err = repo_model.ErrRepoFilesAlreadyExist{ + Uname: repo.OwnerName, + Name: repo.Name, + } + return nil, err + } + + // 3 - Clone the repository + cloneCmd := git.NewCommand("clone", "--bare") + if opts.SingleBranch != "" { + cloneCmd.AddArguments("--single-branch", "--branch").AddDynamicArguments(opts.SingleBranch) + } + var stdout []byte + if stdout, _, err = cloneCmd.AddDynamicArguments(opts.BaseRepo.RepoPath(), repo.RepoPath()). + RunStdBytes(ctx, &git.RunOpts{Timeout: 10 * time.Minute}); err != nil { + log.Error("Fork Repository (git clone) Failed for %v (from %v):\nStdout: %s\nError: %v", repo, opts.BaseRepo, stdout, err) + return nil, fmt.Errorf("git clone: %w", err) + } + + // 4 - Update the git repository + if err = updateGitRepoAfterCreate(ctx, repo); err != nil { + return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err) + } + + // 5 - Create hooks + if err = gitrepo.CreateDelegateHooks(ctx, repo); err != nil { + return nil, fmt.Errorf("createDelegateHooks: %w", err) + } + + // 6 - Sync the repository branches and tags + var gitRepo *git.Repository + gitRepo, err = gitrepo.OpenRepository(ctx, repo) + if err != nil { + return nil, fmt.Errorf("OpenRepository: %w", err) + } + defer gitRepo.Close() + + if _, err = repo_module.SyncRepoBranchesWithRepo(ctx, repo, gitRepo, doer.ID); err != nil { + return nil, fmt.Errorf("SyncRepoBranchesWithRepo: %w", err) + } + if err = repo_module.SyncReleasesWithTags(ctx, repo, gitRepo); err != nil { + return nil, fmt.Errorf("Sync releases from git tags failed: %v", err) + } + + // 7 - Update the repository + // even if below operations failed, it could be ignored. And they will be retried + if err = repo_module.UpdateRepoSize(ctx, repo); err != nil { + log.Error("Failed to update size for repository: %v", err) + err = nil + } + if err = repo_model.CopyLanguageStat(ctx, opts.BaseRepo, repo); err != nil { + log.Error("Copy language stat from oldRepo failed: %v", err) + err = nil + } + if err = repo_model.CopyLicense(ctx, opts.BaseRepo, repo); err != nil { + return nil, err + } + + // 8 - update repository status to be ready + repo.Status = repo_model.RepositoryReady + if err = repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil { + return nil, fmt.Errorf("UpdateRepositoryCols: %w", err) } notify_service.ForkRepository(ctx, doer, opts.BaseRepo, repo) @@ -235,7 +227,7 @@ func ConvertForkToNormalRepository(ctx context.Context, repo *repo_model.Reposit repo.IsFork = false repo.ForkID = 0 - if err := repo_module.UpdateRepository(ctx, repo, false); err != nil { + if err := updateRepository(ctx, repo, false); err != nil { log.Error("Unable to update repository %-v whilst converting from fork. Error: %v", repo, err) return err } diff --git a/services/repository/fork_test.go b/services/repository/fork_test.go index 452798b25b..9edc0aa39f 100644 --- a/services/repository/fork_test.go +++ b/services/repository/fork_test.go @@ -4,13 +4,16 @@ package repository import ( + "os" "testing" + "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" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) @@ -46,3 +49,43 @@ func TestForkRepository(t *testing.T) { assert.Nil(t, fork2) assert.True(t, repo_model.IsErrReachLimitOfRepo(err)) } + +func TestForkRepositoryCleanup(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + // a successful fork + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + repo10 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) + + fork, err := ForkRepository(git.DefaultContext, user2, user2, ForkRepoOptions{ + BaseRepo: repo10, + Name: "test", + }) + assert.NoError(t, err) + assert.NotNil(t, fork) + + exist, err := util.IsExist(repo_model.RepoPath(user2.Name, "test")) + assert.NoError(t, err) + assert.True(t, exist) + + err = DeleteRepositoryDirectly(db.DefaultContext, user2, fork.ID) + assert.NoError(t, err) + + // a failed creating because some mock data + // create the repository directory so that the creation will fail after database record created. + assert.NoError(t, os.MkdirAll(repo_model.RepoPath(user2.Name, "test"), os.ModePerm)) + + fork2, err := ForkRepository(db.DefaultContext, user2, user2, ForkRepoOptions{ + BaseRepo: repo10, + Name: "test", + }) + assert.Nil(t, fork2) + assert.Error(t, err) + + // assert the cleanup is successful + unittest.AssertNotExistsBean(t, &repo_model.Repository{OwnerName: user2.Name, Name: "test"}) + + exist, err = util.IsExist(repo_model.RepoPath(user2.Name, "test")) + assert.NoError(t, err) + assert.False(t, exist) +} diff --git a/services/repository/generate.go b/services/repository/generate.go index 9d2bbb1f7f..b02f7c9482 100644 --- a/services/repository/generate.go +++ b/services/repository/generate.go @@ -17,11 +17,11 @@ import ( git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" - user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "github.com/gobwas/glob" @@ -256,16 +256,11 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r } func generateGitContent(ctx context.Context, repo, templateRepo, generateRepo *repo_model.Repository) (err error) { - tmpDir, err := os.MkdirTemp(os.TempDir(), "gitea-"+repo.Name) + tmpDir, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("gitea-" + repo.Name) if err != nil { - return fmt.Errorf("Failed to create temp dir for repository %s: %w", repo.FullName(), err) + return fmt.Errorf("failed to create temp dir for repository %s: %w", repo.FullName(), err) } - - defer func() { - if err := util.RemoveAll(tmpDir); err != nil { - log.Error("RemoveAll: %v", err) - } - }() + defer cleanup() if err = generateRepoCommit(ctx, repo, templateRepo, generateRepo, tmpDir); err != nil { return fmt.Errorf("generateRepoCommit: %w", err) @@ -328,57 +323,6 @@ func (gro GenerateRepoOptions) IsValid() bool { gro.IssueLabels || gro.ProtectedBranch // or other items as they are added } -// generateRepository generates a repository from a template -func generateRepository(ctx context.Context, doer, owner *user_model.User, templateRepo *repo_model.Repository, opts GenerateRepoOptions) (_ *repo_model.Repository, err error) { - generateRepo := &repo_model.Repository{ - OwnerID: owner.ID, - Owner: owner, - OwnerName: owner.Name, - Name: opts.Name, - LowerName: strings.ToLower(opts.Name), - Description: opts.Description, - DefaultBranch: opts.DefaultBranch, - IsPrivate: opts.Private, - IsEmpty: !opts.GitContent || templateRepo.IsEmpty, - IsFsckEnabled: templateRepo.IsFsckEnabled, - TemplateID: templateRepo.ID, - TrustModel: templateRepo.TrustModel, - ObjectFormatName: templateRepo.ObjectFormatName, - } - - if err = CreateRepositoryByExample(ctx, doer, owner, generateRepo, false, false); err != nil { - return nil, err - } - - isExist, err := gitrepo.IsRepositoryExist(ctx, generateRepo) - if err != nil { - log.Error("Unable to check if %s exists. Error: %v", generateRepo.FullName(), err) - return nil, err - } - if isExist { - return nil, repo_model.ErrRepoFilesAlreadyExist{ - Uname: generateRepo.OwnerName, - Name: generateRepo.Name, - } - } - - if err = repo_module.CheckInitRepository(ctx, generateRepo); err != nil { - return generateRepo, err - } - - if err = repo_module.CheckDaemonExportOK(ctx, generateRepo); err != nil { - return generateRepo, fmt.Errorf("checkDaemonExportOK: %w", err) - } - - if stdout, _, err := git.NewCommand("update-server-info"). - RunStdString(ctx, &git.RunOpts{Dir: generateRepo.RepoPath()}); err != nil { - log.Error("GenerateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", generateRepo, stdout, err) - return generateRepo, fmt.Errorf("error in GenerateRepository(git update-server-info): %w", err) - } - - return generateRepo, nil -} - var fileNameSanitizeRegexp = regexp.MustCompile(`(?i)\.\.|[<>:\"/\\|?*\x{0000}-\x{001F}]|^(con|prn|aux|nul|com\d|lpt\d)$`) // Sanitize user input to valid OS filenames diff --git a/services/repository/license_test.go b/services/repository/license_test.go index 9e74a268f5..eb897f3c03 100644 --- a/services/repository/license_test.go +++ b/services/repository/license_test.go @@ -4,7 +4,6 @@ package repository import ( - "fmt" "strings" "testing" @@ -45,7 +44,7 @@ func Test_detectLicense(t *testing.T) { assert.NoError(t, err) tests = append(tests, DetectLicenseTest{ - name: fmt.Sprintf("single license test: %s", licenseName), + name: "single license test: " + licenseName, arg: string(license), want: []string{licenseName}, }) diff --git a/services/repository/migrate.go b/services/repository/migrate.go index 9a5c6ffb0f..003be1a9ab 100644 --- a/services/repository/migrate.go +++ b/services/repository/migrate.go @@ -118,14 +118,8 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, repo.Owner = u } - if err := repo_module.CheckDaemonExportOK(ctx, repo); err != nil { - return repo, fmt.Errorf("checkDaemonExportOK: %w", err) - } - - if stdout, _, err := git.NewCommand("update-server-info"). - RunStdString(ctx, &git.RunOpts{Dir: repoPath}); err != nil { - log.Error("MigrateRepositoryGitData(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err) - return repo, fmt.Errorf("error in MigrateRepositoryGitData(git update-server-info): %w", err) + if err := updateGitRepoAfterCreate(ctx, repo); err != nil { + return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err) } gitRepo, err := git.OpenRepository(ctx, repoPath) @@ -142,12 +136,12 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, if !repo.IsEmpty { if len(repo.DefaultBranch) == 0 { // Try to get HEAD branch and set it as default branch. - headBranch, err := gitRepo.GetHEADBranch() + headBranchName, err := git.GetDefaultBranch(ctx, repoPath) if err != nil { return repo, fmt.Errorf("GetHEADBranch: %w", err) } - if headBranch != nil { - repo.DefaultBranch = headBranch.Name + if headBranchName != "" { + repo.DefaultBranch = headBranchName } } diff --git a/services/repository/push.go b/services/repository/push.go index 6d3b9dd252..ba801ad019 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -66,7 +66,7 @@ func PushUpdates(opts []*repo_module.PushUpdateOptions) error { for _, opt := range opts { if opt.IsNewRef() && opt.IsDelRef() { - return fmt.Errorf("Old and new revisions are both NULL") + return errors.New("Old and new revisions are both NULL") } } @@ -385,7 +385,7 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo if err != nil { return fmt.Errorf("GetTag: %w", err) } - commit, err := tag.Commit(gitRepo) + commit, err := gitRepo.GetTagCommit(tag.Name) if err != nil { return fmt.Errorf("Commit: %w", err) } diff --git a/services/repository/repository.go b/services/repository/repository.go index fcc617979e..90aad4f9d8 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -5,22 +5,29 @@ package repository import ( "context" + "errors" "fmt" + "os" + "path/filepath" + "strings" + activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" + access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" - system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/graceful" + issue_indexer "code.gitea.io/gitea/modules/indexer/issues" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/queue" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" notify_service "code.gitea.io/gitea/services/notify" pull_service "code.gitea.io/gitea/services/pull" ) @@ -72,10 +79,10 @@ func PushCreateRepo(ctx context.Context, authUser, owner *user_model.User, repoN if ok, err := organization.CanCreateOrgRepo(ctx, owner.ID, authUser.ID); err != nil { return nil, err } else if !ok { - return nil, fmt.Errorf("cannot push-create repository for org") + return nil, errors.New("cannot push-create repository for org") } } else if authUser.ID != owner.ID { - return nil, fmt.Errorf("cannot push-create repository for another user") + return nil, errors.New("cannot push-create repository for another user") } } @@ -94,15 +101,13 @@ func PushCreateRepo(ctx context.Context, authUser, owner *user_model.User, repoN func Init(ctx context.Context) error { licenseUpdaterQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "repo_license_updater", repoLicenseUpdater) if licenseUpdaterQueue == nil { - return fmt.Errorf("unable to create repo_license_updater queue") + return errors.New("unable to create repo_license_updater queue") } go graceful.GetManager().RunWithCancel(licenseUpdaterQueue) if err := repo_module.LoadRepoConfig(); err != nil { return err } - system_model.RemoveAllWithNotice(ctx, "Clean up temporary repository uploads", setting.Repository.Upload.TempPath) - system_model.RemoveAllWithNotice(ctx, "Clean up temporary repositories", repo_module.LocalCopyPath()) if err := initPushQueue(); err != nil { return err } @@ -111,42 +116,32 @@ func Init(ctx context.Context) error { // UpdateRepository updates a repository func UpdateRepository(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err = repo_module.UpdateRepository(ctx, repo, visibilityChanged); err != nil { - return fmt.Errorf("updateRepository: %w", err) - } - - return committer.Commit() -} - -func UpdateRepositoryVisibility(ctx context.Context, repo *repo_model.Repository, isPrivate bool) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - - defer committer.Close() - - repo.IsPrivate = isPrivate - - if err = repo_module.UpdateRepository(ctx, repo, true); err != nil { - return fmt.Errorf("UpdateRepositoryVisibility: %w", err) - } - - return committer.Commit() + return db.WithTx(ctx, func(ctx context.Context) error { + if err = updateRepository(ctx, repo, visibilityChanged); err != nil { + return fmt.Errorf("updateRepository: %w", err) + } + return nil + }) } func MakeRepoPublic(ctx context.Context, repo *repo_model.Repository) (err error) { - return UpdateRepositoryVisibility(ctx, repo, false) + return db.WithTx(ctx, func(ctx context.Context) error { + repo.IsPrivate = false + if err = updateRepository(ctx, repo, true); err != nil { + return fmt.Errorf("MakeRepoPublic: %w", err) + } + return nil + }) } func MakeRepoPrivate(ctx context.Context, repo *repo_model.Repository) (err error) { - return UpdateRepositoryVisibility(ctx, repo, true) + return db.WithTx(ctx, func(ctx context.Context) error { + repo.IsPrivate = true + if err = updateRepository(ctx, repo, true); err != nil { + return fmt.Errorf("MakeRepoPrivate: %w", err) + } + return nil + }) } // LinkedRepository returns the linked repo if any @@ -172,3 +167,97 @@ func LinkedRepository(ctx context.Context, a *repo_model.Attachment) (*repo_mode } return nil, -1, nil } + +// checkDaemonExportOK creates/removes git-daemon-export-ok for git-daemon... +func checkDaemonExportOK(ctx context.Context, repo *repo_model.Repository) error { + if err := repo.LoadOwner(ctx); err != nil { + return err + } + + // Create/Remove git-daemon-export-ok for git-daemon... + daemonExportFile := filepath.Join(repo.RepoPath(), `git-daemon-export-ok`) + + isExist, err := util.IsExist(daemonExportFile) + if err != nil { + log.Error("Unable to check if %s exists. Error: %v", daemonExportFile, err) + return err + } + + isPublic := !repo.IsPrivate && repo.Owner.Visibility == structs.VisibleTypePublic + if !isPublic && isExist { + if err = util.Remove(daemonExportFile); err != nil { + log.Error("Failed to remove %s: %v", daemonExportFile, err) + } + } else if isPublic && !isExist { + if f, err := os.Create(daemonExportFile); err != nil { + log.Error("Failed to create %s: %v", daemonExportFile, err) + } else { + f.Close() + } + } + + return nil +} + +// updateRepository updates a repository with db context +func updateRepository(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) { + repo.LowerName = strings.ToLower(repo.Name) + + e := db.GetEngine(ctx) + + if _, err = e.ID(repo.ID).AllCols().Update(repo); err != nil { + return fmt.Errorf("update: %w", err) + } + + if err = repo_module.UpdateRepoSize(ctx, repo); err != nil { + log.Error("Failed to update size for repository: %v", err) + } + + if visibilityChanged { + if err = repo.LoadOwner(ctx); err != nil { + return fmt.Errorf("LoadOwner: %w", err) + } + if repo.Owner.IsOrganization() { + // Organization repository need to recalculate access table when visibility is changed. + if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil { + return fmt.Errorf("recalculateTeamAccesses: %w", err) + } + } + + // If repo has become private, we need to set its actions to private. + if repo.IsPrivate { + _, err = e.Where("repo_id = ?", repo.ID).Cols("is_private").Update(&activities_model.Action{ + IsPrivate: true, + }) + if err != nil { + return err + } + + if err = repo_model.ClearRepoStars(ctx, repo.ID); err != nil { + return err + } + } + + // Create/Remove git-daemon-export-ok for git-daemon... + if err := checkDaemonExportOK(ctx, repo); err != nil { + return err + } + + forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID) + if err != nil { + return fmt.Errorf("getRepositoriesByForkID: %w", err) + } + for i := range forkRepos { + forkRepos[i].IsPrivate = repo.IsPrivate || repo.Owner.Visibility == structs.VisibleTypePrivate + if err = updateRepository(ctx, forkRepos[i], true); err != nil { + return fmt.Errorf("updateRepository[%d]: %w", forkRepos[i].ID, err) + } + } + + // If visibility is changed, we need to update the issue indexer. + // Since the data in the issue indexer have field to indicate if the repo is public or not. + issue_indexer.UpdateRepoIndexer(ctx, repo.ID) + } + + return nil +} diff --git a/services/repository/repository_test.go b/services/repository/repository_test.go index 892a11a23e..8f9fdf8fa1 100644 --- a/services/repository/repository_test.go +++ b/services/repository/repository_test.go @@ -6,6 +6,7 @@ package repository import ( "testing" + activities_model "code.gitea.io/gitea/models/activities" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" @@ -40,3 +41,23 @@ func TestLinkedRepository(t *testing.T) { }) } } + +func TestUpdateRepositoryVisibilityChanged(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + // Get sample repo and change visibility + repo, err := repo_model.GetRepositoryByID(db.DefaultContext, 9) + assert.NoError(t, err) + repo.IsPrivate = true + + // Update it + err = updateRepository(db.DefaultContext, repo, true) + assert.NoError(t, err) + + // Check visibility of action has become private + act := activities_model.Action{} + _, err = db.GetEngine(db.DefaultContext).ID(3).Get(&act) + + assert.NoError(t, err) + assert.True(t, act.IsPrivate) +} diff --git a/services/repository/template.go b/services/repository/template.go index 36a680c8e2..95f585cead 100644 --- a/services/repository/template.go +++ b/services/repository/template.go @@ -5,12 +5,17 @@ package repository import ( "context" + "fmt" + "strings" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" + "code.gitea.io/gitea/modules/log" notify_service "code.gitea.io/gitea/services/notify" ) @@ -63,72 +68,126 @@ func GenerateProtectedBranch(ctx context.Context, templateRepo, generateRepo *re // GenerateRepository generates a repository from a template func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templateRepo *repo_model.Repository, opts GenerateRepoOptions) (_ *repo_model.Repository, err error) { - if !doer.IsAdmin && !owner.CanCreateRepo() { + if !doer.CanCreateRepoIn(owner) { return nil, repo_model.ErrReachLimitOfRepo{ Limit: owner.MaxRepoCreation, } } - var generateRepo *repo_model.Repository - if err = db.WithTx(ctx, func(ctx context.Context) error { - generateRepo, err = generateRepository(ctx, doer, owner, templateRepo, opts) - if err != nil { - return err - } + generateRepo := &repo_model.Repository{ + OwnerID: owner.ID, + Owner: owner, + OwnerName: owner.Name, + Name: opts.Name, + LowerName: strings.ToLower(opts.Name), + Description: opts.Description, + DefaultBranch: opts.DefaultBranch, + IsPrivate: opts.Private, + IsEmpty: !opts.GitContent || templateRepo.IsEmpty, + IsFsckEnabled: templateRepo.IsFsckEnabled, + TemplateID: templateRepo.ID, + TrustModel: templateRepo.TrustModel, + ObjectFormatName: templateRepo.ObjectFormatName, + Status: repo_model.RepositoryBeingMigrated, + } - // Git Content - if opts.GitContent && !templateRepo.IsEmpty { - if err = GenerateGitContent(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - // Topics - if opts.Topics { - if err = repo_model.GenerateTopics(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - // Git Hooks - if opts.GitHooks { - if err = GenerateGitHooks(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - // Webhooks - if opts.Webhooks { - if err = GenerateWebhooks(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - // Avatar - if opts.Avatar && len(templateRepo.Avatar) > 0 { - if err = generateAvatar(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - // Issue Labels - if opts.IssueLabels { - if err = GenerateIssueLabels(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - if opts.ProtectedBranch { - if err = GenerateProtectedBranch(ctx, templateRepo, generateRepo); err != nil { - return err - } - } - - return nil + // 1 - Create the repository in the database + if err := db.WithTx(ctx, func(ctx context.Context) error { + return createRepositoryInDB(ctx, doer, owner, generateRepo, false) }); err != nil { return nil, err } + // last - clean up the repository if something goes wrong + defer func() { + if err != nil { + // we can not use the ctx because it maybe canceled or timeout + cleanupRepository(doer, generateRepo.ID) + } + }() + + // 2 - check whether the repository with the same storage exists + isExist, err := gitrepo.IsRepositoryExist(ctx, generateRepo) + if err != nil { + log.Error("Unable to check if %s exists. Error: %v", generateRepo.FullName(), err) + return nil, err + } + if isExist { + // Don't return directly, we need err in defer to cleanupRepository + err = repo_model.ErrRepoFilesAlreadyExist{ + Uname: generateRepo.OwnerName, + Name: generateRepo.Name, + } + return nil, err + } + + // 3 -Init git bare new repository. + if err = git.InitRepository(ctx, generateRepo.RepoPath(), true, generateRepo.ObjectFormatName); err != nil { + return nil, fmt.Errorf("git.InitRepository: %w", err) + } else if err = gitrepo.CreateDelegateHooks(ctx, generateRepo); err != nil { + return nil, fmt.Errorf("createDelegateHooks: %w", err) + } + + // 4 - Update the git repository + if err = updateGitRepoAfterCreate(ctx, generateRepo); err != nil { + return nil, fmt.Errorf("updateGitRepoAfterCreate: %w", err) + } + + // 5 - generate the repository contents according to the template + // Git Content + if opts.GitContent && !templateRepo.IsEmpty { + if err = GenerateGitContent(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + // Topics + if opts.Topics { + if err = repo_model.GenerateTopics(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + // Git Hooks + if opts.GitHooks { + if err = GenerateGitHooks(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + // Webhooks + if opts.Webhooks { + if err = GenerateWebhooks(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + // Avatar + if opts.Avatar && len(templateRepo.Avatar) > 0 { + if err = generateAvatar(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + // Issue Labels + if opts.IssueLabels { + if err = GenerateIssueLabels(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + if opts.ProtectedBranch { + if err = GenerateProtectedBranch(ctx, templateRepo, generateRepo); err != nil { + return nil, err + } + } + + // 6 - update repository status to be ready + generateRepo.Status = repo_model.RepositoryReady + if err = repo_model.UpdateRepositoryCols(ctx, generateRepo, "status"); err != nil { + return nil, fmt.Errorf("UpdateRepositoryCols: %w", err) + } + notify_service.CreateRepository(ctx, doer, owner, generateRepo) return generateRepo, nil diff --git a/services/repository/transfer.go b/services/repository/transfer.go index a589bc469d..86917ad285 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -20,10 +20,22 @@ import ( "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/globallock" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" notify_service "code.gitea.io/gitea/services/notify" ) +type LimitReachedError struct{ Limit int } + +func (LimitReachedError) Error() string { + return "Repository limit has been reached" +} + +func IsRepositoryLimitReached(err error) bool { + _, ok := err.(LimitReachedError) + return ok +} + func getRepoWorkingLockKey(repoID int64) string { return fmt.Sprintf("repo_working_%d", repoID) } @@ -49,6 +61,11 @@ func AcceptTransferOwnership(ctx context.Context, repo *repo_model.Repository, d return err } + if !doer.CanCreateRepoIn(repoTransfer.Recipient) { + limit := util.Iif(repoTransfer.Recipient.MaxRepoCreation >= 0, repoTransfer.Recipient.MaxRepoCreation, setting.Repository.MaxCreationLimit) + return LimitReachedError{Limit: limit} + } + if !repoTransfer.CanUserAcceptOrRejectTransfer(ctx, doer) { return util.ErrPermissionDenied } @@ -399,6 +416,11 @@ func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.Use return err } + if !doer.CanForkRepoIn(newOwner) { + limit := util.Iif(newOwner.MaxRepoCreation >= 0, newOwner.MaxRepoCreation, setting.Repository.MaxCreationLimit) + return LimitReachedError{Limit: limit} + } + var isDirectTransfer bool oldOwnerName := repo.OwnerName diff --git a/services/repository/transfer_test.go b/services/repository/transfer_test.go index 16a8fb6e1e..80a073e9f9 100644 --- a/services/repository/transfer_test.go +++ b/services/repository/transfer_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2025 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package repository @@ -14,11 +14,14 @@ import ( 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/setting" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/feed" notify_service "code.gitea.io/gitea/services/notify" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var notifySync sync.Once @@ -125,3 +128,40 @@ func TestRepositoryTransfer(t *testing.T) { err = RejectRepositoryTransfer(db.DefaultContext, repo2, doer) assert.True(t, repo_model.IsErrNoPendingTransfer(err)) } + +// Test transfer rejections +func TestRepositoryTransferRejection(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + // Set limit to 0 repositories so no repositories can be transferred + defer test.MockVariableValue(&setting.Repository.MaxCreationLimit, 0)() + + // Admin case + doerAdmin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 5}) + + transfer, err := repo_model.GetPendingRepositoryTransfer(db.DefaultContext, repo) + require.NoError(t, err) + require.NotNil(t, transfer) + require.NoError(t, transfer.LoadRecipient(db.DefaultContext)) + + require.True(t, doerAdmin.CanCreateRepoIn(transfer.Recipient)) // admin is not subject to limits + + // Administrator should not be affected by the limits so transfer should be successful + assert.NoError(t, AcceptTransferOwnership(db.DefaultContext, repo, doerAdmin)) + + // Non admin user case + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10}) + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21}) + + transfer, err = repo_model.GetPendingRepositoryTransfer(db.DefaultContext, repo) + require.NoError(t, err) + require.NotNil(t, transfer) + require.NoError(t, transfer.LoadRecipient(db.DefaultContext)) + + require.False(t, doer.CanCreateRepoIn(transfer.Recipient)) // regular user is subject to limits + + // Cannot accept because of the limit + err = AcceptTransferOwnership(db.DefaultContext, repo, doer) + assert.Error(t, err) + assert.True(t, IsRepositoryLimitReached(err)) +} diff --git a/services/task/task.go b/services/task/task.go index c90ee91270..105aee2a25 100644 --- a/services/task/task.go +++ b/services/task/task.go @@ -5,6 +5,7 @@ package task import ( "context" + "errors" "fmt" admin_model "code.gitea.io/gitea/models/admin" @@ -41,7 +42,7 @@ func Run(ctx context.Context, t *admin_model.Task) error { func Init() error { taskQueue = queue.CreateSimpleQueue(graceful.GetManager().ShutdownContext(), "task", handler) if taskQueue == nil { - return fmt.Errorf("unable to create task queue") + return errors.New("unable to create task queue") } go graceful.GetManager().RunWithCancel(taskQueue) return nil diff --git a/services/user/user_test.go b/services/user/user_test.go index 162a735cd4..28a0df8628 100644 --- a/services/user/user_test.go +++ b/services/user/user_test.go @@ -150,7 +150,7 @@ func TestRenameUser(t *testing.T) { redirectUID, err := user_model.LookupUserRedirect(db.DefaultContext, oldUsername) assert.NoError(t, err) - assert.EqualValues(t, user.ID, redirectUID) + assert.Equal(t, user.ID, redirectUID) unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, OwnerName: user.Name}) }) diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go index df32d5741e..e8e6ed19c1 100644 --- a/services/webhook/deliver.go +++ b/services/webhook/deliver.go @@ -10,6 +10,7 @@ import ( "crypto/sha256" "crypto/tls" "encoding/hex" + "errors" "fmt" "io" "net/http" @@ -41,7 +42,7 @@ func newDefaultRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook case http.MethodPost: switch w.ContentType { case webhook_model.ContentTypeJSON: - req, err = http.NewRequest("POST", w.URL, strings.NewReader(t.PayloadContent)) + req, err = http.NewRequest(http.MethodPost, w.URL, strings.NewReader(t.PayloadContent)) if err != nil { return nil, nil, err } @@ -52,7 +53,7 @@ func newDefaultRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook "payload": []string{t.PayloadContent}, } - req, err = http.NewRequest("POST", w.URL, strings.NewReader(forms.Encode())) + req, err = http.NewRequest(http.MethodPost, w.URL, strings.NewReader(forms.Encode())) if err != nil { return nil, nil, err } @@ -69,7 +70,7 @@ func newDefaultRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook vals := u.Query() vals["payload"] = []string{t.PayloadContent} u.RawQuery = vals.Encode() - req, err = http.NewRequest("GET", u.String(), nil) + req, err = http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { return nil, nil, err } @@ -81,7 +82,7 @@ func newDefaultRequest(ctx context.Context, w *webhook_model.Webhook, t *webhook return nil, nil, err } url := fmt.Sprintf("%s/%s", w.URL, url.PathEscape(txnID)) - req, err = http.NewRequest("PUT", url, strings.NewReader(t.PayloadContent)) + req, err = http.NewRequest(http.MethodPut, url, strings.NewReader(t.PayloadContent)) if err != nil { return nil, nil, err } @@ -328,7 +329,7 @@ func Init() error { hookQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "webhook_sender", handler) if hookQueue == nil { - return fmt.Errorf("unable to create webhook_sender queue") + return errors.New("unable to create webhook_sender queue") } go graceful.GetManager().RunWithCancel(hookQueue) diff --git a/services/webhook/deliver_test.go b/services/webhook/deliver_test.go index 6a74b1455c..1d32d7b772 100644 --- a/services/webhook/deliver_test.go +++ b/services/webhook/deliver_test.go @@ -64,7 +64,7 @@ func TestWebhookProxy(t *testing.T) { } for _, tt := range tests { t.Run(tt.req, func(t *testing.T) { - req, err := http.NewRequest("POST", tt.req, nil) + req, err := http.NewRequest(http.MethodPost, tt.req, nil) require.NoError(t, err) u, err := webhookProxy(allowedHostMatcher)(req) @@ -91,7 +91,7 @@ func TestWebhookDeliverAuthorizationHeader(t *testing.T) { s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/webhook", r.URL.Path) assert.Equal(t, "Bearer s3cr3t-t0ken", r.Header.Get("Authorization")) - w.WriteHeader(200) + w.WriteHeader(http.StatusOK) done <- struct{}{} })) t.Cleanup(s.Close) @@ -138,7 +138,7 @@ func TestWebhookDeliverHookTask(t *testing.T) { case "/webhook/66d222a5d6349e1311f551e50722d837e30fce98": // Version 1 assert.Equal(t, "push", r.Header.Get("X-GitHub-Event")) - assert.Equal(t, "", r.Header.Get("Content-Type")) + assert.Empty(t, r.Header.Get("Content-Type")) body, err := io.ReadAll(r.Body) assert.NoError(t, err) assert.Equal(t, `{"data": 42}`, string(body)) @@ -152,11 +152,11 @@ func TestWebhookDeliverHookTask(t *testing.T) { assert.Len(t, body, 2147) default: - w.WriteHeader(404) + w.WriteHeader(http.StatusNotFound) t.Fatalf("unexpected url path %s", r.URL.Path) return } - w.WriteHeader(200) + w.WriteHeader(http.StatusOK) done <- struct{}{} })) t.Cleanup(s.Close) diff --git a/services/webhook/dingtalk.go b/services/webhook/dingtalk.go index 5afca8d65a..ce907bf0cb 100644 --- a/services/webhook/dingtalk.go +++ b/services/webhook/dingtalk.go @@ -30,7 +30,7 @@ func (dc dingtalkConvertor) Create(p *api.CreatePayload) (DingtalkPayload, error refName := git.RefName(p.Ref).ShortName() title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName) - return createDingtalkPayload(title, title, fmt.Sprintf("view ref %s", refName), p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName)), nil + return createDingtalkPayload(title, title, "view ref "+refName, p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName)), nil } // Delete implements PayloadConvertor Delete method @@ -39,14 +39,14 @@ func (dc dingtalkConvertor) Delete(p *api.DeletePayload) (DingtalkPayload, error refName := git.RefName(p.Ref).ShortName() title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) - return createDingtalkPayload(title, title, fmt.Sprintf("view ref %s", refName), p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName)), nil + return createDingtalkPayload(title, title, "view ref "+refName, p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName)), nil } // Fork implements PayloadConvertor Fork method func (dc dingtalkConvertor) Fork(p *api.ForkPayload) (DingtalkPayload, error) { title := fmt.Sprintf("%s is forked to %s", p.Forkee.FullName, p.Repo.FullName) - return createDingtalkPayload(title, title, fmt.Sprintf("view forked repo %s", p.Repo.FullName), p.Repo.HTMLURL), nil + return createDingtalkPayload(title, title, "view forked repo "+p.Repo.FullName, p.Repo.HTMLURL), nil } // Push implements PayloadConvertor Push method diff --git a/services/webhook/discord.go b/services/webhook/discord.go index 0a7eb0b166..0e8a9aa67c 100644 --- a/services/webhook/discord.go +++ b/services/webhook/discord.go @@ -101,6 +101,13 @@ var ( redColor = color("ff3232") ) +// https://discord.com/developers/docs/resources/message#embed-object-embed-limits +// Discord has some limits in place for the embeds. +// According to some tests, there is no consistent limit for different character sets. +// For example: 4096 ASCII letters are allowed, but only 2490 emoji characters are allowed. +// To keep it simple, we currently truncate at 2000. +const discordDescriptionCharactersLimit = 2000 + type discordConvertor struct { Username string AvatarURL string @@ -313,7 +320,7 @@ func (d discordConvertor) createPayload(s *api.User, title, text, url string, co Embeds: []DiscordEmbed{ { Title: title, - Description: text, + Description: util.TruncateRunes(text, discordDescriptionCharactersLimit), URL: url, Color: color, Author: DiscordEmbedAuthor{ diff --git a/services/webhook/general.go b/services/webhook/general.go index ea75038faf..251659e75e 100644 --- a/services/webhook/general.go +++ b/services/webhook/general.go @@ -39,19 +39,20 @@ func getPullRequestInfo(p *api.PullRequestPayload) (title, link, by, operator, o for i, user := range assignList { assignStringList[i] = user.UserName } - if p.Action == api.HookIssueAssigned { + switch p.Action { + case api.HookIssueAssigned: operateResult = fmt.Sprintf("%s assign this to %s", p.Sender.UserName, assignList[len(assignList)-1].UserName) - } else if p.Action == api.HookIssueUnassigned { - operateResult = fmt.Sprintf("%s unassigned this for someone", p.Sender.UserName) - } else if p.Action == api.HookIssueMilestoned { + case api.HookIssueUnassigned: + operateResult = p.Sender.UserName + " unassigned this for someone" + case api.HookIssueMilestoned: operateResult = fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.PullRequest.Milestone.ID) } link = p.PullRequest.HTMLURL - by = fmt.Sprintf("PullRequest by %s", p.PullRequest.Poster.UserName) + by = "PullRequest by " + p.PullRequest.Poster.UserName if len(assignStringList) > 0 { - assignees = fmt.Sprintf("Assignees: %s", strings.Join(assignStringList, ", ")) + assignees = "Assignees: " + strings.Join(assignStringList, ", ") } - operator = fmt.Sprintf("Operator: %s", p.Sender.UserName) + operator = "Operator: " + p.Sender.UserName return title, link, by, operator, operateResult, assignees } @@ -64,19 +65,20 @@ func getIssuesInfo(p *api.IssuePayload) (issueTitle, link, by, operator, operate for i, user := range assignList { assignStringList[i] = user.UserName } - if p.Action == api.HookIssueAssigned { + switch p.Action { + case api.HookIssueAssigned: operateResult = fmt.Sprintf("%s assign this to %s", p.Sender.UserName, assignList[len(assignList)-1].UserName) - } else if p.Action == api.HookIssueUnassigned { - operateResult = fmt.Sprintf("%s unassigned this for someone", p.Sender.UserName) - } else if p.Action == api.HookIssueMilestoned { + case api.HookIssueUnassigned: + operateResult = p.Sender.UserName + " unassigned this for someone" + case api.HookIssueMilestoned: operateResult = fmt.Sprintf("%s/milestone/%d", p.Repository.HTMLURL, p.Issue.Milestone.ID) } link = p.Issue.HTMLURL - by = fmt.Sprintf("Issue by %s", p.Issue.Poster.UserName) + by = "Issue by " + p.Issue.Poster.UserName if len(assignStringList) > 0 { - assignees = fmt.Sprintf("Assignees: %s", strings.Join(assignStringList, ", ")) + assignees = "Assignees: " + strings.Join(assignStringList, ", ") } - operator = fmt.Sprintf("Operator: %s", p.Sender.UserName) + operator = "Operator: " + p.Sender.UserName return issueTitle, link, by, operator, operateResult, assignees } @@ -85,11 +87,11 @@ func getIssuesCommentInfo(p *api.IssueCommentPayload) (title, link, by, operator title = fmt.Sprintf("[Comment-%s #%d]: %s\n%s", p.Repository.FullName, p.Issue.Index, p.Action, p.Issue.Title) link = p.Issue.HTMLURL if p.IsPull { - by = fmt.Sprintf("PullRequest by %s", p.Issue.Poster.UserName) + by = "PullRequest by " + p.Issue.Poster.UserName } else { - by = fmt.Sprintf("Issue by %s", p.Issue.Poster.UserName) + by = "Issue by " + p.Issue.Poster.UserName } - operator = fmt.Sprintf("Operator: %s", p.Sender.UserName) + operator = "Operator: " + p.Sender.UserName return title, link, by, operator } @@ -133,7 +135,7 @@ func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter, with text = fmt.Sprintf("[%s] Issue milestone cleared: %s", repoLink, titleLink) } if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) + text += " by " + linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName) } if p.Action == api.HookIssueOpened || p.Action == api.HookIssueEdited { @@ -198,7 +200,7 @@ func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkForm text = fmt.Sprintf("[%s] Pull request review request removed: %s", repoLink, titleLink) } if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)) + text += " by " + linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) } return text, issueTitle, extraMarkdown, color @@ -220,7 +222,7 @@ func getReleasePayloadInfo(p *api.ReleasePayload, linkFormatter linkFormatter, w color = redColor } if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) + text += " by " + linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName) } return text, color @@ -249,7 +251,7 @@ func getWikiPayloadInfo(p *api.WikiPayload, linkFormatter linkFormatter, withSen } if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) + text += " by " + linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName) } return text, color, pageLink @@ -285,7 +287,7 @@ func getIssueCommentPayloadInfo(p *api.IssueCommentPayload, linkFormatter linkFo color = redColor } if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) + text += " by " + linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName) } return text, issueTitle, color @@ -296,14 +298,14 @@ func getPackagePayloadInfo(p *api.PackagePayload, linkFormatter linkFormatter, w switch p.Action { case api.HookPackageCreated: - text = fmt.Sprintf("Package created: %s", refLink) + text = "Package created: " + refLink color = greenColor case api.HookPackageDeleted: - text = fmt.Sprintf("Package deleted: %s", refLink) + text = "Package deleted: " + refLink color = redColor } if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) + text += " by " + linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName) } return text, color @@ -316,9 +318,9 @@ func getStatusPayloadInfo(p *api.CommitStatusPayload, linkFormatter linkFormatte color = greenColor if withSender { if user_model.IsGiteaActionsUserName(p.Sender.UserName) { - text += fmt.Sprintf(" by %s", p.Sender.FullName) + text += " by " + p.Sender.FullName } else { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) + text += " by " + linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName) } } @@ -350,7 +352,7 @@ func getWorkflowJobPayloadInfo(p *api.WorkflowJobPayload, linkFormatter linkForm color = greyColor } if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) + text += " by " + linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName) } return text, color diff --git a/services/webhook/msteams.go b/services/webhook/msteams.go index f70e235f20..07d28c3867 100644 --- a/services/webhook/msteams.go +++ b/services/webhook/msteams.go @@ -8,6 +8,7 @@ import ( "fmt" "net/http" "net/url" + "strconv" "strings" webhook_model "code.gitea.io/gitea/models/webhook" @@ -73,7 +74,7 @@ func (m msteamsConvertor) Create(p *api.CreatePayload) (MSTeamsPayload, error) { "", p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName), greenColor, - &MSTeamsFact{fmt.Sprintf("%s:", p.RefType), refName}, + &MSTeamsFact{p.RefType + ":", refName}, ), nil } @@ -90,7 +91,7 @@ func (m msteamsConvertor) Delete(p *api.DeletePayload) (MSTeamsPayload, error) { "", p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName), yellowColor, - &MSTeamsFact{fmt.Sprintf("%s:", p.RefType), refName}, + &MSTeamsFact{p.RefType + ":", refName}, ), nil } @@ -148,7 +149,7 @@ func (m msteamsConvertor) Push(p *api.PushPayload) (MSTeamsPayload, error) { text, titleLink, greenColor, - &MSTeamsFact{"Commit count:", fmt.Sprintf("%d", p.TotalCommits)}, + &MSTeamsFact{"Commit count:", strconv.Itoa(p.TotalCommits)}, ), nil } @@ -163,7 +164,7 @@ func (m msteamsConvertor) Issue(p *api.IssuePayload) (MSTeamsPayload, error) { extraMarkdown, p.Issue.HTMLURL, color, - &MSTeamsFact{"Issue #:", fmt.Sprintf("%d", p.Issue.ID)}, + &MSTeamsFact{"Issue #:", strconv.FormatInt(p.Issue.ID, 10)}, ), nil } @@ -178,7 +179,7 @@ func (m msteamsConvertor) IssueComment(p *api.IssueCommentPayload) (MSTeamsPaylo p.Comment.Body, p.Comment.HTMLURL, color, - &MSTeamsFact{"Issue #:", fmt.Sprintf("%d", p.Issue.ID)}, + &MSTeamsFact{"Issue #:", strconv.FormatInt(p.Issue.ID, 10)}, ), nil } @@ -193,7 +194,7 @@ func (m msteamsConvertor) PullRequest(p *api.PullRequestPayload) (MSTeamsPayload extraMarkdown, p.PullRequest.HTMLURL, color, - &MSTeamsFact{"Pull request #:", fmt.Sprintf("%d", p.PullRequest.ID)}, + &MSTeamsFact{"Pull request #:", strconv.FormatInt(p.PullRequest.ID, 10)}, ), nil } @@ -230,7 +231,7 @@ func (m msteamsConvertor) Review(p *api.PullRequestPayload, event webhook_module text, p.PullRequest.HTMLURL, color, - &MSTeamsFact{"Pull request #:", fmt.Sprintf("%d", p.PullRequest.ID)}, + &MSTeamsFact{"Pull request #:", strconv.FormatInt(p.PullRequest.ID, 10)}, ), nil } diff --git a/services/webhook/msteams_test.go b/services/webhook/msteams_test.go index d5020d3ff5..0d98b94bad 100644 --- a/services/webhook/msteams_test.go +++ b/services/webhook/msteams_test.go @@ -335,7 +335,7 @@ func TestMSTeamsPayload(t *testing.T) { assert.Equal(t, "[test/repo] New wiki page 'index' (Wiki change comment)", pl.Summary) assert.Len(t, pl.Sections, 1) assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Equal(t, "", pl.Sections[0].Text) + assert.Empty(t, pl.Sections[0].Text) assert.Len(t, pl.Sections[0].Facts, 2) for _, fact := range pl.Sections[0].Facts { if fact.Name == "Repository:" { @@ -356,7 +356,7 @@ func TestMSTeamsPayload(t *testing.T) { assert.Equal(t, "[test/repo] Wiki page 'index' edited (Wiki change comment)", pl.Summary) assert.Len(t, pl.Sections, 1) assert.Equal(t, "user1", pl.Sections[0].ActivitySubtitle) - assert.Equal(t, "", pl.Sections[0].Text) + assert.Empty(t, pl.Sections[0].Text) assert.Len(t, pl.Sections[0].Facts, 2) for _, fact := range pl.Sections[0].Facts { if fact.Name == "Repository:" { diff --git a/services/webhook/packagist_test.go b/services/webhook/packagist_test.go index 638dcfd302..4e77f29edc 100644 --- a/services/webhook/packagist_test.go +++ b/services/webhook/packagist_test.go @@ -210,5 +210,5 @@ func TestPackagistEmptyPayload(t *testing.T) { var body PackagistPayload err = json.NewDecoder(req.Body).Decode(&body) assert.NoError(t, err) - assert.Equal(t, "", body.PackagistRepository.URL) + assert.Empty(t, body.PackagistRepository.URL) } diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index 4cc372c70d..84cb9ced4d 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -102,15 +102,11 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model hasDefaultBranch := gitrepo.IsBranchExist(ctx, repo.WikiStorageRepo(), repo.DefaultWikiBranch) - basePath, err := repo_module.CreateTemporaryPath("update-wiki") + basePath, cleanup, err := repo_module.CreateTemporaryPath("update-wiki") if err != nil { return err } - defer func() { - if err := repo_module.RemoveTemporaryPath(basePath); err != nil { - log.Error("Merge: RemoveTemporaryPath: %s", err) - } - }() + defer cleanup() cloneOpts := git.CloneRepoOptions{ Bare: true, @@ -264,15 +260,11 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model return fmt.Errorf("InitWiki: %w", err) } - basePath, err := repo_module.CreateTemporaryPath("update-wiki") + basePath, cleanup, err := repo_module.CreateTemporaryPath("update-wiki") if err != nil { return err } - defer func() { - if err := repo_module.RemoveTemporaryPath(basePath); err != nil { - log.Error("Merge: RemoveTemporaryPath: %s", err) - } - }() + defer cleanup() if err := git.Clone(ctx, repo.WikiPath(), basePath, git.CloneRepoOptions{ Bare: true, diff --git a/services/wiki/wiki_test.go b/services/wiki/wiki_test.go index 288d258279..f441c2939b 100644 --- a/services/wiki/wiki_test.go +++ b/services/wiki/wiki_test.go @@ -26,7 +26,7 @@ func TestMain(m *testing.M) { func TestWebPathSegments(t *testing.T) { a := WebPathSegments("a%2Fa/b+c/d-e/f-g.-") - assert.EqualValues(t, []string{"a/a", "b c", "d e", "f-g"}, a) + assert.Equal(t, []string{"a/a", "b c", "d e", "f-g"}, a) } func TestUserTitleToWebPath(t *testing.T) { @@ -63,7 +63,7 @@ func TestWebPathToDisplayName(t *testing.T) { {"a b", "a%20b.md"}, } { _, displayName := WebPathToUserTitle(test.WebPath) - assert.EqualValues(t, test.Expected, displayName) + assert.Equal(t, test.Expected, displayName) } } @@ -80,7 +80,7 @@ func TestWebPathToGitPath(t *testing.T) { {"2000-01-02-meeting.md", "2000-01-02+meeting"}, {"2000-01-02 meeting.-.md", "2000-01-02%20meeting.-"}, } { - assert.EqualValues(t, test.Expected, WebPathToGitPath(test.WikiName)) + assert.Equal(t, test.Expected, WebPathToGitPath(test.WikiName)) } } @@ -134,9 +134,9 @@ func TestUserWebGitPathConsistency(t *testing.T) { _, userTitle1 := WebPathToUserTitle(webPath1) gitPath1 := WebPathToGitPath(webPath1) - assert.EqualValues(t, userTitle, userTitle1, "UserTitle for userTitle: %q", userTitle) - assert.EqualValues(t, webPath, webPath1, "WebPath for userTitle: %q", userTitle) - assert.EqualValues(t, gitPath, gitPath1, "GitPath for userTitle: %q", userTitle) + assert.Equal(t, userTitle, userTitle1, "UserTitle for userTitle: %q", userTitle) + assert.Equal(t, webPath, webPath1, "WebPath for userTitle: %q", userTitle) + assert.Equal(t, gitPath, gitPath1, "GitPath for userTitle: %q", userTitle) } } @@ -175,7 +175,7 @@ func TestRepository_AddWikiPage(t *testing.T) { gitPath := WebPathToGitPath(webPath) entry, err := masterTree.GetTreeEntryByPath(gitPath) assert.NoError(t, err) - assert.EqualValues(t, gitPath, entry.Name(), "%s not added correctly", userTitle) + assert.Equal(t, gitPath, entry.Name(), "%s not added correctly", userTitle) }) } @@ -220,7 +220,7 @@ func TestRepository_EditWikiPage(t *testing.T) { gitPath := WebPathToGitPath(webPath) entry, err := masterTree.GetTreeEntryByPath(gitPath) assert.NoError(t, err) - assert.EqualValues(t, gitPath, entry.Name(), "%s not edited correctly", newWikiName) + assert.Equal(t, gitPath, entry.Name(), "%s not edited correctly", newWikiName) if newWikiName != "Home" { _, err := masterTree.GetTreeEntryByPath("Home.md") @@ -290,7 +290,7 @@ func TestPrepareWikiFileName(t *testing.T) { t.Errorf("expect to find an escaped file but we could not detect one") } } - assert.EqualValues(t, tt.wikiPath, newWikiPath) + assert.Equal(t, tt.wikiPath, newWikiPath) }) } } @@ -312,13 +312,13 @@ func TestPrepareWikiFileName_FirstPage(t *testing.T) { existence, newWikiPath, err := prepareGitPath(gitRepo, "master", "Home") assert.False(t, existence) assert.NoError(t, err) - assert.EqualValues(t, "Home.md", newWikiPath) + assert.Equal(t, "Home.md", newWikiPath) } func TestWebPathConversion(t *testing.T) { assert.Equal(t, "path/wiki", WebPathToURLPath(WebPath("path/wiki"))) assert.Equal(t, "wiki", WebPathToURLPath(WebPath("wiki"))) - assert.Equal(t, "", WebPathToURLPath(WebPath(""))) + assert.Empty(t, WebPathToURLPath(WebPath(""))) } func TestWebPathFromRequest(t *testing.T) { diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 29a5e1b473..806347c720 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -69,10 +69,6 @@ {{if not .SSH.StartBuiltinServer}}
{{svg "octicon-git-commit" 16 "tw-mr-1"}}{{ShortSha .DefaultBranchBranch.DBBranch.CommitID}} · {{ctx.RenderUtils.RenderCommitMessage .DefaultBranchBranch.DBBranch.CommitMessage (.Repository.ComposeMetas ctx)}} · {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DefaultBranchBranch.DBBranch.CommitTime}}{{if .DefaultBranchBranch.DBBranch.Pusher}} {{template "shared/user/avatarlink" dict "user" .DefaultBranchBranch.DBBranch.Pusher}}{{template "shared/user/namelink" .DefaultBranchBranch.DBBranch.Pusher}}{{end}}
{{svg "octicon-git-commit" 16 "tw-mr-1"}}{{ShortSha .DefaultBranchBranch.DBBranch.CommitID}} · {{ctx.RenderUtils.RenderCommitMessage .DefaultBranchBranch.DBBranch.CommitMessage (.Repository.ComposeCommentMetas ctx)}} · {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DefaultBranchBranch.DBBranch.CommitTime}}{{if .DefaultBranchBranch.DBBranch.Pusher}} {{template "shared/user/avatarlink" dict "user" .DefaultBranchBranch.DBBranch.Pusher}}{{template "shared/user/namelink" .DefaultBranchBranch.DBBranch.Pusher}}{{end}}
{{svg "octicon-git-commit" 16 "tw-mr-1"}}{{ShortSha .DBBranch.CommitID}} · {{ctx.RenderUtils.RenderCommitMessage .DBBranch.CommitMessage ($.Repository.ComposeMetas ctx)}} · {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DBBranch.CommitTime}}{{if .DBBranch.Pusher}} {{template "shared/user/avatarlink" dict "user" .DBBranch.Pusher}} {{template "shared/user/namelink" .DBBranch.Pusher}}{{end}}
{{svg "octicon-git-commit" 16 "tw-mr-1"}}{{ShortSha .DBBranch.CommitID}} · {{ctx.RenderUtils.RenderCommitMessage .DBBranch.CommitMessage ($.Repository.ComposeCommentMetas ctx)}} · {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DBBranch.CommitTime}}{{if .DBBranch.Pusher}} {{template "shared/user/avatarlink" dict "user" .DBBranch.Pusher}} {{template "shared/user/namelink" .DBBranch.Pusher}}{{end}}
{{ctx.RenderUtils.RenderCommitBody .Commit.Message ($.Repository.ComposeMetas ctx)}}
{{ctx.RenderUtils.RenderCommitBody .Commit.Message ($.Repository.ComposeCommentMetas ctx)}}
{{ctx.RenderUtils.RenderCommitBody .Message ($.Repository.ComposeMetas ctx)}}
{{ctx.RenderUtils.RenderCommitBody .Message ($.Repository.ComposeCommentMetas ctx)}}
- {{- ctx.RenderUtils.RenderCommitBody .Message ($.comment.Issue.PullRequest.BaseRepo.ComposeMetas ctx) -}} + {{- ctx.RenderUtils.RenderCommitBody .Message ($.comment.Issue.PullRequest.BaseRepo.ComposeCommentMetas ctx) -}}
{{ctx.RenderUtils.RenderCommitBody .LatestCommit.Message ($.Repository.ComposeMetas ctx)}}
{{ctx.RenderUtils.RenderCommitBody .LatestCommit.Message ($.Repository.ComposeCommentMetas ctx)}}
+ {{ctx.Locale.Tr "repo.settings.public_access_desc"}} +
{{ctx.Locale.Tr "error.occurred"}}:
{{.ErrorMsg}}