Merge branch 'forgejo' into bugfix-arch

This commit is contained in:
Exploding Dragon 2024-08-27 02:34:54 +00:00
commit bc4849a904
71 changed files with 1044 additions and 365 deletions

View file

@ -10,7 +10,7 @@ labels:
## Checklist ## Checklist
The [developer guide](https://forgejo.org/docs/next/developer/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).
### Tests ### Tests

View file

@ -1,5 +1,5 @@
# #
# See also https://forgejo.org/docs/next/developer/RELEASE/#release-process # See also https://forgejo.org/docs/next/contributor/release/#stable-release-process
# #
# https://codeberg.org/forgejo-integration/forgejo # https://codeberg.org/forgejo-integration/forgejo
# #

View file

@ -22,7 +22,7 @@ jobs:
go-version-file: "go.mod" go-version-file: "go.mod"
- run: | - run: |
apt-get -qq update apt-get -qq update
apt-get -qq install -q sudo apt-get -qq install -q sudo git git-lfs
sed -i -e 's/%sudo.*/%sudo ALL=(ALL:ALL) NOPASSWD:ALL/' /etc/sudoers sed -i -e 's/%sudo.*/%sudo ALL=(ALL:ALL) NOPASSWD:ALL/' /etc/sudoers
git config --add safe.directory '*' git config --add safe.directory '*'
adduser --quiet --comment forgejo --disabled-password forgejo adduser --quiet --comment forgejo --disabled-password forgejo
@ -30,6 +30,8 @@ jobs:
chown -R forgejo:forgejo . chown -R forgejo:forgejo .
- run: | - run: |
su forgejo -c 'make deps-frontend frontend deps-backend' su forgejo -c 'make deps-frontend frontend deps-backend'
- run: |
su forgejo -c 'make backend'
- run: | - run: |
su forgejo -c 'make generate test-e2e-sqlite' su forgejo -c 'make generate test-e2e-sqlite'
timeout-minutes: 40 timeout-minutes: 40

View file

@ -1,6 +1,6 @@
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
# #
# See also https://forgejo.org/docs/next/developer/RELEASE/#release-process # See also https://forgejo.org/docs/next/contributor/release/#stable-release-process
# #
# https://codeberg.org/forgejo-experimental/forgejo # https://codeberg.org/forgejo-experimental/forgejo
# #

View file

@ -23,7 +23,7 @@ jobs:
runs-on: docker runs-on: docker
container: container:
image: code.forgejo.org/forgejo-contrib/renovate:38.39.6 image: code.forgejo.org/forgejo-contrib/renovate:38.52.3
steps: steps:
- name: Load renovate repo cache - name: Load renovate repo cache

View file

@ -4,4 +4,4 @@ The Forgejo project is run by a community of people who are expected to follow t
Sensitive security-related issues should be reported to [security@forgejo.org](mailto:security@forgejo.org) using [encryption](https://keyoxide.org/security@forgejo.org). Sensitive security-related issues should be reported to [security@forgejo.org](mailto:security@forgejo.org) using [encryption](https://keyoxide.org/security@forgejo.org).
You can find links to the different aspects of Developer documentation on this page: [Forgejo developer guide](https://forgejo.org/docs/next/developer/). You can find links to the different aspects of Developer documentation on this page: [Forgejo Contributor Guide](https://forgejo.org/docs/next/contributor/).

View file

@ -39,7 +39,7 @@ GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasour
DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.24.0 # renovate: datasource=go DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.24.0 # renovate: datasource=go
GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.4.0 # renovate: datasource=go GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.4.0 # renovate: datasource=go
GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.16.1 # renovate: datasource=go GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.16.1 # renovate: datasource=go
RENOVATE_NPM_PACKAGE ?= renovate@38.39.6 # renovate: datasource=docker packageName=code.forgejo.org/forgejo-contrib/renovate RENOVATE_NPM_PACKAGE ?= renovate@38.52.3 # renovate: datasource=docker packageName=code.forgejo.org/forgejo-contrib/renovate
ifeq ($(HAS_GO), yes) ifeq ($(HAS_GO), yes)
CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766 CGO_EXTRA_CFLAGS := -DSQLITE_MAX_VARIABLE_NUMBER=32766

File diff suppressed because one or more lines are too long

View file

@ -20,7 +20,7 @@ import (
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"gitea.com/go-chi/session" "code.forgejo.org/go-chi/session"
"github.com/mholt/archiver/v3" "github.com/mholt/archiver/v3"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )

17
go.mod
View file

@ -5,6 +5,7 @@ go 1.23.0
require ( require (
code.forgejo.org/f3/gof3/v3 v3.7.0 code.forgejo.org/f3/gof3/v3 v3.7.0
code.forgejo.org/forgejo/reply v1.0.2 code.forgejo.org/forgejo/reply v1.0.2
code.forgejo.org/go-chi/session v0.0.0-20240825010209-bd25d509c8bf
code.gitea.io/actions-proto-go v0.4.0 code.gitea.io/actions-proto-go v0.4.0
code.gitea.io/gitea-vet v0.2.3 code.gitea.io/gitea-vet v0.2.3
code.gitea.io/sdk/gitea v0.17.1 code.gitea.io/sdk/gitea v0.17.1
@ -13,7 +14,6 @@ require (
gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed
gitea.com/go-chi/cache v0.2.0 gitea.com/go-chi/cache v0.2.0
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121 github.com/42wim/sshsig v0.0.0-20211121163825-841cf5bbc121
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
@ -37,7 +37,7 @@ require (
github.com/gliderlabs/ssh v0.3.7 github.com/gliderlabs/ssh v0.3.7
github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9 github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
github.com/go-chi/chi/v5 v5.0.14 github.com/go-chi/chi/v5 v5.1.0
github.com/go-chi/cors v1.2.1 github.com/go-chi/cors v1.2.1
github.com/go-co-op/gocron v1.37.0 github.com/go-co-op/gocron v1.37.0
github.com/go-enry/go-enry/v2 v2.8.9 github.com/go-enry/go-enry/v2 v2.8.9
@ -84,7 +84,7 @@ require (
github.com/pquerna/otp v1.4.0 github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.18.0 github.com/prometheus/client_golang v1.18.0
github.com/quasoft/websspi v1.1.2 github.com/quasoft/websspi v1.1.2
github.com/redis/go-redis/v9 v9.5.2 github.com/redis/go-redis/v9 v9.6.1
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 github.com/santhosh-tekuri/jsonschema/v6 v6.0.1
github.com/sassoftware/go-rpmutils v0.4.0 github.com/sassoftware/go-rpmutils v0.4.0
@ -161,9 +161,6 @@ require (
github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudflare/circl v1.3.8 // indirect github.com/cloudflare/circl v1.3.8 // indirect
github.com/couchbase/go-couchbase v0.1.1 // indirect
github.com/couchbase/gomemcached v0.3.0 // indirect
github.com/couchbase/goutils v0.1.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
@ -300,12 +297,4 @@ replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142
replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.21.2 replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.21.2
exclude github.com/gofrs/uuid v3.2.0+incompatible
exclude github.com/gofrs/uuid v4.0.0+incompatible
exclude github.com/goccy/go-json v0.4.11
exclude github.com/satori/go.uuid v1.2.0
replace github.com/mholt/archiver/v3 => code.forgejo.org/forgejo/archiver/v3 v3.5.1 replace github.com/mholt/archiver/v3 => code.forgejo.org/forgejo/archiver/v3 v3.5.1

18
go.sum
View file

@ -8,6 +8,8 @@ code.forgejo.org/forgejo/archiver/v3 v3.5.1 h1:UmmbA7D5550uf71SQjarmrn6yKwOGxtEj
code.forgejo.org/forgejo/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= code.forgejo.org/forgejo/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
code.forgejo.org/forgejo/reply v1.0.2 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCdgFuQ= code.forgejo.org/forgejo/reply v1.0.2 h1:dMhQCHV6/O3L5CLWNTol+dNzDAuyCK88z4J/lCdgFuQ=
code.forgejo.org/forgejo/reply v1.0.2/go.mod h1:RyZUfzQLc+fuLIGjTSQWDAJWPiL4WtKXB/FifT5fM7U= code.forgejo.org/forgejo/reply v1.0.2/go.mod h1:RyZUfzQLc+fuLIGjTSQWDAJWPiL4WtKXB/FifT5fM7U=
code.forgejo.org/go-chi/session v0.0.0-20240825010209-bd25d509c8bf h1:gJRuqEPd3/U0/1YM+uSgbC/fpR8qrcMdvT6E7eSetyM=
code.forgejo.org/go-chi/session v0.0.0-20240825010209-bd25d509c8bf/go.mod h1:PcnIg89MAhO1yExkw1QXXNDiPssVdCsMmwUo67g7GD4=
code.gitea.io/actions-proto-go v0.4.0 h1:OsPBPhodXuQnsspG1sQ4eRE1PeoZyofd7+i73zCwnsU= code.gitea.io/actions-proto-go v0.4.0 h1:OsPBPhodXuQnsspG1sQ4eRE1PeoZyofd7+i73zCwnsU=
code.gitea.io/actions-proto-go v0.4.0/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas= code.gitea.io/actions-proto-go v0.4.0/go.mod h1:mn7Wkqz6JbnTOHQpot3yDeHx+O5C9EGhMEE+htvHBas=
code.gitea.io/gitea-vet v0.2.3 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI= code.gitea.io/gitea-vet v0.2.3 h1:gdFmm6WOTM65rE8FUBTRzeQZYzXePKSSB1+r574hWwI=
@ -30,8 +32,6 @@ gitea.com/go-chi/cache v0.2.0 h1:E0npuTfDW6CT1yD8NMDVc1SK6IeRjfmRL2zlEsCEd7w=
gitea.com/go-chi/cache v0.2.0/go.mod h1:iQlVK2aKTZ/rE9UcHyz9pQWGvdP9i1eI2spOpzgCrtE= gitea.com/go-chi/cache v0.2.0/go.mod h1:iQlVK2aKTZ/rE9UcHyz9pQWGvdP9i1eI2spOpzgCrtE=
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR98IP2KZQKRzJJiV7aTeMAFwaWo= gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098 h1:p2ki+WK0cIeNQuqjR98IP2KZQKRzJJiV7aTeMAFwaWo=
gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098/go.mod h1:LjzIOHlRemuUyO7WR12fmm18VZIlCAaOt9L3yKw40pk= gitea.com/go-chi/captcha v0.0.0-20240315150714-fb487f629098/go.mod h1:LjzIOHlRemuUyO7WR12fmm18VZIlCAaOt9L3yKw40pk=
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96 h1:IFDiMBObsP6CZIRaDLd54SR6zPYAffPXiXck5Xslu0Q=
gitea.com/go-chi/session v0.0.0-20240316035857-16768d98ec96/go.mod h1:0iEpFKnwO5dG0aF98O4eq6FMsAiXkNBaDIlUOlq4BtM=
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHqdhS7keYWioqfmxdnfblFDTGoOwcZ+o= gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4 h1:IFT+hup2xejHqdhS7keYWioqfmxdnfblFDTGoOwcZ+o=
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU= gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4/go.mod h1:HBqmLbz56JWpfEGG0prskAV97ATNRoj5LDmPicD22hU=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
@ -165,12 +165,6 @@ github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI= github.com/cloudflare/circl v1.3.8 h1:j+V8jJt09PoeMFIu2uh5JUyEaIHTXVOHslFoLNAKqwI=
github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/couchbase/go-couchbase v0.1.1 h1:ClFXELcKj/ojyoTYbsY34QUrrYCBi/1G749sXSCkdhk=
github.com/couchbase/go-couchbase v0.1.1/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A=
github.com/couchbase/gomemcached v0.3.0 h1:XkMDdP6w7rtvLijDE0/RhcccX+XvAk5cboyBv1YcI0U=
github.com/couchbase/gomemcached v0.3.0/go.mod h1:mxliKQxOv84gQ0bJWbI+w9Wxdpt9HjDvgW9MjCym5Vo=
github.com/couchbase/goutils v0.1.2 h1:gWr8B6XNWPIhfalHNog3qQKfGiYyh4K4VhO3P2o9BCs=
github.com/couchbase/goutils v0.1.2/go.mod h1:h89Ek/tiOxxqjz30nPPlwZdQbdB8BwgnuBxeoUe/ViE=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@ -244,8 +238,8 @@ github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73/go.mod h1:jyveZeGw5La
github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.0.14 h1:PyEwo2Vudraa0x/Wl6eDRRW2NXBvekgfxyydcM0WGE0= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.0.14/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
@ -608,8 +602,8 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/quasoft/websspi v1.1.2 h1:/mA4w0LxWlE3novvsoEL6BBA1WnjJATbjkh1kFrTidw= github.com/quasoft/websspi v1.1.2 h1:/mA4w0LxWlE3novvsoEL6BBA1WnjJATbjkh1kFrTidw=
github.com/quasoft/websspi v1.1.2/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk= github.com/quasoft/websspi v1.1.2/go.mod h1:HmVdl939dQ0WIXZhyik+ARdI03M6bQzaSEKcgpFmewk=
github.com/redis/go-redis/v9 v9.5.2 h1:L0L3fcSNReTRGyZ6AqAEN0K56wYeYAwapBIhkvh0f3E= github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
github.com/redis/go-redis/v9 v9.5.2/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rhysd/actionlint v1.6.27 h1:xxwe8YmveBcC8lydW6GoHMGmB6H/MTqUU60F2p10wjw= github.com/rhysd/actionlint v1.6.27 h1:xxwe8YmveBcC8lydW6GoHMGmB6H/MTqUU60F2p10wjw=

25
models/quota/default.go Normal file
View file

@ -0,0 +1,25 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package quota
import (
"code.gitea.io/gitea/modules/setting"
)
func EvaluateDefault(used Used, forSubject LimitSubject) bool {
groups := GroupList{
&Group{
Name: "builtin-default-group",
Rules: []Rule{
{
Name: "builtin-default-rule",
Limit: setting.Quota.Default.Total,
Subjects: LimitSubjects{LimitSubjectSizeAll},
},
},
},
}
return groups.Evaluate(used, forSubject)
}

View file

@ -230,9 +230,9 @@ func (g *Group) Evaluate(used Used, forSubject LimitSubject) (bool, bool) {
} }
func (gl *GroupList) Evaluate(used Used, forSubject LimitSubject) bool { func (gl *GroupList) Evaluate(used Used, forSubject LimitSubject) bool {
// If there are no groups, default to success: // If there are no groups, use the configured defaults:
if gl == nil || len(*gl) == 0 { if gl == nil || len(*gl) == 0 {
return true return EvaluateDefault(used, forSubject)
} }
for _, group := range *gl { for _, group := range *gl {

46
modules/git/batch.go Normal file
View file

@ -0,0 +1,46 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"bufio"
"context"
)
type Batch struct {
cancel context.CancelFunc
Reader *bufio.Reader
Writer WriteCloserError
}
func (repo *Repository) NewBatch(ctx context.Context) (*Batch, error) {
// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
if err := ensureValidGitRepository(ctx, repo.Path); err != nil {
return nil, err
}
var batch Batch
batch.Writer, batch.Reader, batch.cancel = catFileBatch(ctx, repo.Path)
return &batch, nil
}
func (repo *Repository) NewBatchCheck(ctx context.Context) (*Batch, error) {
// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
if err := ensureValidGitRepository(ctx, repo.Path); err != nil {
return nil, err
}
var check Batch
check.Writer, check.Reader, check.cancel = catFileBatchCheck(ctx, repo.Path)
return &check, nil
}
func (b *Batch) Close() {
if b.cancel != nil {
b.cancel()
b.Reader = nil
b.Writer = nil
b.cancel = nil
}
}

View file

@ -26,10 +26,10 @@ type WriteCloserError interface {
CloseWithError(err error) error CloseWithError(err error) error
} }
// EnsureValidGitRepository runs git rev-parse in the repository path - thus ensuring that the repository is a valid repository. // ensureValidGitRepository runs git rev-parse in the repository path - thus ensuring that the repository is a valid repository.
// Run before opening git cat-file. // Run before opening git cat-file.
// This is needed otherwise the git cat-file will hang for invalid repositories. // This is needed otherwise the git cat-file will hang for invalid repositories.
func EnsureValidGitRepository(ctx context.Context, repoPath string) error { func ensureValidGitRepository(ctx context.Context, repoPath string) error {
stderr := strings.Builder{} stderr := strings.Builder{}
err := NewCommand(ctx, "rev-parse"). err := NewCommand(ctx, "rev-parse").
SetDescription(fmt.Sprintf("%s rev-parse [repo_path: %s]", GitExecutable, repoPath)). SetDescription(fmt.Sprintf("%s rev-parse [repo_path: %s]", GitExecutable, repoPath)).
@ -43,8 +43,8 @@ func EnsureValidGitRepository(ctx context.Context, repoPath string) error {
return nil return nil
} }
// CatFileBatchCheck opens git cat-file --batch-check in the provided repo and returns a stdin pipe, a stdout reader and cancel function // catFileBatchCheck opens git cat-file --batch-check in the provided repo and returns a stdin pipe, a stdout reader and cancel function
func CatFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError, *bufio.Reader, func()) { func catFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError, *bufio.Reader, func()) {
batchStdinReader, batchStdinWriter := io.Pipe() batchStdinReader, batchStdinWriter := io.Pipe()
batchStdoutReader, batchStdoutWriter := io.Pipe() batchStdoutReader, batchStdoutWriter := io.Pipe()
ctx, ctxCancel := context.WithCancel(ctx) ctx, ctxCancel := context.WithCancel(ctx)
@ -93,8 +93,8 @@ func CatFileBatchCheck(ctx context.Context, repoPath string) (WriteCloserError,
return batchStdinWriter, batchReader, cancel return batchStdinWriter, batchReader, cancel
} }
// CatFileBatch opens git cat-file --batch in the provided repo and returns a stdin pipe, a stdout reader and cancel function // catFileBatch opens git cat-file --batch in the provided repo and returns a stdin pipe, a stdout reader and cancel function
func CatFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufio.Reader, func()) { func catFileBatch(ctx context.Context, repoPath string) (WriteCloserError, *bufio.Reader, func()) {
// We often want to feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary. // We often want to feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary.
// so let's create a batch stdin and stdout // so let's create a batch stdin and stdout
batchStdinReader, batchStdinWriter := io.Pipe() batchStdinReader, batchStdinWriter := io.Pipe()

View file

@ -28,9 +28,12 @@ type Blob struct {
// DataAsync gets a ReadCloser for the contents of a blob without reading it all. // DataAsync gets a ReadCloser for the contents of a blob without reading it all.
// Calling the Close function on the result will discard all unread output. // Calling the Close function on the result will discard all unread output.
func (b *Blob) DataAsync() (io.ReadCloser, error) { func (b *Blob) DataAsync() (io.ReadCloser, error) {
wr, rd, cancel := b.repo.CatFileBatch(b.repo.Ctx) wr, rd, cancel, err := b.repo.CatFileBatch(b.repo.Ctx)
if err != nil {
return nil, err
}
_, err := wr.Write([]byte(b.ID.String() + "\n")) _, err = wr.Write([]byte(b.ID.String() + "\n"))
if err != nil { if err != nil {
cancel() cancel()
return nil, err return nil, err
@ -66,9 +69,13 @@ func (b *Blob) Size() int64 {
return b.size return b.size
} }
wr, rd, cancel := b.repo.CatFileBatchCheck(b.repo.Ctx) wr, rd, cancel, err := b.repo.CatFileBatchCheck(b.repo.Ctx)
if err != nil {
log.Debug("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repo.Path, err)
return 0
}
defer cancel() defer cancel()
_, err := wr.Write([]byte(b.ID.String() + "\n")) _, err = wr.Write([]byte(b.ID.String() + "\n"))
if err != nil { if err != nil {
log.Debug("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repo.Path, err) log.Debug("error whilst reading size for %s in %s. Error: %v", b.ID.String(), b.repo.Path, err)
return 0 return 0

View file

@ -129,7 +129,10 @@ func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string,
return nil, err return nil, err
} }
batchStdinWriter, batchReader, cancel := commit.repo.CatFileBatch(ctx) batchStdinWriter, batchReader, cancel, err := commit.repo.CatFileBatch(ctx)
if err != nil {
return nil, err
}
defer cancel() defer cancel()
commitsMap := map[string]*Commit{} commitsMap := map[string]*Commit{}

View file

@ -38,6 +38,8 @@ var (
InvertedGitFlushEnv bool // 2.43.1 InvertedGitFlushEnv bool // 2.43.1
SupportCheckAttrOnBare bool // >= 2.40 SupportCheckAttrOnBare bool // >= 2.40
HasSSHExecutable bool
gitVersion *version.Version gitVersion *version.Version
) )
@ -203,6 +205,10 @@ func InitFull(ctx context.Context) (err error) {
globalCommandArgs = append(globalCommandArgs, "-c", "filter.lfs.required=", "-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=") globalCommandArgs = append(globalCommandArgs, "-c", "filter.lfs.required=", "-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=")
} }
// Detect the presence of the ssh executable in $PATH.
_, err = exec.LookPath("ssh")
HasSSHExecutable = err == nil
return syncGitConfig() return syncGitConfig()
} }

View file

@ -67,7 +67,10 @@ func FindLFSFile(repo *git.Repository, objectID git.ObjectID) ([]*LFSResult, err
// Next feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary. // Next feed the commits in order into cat-file --batch, followed by their trees and sub trees as necessary.
// so let's create a batch stdin and stdout // so let's create a batch stdin and stdout
batchStdinWriter, batchReader, cancel := repo.CatFileBatch(repo.Ctx) batchStdinWriter, batchReader, cancel, err := repo.CatFileBatch(repo.Ctx)
if err != nil {
return nil, err
}
defer cancel() defer cancel()
// We'll use a scanner for the revList because it's simpler than a bufio.Reader // We'll use a scanner for the revList because it's simpler than a bufio.Reader

View file

@ -22,14 +22,10 @@ type Repository struct {
gpgSettings *GPGSettings gpgSettings *GPGSettings
batchInUse bool batchInUse bool
batchCancel context.CancelFunc batch *Batch
batchReader *bufio.Reader
batchWriter WriteCloserError
checkInUse bool checkInUse bool
checkCancel context.CancelFunc check *Batch
checkReader *bufio.Reader
checkWriter WriteCloserError
Ctx context.Context Ctx context.Context
LastCommitCache *LastCommitCache LastCommitCache *LastCommitCache
@ -51,63 +47,75 @@ func OpenRepository(ctx context.Context, repoPath string) (*Repository, error) {
return nil, errors.New("no such file or directory") return nil, errors.New("no such file or directory")
} }
// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first! return &Repository{
if err := EnsureValidGitRepository(ctx, repoPath); err != nil {
return nil, err
}
repo := &Repository{
Path: repoPath, Path: repoPath,
tagCache: newObjectCache(), tagCache: newObjectCache(),
Ctx: ctx, Ctx: ctx,
} }, nil
repo.batchWriter, repo.batchReader, repo.batchCancel = CatFileBatch(ctx, repoPath)
repo.checkWriter, repo.checkReader, repo.checkCancel = CatFileBatchCheck(ctx, repoPath)
return repo, nil
} }
// CatFileBatch obtains a CatFileBatch for this repository // CatFileBatch obtains a CatFileBatch for this repository
func (repo *Repository) CatFileBatch(ctx context.Context) (WriteCloserError, *bufio.Reader, func()) { func (repo *Repository) CatFileBatch(ctx context.Context) (WriteCloserError, *bufio.Reader, func(), error) {
if repo.batchCancel == nil || repo.batchInUse { if repo.batch == nil {
log.Debug("Opening temporary cat file batch for: %s", repo.Path) var err error
return CatFileBatch(ctx, repo.Path) repo.batch, err = repo.NewBatch(ctx)
if err != nil {
return nil, nil, nil, err
} }
}
if !repo.batchInUse {
repo.batchInUse = true repo.batchInUse = true
return repo.batchWriter, repo.batchReader, func() { return repo.batch.Writer, repo.batch.Reader, func() {
repo.batchInUse = false repo.batchInUse = false
}, nil
} }
log.Debug("Opening temporary cat file batch for: %s", repo.Path)
tempBatch, err := repo.NewBatch(ctx)
if err != nil {
return nil, nil, nil, err
}
return tempBatch.Writer, tempBatch.Reader, tempBatch.Close, nil
} }
// CatFileBatchCheck obtains a CatFileBatchCheck for this repository // CatFileBatchCheck obtains a CatFileBatchCheck for this repository
func (repo *Repository) CatFileBatchCheck(ctx context.Context) (WriteCloserError, *bufio.Reader, func()) { func (repo *Repository) CatFileBatchCheck(ctx context.Context) (WriteCloserError, *bufio.Reader, func(), error) {
if repo.checkCancel == nil || repo.checkInUse { if repo.check == nil {
log.Debug("Opening temporary cat file batch-check for: %s", repo.Path) var err error
return CatFileBatchCheck(ctx, repo.Path) repo.check, err = repo.NewBatchCheck(ctx)
if err != nil {
return nil, nil, nil, err
} }
}
if !repo.checkInUse {
repo.checkInUse = true repo.checkInUse = true
return repo.checkWriter, repo.checkReader, func() { return repo.check.Writer, repo.check.Reader, func() {
repo.checkInUse = false repo.checkInUse = false
}, nil
} }
log.Debug("Opening temporary cat file batch-check for: %s", repo.Path)
tempBatchCheck, err := repo.NewBatchCheck(ctx)
if err != nil {
return nil, nil, nil, err
}
return tempBatchCheck.Writer, tempBatchCheck.Reader, tempBatchCheck.Close, nil
} }
func (repo *Repository) Close() error { func (repo *Repository) Close() error {
if repo == nil { if repo == nil {
return nil return nil
} }
if repo.batchCancel != nil { if repo.batch != nil {
repo.batchCancel() repo.batch.Close()
repo.batchReader = nil repo.batch = nil
repo.batchWriter = nil
repo.batchCancel = nil
repo.batchInUse = false repo.batchInUse = false
} }
if repo.checkCancel != nil { if repo.check != nil {
repo.checkCancel() repo.check.Close()
repo.checkCancel = nil repo.check = nil
repo.checkReader = nil
repo.checkWriter = nil
repo.checkInUse = false repo.checkInUse = false
} }
repo.LastCommitCache = nil repo.LastCommitCache = nil

View file

@ -0,0 +1,163 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package git
import (
"bufio"
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// This unit test relies on the implementation detail of CatFileBatch.
func TestCatFileBatch(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
repo, err := OpenRepository(ctx, "./tests/repos/repo1_bare")
require.NoError(t, err)
defer repo.Close()
var wr WriteCloserError
var r *bufio.Reader
var cancel1 func()
t.Run("Request cat file batch", func(t *testing.T) {
assert.Nil(t, repo.batch)
wr, r, cancel1, err = repo.CatFileBatch(ctx)
require.NoError(t, err)
assert.NotNil(t, repo.batch)
assert.Equal(t, repo.batch.Writer, wr)
assert.True(t, repo.batchInUse)
})
t.Run("Request temporary cat file batch", func(t *testing.T) {
wr, r, cancel, err := repo.CatFileBatch(ctx)
require.NoError(t, err)
assert.NotEqual(t, repo.batch.Writer, wr)
t.Run("Check temporary cat file batch", func(t *testing.T) {
_, err = wr.Write([]byte("95bb4d39648ee7e325106df01a621c530863a653" + "\n"))
require.NoError(t, err)
sha, typ, size, err := ReadBatchLine(r)
require.NoError(t, err)
assert.Equal(t, "commit", typ)
assert.EqualValues(t, []byte("95bb4d39648ee7e325106df01a621c530863a653"), sha)
assert.EqualValues(t, 144, size)
})
cancel()
assert.True(t, repo.batchInUse)
})
t.Run("Check cached cat file batch", func(t *testing.T) {
_, err = wr.Write([]byte("95bb4d39648ee7e325106df01a621c530863a653" + "\n"))
require.NoError(t, err)
sha, typ, size, err := ReadBatchLine(r)
require.NoError(t, err)
assert.Equal(t, "commit", typ)
assert.EqualValues(t, []byte("95bb4d39648ee7e325106df01a621c530863a653"), sha)
assert.EqualValues(t, 144, size)
})
t.Run("Cancel cached cat file batch", func(t *testing.T) {
cancel1()
assert.False(t, repo.batchInUse)
assert.NotNil(t, repo.batch)
})
t.Run("Request cached cat file batch", func(t *testing.T) {
wr, _, _, err := repo.CatFileBatch(ctx)
require.NoError(t, err)
assert.NotNil(t, repo.batch)
assert.Equal(t, repo.batch.Writer, wr)
assert.True(t, repo.batchInUse)
t.Run("Close git repo", func(t *testing.T) {
require.NoError(t, repo.Close())
assert.Nil(t, repo.batch)
})
_, err = wr.Write([]byte("95bb4d39648ee7e325106df01a621c530863a653" + "\n"))
require.Error(t, err)
})
}
// This unit test relies on the implementation detail of CatFileBatchCheck.
func TestCatFileBatchCheck(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
repo, err := OpenRepository(ctx, "./tests/repos/repo1_bare")
require.NoError(t, err)
defer repo.Close()
var wr WriteCloserError
var r *bufio.Reader
var cancel1 func()
t.Run("Request cat file batch check", func(t *testing.T) {
assert.Nil(t, repo.check)
wr, r, cancel1, err = repo.CatFileBatchCheck(ctx)
require.NoError(t, err)
assert.NotNil(t, repo.check)
assert.Equal(t, repo.check.Writer, wr)
assert.True(t, repo.checkInUse)
})
t.Run("Request temporary cat file batch check", func(t *testing.T) {
wr, r, cancel, err := repo.CatFileBatchCheck(ctx)
require.NoError(t, err)
assert.NotEqual(t, repo.check.Writer, wr)
t.Run("Check temporary cat file batch check", func(t *testing.T) {
_, err = wr.Write([]byte("test" + "\n"))
require.NoError(t, err)
sha, typ, size, err := ReadBatchLine(r)
require.NoError(t, err)
assert.Equal(t, "tag", typ)
assert.EqualValues(t, []byte("3ad28a9149a2864384548f3d17ed7f38014c9e8a"), sha)
assert.EqualValues(t, 807, size)
})
cancel()
assert.True(t, repo.checkInUse)
})
t.Run("Check cached cat file batch check", func(t *testing.T) {
_, err = wr.Write([]byte("test" + "\n"))
require.NoError(t, err)
sha, typ, size, err := ReadBatchLine(r)
require.NoError(t, err)
assert.Equal(t, "tag", typ)
assert.EqualValues(t, []byte("3ad28a9149a2864384548f3d17ed7f38014c9e8a"), sha)
assert.EqualValues(t, 807, size)
})
t.Run("Cancel cached cat file batch check", func(t *testing.T) {
cancel1()
assert.False(t, repo.checkInUse)
assert.NotNil(t, repo.check)
})
t.Run("Request cached cat file batch check", func(t *testing.T) {
wr, _, _, err := repo.CatFileBatchCheck(ctx)
require.NoError(t, err)
assert.NotNil(t, repo.check)
assert.Equal(t, repo.check.Writer, wr)
assert.True(t, repo.checkInUse)
t.Run("Close git repo", func(t *testing.T) {
require.NoError(t, repo.Close())
assert.Nil(t, repo.check)
})
_, err = wr.Write([]byte("test" + "\n"))
require.Error(t, err)
})
}

View file

@ -169,9 +169,13 @@ func (repo *Repository) IsObjectExist(name string) bool {
return false return false
} }
wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx) wr, rd, cancel, err := repo.CatFileBatchCheck(repo.Ctx)
if err != nil {
log.Debug("Error writing to CatFileBatchCheck %v", err)
return false
}
defer cancel() defer cancel()
_, err := wr.Write([]byte(name + "\n")) _, err = wr.Write([]byte(name + "\n"))
if err != nil { if err != nil {
log.Debug("Error writing to CatFileBatchCheck %v", err) log.Debug("Error writing to CatFileBatchCheck %v", err)
return false return false
@ -186,9 +190,13 @@ func (repo *Repository) IsReferenceExist(name string) bool {
return false return false
} }
wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx) wr, rd, cancel, err := repo.CatFileBatchCheck(repo.Ctx)
if err != nil {
log.Debug("Error writing to CatFileBatchCheck %v", err)
return false
}
defer cancel() defer cancel()
_, err := wr.Write([]byte(name + "\n")) _, err = wr.Write([]byte(name + "\n"))
if err != nil { if err != nil {
log.Debug("Error writing to CatFileBatchCheck %v", err) log.Debug("Error writing to CatFileBatchCheck %v", err)
return false return false

View file

@ -536,9 +536,12 @@ func (repo *Repository) ResolveReference(name string) (string, error) {
// GetRefCommitID returns the last commit ID string of given reference (branch or tag). // GetRefCommitID returns the last commit ID string of given reference (branch or tag).
func (repo *Repository) GetRefCommitID(name string) (string, error) { func (repo *Repository) GetRefCommitID(name string) (string, error) {
wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx) wr, rd, cancel, err := repo.CatFileBatchCheck(repo.Ctx)
if err != nil {
return "", err
}
defer cancel() defer cancel()
_, err := wr.Write([]byte(name + "\n")) _, err = wr.Write([]byte(name + "\n"))
if err != nil { if err != nil {
return "", err return "", err
} }
@ -564,12 +567,19 @@ func (repo *Repository) RemoveReference(name string) error {
// IsCommitExist returns true if given commit exists in current repository. // IsCommitExist returns true if given commit exists in current repository.
func (repo *Repository) IsCommitExist(name string) bool { func (repo *Repository) IsCommitExist(name string) bool {
if err := ensureValidGitRepository(repo.Ctx, repo.Path); err != nil {
log.Error("IsCommitExist: %v", err)
return false
}
_, _, err := NewCommand(repo.Ctx, "cat-file", "-e").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path}) _, _, err := NewCommand(repo.Ctx, "cat-file", "-e").AddDynamicArguments(name).RunStdString(&RunOpts{Dir: repo.Path})
return err == nil return err == nil
} }
func (repo *Repository) getCommit(id ObjectID) (*Commit, error) { func (repo *Repository) getCommit(id ObjectID) (*Commit, error) {
wr, rd, cancel := repo.CatFileBatch(repo.Ctx) wr, rd, cancel, err := repo.CatFileBatch(repo.Ctx)
if err != nil {
return nil, err
}
defer cancel() defer cancel()
_, _ = wr.Write([]byte(id.String() + "\n")) _, _ = wr.Write([]byte(id.String() + "\n"))
@ -646,7 +656,10 @@ func (repo *Repository) ConvertToGitID(commitID string) (ObjectID, error) {
} }
} }
wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx) wr, rd, cancel, err := repo.CatFileBatchCheck(repo.Ctx)
if err != nil {
return nil, err
}
defer cancel() defer cancel()
_, err = wr.Write([]byte(commitID + "\n")) _, err = wr.Write([]byte(commitID + "\n"))
if err != nil { if err != nil {

View file

@ -60,7 +60,10 @@ func mergeLanguageStats(stats map[string]int64) map[string]int64 {
func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, error) { func (repo *Repository) GetLanguageStats(commitID string) (map[string]int64, error) {
// We will feed the commit IDs in order into cat-file --batch, followed by blobs as necessary. // 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 // so let's create a batch stdin and stdout
batchStdinWriter, batchReader, cancel := repo.CatFileBatch(repo.Ctx) batchStdinWriter, batchReader, cancel, err := repo.CatFileBatch(repo.Ctx)
if err != nil {
return nil, err
}
defer cancel() defer cancel()
writeID := func(id string) error { writeID := func(id string) error {

View file

@ -257,9 +257,12 @@ func (repo *Repository) GetTags(skip, limit int) (tags []string, err error) {
// GetTagType gets the type of the tag, either commit (simple) or tag (annotated) // GetTagType gets the type of the tag, either commit (simple) or tag (annotated)
func (repo *Repository) GetTagType(id ObjectID) (string, error) { func (repo *Repository) GetTagType(id ObjectID) (string, error) {
wr, rd, cancel := repo.CatFileBatchCheck(repo.Ctx) wr, rd, cancel, err := repo.CatFileBatchCheck(repo.Ctx)
if err != nil {
return "", err
}
defer cancel() defer cancel()
_, err := wr.Write([]byte(id.String() + "\n")) _, err = wr.Write([]byte(id.String() + "\n"))
if err != nil { if err != nil {
return "", err return "", err
} }
@ -315,7 +318,10 @@ func (repo *Repository) getTag(tagID ObjectID, name string) (*Tag, error) {
} }
// The tag is an annotated tag with a message. // The tag is an annotated tag with a message.
wr, rd, cancel := repo.CatFileBatch(repo.Ctx) wr, rd, cancel, err := repo.CatFileBatch(repo.Ctx)
if err != nil {
return nil, err
}
defer cancel() defer cancel()
if _, err := wr.Write([]byte(tagID.String() + "\n")); err != nil { if _, err := wr.Write([]byte(tagID.String() + "\n")); err != nil {

View file

@ -68,7 +68,10 @@ func (repo *Repository) CommitTree(author, committer *Signature, tree *Tree, opt
} }
func (repo *Repository) getTree(id ObjectID) (*Tree, error) { func (repo *Repository) getTree(id ObjectID) (*Tree, error) {
wr, rd, cancel := repo.CatFileBatch(repo.Ctx) wr, rd, cancel, err := repo.CatFileBatch(repo.Ctx)
if err != nil {
return nil, err
}
defer cancel() defer cancel()
_, _ = wr.Write([]byte(id.String() + "\n")) _, _ = wr.Write([]byte(id.String() + "\n"))

View file

@ -41,7 +41,10 @@ func (t *Tree) ListEntries() (Entries, error) {
} }
if t.repo != nil { if t.repo != nil {
wr, rd, cancel := t.repo.CatFileBatch(t.repo.Ctx) wr, rd, cancel, err := t.repo.CatFileBatch(t.repo.Ctx)
if err != nil {
return nil, err
}
defer cancel() defer cancel()
_, _ = wr.Write([]byte(t.ID.String() + "\n")) _, _ = wr.Write([]byte(t.ID.String() + "\n"))

View file

@ -47,9 +47,13 @@ func (te *TreeEntry) Size() int64 {
return te.size return te.size
} }
wr, rd, cancel := te.ptree.repo.CatFileBatchCheck(te.ptree.repo.Ctx) wr, rd, cancel, err := te.ptree.repo.CatFileBatchCheck(te.ptree.repo.Ctx)
if err != nil {
log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), te.ptree.repo.Path, err)
return 0
}
defer cancel() defer cancel()
_, err := wr.Write([]byte(te.ID.String() + "\n")) _, err = wr.Write([]byte(te.ID.String() + "\n"))
if err != nil { if err != nil {
log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), te.ptree.repo.Path, err) log.Debug("error whilst reading size for %s in %s. Error: %v", te.ID.String(), te.ptree.repo.Path, err)
return 0 return 0

View file

@ -16,10 +16,10 @@ import (
"code.gitea.io/gitea/modules/analyze" "code.gitea.io/gitea/modules/analyze"
"code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/indexer/code/internal" "code.gitea.io/gitea/modules/indexer/code/internal"
indexer_internal "code.gitea.io/gitea/modules/indexer/internal" indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
inner_bleve "code.gitea.io/gitea/modules/indexer/internal/bleve" inner_bleve "code.gitea.io/gitea/modules/indexer/internal/bleve"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/typesniffer" "code.gitea.io/gitea/modules/typesniffer"
@ -193,21 +193,23 @@ func (b *Indexer) addDelete(filename string, repo *repo_model.Repository, batch
func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error { func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error {
batch := inner_bleve.NewFlushingBatch(b.inner.Indexer, maxBatchSize) batch := inner_bleve.NewFlushingBatch(b.inner.Indexer, maxBatchSize)
if len(changes.Updates) > 0 { if len(changes.Updates) > 0 {
// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first! r, err := gitrepo.OpenRepository(ctx, repo)
if err := git.EnsureValidGitRepository(ctx, repo.RepoPath()); err != nil { if err != nil {
log.Error("Unable to open git repo: %s for %-v: %v", repo.RepoPath(), repo, err)
return err return err
} }
defer r.Close()
batchWriter, batchReader, cancel := git.CatFileBatch(ctx, repo.RepoPath()) gitBatch, err := r.NewBatch(ctx)
defer cancel() if err != nil {
return err
}
defer gitBatch.Close()
for _, update := range changes.Updates { for _, update := range changes.Updates {
if err := b.addUpdate(ctx, batchWriter, batchReader, sha, update, repo, batch); err != nil { if err := b.addUpdate(ctx, gitBatch.Writer, gitBatch.Reader, sha, update, repo, batch); err != nil {
return err return err
} }
} }
cancel() gitBatch.Close()
} }
for _, filename := range changes.RemovedFilenames { for _, filename := range changes.RemovedFilenames {
if err := b.addDelete(filename, repo, batch); err != nil { if err := b.addDelete(filename, repo, batch); err != nil {

View file

@ -15,11 +15,11 @@ import (
"code.gitea.io/gitea/modules/analyze" "code.gitea.io/gitea/modules/analyze"
"code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/indexer/code/internal" "code.gitea.io/gitea/modules/indexer/code/internal"
indexer_internal "code.gitea.io/gitea/modules/indexer/internal" indexer_internal "code.gitea.io/gitea/modules/indexer/internal"
inner_elasticsearch "code.gitea.io/gitea/modules/indexer/internal/elasticsearch" inner_elasticsearch "code.gitea.io/gitea/modules/indexer/internal/elasticsearch"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/typesniffer" "code.gitea.io/gitea/modules/typesniffer"
@ -154,17 +154,19 @@ func (b *Indexer) addDelete(filename string, repo *repo_model.Repository) elasti
func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error { func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha string, changes *internal.RepoChanges) error {
reqs := make([]elastic.BulkableRequest, 0) reqs := make([]elastic.BulkableRequest, 0)
if len(changes.Updates) > 0 { if len(changes.Updates) > 0 {
// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first! r, err := gitrepo.OpenRepository(ctx, repo)
if err := git.EnsureValidGitRepository(ctx, repo.RepoPath()); err != nil { if err != nil {
log.Error("Unable to open git repo: %s for %-v: %v", repo.RepoPath(), repo, err)
return err return err
} }
defer r.Close()
batchWriter, batchReader, cancel := git.CatFileBatch(ctx, repo.RepoPath()) batch, err := r.NewBatch(ctx)
defer cancel() if err != nil {
return err
}
defer batch.Close()
for _, update := range changes.Updates { for _, update := range changes.Updates {
updateReqs, err := b.addUpdate(ctx, batchWriter, batchReader, sha, update, repo) updateReqs, err := b.addUpdate(ctx, batch.Writer, batch.Reader, sha, update, repo)
if err != nil { if err != nil {
return err return err
} }
@ -172,7 +174,7 @@ func (b *Indexer) Index(ctx context.Context, repo *repo_model.Repository, sha st
reqs = append(reqs, updateReqs...) reqs = append(reqs, updateReqs...)
} }
} }
cancel() batch.Close()
} }
for _, filename := range changes.RemovedFilenames { for _, filename := range changes.RemovedFilenames {

View file

@ -2540,6 +2540,101 @@ func (mr *MockUniversalClientMockRecorder) HExists(arg0, arg1, arg2 any) *gomock
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HExists", reflect.TypeOf((*MockUniversalClient)(nil).HExists), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HExists", reflect.TypeOf((*MockUniversalClient)(nil).HExists), arg0, arg1, arg2)
} }
// HExpire mocks base method.
func (m *MockUniversalClient) HExpire(arg0 context.Context, arg1 string, arg2 time.Duration, arg3 ...string) *redis.IntSliceCmd {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1, arg2}
for _, a := range arg3 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "HExpire", varargs...)
ret0, _ := ret[0].(*redis.IntSliceCmd)
return ret0
}
// HExpire indicates an expected call of HExpire.
func (mr *MockUniversalClientMockRecorder) HExpire(arg0, arg1, arg2 any, arg3 ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1, arg2}, arg3...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HExpire", reflect.TypeOf((*MockUniversalClient)(nil).HExpire), varargs...)
}
// HExpireAt mocks base method.
func (m *MockUniversalClient) HExpireAt(arg0 context.Context, arg1 string, arg2 time.Time, arg3 ...string) *redis.IntSliceCmd {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1, arg2}
for _, a := range arg3 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "HExpireAt", varargs...)
ret0, _ := ret[0].(*redis.IntSliceCmd)
return ret0
}
// HExpireAt indicates an expected call of HExpireAt.
func (mr *MockUniversalClientMockRecorder) HExpireAt(arg0, arg1, arg2 any, arg3 ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1, arg2}, arg3...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HExpireAt", reflect.TypeOf((*MockUniversalClient)(nil).HExpireAt), varargs...)
}
// HExpireAtWithArgs mocks base method.
func (m *MockUniversalClient) HExpireAtWithArgs(arg0 context.Context, arg1 string, arg2 time.Time, arg3 redis.HExpireArgs, arg4 ...string) *redis.IntSliceCmd {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1, arg2, arg3}
for _, a := range arg4 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "HExpireAtWithArgs", varargs...)
ret0, _ := ret[0].(*redis.IntSliceCmd)
return ret0
}
// HExpireAtWithArgs indicates an expected call of HExpireAtWithArgs.
func (mr *MockUniversalClientMockRecorder) HExpireAtWithArgs(arg0, arg1, arg2, arg3 any, arg4 ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1, arg2, arg3}, arg4...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HExpireAtWithArgs", reflect.TypeOf((*MockUniversalClient)(nil).HExpireAtWithArgs), varargs...)
}
// HExpireTime mocks base method.
func (m *MockUniversalClient) HExpireTime(arg0 context.Context, arg1 string, arg2 ...string) *redis.IntSliceCmd {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "HExpireTime", varargs...)
ret0, _ := ret[0].(*redis.IntSliceCmd)
return ret0
}
// HExpireTime indicates an expected call of HExpireTime.
func (mr *MockUniversalClientMockRecorder) HExpireTime(arg0, arg1 any, arg2 ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HExpireTime", reflect.TypeOf((*MockUniversalClient)(nil).HExpireTime), varargs...)
}
// HExpireWithArgs mocks base method.
func (m *MockUniversalClient) HExpireWithArgs(arg0 context.Context, arg1 string, arg2 time.Duration, arg3 redis.HExpireArgs, arg4 ...string) *redis.IntSliceCmd {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1, arg2, arg3}
for _, a := range arg4 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "HExpireWithArgs", varargs...)
ret0, _ := ret[0].(*redis.IntSliceCmd)
return ret0
}
// HExpireWithArgs indicates an expected call of HExpireWithArgs.
func (mr *MockUniversalClientMockRecorder) HExpireWithArgs(arg0, arg1, arg2, arg3 any, arg4 ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1, arg2, arg3}, arg4...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HExpireWithArgs", reflect.TypeOf((*MockUniversalClient)(nil).HExpireWithArgs), varargs...)
}
// HGet mocks base method. // HGet mocks base method.
func (m *MockUniversalClient) HGet(arg0 context.Context, arg1, arg2 string) *redis.StringCmd { func (m *MockUniversalClient) HGet(arg0 context.Context, arg1, arg2 string) *redis.StringCmd {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -2662,6 +2757,139 @@ func (mr *MockUniversalClientMockRecorder) HMSet(arg0, arg1 any, arg2 ...any) *g
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HMSet", reflect.TypeOf((*MockUniversalClient)(nil).HMSet), varargs...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HMSet", reflect.TypeOf((*MockUniversalClient)(nil).HMSet), varargs...)
} }
// HPExpire mocks base method.
func (m *MockUniversalClient) HPExpire(arg0 context.Context, arg1 string, arg2 time.Duration, arg3 ...string) *redis.IntSliceCmd {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1, arg2}
for _, a := range arg3 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "HPExpire", varargs...)
ret0, _ := ret[0].(*redis.IntSliceCmd)
return ret0
}
// HPExpire indicates an expected call of HPExpire.
func (mr *MockUniversalClientMockRecorder) HPExpire(arg0, arg1, arg2 any, arg3 ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1, arg2}, arg3...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HPExpire", reflect.TypeOf((*MockUniversalClient)(nil).HPExpire), varargs...)
}
// HPExpireAt mocks base method.
func (m *MockUniversalClient) HPExpireAt(arg0 context.Context, arg1 string, arg2 time.Time, arg3 ...string) *redis.IntSliceCmd {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1, arg2}
for _, a := range arg3 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "HPExpireAt", varargs...)
ret0, _ := ret[0].(*redis.IntSliceCmd)
return ret0
}
// HPExpireAt indicates an expected call of HPExpireAt.
func (mr *MockUniversalClientMockRecorder) HPExpireAt(arg0, arg1, arg2 any, arg3 ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1, arg2}, arg3...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HPExpireAt", reflect.TypeOf((*MockUniversalClient)(nil).HPExpireAt), varargs...)
}
// HPExpireAtWithArgs mocks base method.
func (m *MockUniversalClient) HPExpireAtWithArgs(arg0 context.Context, arg1 string, arg2 time.Time, arg3 redis.HExpireArgs, arg4 ...string) *redis.IntSliceCmd {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1, arg2, arg3}
for _, a := range arg4 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "HPExpireAtWithArgs", varargs...)
ret0, _ := ret[0].(*redis.IntSliceCmd)
return ret0
}
// HPExpireAtWithArgs indicates an expected call of HPExpireAtWithArgs.
func (mr *MockUniversalClientMockRecorder) HPExpireAtWithArgs(arg0, arg1, arg2, arg3 any, arg4 ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1, arg2, arg3}, arg4...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HPExpireAtWithArgs", reflect.TypeOf((*MockUniversalClient)(nil).HPExpireAtWithArgs), varargs...)
}
// HPExpireTime mocks base method.
func (m *MockUniversalClient) HPExpireTime(arg0 context.Context, arg1 string, arg2 ...string) *redis.IntSliceCmd {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "HPExpireTime", varargs...)
ret0, _ := ret[0].(*redis.IntSliceCmd)
return ret0
}
// HPExpireTime indicates an expected call of HPExpireTime.
func (mr *MockUniversalClientMockRecorder) HPExpireTime(arg0, arg1 any, arg2 ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HPExpireTime", reflect.TypeOf((*MockUniversalClient)(nil).HPExpireTime), varargs...)
}
// HPExpireWithArgs mocks base method.
func (m *MockUniversalClient) HPExpireWithArgs(arg0 context.Context, arg1 string, arg2 time.Duration, arg3 redis.HExpireArgs, arg4 ...string) *redis.IntSliceCmd {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1, arg2, arg3}
for _, a := range arg4 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "HPExpireWithArgs", varargs...)
ret0, _ := ret[0].(*redis.IntSliceCmd)
return ret0
}
// HPExpireWithArgs indicates an expected call of HPExpireWithArgs.
func (mr *MockUniversalClientMockRecorder) HPExpireWithArgs(arg0, arg1, arg2, arg3 any, arg4 ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1, arg2, arg3}, arg4...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HPExpireWithArgs", reflect.TypeOf((*MockUniversalClient)(nil).HPExpireWithArgs), varargs...)
}
// HPTTL mocks base method.
func (m *MockUniversalClient) HPTTL(arg0 context.Context, arg1 string, arg2 ...string) *redis.IntSliceCmd {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "HPTTL", varargs...)
ret0, _ := ret[0].(*redis.IntSliceCmd)
return ret0
}
// HPTTL indicates an expected call of HPTTL.
func (mr *MockUniversalClientMockRecorder) HPTTL(arg0, arg1 any, arg2 ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HPTTL", reflect.TypeOf((*MockUniversalClient)(nil).HPTTL), varargs...)
}
// HPersist mocks base method.
func (m *MockUniversalClient) HPersist(arg0 context.Context, arg1 string, arg2 ...string) *redis.IntSliceCmd {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "HPersist", varargs...)
ret0, _ := ret[0].(*redis.IntSliceCmd)
return ret0
}
// HPersist indicates an expected call of HPersist.
func (mr *MockUniversalClientMockRecorder) HPersist(arg0, arg1 any, arg2 ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HPersist", reflect.TypeOf((*MockUniversalClient)(nil).HPersist), varargs...)
}
// HRandField mocks base method. // HRandField mocks base method.
func (m *MockUniversalClient) HRandField(arg0 context.Context, arg1 string, arg2 int) *redis.StringSliceCmd { func (m *MockUniversalClient) HRandField(arg0 context.Context, arg1 string, arg2 int) *redis.StringSliceCmd {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -2704,6 +2932,20 @@ func (mr *MockUniversalClientMockRecorder) HScan(arg0, arg1, arg2, arg3, arg4 an
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HScan", reflect.TypeOf((*MockUniversalClient)(nil).HScan), arg0, arg1, arg2, arg3, arg4) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HScan", reflect.TypeOf((*MockUniversalClient)(nil).HScan), arg0, arg1, arg2, arg3, arg4)
} }
// HScanNoValues mocks base method.
func (m *MockUniversalClient) HScanNoValues(arg0 context.Context, arg1 string, arg2 uint64, arg3 string, arg4 int64) *redis.ScanCmd {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "HScanNoValues", arg0, arg1, arg2, arg3, arg4)
ret0, _ := ret[0].(*redis.ScanCmd)
return ret0
}
// HScanNoValues indicates an expected call of HScanNoValues.
func (mr *MockUniversalClientMockRecorder) HScanNoValues(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HScanNoValues", reflect.TypeOf((*MockUniversalClient)(nil).HScanNoValues), arg0, arg1, arg2, arg3, arg4)
}
// HSet mocks base method. // HSet mocks base method.
func (m *MockUniversalClient) HSet(arg0 context.Context, arg1 string, arg2 ...any) *redis.IntCmd { func (m *MockUniversalClient) HSet(arg0 context.Context, arg1 string, arg2 ...any) *redis.IntCmd {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -2737,6 +2979,25 @@ func (mr *MockUniversalClientMockRecorder) HSetNX(arg0, arg1, arg2, arg3 any) *g
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HSetNX", reflect.TypeOf((*MockUniversalClient)(nil).HSetNX), arg0, arg1, arg2, arg3) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HSetNX", reflect.TypeOf((*MockUniversalClient)(nil).HSetNX), arg0, arg1, arg2, arg3)
} }
// HTTL mocks base method.
func (m *MockUniversalClient) HTTL(arg0 context.Context, arg1 string, arg2 ...string) *redis.IntSliceCmd {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "HTTL", varargs...)
ret0, _ := ret[0].(*redis.IntSliceCmd)
return ret0
}
// HTTL indicates an expected call of HTTL.
func (mr *MockUniversalClientMockRecorder) HTTL(arg0, arg1 any, arg2 ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HTTL", reflect.TypeOf((*MockUniversalClient)(nil).HTTL), varargs...)
}
// HVals mocks base method. // HVals mocks base method.
func (m *MockUniversalClient) HVals(arg0 context.Context, arg1 string) *redis.StringSliceCmd { func (m *MockUniversalClient) HVals(arg0 context.Context, arg1 string) *redis.StringSliceCmd {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View file

@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"gitea.com/go-chi/session" "code.forgejo.org/go-chi/session"
) )
// DBStore represents a session store implementation based on the DB. // DBStore represents a session store implementation based on the DB.

