diff --git a/.air.toml b/.air.toml index 069a88924388..71d7623dfb0d 100644 --- a/.air.toml +++ b/.air.toml @@ -4,7 +4,9 @@ tmp_dir = ".air" [build] cmd = "make backend" bin = "gitea" +delay = 1000 include_ext = ["go", "tmpl"] -exclude_dir = ["modules/git/tests", "services/gitdiff/testdata", "modules/avatar/testdata", "models/fixtures", "models/migrations/fixtures", "modules/migration/file_format_testdata", "modules/avatar/identicon/testdata"] +include_file = ["main.go"] include_dir = ["cmd", "models", "modules", "options", "routers", "services"] +exclude_dir = ["modules/git/tests", "services/gitdiff/testdata", "modules/avatar/testdata", "models/fixtures", "models/migrations/fixtures", "modules/migration/file_format_testdata", "modules/avatar/identicon/testdata"] exclude_regex = ["_test.go$", "_gen.go$"] diff --git a/.drone.yml b/.drone.yml index 789f1ffca415..d1eca955fcc3 100644 --- a/.drone.yml +++ b/.drone.yml @@ -59,7 +59,7 @@ steps: - name: lint-backend-windows image: gitea/test_env:linux-1.20-amd64 # https://gitea.com/gitea/test-env commands: - - make golangci-lint-windows vet + - make lint-go-windows lint-go-vet environment: GOPROXY: https://goproxy.io # proxy.golang.org is blocked in China, this proxy is not GOSUMDB: sum.golang.org diff --git a/.eslintrc.yaml b/.eslintrc.yaml index a3a89e76d4f4..f87a4149ac36 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -193,7 +193,6 @@ rules: jquery/no-val: [0] jquery/no-when: [2] jquery/no-wrap: [2] - no-jquery/no-event-shorthand: [2] key-spacing: [2] keyword-spacing: [2] line-comment-position: [0] @@ -272,6 +271,99 @@ rules: no-invalid-this: [0] no-irregular-whitespace: [2] no-iterator: [2] + no-jquery/no-ajax-events: [2] + no-jquery/no-ajax: [0] + no-jquery/no-and-self: [2] + no-jquery/no-animate-toggle: [2] + no-jquery/no-animate: [2] + no-jquery/no-append-html: [0] + no-jquery/no-attr: [0] + no-jquery/no-bind: [2] + no-jquery/no-box-model: [2] + no-jquery/no-browser: [2] + no-jquery/no-camel-case: [2] + no-jquery/no-class-state: [0] + no-jquery/no-class: [0] + no-jquery/no-clone: [2] + no-jquery/no-closest: [0] + no-jquery/no-constructor-attributes: [2] + no-jquery/no-contains: [2] + no-jquery/no-context-prop: [2] + no-jquery/no-css: [0] + no-jquery/no-data: [0] + no-jquery/no-deferred: [2] + no-jquery/no-delegate: [2] + no-jquery/no-each-collection: [0] + no-jquery/no-each-util: [0] + no-jquery/no-each: [0] + no-jquery/no-error-shorthand: [2] + no-jquery/no-error: [2] + no-jquery/no-escape-selector: [2] + no-jquery/no-event-shorthand: [2] + no-jquery/no-extend: [2] + no-jquery/no-fade: [2] + no-jquery/no-filter: [0] + no-jquery/no-find-collection: [0] + no-jquery/no-find-util: [2] + no-jquery/no-find: [0] + no-jquery/no-fx-interval: [2] + no-jquery/no-global-eval: [2] + no-jquery/no-global-selector: [0] + no-jquery/no-grep: [2] + no-jquery/no-has: [2] + no-jquery/no-hold-ready: [2] + no-jquery/no-html: [0] + no-jquery/no-in-array: [2] + no-jquery/no-is-array: [2] + no-jquery/no-is-empty-object: [2] + no-jquery/no-is-function: [2] + no-jquery/no-is-numeric: [2] + no-jquery/no-is-plain-object: [2] + no-jquery/no-is-window: [2] + no-jquery/no-is: [0] + no-jquery/no-jquery-constructor: [0] + no-jquery/no-live: [2] + no-jquery/no-load-shorthand: [2] + no-jquery/no-load: [2] + no-jquery/no-map-collection: [0] + no-jquery/no-map-util: [2] + no-jquery/no-map: [0] + no-jquery/no-merge: [2] + no-jquery/no-node-name: [2] + no-jquery/no-noop: [2] + no-jquery/no-now: [2] + no-jquery/no-on-ready: [2] + no-jquery/no-other-methods: [0] + no-jquery/no-other-utils: [2] + no-jquery/no-param: [2] + no-jquery/no-parent: [0] + no-jquery/no-parents: [0] + no-jquery/no-parse-html-literal: [0] + no-jquery/no-parse-html: [2] + no-jquery/no-parse-json: [2] + no-jquery/no-parse-xml: [2] + no-jquery/no-prop: [0] + no-jquery/no-proxy: [2] + no-jquery/no-ready-shorthand: [2] + no-jquery/no-ready: [2] + no-jquery/no-selector-prop: [2] + no-jquery/no-serialize: [2] + no-jquery/no-size: [2] + no-jquery/no-sizzle: [0] + no-jquery/no-slide: [2] + no-jquery/no-sub: [2] + no-jquery/no-support: [2] + no-jquery/no-text: [0] + no-jquery/no-trigger: [0] + no-jquery/no-trim: [2] + no-jquery/no-type: [2] + no-jquery/no-unique: [2] + no-jquery/no-unload-shorthand: [2] + no-jquery/no-val: [0] + no-jquery/no-visibility: [2] + no-jquery/no-when: [2] + no-jquery/no-wrap: [2] + no-jquery/variable-pattern: [0] no-label-var: [2] no-labels: [0] # handled by no-restricted-syntax no-lone-blocks: [2] diff --git a/.gitignore b/.gitignore index 1ce2a87611e8..ad141c30cd9a 100644 --- a/.gitignore +++ b/.gitignore @@ -71,6 +71,7 @@ cpu.out /tests/e2e/test-artifacts /tests/e2e/test-snapshots /tests/*.ini +/tests/**/*.git/**/*.sample /node_modules /yarn.lock /yarn-error.log diff --git a/.golangci.yml b/.golangci.yml index ebb90af9d504..1be6b63ef3a9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -22,7 +22,7 @@ linters: - unconvert - unused # - varcheck # deprecated - https://github.com/golangci/golangci-lint/issues/1841 - # - wastedassign # disabled - https://github.com/golangci/golangci-lint/issues/2649 + - wastedassign enable-all: false disable-all: true fast: false diff --git a/MAINTAINERS b/MAINTAINERS index f920f71f63e8..ef416639b0fe 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -41,7 +41,7 @@ Patrick Schratz (@pat-s) Janis Estelmann (@KN4CK3R) Steven Kriegler (@justusbunsi) Jimmy Praet (@jpraet) -Leon Hofmeister (@delvh) +Leon Hofmeister (@delvh) Wim (@42wim) Jason Song (@wolfogre) Yarden Shoham (@yardenshoham) @@ -49,3 +49,4 @@ Yu Tian (@Zettat123) Eddie Yang <576951401@qq.com> (@yp05327) Dong Ge (@sillyguodong) Xinyi Gong (@HesterG) +wxiaoguang (@wxiaoguang) diff --git a/Makefile b/Makefile index 59cc27ee8a10..c94f36d6a92f 100644 --- a/Makefile +++ b/Makefile @@ -25,16 +25,15 @@ COMMA := , XGO_VERSION := go-1.20.x -AIR_PACKAGE ?= github.com/cosmtrek/air@v1.40.4 -EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.6.0 -ERRCHECK_PACKAGE ?= github.com/kisielk/errcheck@v1.6.2 -GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.4.0 -GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.2 -GXZ_PAGAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.10 +AIR_PACKAGE ?= github.com/cosmtrek/air@v1.43.0 +EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@2.7.0 +GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.5.0 +GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.52.2 +GXZ_PAGAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 MISSPELL_PACKAGE ?= github.com/client9/misspell/cmd/misspell@v0.3.4 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.30.4 XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest -GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.5.0 +GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@latest DOCKER_IMAGE ?= gitea/gitea @@ -194,9 +193,20 @@ help: @echo " - deps-backend install backend dependencies" @echo " - deps-tools install tool dependencies" @echo " - lint lint everything" + @echo " - lint-fix lint everything and fix issues" @echo " - lint-frontend lint frontend files" + @echo " - lint-frontend-fix lint frontend files and fix issues" @echo " - lint-backend lint backend files" + @echo " - lint-backend-fix lint backend files and fix issues" + @echo " - lint-go lint go files" + @echo " - lint-go-fix lint go files and fix issues" + @echo " - lint-go-vet lint go files with vet" + @echo " - lint-js lint js files" + @echo " - lint-js-fix lint js files and fix issues" + @echo " - lint-css lint css files" + @echo " - lint-css-fix lint css files and fix issues" @echo " - lint-md lint markdown files" + @echo " - lint-swagger lint swagger files" @echo " - checks run various consistency checks" @echo " - checks-frontend check frontend files" @echo " - checks-backend check backend files" @@ -214,9 +224,7 @@ help: @echo " - generate-manpage generate manpage" @echo " - generate-swagger generate the swagger spec from code comments" @echo " - swagger-validate check if the swagger spec is valid" - @echo " - golangci-lint run golangci-lint linter" @echo " - go-licenses regenerate go licenses" - @echo " - vet examines Go source code and reports suspicious constructs" @echo " - tidy run go mod tidy" @echo " - test[\#TestSpecificName] run unit test" @echo " - test-sqlite[\#TestSpecificName] run integration test for sqlite" @@ -286,12 +294,6 @@ fmt-check: fmt misspell-check: go run $(MISSPELL_PACKAGE) -error $(GO_DIRS) $(WEB_DIRS) -.PHONY: vet -vet: - @echo "Running go vet..." - @GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet - @$(GO) vet -vettool=gitea-vet $(GO_PACKAGES) - .PHONY: $(TAGS_EVIDENCE) $(TAGS_EVIDENCE): @mkdir -p $(MAKE_EVIDENCE_DIR) @@ -324,11 +326,6 @@ swagger-validate: $(GO) run $(SWAGGER_PACKAGE) validate './$(SWAGGER_SPEC)' $(SED_INPLACE) '$(SWAGGER_SPEC_S_TMPL)' './$(SWAGGER_SPEC)' -.PHONY: errcheck -errcheck: - @echo "Running errcheck..." - $(GO) run $(ERRCHECK_PACKAGE) $(GO_PACKAGES) - .PHONY: checks checks: checks-frontend checks-backend @@ -341,18 +338,69 @@ checks-backend: tidy-check swagger-check fmt-check misspell-check swagger-valida .PHONY: lint lint: lint-frontend lint-backend +.PHONY: lint-fix +lint-fix: lint-frontend-fix lint-backend-fix + .PHONY: lint-frontend -lint-frontend: node_modules lint-md +lint-frontend: lint-js lint-css lint-md lint-swagger + +.PHONY: lint-frontend-fix +lint-frontend-fix: lint-js-fix lint-css-fix lint-md lint-swagger + +.PHONY: lint-backend +lint-backend: lint-go lint-go-vet lint-editorconfig + +.PHONY: lint-backend-fix +lint-backend-fix: lint-go-fix lint-go-vet lint-editorconfig + +.PHONY: lint-js +lint-js: node_modules npx eslint --color --max-warnings=0 --ext js,vue web_src/js build *.config.js docs/assets/js tests/e2e + +.PHONY: lint-js-fix +lint-js-fix: node_modules + npx eslint --color --max-warnings=0 --ext js,vue web_src/js build *.config.js docs/assets/js tests/e2e --fix + +.PHONY: lint-css +lint-css: node_modules npx stylelint --color --max-warnings=0 web_src/css + +.PHONY: lint-css-fix +lint-css-fix: node_modules + npx stylelint --color --max-warnings=0 web_src/css --fix + +.PHONY: lint-swagger +lint-swagger: node_modules npx spectral lint -q -F hint $(SWAGGER_SPEC) .PHONY: lint-md lint-md: node_modules npx markdownlint docs *.md -.PHONY: lint-backend -lint-backend: golangci-lint vet editorconfig-checker +.PHONY: lint-go +lint-go: + $(GO) run $(GOLANGCI_LINT_PACKAGE) run + +.PHONY: lint-go-fix +lint-go-fix: + $(GO) run $(GOLANGCI_LINT_PACKAGE) run --fix + +# workaround step for the lint-backend-windows CI task because 'go run' can not +# have distinct GOOS/GOARCH for its build and run steps +.PHONY: lint-go-windows +lint-go-windows: + @GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE) + golangci-lint run + +.PHONY: lint-go-vet +lint-go-vet: + @echo "Running go vet..." + @GOOS= GOARCH= $(GO) build code.gitea.io/gitea-vet + @$(GO) vet -vettool=gitea-vet $(GO_PACKAGES) + +.PHONY: lint-editorconfig +lint-editorconfig: + $(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) templates .PHONY: watch watch: @@ -843,7 +891,6 @@ deps-backend: deps-tools: $(GO) install $(AIR_PACKAGE) $(GO) install $(EDITORCONFIG_CHECKER_PACKAGE) - $(GO) install $(ERRCHECK_PACKAGE) $(GO) install $(GOFUMPT_PACKAGE) $(GO) install $(GOLANGCI_LINT_PACKAGE) $(GO) install $(GXZ_PAGAGE) @@ -942,21 +989,6 @@ generate-manpage: @gzip -9 man/man1/gitea.1 && echo man/man1/gitea.1.gz created @#TODO A small script that formats config-cheat-sheet.en-us.md nicely for use as a config man page -.PHONY: golangci-lint -golangci-lint: - $(GO) run $(GOLANGCI_LINT_PACKAGE) run - -# workaround step for the lint-backend-windows CI task because 'go run' can not -# have distinct GOOS/GOARCH for its build and run steps -.PHONY: golangci-lint-windows -golangci-lint-windows: - @GOOS= GOARCH= $(GO) install $(GOLANGCI_LINT_PACKAGE) - golangci-lint run - -.PHONY: editorconfig-checker -editorconfig-checker: - $(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) templates - .PHONY: docker docker: docker build --disable-content-trust=false -t $(DOCKER_REF) . diff --git a/assets/go-licenses.json b/assets/go-licenses.json index 3e7db196f721..1b6ead5df654 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -1004,6 +1004,11 @@ "path": "golang.org/x/crypto/LICENSE", "licenseText": "Copyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" }, + { + "name": "golang.org/x/image", + "path": "golang.org/x/image/LICENSE", + "licenseText": "Copyright (c) 2009 The Go Authors. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" + }, { "name": "golang.org/x/mod/semver", "path": "golang.org/x/mod/semver/LICENSE", diff --git a/cmd/migrate_storage_test.go b/cmd/migrate_storage_test.go index aae366c0cf52..e6658b6e65ef 100644 --- a/cmd/migrate_storage_test.go +++ b/cmd/migrate_storage_test.go @@ -67,7 +67,7 @@ func TestMigratePackages(t *testing.T) { entries, err := os.ReadDir(p) assert.NoError(t, err) - assert.EqualValues(t, 2, len(entries)) + assert.Len(t, entries, 2) assert.EqualValues(t, "01", entries[0].Name()) assert.EqualValues(t, "tmp", entries[1].Name()) } diff --git a/contrib/upgrade.sh b/contrib/upgrade.sh index 3a98c277d6b3..2d15f1733207 100755 --- a/contrib/upgrade.sh +++ b/contrib/upgrade.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # This is an update script for gitea installed via the binary distribution -# from dl.gitea.io on linux as systemd service. It performs a backup and updates +# from dl.gitea.com on linux as systemd service. It performs a backup and updates # Gitea in place. # NOTE: This adds the GPG Signing Key of the Gitea maintainers to the keyring. # Depends on: bash, curl, xz, sha256sum. optionally jq, gpg @@ -69,7 +69,7 @@ require curl xz sha256sum "$sudocmd" # select version to install if [[ -z "${giteaversion:-}" ]]; then require jq - giteaversion=$(curl --connect-timeout 10 -sL https://dl.gitea.io/gitea/version.json | jq -r .latest.version) + giteaversion=$(curl --connect-timeout 10 -sL https://dl.gitea.com/gitea/version.json | jq -r .latest.version) echo "Latest available version is $giteaversion" fi @@ -91,7 +91,7 @@ cd "$giteahome" # needed for gitea dump later # download new binary binname="gitea-${giteaversion}-${arch}" -binurl="https://dl.gitea.io/gitea/${giteaversion}/${binname}.xz" +binurl="https://dl.gitea.com/gitea/${giteaversion}/${binname}.xz" echo "Downloading $binurl..." curl --connect-timeout 10 --silent --show-error --fail --location -O "$binurl{,.sha256,.asc}" diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index f9f207522c7d..29ebb4699bc1 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -2340,7 +2340,6 @@ ROUTER = console ;[other] ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;SHOW_FOOTER_BRANDING = false ;; Show version information about Gitea and Go in the footer ;SHOW_FOOTER_VERSION = true ;; Show template execution time in the footer diff --git a/docs/content/doc/administration/config-cheat-sheet.en-us.md b/docs/content/doc/administration/config-cheat-sheet.en-us.md index f26e7eaa085a..772e0535e91e 100644 --- a/docs/content/doc/administration/config-cheat-sheet.en-us.md +++ b/docs/content/doc/administration/config-cheat-sheet.en-us.md @@ -1391,7 +1391,6 @@ although Github don't support this form. ## Other (`other`) -- `SHOW_FOOTER_BRANDING`: **false**: Show Gitea branding in the footer. - `SHOW_FOOTER_VERSION`: **true**: Show Gitea and Go version information in the footer. - `SHOW_FOOTER_TEMPLATE_LOAD_TIME`: **true**: Show time of template execution in the footer. - `ENABLE_SITEMAP`: **true**: Generate sitemap. diff --git a/docs/content/doc/administration/config-cheat-sheet.zh-cn.md b/docs/content/doc/administration/config-cheat-sheet.zh-cn.md index 83e212f32b84..485d10623469 100644 --- a/docs/content/doc/administration/config-cheat-sheet.zh-cn.md +++ b/docs/content/doc/administration/config-cheat-sheet.zh-cn.md @@ -483,5 +483,4 @@ PROXY_HOSTS = *.github.com ## Other (`other`) -- `SHOW_FOOTER_BRANDING`: 为真则在页面底部显示Gitea的字样。 - `SHOW_FOOTER_VERSION`: 为真则在页面底部显示Gitea的版本。 diff --git a/go.mod b/go.mod index 944f6d2c918c..9f0a23f4550f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module code.gitea.io/gitea go 1.19 require ( - code.gitea.io/actions-proto-go v0.2.0 + code.gitea.io/actions-proto-go v0.2.1 code.gitea.io/gitea-vet v0.2.2 code.gitea.io/sdk/gitea v0.15.1 codeberg.org/gusted/mcaptcha v0.0.0-20220723083913-4f3072e1d570 @@ -104,10 +104,11 @@ require ( github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87 github.com/yuin/goldmark-meta v1.1.0 golang.org/x/crypto v0.7.0 + golang.org/x/image v0.7.0 golang.org/x/net v0.8.0 golang.org/x/oauth2 v0.6.0 golang.org/x/sys v0.6.0 - golang.org/x/text v0.8.0 + golang.org/x/text v0.9.0 golang.org/x/tools v0.6.0 google.golang.org/grpc v1.53.0 google.golang.org/protobuf v1.28.1 diff --git a/go.sum b/go.sum index df57c65918b0..e48fa8b45b80 100644 --- a/go.sum +++ b/go.sum @@ -40,8 +40,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -code.gitea.io/actions-proto-go v0.2.0 h1:nYh9nhhfk67YA4wVNLsCzd//RCvXnljwXClJ33+HPVk= -code.gitea.io/actions-proto-go v0.2.0/go.mod h1:00ys5QDo1iHN1tHNvvddAcy2W/g+425hQya1cCSvq9A= +code.gitea.io/actions-proto-go v0.2.1 h1:ToMN/8thz2q10TuCq8dL2d8mI+/pWpJcHCvG+TELwa0= +code.gitea.io/actions-proto-go v0.2.1/go.mod h1:00ys5QDo1iHN1tHNvvddAcy2W/g+425hQya1cCSvq9A= code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= code.gitea.io/gitea-vet v0.2.2 h1:TEOV/Glf38iGmKzKP0EB++Z5OSL4zGg3RrAvlwaMuvk= code.gitea.io/gitea-vet v0.2.2/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= @@ -1331,6 +1331,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw= +golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1415,6 +1417,7 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1533,6 +1536,7 @@ golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -1540,6 +1544,7 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1551,8 +1556,9 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/models/actions/task_output.go b/models/actions/task_output.go new file mode 100644 index 000000000000..5291a783d669 --- /dev/null +++ b/models/actions/task_output.go @@ -0,0 +1,51 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "context" + + "code.gitea.io/gitea/models/db" +) + +// ActionTaskOutput represents an output of ActionTask. +// So the outputs are bound to a task, that means when a completed job has been rerun, +// the outputs of the job will be reset because the task is new. +// It's by design, to avoid the outputs of the old task to be mixed with the new task. +type ActionTaskOutput struct { + ID int64 + TaskID int64 `xorm:"INDEX UNIQUE(task_id_output_key)"` + OutputKey string `xorm:"VARCHAR(255) UNIQUE(task_id_output_key)"` + OutputValue string `xorm:"MEDIUMTEXT"` +} + +// FindTaskOutputByTaskID returns the outputs of the task. +func FindTaskOutputByTaskID(ctx context.Context, taskID int64) ([]*ActionTaskOutput, error) { + var outputs []*ActionTaskOutput + return outputs, db.GetEngine(ctx).Where("task_id=?", taskID).Find(&outputs) +} + +// FindTaskOutputKeyByTaskID returns the keys of the outputs of the task. +func FindTaskOutputKeyByTaskID(ctx context.Context, taskID int64) ([]string, error) { + var keys []string + return keys, db.GetEngine(ctx).Table(ActionTaskOutput{}).Where("task_id=?", taskID).Cols("output_key").Find(&keys) +} + +// InsertTaskOutputIfNotExist inserts a new task output if it does not exist. +func InsertTaskOutputIfNotExist(ctx context.Context, taskID int64, key, value string) error { + return db.WithTx(ctx, func(ctx context.Context) error { + sess := db.GetEngine(ctx) + if exist, err := sess.Exist(&ActionTaskOutput{TaskID: taskID, OutputKey: key}); err != nil { + return err + } else if exist { + return nil + } + _, err := sess.Insert(&ActionTaskOutput{ + TaskID: taskID, + OutputKey: key, + OutputValue: value, + }) + return err + }) +} diff --git a/models/db/list_test.go b/models/db/list_test.go index 6b9bebd64bd4..b923dc9c22da 100644 --- a/models/db/list_test.go +++ b/models/db/list_test.go @@ -40,7 +40,7 @@ func TestFind(t *testing.T) { var repoUnits []repo_model.RepoUnit err = db.Find(db.DefaultContext, &opts, &repoUnits) assert.NoError(t, err) - assert.EqualValues(t, repoUnitCount, len(repoUnits)) + assert.Len(t, repoUnits, repoUnitCount) cnt, err := db.Count(db.DefaultContext, &opts, new(repo_model.RepoUnit)) assert.NoError(t, err) diff --git a/models/git/branches_test.go b/models/git/branches_test.go index b7df7f243e33..5d18d9525ef8 100644 --- a/models/git/branches_test.go +++ b/models/git/branches_test.go @@ -88,12 +88,12 @@ func TestFindRenamedBranch(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) branch, exist, err := git_model.FindRenamedBranch(db.DefaultContext, 1, "dev") assert.NoError(t, err) - assert.Equal(t, true, exist) + assert.True(t, exist) assert.Equal(t, "master", branch.To) _, exist, err = git_model.FindRenamedBranch(db.DefaultContext, 1, "unknow") assert.NoError(t, err) - assert.Equal(t, false, exist) + assert.False(t, exist) } func TestRenameBranch(t *testing.T) { @@ -115,7 +115,7 @@ func TestRenameBranch(t *testing.T) { return nil })) - assert.Equal(t, true, _isDefault) + assert.True(t, _isDefault) repo1 = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) assert.Equal(t, "main", repo1.DefaultBranch) diff --git a/models/issues/comment_list.go b/models/issues/comment_list.go index 0411d44531ee..477337443ddf 100644 --- a/models/issues/comment_list.go +++ b/models/issues/comment_list.go @@ -56,7 +56,7 @@ func (comments CommentList) getLabelIDs() []int64 { return ids.Values() } -func (comments CommentList) loadLabels(ctx context.Context) error { //nolint +func (comments CommentList) loadLabels(ctx context.Context) error { if len(comments) == 0 { return nil } @@ -415,7 +415,7 @@ func (comments CommentList) getReviewIDs() []int64 { return ids.Values() } -func (comments CommentList) loadReviews(ctx context.Context) error { //nolint +func (comments CommentList) loadReviews(ctx context.Context) error { if len(comments) == 0 { return nil } @@ -453,6 +453,14 @@ func (comments CommentList) loadReviews(ctx context.Context) error { //nolint for _, comment := range comments { comment.Review = reviews[comment.ReviewID] + + // If the comment dismisses a review, we need to load the reviewer to show whose review has been dismissed. + // Otherwise, the reviewer is the poster of the comment, so we don't need to load it. + if comment.Type == CommentTypeDismissReview { + if err := comment.Review.LoadReviewer(ctx); err != nil { + return err + } + } } return nil } diff --git a/models/issues/pull_test.go b/models/issues/pull_test.go index bcd33329eb9b..8d99b2667ca6 100644 --- a/models/issues/pull_test.go +++ b/models/issues/pull_test.go @@ -109,11 +109,11 @@ func TestHasUnmergedPullRequestsByHeadInfo(t *testing.T) { exist, err := issues_model.HasUnmergedPullRequestsByHeadInfo(db.DefaultContext, 1, "branch2") assert.NoError(t, err) - assert.Equal(t, true, exist) + assert.True(t, exist) exist, err = issues_model.HasUnmergedPullRequestsByHeadInfo(db.DefaultContext, 1, "not_exist_branch") assert.NoError(t, err) - assert.Equal(t, false, exist) + assert.False(t, exist) } func TestGetUnmergedPullRequestsByHeadInfo(t *testing.T) { diff --git a/models/issues/tracked_time_test.go b/models/issues/tracked_time_test.go index becfd79d41b0..99b977cca526 100644 --- a/models/issues/tracked_time_test.go +++ b/models/issues/tracked_time_test.go @@ -35,7 +35,7 @@ func TestAddTime(t *testing.T) { assert.Equal(t, int64(3661), tt.Time) comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{Type: issues_model.CommentTypeAddTimeManual, PosterID: 3, IssueID: 1}) - assert.Equal(t, comment.Content, "1 hour 1 minute") + assert.Equal(t, "1 hour 1 minute", comment.Content) } func TestGetTrackedTimes(t *testing.T) { diff --git a/models/migrations/base/testlogger.go b/models/migrations/base/testlogger.go index 7cbf4602be00..80e672952a64 100644 --- a/models/migrations/base/testlogger.go +++ b/models/migrations/base/testlogger.go @@ -165,11 +165,6 @@ func (log *TestLogger) Init(config string) error { return nil } -// Content returns the content accumulated in the content provider -func (log *TestLogger) Content() (string, error) { - return "", fmt.Errorf("not supported") -} - // Flush when log should be flushed func (log *TestLogger) Flush() { } diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 42806a808f79..9de5931d7146 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -485,6 +485,8 @@ var migrations = []Migration{ NewMigration("Fix incorrect admin team unit access mode", v1_20.FixIncorrectAdminTeamUnitAccessMode), // v253 -> v254 NewMigration("Fix ExternalTracker and ExternalWiki accessMode in owner and admin team", v1_20.FixExternalTrackerAndExternalWikiAccessModeInOwnerAndAdminTeam), + // v254 -> v255 + NewMigration("Add ActionTaskOutput table", v1_20.AddActionTaskOutputTable), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v1_20/v254.go b/models/migrations/v1_20/v254.go new file mode 100644 index 000000000000..1e26979a5b2a --- /dev/null +++ b/models/migrations/v1_20/v254.go @@ -0,0 +1,18 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package v1_20 //nolint + +import ( + "xorm.io/xorm" +) + +func AddActionTaskOutputTable(x *xorm.Engine) error { + type ActionTaskOutput struct { + ID int64 + TaskID int64 `xorm:"INDEX UNIQUE(task_id_output_key)"` + OutputKey string `xorm:"VARCHAR(255) UNIQUE(task_id_output_key)"` + OutputValue string `xorm:"MEDIUMTEXT"` + } + return x.Sync(new(ActionTaskOutput)) +} diff --git a/models/unittest/consistency.go b/models/unittest/consistency.go index 17ff75c089f1..41798c62536f 100644 --- a/models/unittest/consistency.go +++ b/models/unittest/consistency.go @@ -152,7 +152,7 @@ func init() { Query() assert.NoError(t, err) - assert.EqualValues(t, label.int("NumIssues"), len(issueLabels), "Unexpected number of issue for label id: %d", label.int("ID")) + assert.Len(t, issueLabels, label.int("NumIssues"), "Unexpected number of issue for label id: %d", label.int("ID")) issueIDs := make([]int, len(issueLabels)) for i, issueLabel := range issueLabels { diff --git a/models/user/user.go b/models/user/user.go index 5f152780bff0..053d6680cdde 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -62,7 +62,7 @@ const ( EmailNotificationsOnMention = "onmention" // EmailNotificationsDisabled indicates that the user would not like to be notified via email. EmailNotificationsDisabled = "disabled" - // EmailNotificationsEnabled indicates that the user would like to receive all email notifications and your own + // EmailNotificationsAndYourOwn indicates that the user would like to receive all email notifications and your own EmailNotificationsAndYourOwn = "andyourown" ) diff --git a/models/user/user_test.go b/models/user/user_test.go index c2314d5c0388..cbfcd15463a8 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -36,10 +36,10 @@ func TestGetUserEmailsByNames(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) // ignore none active user email - assert.Equal(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user9"})) - assert.Equal(t, []string{"user8@example.com", "user5@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user5"})) + assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user9"})) + assert.ElementsMatch(t, []string{"user8@example.com", "user5@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user5"})) - assert.Equal(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user7"})) + assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user7"})) } func TestCanCreateOrganization(t *testing.T) { diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index f37f4f2878af..d560b7718f27 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -29,6 +29,14 @@ func init() { } } +func IsWorkflow(path string) bool { + if (!strings.HasSuffix(path, ".yaml")) && (!strings.HasSuffix(path, ".yml")) { + return false + } + + return strings.HasPrefix(path, ".gitea/workflows") || strings.HasPrefix(path, ".github/workflows") +} + func ListWorkflows(commit *git.Commit) (git.Entries, error) { tree, err := commit.SubTree(".gitea/workflows") if _, ok := err.(git.ErrNotExist); ok { diff --git a/modules/avatar/avatar.go b/modules/avatar/avatar.go index 9ee926b059ba..c166f144042a 100644 --- a/modules/avatar/avatar.go +++ b/modules/avatar/avatar.go @@ -18,6 +18,8 @@ import ( "github.com/nfnt/resize" "github.com/oliamb/cutter" + + _ "golang.org/x/image/webp" // for processing webp images ) // AvatarSize returns avatar's size diff --git a/modules/charset/escape_stream.go b/modules/charset/escape_stream.go index 89bf36edce1a..03d4cfc0c17b 100644 --- a/modules/charset/escape_stream.go +++ b/modules/charset/escape_stream.go @@ -47,7 +47,9 @@ func (e *escapeStreamer) EscapeStatus() *EscapeStatus { // Text tells the next streamer there is a text func (e *escapeStreamer) Text(data string) error { sb := &strings.Builder{} - pos, until, next := 0, 0, 0 + var until int + var next int + pos := 0 if len(data) > len(UTF8BOM) && data[:len(UTF8BOM)] == string(UTF8BOM) { _, _ = sb.WriteString(data[:len(UTF8BOM)]) pos = len(UTF8BOM) diff --git a/modules/context/auth.go b/modules/context/auth.go deleted file mode 100644 index 5e071b4fabca..000000000000 --- a/modules/context/auth.go +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2014 The Gogs Authors. All rights reserved. -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package context - -import ( - "net/http" - "strings" - - "code.gitea.io/gitea/models/auth" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/web/middleware" -) - -// ToggleOptions contains required or check options -type ToggleOptions struct { - SignInRequired bool - SignOutRequired bool - AdminRequired bool - DisableCSRF bool -} - -// Toggle returns toggle options as middleware -func Toggle(options *ToggleOptions) func(ctx *Context) { - return func(ctx *Context) { - // Check prohibit login users. - if ctx.IsSigned { - if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm { - ctx.Data["Title"] = ctx.Tr("auth.active_your_account") - ctx.HTML(http.StatusOK, "user/auth/activate") - return - } - if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin { - log.Info("Failed authentication attempt for %s from %s", ctx.Doer.Name, ctx.RemoteAddr()) - ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") - ctx.HTML(http.StatusOK, "user/auth/prohibit_login") - return - } - - if ctx.Doer.MustChangePassword { - if ctx.Req.URL.Path != "/user/settings/change_password" { - if strings.HasPrefix(ctx.Req.UserAgent(), "git") { - ctx.Error(http.StatusUnauthorized, ctx.Tr("auth.must_change_password")) - return - } - ctx.Data["Title"] = ctx.Tr("auth.must_change_password") - ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password" - if ctx.Req.URL.Path != "/user/events" { - middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI()) - } - ctx.Redirect(setting.AppSubURL + "/user/settings/change_password") - return - } - } else if ctx.Req.URL.Path == "/user/settings/change_password" { - // make sure that the form cannot be accessed by users who don't need this - ctx.Redirect(setting.AppSubURL + "/") - return - } - } - - // Redirect to dashboard if user tries to visit any non-login page. - if options.SignOutRequired && ctx.IsSigned && ctx.Req.URL.RequestURI() != "/" { - ctx.Redirect(setting.AppSubURL + "/") - return - } - - if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == "POST" { - ctx.Csrf.Validate(ctx) - if ctx.Written() { - return - } - } - - if options.SignInRequired { - if !ctx.IsSigned { - if ctx.Req.URL.Path != "/user/events" { - middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI()) - } - ctx.Redirect(setting.AppSubURL + "/user/login") - return - } else if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm { - ctx.Data["Title"] = ctx.Tr("auth.active_your_account") - ctx.HTML(http.StatusOK, "user/auth/activate") - return - } - } - - // Redirect to log in page if auto-signin info is provided and has not signed in. - if !options.SignOutRequired && !ctx.IsSigned && - len(ctx.GetSiteCookie(setting.CookieUserName)) > 0 { - if ctx.Req.URL.Path != "/user/events" { - middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI()) - } - ctx.Redirect(setting.AppSubURL + "/user/login") - return - } - - if options.AdminRequired { - if !ctx.Doer.IsAdmin { - ctx.Error(http.StatusForbidden) - return - } - ctx.Data["PageIsAdmin"] = true - } - } -} - -// ToggleAPI returns toggle options as middleware -func ToggleAPI(options *ToggleOptions) func(ctx *APIContext) { - return func(ctx *APIContext) { - // Check prohibit login users. - if ctx.IsSigned { - if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm { - ctx.Data["Title"] = ctx.Tr("auth.active_your_account") - ctx.JSON(http.StatusForbidden, map[string]string{ - "message": "This account is not activated.", - }) - return - } - if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin { - log.Info("Failed authentication attempt for %s from %s", ctx.Doer.Name, ctx.RemoteAddr()) - ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") - ctx.JSON(http.StatusForbidden, map[string]string{ - "message": "This account is prohibited from signing in, please contact your site administrator.", - }) - return - } - - if ctx.Doer.MustChangePassword { - ctx.JSON(http.StatusForbidden, map[string]string{ - "message": "You must change your password. Change it at: " + setting.AppURL + "/user/change_password", - }) - return - } - } - - // Redirect to dashboard if user tries to visit any non-login page. - if options.SignOutRequired && ctx.IsSigned && ctx.Req.URL.RequestURI() != "/" { - ctx.Redirect(setting.AppSubURL + "/") - return - } - - if options.SignInRequired { - if !ctx.IsSigned { - // Restrict API calls with error message. - ctx.JSON(http.StatusForbidden, map[string]string{ - "message": "Only signed in user is allowed to call APIs.", - }) - return - } else if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm { - ctx.Data["Title"] = ctx.Tr("auth.active_your_account") - ctx.HTML(http.StatusOK, "user/auth/activate") - return - } - if ctx.IsSigned && ctx.IsBasicAuth { - if skip, ok := ctx.Data["SkipLocalTwoFA"]; ok && skip.(bool) { - return // Skip 2FA - } - twofa, err := auth.GetTwoFactorByUID(ctx.Doer.ID) - if err != nil { - if auth.IsErrTwoFactorNotEnrolled(err) { - return // No 2FA enrollment for this user - } - ctx.InternalServerError(err) - return - } - otpHeader := ctx.Req.Header.Get("X-Gitea-OTP") - ok, err := twofa.ValidateTOTP(otpHeader) - if err != nil { - ctx.InternalServerError(err) - return - } - if !ok { - ctx.JSON(http.StatusForbidden, map[string]string{ - "message": "Only signed in user is allowed to call APIs.", - }) - return - } - } - } - - if options.AdminRequired { - if !ctx.Doer.IsAdmin { - ctx.JSON(http.StatusForbidden, map[string]string{ - "message": "You have no permission to request for this.", - }) - return - } - ctx.Data["PageIsAdmin"] = true - } - } -} diff --git a/modules/context/context.go b/modules/context/context.go index 7e986b0119e2..f262c7cce7cb 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -741,8 +741,7 @@ func Contexter(ctx context.Context) func(next http.Handler) http.Handler { ctx.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton ctx.Data["ShowMilestonesDashboardPage"] = setting.Service.ShowMilestonesDashboardPage - ctx.Data["ShowFooterBranding"] = setting.ShowFooterBranding - ctx.Data["ShowFooterVersion"] = setting.ShowFooterVersion + ctx.Data["ShowFooterVersion"] = setting.Other.ShowFooterVersion ctx.Data["EnableSwagger"] = setting.API.EnableSwagger ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn diff --git a/modules/context/repo.go b/modules/context/repo.go index 1736de2e4bad..6b811054c231 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -452,7 +452,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { userName := ctx.Params(":username") repoName := ctx.Params(":reponame") repoName = strings.TrimSuffix(repoName, ".git") - if setting.EnableFeed { + if setting.Other.EnableFeed { repoName = strings.TrimSuffix(repoName, ".rss") repoName = strings.TrimSuffix(repoName, ".atom") } @@ -540,7 +540,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { ctx.Data["RepoLink"] = ctx.Repo.RepoLink ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name - if setting.EnableFeed { + if setting.Other.EnableFeed { ctx.Data["EnableFeed"] = true ctx.Data["FeedURL"] = ctx.Repo.RepoLink } diff --git a/modules/git/repo_attribute_test.go b/modules/git/repo_attribute_test.go index f88ae9540786..350b69dd251b 100644 --- a/modules/git/repo_attribute_test.go +++ b/modules/git/repo_attribute_test.go @@ -19,7 +19,7 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) { n, err := wr.Write([]byte(testStr)) - assert.Equal(t, n, len(testStr)) + assert.Len(t, testStr, n) assert.NoError(t, err) select { case attr := <-wr.ReadAttribute(): @@ -32,7 +32,7 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) { // Write a second attribute again n, err = wr.Write([]byte(testStr)) - assert.Equal(t, n, len(testStr)) + assert.Len(t, testStr, n) assert.NoError(t, err) select { diff --git a/modules/git/repo_commit_test.go b/modules/git/repo_commit_test.go index 729fb0ba103a..fee145e924e9 100644 --- a/modules/git/repo_commit_test.go +++ b/modules/git/repo_commit_test.go @@ -97,6 +97,6 @@ func TestRepository_CommitsBetweenIDs(t *testing.T) { for i, c := range cases { commits, err := bareRepo1.CommitsBetweenIDs(c.NewID, c.OldID) assert.NoError(t, err) - assert.Equal(t, c.ExpectedCommits, len(commits), "case %d", i) + assert.Len(t, commits, c.ExpectedCommits, "case %d", i) } } diff --git a/modules/git/repo_tag_test.go b/modules/git/repo_tag_test.go index 8ccfec3eb23e..fb6a7cdd1add 100644 --- a/modules/git/repo_tag_test.go +++ b/modules/git/repo_tag_test.go @@ -26,7 +26,7 @@ func TestRepository_GetTags(t *testing.T) { return } assert.Len(t, tags, 2) - assert.Equal(t, len(tags), total) + assert.Len(t, tags, total) assert.EqualValues(t, "signed-tag", tags[0].Name) assert.EqualValues(t, "36f97d9a96457e2bab511db30fe2db03893ebc64", tags[0].ID.String()) assert.EqualValues(t, "tag", tags[0].Type) diff --git a/modules/indexer/code/indexer_test.go b/modules/indexer/code/indexer_test.go index c353d5b6880f..52f7e76e413f 100644 --- a/modules/indexer/code/indexer_test.go +++ b/modules/indexer/code/indexer_test.go @@ -69,7 +69,7 @@ func testIndexer(name string, t *testing.T, indexer Indexer) { t.Run(kw.Keyword, func(t *testing.T) { total, res, langs, err := indexer.Search(context.TODO(), kw.RepoIDs, "", kw.Keyword, 1, 10, false) assert.NoError(t, err) - assert.EqualValues(t, len(kw.IDs), total) + assert.Len(t, kw.IDs, int(total)) assert.Len(t, langs, kw.Langs) ids := make([]int64, 0, len(res)) diff --git a/modules/lfs/http_client_test.go b/modules/lfs/http_client_test.go index a8c7e379f66f..cb71b9008a9d 100644 --- a/modules/lfs/http_client_test.go +++ b/modules/lfs/http_client_test.go @@ -162,7 +162,7 @@ func TestHTTPClientDownload(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "download", batchRequest.Operation) - assert.Equal(t, 1, len(batchRequest.Objects)) + assert.Len(t, batchRequest.Objects, 1) assert.Equal(t, p.Oid, batchRequest.Objects[0].Oid) assert.Equal(t, p.Size, batchRequest.Objects[0].Size) @@ -269,7 +269,7 @@ func TestHTTPClientUpload(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "upload", batchRequest.Operation) - assert.Equal(t, 1, len(batchRequest.Objects)) + assert.Len(t, batchRequest.Objects, 1) assert.Equal(t, p.Oid, batchRequest.Objects[0].Oid) assert.Equal(t, p.Size, batchRequest.Objects[0].Size) diff --git a/modules/log/buffer.go b/modules/log/buffer.go deleted file mode 100644 index 1eee2465f14e..000000000000 --- a/modules/log/buffer.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package log - -import ( - "bytes" - "sync" -) - -type bufferWriteCloser struct { - mu sync.Mutex - buffer bytes.Buffer -} - -func (b *bufferWriteCloser) Write(p []byte) (int, error) { - b.mu.Lock() - defer b.mu.Unlock() - return b.buffer.Write(p) -} - -func (b *bufferWriteCloser) Close() error { - return nil -} - -func (b *bufferWriteCloser) String() string { - b.mu.Lock() - defer b.mu.Unlock() - return b.buffer.String() -} - -// BufferLogger implements LoggerProvider and writes messages in a buffer. -type BufferLogger struct { - WriterLogger -} - -// NewBufferLogger create BufferLogger returning as LoggerProvider. -func NewBufferLogger() LoggerProvider { - log := &BufferLogger{} - log.NewWriterLogger(&bufferWriteCloser{}) - return log -} - -// Init inits connection writer -func (log *BufferLogger) Init(string) error { - log.NewWriterLogger(log.out) - return nil -} - -// Content returns the content accumulated in the content provider -func (log *BufferLogger) Content() (string, error) { - return log.out.(*bufferWriteCloser).String(), nil -} - -// Flush when log should be flushed -func (log *BufferLogger) Flush() { -} - -// ReleaseReopen does nothing -func (log *BufferLogger) ReleaseReopen() error { - return nil -} - -// GetName returns the default name for this implementation -func (log *BufferLogger) GetName() string { - return "buffer" -} - -func init() { - Register("buffer", NewBufferLogger) -} diff --git a/modules/log/buffer_test.go b/modules/log/buffer_test.go deleted file mode 100644 index 1c390060509e..000000000000 --- a/modules/log/buffer_test.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package log - -import ( - "fmt" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestBufferLogger(t *testing.T) { - logger := NewBufferLogger() - bufferLogger := logger.(*BufferLogger) - assert.NotNil(t, bufferLogger) - - err := logger.Init("") - assert.NoError(t, err) - - location, _ := time.LoadLocation("EST") - date := time.Date(2019, time.January, 13, 22, 3, 30, 15, location) - - msg := "TEST MSG" - event := Event{ - level: INFO, - msg: msg, - caller: "CALLER", - filename: "FULL/FILENAME", - line: 1, - time: date, - } - logger.LogEvent(&event) - content, err := bufferLogger.Content() - assert.NoError(t, err) - assert.Contains(t, content, msg) - logger.Close() -} - -func TestBufferLoggerContent(t *testing.T) { - level := INFO - logger := NewLogger(0, "console", "console", fmt.Sprintf(`{"level":"%s"}`, level.String())) - - logger.SetLogger("buffer", "buffer", "{}") - defer logger.DelLogger("buffer") - - msg := "A UNIQUE MESSAGE" - Error(msg) - - found := false - for i := 0; i < 30000; i++ { - content, err := logger.GetLoggerProviderContent("buffer") - assert.NoError(t, err) - if strings.Contains(content, msg) { - found = true - break - } - time.Sleep(1 * time.Millisecond) - } - assert.True(t, found) -} diff --git a/modules/log/conn.go b/modules/log/conn.go index 39d5234214d7..b21a744037bc 100644 --- a/modules/log/conn.go +++ b/modules/log/conn.go @@ -118,11 +118,6 @@ func (log *ConnLogger) Init(jsonconfig string) error { return nil } -// Content returns the content accumulated in the content provider -func (log *ConnLogger) Content() (string, error) { - return "", fmt.Errorf("not supported") -} - // Flush does nothing for this implementation func (log *ConnLogger) Flush() { } diff --git a/modules/log/console.go b/modules/log/console.go index 8764d984ac10..ce0415d1390c 100644 --- a/modules/log/console.go +++ b/modules/log/console.go @@ -65,11 +65,6 @@ func (log *ConsoleLogger) Init(config string) error { return nil } -// Content returns the content accumulated in the content provider -func (log *ConsoleLogger) Content() (string, error) { - return "", fmt.Errorf("not supported") -} - // Flush when log should be flushed func (log *ConsoleLogger) Flush() { } diff --git a/modules/log/event.go b/modules/log/event.go index d23e56807b41..5729e022bfe4 100644 --- a/modules/log/event.go +++ b/modules/log/event.go @@ -247,12 +247,6 @@ func (m *MultiChannelledLog) GetEventLogger(name string) EventLogger { return m.loggers[name] } -// GetEventProvider returns a sub logger provider content from this MultiChannelledLog -func (m *MultiChannelledLog) GetLoggerProviderContent(name string) (string, error) { - channelledLogger := m.GetEventLogger(name).(*ChannelledLog) - return channelledLogger.loggerProvider.Content() -} - // GetEventLoggerNames returns a list of names func (m *MultiChannelledLog) GetEventLoggerNames() []string { m.rwmutex.RLock() @@ -460,3 +454,7 @@ func (m *MultiChannelledLog) ResetLevel() Level { func (m *MultiChannelledLog) GetName() string { return m.name } + +func (e *Event) GetMsg() string { + return e.msg +} diff --git a/modules/log/file.go b/modules/log/file.go index 55147ffac829..2ec6de450c7e 100644 --- a/modules/log/file.go +++ b/modules/log/file.go @@ -253,15 +253,6 @@ func (log *FileLogger) deleteOldLog() { }) } -// Content returns the content accumulated in the content provider -func (log *FileLogger) Content() (string, error) { - b, err := os.ReadFile(log.Filename) - if err != nil { - return "", err - } - return string(b), nil -} - // Flush flush file logger. // there are no buffering messages in file logger in memory. // flush file means sync file from disk. diff --git a/modules/log/file_test.go b/modules/log/file_test.go index f1ca23d342bf..34f74598067c 100644 --- a/modules/log/file_test.go +++ b/modules/log/file_test.go @@ -51,7 +51,7 @@ func TestFileLogger(t *testing.T) { fileLogger := NewFileLogger() realFileLogger, ok := fileLogger.(*FileLogger) - assert.Equal(t, true, ok) + assert.True(t, ok) location, _ := time.LoadLocation("EST") diff --git a/modules/log/provider.go b/modules/log/provider.go index 490c3fa71b18..b5058139d705 100644 --- a/modules/log/provider.go +++ b/modules/log/provider.go @@ -6,7 +6,6 @@ package log // LoggerProvider represents behaviors of a logger provider. type LoggerProvider interface { Init(config string) error - Content() (string, error) EventLogger } diff --git a/modules/log/smtp.go b/modules/log/smtp.go index e385020c67c1..4e896496d75f 100644 --- a/modules/log/smtp.go +++ b/modules/log/smtp.go @@ -95,11 +95,6 @@ func (log *SMTPLogger) sendMail(p []byte) (int, error) { ) } -// Content returns the content accumulated in the content provider -func (log *SMTPLogger) Content() (string, error) { - return "", fmt.Errorf("not supported") -} - // Flush when log should be flushed func (log *SMTPLogger) Flush() { } diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index f8b84485c06d..cb1216ec946e 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -593,5 +593,5 @@ func TestIssue18471(t *testing.T) { }, strings.NewReader(data), &res) assert.NoError(t, err) - assert.Equal(t, res.String(), "783b039...da951ce") + assert.Equal(t, "783b039...da951ce", res.String()) } diff --git a/modules/packages/container/metadata_test.go b/modules/packages/container/metadata_test.go index 48809f4c99c6..195b3982cb7b 100644 --- a/modules/packages/container/metadata_test.go +++ b/modules/packages/container/metadata_test.go @@ -33,7 +33,7 @@ func TestParseImageConfig(t *testing.T) { assert.Equal(t, projectURL, metadata.ProjectURL) assert.Equal(t, repositoryURL, metadata.RepositoryURL) assert.Equal(t, documentationURL, metadata.DocumentationURL) - assert.Equal(t, []string{"do it 1", "do it 2"}, metadata.ImageLayers) + assert.ElementsMatch(t, []string{"do it 1", "do it 2"}, metadata.ImageLayers) assert.Equal( t, map[string]string{ diff --git a/modules/paginator/paginator_test.go b/modules/paginator/paginator_test.go index 41bb30533756..8a56ee5121e8 100644 --- a/modules/paginator/paginator_test.go +++ b/modules/paginator/paginator_test.go @@ -70,19 +70,19 @@ func TestPaginator(t *testing.T) { t.Run("Generate pages", func(t *testing.T) { p := New(0, 10, 1, 0) pages := p.Pages() - assert.Equal(t, 0, len(pages)) + assert.Empty(t, pages) }) t.Run("Only current page", func(t *testing.T) { p := New(0, 10, 1, 1) pages := p.Pages() - assert.Equal(t, 1, len(pages)) + assert.Len(t, pages, 1) assert.Equal(t, 1, pages[0].Num()) assert.True(t, pages[0].IsCurrent()) p = New(1, 10, 1, 1) pages = p.Pages() - assert.Equal(t, 1, len(pages)) + assert.Len(t, pages, 1) assert.Equal(t, 1, pages[0].Num()) assert.True(t, pages[0].IsCurrent()) }) @@ -90,13 +90,13 @@ func TestPaginator(t *testing.T) { t.Run("Total page number is less or equal", func(t *testing.T) { p := New(1, 10, 1, 2) pages := p.Pages() - assert.Equal(t, 1, len(pages)) + assert.Len(t, pages, 1) assert.Equal(t, 1, pages[0].Num()) assert.True(t, pages[0].IsCurrent()) p = New(11, 10, 1, 2) pages = p.Pages() - assert.Equal(t, 2, len(pages)) + assert.Len(t, pages, 2) assert.Equal(t, 1, pages[0].Num()) assert.True(t, pages[0].IsCurrent()) assert.Equal(t, 2, pages[1].Num()) @@ -104,7 +104,7 @@ func TestPaginator(t *testing.T) { p = New(11, 10, 2, 2) pages = p.Pages() - assert.Equal(t, 2, len(pages)) + assert.Len(t, pages, 2) assert.Equal(t, 1, pages[0].Num()) assert.False(t, pages[0].IsCurrent()) assert.Equal(t, 2, pages[1].Num()) @@ -112,7 +112,7 @@ func TestPaginator(t *testing.T) { p = New(25, 10, 2, 3) pages = p.Pages() - assert.Equal(t, 3, len(pages)) + assert.Len(t, pages, 3) assert.Equal(t, 1, pages[0].Num()) assert.False(t, pages[0].IsCurrent()) assert.Equal(t, 2, pages[1].Num()) @@ -125,7 +125,7 @@ func TestPaginator(t *testing.T) { // ... 2 p := New(11, 10, 2, 1) pages := p.Pages() - assert.Equal(t, 2, len(pages)) + assert.Len(t, pages, 2) assert.Equal(t, -1, pages[0].Num()) assert.False(t, pages[0].IsCurrent()) assert.Equal(t, 2, pages[1].Num()) @@ -134,7 +134,7 @@ func TestPaginator(t *testing.T) { // ... 2 3 p = New(21, 10, 2, 2) pages = p.Pages() - assert.Equal(t, 3, len(pages)) + assert.Len(t, pages, 3) assert.Equal(t, -1, pages[0].Num()) assert.False(t, pages[0].IsCurrent()) assert.Equal(t, 2, pages[1].Num()) @@ -145,7 +145,7 @@ func TestPaginator(t *testing.T) { // ... 2 3 4 p = New(31, 10, 3, 3) pages = p.Pages() - assert.Equal(t, 4, len(pages)) + assert.Len(t, pages, 4) assert.Equal(t, -1, pages[0].Num()) assert.False(t, pages[0].IsCurrent()) assert.Equal(t, 2, pages[1].Num()) @@ -158,7 +158,7 @@ func TestPaginator(t *testing.T) { // ... 3 4 5 p = New(41, 10, 4, 3) pages = p.Pages() - assert.Equal(t, 4, len(pages)) + assert.Len(t, pages, 4) assert.Equal(t, -1, pages[0].Num()) assert.False(t, pages[0].IsCurrent()) assert.Equal(t, 3, pages[1].Num()) @@ -171,7 +171,7 @@ func TestPaginator(t *testing.T) { // ... 4 5 6 7 8 9 10 p = New(100, 10, 9, 7) pages = p.Pages() - assert.Equal(t, 8, len(pages)) + assert.Len(t, pages, 8) assert.Equal(t, -1, pages[0].Num()) assert.False(t, pages[0].IsCurrent()) assert.Equal(t, 4, pages[1].Num()) @@ -194,7 +194,7 @@ func TestPaginator(t *testing.T) { // 1 ... p := New(21, 10, 1, 1) pages := p.Pages() - assert.Equal(t, 2, len(pages)) + assert.Len(t, pages, 2) assert.Equal(t, 1, pages[0].Num()) assert.True(t, pages[0].IsCurrent()) assert.Equal(t, -1, pages[1].Num()) @@ -203,7 +203,7 @@ func TestPaginator(t *testing.T) { // 1 2 ... p = New(21, 10, 1, 2) pages = p.Pages() - assert.Equal(t, 3, len(pages)) + assert.Len(t, pages, 3) assert.Equal(t, 1, pages[0].Num()) assert.True(t, pages[0].IsCurrent()) assert.Equal(t, 2, pages[1].Num()) @@ -214,7 +214,7 @@ func TestPaginator(t *testing.T) { // 1 2 3 ... p = New(31, 10, 2, 3) pages = p.Pages() - assert.Equal(t, 4, len(pages)) + assert.Len(t, pages, 4) assert.Equal(t, 1, pages[0].Num()) assert.False(t, pages[0].IsCurrent()) assert.Equal(t, 2, pages[1].Num()) @@ -227,7 +227,7 @@ func TestPaginator(t *testing.T) { // 1 2 3 ... p = New(41, 10, 2, 3) pages = p.Pages() - assert.Equal(t, 4, len(pages)) + assert.Len(t, pages, 4) assert.Equal(t, 1, pages[0].Num()) assert.False(t, pages[0].IsCurrent()) assert.Equal(t, 2, pages[1].Num()) @@ -240,7 +240,7 @@ func TestPaginator(t *testing.T) { // 1 2 3 4 5 6 7 ... p = New(100, 10, 1, 7) pages = p.Pages() - assert.Equal(t, 8, len(pages)) + assert.Len(t, pages, 8) assert.Equal(t, 1, pages[0].Num()) assert.True(t, pages[0].IsCurrent()) assert.Equal(t, 2, pages[1].Num()) @@ -261,7 +261,7 @@ func TestPaginator(t *testing.T) { // 1 2 3 4 5 6 7 ... p = New(100, 10, 2, 7) pages = p.Pages() - assert.Equal(t, 8, len(pages)) + assert.Len(t, pages, 8) assert.Equal(t, 1, pages[0].Num()) assert.False(t, pages[0].IsCurrent()) assert.Equal(t, 2, pages[1].Num()) @@ -284,7 +284,7 @@ func TestPaginator(t *testing.T) { // ... 2 3 ... p := New(35, 10, 2, 2) pages := p.Pages() - assert.Equal(t, 4, len(pages)) + assert.Len(t, pages, 4) assert.Equal(t, -1, pages[0].Num()) assert.False(t, pages[0].IsCurrent()) assert.Equal(t, 2, pages[1].Num()) @@ -297,7 +297,7 @@ func TestPaginator(t *testing.T) { // ... 2 3 4 ... p = New(49, 10, 3, 3) pages = p.Pages() - assert.Equal(t, 5, len(pages)) + assert.Len(t, pages, 5) assert.Equal(t, -1, pages[0].Num()) assert.False(t, pages[0].IsCurrent()) assert.Equal(t, 2, pages[1].Num()) diff --git a/modules/queue/unique_queue_disk_channel_test.go b/modules/queue/unique_queue_disk_channel_test.go index fd76163f4aac..e1a2132dd666 100644 --- a/modules/queue/unique_queue_disk_channel_test.go +++ b/modules/queue/unique_queue_disk_channel_test.go @@ -208,7 +208,7 @@ func TestPersistableChannelUniqueQueue(t *testing.T) { mapLock.Lock() assert.Equal(t, 101, len(executedInitial[name])+len(executedEmpty[name])) - assert.Equal(t, 0, len(hasEmpty[name])) + assert.Empty(t, hasEmpty[name]) mapLock.Unlock() }) close(done) diff --git a/modules/secret/secret_test.go b/modules/secret/secret_test.go index 0a189ecebf91..4b018fddb678 100644 --- a/modules/secret/secret_test.go +++ b/modules/secret/secret_test.go @@ -15,9 +15,9 @@ func TestEncryptDecrypt(t *testing.T) { hex, _ = EncryptSecret("foo", "baz") str, _ = DecryptSecret("foo", hex) - assert.Equal(t, str, "baz") + assert.Equal(t, "baz", str) hex, _ = EncryptSecret("bar", "baz") str, _ = DecryptSecret("foo", hex) - assert.NotEqual(t, str, "baz") + assert.NotEqual(t, "baz", str) } diff --git a/modules/setting/other.go b/modules/setting/other.go index 4fba754a0848..706cb1e3d9b8 100644 --- a/modules/setting/other.go +++ b/modules/setting/other.go @@ -3,20 +3,25 @@ package setting -var ( - // Other settings - ShowFooterBranding bool +import "code.gitea.io/gitea/modules/log" + +type OtherConfig struct { ShowFooterVersion bool ShowFooterTemplateLoadTime bool EnableFeed bool EnableSitemap bool -) +} + +var Other = OtherConfig{ + ShowFooterVersion: true, + ShowFooterTemplateLoadTime: true, + EnableSitemap: true, + EnableFeed: true, +} func loadOtherFrom(rootCfg ConfigProvider) { sec := rootCfg.Section("other") - ShowFooterBranding = sec.Key("SHOW_FOOTER_BRANDING").MustBool(false) - ShowFooterVersion = sec.Key("SHOW_FOOTER_VERSION").MustBool(true) - ShowFooterTemplateLoadTime = sec.Key("SHOW_FOOTER_TEMPLATE_LOAD_TIME").MustBool(true) - EnableSitemap = sec.Key("ENABLE_SITEMAP").MustBool(true) - EnableFeed = sec.Key("ENABLE_FEED").MustBool(true) + if err := sec.MapTo(&Other); err != nil { + log.Fatal("Failed to map [other] settings: %v", err) + } } diff --git a/modules/storage/local_test.go b/modules/storage/local_test.go index 9649761a0f96..0873f8e14ef0 100644 --- a/modules/storage/local_test.go +++ b/modules/storage/local_test.go @@ -89,6 +89,6 @@ func TestLocalStorageIterator(t *testing.T) { return nil }) assert.NoError(t, err) - assert.Equal(t, count, len(expected)) + assert.Len(t, expected, count) } } diff --git a/modules/templates/base.go b/modules/templates/base.go index e95ce31cfcab..4254a569764e 100644 --- a/modules/templates/base.go +++ b/modules/templates/base.go @@ -33,8 +33,7 @@ func BaseVars() Vars { "ShowRegistrationButton": setting.Service.ShowRegistrationButton, "ShowMilestonesDashboardPage": setting.Service.ShowMilestonesDashboardPage, - "ShowFooterBranding": setting.ShowFooterBranding, - "ShowFooterVersion": setting.ShowFooterVersion, + "ShowFooterVersion": setting.Other.ShowFooterVersion, "DisableDownloadSourceArchives": setting.Repository.DisableDownloadSourceArchives, "EnableSwagger": setting.API.EnableSwagger, diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 24687a460613..b7bef205603f 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -15,7 +15,6 @@ import ( "mime" "net/url" "path/filepath" - "reflect" "regexp" "strings" "time" @@ -68,12 +67,17 @@ func NewFuncMap() []template.FuncMap { "PathEscape": url.PathEscape, "PathEscapeSegments": util.PathEscapeSegments, + // utils + "StringUtils": NewStringUtils, + "SliceUtils": NewSliceUtils, + // ----------------------------------------------------------------- // string / json + // TODO: move string helper functions to StringUtils "Join": strings.Join, "DotEscape": DotEscape, - "HasPrefix": strings.HasPrefix, "EllipsisString": base.EllipsisString, + "DumpVar": dumpVar, "Json": func(in interface{}) string { out, err := json.Marshal(in) @@ -135,6 +139,7 @@ func NewFuncMap() []template.FuncMap { "CountFmt": base.FormatNumberSI, "TimeSince": timeutil.TimeSince, "TimeSinceUnix": timeutil.TimeSinceUnix, + "DateTime": timeutil.DateTime, "Sec2Time": util.SecToTime, "DateFmtLong": func(t time.Time) string { return t.Format(time.RFC3339) @@ -143,35 +148,6 @@ func NewFuncMap() []template.FuncMap { return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms" }, - // ----------------------------------------------------------------- - // slice - "containGeneric": func(arr, v interface{}) bool { - arrV := reflect.ValueOf(arr) - if arrV.Kind() == reflect.String && reflect.ValueOf(v).Kind() == reflect.String { - return strings.Contains(arr.(string), v.(string)) - } - if arrV.Kind() == reflect.Slice { - for i := 0; i < arrV.Len(); i++ { - iV := arrV.Index(i) - if !iV.CanInterface() { - continue - } - if iV.Interface() == v { - return true - } - } - } - return false - }, - "contain": func(s []int64, id int64) bool { - for i := 0; i < len(s); i++ { - if s[i] == id { - return true - } - } - return false - }, - // ----------------------------------------------------------------- // setting "AppName": func() string { @@ -206,7 +182,7 @@ func NewFuncMap() []template.FuncMap { return setting.UI.DefaultShowFullName }, "ShowFooterTemplateLoadTime": func() bool { - return setting.ShowFooterTemplateLoadTime + return setting.Other.ShowFooterTemplateLoadTime }, "AllowedReactions": func() []string { return setting.UI.Reactions diff --git a/modules/templates/util.go b/modules/templates/util.go deleted file mode 100644 index 13f3a56808a2..000000000000 --- a/modules/templates/util.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package templates - -import ( - "fmt" - "reflect" -) - -func dictMerge(base map[string]any, arg any) bool { - if arg == nil { - return true - } - rv := reflect.ValueOf(arg) - if rv.Kind() == reflect.Map { - for _, k := range rv.MapKeys() { - base[k.String()] = rv.MapIndex(k).Interface() - } - return true - } - return false -} - -// dict is a helper function for creating a map[string]any from a list of key-value pairs. -// If the key is dot ".", the value is merged into the base map, just like Golang template's dot syntax: dot means current -// The dot syntax is highly discouraged because it might cause unclear key conflicts. It's always good to use explicit keys. -func dict(args ...any) (map[string]any, error) { - if len(args)%2 != 0 { - return nil, fmt.Errorf("invalid dict constructor syntax: must have key-value pairs") - } - m := make(map[string]any, len(args)/2) - for i := 0; i < len(args); i += 2 { - key, ok := args[i].(string) - if !ok { - return nil, fmt.Errorf("invalid dict constructor syntax: unable to merge args[%d]", i) - } - if key == "." { - if ok = dictMerge(m, args[i+1]); !ok { - return nil, fmt.Errorf("invalid dict constructor syntax: dot arg[%d] must be followed by a dict", i) - } - } else { - m[key] = args[i+1] - } - } - return m, nil -} diff --git a/modules/templates/util_dict.go b/modules/templates/util_dict.go new file mode 100644 index 000000000000..79c307b68d9a --- /dev/null +++ b/modules/templates/util_dict.go @@ -0,0 +1,121 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package templates + +import ( + "fmt" + "html" + "html/template" + "reflect" + + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/setting" +) + +func dictMerge(base map[string]any, arg any) bool { + if arg == nil { + return true + } + rv := reflect.ValueOf(arg) + if rv.Kind() == reflect.Map { + for _, k := range rv.MapKeys() { + base[k.String()] = rv.MapIndex(k).Interface() + } + return true + } + return false +} + +// dict is a helper function for creating a map[string]any from a list of key-value pairs. +// If the key is dot ".", the value is merged into the base map, just like Golang template's dot syntax: dot means current +// The dot syntax is highly discouraged because it might cause unclear key conflicts. It's always good to use explicit keys. +func dict(args ...any) (map[string]any, error) { + if len(args)%2 != 0 { + return nil, fmt.Errorf("invalid dict constructor syntax: must have key-value pairs") + } + m := make(map[string]any, len(args)/2) + for i := 0; i < len(args); i += 2 { + key, ok := args[i].(string) + if !ok { + return nil, fmt.Errorf("invalid dict constructor syntax: unable to merge args[%d]", i) + } + if key == "." { + if ok = dictMerge(m, args[i+1]); !ok { + return nil, fmt.Errorf("invalid dict constructor syntax: dot arg[%d] must be followed by a dict", i) + } + } else { + m[key] = args[i+1] + } + } + return m, nil +} + +func dumpVarMarshalable(v any, dumped map[uintptr]bool) (ret any, ok bool) { + if v == nil { + return nil, true + } + e := reflect.ValueOf(v) + for e.Kind() == reflect.Pointer { + e = e.Elem() + } + if e.CanAddr() { + addr := e.UnsafeAddr() + if dumped[addr] { + return "[dumped]", false + } + dumped[addr] = true + defer delete(dumped, addr) + } + switch e.Kind() { + case reflect.Bool, reflect.String, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Float32, reflect.Float64: + return e.Interface(), true + case reflect.Struct: + m := map[string]any{} + for i := 0; i < e.NumField(); i++ { + k := e.Type().Field(i).Name + if !e.Type().Field(i).IsExported() { + continue + } + v := e.Field(i).Interface() + m[k], _ = dumpVarMarshalable(v, dumped) + } + return m, true + case reflect.Map: + m := map[string]any{} + for _, k := range e.MapKeys() { + m[k.String()], _ = dumpVarMarshalable(e.MapIndex(k).Interface(), dumped) + } + return m, true + case reflect.Array, reflect.Slice: + var m []any + for i := 0; i < e.Len(); i++ { + v, _ := dumpVarMarshalable(e.Index(i).Interface(), dumped) + m = append(m, v) + } + return m, true + default: + return "[" + reflect.TypeOf(v).String() + "]", false + } +} + +// dumpVar helps to dump a variable in a template, to help debugging and development. +func dumpVar(v any) template.HTML { + if setting.IsProd { + return "
dumpVar: only available in dev mode
" + } + m, ok := dumpVarMarshalable(v, map[uintptr]bool{}) + var dumpStr string + jsonBytes, err := json.MarshalIndent(m, "", " ") + if err != nil { + dumpStr = fmt.Sprintf("dumpVar: unable to marshal %T: %v", v, err) + } else if ok { + dumpStr = fmt.Sprintf("dumpVar: %T\n%s", v, string(jsonBytes)) + } else { + dumpStr = fmt.Sprintf("dumpVar: unmarshalable %T\n%s", v, string(jsonBytes)) + } + return template.HTML("
" + html.EscapeString(dumpStr) + "
") +} diff --git a/modules/templates/util_slice.go b/modules/templates/util_slice.go new file mode 100644 index 000000000000..a3318cc11a43 --- /dev/null +++ b/modules/templates/util_slice.go @@ -0,0 +1,35 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package templates + +import ( + "fmt" + "reflect" +) + +type SliceUtils struct{} + +func NewSliceUtils() *SliceUtils { + return &SliceUtils{} +} + +func (su *SliceUtils) Contains(s, v any) bool { + if s == nil { + return false + } + sv := reflect.ValueOf(s) + if sv.Kind() != reflect.Slice && sv.Kind() != reflect.Array { + panic(fmt.Sprintf("invalid type, expected slice or array, but got: %T", s)) + } + for i := 0; i < sv.Len(); i++ { + it := sv.Index(i) + if !it.CanInterface() { + continue + } + if it.Interface() == v { + return true + } + } + return false +} diff --git a/modules/templates/util_string.go b/modules/templates/util_string.go new file mode 100644 index 000000000000..e86bbe9e705f --- /dev/null +++ b/modules/templates/util_string.go @@ -0,0 +1,20 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package templates + +import "strings" + +type StringUtils struct{} + +func NewStringUtils() *StringUtils { + return &StringUtils{} +} + +func (su *StringUtils) HasPrefix(s, prefix string) bool { + return strings.HasPrefix(s, prefix) +} + +func (su *StringUtils) Contains(s, substr string) bool { + return strings.Contains(s, substr) +} diff --git a/modules/templates/util_test.go b/modules/templates/util_test.go index dfa691c5e2d6..febaf7fa8815 100644 --- a/modules/templates/util_test.go +++ b/modules/templates/util_test.go @@ -4,6 +4,9 @@ package templates import ( + "html/template" + "io" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -41,3 +44,36 @@ func TestDict(t *testing.T) { assert.Error(t, err) } } + +func TestUtils(t *testing.T) { + execTmpl := func(code string, data any) string { + tmpl := template.New("test") + tmpl.Funcs(template.FuncMap{"SliceUtils": NewSliceUtils, "StringUtils": NewStringUtils}) + template.Must(tmpl.Parse(code)) + w := &strings.Builder{} + assert.NoError(t, tmpl.Execute(w, data)) + return w.String() + } + + actual := execTmpl("{{SliceUtils.Contains .Slice .Value}}", map[string]any{"Slice": []string{"a", "b"}, "Value": "a"}) + assert.Equal(t, "true", actual) + + actual = execTmpl("{{SliceUtils.Contains .Slice .Value}}", map[string]any{"Slice": []string{"a", "b"}, "Value": "x"}) + assert.Equal(t, "false", actual) + + actual = execTmpl("{{SliceUtils.Contains .Slice .Value}}", map[string]any{"Slice": []int64{1, 2}, "Value": int64(2)}) + assert.Equal(t, "true", actual) + + actual = execTmpl("{{StringUtils.Contains .String .Value}}", map[string]any{"String": "abc", "Value": "b"}) + assert.Equal(t, "true", actual) + + actual = execTmpl("{{StringUtils.Contains .String .Value}}", map[string]any{"String": "abc", "Value": "x"}) + assert.Equal(t, "false", actual) + + tmpl := template.New("test") + tmpl.Funcs(template.FuncMap{"SliceUtils": NewSliceUtils, "StringUtils": NewStringUtils}) + template.Must(tmpl.Parse("{{SliceUtils.Contains .Slice .Value}}")) + // error is like this: `template: test:1:12: executing "test" at : error calling Contains: ...` + err := tmpl.Execute(io.Discard, map[string]any{"Slice": struct{}{}}) + assert.ErrorContains(t, err, "invalid type, expected slice or array") +} diff --git a/modules/test/logchecker.go b/modules/test/logchecker.go new file mode 100644 index 000000000000..8f8c753c76fc --- /dev/null +++ b/modules/test/logchecker.go @@ -0,0 +1,116 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package test + +import ( + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "code.gitea.io/gitea/modules/log" +) + +type LogChecker struct { + logger *log.MultiChannelledLogger + loggerName string + eventLoggerName string + + filterMessages []string + filtered []bool + + stopMark string + stopped bool + + mu sync.Mutex +} + +func (lc *LogChecker) LogEvent(event *log.Event) error { + lc.mu.Lock() + defer lc.mu.Unlock() + for i, msg := range lc.filterMessages { + if strings.Contains(event.GetMsg(), msg) { + lc.filtered[i] = true + } + } + if strings.Contains(event.GetMsg(), lc.stopMark) { + lc.stopped = true + } + return nil +} + +func (lc *LogChecker) Close() {} + +func (lc *LogChecker) Flush() {} + +func (lc *LogChecker) GetLevel() log.Level { + return log.TRACE +} + +func (lc *LogChecker) GetStacktraceLevel() log.Level { + return log.NONE +} + +func (lc *LogChecker) GetName() string { + return lc.eventLoggerName +} + +func (lc *LogChecker) ReleaseReopen() error { + return nil +} + +var checkerIndex int64 + +func NewLogChecker(loggerName string) (logChecker *LogChecker, cancel func()) { + logger := log.GetLogger(loggerName) + newCheckerIndex := atomic.AddInt64(&checkerIndex, 1) + lc := &LogChecker{ + logger: logger, + loggerName: loggerName, + eventLoggerName: "TestLogChecker-" + strconv.FormatInt(newCheckerIndex, 10), + } + if err := logger.AddLogger(lc); err != nil { + panic(err) // it's impossible + } + return lc, func() { _, _ = logger.DelLogger(lc.GetName()) } +} + +// Filter will make the `Check` function to check if these logs are outputted. +func (lc *LogChecker) Filter(msgs ...string) *LogChecker { + lc.mu.Lock() + defer lc.mu.Unlock() + lc.filterMessages = make([]string, len(msgs)) + copy(lc.filterMessages, msgs) + lc.filtered = make([]bool, len(lc.filterMessages)) + return lc +} + +func (lc *LogChecker) StopMark(msg string) *LogChecker { + lc.mu.Lock() + defer lc.mu.Unlock() + lc.stopMark = msg + lc.stopped = false + return lc +} + +// Check returns the filtered slice and whether the stop mark is reached. +func (lc *LogChecker) Check(d time.Duration) (filtered []bool, stopped bool) { + stop := time.Now().Add(d) + + for { + lc.mu.Lock() + stopped = lc.stopped + lc.mu.Unlock() + + if time.Now().After(stop) || stopped { + lc.mu.Lock() + f := make([]bool, len(lc.filtered)) + copy(f, lc.filtered) + lc.mu.Unlock() + return f, stopped + } + time.Sleep(10 * time.Millisecond) + } +} diff --git a/modules/test/logchecker_test.go b/modules/test/logchecker_test.go new file mode 100644 index 000000000000..4dfea8c3e382 --- /dev/null +++ b/modules/test/logchecker_test.go @@ -0,0 +1,47 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package test + +import ( + "testing" + "time" + + "code.gitea.io/gitea/modules/log" + + "github.com/stretchr/testify/assert" +) + +func TestLogChecker(t *testing.T) { + _ = log.NewLogger(1000, "console", "console", `{"level":"info","stacktracelevel":"NONE","stderr":true}`) + + lc, cleanup := NewLogChecker(log.DEFAULT) + defer cleanup() + + lc.Filter("First", "Third").StopMark("End") + log.Info("test") + + filtered, stopped := lc.Check(100 * time.Millisecond) + assert.ElementsMatch(t, []bool{false, false}, filtered) + assert.False(t, stopped) + + log.Info("First") + filtered, stopped = lc.Check(100 * time.Millisecond) + assert.ElementsMatch(t, []bool{true, false}, filtered) + assert.False(t, stopped) + + log.Info("Second") + filtered, stopped = lc.Check(100 * time.Millisecond) + assert.ElementsMatch(t, []bool{true, false}, filtered) + assert.False(t, stopped) + + log.Info("Third") + filtered, stopped = lc.Check(100 * time.Millisecond) + assert.ElementsMatch(t, []bool{true, true}, filtered) + assert.False(t, stopped) + + log.Info("End") + filtered, stopped = lc.Check(100 * time.Millisecond) + assert.ElementsMatch(t, []bool{true, true}, filtered) + assert.True(t, stopped) +} diff --git a/modules/timeutil/datetime.go b/modules/timeutil/datetime.go new file mode 100644 index 000000000000..02275bca0c32 --- /dev/null +++ b/modules/timeutil/datetime.go @@ -0,0 +1,25 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package timeutil + +import ( + "fmt" + "html" + "html/template" +) + +// DateTime renders an absolute time HTML given a time as a string +func DateTime(format, datetime, fallback string) template.HTML { + datetimeEscaped := html.EscapeString(datetime) + fallbackEscaped := html.EscapeString(fallback) + switch format { + case "short": + return template.HTML(fmt.Sprintf(`%s`, datetimeEscaped, fallbackEscaped)) + case "long": + return template.HTML(fmt.Sprintf(`%s`, datetimeEscaped, fallbackEscaped)) + case "full": + return template.HTML(fmt.Sprintf(`%s`, datetimeEscaped, fallbackEscaped)) + } + return template.HTML("error in DateTime") +} diff --git a/modules/timeutil/since.go b/modules/timeutil/since.go index e6a2519d2120..4aee814ecdc6 100644 --- a/modules/timeutil/since.go +++ b/modules/timeutil/since.go @@ -23,7 +23,7 @@ const ( ) func computeTimeDiffFloor(diff int64, lang translation.Locale) (int64, string) { - diffStr := "" + var diffStr string switch { case diff <= 0: diff = 0 diff --git a/modules/translation/i18n/i18n_test.go b/modules/translation/i18n/i18n_test.go index 085d03811f6d..1d1be43318dc 100644 --- a/modules/translation/i18n/i18n_test.go +++ b/modules/translation/i18n/i18n_test.go @@ -51,8 +51,8 @@ sub = Changed Sub String assert.Equal(t, `test value; more text`, result) langs, descs := ls.ListLangNameDesc() - assert.Equal(t, []string{"lang1", "lang2"}, langs) - assert.Equal(t, []string{"Lang1", "Lang2"}, descs) + assert.ElementsMatch(t, []string{"lang1", "lang2"}, langs) + assert.ElementsMatch(t, []string{"Lang1", "Lang2"}, descs) found := ls.Has("lang1", "no-such") assert.False(t, found) diff --git a/modules/translation/i18n/localestore.go b/modules/translation/i18n/localestore.go index 90f574127d11..0664bcfd1a22 100644 --- a/modules/translation/i18n/localestore.go +++ b/modules/translation/i18n/localestore.go @@ -72,7 +72,6 @@ func (store *localeStore) AddLocaleByIni(langName, langDesc string, source, more l.idxToMsgMap[idx] = key.Value() } } - iniFile = nil return nil } diff --git a/modules/util/slice_test.go b/modules/util/slice_test.go index b0b771a79a8f..373c1a3b7b15 100644 --- a/modules/util/slice_test.go +++ b/modules/util/slice_test.go @@ -74,15 +74,15 @@ func TestSliceEqual(t *testing.T) { } func TestSliceRemoveAll(t *testing.T) { - assert.Equal(t, SliceRemoveAll([]int{2, 0, 2, 3}, 0), []int{2, 2, 3}) - assert.Equal(t, SliceRemoveAll([]int{2, 0, 2, 3}, 2), []int{0, 3}) - assert.Equal(t, SliceRemoveAll([]int{0, 0, 0, 0}, 0), []int{}) - assert.Equal(t, SliceRemoveAll([]int{2, 0, 2, 3}, 4), []int{2, 0, 2, 3}) - assert.Equal(t, SliceRemoveAll([]int{}, 0), []int{}) - assert.Equal(t, SliceRemoveAll([]int(nil), 0), []int(nil)) - assert.Equal(t, SliceRemoveAll([]int{}, 0), []int{}) - - assert.Equal(t, SliceRemoveAll([]string{"2", "0", "2", "3"}, "0"), []string{"2", "2", "3"}) - assert.Equal(t, SliceRemoveAll([]float64{2, 0, 2, 3}, 0), []float64{2, 2, 3}) - assert.Equal(t, SliceRemoveAll([]bool{false, true, false}, true), []bool{false, false}) + assert.ElementsMatch(t, []int{2, 2, 3}, SliceRemoveAll([]int{2, 0, 2, 3}, 0)) + assert.ElementsMatch(t, []int{0, 3}, SliceRemoveAll([]int{2, 0, 2, 3}, 2)) + assert.Empty(t, SliceRemoveAll([]int{0, 0, 0, 0}, 0)) + assert.ElementsMatch(t, []int{2, 0, 2, 3}, SliceRemoveAll([]int{2, 0, 2, 3}, 4)) + assert.Empty(t, SliceRemoveAll([]int{}, 0)) + assert.ElementsMatch(t, []int(nil), SliceRemoveAll([]int(nil), 0)) + assert.Empty(t, SliceRemoveAll([]int{}, 0)) + + assert.ElementsMatch(t, []string{"2", "2", "3"}, SliceRemoveAll([]string{"2", "0", "2", "3"}, "0")) + assert.ElementsMatch(t, []float64{2, 2, 3}, SliceRemoveAll([]float64{2, 0, 2, 3}, 0)) + assert.ElementsMatch(t, []bool{false, false}, SliceRemoveAll([]bool{false, true, false}, true)) } diff --git a/modules/util/truncate_test.go b/modules/util/truncate_test.go index 05e2bc03019b..dfe1230fd447 100644 --- a/modules/util/truncate_test.go +++ b/modules/util/truncate_test.go @@ -25,7 +25,7 @@ func TestSplitString(t *testing.T) { assert.Equal(t, c.ellipsis+c.input[len(c.leftSub):], r, "test split %s at %d, expected rightSub: %q", c.input, c.n, c.input[len(c.leftSub):]) } else { assert.Equal(t, c.leftSub, l, "test split %q at %d, expected leftSub: %q", c.input, c.n, c.leftSub) - assert.Equal(t, "", r, "test split %q at %d, expected rightSub: %q", c.input, c.n, "") + assert.Empty(t, r, "test split %q at %d, expected rightSub: %q", c.input, c.n, "") } } } diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index fa35c92ed3bd..4ac0a1d85bdc 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -3193,3 +3193,5 @@ runs.closed_tab=%d uzavřeno runs.commit=Commit +[projects] + diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index e810a89d339f..94a6440dd034 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -3054,3 +3054,5 @@ runners.version=Version runs.commit=Commit +[projects] + diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index b4b99955c228..949aa0de15b7 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -19,6 +19,7 @@ active_stopwatch=Ενεργή Καταγραφή Χρόνου create_new=Δημιουργία… user_profile_and_more=Προφίλ και ρυθμίσεις… signed_in_as=Είσοδος ως +enable_javascript=Απαιτείται JavaScript για να εμφανιστεί αυτή η ιστοσελίδα. toc=Πίνακας Περιεχομένων licenses=Άδειες return_to_gitea=Επιστροφή στο Gitea @@ -83,6 +84,7 @@ add=Προσθήκη add_all=Προσθήκη Όλων remove=Αφαίρεση remove_all=Αφαίρεση Όλων +remove_label_str=`Αφαίρεση του αντικειμένου "%s"` edit=Επεξεργασία enabled=Ενεργοποιημένο @@ -117,9 +119,26 @@ footer.software=Σχετικά με το Λογισμικό footer.links=Συνδέσεις [heatmap] +number_of_contributions_in_the_last_12_months=%s συνεισφορές τους τελευταίους 12 μήνες +no_contributions=Χωρίς συνεισφορές +less=Λιγότερα +more=Περισσότερα [editor] +buttons.heading.tooltip=Προσθήκη επικεφαλίδας +buttons.bold.tooltip=Προσθήκη έντονου κειμένου +buttons.italic.tooltip=Προσθήκη πλαγίου κειμένου +buttons.quote.tooltip=Παράθεση κειμένου +buttons.code.tooltip=Προσθήκη κώδικα +buttons.link.tooltip=Προσθήκη συνδέσμου buttons.list.unordered.tooltip=Προσθήκη απλής λίστας +buttons.list.ordered.tooltip=Προσθήκη αριθμημένης λίστας +buttons.list.task.tooltip=Προσθήκη λίστας εργασιών +buttons.mention.tooltip=Μνημόνευση ενός χρήστη ή ομάδας +buttons.ref.tooltip=Μνημόνευση ενός θέματος ή pull request +buttons.switch_to_legacy.tooltip=Χρήση του κλασσικού κειμενογράφου +buttons.enable_monospace_font=Ενεργοποίηση σταθερής γραμματοσειράς +buttons.disable_monospace_font=Απενεργοποίηση σταθερής γραμματοσειράς [filter] string.asc=A - Z @@ -222,6 +241,7 @@ openid_signup_popup=Ενεργοποίηση ιδιοεγγραφής χρηστ enable_captcha=Ενεργοποίηση CAPTCHA στην εγγραφή enable_captcha_popup=Απαιτείται ένα CAPTCHA για τη ιδιοεγγραφή του χρήστη. require_sign_in_view=Απαιτείται Είσοδος για τη Προβολή Σελίδων +require_sign_in_view_popup=Περιορισμός της πρόσβασης σε συνδεδεμένους χρήστες. Οι επισκέπτες θα μπορούν μόνο να δουν τις σελίδες εισόδου και εγγραφής. admin_setting_desc=Η δημιουργία ενός λογαριασμού διαχειριστή είναι προαιρετική. Ο πρώτος εγγεγραμμένος χρήστης θα γίνει αυτόματα διαχειριστής. admin_title=Ρυθμίσεις Λογαριασμού Διαχειριστή admin_name=Όνομα Χρήστη Διαχειριστή @@ -232,6 +252,7 @@ install_btn_confirm=Εγκατάσταση Gitea test_git_failed=Αδυναμία δοκιμής της εντολής 'git': %v sqlite3_not_available=Αυτή η έκδοση Gitea δεν υποστηρίζει την SQLite3. Παρακαλώ κατεβάστε την επίσημη δυαδική έκδοση από το %s (όχι την έκδοση 'gobuild'). invalid_db_setting=Οι ρυθμίσεις της βάσης δεδομένων δεν είναι έγκυρες: %v +invalid_db_table=Ο πίνακας βάσης δεδομένων "%s" δεν είναι έγκυρος: %v invalid_repo_path=Η αρχική διαδρομή των αποθετηρίων δεν είναι έγκυρη: %v invalid_app_data_path=Η διαδρομή δεδομένων εφαρμογής (app data) δεν είναι έγκυρη: %v run_user_not_match=Το όνομα χρήστη 'εκτέλεση ως' δεν είναι το τρέχον όνομα χρήστη: %s -> %s @@ -250,6 +271,7 @@ no_reply_address=Κρυφό Όνομα Τομέα Email no_reply_address_helper=Όνομα τομέα για χρήστες με μια κρυφή διεύθυνση email. Για παράδειγμα, το όνομα χρήστη 'nikos' θα συνδεθεί στο Git ως 'nikos@noreply.example.org' αν ο κρυφός τομέας email έχει οριστεί ως 'noreply.example.org'. password_algorithm=Αλγόριθμος Hash Κωδικού Πρόσβασης invalid_password_algorithm=Μη έγκυρος αλγόριθμος κωδικού πρόσβασης +password_algorithm_helper=Ορίστε τον αλγόριθμο κατακερματισμού για το κωδικό πρόσβασης. Οι αλγόριθμοι διαφέρουν σε απαιτήσεις και αντοχή. Ο αλγόριθμος argon2 είναι αρκετά ασφαλής, αλλά χρησιμοποιεί πολλή μνήμη και μπορεί να είναι ακατάλληλος για μικρά συστήματα. enable_update_checker=Ενεργοποίηση Ελεγκτή Ενημερώσεων enable_update_checker_helper=Ελέγχει περιοδικά για νέες εκδόσεις κάνοντας σύνδεση στο gitea.io. @@ -296,6 +318,7 @@ repo_no_results=Δεν βρέθηκαν αποθετήρια που να ται user_no_results=Δεν βρέθηκαν χρήστες που να ταιριάζουν με τα κριτήρια. org_no_results=Δεν βρέθηκαν οργανισμοί που να ταιριάζουν με τα κριτήρια. code_no_results=Δεν βρέθηκε πηγαίος κώδικας που να ταιριάζει με τον όρο αναζήτησης. +code_search_results=`Αποτελέσματα αναζήτησης για "%s"` code_last_indexed_at=Τελευταίο δημιουργία ευρετηρίου στις %s relevant_repositories_tooltip=Τα αποθετήρια που είναι forks ή που δεν έχουν θέμα, εικονίδιο και περιγραφή είναι κρυμμένα. relevant_repositories=Εμφανίζονται μόνο τα σχετικά αποθετήρια, εμφάνιση χωρίς φίλτρο. @@ -472,6 +495,8 @@ size_error=`πρέπει να έχει μέγεθος %s.` min_size_error=` πρέπει να περιέχει τουλάχιστον %s χαρακτήρες.` max_size_error=` πρέπει να περιέχει το πολύ %s χαρακτήρες.` email_error=` δεν είναι έγκυρη διεύθυνση email.` +url_error=`Το "%s" δεν είναι ένα έγκυρο URL.` +include_error=` πρέπει να περιέχει το μέρος "%s".` glob_pattern_error=` το μοτίβο ταιριάσματος (glob) δεν είναι έγκυρο: %s.` regex_pattern_error=` το μοτίβο regex δεν είναι έγκυρο: %s.` username_error=` μπορεί να περιέχει μόνο αλφαριθμητικούς χαρακτήρες ('0-9','a-z','A-Z'), παύλα ('-'), κάτω παύλα ('_') και τελεία ('.'). Δεν μπορεί να ξεκινά ή να τελειώνει με μη αλφαριθμητικούς χαρακτήρες, επίσης απαγορεύονται οι διαδοχικοί μη αλφαριθμητικοί χαρακτήρες.` @@ -496,6 +521,7 @@ team_name_been_taken=Το όνομα της ομάδας χρησιμοποιε team_no_units_error=Να επιτρέπεται η πρόσβαση σε τουλάχιστον μία ενότητα αποθετηρίου. email_been_used=Η διεύθυνση email χρησιμοποιείται ήδη. email_invalid=Η διεύθυνση email δεν είναι έγκυρη. +openid_been_used=Η διεύθυνση OpenID "%s" χρησιμοποιείται ήδη. username_password_incorrect=Το όνομα χρήστη ή ο κωδικός πρόσβασης δεν είναι σωστά. password_complexity=Ο κωδικός πρόσβασης δεν περνά τις απαιτήσεις πολυπλοκότητας: password_lowercase_one=Τουλάχιστον ένα πεζό γράμμα @@ -515,10 +541,16 @@ organization_leave_success=Έχετε εγκαταλείψει με επιτυχ invalid_ssh_key=Δεν είναι δυνατή η επαλήθευση του SSH κλειδιού σας: %s invalid_gpg_key=Δεν είναι δυνατή η επαλήθευση του GPG κλειδιού σας: %s -invalid_ssh_principal=Μη έγκυρος ssh principal: %s +invalid_ssh_principal=Μη έγκυρη αρχή: %s must_use_public_key=Το κλειδί που δώσατε είναι ένα ιδιωτικό κλειδί. Παρακαλώ μην ανεβάζετε το ιδιωτικό σας κλειδί οπουδήποτε. Χρησιμοποιήστε το δημόσιο κλειδί αντί αυτού. +unable_verify_ssh_key=Αδυναμία επαλήθευσης του κλειδιού SSH, ελέγξτε το ξανά για λάθη. auth_failed=Αποτυχία ταυτοποίησης: %v +still_own_repo=Ο λογαριασμός σας κατέχει ένα ή περισσότερα αποθετήρια, διαγράψτε ή μεταφέρετε τα πρώτα. +still_has_org=Ο λογαριασμός σας είναι μέλος σε ένα ή περισσότερους οργανισμούς, φύγετε από αυτούς πρώτα. +still_own_packages=Ο λογαριασμός σας κατέχει ένα ή περισσότερα πακέτα, διαγράψτε τα πρώτα. +org_still_own_repo=Αυτός ο οργανισμός κατέχει ακόμα ένα ή περισσότερα αποθετήρια, διαγράψτε τα ή μεταφέρετε τα πρώτα. +org_still_own_packages=Αυτός ο οργανισμός κατέχει ακόμα ένα ή περισσότερα πακέτα, διαγράψτε τα πρώτα. target_branch_not_exist=Ο κλάδος προορισμού δεν υπάρχει. @@ -538,7 +570,12 @@ unfollow=Να μην ακολουθώ heatmap.loading=Φόρτωση heatmap… user_bio=Βιογραφικό disabled_public_activity=Αυτός ο χρήστης έχει απενεργοποιήσει τη δημόσια προβολή της δραστηριότητας. +email_visibility.limited=Η διεύθυνση email σας είναι ορατή σε όλους τους ταυτοποιημένους χρήστες +email_visibility.private=Η διεύθυνση email σας είναι ορατή μόνο σε εσάς και στους διαχειριστές +form.name_reserved=Το όνομα χρήστη "%s" είναι δεσμευμένο. +form.name_pattern_not_allowed=Το μοτίβο "%s" δεν επιτρέπεται μέσα σε ένα όνομα χρήστη. +form.name_chars_not_allowed=Το όνομα χρήστη "%s" περιέχει μη έγκυρους χαρακτήρες. [settings] profile=Προφίλ @@ -569,6 +606,7 @@ location=Τοποθεσία update_theme=Ενημέρωση Θέματος Διεπαφής update_profile=Ενημέρωση Προφίλ update_language=Ενημέρωση Γλώσσας +update_language_not_found=Η γλώσσα "%s" δεν είναι διαθέσιμη. update_language_success=Η γλώσσα ενημερώθηκε. update_profile_success=Το προφίλ σας έχει ενημερωθεί. change_username=Το όνομα χρήστη σας έχει αλλάξει. @@ -579,6 +617,9 @@ cancel=Ακύρωση language=Γλώσσα ui=Θέμα Διεπαφής hidden_comment_types=Κρυμμένοι τύποι σχολίων +hidden_comment_types_description=Οι τύποι σχολίων που επιλέγονται εδώ δε θα εμφανίζονται μέσα στις σελίδες ζητημάτων. Επιλέγοντας π.χ το "Σήματα", θα αφαιρεθούν όλα τα σχόλια σαν το " πρόσθεσε/αφαίρεσε τα σήματα