View file

@ -25,7 +25,7 @@ import (
"code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/nosql" "code.gitea.io/gitea/modules/nosql"
"gitea.com/go-chi/session" "code.forgejo.org/go-chi/session"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
) )

View file

@ -6,7 +6,7 @@ package session
import ( import (
"net/http" "net/http"
"gitea.com/go-chi/session" "code.forgejo.org/go-chi/session"
) )
// Store represents a session store // Store represents a session store

View file

@ -8,12 +8,12 @@ import (
"sync" "sync"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"gitea.com/go-chi/session" "code.forgejo.org/go-chi/session"
couchbase "gitea.com/go-chi/session/couchbase" memcache "code.forgejo.org/go-chi/session/memcache"
memcache "gitea.com/go-chi/session/memcache" mysql "code.forgejo.org/go-chi/session/mysql"
mysql "gitea.com/go-chi/session/mysql" postgres "code.forgejo.org/go-chi/session/postgres"
postgres "gitea.com/go-chi/session/postgres"
) )
// VirtualSessionProvider represents a shadowed session provider implementation. // VirtualSessionProvider represents a shadowed session provider implementation.
@ -35,6 +35,9 @@ func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error {
switch opts.Provider { switch opts.Provider {
case "memory": case "memory":
o.provider = &session.MemProvider{} o.provider = &session.MemProvider{}
case "couchbase":
log.Warn("Couchbase as session provider is no longer supported, falling back to file as session provider")
fallthrough
case "file": case "file":
o.provider = &session.FileProvider{} o.provider = &session.FileProvider{}
case "redis": case "redis":
@ -45,8 +48,6 @@ func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error {
o.provider = &mysql.MysqlProvider{} o.provider = &mysql.MysqlProvider{}
case "postgres": case "postgres":
o.provider = &postgres.PostgresProvider{} o.provider = &postgres.PostgresProvider{}
case "couchbase":
o.provider = &couchbase.CouchbaseProvider{}
case "memcache": case "memcache":
o.provider = &memcache.MemcacheProvider{} o.provider = &memcache.MemcacheProvider{}
default: default:

View file

@ -7,9 +7,18 @@ package setting
var Quota = struct { var Quota = struct {
Enabled bool `ini:"ENABLED"` Enabled bool `ini:"ENABLED"`
DefaultGroups []string `ini:"DEFAULT_GROUPS"` DefaultGroups []string `ini:"DEFAULT_GROUPS"`
Default struct {
Total int64
} `ini:"quota.default"`
}{ }{
Enabled: false, Enabled: false,
DefaultGroups: []string{}, DefaultGroups: []string{},
Default: struct {
Total int64
}{
Total: -1,
},
} }
func loadQuotaFrom(rootCfg ConfigProvider) { func loadQuotaFrom(rootCfg ConfigProvider) {

View file

@ -1105,6 +1105,7 @@ mirror_interval_invalid = The mirror interval is not valid.
mirror_public_key = Public SSH key mirror_public_key = Public SSH key
mirror_use_ssh.text = Use SSH authentication mirror_use_ssh.text = Use SSH authentication
mirror_use_ssh.helper = Forgejo will mirror the repository via Git over SSH and create a keypair for you when you select this option. You must ensure that the generated public key is authorized to push to the destination repository. You cannot use password-based authorization when selecting this. mirror_use_ssh.helper = Forgejo will mirror the repository via Git over SSH and create a keypair for you when you select this option. You must ensure that the generated public key is authorized to push to the destination repository. You cannot use password-based authorization when selecting this.
mirror_use_ssh.not_available = SSH authentication isn't available.
mirror_denied_combination = Cannot use public key and password based authentication in combination. mirror_denied_combination = Cannot use public key and password based authentication in combination.
mirror_sync = synced mirror_sync = synced
mirror_sync_on_commit = Sync when commits are pushed mirror_sync_on_commit = Sync when commits are pushed

1
release-notes/5090.md Normal file
View file

@ -0,0 +1 @@
Remove support for Couchbase as a session provider; it instead will now fallback to the file provider. The rationale for removing Couchbase support is that it's not free software, https://www.couchbase.com/blog/couchbase-adopts-bsl-license/, and therefore cannot be tested in Forgejo and neither should be supported.

2
release-notes/5120.md Normal file
View file

@ -0,0 +1,2 @@
feat: Language detection in the repository learned about the following languages: [Luau](https://github.com/github-linguist/linguist/pull/6612), [BQN](https://github.com/github-linguist/linguist/pull/6623), [Cron table](https://github.com/github-linguist/linguist/pull/6759), [NMODL](https://github.com/github-linguist/linguist/pull/6776), [Pkl](https://github.com/github-linguist/linguist/pull/6730), [templ](https://github.com/github-linguist/linguist/pull/6798), [FIRRTL](https://github.com/github-linguist/linguist/pull/6848), [Julia REPL](https://github.com/github-linguist/linguist/pull/6859), [Caddyfile](https://github.com/github-linguist/linguist/pull/6862).
feat: The following extensions or filenames in a repository are associated with the matching language: [.sublime-color-scheme](https://github.com/github-linguist/linguist/pull/6758), [MODULE.bazel.lock](https://github.com/github-linguist/linguist/pull/6783), [Cargo.toml.orig](https://github.com/github-linguist/linguist/pull/6787), [tsx](https://github.com/github-linguist/linguist/pull/6788), [justfile](https://github.com/github-linguist/linguist/pull/6795), [.zig.zon](https://github.com/github-linguist/linguist/pull/6820), [.envrc](https://github.com/github-linguist/linguist/pull/6865).

View file

@ -19,9 +19,11 @@ func TestTestHook(t *testing.T) {
ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/wiki/_pages") ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/wiki/_pages")
ctx.SetParams(":id", "1") ctx.SetParams(":id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2) contexttest.LoadUser(t, ctx, 2)
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
contexttest.LoadRepoCommit(t, ctx)
TestHook(ctx) TestHook(ctx)
assert.EqualValues(t, http.StatusNoContent, ctx.Resp.Status()) assert.EqualValues(t, http.StatusNoContent, ctx.Resp.Status())

View file

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -350,6 +351,11 @@ func CreatePushMirror(ctx *context.APIContext, mirrorOption *api.CreatePushMirro
return return
} }
if mirrorOption.UseSSH && !git.HasSSHExecutable {
ctx.Error(http.StatusBadRequest, "CreatePushMirror", "SSH authentication not available.")
return
}
if mirrorOption.UseSSH && (mirrorOption.RemoteUsername != "" || mirrorOption.RemotePassword != "") { if mirrorOption.UseSSH && (mirrorOption.RemoteUsername != "" || mirrorOption.RemotePassword != "") {
ctx.Error(http.StatusBadRequest, "CreatePushMirror", "'use_ssh' is mutually exclusive with 'remote_username' and 'remote_passoword'") ctx.Error(http.StatusBadRequest, "CreatePushMirror", "'use_ssh' is mutually exclusive with 'remote_username' and 'remote_passoword'")
return return

View file

@ -15,7 +15,7 @@ import (
"code.gitea.io/gitea/modules/web/routing" "code.gitea.io/gitea/modules/web/routing"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
"gitea.com/go-chi/session" "code.forgejo.org/go-chi/session"
"github.com/chi-middleware/proxy" "github.com/chi-middleware/proxy"
chi "github.com/go-chi/chi/v5" chi "github.com/go-chi/chi/v5"
) )

View file

@ -36,7 +36,7 @@ import (
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
"gitea.com/go-chi/session" "code.forgejo.org/go-chi/session"
) )
const ( const (

View file

@ -22,7 +22,7 @@ import (
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/mailer" "code.gitea.io/gitea/services/mailer"
"gitea.com/go-chi/session" "code.forgejo.org/go-chi/session"
) )
const ( const (

View file

@ -6,6 +6,7 @@ package repo
import ( import (
"testing" "testing"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
@ -45,7 +46,6 @@ func TestGetUniquePatchBranchName(t *testing.T) {
ctx, _ := contexttest.MockContext(t, "user2/repo1") ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetParams(":id", "1") ctx.SetParams(":id", "1")
contexttest.LoadRepo(t, ctx, 1) contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2) contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx) contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close() defer ctx.Repo.GitRepo.Close()
@ -57,15 +57,7 @@ func TestGetUniquePatchBranchName(t *testing.T) {
func TestGetClosestParentWithFiles(t *testing.T) { func TestGetClosestParentWithFiles(t *testing.T) {
unittest.PrepareTestEnv(t) unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1") repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
ctx.SetParams(":id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
branch := repo.DefaultBranch branch := repo.DefaultBranch
gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo) gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo)
defer gitRepo.Close() defer gitRepo.Close()

View file

@ -154,6 +154,7 @@ func GetContentHistoryDetail(ctx *context.Context) {
dmp := diffmatchpatch.New() dmp := diffmatchpatch.New()
// `checklines=false` makes better diff result // `checklines=false` makes better diff result
diff := dmp.DiffMain(prevHistoryContentText, history.ContentText, false) diff := dmp.DiffMain(prevHistoryContentText, history.ContentText, false)
diff = dmp.DiffCleanupSemantic(diff)
diff = dmp.DiffCleanupEfficiency(diff) diff = dmp.DiffCleanupEfficiency(diff)
// use chroma to render the diff html // use chroma to render the diff html

View file

@ -92,6 +92,7 @@ func SettingsCtxData(ctx *context.Context) {
return return
} }
ctx.Data["PushMirrors"] = pushMirrors ctx.Data["PushMirrors"] = pushMirrors
ctx.Data["CanUseSSHMirroring"] = git.HasSSHExecutable
} }
// Units show a repositorys unit settings page // Units show a repositorys unit settings page
@ -643,6 +644,11 @@ func SettingsPost(ctx *context.Context) {
return return
} }
if form.PushMirrorUseSSH && !git.HasSSHExecutable {
ctx.RenderWithErr(ctx.Tr("repo.mirror_use_ssh.not_available"), tplSettingsOptions, &form)
return
}
address, err := forms.ParseRemoteAddr(form.PushMirrorAddress, form.PushMirrorUsername, form.PushMirrorPassword) address, err := forms.ParseRemoteAddr(form.PushMirrorAddress, form.PushMirrorUsername, form.PushMirrorPassword)
if err == nil { if err == nil {
err = migrations.IsMigrateURLAllowed(address, ctx.Doer) err = migrations.IsMigrateURLAllowed(address, ctx.Doer)

View file

@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
session_module "code.gitea.io/gitea/modules/session" session_module "code.gitea.io/gitea/modules/session"
chiSession "gitea.com/go-chi/session" chiSession "code.forgejo.org/go-chi/session"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
) )

View file

@ -27,8 +27,8 @@ import (
"code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/modules/web/middleware"
web_types "code.gitea.io/gitea/modules/web/types" web_types "code.gitea.io/gitea/modules/web/types"
"code.forgejo.org/go-chi/session"
"gitea.com/go-chi/cache" "gitea.com/go-chi/cache"
"gitea.com/go-chi/session"
) )
// Render represents a template render // Render represents a template render

View file

@ -136,14 +136,15 @@ func LoadRepoCommit(t *testing.T, ctx gocontext.Context) {
assert.FailNow(t, "context is not *context.Context or *context.APIContext") assert.FailNow(t, "context is not *context.Context or *context.APIContext")
} }
gitRepo, err := gitrepo.OpenRepository(ctx, repo.Repository) if repo.GitRepo == nil {
require.NoError(t, err) assert.FailNow(t, "must call LoadGitRepo")
defer gitRepo.Close() }
branch, err := gitRepo.GetHEADBranch()
branch, err := repo.GitRepo.GetHEADBranch()
require.NoError(t, err) require.NoError(t, err)
assert.NotNil(t, branch) assert.NotNil(t, branch)
if branch != nil { if branch != nil {
repo.Commit, err = gitRepo.GetBranchCommit(branch.Name) repo.Commit, err = repo.GitRepo.GetBranchCommit(branch.Name)
require.NoError(t, err) require.NoError(t, err)
} }
} }
@ -176,10 +177,20 @@ func LoadOrganization(t *testing.T, ctx gocontext.Context, orgID int64) {
// LoadGitRepo load a git repo into a test context. Requires that ctx.Repo has // LoadGitRepo load a git repo into a test context. Requires that ctx.Repo has
// already been populated. // already been populated.
func LoadGitRepo(t *testing.T, ctx *context.Context) { func LoadGitRepo(t *testing.T, ctx gocontext.Context) {
require.NoError(t, ctx.Repo.Repository.LoadOwner(ctx)) var repo *context.Repository
switch ctx := ctx.(type) {
case *context.Context:
repo = ctx.Repo
case *context.APIContext:
repo = ctx.Repo
default:
assert.FailNow(t, "context is not *context.Context or *context.APIContext")
}
require.NoError(t, repo.Repository.LoadOwner(ctx))
var err error var err error
ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository) repo.GitRepo, err = gitrepo.OpenRepository(ctx, repo.Repository)
require.NoError(t, err) require.NoError(t, err)
} }

View file

@ -75,9 +75,9 @@ func checkAuthorizedKeys(ctx context.Context, logger log.Logger, autofix bool) e
logger.Critical( logger.Critical(
"authorized_keys file %q is out of date.\nRegenerate it with:\n\t\"%s\"\nor\n\t\"%s\"", "authorized_keys file %q is out of date.\nRegenerate it with:\n\t\"%s\"\nor\n\t\"%s\"",
fPath, fPath,
"gitea admin regenerate keys", "forgejo admin regenerate keys",
"gitea doctor --run authorized-keys --fix") "forgejo doctor check --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 fmt.Errorf(`authorized_keys is out of date and should be regenerated with "forgejo admin regenerate keys" or "forgejo doctor check --run authorized-keys --fix"`)
} }
logger.Warn("authorized_keys is out of date. Attempting rewrite...") logger.Warn("authorized_keys is out of date. Attempting rewrite...")
err = asymkey_model.RewriteAllPublicKeys(ctx) err = asymkey_model.RewriteAllPublicKeys(ctx)

View file

@ -97,6 +97,7 @@ func (hcd *HighlightCodeDiff) diffWithHighlight(filename, language, codeA, codeB
convertedCodeB := hcd.ConvertToPlaceholders(string(highlightCodeB)) convertedCodeB := hcd.ConvertToPlaceholders(string(highlightCodeB))
diffs := diffMatchPatch.DiffMain(convertedCodeA, convertedCodeB, true) diffs := diffMatchPatch.DiffMain(convertedCodeA, convertedCodeB, true)
diffs = diffMatchPatch.DiffCleanupSemantic(diffs)
diffs = diffMatchPatch.DiffCleanupEfficiency(diffs) diffs = diffMatchPatch.DiffCleanupEfficiency(diffs)
for i := range diffs { for i := range diffs {

View file

@ -6,10 +6,11 @@ package files
import ( import (
"testing" "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/models/unittest"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/contexttest"
_ "code.gitea.io/gitea/models/actions" _ "code.gitea.io/gitea/models/actions"
@ -53,27 +54,21 @@ func getExpectedReadmeContentsResponse() *api.ContentsResponse {
func TestGetContents(t *testing.T) { func TestGetContents(t *testing.T) {
unittest.PrepareTestEnv(t) unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1") repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
ctx.SetParams(":id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
treePath := "README.md" treePath := "README.md"
ref := ctx.Repo.Repository.DefaultBranch ref := repo.DefaultBranch
expectedContentsResponse := getExpectedReadmeContentsResponse() expectedContentsResponse := getExpectedReadmeContentsResponse()
t.Run("Get README.md contents with GetContents(ctx, )", func(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) fileContentResponse, err := GetContents(db.DefaultContext, repo, treePath, ref, false)
assert.EqualValues(t, expectedContentsResponse, fileContentResponse) assert.EqualValues(t, expectedContentsResponse, fileContentResponse)
require.NoError(t, err) require.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) { 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) fileContentResponse, err := GetContents(db.DefaultContext, repo, treePath, "", false)
assert.EqualValues(t, expectedContentsResponse, fileContentResponse) assert.EqualValues(t, expectedContentsResponse, fileContentResponse)
require.NoError(t, err) require.NoError(t, err)
}) })
@ -81,16 +76,10 @@ func TestGetContents(t *testing.T) {
func TestGetContentsOrListForDir(t *testing.T) { func TestGetContentsOrListForDir(t *testing.T) {
unittest.PrepareTestEnv(t) unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1") repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
ctx.SetParams(":id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
treePath := "" // root dir treePath := "" // root dir
ref := ctx.Repo.Repository.DefaultBranch ref := repo.DefaultBranch
readmeContentsResponse := getExpectedReadmeContentsResponse() readmeContentsResponse := getExpectedReadmeContentsResponse()
// because will be in a list, doesn't have encoding and content // because will be in a list, doesn't have encoding and content
@ -102,13 +91,13 @@ func TestGetContentsOrListForDir(t *testing.T) {
} }
t.Run("Get root dir contents with GetContentsOrList(ctx, )", func(t *testing.T) { t.Run("Get root dir contents with GetContentsOrList(ctx, )", func(t *testing.T) {
fileContentResponse, err := GetContentsOrList(ctx, ctx.Repo.Repository, treePath, ref) fileContentResponse, err := GetContentsOrList(db.DefaultContext, repo, treePath, ref)
assert.EqualValues(t, expectedContentsListResponse, fileContentResponse) assert.EqualValues(t, expectedContentsListResponse, fileContentResponse)
require.NoError(t, err) require.NoError(t, err)
}) })
t.Run("Get root dir contents with ref as empty string (should then use the repo's default branch) with GetContentsOrList(ctx, )", func(t *testing.T) { t.Run("Get root dir contents with ref as empty string (should then use the repo's default branch) with GetContentsOrList(ctx, )", func(t *testing.T) {
fileContentResponse, err := GetContentsOrList(ctx, ctx.Repo.Repository, treePath, "") fileContentResponse, err := GetContentsOrList(db.DefaultContext, repo, treePath, "")
assert.EqualValues(t, expectedContentsListResponse, fileContentResponse) assert.EqualValues(t, expectedContentsListResponse, fileContentResponse)
require.NoError(t, err) require.NoError(t, err)
}) })
@ -116,27 +105,21 @@ func TestGetContentsOrListForDir(t *testing.T) {
func TestGetContentsOrListForFile(t *testing.T) { func TestGetContentsOrListForFile(t *testing.T) {
unittest.PrepareTestEnv(t) unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1") repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
ctx.SetParams(":id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
treePath := "README.md" treePath := "README.md"
ref := ctx.Repo.Repository.DefaultBranch ref := repo.DefaultBranch
expectedContentsResponse := getExpectedReadmeContentsResponse() expectedContentsResponse := getExpectedReadmeContentsResponse()
t.Run("Get README.md contents with GetContentsOrList(ctx, )", func(t *testing.T) { t.Run("Get README.md contents with GetContentsOrList(ctx, )", func(t *testing.T) {
fileContentResponse, err := GetContentsOrList(ctx, ctx.Repo.Repository, treePath, ref) fileContentResponse, err := GetContentsOrList(db.DefaultContext, repo, treePath, ref)
assert.EqualValues(t, expectedContentsResponse, fileContentResponse) assert.EqualValues(t, expectedContentsResponse, fileContentResponse)
require.NoError(t, err) require.NoError(t, err)
}) })
t.Run("Get README.md contents with ref as empty string (should then use the repo's default branch) with GetContentsOrList(ctx, )", func(t *testing.T) { t.Run("Get README.md contents with ref as empty string (should then use the repo's default branch) with GetContentsOrList(ctx, )", func(t *testing.T) {
fileContentResponse, err := GetContentsOrList(ctx, ctx.Repo.Repository, treePath, "") fileContentResponse, err := GetContentsOrList(db.DefaultContext, repo, treePath, "")
assert.EqualValues(t, expectedContentsResponse, fileContentResponse) assert.EqualValues(t, expectedContentsResponse, fileContentResponse)
require.NoError(t, err) require.NoError(t, err)
}) })
@ -144,28 +127,21 @@ func TestGetContentsOrListForFile(t *testing.T) {
func TestGetContentsErrors(t *testing.T) { func TestGetContentsErrors(t *testing.T) {
unittest.PrepareTestEnv(t) unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1") repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
ctx.SetParams(":id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
treePath := "README.md" treePath := "README.md"
ref := repo.DefaultBranch ref := repo.DefaultBranch
t.Run("bad treePath", func(t *testing.T) { t.Run("bad treePath", func(t *testing.T) {
badTreePath := "bad/tree.md" badTreePath := "bad/tree.md"
fileContentResponse, err := GetContents(ctx, repo, badTreePath, ref, false) fileContentResponse, err := GetContents(db.DefaultContext, repo, badTreePath, ref, false)
require.EqualError(t, err, "object does not exist [id: , rel_path: bad]") require.EqualError(t, err, "object does not exist [id: , rel_path: bad]")
assert.Nil(t, fileContentResponse) assert.Nil(t, fileContentResponse)
}) })
t.Run("bad ref", func(t *testing.T) { t.Run("bad ref", func(t *testing.T) {
badRef := "bad_ref" badRef := "bad_ref"
fileContentResponse, err := GetContents(ctx, repo, treePath, badRef, false) fileContentResponse, err := GetContents(db.DefaultContext, repo, treePath, badRef, false)
require.EqualError(t, err, "object does not exist [id: "+badRef+", rel_path: ]") require.EqualError(t, err, "object does not exist [id: "+badRef+", rel_path: ]")
assert.Nil(t, fileContentResponse) assert.Nil(t, fileContentResponse)
}) })
@ -173,28 +149,21 @@ func TestGetContentsErrors(t *testing.T) {
func TestGetContentsOrListErrors(t *testing.T) { func TestGetContentsOrListErrors(t *testing.T) {
unittest.PrepareTestEnv(t) unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1") repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
ctx.SetParams(":id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
treePath := "README.md" treePath := "README.md"
ref := repo.DefaultBranch ref := repo.DefaultBranch
t.Run("bad treePath", func(t *testing.T) { t.Run("bad treePath", func(t *testing.T) {
badTreePath := "bad/tree.md" badTreePath := "bad/tree.md"
fileContentResponse, err := GetContentsOrList(ctx, repo, badTreePath, ref) fileContentResponse, err := GetContentsOrList(db.DefaultContext, repo, badTreePath, ref)
require.EqualError(t, err, "object does not exist [id: , rel_path: bad]") require.EqualError(t, err, "object does not exist [id: , rel_path: bad]")
assert.Nil(t, fileContentResponse) assert.Nil(t, fileContentResponse)
}) })
t.Run("bad ref", func(t *testing.T) { t.Run("bad ref", func(t *testing.T) {
badRef := "bad_ref" badRef := "bad_ref"
fileContentResponse, err := GetContentsOrList(ctx, repo, treePath, badRef) fileContentResponse, err := GetContentsOrList(db.DefaultContext, repo, treePath, badRef)
require.EqualError(t, err, "object does not exist [id: "+badRef+", rel_path: ]") require.EqualError(t, err, "object does not exist [id: "+badRef+", rel_path: ]")
assert.Nil(t, fileContentResponse) assert.Nil(t, fileContentResponse)
}) })
@ -202,17 +171,10 @@ func TestGetContentsOrListErrors(t *testing.T) {
func TestGetContentsOrListOfEmptyRepos(t *testing.T) { func TestGetContentsOrListOfEmptyRepos(t *testing.T) {
unittest.PrepareTestEnv(t) unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user30/empty") repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 52})
ctx.SetParams(":id", "52")
contexttest.LoadRepo(t, ctx, 52)
contexttest.LoadUser(t, ctx, 30)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
t.Run("empty repo", func(t *testing.T) { t.Run("empty repo", func(t *testing.T) {
contents, err := GetContentsOrList(ctx, repo, "", "") contents, err := GetContentsOrList(db.DefaultContext, repo, "", "")
require.NoError(t, err) require.NoError(t, err)
assert.Empty(t, contents) assert.Empty(t, contents)
}) })
@ -220,23 +182,13 @@ func TestGetContentsOrListOfEmptyRepos(t *testing.T) {
func TestGetBlobBySHA(t *testing.T) { func TestGetBlobBySHA(t *testing.T) {
unittest.PrepareTestEnv(t) unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1") repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
sha := "65f1bf27bc3bf70f64657658635e66094edbcb4d" gitRepo, err := gitrepo.OpenRepository(db.DefaultContext, repo)
ctx.SetParams(":id", "1") require.NoError(t, err)
ctx.SetParams(":sha", sha) defer gitRepo.Close()
gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository) gbr, err := GetBlobBySHA(db.DefaultContext, repo, gitRepo, "65f1bf27bc3bf70f64657658635e66094edbcb4d")
if err != nil {
t.Fail()
}
gbr, err := GetBlobBySHA(ctx, ctx.Repo.Repository, gitRepo, ctx.Params(":sha"))
expectedGBR := &api.GitBlobResponse{ expectedGBR := &api.GitBlobResponse{
Content: "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK", Content: "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK",
Encoding: "base64", Encoding: "base64",

View file

@ -6,6 +6,7 @@ package files
import ( import (
"testing" "testing"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
@ -21,7 +22,6 @@ func TestGetDiffPreview(t *testing.T) {
ctx, _ := contexttest.MockContext(t, "user2/repo1") ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetParams(":id", "1") ctx.SetParams(":id", "1")
contexttest.LoadRepo(t, ctx, 1) contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2) contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx) contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close() defer ctx.Repo.GitRepo.Close()
@ -140,33 +140,26 @@ func TestGetDiffPreview(t *testing.T) {
func TestGetDiffPreviewErrors(t *testing.T) { func TestGetDiffPreviewErrors(t *testing.T) {
unittest.PrepareTestEnv(t) unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1") repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
ctx.SetParams(":id", "1") branch := repo.DefaultBranch
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
branch := ctx.Repo.Repository.DefaultBranch
treePath := "README.md" treePath := "README.md"
content := "# repo1\n\nDescription for repo1\nthis is a new line" content := "# repo1\n\nDescription for repo1\nthis is a new line"
t.Run("empty repo", func(t *testing.T) { t.Run("empty repo", func(t *testing.T) {
diff, err := GetDiffPreview(ctx, &repo_model.Repository{}, branch, treePath, content) diff, err := GetDiffPreview(db.DefaultContext, &repo_model.Repository{}, branch, treePath, content)
assert.Nil(t, diff) assert.Nil(t, diff)
assert.EqualError(t, err, "repository does not exist [id: 0, uid: 0, owner_name: , name: ]") assert.EqualError(t, err, "repository does not exist [id: 0, uid: 0, owner_name: , name: ]")
}) })
t.Run("bad branch", func(t *testing.T) { t.Run("bad branch", func(t *testing.T) {
badBranch := "bad_branch" badBranch := "bad_branch"
diff, err := GetDiffPreview(ctx, ctx.Repo.Repository, badBranch, treePath, content) diff, err := GetDiffPreview(db.DefaultContext, repo, badBranch, treePath, content)
assert.Nil(t, diff) assert.Nil(t, diff)
assert.EqualError(t, err, "branch does not exist [name: "+badBranch+"]") assert.EqualError(t, err, "branch does not exist [name: "+badBranch+"]")
}) })
t.Run("empty treePath", func(t *testing.T) { t.Run("empty treePath", func(t *testing.T) {
diff, err := GetDiffPreview(ctx, ctx.Repo.Repository, branch, "", content) diff, err := GetDiffPreview(db.DefaultContext, repo, branch, "", content)
assert.Nil(t, diff) assert.Nil(t, diff)
assert.EqualError(t, err, "path is invalid [path: ]") assert.EqualError(t, err, "path is invalid [path: ]")
}) })

View file

@ -6,11 +6,12 @@ package files
import ( import (
"testing" "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/models/unittest"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/contexttest"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -99,23 +100,16 @@ func getExpectedFileResponse() *api.FileResponse {
func TestGetFileResponseFromCommit(t *testing.T) { func TestGetFileResponseFromCommit(t *testing.T) {
unittest.PrepareTestEnv(t) unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1")
ctx.SetParams(":id", "1")
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
branch := repo.DefaultBranch branch := repo.DefaultBranch
treePath := "README.md" treePath := "README.md"
gitRepo, _ := gitrepo.OpenRepository(ctx, repo) gitRepo, _ := gitrepo.OpenRepository(db.DefaultContext, repo)
defer gitRepo.Close() defer gitRepo.Close()
commit, _ := gitRepo.GetBranchCommit(branch) commit, _ := gitRepo.GetBranchCommit(branch)
expectedFileResponse := getExpectedFileResponse() expectedFileResponse := getExpectedFileResponse()
fileResponse, err := GetFileResponseFromCommit(ctx, repo, commit, branch, treePath) fileResponse, err := GetFileResponseFromCommit(db.DefaultContext, repo, commit, branch, treePath)
require.NoError(t, err) require.NoError(t, err)
assert.EqualValues(t, expectedFileResponse, fileResponse) assert.EqualValues(t, expectedFileResponse, fileResponse)
} }

View file

@ -18,7 +18,6 @@ func TestGetTreeBySHA(t *testing.T) {
unittest.PrepareTestEnv(t) unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1") ctx, _ := contexttest.MockContext(t, "user2/repo1")
contexttest.LoadRepo(t, ctx, 1) contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2) contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx) contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close() defer ctx.Repo.GitRepo.Close()

View file

@ -300,6 +300,7 @@
<label for="push_mirror_password">{{ctx.Locale.Tr "password"}}</label> <label for="push_mirror_password">{{ctx.Locale.Tr "password"}}</label>
<input id="push_mirror_password" name="push_mirror_password" type="password" value="{{.push_mirror_password}}" autocomplete="off"> <input id="push_mirror_password" name="push_mirror_password" type="password" value="{{.push_mirror_password}}" autocomplete="off">
</div> </div>
{{if .CanUseSSHMirroring}}
<div class="inline field {{if .Err_PushMirrorUseSSH}}error{{end}}"> <div class="inline field {{if .Err_PushMirrorUseSSH}}error{{end}}">
<div class="ui checkbox df ac"> <div class="ui checkbox df ac">
<input id="push_mirror_use_ssh" name="push_mirror_use_ssh" type="checkbox" {{if .push_mirror_use_ssh}}checked{{end}}> <input id="push_mirror_use_ssh" name="push_mirror_use_ssh" type="checkbox" {{if .push_mirror_use_ssh}}checked{{end}}>
@ -307,6 +308,7 @@
<span class="help tw-block">{{ctx.Locale.Tr "repo.mirror_use_ssh.helper"}} <span class="help tw-block">{{ctx.Locale.Tr "repo.mirror_use_ssh.helper"}}
</div> </div>
</div> </div>
{{end}}
</div> </div>
</details> </details>
<div class="field"> <div class="field">

View file

@ -64,7 +64,7 @@
</div> </div>
</div> </div>
<div class="tw-mb-4"> <div class="tw-mb-4">
{{ctx.Locale.Tr "settings.language.localization_project" "https://forgejo.org/docs/latest/developer/localization/"}} {{ctx.Locale.Tr "settings.language.localization_project" "https://forgejo.org/docs/next/contributor/localization/"}}
</div> </div>
<div class="field"> <div class="field">
<button class="ui primary button">{{ctx.Locale.Tr "settings.update_language"}}</button> <button class="ui primary button">{{ctx.Locale.Tr "settings.update_language"}}</button>

View file

@ -14,6 +14,7 @@ env:
rules: rules:
playwright/no-conditional-in-test: [0] playwright/no-conditional-in-test: [0]
playwright/no-conditional-expect: [0]
playwright/no-networkidle: [0] playwright/no-networkidle: [0]
playwright/no-skipped-test: [2, {allowConditional: true}] playwright/no-skipped-test: [2, {allowConditional: true}]
playwright/prefer-comparison-matcher: [2] playwright/prefer-comparison-matcher: [2]

View file

@ -24,7 +24,8 @@ func TestDebugserver(t *testing.T) {
done := make(chan os.Signal, 1) done := make(chan os.Signal, 1)
signal.Notify(done, syscall.SIGINT, syscall.SIGTERM) signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)
onGiteaRun(t, func(*testing.T, *url.URL) { onForgejoRun(t, func(*testing.T, *url.URL) {
defer DeclareGitRepos(t)()
fmt.Println(setting.AppURL) fmt.Println(setting.AppURL)
<-done <-done
}) })

View file

@ -0,0 +1,83 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package e2e
import (
"fmt"
"strconv"
"strings"
"testing"
"time"
unit_model "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"
files_service "code.gitea.io/gitea/services/repository/files"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type FileChanges [][]string
// put your Git repo declarations in here
// feel free to amend the helper function below or use the raw variant directly
func DeclareGitRepos(t *testing.T) func() {
var cleanupFunctions []func()
cleanupFunctions = append(cleanupFunctions, newRepo(t, 2, "diff-test", FileChanges{
{"testfile", "hello", "hallo", "hola", "native", "ubuntu-latest", "- runs-on: ubuntu-latest", "- runs-on: debian-latest"},
}))
return func() {
for _, cleanup := range cleanupFunctions {
cleanup()
}
}
}
func newRepo(t *testing.T, userID int64, repoName string, fileChanges FileChanges) func() {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID})
somerepo, _, cleanupFunc := tests.CreateDeclarativeRepo(t, user, repoName,
[]unit_model.Type{unit_model.TypeCode, unit_model.TypeIssues}, nil,
nil,
)
for _, file := range fileChanges {
changeLen := len(file)
for i := 1; i < changeLen; i++ {
operation := "create"
if i != 1 {
operation = "update"
}
resp, err := files_service.ChangeRepoFiles(git.DefaultContext, somerepo, user, &files_service.ChangeRepoFilesOptions{
Files: []*files_service.ChangeRepoFile{{
Operation: operation,
TreePath: file[0],
ContentReader: strings.NewReader(file[i]),
}},
Message: fmt.Sprintf("Patch: %s-%s", file[0], strconv.Itoa(i)),
OldBranch: "main",
NewBranch: "main",
Author: &files_service.IdentityOptions{
Name: user.Name,
Email: user.Email,
},
Committer: &files_service.IdentityOptions{
Name: user.Name,
Email: user.Email,
},
Dates: &files_service.CommitDateOptions{
Author: time.Now(),
Committer: time.Now(),
},
})
require.NoError(t, err)
assert.NotEmpty(t, resp)
}
}
return cleanupFunc
}

View file

@ -37,7 +37,7 @@ func TestMain(m *testing.M) {
graceful.InitManager(managerCtx) graceful.InitManager(managerCtx)
defer cancel() defer cancel()
tests.InitTest(false) tests.InitTest(true)
testE2eWebRoutes = routers.NormalRoutes() testE2eWebRoutes = routers.NormalRoutes()
os.Unsetenv("GIT_AUTHOR_NAME") os.Unsetenv("GIT_AUTHOR_NAME")
@ -102,7 +102,8 @@ func TestE2e(t *testing.T) {
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
// Default 2 minute timeout // Default 2 minute timeout
onGiteaRun(t, func(*testing.T, *url.URL) { onForgejoRun(t, func(*testing.T, *url.URL) {
defer DeclareGitRepos(t)()
thisTest := runArgs thisTest := runArgs
thisTest = append(thisTest, path) thisTest = append(thisTest, path)
cmd := exec.Command(runArgs[0], thisTest...) cmd := exec.Command(runArgs[0], thisTest...)

View file

@ -51,3 +51,29 @@ test('Line Range Selection', async ({browser}, workerInfo) => {
await page.goto(`${filePath}#L1-L100`); await page.goto(`${filePath}#L1-L100`);
await assertSelectedLines(page, ['1', '2', '3']); await assertSelectedLines(page, ['1', '2', '3']);
}); });
test('Readable diff', async ({page}, workerInfo) => {
// remove this when the test covers more (e.g. accessibility scans or interactive behaviour)
test.skip(workerInfo.project.name !== 'firefox', 'This currently only tests the backend-generated HTML code and it is not necessary to test with multiple browsers.');
const expectedDiffs = [
{id: 'testfile-2', removed: 'e', added: 'a'},
{id: 'testfile-3', removed: 'allo', added: 'ola'},
{id: 'testfile-4', removed: 'hola', added: 'native'},
{id: 'testfile-5', removed: 'native', added: 'ubuntu-latest'},
{id: 'testfile-6', added: '- runs-on: '},
{id: 'testfile-7', removed: 'ubuntu', added: 'debian'},
];
for (const thisDiff of expectedDiffs) {
const response = await page.goto('/user2/diff-test/commits/branch/main');
await expect(response?.status()).toBe(200); // Status OK
await page.getByText(`Patch: ${thisDiff.id}`).click();
if (thisDiff.removed) {
await expect(page.getByText(thisDiff.removed, {exact: true})).toHaveClass(/removed-code/);
await expect(page.getByText(thisDiff.removed, {exact: true})).toHaveCSS('background-color', 'rgb(252, 165, 165)');
}
if (thisDiff.added) {
await expect(page.getByText(thisDiff.added, {exact: true})).toHaveClass(/added-code/);
await expect(page.getByText(thisDiff.added, {exact: true})).toHaveCSS('background-color', 'rgb(134, 239, 172)');
}
}
});

View file

@ -17,7 +17,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func onGiteaRunTB(t testing.TB, callback func(testing.TB, *url.URL), prepare ...bool) { func onForgejoRunTB(t testing.TB, callback func(testing.TB, *url.URL), prepare ...bool) {
if len(prepare) == 0 || prepare[0] { if len(prepare) == 0 || prepare[0] {
defer tests.PrepareTestEnv(t, 1)() defer tests.PrepareTestEnv(t, 1)()
} }
@ -49,8 +49,8 @@ func onGiteaRunTB(t testing.TB, callback func(testing.TB, *url.URL), prepare ...
callback(t, u) callback(t, u)
} }
func onGiteaRun(t *testing.T, callback func(*testing.T, *url.URL), prepare ...bool) { func onForgejoRun(t *testing.T, callback func(*testing.T, *url.URL), prepare ...bool) {
onGiteaRunTB(t, func(t testing.TB, u *url.URL) { onForgejoRunTB(t, func(t testing.TB, u *url.URL) {
callback(t.(*testing.T), u) callback(t.(*testing.T), u)
}, prepare...) }, prepare...)
} }

View file

@ -11,6 +11,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strconv" "strconv"
"testing" "testing"
@ -23,6 +24,7 @@ import (
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
@ -141,6 +143,11 @@ func testAPIPushMirror(t *testing.T, u *url.URL) {
} }
func TestAPIPushMirrorSSH(t *testing.T) { func TestAPIPushMirrorSSH(t *testing.T) {
_, err := exec.LookPath("ssh")
if err != nil {
t.Skip("SSH executable not present")
}
onGiteaRun(t, func(t *testing.T, _ *url.URL) { onGiteaRun(t, func(t *testing.T, _ *url.URL) {
defer test.MockVariableValue(&setting.Migrations.AllowLocalNetworks, true)() defer test.MockVariableValue(&setting.Migrations.AllowLocalNetworks, true)()
defer test.MockVariableValue(&setting.Mirror.Enabled, true)() defer test.MockVariableValue(&setting.Mirror.Enabled, true)()
@ -178,6 +185,22 @@ func TestAPIPushMirrorSSH(t *testing.T) {
assert.EqualValues(t, "'use_ssh' is mutually exclusive with 'remote_username' and 'remote_passoword'", apiError.Message) assert.EqualValues(t, "'use_ssh' is mutually exclusive with 'remote_username' and 'remote_passoword'", apiError.Message)
}) })
t.Run("SSH not available", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
defer test.MockVariableValue(&git.HasSSHExecutable, false)()
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/push_mirrors", srcRepo.FullName()), &api.CreatePushMirrorOption{
RemoteAddress: sshURL,
Interval: "8h",
UseSSH: true,
}).AddTokenAuth(token)
resp := MakeRequest(t, req, http.StatusBadRequest)
var apiError api.APIError
DecodeJSON(t, resp, &apiError)
assert.EqualValues(t, "SSH authentication not available.", apiError.Message)
})
t.Run("Normal", func(t *testing.T) { t.Run("Normal", func(t *testing.T) {
var pushMirror *repo_model.PushMirror var pushMirror *repo_model.PushMirror
t.Run("Adding", func(t *testing.T) { t.Run("Adding", func(t *testing.T) {

View file

@ -15,7 +15,7 @@ import (
"code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"gitea.com/go-chi/session" "code.forgejo.org/go-chi/session"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )

View file

@ -11,6 +11,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strconv" "strconv"
"testing" "testing"
@ -157,6 +158,11 @@ func doRemovePushMirror(ctx APITestContext, address, username, password string,
} }
func TestSSHPushMirror(t *testing.T) { func TestSSHPushMirror(t *testing.T) {
_, err := exec.LookPath("ssh")
if err != nil {
t.Skip("SSH executable not present")
}
onGiteaRun(t, func(t *testing.T, _ *url.URL) { onGiteaRun(t, func(t *testing.T, _ *url.URL) {
defer test.MockVariableValue(&setting.Migrations.AllowLocalNetworks, true)() defer test.MockVariableValue(&setting.Migrations.AllowLocalNetworks, true)()
defer test.MockVariableValue(&setting.Mirror.Enabled, true)() defer test.MockVariableValue(&setting.Mirror.Enabled, true)()
@ -194,6 +200,36 @@ func TestSSHPushMirror(t *testing.T) {
assert.Contains(t, errMsg, "Cannot use public key and password based authentication in combination.") assert.Contains(t, errMsg, "Cannot use public key and password based authentication in combination.")
}) })
inputSelector := `input[id="push_mirror_use_ssh"]`
t.Run("SSH not available", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
defer test.MockVariableValue(&git.HasSSHExecutable, false)()
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/%s/settings", srcRepo.FullName()), map[string]string{
"_csrf": GetCSRF(t, sess, fmt.Sprintf("/%s/settings", srcRepo.FullName())),
"action": "push-mirror-add",
"push_mirror_address": sshURL,
"push_mirror_use_ssh": "true",
"push_mirror_interval": "0",
})
resp := sess.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
errMsg := htmlDoc.Find(".ui.negative.message").Text()
assert.Contains(t, errMsg, "SSH authentication isn't available.")
htmlDoc.AssertElement(t, inputSelector, false)
})
t.Run("SSH available", func(t *testing.T) {
req := NewRequest(t, "GET", fmt.Sprintf("/%s/settings", srcRepo.FullName()))
resp := sess.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
htmlDoc.AssertElement(t, inputSelector, true)
})
t.Run("Normal", func(t *testing.T) { t.Run("Normal", func(t *testing.T) {
var pushMirror *repo_model.PushMirror var pushMirror *repo_model.PushMirror
t.Run("Adding", func(t *testing.T) { t.Run("Adding", func(t *testing.T) {

View file

@ -548,6 +548,42 @@ func TestGitQuotaEnforcement(t *testing.T) {
}) })
} }
func TestQuotaConfigDefault(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
env := createQuotaWebEnv(t)
defer env.Cleanup()
t.Run("with config-based default", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
defer test.MockVariableValue(&setting.Quota.Default.Total, 0)()
env.As(t, env.Users.Ungrouped).
With(Context{
Payload: &Payload{
"uid": env.Users.Ungrouped.ID().AsString(),
"repo_name": "quota-config-default",
},
}).
PostToPage("/repo/create").
ExpectStatus(http.StatusRequestEntityTooLarge)
})
t.Run("without config-based default", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
env.As(t, env.Users.Ungrouped).
With(Context{
Payload: &Payload{
"uid": env.Users.Ungrouped.ID().AsString(),
"repo_name": "quota-config-default",
},
}).
PostToPage("/repo/create").
ExpectStatus(http.StatusSeeOther)
})
})
}
/********************** /**********************
* Here be dragons! * * Here be dragons! *
* * * *
@ -568,6 +604,7 @@ type quotaWebEnv struct {
type quotaWebEnvUsers struct { type quotaWebEnvUsers struct {
Limited quotaWebEnvUser Limited quotaWebEnvUser
Contributor quotaWebEnvUser Contributor quotaWebEnvUser
Ungrouped quotaWebEnvUser
} }
type quotaWebEnvOrgs struct { type quotaWebEnvOrgs struct {
@ -1005,8 +1042,7 @@ func createQuotaWebEnv(t *testing.T) *quotaWebEnv {
// *** helpers *** // *** helpers ***
// Create a user, its quota group & rule makeUngroupedUser := func(t *testing.T) quotaWebEnvUser {
makeUser := func(t *testing.T, limit int64) quotaWebEnvUser {
t.Helper() t.Helper()
user := quotaWebEnvUser{} user := quotaWebEnvUser{}
@ -1021,6 +1057,16 @@ func createQuotaWebEnv(t *testing.T) *quotaWebEnv {
repo, _, _ := tests.CreateDeclarativeRepoWithOptions(t, user.User, tests.DeclarativeRepoOptions{}) repo, _, _ := tests.CreateDeclarativeRepoWithOptions(t, user.User, tests.DeclarativeRepoOptions{})
user.Repo = repo user.Repo = repo
return user
}
// Create a user, its quota group & rule
makeUser := func(t *testing.T, limit int64) quotaWebEnvUser {
t.Helper()
user := makeUngroupedUser(t)
userName := user.User.Name
// Create a quota group for them // Create a quota group for them
group, err := quota_model.CreateGroup(db.DefaultContext, userName) group, err := quota_model.CreateGroup(db.DefaultContext, userName)
require.NoError(t, err) require.NoError(t, err)
@ -1095,5 +1141,7 @@ func createQuotaWebEnv(t *testing.T) *quotaWebEnv {
env.Orgs.Limited = makeOrg(t, env.Users.Limited.User, int64(0)) env.Orgs.Limited = makeOrg(t, env.Users.Limited.User, int64(0))
env.Orgs.Unlimited = makeOrg(t, env.Users.Limited.User, int64(-1)) env.Orgs.Unlimited = makeOrg(t, env.Users.Limited.User, int64(-1))
env.Users.Ungrouped = makeUngroupedUser(t)
return &env return &env
} }

View file

@ -52,8 +52,13 @@ func createRepoAndGetContext(t *testing.T, files []string, deleteMdReadme bool)
ctx, _ := contexttest.MockContext(t, "user1/readmetest") ctx, _ := contexttest.MockContext(t, "user1/readmetest")
ctx.SetParams(":id", fmt.Sprint(repo.ID)) ctx.SetParams(":id", fmt.Sprint(repo.ID))
contexttest.LoadRepo(t, ctx, repo.ID) contexttest.LoadRepo(t, ctx, repo.ID)
contexttest.LoadGitRepo(t, ctx)
contexttest.LoadRepoCommit(t, ctx) contexttest.LoadRepoCommit(t, ctx)
return ctx, f
return ctx, func() {
f()
ctx.Repo.GitRepo.Close()
}
} }
func TestRepoView_FindReadme(t *testing.T) { func TestRepoView_FindReadme(t *testing.T) {

View file

@ -12,11 +12,11 @@ import (
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "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/git"
"code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/gitrepo"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/contexttest"
files_service "code.gitea.io/gitea/services/repository/files" files_service "code.gitea.io/gitea/services/repository/files"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -247,16 +247,8 @@ func getExpectedFileResponseForRepofilesUpdate(commitID, filename, lastCommitSHA
func TestChangeRepoFilesForCreate(t *testing.T) { func TestChangeRepoFilesForCreate(t *testing.T) {
// setup // setup
onGiteaRun(t, func(t *testing.T, u *url.URL) { onGiteaRun(t, func(t *testing.T, u *url.URL) {
ctx, _ := contexttest.MockContext(t, "user2/repo1") doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
ctx.SetParams(":id", "1") repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
doer := ctx.Doer
opts := getCreateRepoFilesOptions(repo) opts := getCreateRepoFilesOptions(repo)
// test // test
@ -284,16 +276,8 @@ func TestChangeRepoFilesForCreate(t *testing.T) {
func TestChangeRepoFilesForUpdate(t *testing.T) { func TestChangeRepoFilesForUpdate(t *testing.T) {
// setup // setup
onGiteaRun(t, func(t *testing.T, u *url.URL) { onGiteaRun(t, func(t *testing.T, u *url.URL) {
ctx, _ := contexttest.MockContext(t, "user2/repo1") doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
ctx.SetParams(":id", "1") repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
doer := ctx.Doer
opts := getUpdateRepoFilesOptions(repo) opts := getUpdateRepoFilesOptions(repo)
// test // test
@ -318,16 +302,8 @@ func TestChangeRepoFilesForUpdate(t *testing.T) {
func TestChangeRepoFilesForUpdateWithFileMove(t *testing.T) { func TestChangeRepoFilesForUpdateWithFileMove(t *testing.T) {
// setup // setup
onGiteaRun(t, func(t *testing.T, u *url.URL) { onGiteaRun(t, func(t *testing.T, u *url.URL) {
ctx, _ := contexttest.MockContext(t, "user2/repo1") doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
ctx.SetParams(":id", "1") repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
doer := ctx.Doer
opts := getUpdateRepoFilesOptions(repo) opts := getUpdateRepoFilesOptions(repo)
opts.Files[0].FromTreePath = "README.md" opts.Files[0].FromTreePath = "README.md"
opts.Files[0].TreePath = "README_new.md" // new file name, README_new.md opts.Files[0].TreePath = "README_new.md" // new file name, README_new.md
@ -369,16 +345,8 @@ func TestChangeRepoFilesForUpdateWithFileMove(t *testing.T) {
func TestChangeRepoFilesWithoutBranchNames(t *testing.T) { func TestChangeRepoFilesWithoutBranchNames(t *testing.T) {
// setup // setup
onGiteaRun(t, func(t *testing.T, u *url.URL) { onGiteaRun(t, func(t *testing.T, u *url.URL) {
ctx, _ := contexttest.MockContext(t, "user2/repo1") doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
ctx.SetParams(":id", "1") repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
doer := ctx.Doer
opts := getUpdateRepoFilesOptions(repo) opts := getUpdateRepoFilesOptions(repo)
opts.OldBranch = "" opts.OldBranch = ""
opts.NewBranch = "" opts.NewBranch = ""
@ -405,15 +373,8 @@ func TestChangeRepoFilesForDelete(t *testing.T) {
func testDeleteRepoFiles(t *testing.T, u *url.URL) { func testDeleteRepoFiles(t *testing.T, u *url.URL) {
// setup // setup
unittest.PrepareTestEnv(t) unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1") doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
ctx.SetParams(":id", "1") repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
doer := ctx.Doer
opts := getDeleteRepoFilesOptions(repo) opts := getDeleteRepoFilesOptions(repo)
t.Run("Delete README.md file", func(t *testing.T) { t.Run("Delete README.md file", func(t *testing.T) {
@ -444,16 +405,9 @@ func TestChangeRepoFilesForDeleteWithoutBranchNames(t *testing.T) {
func testDeleteRepoFilesWithoutBranchNames(t *testing.T, u *url.URL) { func testDeleteRepoFilesWithoutBranchNames(t *testing.T, u *url.URL) {
// setup // setup
unittest.PrepareTestEnv(t) unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockContext(t, "user2/repo1") doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
ctx.SetParams(":id", "1") repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
doer := ctx.Doer
opts := getDeleteRepoFilesOptions(repo) opts := getDeleteRepoFilesOptions(repo)
opts.OldBranch = "" opts.OldBranch = ""
opts.NewBranch = "" opts.NewBranch = ""
@ -474,16 +428,8 @@ func testDeleteRepoFilesWithoutBranchNames(t *testing.T, u *url.URL) {
func TestChangeRepoFilesErrors(t *testing.T) { func TestChangeRepoFilesErrors(t *testing.T) {
// setup // setup
onGiteaRun(t, func(t *testing.T, u *url.URL) { onGiteaRun(t, func(t *testing.T, u *url.URL) {
ctx, _ := contexttest.MockContext(t, "user2/repo1") doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
ctx.SetParams(":id", "1") repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadGitRepo(t, ctx)
defer ctx.Repo.GitRepo.Close()
repo := ctx.Repo.Repository
doer := ctx.Doer
t.Run("bad branch", func(t *testing.T) { t.Run("bad branch", func(t *testing.T) {
opts := getUpdateRepoFilesOptions(repo) opts := getUpdateRepoFilesOptions(repo)

View file

@ -389,7 +389,7 @@ func TestUserHints(t *testing.T) {
assert.Equal(t, enabled, hintChecked) assert.Equal(t, enabled, hintChecked)
link, _ := htmlDoc.Find("form[action='/user/settings/appearance/language'] a").Attr("href") link, _ := htmlDoc.Find("form[action='/user/settings/appearance/language'] a").Attr("href")
assert.EqualValues(t, "https://forgejo.org/docs/latest/developer/localization/", link) assert.EqualValues(t, "https://forgejo.org/docs/next/contributor/localization/", link)
} }
t.Run("view", func(t *testing.T) { t.Run("view", func(t *testing.T) {