diff --git a/.github/workflows/pull-docker-dryrun.yml b/.github/workflows/pull-docker-dryrun.yml index 405521c354c..bcc19e3eba8 100644 --- a/.github/workflows/pull-docker-dryrun.yml +++ b/.github/workflows/pull-docker-dryrun.yml @@ -29,6 +29,7 @@ jobs: context: . platforms: linux/amd64,linux/arm64,linux/riscv64 push: false + cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful - name: Build rootless container image uses: docker/build-push-action@v6 with: @@ -36,3 +37,4 @@ jobs: push: false platforms: linux/amd64,linux/arm64,linux/riscv64 file: Dockerfile.rootless + cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index f073df05d39..a7b2fda0426 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -120,6 +120,8 @@ jobs: push: true tags: ${{ steps.meta.outputs.tags }} annotations: ${{ steps.meta.outputs.annotations }} + cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful + cache-to: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful,mode=max - name: build rootless docker image uses: docker/build-push-action@v6 with: @@ -129,3 +131,5 @@ jobs: file: Dockerfile.rootless tags: ${{ steps.meta_rootless.outputs.tags }} annotations: ${{ steps.meta_rootless.outputs.annotations }} + cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless + cache-to: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless,mode=max diff --git a/.golangci.yml b/.golangci.yml index 2b85c89fdce..afd91d65e59 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -51,8 +51,6 @@ linters: desc: do not use the go-chi cache package, use gitea's cache system - pkg: github.com/pkg/errors desc: use builtin errors package instead - - pkg: github.com/go-ap/errors - desc: use builtin errors package instead nolintlint: allow-unused: false require-explanation: true @@ -141,9 +139,6 @@ linters: - linters: - unused text: (?i)swagger - - linters: - - staticcheck - text: (?i)argument x is overwritten before first use - linters: - gocritic text: '(?i)commentFormatting: put a space between `//` and comment text' diff --git a/Makefile b/Makefile index cb7742c5c74..280aa853b8e 100644 --- a/Makefile +++ b/Makefile @@ -15,13 +15,13 @@ XGO_VERSION := go-1.25.x AIR_PACKAGE ?= github.com/air-verse/air@v1 EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3 GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.9.2 -GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.9.0 +GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.10.1 GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 -MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.7.0 +MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.8.0 SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1 XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 -ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.10 +ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.11 DOCKER_IMAGE ?= gitea/gitea DOCKER_TAG ?= latest @@ -322,11 +322,11 @@ lint-md-fix: node_modules ## lint markdown files and fix issues .PHONY: lint-spell lint-spell: ## lint spelling - @go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -error $(SPELLCHECK_FILES) + @git ls-files $(SPELLCHECK_FILES) | xargs go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -error .PHONY: lint-spell-fix lint-spell-fix: ## lint spelling and fix issues - @go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -w $(SPELLCHECK_FILES) + @git ls-files $(SPELLCHECK_FILES) | xargs go run $(MISSPELL_PACKAGE) -dict assets/misspellings.csv -w .PHONY: lint-go lint-go: ## lint go files diff --git a/assets/go-licenses.json b/assets/go-licenses.json index 03fdbcc0b20..30f56e5f872 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -39,11 +39,6 @@ "path": "filippo.io/edwards25519/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": "git.sr.ht/~mariusor/go-xsd-duration", - "path": "git.sr.ht/~mariusor/go-xsd-duration/LICENSE", - "licenseText": "MIT License\n\nCopyright (c) 2019 Go xsd:duration\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" - }, { "name": "gitea.com/go-chi/binding", "path": "gitea.com/go-chi/binding/LICENSE", @@ -489,21 +484,6 @@ "path": "github.com/gliderlabs/ssh/LICENSE", "licenseText": "Copyright (c) 2016 Glider Labs. 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 Glider Labs 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": "github.com/go-ap/activitypub", - "path": "github.com/go-ap/activitypub/LICENSE", - "licenseText": "MIT License\n\nCopyright (c) 2017 Golang ActitvityPub\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" - }, - { - "name": "github.com/go-ap/errors", - "path": "github.com/go-ap/errors/LICENSE", - "licenseText": "MIT License\n\nCopyright (c) 2019 Golang ActitvityPub\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" - }, - { - "name": "github.com/go-ap/jsonld", - "path": "github.com/go-ap/jsonld/LICENSE", - "licenseText": "MIT License\n\nCopyright (c) 2017 Marius Orcsik\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" - }, { "name": "github.com/go-asn1-ber/asn1-ber", "path": "github.com/go-asn1-ber/asn1-ber/LICENSE", @@ -1154,11 +1134,6 @@ "path": "github.com/urfave/cli/v3/LICENSE", "licenseText": "MIT License\n\nCopyright (c) 2023 urfave/cli maintainers\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" }, - { - "name": "github.com/valyala/fastjson", - "path": "github.com/valyala/fastjson/LICENSE", - "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2018 Aliaksandr Valialkin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n" - }, { "name": "github.com/wneessen/go-mail", "path": "github.com/wneessen/go-mail/LICENSE", diff --git a/cmd/hook.go b/cmd/hook.go index 6004f679acf..4f6492b0f05 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -205,6 +205,7 @@ Gitea or set your environment appropriately.`, "") PullRequestID: prID, DeployKeyID: deployKeyID, ActionPerm: actionPerm, + IsWiki: isWiki, } scanner := bufio.NewScanner(os.Stdin) @@ -366,6 +367,7 @@ Gitea or set your environment appropriately.`, "") GitPushOptions: pushOptions(), PullRequestID: prID, PushTrigger: repo_module.PushTrigger(os.Getenv(repo_module.EnvPushTrigger)), + IsWiki: isWiki, } oldCommitIDs := make([]string, hookBatchSize) newCommitIDs := make([]string, hookBatchSize) @@ -513,6 +515,7 @@ Gitea or set your environment appropriately.`, "") reader := bufio.NewReader(os.Stdin) repoUser := os.Getenv(repo_module.EnvRepoUsername) + isWiki, _ := strconv.ParseBool(os.Getenv(repo_module.EnvRepoIsWiki)) repoName := os.Getenv(repo_module.EnvRepoName) pusherID, _ := strconv.ParseInt(os.Getenv(repo_module.EnvPusherID), 10, 64) pusherName := os.Getenv(repo_module.EnvPusherName) @@ -590,6 +593,7 @@ Gitea or set your environment appropriately.`, "") UserName: pusherName, UserID: pusherID, GitPushOptions: make(map[string]string), + IsWiki: isWiki, } hookOptions.OldCommitIDs = make([]string, 0, hookBatchSize) hookOptions.NewCommitIDs = make([]string, 0, hookBatchSize) diff --git a/cmd/mailer.go b/cmd/mailer.go index 72bd8e56012..a7dbd68e20a 100644 --- a/cmd/mailer.go +++ b/cmd/mailer.go @@ -17,10 +17,10 @@ func runSendMail(ctx context.Context, c *cli.Command) error { setting.MustInstalled() subject := c.String("title") - confirmSkiped := c.Bool("force") + confirmSkipped := c.Bool("force") body := c.String("content") - if !confirmSkiped { + if !confirmSkipped { if len(body) == 0 { fmt.Print("warning: Content is empty") } diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index f5e8f03ae23..9297f3d0627 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -74,6 +74,7 @@ RUN_USER = ; git ;; especially when the Gitea instance needs to be accessed in a container network. ;; * legacy: detect the public URL from "Host" header if "X-Forwarded-Proto" header exists, otherwise use "ROOT_URL". ;; * auto: always use "Host" header, and also use "X-Forwarded-Proto" header if it exists. If no "Host" header, use "ROOT_URL". +;; * never: always use "ROOT_URL", never detect from request headers. ;PUBLIC_URL_DETECTION = legacy ;; ;; For development purpose only. It makes Gitea handle sub-path ("/sub-path/owner/repo/...") directly when debugging without a reverse proxy. @@ -238,9 +239,6 @@ RUN_USER = ; git ;; Indicate whether to check minimum key size with corresponding type ;MINIMUM_KEY_SIZE_CHECK = false ;; -;; Disable CDN even in "prod" mode -;OFFLINE_MODE = true -;; ;; TLS Settings: Either ACME or manual ;; (Other common TLS configuration are found before) ;ENABLE_ACME = false @@ -1983,12 +1981,12 @@ LEVEL = Info ;; or a custom avatar source, like: http://cn.gravatar.com/avatar/ ;GRAVATAR_SOURCE = gravatar ;; -;; This value will always be true in offline mode. +;; Deprecated, see Web UI Admin Panel -> Config -> Settings ;DISABLE_GRAVATAR = false ;; ;; Federated avatar lookup uses DNS to discover avatar associated ;; with emails, see https://www.libravatar.org -;; This value will always be false in offline mode or when Gravatar is disabled. +;; Deprecated, see Web UI Admin Panel -> Config -> Settings ;ENABLE_FEDERATED_AVATAR = false ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/flake.lock b/flake.lock index a608aa3b892..d00e8c85018 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1771369470, - "narHash": "sha256-0NBlEBKkN3lufyvFegY4TYv5mCNHbi5OmBDrzihbBMQ=", + "lastModified": 1772198003, + "narHash": "sha256-I45esRSssFtJ8p/gLHUZ1OUaaTaVLluNkABkk6arQwE=", "owner": "nixos", "repo": "nixpkgs", - "rev": "0182a361324364ae3f436a63005877674cf45efb", + "rev": "dd9b079222d43e1943b6ebd802f04fd959dc8e61", "type": "github" }, "original": { diff --git a/go.mod b/go.mod index c5486fceb89..c85c4c0b399 100644 --- a/go.mod +++ b/go.mod @@ -46,8 +46,6 @@ require ( github.com/felixge/fgprof v0.9.5 github.com/fsnotify/fsnotify v1.9.0 github.com/gliderlabs/ssh v0.3.8 - github.com/go-ap/activitypub v0.0.0-20250810115208-cb73b20a1742 - github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 github.com/go-chi/chi/v5 v5.2.5 github.com/go-chi/cors v1.2.2 github.com/go-co-op/gocron v1.37.0 @@ -135,7 +133,6 @@ require ( code.gitea.io/gitea-vet v0.2.3 // indirect dario.cat/mergo v1.0.2 // indirect filippo.io/edwards25519 v1.1.0 // indirect - git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/DataDog/zstd v1.5.7 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect @@ -177,7 +174,7 @@ require ( github.com/caddyserver/zerossl v0.1.4 // indirect github.com/cention-sany/utf7 v0.0.0-20170124080048-26cad61bd60a // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cloudflare/circl v1.6.1 // indirect + github.com/cloudflare/circl v1.6.3 // indirect github.com/couchbase/go-couchbase v0.1.1 // indirect github.com/couchbase/gomemcached v0.3.3 // indirect github.com/couchbase/goutils v0.1.2 // indirect @@ -191,7 +188,6 @@ require ( github.com/fatih/color v1.18.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect - github.com/go-ap/errors v0.0.0-20250527110557-c8db454e53fd // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect github.com/go-enry/go-oniguruma v1.2.1 // indirect github.com/go-fed/httpsig v1.1.1-0.20201223112313-55836744818e // indirect @@ -263,7 +259,6 @@ require ( github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/tinylib/msgp v1.6.1 // indirect github.com/unknwon/com v1.0.1 // indirect - github.com/valyala/fastjson v1.6.4 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect @@ -297,8 +292,6 @@ replace github.com/jaytaylor/html2text => github.com/Necoro/html2text v0.0.0-202 replace github.com/nektos/act => gitea.com/gitea/act v0.261.8 -replace git.sr.ht/~mariusor/go-xsd-duration => gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 - exclude github.com/gofrs/uuid v3.2.0+incompatible exclude github.com/gofrs/uuid v4.0.0+incompatible diff --git a/go.sum b/go.sum index 883547adad9..02e65325429 100644 --- a/go.sum +++ b/go.sum @@ -33,8 +33,6 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= gitea.com/gitea/act v0.261.8 h1:rUWB5GOZOubfe2VteKb7XP3HRIbcW3UUmfh7bVAgQcA= gitea.com/gitea/act v0.261.8/go.mod h1:lTp4136rwbZiZS3ZVQeHCvd4qRAZ7LYeiRBqOSdMY/4= -gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:BAFmdZpRW7zMQZQDClaCWobRj9uL1MR3MzpCVJvc5s4= -gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs= gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed h1:EZZBtilMLSZNWtHHcgq2mt6NSGhJSZBuduAlinMEmso= gitea.com/go-chi/binding v0.0.0-20240430071103-39a851e106ed/go.mod h1:E3i3cgB04dDx0v3CytCgRTTn9Z/9x891aet3r456RVw= gitea.com/go-chi/cache v0.2.1 h1:bfAPkvXlbcZxPCpcmDVCWoHgiBSBmZN/QosnZvEC0+g= @@ -229,8 +227,8 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= -github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -305,12 +303,6 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= -github.com/go-ap/activitypub v0.0.0-20250810115208-cb73b20a1742 h1:X+SsQlZSgJO0A4d1+nI7+g4axZ8u3iUKPirYb5nB5ic= -github.com/go-ap/activitypub v0.0.0-20250810115208-cb73b20a1742/go.mod h1:0rgUaERG5qjYenwz4oN5OnUjvkdRuHRjb+2c8FRjz+w= -github.com/go-ap/errors v0.0.0-20250527110557-c8db454e53fd h1:fM5mNIWTPoxoOYoTLd6ifkKXSlXa830l5MYXsrt1UmE= -github.com/go-ap/errors v0.0.0-20250527110557-c8db454e53fd/go.mod h1:Vkh+Z3f24K8nMsJKXo1FHn5ebPsXvB/WDH5JRtYqdNo= -github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 h1:GMKIYXyXPGIp+hYiWOhfqK4A023HdgisDT4YGgf99mw= -github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73/go.mod h1:jyveZeGw5LaADntW+UEsMjl3IlIwk+DxlYNsbofQkGA= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= @@ -766,8 +758,6 @@ github.com/urfave/cli-docs/v3 v3.0.0-alpha6 h1:w/l/N0xw1rO/aHRIGXJ0lDwwYFOzilup1 github.com/urfave/cli-docs/v3 v3.0.0-alpha6/go.mod h1:p7Z4lg8FSTrPB9GTaNyTrK3ygffHZcK3w0cU2VE+mzU= github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM= github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= -github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= -github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/wneessen/go-mail v0.7.2 h1:xxPnhZ6IZLSgxShebmZ6DPKh1b6OJcoHfzy7UjOkzS8= github.com/wneessen/go-mail v0.7.2/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k= diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go index 2f5aff09335..e2bb72b722a 100644 --- a/models/auth/oauth2.go +++ b/models/auth/oauth2.go @@ -14,6 +14,7 @@ import ( "net/url" "slices" "strings" + "time" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/container" @@ -27,6 +28,11 @@ import ( "xorm.io/xorm" ) +// Authorization codes should expire within 10 minutes per https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2 +const oauth2AuthorizationCodeValidity = 10 * time.Minute + +var ErrOAuth2AuthorizationCodeInvalidated = errors.New("oauth2 authorization code already invalidated") + // OAuth2Application represents an OAuth2 client (RFC 6749) type OAuth2Application struct { ID int64 `xorm:"pk autoincr"` @@ -386,6 +392,14 @@ func (code *OAuth2AuthorizationCode) TableName() string { return "oauth2_authorization_code" } +// IsExpired reports whether the authorization code is expired. +func (code *OAuth2AuthorizationCode) IsExpired() bool { + if code.ValidUntil.IsZero() { + return true + } + return code.ValidUntil <= timeutil.TimeStampNow() +} + // GenerateRedirectURI generates a redirect URI for a successful authorization request. State will be used if not empty. func (code *OAuth2AuthorizationCode) GenerateRedirectURI(state string) (*url.URL, error) { redirect, err := url.Parse(code.RedirectURI) @@ -403,8 +417,14 @@ func (code *OAuth2AuthorizationCode) GenerateRedirectURI(state string) (*url.URL // Invalidate deletes the auth code from the database to invalidate this code func (code *OAuth2AuthorizationCode) Invalidate(ctx context.Context) error { - _, err := db.GetEngine(ctx).ID(code.ID).NoAutoCondition().Delete(code) - return err + affected, err := db.GetEngine(ctx).ID(code.ID).NoAutoCondition().Delete(code) + if err != nil { + return err + } + if affected == 0 { + return ErrOAuth2AuthorizationCodeInvalidated + } + return nil } // ValidateCodeChallenge validates the given verifier against the saved code challenge. This is part of the PKCE implementation. @@ -472,6 +492,7 @@ func (grant *OAuth2Grant) GenerateNewAuthorizationCode(ctx context.Context, redi // for code scanners to grab sensitive tokens. codeSecret := "gta_" + base32Lower.EncodeToString(rBytes) + validUntil := time.Now().Add(oauth2AuthorizationCodeValidity) code = &OAuth2AuthorizationCode{ Grant: grant, GrantID: grant.ID, @@ -479,6 +500,7 @@ func (grant *OAuth2Grant) GenerateNewAuthorizationCode(ctx context.Context, redi Code: codeSecret, CodeChallenge: codeChallenge, CodeChallengeMethod: codeChallengeMethod, + ValidUntil: timeutil.TimeStamp(validUntil.Unix()), } if err := db.Insert(ctx, code); err != nil { return nil, err diff --git a/models/auth/oauth2_test.go b/models/auth/oauth2_test.go index 97f750755a2..88ae065652c 100644 --- a/models/auth/oauth2_test.go +++ b/models/auth/oauth2_test.go @@ -5,13 +5,45 @@ package auth_test import ( "testing" + "time" auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/timeutil" "github.com/stretchr/testify/assert" ) +func TestOAuth2AuthorizationCodeValidity(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + + t.Run("GenerateSetsValidUntil", func(t *testing.T) { + grant := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Grant{ID: 1}) + expectedValidUntil := timeutil.TimeStamp(time.Now().Unix() + 600) + code, err := grant.GenerateNewAuthorizationCode(t.Context(), "http://127.0.0.1/", "", "") + assert.NoError(t, err) + assert.Equal(t, expectedValidUntil, code.ValidUntil) + assert.False(t, code.IsExpired()) + assert.NoError(t, code.Invalidate(t.Context())) + }) + + t.Run("Expired", func(t *testing.T) { + defer timeutil.MockSet(time.Unix(2, 0).UTC())() + + code := &auth_model.OAuth2AuthorizationCode{ValidUntil: timeutil.TimeStamp(1)} + assert.True(t, code.IsExpired()) + }) + + t.Run("InvalidateTwice", func(t *testing.T) { + code, err := auth_model.GetOAuth2AuthorizationByCode(t.Context(), "authcode") + assert.NoError(t, err) + if assert.NotNil(t, code) { + assert.NoError(t, code.Invalidate(t.Context())) + assert.ErrorIs(t, code.Invalidate(t.Context()), auth_model.ErrOAuth2AuthorizationCodeInvalidated) + } + }) +} + func TestOAuth2Application_GenerateClientSecret(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1}) diff --git a/models/auth/source.go b/models/auth/source.go index 08cfc9615b0..c0b262f870a 100644 --- a/models/auth/source.go +++ b/models/auth/source.go @@ -117,7 +117,7 @@ func RegisterTypeConfig(typ Type, exemplar Config) { type Source struct { ID int64 `xorm:"pk autoincr"` Type Type - Name string `xorm:"UNIQUE"` + Name string `xorm:"UNIQUE"` // it can be the OIDC's provider name, see services/auth/source/oauth2/source_register.go: RegisterSource IsActive bool `xorm:"INDEX NOT NULL DEFAULT false"` IsSyncEnabled bool `xorm:"INDEX NOT NULL DEFAULT false"` TwoFactorPolicy string `xorm:"two_factor_policy NOT NULL DEFAULT ''"` diff --git a/models/git/branch.go b/models/git/branch.go index bc6e2a17486..698c43f1472 100644 --- a/models/git/branch.go +++ b/models/git/branch.go @@ -247,7 +247,7 @@ func DeleteBranches(ctx context.Context, repoID, doerID int64, branchIDs []int64 return err } for _, branch := range branches { - if err := AddDeletedBranch(ctx, repoID, branch.Name, doerID); err != nil { + if err := MarkBranchAsDeleted(ctx, repoID, branch.Name, doerID); err != nil { return err } } @@ -268,8 +268,8 @@ func UpdateBranch(ctx context.Context, repoID, pusherID int64, branchName string }) } -// AddDeletedBranch adds a deleted branch to the database -func AddDeletedBranch(ctx context.Context, repoID int64, branchName string, deletedByID int64) error { +// MarkBranchAsDeleted marks branch as deleted +func MarkBranchAsDeleted(ctx context.Context, repoID int64, branchName string, deletedByID int64) error { branch, err := GetBranch(ctx, repoID, branchName) if err != nil { return err @@ -583,3 +583,12 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o return newBranches, nil } + +// CountBranches returns the number of branches in the repository +func CountBranches(ctx context.Context, repoID int64, includeDeleted bool) (int64, error) { + sess := db.GetEngine(ctx).Where("repo_id=?", repoID) + if !includeDeleted { + sess.And("is_deleted=?", false) + } + return sess.Count(new(Branch)) +} diff --git a/models/git/branch_test.go b/models/git/branch_test.go index 9e6148946d7..3832df93509 100644 --- a/models/git/branch_test.go +++ b/models/git/branch_test.go @@ -28,8 +28,8 @@ func TestAddDeletedBranch(t *testing.T) { firstBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{ID: 1}) assert.True(t, firstBranch.IsDeleted) - assert.NoError(t, git_model.AddDeletedBranch(t.Context(), repo.ID, firstBranch.Name, firstBranch.DeletedByID)) - assert.NoError(t, git_model.AddDeletedBranch(t.Context(), repo.ID, "branch2", int64(1))) + assert.NoError(t, git_model.MarkBranchAsDeleted(t.Context(), repo.ID, firstBranch.Name, firstBranch.DeletedByID)) + assert.NoError(t, git_model.MarkBranchAsDeleted(t.Context(), repo.ID, "branch2", int64(1))) secondBranch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo.ID, Name: "branch2"}) assert.True(t, secondBranch.IsDeleted) @@ -263,3 +263,25 @@ func TestOnlyGetDeletedBranchOnCorrectRepo(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, deletedBranch) } + +func TestCountBranches(t *testing.T) { + // 1. Setup - Exactly like TestAddDeletedBranch + assert.NoError(t, unittest.PrepareTestDatabase()) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + + // 2. Execution - Using t.Context() to match the rest of the file + initialCount, err := git_model.CountBranches(t.Context(), repo.ID, false) + assert.NoError(t, err) + + // 3. Database Action - Using t.Context() + err = db.Insert(t.Context(), &git_model.Branch{ + RepoID: repo.ID, + Name: "test-branch-for-counting", + }) + assert.NoError(t, err) + + // 4. Verification + newCount, err := git_model.CountBranches(t.Context(), repo.ID, false) + assert.NoError(t, err) + assert.Equal(t, initialCount+1, newCount) +} diff --git a/models/issues/review_list.go b/models/issues/review_list.go index 86b1a2e76e6..d0430cd06d3 100644 --- a/models/issues/review_list.go +++ b/models/issues/review_list.go @@ -173,9 +173,9 @@ func GetReviewsByIssueID(ctx context.Context, issueID int64) (latestReviews, mig reviewersMap := make(map[int64][]*Review) // key is reviewer id originalReviewersMap := make(map[int64][]*Review) // key is original author id reviewTeamsMap := make(map[int64][]*Review) // key is reviewer team id - countedReivewTypes := []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest, ReviewTypeComment} + countedReviewTypes := []ReviewType{ReviewTypeApprove, ReviewTypeReject, ReviewTypeRequest, ReviewTypeComment} for _, review := range reviews { - if review.ReviewerTeamID == 0 && slices.Contains(countedReivewTypes, review.Type) && !review.Dismissed { + if review.ReviewerTeamID == 0 && slices.Contains(countedReviewTypes, review.Type) && !review.Dismissed { if review.OriginalAuthorID != 0 { originalReviewersMap[review.OriginalAuthorID] = append(originalReviewersMap[review.OriginalAuthorID], review) } else { diff --git a/models/organization/org.go b/models/organization/org.go index b4d28f54054..10a2c330e56 100644 --- a/models/organization/org.go +++ b/models/organization/org.go @@ -178,7 +178,7 @@ func (org *Organization) HomeLink() string { return org.AsUser().HomeLink() } -// FindOrgMembersOpts represensts find org members conditions +// FindOrgMembersOpts represents find org members conditions type FindOrgMembersOpts struct { db.ListOptions Doer *user_model.User diff --git a/models/repo/avatar.go b/models/repo/avatar.go index eff64bd2398..bfb08d9982d 100644 --- a/models/repo/avatar.go +++ b/models/repo/avatar.go @@ -41,20 +41,14 @@ func generateRandomAvatar(ctx context.Context, repo *Repository) error { idToString := strconv.FormatInt(repo.ID, 10) seed := idToString - img, err := avatar.RandomImage([]byte(seed)) - if err != nil { - return fmt.Errorf("RandomImage: %w", err) - } + img := avatar.RandomImageDefaultSize([]byte(seed)) repo.Avatar = idToString if err := storage.SaveFrom(storage.RepoAvatars, repo.CustomAvatarRelativePath(), func(w io.Writer) error { - if err := png.Encode(w, img); err != nil { - log.Error("Encode: %v", err) - } - return err + return png.Encode(w, img) }); err != nil { - return fmt.Errorf("Failed to create dir %s: %w", repo.CustomAvatarRelativePath(), err) + return fmt.Errorf("failed to create dir %s: %w", repo.CustomAvatarRelativePath(), err) } log.Info("New random avatar created for repository: %d", repo.ID) diff --git a/models/repo/repo.go b/models/repo/repo.go index 07b9bf30ccd..25207cc28b0 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -281,7 +281,7 @@ func (repo *Repository) SizeDetailsString() string { var str strings.Builder sizeDetails := repo.SizeDetails() for _, detail := range sizeDetails { - str.WriteString(fmt.Sprintf("%s: %s, ", detail.Name, base.FileSize(detail.Size))) + fmt.Fprintf(&str, "%s: %s, ", detail.Name, base.FileSize(detail.Size)) } return strings.TrimSuffix(str.String(), ", ") } @@ -422,52 +422,37 @@ func (repo *Repository) UnitEnabled(ctx context.Context, tp unit.Type) bool { return false } -// MustGetUnit always returns a RepoUnit object +// MustGetUnit always returns a RepoUnit object even if the unit doesn't exist (not enabled) func (repo *Repository) MustGetUnit(ctx context.Context, tp unit.Type) *RepoUnit { ru, err := repo.GetUnit(ctx, tp) if err == nil { return ru } - + if !errors.Is(err, util.ErrNotExist) { + setting.PanicInDevOrTesting("Failed to get unit %v for repository %d: %v", tp, repo.ID, err) + } + ru = &RepoUnit{RepoID: repo.ID, Type: tp} switch tp { case unit.TypeExternalWiki: - return &RepoUnit{ - Type: tp, - Config: new(ExternalWikiConfig), - } + ru.Config = new(ExternalWikiConfig) case unit.TypeExternalTracker: - return &RepoUnit{ - Type: tp, - Config: new(ExternalTrackerConfig), - } + ru.Config = new(ExternalTrackerConfig) case unit.TypePullRequests: - return &RepoUnit{ - Type: tp, - Config: new(PullRequestsConfig), - } + ru.Config = new(PullRequestsConfig) case unit.TypeIssues: - return &RepoUnit{ - Type: tp, - Config: new(IssuesConfig), - } + ru.Config = new(IssuesConfig) case unit.TypeActions: - return &RepoUnit{ - Type: tp, - Config: new(ActionsConfig), - } + ru.Config = new(ActionsConfig) case unit.TypeProjects: - cfg := new(ProjectsConfig) - cfg.ProjectsMode = ProjectsModeNone - return &RepoUnit{ - Type: tp, - Config: cfg, + ru.Config = new(ProjectsConfig) + default: // other units don't have config + } + if ru.Config != nil { + if err = ru.Config.FromDB(nil); err != nil { + setting.PanicInDevOrTesting("Failed to load default config for unit %v of repository %d: %v", tp, repo.ID, err) } } - - return &RepoUnit{ - Type: tp, - Config: new(UnitConfig), - } + return ru } // GetUnit returns a RepoUnit object diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go index d03d5e1e6a4..491e96770c4 100644 --- a/models/repo/repo_unit.go +++ b/models/repo/repo_unit.go @@ -134,10 +134,25 @@ type PullRequestsConfig struct { DefaultTargetBranch string } +func DefaultPullRequestsConfig() *PullRequestsConfig { + cfg := &PullRequestsConfig{ + AllowMerge: true, + AllowRebase: true, + AllowRebaseMerge: true, + AllowSquash: true, + AllowFastForwardOnly: true, + AllowRebaseUpdate: true, + DefaultAllowMaintainerEdit: true, + } + cfg.DefaultMergeStyle = MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle) + cfg.DefaultMergeStyle = util.IfZero(cfg.DefaultMergeStyle, MergeStyleMerge) + return cfg +} + // FromDB fills up a PullRequestsConfig from serialized format. func (cfg *PullRequestsConfig) FromDB(bs []byte) error { - // AllowRebaseUpdate = true as default for existing PullRequestConfig in DB - cfg.AllowRebaseUpdate = true + // set default values for existing PullRequestConfig in DB + *cfg = *DefaultPullRequestsConfig() return json.UnmarshalHandleDoubleEncode(bs, &cfg) } @@ -156,17 +171,8 @@ func (cfg *PullRequestsConfig) IsMergeStyleAllowed(mergeStyle MergeStyle) bool { mergeStyle == MergeStyleManuallyMerged && cfg.AllowManualMerge } -// GetDefaultMergeStyle returns the default merge style for this pull request -func (cfg *PullRequestsConfig) GetDefaultMergeStyle() MergeStyle { - if len(cfg.DefaultMergeStyle) != 0 { - return cfg.DefaultMergeStyle - } - - if setting.Repository.PullRequest.DefaultMergeStyle != "" { - return MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle) - } - - return MergeStyleMerge +func DefaultPullRequestsUnit(repoID int64) RepoUnit { + return RepoUnit{RepoID: repoID, Type: unit.TypePullRequests, Config: DefaultPullRequestsConfig()} } type ActionsConfig struct { @@ -241,6 +247,8 @@ type ProjectsConfig struct { // FromDB fills up a ProjectsConfig from serialized format. func (cfg *ProjectsConfig) FromDB(bs []byte) error { + // TODO: remove GetProjectsMode, only use ProjectsMode + cfg.ProjectsMode = ProjectsModeAll return json.UnmarshalHandleDoubleEncode(bs, &cfg) } diff --git a/models/repo/user_repo.go b/models/repo/user_repo.go index 08cf964bc8b..e15a64b01e8 100644 --- a/models/repo/user_repo.go +++ b/models/repo/user_repo.go @@ -147,19 +147,21 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us } // GetIssuePostersWithSearch returns users with limit of 30 whose username started with prefix that have authored an issue/pull request for the given repository -// If isShowFullName is set to true, also include full name prefix search -func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string, isShowFullName bool) ([]*user_model.User, error) { +// It searches with the "user.name" and "user.full_name" fields case-insensitively. +func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string) ([]*user_model.User, error) { users := make([]*user_model.User, 0, 30) - var prefixCond builder.Cond = builder.Like{"lower_name", strings.ToLower(search) + "%"} - if search != "" && isShowFullName { - prefixCond = prefixCond.Or(db.BuildCaseInsensitiveLike("full_name", "%"+search+"%")) - } cond := builder.In("`user`.id", builder.Select("poster_id").From("issue").Where( builder.Eq{"repo_id": repo.ID}. And(builder.Eq{"is_pull": isPull}), - ).GroupBy("poster_id")).And(prefixCond) + ).GroupBy("poster_id")) + + if search != "" { + var prefixCond builder.Cond = builder.Like{"lower_name", strings.ToLower(search) + "%"} + prefixCond = prefixCond.Or(db.BuildCaseInsensitiveLike("full_name", "%"+search+"%")) + cond = cond.And(prefixCond) + } return users, db.GetEngine(ctx). Where(cond). diff --git a/models/repo/user_repo_test.go b/models/repo/user_repo_test.go index a53cf39dc4c..cd8a0f1a1f7 100644 --- a/models/repo/user_repo_test.go +++ b/models/repo/user_repo_test.go @@ -44,12 +44,12 @@ func TestGetIssuePostersWithSearch(t *testing.T) { repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - users, err := repo_model.GetIssuePostersWithSearch(t.Context(), repo2, false, "USER", false /* full name */) + users, err := repo_model.GetIssuePostersWithSearch(t.Context(), repo2, false, "USER") require.NoError(t, err) require.Len(t, users, 1) assert.Equal(t, "user2", users[0].Name) - users, err = repo_model.GetIssuePostersWithSearch(t.Context(), repo2, false, "TW%O", true /* full name */) + users, err = repo_model.GetIssuePostersWithSearch(t.Context(), repo2, false, "TW%O") require.NoError(t, err) require.Len(t, users, 1) assert.Equal(t, "user2", users[0].Name) diff --git a/models/user/avatar.go b/models/user/avatar.go index 542bd93b982..5811d859117 100644 --- a/models/user/avatar.go +++ b/models/user/avatar.go @@ -30,22 +30,16 @@ func GenerateRandomAvatar(ctx context.Context, u *User) error { seed = u.Name } - img, err := avatar.RandomImage([]byte(seed)) - if err != nil { - return fmt.Errorf("RandomImage: %w", err) - } + img := avatar.RandomImageDefaultSize([]byte(seed)) u.Avatar = avatars.HashEmail(seed) - _, err = storage.Avatars.Stat(u.CustomAvatarRelativePath()) + _, err := storage.Avatars.Stat(u.CustomAvatarRelativePath()) if err != nil { // If unable to Stat the avatar file (usually it means non-existing), then try to save a new one // Don't share the images so that we can delete them easily if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error { - if err := png.Encode(w, img); err != nil { - log.Error("Encode: %v", err) - } - return nil + return png.Encode(w, img) }); err != nil { return fmt.Errorf("failed to save avatar %s: %w", u.CustomAvatarRelativePath(), err) } @@ -74,7 +68,7 @@ func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string { switch { case u.UseCustomAvatar: useLocalAvatar = true - case disableGravatar, setting.OfflineMode: + case disableGravatar: useLocalAvatar = true autoGenerateAvatar = true } diff --git a/models/user/list.go b/models/user/list.go index 4337c34963e..aaaa7965c89 100644 --- a/models/user/list.go +++ b/models/user/list.go @@ -24,7 +24,7 @@ func (users UserList) GetUserIDs() []int64 { return userIDs } -// GetTwoFaStatus return state of 2FA enrollement +// GetTwoFaStatus return state of 2FA enrollment func (users UserList) GetTwoFaStatus(ctx context.Context) map[int64]bool { results := make(map[int64]bool, len(users)) for _, user := range users { diff --git a/models/user/setting_options.go b/models/user/setting_options.go index 6d37ef63d55..587a46e8dec 100644 --- a/models/user/setting_options.go +++ b/models/user/setting_options.go @@ -11,10 +11,6 @@ const ( // SettingsKeyShowOutdatedComments is the setting key whether or not to show outdated comments in PRs SettingsKeyShowOutdatedComments = "comment_code.show_outdated" - // UserActivityPubPrivPem is user's private key - UserActivityPubPrivPem = "activitypub.priv_pem" - // UserActivityPubPubPem is user's public key - UserActivityPubPubPem = "activitypub.pub_pem" // SignupIP is the IP address that the user signed up with SignupIP = "signup.ip" // SignupUserAgent is the user agent that the user signed up with diff --git a/models/user/user.go b/models/user/user.go index f8e8b5c64a2..a74662bb12a 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -8,6 +8,7 @@ import ( "context" "encoding/hex" "fmt" + "html/template" "mime" "net/mail" "net/url" @@ -28,6 +29,7 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/htmlutil" "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" @@ -185,7 +187,7 @@ func (u *User) BeforeUpdate() { } // FIXME: this email doesn't need to be in lowercase, because the emails are mainly managed by the email table with lower_email field - // This trick could be removed in new releases to display the user inputed email as-is. + // This trick could be removed in new releases to display the user inputted email as-is. u.Email = strings.ToLower(u.Email) if !u.IsOrganization() { if len(u.AvatarEmail) == 0 { @@ -417,16 +419,6 @@ func (u *User) IsTokenAccessAllowed() bool { return u.Type == UserTypeIndividual || u.Type == UserTypeBot } -// DisplayName returns full name if it's not empty, -// returns username otherwise. -func (u *User) DisplayName() string { - trimmed := strings.TrimSpace(u.FullName) - if len(trimmed) > 0 { - return trimmed - } - return u.Name -} - // EmailTo returns a string suitable to be put into a e-mail `To:` header. func (u *User) EmailTo() string { sanitizedDisplayName := globalVars().emailToReplacer.Replace(u.DisplayName()) @@ -445,27 +437,45 @@ func (u *User) EmailTo() string { return fmt.Sprintf("%s <%s>", mime.QEncoding.Encode("utf-8", add.Name), add.Address) } -// GetDisplayName returns full name if it's not empty and DEFAULT_SHOW_FULL_NAME is set, -// returns username otherwise. +// TODO: DefaultShowFullName causes messy logic, there are already too many methods to display a user's "display name", need to refactor them +// * user.Name / user.FullName: directly used in templates +// * user.DisplayName(): always show FullName if it's not empty, otherwise show Name +// * user.GetDisplayName(): show FullName if it's not empty and DefaultShowFullName is set, otherwise show Name +// * user.ShortName(): used a lot in templates, but it should be removed and let frontend use "ellipsis" styles +// * activity action.ShortActUserName/GetActDisplayName/GetActDisplayNameTitle, etc: duplicate and messy + +// DisplayName returns full name if it's not empty, returns username otherwise. +func (u *User) DisplayName() string { + fullName := strings.TrimSpace(u.FullName) + if fullName != "" { + return fullName + } + return u.Name +} + +// GetDisplayName returns full name if it's not empty and DEFAULT_SHOW_FULL_NAME is set, otherwise, username. func (u *User) GetDisplayName() string { if setting.UI.DefaultShowFullName { - trimmed := strings.TrimSpace(u.FullName) - if len(trimmed) > 0 { - return trimmed + fullName := strings.TrimSpace(u.FullName) + if fullName != "" { + return fullName } } return u.Name } -// GetCompleteName returns the full name and username in the form of -// "Full Name (username)" if full name is not empty, otherwise it returns -// "username". -func (u *User) GetCompleteName() string { - trimmedFullName := strings.TrimSpace(u.FullName) - if len(trimmedFullName) > 0 { - return fmt.Sprintf("%s (%s)", trimmedFullName, u.Name) +// ShortName ellipses username to length (still used by many templates), it calls GetDisplayName and respects DEFAULT_SHOW_FULL_NAME +func (u *User) ShortName(length int) string { + return util.EllipsisDisplayString(u.GetDisplayName(), length) +} + +func (u *User) GetShortDisplayNameLinkHTML() template.HTML { + fullName := strings.TrimSpace(u.FullName) + displayName, displayTooltip := u.Name, fullName + if setting.UI.DefaultShowFullName && fullName != "" { + displayName, displayTooltip = fullName, u.Name } - return u.Name + return htmlutil.HTMLFormat(`%s`, u.HomeLink(), displayTooltip, displayName) } func gitSafeName(name string) string { @@ -488,14 +498,6 @@ func (u *User) GitName() string { return fmt.Sprintf("user-%d", u.ID) } -// ShortName ellipses username to length -func (u *User) ShortName(length int) string { - if setting.UI.DefaultShowFullName && len(u.FullName) > 0 { - return util.EllipsisDisplayString(u.FullName, length) - } - return util.EllipsisDisplayString(u.Name, length) -} - // IsMailable checks if a user is eligible to receive emails. // System users like Ghost and Gitea Actions are excluded. func (u *User) IsMailable() bool { diff --git a/modules/actions/workflows_test.go b/modules/actions/workflows_test.go index 77a65aae496..ea027366f7e 100644 --- a/modules/actions/workflows_test.go +++ b/modules/actions/workflows_test.go @@ -190,7 +190,7 @@ func TestDetectMatched(t *testing.T) { expected: true, }, { - desc: "HookEventSchedue(schedule) matches GithubEventSchedule(schedule)", + desc: "HookEventSchedule(schedule) matches GithubEventSchedule(schedule)", triggedEvent: webhook_module.HookEventSchedule, payload: nil, yamlOn: "on: schedule", diff --git a/modules/activitypub/client.go b/modules/activitypub/client.go deleted file mode 100644 index a9941f9b759..00000000000 --- a/modules/activitypub/client.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package activitypub - -import ( - "bytes" - "context" - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "fmt" - "net/http" - "strings" - "time" - - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/proxy" - "code.gitea.io/gitea/modules/setting" - - "github.com/42wim/httpsig" -) - -const ( - // ActivityStreamsContentType const - ActivityStreamsContentType = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"` - httpsigExpirationTime = 60 -) - -// Gets the current time as an RFC 2616 formatted string -// RFC 2616 requires RFC 1123 dates but with GMT instead of UTC -func CurrentTime() string { - return strings.ReplaceAll(time.Now().UTC().Format(time.RFC1123), "UTC", "GMT") -} - -func containsRequiredHTTPHeaders(method string, headers []string) error { - var hasRequestTarget, hasDate, hasDigest bool - for _, header := range headers { - hasRequestTarget = hasRequestTarget || header == httpsig.RequestTarget - hasDate = hasDate || header == "Date" - hasDigest = hasDigest || header == "Digest" - } - if !hasRequestTarget { - return fmt.Errorf("missing http header for %s: %s", method, httpsig.RequestTarget) - } else if !hasDate { - return fmt.Errorf("missing http header for %s: Date", method) - } else if !hasDigest && method != http.MethodGet { - return fmt.Errorf("missing http header for %s: Digest", method) - } - return nil -} - -// Client struct -type Client struct { - client *http.Client - algs []httpsig.Algorithm - digestAlg httpsig.DigestAlgorithm - getHeaders []string - postHeaders []string - priv *rsa.PrivateKey - pubID string -} - -// NewClient function -func NewClient(ctx context.Context, user *user_model.User, pubID string) (c *Client, err error) { - if err = containsRequiredHTTPHeaders(http.MethodGet, setting.Federation.GetHeaders); err != nil { - return nil, err - } else if err = containsRequiredHTTPHeaders(http.MethodPost, setting.Federation.PostHeaders); err != nil { - return nil, err - } - - priv, err := GetPrivateKey(ctx, user) - if err != nil { - return nil, err - } - privPem, _ := pem.Decode([]byte(priv)) - privParsed, err := x509.ParsePKCS1PrivateKey(privPem.Bytes) - if err != nil { - return nil, err - } - - c = &Client{ - client: &http.Client{ - Transport: &http.Transport{ - Proxy: proxy.Proxy(), - }, - }, - algs: setting.HttpsigAlgs, - digestAlg: httpsig.DigestAlgorithm(setting.Federation.DigestAlgorithm), - getHeaders: setting.Federation.GetHeaders, - postHeaders: setting.Federation.PostHeaders, - priv: privParsed, - pubID: pubID, - } - return c, err -} - -// NewRequest function -func (c *Client) NewRequest(b []byte, to string) (req *http.Request, err error) { - buf := bytes.NewBuffer(b) - req, err = http.NewRequest(http.MethodPost, to, buf) - if err != nil { - return nil, err - } - req.Header.Add("Content-Type", ActivityStreamsContentType) - req.Header.Add("Date", CurrentTime()) - req.Header.Add("User-Agent", "Gitea/"+setting.AppVer) - signer, _, err := httpsig.NewSigner(c.algs, c.digestAlg, c.postHeaders, httpsig.Signature, httpsigExpirationTime) - if err != nil { - return nil, err - } - err = signer.SignRequest(c.priv, c.pubID, req, b) - return req, err -} - -// Post function -func (c *Client) Post(b []byte, to string) (resp *http.Response, err error) { - var req *http.Request - if req, err = c.NewRequest(b, to); err != nil { - return nil, err - } - resp, err = c.client.Do(req) - return resp, err -} diff --git a/modules/activitypub/client_test.go b/modules/activitypub/client_test.go deleted file mode 100644 index 361270a8005..00000000000 --- a/modules/activitypub/client_test.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package activitypub - -import ( - "fmt" - "io" - "net/http" - "net/http/httptest" - "testing" - - "code.gitea.io/gitea/models/unittest" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/setting" - - "github.com/stretchr/testify/assert" -) - -func TestActivityPubSignedPost(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - pubID := "https://example.com/pubID" - c, err := NewClient(t.Context(), user, pubID) - assert.NoError(t, err) - - expected := "BODY" - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Regexp(t, "^"+setting.Federation.DigestAlgorithm, r.Header.Get("Digest")) - assert.Contains(t, r.Header.Get("Signature"), pubID) - assert.Equal(t, ActivityStreamsContentType, r.Header.Get("Content-Type")) - body, err := io.ReadAll(r.Body) - assert.NoError(t, err) - assert.Equal(t, expected, string(body)) - fmt.Fprint(w, expected) - })) - defer srv.Close() - - r, err := c.Post([]byte(expected), srv.URL) - assert.NoError(t, err) - defer r.Body.Close() - body, err := io.ReadAll(r.Body) - assert.NoError(t, err) - assert.Equal(t, expected, string(body)) -} diff --git a/modules/activitypub/main_test.go b/modules/activitypub/main_test.go deleted file mode 100644 index 4591f1fa555..00000000000 --- a/modules/activitypub/main_test.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package activitypub - -import ( - "testing" - - "code.gitea.io/gitea/models/unittest" - - _ "code.gitea.io/gitea/models" - _ "code.gitea.io/gitea/models/actions" - _ "code.gitea.io/gitea/models/activities" -) - -func TestMain(m *testing.M) { - unittest.MainTest(m) -} diff --git a/modules/activitypub/user_settings.go b/modules/activitypub/user_settings.go deleted file mode 100644 index 7f939af352a..00000000000 --- a/modules/activitypub/user_settings.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package activitypub - -import ( - "context" - - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/util" -) - -const rsaBits = 3072 - -// GetKeyPair function returns a user's private and public keys -func GetKeyPair(ctx context.Context, user *user_model.User) (pub, priv string, err error) { - var settings map[string]*user_model.Setting - settings, err = user_model.GetSettings(ctx, user.ID, []string{user_model.UserActivityPubPrivPem, user_model.UserActivityPubPubPem}) - if err != nil { - return pub, priv, err - } else if len(settings) == 0 { - if priv, pub, err = util.GenerateKeyPair(rsaBits); err != nil { - return pub, priv, err - } - if err = user_model.SetUserSetting(ctx, user.ID, user_model.UserActivityPubPrivPem, priv); err != nil { - return pub, priv, err - } - if err = user_model.SetUserSetting(ctx, user.ID, user_model.UserActivityPubPubPem, pub); err != nil { - return pub, priv, err - } - return pub, priv, err - } - priv = settings[user_model.UserActivityPubPrivPem].SettingValue - pub = settings[user_model.UserActivityPubPubPem].SettingValue - return pub, priv, err -} - -// GetPublicKey function returns a user's public key -func GetPublicKey(ctx context.Context, user *user_model.User) (pub string, err error) { - pub, _, err = GetKeyPair(ctx, user) - return pub, err -} - -// GetPrivateKey function returns a user's private key -func GetPrivateKey(ctx context.Context, user *user_model.User) (priv string, err error) { - _, priv, err = GetKeyPair(ctx, user) - return priv, err -} diff --git a/modules/activitypub/user_settings_test.go b/modules/activitypub/user_settings_test.go deleted file mode 100644 index 105d4aedea1..00000000000 --- a/modules/activitypub/user_settings_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package activitypub - -import ( - "testing" - - "code.gitea.io/gitea/models/unittest" - user_model "code.gitea.io/gitea/models/user" - - _ "code.gitea.io/gitea/models" // https://forum.gitea.com/t/testfixtures-could-not-clean-table-access-no-such-table-access/4137/4 - - "github.com/stretchr/testify/assert" -) - -func TestUserSettings(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - pub, priv, err := GetKeyPair(t.Context(), user1) - assert.NoError(t, err) - pub1, err := GetPublicKey(t.Context(), user1) - assert.NoError(t, err) - assert.Equal(t, pub, pub1) - priv1, err := GetPrivateKey(t.Context(), user1) - assert.NoError(t, err) - assert.Equal(t, priv, priv1) -} diff --git a/modules/assetfs/embed.go b/modules/assetfs/embed.go index 0b544635db0..7c90e3aca21 100644 --- a/modules/assetfs/embed.go +++ b/modules/assetfs/embed.go @@ -260,7 +260,7 @@ func (fi *embeddedFileInfo) Mode() fs.FileMode { } func (fi *embeddedFileInfo) ModTime() time.Time { - return getExecutableModTime() + return GetExecutableModTime() } func (fi *embeddedFileInfo) IsDir() bool { @@ -279,9 +279,9 @@ func (fi *embeddedFileInfo) Info() (fs.FileInfo, error) { return fi, nil } -// getExecutableModTime returns the modification time of the executable file. +// GetExecutableModTime returns the modification time of the executable file. // In bindata, we can't use the ModTime of the files because we need to make the build reproducible -var getExecutableModTime = sync.OnceValue(func() (modTime time.Time) { +var GetExecutableModTime = sync.OnceValue(func() (modTime time.Time) { exePath, err := os.Executable() if err != nil { return modTime diff --git a/modules/auth/password/hash/setting.go b/modules/auth/password/hash/setting.go index f0715f31e1a..e0caed5ba88 100644 --- a/modules/auth/password/hash/setting.go +++ b/modules/auth/password/hash/setting.go @@ -14,7 +14,7 @@ const DefaultHashAlgorithmName = "pbkdf2" var DefaultHashAlgorithm *PasswordHashAlgorithm -// aliasAlgorithNames provides a mapping between the value of PASSWORD_HASH_ALGO +// aliasAlgorithmNames provides a mapping between the value of PASSWORD_HASH_ALGO // configured in the app.ini and the parameters used within the hashers internally. // // If it is necessary to change the default parameters for any hasher in future you diff --git a/modules/avatar/avatar.go b/modules/avatar/avatar.go index 106215ec0b2..3b622b99af0 100644 --- a/modules/avatar/avatar.go +++ b/modules/avatar/avatar.go @@ -28,21 +28,18 @@ import ( // than the size after resizing. const DefaultAvatarSize = 256 -// RandomImageSize generates and returns a random avatar image unique to input data +// RandomImageWithSize generates and returns a random avatar image unique to input data // in custom size (height and width). -func RandomImageSize(size int, data []byte) (image.Image, error) { +func RandomImageWithSize(size int, data []byte) image.Image { // we use white as background, and use dark colors to draw blocks - imgMaker, err := identicon.New(size, color.White, identicon.DarkColors...) - if err != nil { - return nil, fmt.Errorf("identicon.New: %w", err) - } - return imgMaker.Make(data), nil + imgMaker := identicon.New(size, color.White, identicon.DarkColors) + return imgMaker.Make(data) } -// RandomImage generates and returns a random avatar image unique to input data +// RandomImageDefaultSize generates and returns a random avatar image unique to input data // in default size (height and width). -func RandomImage(data []byte) (image.Image, error) { - return RandomImageSize(DefaultAvatarSize*setting.Avatar.RenderedSizeFactor, data) +func RandomImageDefaultSize(data []byte) image.Image { + return RandomImageWithSize(DefaultAvatarSize*setting.Avatar.RenderedSizeFactor, data) } // processAvatarImage process the avatar image data, crop and resize it if necessary. diff --git a/modules/avatar/avatar_test.go b/modules/avatar/avatar_test.go index a5a1a7c1b06..5414184b6b2 100644 --- a/modules/avatar/avatar_test.go +++ b/modules/avatar/avatar_test.go @@ -15,19 +15,6 @@ import ( "github.com/stretchr/testify/assert" ) -func Test_RandomImageSize(t *testing.T) { - _, err := RandomImageSize(0, []byte("gitea@local")) - assert.Error(t, err) - - _, err = RandomImageSize(64, []byte("gitea@local")) - assert.NoError(t, err) -} - -func Test_RandomImage(t *testing.T) { - _, err := RandomImage([]byte("gitea@local")) - assert.NoError(t, err) -} - func Test_ProcessAvatarPNG(t *testing.T) { setting.Avatar.MaxWidth = 4096 setting.Avatar.MaxHeight = 4096 @@ -134,3 +121,18 @@ func Test_ProcessAvatarImage(t *testing.T) { _, err = processAvatarImage(origin, 262144) assert.ErrorContains(t, err, "image width is too large: 10 > 5") } + +func BenchmarkRandomImage(b *testing.B) { + b.Run("size-48", func(b *testing.B) { + for b.Loop() { + // BenchmarkRandomImage/size-48-12 49549 22899 ns/op + RandomImageWithSize(48, []byte("test-content")) + } + }) + b.Run("size-96", func(b *testing.B) { + for b.Loop() { + // BenchmarkRandomImage/size-96-12 13816 88187 ns/op + RandomImageWithSize(96, []byte("test-content")) + } + }) +} diff --git a/modules/avatar/identicon/identicon.go b/modules/avatar/identicon/identicon.go index 19f87da85af..a1a0f0d7bfe 100644 --- a/modules/avatar/identicon/identicon.go +++ b/modules/avatar/identicon/identicon.go @@ -8,13 +8,14 @@ package identicon import ( "crypto/sha256" - "errors" - "fmt" "image" "image/color" ) -const minImageSize = 16 +const ( + minImageSize = 16 + maxImageSize = 2048 +) // Identicon is used to generate pseudo-random avatars type Identicon struct { @@ -24,25 +25,17 @@ type Identicon struct { rect image.Rectangle } -// New returns an Identicon struct with the correct settings -// size image size -// back background color -// fore all possible foreground colors. only one foreground color will be picked randomly for one image -func New(size int, back color.Color, fore ...color.Color) (*Identicon, error) { - if len(fore) == 0 { - return nil, errors.New("foreground is not set") - } - - if size < minImageSize { - return nil, fmt.Errorf("size %d is smaller than min size %d", size, minImageSize) - } - +// New returns an Identicon struct. +// Only one foreground color will be picked randomly for one image. +func New(size int, backColor color.Color, foreColors []color.Color) *Identicon { + size = max(size, minImageSize) + size = min(size, maxImageSize) return &Identicon{ - foreColors: fore, - backColor: back, + foreColors: foreColors, + backColor: backColor, size: size, rect: image.Rect(0, 0, size, size), - }, nil + } } // Make generates an avatar by data diff --git a/modules/avatar/identicon/identicon_test.go b/modules/avatar/identicon/identicon_test.go index 23bcc73e2e6..6af4b41cce7 100644 --- a/modules/avatar/identicon/identicon_test.go +++ b/modules/avatar/identicon/identicon_test.go @@ -23,7 +23,7 @@ func TestGenerate(t *testing.T) { } backColor := color.White - imgMaker, err := New(64, backColor, DarkColors...) + imgMaker, err := New(64, backColor, DarkColors) assert.NoError(t, err) for i := 0; i < 100; i++ { s := strconv.Itoa(i) diff --git a/modules/git/commit.go b/modules/git/commit.go index b98d36d946f..dfecfe60575 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -86,50 +86,6 @@ func (c *Commit) GetCommitByPath(relpath string) (*Commit, error) { return c.repo.getCommitByPathWithID(c.ID, relpath) } -// AddChanges marks local changes to be ready for commit. -func AddChanges(ctx context.Context, repoPath string, all bool, files ...string) error { - cmd := gitcmd.NewCommand().AddArguments("add") - if all { - cmd.AddArguments("--all") - } - cmd.AddDashesAndList(files...) - _, _, err := cmd.WithDir(repoPath).RunStdString(ctx) - return err -} - -// CommitChangesOptions the options when a commit created -type CommitChangesOptions struct { - Committer *Signature - Author *Signature - Message string -} - -// CommitChanges commits local changes with given committer, author and message. -// If author is nil, it will be the same as committer. -func CommitChanges(ctx context.Context, repoPath string, opts CommitChangesOptions) error { - cmd := gitcmd.NewCommand() - if opts.Committer != nil { - cmd.AddOptionValues("-c", "user.name="+opts.Committer.Name) - cmd.AddOptionValues("-c", "user.email="+opts.Committer.Email) - } - cmd.AddArguments("commit") - - if opts.Author == nil { - opts.Author = opts.Committer - } - if opts.Author != nil { - cmd.AddOptionFormat("--author='%s <%s>'", opts.Author.Name, opts.Author.Email) - } - cmd.AddOptionFormat("--message=%s", opts.Message) - - _, _, err := cmd.WithDir(repoPath).RunStdString(ctx) - // No stderr but exit status 1 means nothing to commit. - if gitcmd.IsErrorExitCode(err, 1) { - return nil - } - return err -} - // CommitsByRange returns the specific page commits before current revision, every page's number default by CommitsRangeSize func (c *Commit) CommitsByRange(page, pageSize int, not, since, until string) ([]*Commit, error) { return c.repo.commitsByRangeWithTime(c.ID, page, pageSize, not, since, until) diff --git a/modules/git/foreachref/format.go b/modules/git/foreachref/format.go index d2f9998fe81..87c1c9a4ff9 100644 --- a/modules/git/foreachref/format.go +++ b/modules/git/foreachref/format.go @@ -53,7 +53,7 @@ func (f Format) Flag() string { var formatFlag strings.Builder for i, field := range f.fieldNames { // field key and field value - formatFlag.WriteString(fmt.Sprintf("%s %%(%s)", field, field)) + fmt.Fprintf(&formatFlag, "%s %%(%s)", field, field) if i < len(f.fieldNames)-1 { // note: escape delimiters to allow control characters as @@ -72,7 +72,7 @@ func (f Format) Parser(r io.Reader) *Parser { return NewParser(r, f) } -// hexEscaped produces hex-escpaed characters from a string. For example, "\n\0" +// hexEscaped produces hex-escaped characters from a string. For example, "\n\0" // would turn into "%0a%00". func (f Format) hexEscaped(delim []byte) string { var escaped strings.Builder diff --git a/modules/git/grep.go b/modules/git/grep.go index 051a7a1d405..47f66b88b24 100644 --- a/modules/git/grep.go +++ b/modules/git/grep.go @@ -11,6 +11,7 @@ import ( "slices" "strconv" "strings" + "time" "code.gitea.io/gitea/modules/git/gitcmd" "code.gitea.io/gitea/modules/util" @@ -39,6 +40,10 @@ type GrepOptions struct { PathspecList []string } +// grepSearchTimeout is the timeout for git grep search, it should be long enough to get results +// but not too long to cause performance issues +const grepSearchTimeout = 30 * time.Second + func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepOptions) ([]*GrepResult, error) { /* The output is like this ( "^@" means \x00): @@ -76,6 +81,7 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO stdoutReader, stdoutReaderClose := cmd.MakeStdoutPipe() defer stdoutReaderClose() err := cmd.WithDir(repo.Path). + WithTimeout(grepSearchTimeout). WithPipelineFunc(func(ctx gitcmd.Context) error { isInBlock := false rd := bufio.NewReaderSize(stdoutReader, util.IfZero(opts.MaxLineLength, 16*1024)) diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go index dd3efab7a57..e23cf0a92a2 100644 --- a/modules/httpcache/httpcache.go +++ b/modules/httpcache/httpcache.go @@ -60,21 +60,6 @@ func CacheControlForPrivateStatic() *CacheControlOptions { } } -// HandleGenericETagCache handles ETag-based caching for a HTTP request. -// It returns true if the request was handled. -func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag string) (handled bool) { - if len(etag) > 0 { - w.Header().Set("Etag", etag) - if checkIfNoneMatchIsValid(req, etag) { - w.WriteHeader(http.StatusNotModified) - return true - } - } - // not sure whether it is a public content, so just use "private" (old behavior) - SetCacheControlInHeader(w.Header(), CacheControlForPrivateStatic()) - return false -} - // checkIfNoneMatchIsValid tests if the header If-None-Match matches the ETag func checkIfNoneMatchIsValid(req *http.Request, etag string) bool { ifNoneMatch := req.Header.Get("If-None-Match") @@ -89,10 +74,18 @@ func checkIfNoneMatchIsValid(req *http.Request, etag string) bool { return false } -// HandleGenericETagTimeCache handles ETag-based caching with Last-Modified caching for a HTTP request. +func HandleGenericETagPublicCache(req *http.Request, w http.ResponseWriter, etag string, lastModified *time.Time) bool { + return handleGenericETagTimeCache(req, w, etag, lastModified, CacheControlForPublicStatic()) +} + +func HandleGenericETagPrivateCache(req *http.Request, w http.ResponseWriter, etag string, lastModified *time.Time) bool { + return handleGenericETagTimeCache(req, w, etag, lastModified, CacheControlForPrivateStatic()) +} + +// handleGenericETagTimeCache handles ETag-based caching with Last-Modified caching for the HTTP request. // It returns true if the request was handled. -func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag string, lastModified *time.Time) (handled bool) { - if len(etag) > 0 { +func handleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag string, lastModified *time.Time, cacheControlOpts *CacheControlOptions) (handled bool) { + if etag != "" { w.Header().Set("Etag", etag) } if lastModified != nil && !lastModified.IsZero() { @@ -100,7 +93,7 @@ func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag s w.Header().Set("Last-Modified", lastModified.UTC().Format(http.TimeFormat)) } - if len(etag) > 0 { + if etag != "" { if checkIfNoneMatchIsValid(req, etag) { w.WriteHeader(http.StatusNotModified) return true @@ -117,7 +110,6 @@ func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag s } } - // not sure whether it is a public content, so just use "private" (old behavior) - SetCacheControlInHeader(w.Header(), CacheControlForPrivateStatic()) + SetCacheControlInHeader(w.Header(), cacheControlOpts) return false } diff --git a/modules/httpcache/httpcache_test.go b/modules/httpcache/httpcache_test.go index d81f06097c1..d510cbe4d7f 100644 --- a/modules/httpcache/httpcache_test.go +++ b/modules/httpcache/httpcache_test.go @@ -6,92 +6,73 @@ package httpcache import ( "net/http" "net/http/httptest" - "strings" "testing" + "time" + + "code.gitea.io/gitea/modules/util" "github.com/stretchr/testify/assert" ) -func countFormalHeaders(h http.Header) (c int) { - for k := range h { - // ignore our headers for internal usage - if strings.HasPrefix(k, "X-Gitea-") { - continue - } - c++ - } - return c -} - func TestHandleGenericETagCache(t *testing.T) { - etag := `"test"` + matchedEtag := `"matched-etag"` + lastModifiedTime := new(time.Date(2021, time.January, 2, 15, 4, 5, 0, time.FixedZone("test-zone", 8*3600))) + lastModified := lastModifiedTime.UTC().Format(http.TimeFormat) + cacheControl := "max-age=0, private, must-revalidate, no-transform" + type testCase struct { + name string + reqHeaders map[string]string + wantHandled bool + wantHeaders map[string]string + wantStatus int + } + cases := []testCase{ + { + name: "No If-None-Match", + wantHandled: false, + wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": cacheControl, "Etag": matchedEtag}, + }, + { + name: "Mismatched If-None-Match", + reqHeaders: map[string]string{"If-None-Match": `"mismatched-etag"`}, + wantHandled: false, + wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": cacheControl, "Etag": matchedEtag}, + }, + { + name: "Matched If-None-Match", + reqHeaders: map[string]string{"If-None-Match": matchedEtag}, + wantHandled: true, + wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": "", "Etag": matchedEtag}, + wantStatus: http.StatusNotModified, + }, + { + name: "Multiple Mismatched If-None-Match", + reqHeaders: map[string]string{"If-None-Match": `"mismatched-etag1", "mismatched-etag2"`}, + wantHandled: false, + wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": cacheControl, "Etag": matchedEtag}, + }, + { + name: "Multiple Matched If-None-Match", + reqHeaders: map[string]string{"If-None-Match": `"mismatched-etag", ` + matchedEtag}, + wantHandled: true, + wantHeaders: map[string]string{"Last-Modified": lastModified, "Cache-Control": "", "Etag": matchedEtag}, + wantStatus: http.StatusNotModified, + }, + } - t.Run("No_If-None-Match", func(t *testing.T) { - req := &http.Request{Header: make(http.Header)} - w := httptest.NewRecorder() - - handled := HandleGenericETagCache(req, w, etag) - - assert.False(t, handled) - assert.Equal(t, 2, countFormalHeaders(w.Header())) - assert.Contains(t, w.Header(), "Cache-Control") - assert.Contains(t, w.Header(), "Etag") - assert.Equal(t, etag, w.Header().Get("Etag")) - }) - t.Run("Wrong_If-None-Match", func(t *testing.T) { - req := &http.Request{Header: make(http.Header)} - w := httptest.NewRecorder() - - req.Header.Set("If-None-Match", `"wrong etag"`) - - handled := HandleGenericETagCache(req, w, etag) - - assert.False(t, handled) - assert.Equal(t, 2, countFormalHeaders(w.Header())) - assert.Contains(t, w.Header(), "Cache-Control") - assert.Contains(t, w.Header(), "Etag") - assert.Equal(t, etag, w.Header().Get("Etag")) - }) - t.Run("Correct_If-None-Match", func(t *testing.T) { - req := &http.Request{Header: make(http.Header)} - w := httptest.NewRecorder() - - req.Header.Set("If-None-Match", etag) - - handled := HandleGenericETagCache(req, w, etag) - - assert.True(t, handled) - assert.Equal(t, 1, countFormalHeaders(w.Header())) - assert.Contains(t, w.Header(), "Etag") - assert.Equal(t, etag, w.Header().Get("Etag")) - assert.Equal(t, http.StatusNotModified, w.Code) - }) - t.Run("Multiple_Wrong_If-None-Match", func(t *testing.T) { - req := &http.Request{Header: make(http.Header)} - w := httptest.NewRecorder() - - req.Header.Set("If-None-Match", `"wrong etag", "wrong etag "`) - - handled := HandleGenericETagCache(req, w, etag) - - assert.False(t, handled) - assert.Equal(t, 2, countFormalHeaders(w.Header())) - assert.Contains(t, w.Header(), "Cache-Control") - assert.Contains(t, w.Header(), "Etag") - assert.Equal(t, etag, w.Header().Get("Etag")) - }) - t.Run("Multiple_Correct_If-None-Match", func(t *testing.T) { - req := &http.Request{Header: make(http.Header)} - w := httptest.NewRecorder() - - req.Header.Set("If-None-Match", `"wrong etag", `+etag) - - handled := HandleGenericETagCache(req, w, etag) - - assert.True(t, handled) - assert.Equal(t, 1, countFormalHeaders(w.Header())) - assert.Contains(t, w.Header(), "Etag") - assert.Equal(t, etag, w.Header().Get("Etag")) - assert.Equal(t, http.StatusNotModified, w.Code) - }) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "http://example.com/test", nil) + for k, v := range tc.reqHeaders { + req.Header.Set(k, v) + } + w := httptest.NewRecorder() + assert.Equal(t, tc.wantHandled, HandleGenericETagPrivateCache(req, w, matchedEtag, lastModifiedTime)) + resp := w.Result() + for k, v := range tc.wantHeaders { + assert.Equal(t, v, resp.Header.Get(k)) + } + assert.Equal(t, tc.wantStatus, util.Iif(resp.StatusCode == http.StatusOK, 0, resp.StatusCode)) + }) + } } diff --git a/modules/httplib/url.go b/modules/httplib/url.go index 5f35e669643..a689c9e98ee 100644 --- a/modules/httplib/url.go +++ b/modules/httplib/url.go @@ -46,14 +46,14 @@ func IsRelativeURL(s string) bool { func getRequestScheme(req *http.Request) string { // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto - if s := req.Header.Get("X-Forwarded-Proto"); s != "" { - return s + if proto, ok := parseForwardedProtoValue(req.Header.Get("X-Forwarded-Proto")); ok { + return proto } - if s := req.Header.Get("X-Forwarded-Protocol"); s != "" { - return s + if proto, ok := parseForwardedProtoValue(req.Header.Get("X-Forwarded-Protocol")); ok { + return proto } - if s := req.Header.Get("X-Url-Scheme"); s != "" { - return s + if proto, ok := parseForwardedProtoValue(req.Header.Get("X-Url-Scheme")); ok { + return proto } if s := req.Header.Get("Front-End-Https"); s != "" { return util.Iif(s == "on", "https", "http") @@ -64,6 +64,13 @@ func getRequestScheme(req *http.Request) string { return "" } +func parseForwardedProtoValue(val string) (string, bool) { + if val == "http" || val == "https" { + return val, true + } + return "", false +} + // GuessCurrentAppURL tries to guess the current full public URL (with sub-path) by http headers. It always has a '/' suffix, exactly the same as setting.AppURL // TODO: should rename it to GuessCurrentPublicURL in the future func GuessCurrentAppURL(ctx context.Context) string { @@ -72,6 +79,10 @@ func GuessCurrentAppURL(ctx context.Context) string { // GuessCurrentHostURL tries to guess the current full host URL (no sub-path) by http headers, there is no trailing slash. func GuessCurrentHostURL(ctx context.Context) string { + // "never" means always trust ROOT_URL and skip any request header detection. + if setting.PublicURLDetection == setting.PublicURLNever { + return strings.TrimSuffix(setting.AppURL, setting.AppSubURL+"/") + } // Try the best guess to get the current host URL (will be used for public URL) by http headers. // At the moment, if site admin doesn't configure the proxy headers correctly, then Gitea would guess wrong. // There are some cases: diff --git a/modules/httplib/url_test.go b/modules/httplib/url_test.go index 4270d9fa895..373693693b5 100644 --- a/modules/httplib/url_test.go +++ b/modules/httplib/url_test.go @@ -47,6 +47,7 @@ func TestGuessCurrentHostURL(t *testing.T) { defer test.MockVariableValue(&setting.AppURL, "http://cfg-host/sub/")() defer test.MockVariableValue(&setting.AppSubURL, "/sub")() headersWithProto := http.Header{"X-Forwarded-Proto": {"https"}} + maliciousProtoHeaders := http.Header{"X-Forwarded-Proto": {"http://attacker.host/?trash="}} t.Run("Legacy", func(t *testing.T) { defer test.MockVariableValue(&setting.PublicURLDetection, setting.PublicURLLegacy)() @@ -60,6 +61,9 @@ func TestGuessCurrentHostURL(t *testing.T) { // if "X-Forwarded-Proto" exists, then use it and "Host" header ctx = context.WithValue(t.Context(), RequestContextKey, &http.Request{Host: "req-host:3000", Header: headersWithProto}) assert.Equal(t, "https://req-host:3000", GuessCurrentHostURL(ctx)) + + ctx = context.WithValue(t.Context(), RequestContextKey, &http.Request{Host: "req-host:3000", Header: maliciousProtoHeaders}) + assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(ctx)) }) t.Run("Auto", func(t *testing.T) { @@ -76,6 +80,24 @@ func TestGuessCurrentHostURL(t *testing.T) { ctx = context.WithValue(t.Context(), RequestContextKey, &http.Request{Host: "req-host:3000", Header: headersWithProto}) assert.Equal(t, "https://req-host:3000", GuessCurrentHostURL(ctx)) + + ctx = context.WithValue(t.Context(), RequestContextKey, &http.Request{Host: "req-host:3000", Header: maliciousProtoHeaders}) + assert.Equal(t, "http://req-host:3000", GuessCurrentHostURL(ctx)) + }) + + t.Run("Never", func(t *testing.T) { + defer test.MockVariableValue(&setting.PublicURLDetection, setting.PublicURLNever)() + + assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(t.Context())) + + ctx := context.WithValue(t.Context(), RequestContextKey, &http.Request{Host: "req-host:3000"}) + assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(ctx)) + + ctx = context.WithValue(t.Context(), RequestContextKey, &http.Request{Host: "req-host:3000", TLS: &tls.ConnectionState{}}) + assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(ctx)) + + ctx = context.WithValue(t.Context(), RequestContextKey, &http.Request{Host: "req-host:3000", Header: headersWithProto}) + assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(ctx)) }) } diff --git a/modules/indexer/code/elasticsearch/elasticsearch.go b/modules/indexer/code/elasticsearch/elasticsearch.go index 99f974b646d..9d170528ad3 100644 --- a/modules/indexer/code/elasticsearch/elasticsearch.go +++ b/modules/indexer/code/elasticsearch/elasticsearch.go @@ -393,7 +393,7 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int elastic.NewHighlight(). Field("content"). Field("filename"). - NumOfFragments(0). // return all highting content on fragments + NumOfFragments(0). // return all highlighting content on fragments HighlighterType("fvh"), ). Sort("_score", false). @@ -426,7 +426,7 @@ func (b *Indexer) Search(ctx context.Context, opts *internal.SearchOptions) (int elastic.NewHighlight(). Field("content"). Field("filename"). - NumOfFragments(0). // return all highting content on fragments + NumOfFragments(0). // return all highlighting content on fragments HighlighterType("fvh"), ). Sort("_score", false). diff --git a/modules/lfstransfer/backend/backend.go b/modules/lfstransfer/backend/backend.go index f4e6157091a..92f08eac136 100644 --- a/modules/lfstransfer/backend/backend.go +++ b/modules/lfstransfer/backend/backend.go @@ -95,7 +95,7 @@ func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args trans var respBody lfs.BatchResponse err = json.Unmarshal(respBytes, &respBody) if err != nil { - g.logger.Log("json umarshal error", err) + g.logger.Log("json unmarshal error", err) return nil, err } diff --git a/modules/lfstransfer/backend/lock.go b/modules/lfstransfer/backend/lock.go index 2c3c16a9bb0..5bfa3a89c30 100644 --- a/modules/lfstransfer/backend/lock.go +++ b/modules/lfstransfer/backend/lock.go @@ -69,7 +69,7 @@ func (g *giteaLockBackend) Create(path, refname string) (transfer.Lock, error) { var respBody lfslock.LFSLockResponse err = json.Unmarshal(respBytes, &respBody) if err != nil { - g.logger.Log("json umarshal error", err) + g.logger.Log("json unmarshal error", err) return nil, err } @@ -202,7 +202,7 @@ func (g *giteaLockBackend) queryLocks(v url.Values) ([]transfer.Lock, string, er var respBody lfslock.LFSLockList err = json.Unmarshal(respBytes, &respBody) if err != nil { - g.logger.Log("json umarshal error", err) + g.logger.Log("json unmarshal error", err) return nil, "", err } diff --git a/modules/lfstransfer/logger.go b/modules/lfstransfer/logger.go index 517c2d9ba13..dfb9c0bb660 100644 --- a/modules/lfstransfer/logger.go +++ b/modules/lfstransfer/logger.go @@ -17,5 +17,5 @@ func newLogger() transfer.Logger { } // Log implements transfer.Logger -func (g *GiteaLogger) Log(msg string, itms ...any) { +func (g *GiteaLogger) Log(msg string, items ...any) { } diff --git a/modules/markup/common/linkify.go b/modules/markup/common/linkify.go index 3eecb97eac1..aff1813803e 100644 --- a/modules/markup/common/linkify.go +++ b/modules/markup/common/linkify.go @@ -20,13 +20,13 @@ import ( ) type GlobalVarsType struct { - wwwURLRegxp *regexp.Regexp - LinkRegex *regexp.Regexp // fast matching a URL link, no any extra validation. + wwwURLRegexp *regexp.Regexp + LinkRegex *regexp.Regexp // fast matching a URL link, no any extra validation. } var GlobalVars = sync.OnceValue(func() *GlobalVarsType { v := &GlobalVarsType{} - v.wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`) + v.wwwURLRegexp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`) v.LinkRegex, _ = xurls.StrictMatchingScheme("https?://") return v }) @@ -75,7 +75,7 @@ func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Cont m = GlobalVars().LinkRegex.FindSubmatchIndex(line) } if m == nil && bytes.HasPrefix(line, domainWWW) { - m = GlobalVars().wwwURLRegxp.FindSubmatchIndex(line) + m = GlobalVars().wwwURLRegexp.FindSubmatchIndex(line) protocol = []byte("http") } if m != nil { diff --git a/modules/markup/sanitizer_default.go b/modules/markup/sanitizer_default.go index 7fdf66c4bce..77ba8bf4f4c 100644 --- a/modules/markup/sanitizer_default.go +++ b/modules/markup/sanitizer_default.go @@ -81,7 +81,7 @@ func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy { "data-markdown-generated-content", "data-attr-class", } generalSafeElements := []string{ - "h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", "br", "b", "i", "strong", "em", "a", "pre", "code", "img", "tt", + "h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", "br", "b", "center", "i", "strong", "em", "a", "pre", "code", "img", "tt", "div", "ins", "del", "sup", "sub", "p", "ol", "ul", "table", "thead", "tbody", "tfoot", "blockquote", "label", "dl", "dt", "dd", "kbd", "q", "samp", "var", "hr", "ruby", "rt", "rp", "li", "tr", "td", "th", "s", "strike", "summary", "details", "caption", "figure", "figcaption", diff --git a/modules/optional/option.go b/modules/optional/option.go index cbecf869873..a278723bef2 100644 --- a/modules/optional/option.go +++ b/modules/optional/option.go @@ -67,3 +67,17 @@ func ParseBool(s string) Option[bool] { } return Some(v) } + +func AssignPtrValue[T comparable](changed *bool, target, src *T) { + if src != nil && *src != *target { + *target = *src + *changed = true + } +} + +func AssignPtrString[TO, FROM ~string](changed *bool, target *TO, src *FROM) { + if src != nil && string(*src) != string(*target) { + *target = TO(*src) + *changed = true + } +} diff --git a/modules/packages/cran/metadata.go b/modules/packages/cran/metadata.go index 0b0bfb07c66..0856565e101 100644 --- a/modules/packages/cran/metadata.go +++ b/modules/packages/cran/metadata.go @@ -34,7 +34,7 @@ var ( var ( fieldPattern = regexp.MustCompile(`\A\S+:`) namePattern = regexp.MustCompile(`\A[a-zA-Z][a-zA-Z0-9\.]*[a-zA-Z0-9]\z`) - versionPattern = regexp.MustCompile(`\A[0-9]+(?:[.\-][0-9]+){1,3}\z`) + versionPattern = regexp.MustCompile(`\A[0-9]+(?:[.\-][0-9]+)+\z`) authorReplacePattern = regexp.MustCompile(`[\[\(].+?[\]\)]`) ) diff --git a/modules/packages/cran/metadata_test.go b/modules/packages/cran/metadata_test.go index ff68c34c51a..1d652a4a058 100644 --- a/modules/packages/cran/metadata_test.go +++ b/modules/packages/cran/metadata_test.go @@ -128,13 +128,22 @@ func TestParseDescription(t *testing.T) { }) t.Run("InvalidVersion", func(t *testing.T) { - for _, version := range []string{"1", "1 0", "1.2.3.4.5", "1-2-3-4-5", "1.", "1.0.", "1-", "1-0-"} { + for _, version := range []string{"1", "1 0", "1.", "1.0.", "1-", "1-0-"} { p, err := ParseDescription(createDescription(packageName, version)) assert.Nil(t, p) assert.ErrorIs(t, err, ErrInvalidVersion) } }) + t.Run("ValidVersionManyComponents", func(t *testing.T) { + for _, version := range []string{"0.3.4.0.2", "1.2.3.4.5", "1-2-3-4-5"} { + p, err := ParseDescription(createDescription(packageName, version)) + assert.NoError(t, err) + assert.NotNil(t, p) + assert.Equal(t, version, p.Version) + } + }) + t.Run("Valid", func(t *testing.T) { p, err := ParseDescription(createDescription(packageName, packageVersion)) assert.NoError(t, err) diff --git a/modules/setting/config.go b/modules/setting/config.go index fb99325a956..a41734a843c 100644 --- a/modules/setting/config.go +++ b/modules/setting/config.go @@ -12,8 +12,8 @@ import ( ) type PictureStruct struct { - DisableGravatar *config.Value[bool] - EnableFederatedAvatar *config.Value[bool] + DisableGravatar *config.Option[bool] + EnableFederatedAvatar *config.Option[bool] } type OpenWithEditorApp struct { @@ -23,6 +23,9 @@ type OpenWithEditorApp struct { type OpenWithEditorAppsType []OpenWithEditorApp +// ToTextareaString is only used in templates, for help prompt only +// TODO: OPEN-WITH-EDITOR-APP-JSON: Because there is no "rich UI", a plain text editor is used to manage the list of apps +// Maybe we can use some better formats like Yaml in the future, then a simple textarea can manage the config clearly func (t OpenWithEditorAppsType) ToTextareaString() string { var ret strings.Builder for _, app := range t { @@ -31,7 +34,7 @@ func (t OpenWithEditorAppsType) ToTextareaString() string { return ret.String() } -func DefaultOpenWithEditorApps() OpenWithEditorAppsType { +func openWithEditorAppsDefaultValue() OpenWithEditorAppsType { return OpenWithEditorAppsType{ { DisplayName: "VS Code", @@ -49,13 +52,14 @@ func DefaultOpenWithEditorApps() OpenWithEditorAppsType { } type RepositoryStruct struct { - OpenWithEditorApps *config.Value[OpenWithEditorAppsType] - GitGuideRemoteName *config.Value[string] + OpenWithEditorApps *config.Option[OpenWithEditorAppsType] + GitGuideRemoteName *config.Option[string] } type ConfigStruct struct { Picture *PictureStruct Repository *RepositoryStruct + Instance *InstanceStruct } var ( @@ -67,12 +71,16 @@ func initDefaultConfig() { config.SetCfgSecKeyGetter(&cfgSecKeyGetter{}) defaultConfig = &ConfigStruct{ Picture: &PictureStruct{ - DisableGravatar: config.ValueJSON[bool]("picture.disable_gravatar").WithFileConfig(config.CfgSecKey{Sec: "picture", Key: "DISABLE_GRAVATAR"}), - EnableFederatedAvatar: config.ValueJSON[bool]("picture.enable_federated_avatar").WithFileConfig(config.CfgSecKey{Sec: "picture", Key: "ENABLE_FEDERATED_AVATAR"}), + DisableGravatar: config.NewOption[bool]("picture.disable_gravatar").WithDefaultSimple(true).WithFileConfig(config.CfgSecKey{Sec: "picture", Key: "DISABLE_GRAVATAR"}), + EnableFederatedAvatar: config.NewOption[bool]("picture.enable_federated_avatar").WithFileConfig(config.CfgSecKey{Sec: "picture", Key: "ENABLE_FEDERATED_AVATAR"}), }, Repository: &RepositoryStruct{ - OpenWithEditorApps: config.ValueJSON[OpenWithEditorAppsType]("repository.open-with.editor-apps"), - GitGuideRemoteName: config.ValueJSON[string]("repository.git-guide-remote-name").WithDefault("origin"), + OpenWithEditorApps: config.NewOption[OpenWithEditorAppsType]("repository.open-with.editor-apps").WithEmptyAsDefault().WithDefaultFunc(openWithEditorAppsDefaultValue), + GitGuideRemoteName: config.NewOption[string]("repository.git-guide-remote-name").WithEmptyAsDefault().WithDefaultSimple("origin"), + }, + Instance: &InstanceStruct{ + WebBanner: config.NewOption[WebBannerType]("instance.web_banner"), + MaintenanceMode: config.NewOption[MaintenanceModeType]("instance.maintenance_mode"), }, } } diff --git a/modules/setting/config/value.go b/modules/setting/config/value.go index 301c60f5e82..c01f41303c6 100644 --- a/modules/setting/config/value.go +++ b/modules/setting/config/value.go @@ -5,6 +5,7 @@ package config import ( "context" + "reflect" "sync" "code.gitea.io/gitea/modules/json" @@ -16,18 +17,31 @@ type CfgSecKey struct { Sec, Key string } -type Value[T any] struct { +// OptionInterface is used to overcome Golang's generic interface limitation +type OptionInterface interface { + GetDefaultValue() any +} + +type Option[T any] struct { mu sync.RWMutex cfgSecKey CfgSecKey dynKey string - def, value T + value T + defSimple T + defFunc func() T + emptyAsDef bool + has bool revision int } -func (value *Value[T]) parse(key, valStr string) (v T) { - v = value.def +func (opt *Option[T]) GetDefaultValue() any { + return opt.DefaultValue() +} + +func (opt *Option[T]) parse(key, valStr string) (v T) { + v = opt.DefaultValue() if valStr != "" { if err := json.Unmarshal(util.UnsafeStringToBytes(valStr), &v); err != nil { log.Error("Unable to unmarshal json config for key %q, err: %v", key, err) @@ -36,7 +50,35 @@ func (value *Value[T]) parse(key, valStr string) (v T) { return v } -func (value *Value[T]) Value(ctx context.Context) (v T) { +func (opt *Option[T]) HasValue(ctx context.Context) bool { + _, _, has := opt.ValueRevision(ctx) + return has +} + +func (opt *Option[T]) Value(ctx context.Context) (v T) { + v, _, _ = opt.ValueRevision(ctx) + return v +} + +func isZeroOrEmpty(v any) bool { + if v == nil { + return true // interface itself is nil + } + r := reflect.ValueOf(v) + if r.IsZero() { + return true + } + + if r.Kind() == reflect.Slice || r.Kind() == reflect.Map { + if r.IsNil() { + return true + } + return r.Len() == 0 + } + return false +} + +func (opt *Option[T]) ValueRevision(ctx context.Context) (v T, rev int, has bool) { dg := GetDynGetter() if dg == nil { // this is an edge case: the database is not initialized but the system setting is going to be used @@ -44,55 +86,96 @@ func (value *Value[T]) Value(ctx context.Context) (v T) { panic("no config dyn value getter") } - rev := dg.GetRevision(ctx) + rev = dg.GetRevision(ctx) // if the revision in the database doesn't change, use the last value - value.mu.RLock() - if rev == value.revision { - v = value.value - value.mu.RUnlock() - return v + opt.mu.RLock() + if rev == opt.revision { + v = opt.value + has = opt.has + opt.mu.RUnlock() + return v, rev, has } - value.mu.RUnlock() + opt.mu.RUnlock() // try to parse the config and cache it var valStr *string - if dynVal, has := dg.GetValue(ctx, value.dynKey); has { + if dynVal, hasDbValue := dg.GetValue(ctx, opt.dynKey); hasDbValue { valStr = &dynVal - } else if cfgVal, has := GetCfgSecKeyGetter().GetValue(value.cfgSecKey.Sec, value.cfgSecKey.Key); has { + } else if cfgVal, hasCfgValue := GetCfgSecKeyGetter().GetValue(opt.cfgSecKey.Sec, opt.cfgSecKey.Key); hasCfgValue { valStr = &cfgVal } if valStr == nil { - v = value.def + v = opt.DefaultValue() + has = false } else { - v = value.parse(value.dynKey, *valStr) + v = opt.parse(opt.dynKey, *valStr) + if opt.emptyAsDef && isZeroOrEmpty(v) { + v = opt.DefaultValue() + } else { + has = true + } } - value.mu.Lock() - value.value = v - value.revision = rev - value.mu.Unlock() + opt.mu.Lock() + opt.value = v + opt.revision = rev + opt.has = has + opt.mu.Unlock() + return v, rev, has +} + +func (opt *Option[T]) DynKey() string { + return opt.dynKey +} + +// WithDefaultFunc sets the default value with a function +// The "def" value might be changed during runtime (e.g.: Unmarshal with default), so it shouldn't use the same pointer or slice +func (opt *Option[T]) WithDefaultFunc(f func() T) *Option[T] { + opt.defFunc = f + return opt +} + +func (opt *Option[T]) WithDefaultSimple(def T) *Option[T] { + v := any(def) + switch v.(type) { + case string, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + default: + // TODO: use reflect to support convertible basic types like `type State string` + r := reflect.ValueOf(v) + if r.Kind() != reflect.Struct { + panic("invalid type for default value, use WithDefaultFunc instead") + } + } + opt.defSimple = def + return opt +} + +func (opt *Option[T]) WithEmptyAsDefault() *Option[T] { + opt.emptyAsDef = true + return opt +} + +func (opt *Option[T]) DefaultValue() T { + if opt.defFunc != nil { + return opt.defFunc() + } + return opt.defSimple +} + +func (opt *Option[T]) WithFileConfig(cfgSecKey CfgSecKey) *Option[T] { + opt.cfgSecKey = cfgSecKey + return opt +} + +var allConfigOptions = map[string]OptionInterface{} + +func NewOption[T any](dynKey string) *Option[T] { + v := &Option[T]{dynKey: dynKey} + allConfigOptions[dynKey] = v return v } -func (value *Value[T]) DynKey() string { - return value.dynKey -} - -func (value *Value[T]) WithDefault(def T) *Value[T] { - value.def = def - return value -} - -func (value *Value[T]) DefaultValue() T { - return value.def -} - -func (value *Value[T]) WithFileConfig(cfgSecKey CfgSecKey) *Value[T] { - value.cfgSecKey = cfgSecKey - return value -} - -func ValueJSON[T any](dynKey string) *Value[T] { - return &Value[T]{dynKey: dynKey} +func GetConfigOption(dynKey string) OptionInterface { + return allConfigOptions[dynKey] } diff --git a/modules/setting/config_option_instance.go b/modules/setting/config_option_instance.go new file mode 100644 index 00000000000..6d97055a75e --- /dev/null +++ b/modules/setting/config_option_instance.go @@ -0,0 +1,58 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "time" + + "code.gitea.io/gitea/modules/setting/config" +) + +// WebBannerType fields are directly used in templates, +// do remember to update the template if you change the fields +type WebBannerType struct { + DisplayEnabled bool + ContentMessage string + StartTimeUnix int64 + EndTimeUnix int64 +} + +func (b WebBannerType) ShouldDisplay() bool { + if !b.DisplayEnabled || b.ContentMessage == "" { + return false + } + now := time.Now().Unix() + if b.StartTimeUnix > 0 && now < b.StartTimeUnix { + return false + } + if b.EndTimeUnix > 0 && now > b.EndTimeUnix { + return false + } + return true +} + +type MaintenanceModeType struct { + AdminWebAccessOnly bool + StartTimeUnix int64 + EndTimeUnix int64 +} + +func (m MaintenanceModeType) IsActive() bool { + if !m.AdminWebAccessOnly { + return false + } + now := time.Now().Unix() + if m.StartTimeUnix > 0 && now < m.StartTimeUnix { + return false + } + if m.EndTimeUnix > 0 && now > m.EndTimeUnix { + return false + } + return true +} + +type InstanceStruct struct { + WebBanner *config.Option[WebBannerType] + MaintenanceMode *config.Option[MaintenanceModeType] +} diff --git a/modules/setting/picture.go b/modules/setting/picture.go index fafae45baba..d20a110b6c4 100644 --- a/modules/setting/picture.go +++ b/modules/setting/picture.go @@ -22,9 +22,7 @@ var ( RenderedSizeFactor: 2, } - GravatarSource string - DisableGravatar bool // Depreciated: migrated to database - EnableFederatedAvatar bool // Depreciated: migrated to database + GravatarSource string RepoAvatar = struct { Storage *Storage @@ -65,29 +63,12 @@ func loadAvatarsFrom(rootCfg ConfigProvider) error { GravatarSource = source } - DisableGravatar = sec.Key("DISABLE_GRAVATAR").MustBool(GetDefaultDisableGravatar()) - deprecatedSettingDB(rootCfg, "", "DISABLE_GRAVATAR") - EnableFederatedAvatar = sec.Key("ENABLE_FEDERATED_AVATAR").MustBool(GetDefaultEnableFederatedAvatar(DisableGravatar)) - deprecatedSettingDB(rootCfg, "", "ENABLE_FEDERATED_AVATAR") + deprecatedSettingDB(rootCfg, "picture", "DISABLE_GRAVATAR") + deprecatedSettingDB(rootCfg, "picture", "ENABLE_FEDERATED_AVATAR") return nil } -func GetDefaultDisableGravatar() bool { - return OfflineMode -} - -func GetDefaultEnableFederatedAvatar(disableGravatar bool) bool { - v := !InstallLock - if OfflineMode { - v = false - } - if disableGravatar { - v = false - } - return v -} - func loadRepoAvatarFrom(rootCfg ConfigProvider) error { sec := rootCfg.Section("picture") diff --git a/modules/setting/server.go b/modules/setting/server.go index a865e942a62..50a38f544ba 100644 --- a/modules/setting/server.go +++ b/modules/setting/server.go @@ -44,6 +44,7 @@ const ( const ( PublicURLAuto = "auto" PublicURLLegacy = "legacy" + PublicURLNever = "never" ) // Server settings @@ -91,7 +92,6 @@ var ( RedirectOtherPort bool RedirectorUseProxyProtocol bool PortToRedirect string - OfflineMode bool CertFile string KeyFile string StaticRootPath string @@ -287,7 +287,7 @@ func loadServerFrom(rootCfg ConfigProvider) { defaultAppURL := string(Protocol) + "://" + Domain + ":" + HTTPPort AppURL = sec.Key("ROOT_URL").MustString(defaultAppURL) PublicURLDetection = sec.Key("PUBLIC_URL_DETECTION").MustString(PublicURLLegacy) - if PublicURLDetection != PublicURLAuto && PublicURLDetection != PublicURLLegacy { + if PublicURLDetection != PublicURLAuto && PublicURLDetection != PublicURLLegacy && PublicURLDetection != PublicURLNever { log.Fatal("Invalid PUBLIC_URL_DETECTION value: %s", PublicURLDetection) } @@ -346,7 +346,6 @@ func loadServerFrom(rootCfg ConfigProvider) { RedirectOtherPort = sec.Key("REDIRECT_OTHER_PORT").MustBool(false) PortToRedirect = sec.Key("PORT_TO_REDIRECT").MustString("80") RedirectorUseProxyProtocol = sec.Key("REDIRECTOR_USE_PROXY_PROTOCOL").MustBool(UseProxyProtocol) - OfflineMode = sec.Key("OFFLINE_MODE").MustBool(true) if len(StaticRootPath) == 0 { StaticRootPath = AppWorkPath } diff --git a/modules/setting/ui.go b/modules/setting/ui.go index 77a5b45d0a0..722341a71ea 100644 --- a/modules/setting/ui.go +++ b/modules/setting/ui.go @@ -25,7 +25,6 @@ var UI = struct { ReactionMaxUserNum int MaxDisplayFileSize int64 ShowUserEmail bool - DefaultShowFullName bool DefaultTheme string Themes []string FileIconTheme string @@ -43,6 +42,15 @@ var UI = struct { AmbiguousUnicodeDetection bool + // TODO: DefaultShowFullName is introduced by https://github.com/go-gitea/gitea/pull/6710 + // But there are still many edge cases: + // * Many places still use "username", not respecting this setting + // * Many places use "Full Name" if it is not empty, cause inconsistent UI for users who have set their full name but some others don't + // * Even if DefaultShowFullName=false, many places still need to show the full name + // For most cases, either "username" or "username (Full Name)" should be used and are good enough. + // Only in very few cases (e.g.: unimportant lists, narrow layout), "username" or "Full Name" can be used. + DefaultShowFullName bool + Notification struct { MinTimeout time.Duration TimeoutStep time.Duration diff --git a/modules/storage/local.go b/modules/storage/local.go index 8a1776f606d..5ea6f055ce7 100644 --- a/modules/storage/local.go +++ b/modules/storage/local.go @@ -5,6 +5,7 @@ package storage import ( "context" + "errors" "fmt" "io" "net/url" @@ -27,25 +28,32 @@ type LocalStorage struct { // NewLocalStorage returns a local files func NewLocalStorage(ctx context.Context, config *setting.Storage) (ObjectStorage, error) { + // prepare storage root path if !filepath.IsAbs(config.Path) { - return nil, fmt.Errorf("LocalStorageConfig.Path should have been prepared by setting/storage.go and should be an absolute path, but not: %q", config.Path) - } - log.Info("Creating new Local Storage at %s", config.Path) - if err := os.MkdirAll(config.Path, os.ModePerm); err != nil { - return nil, err + return nil, fmt.Errorf("LocalStorage config.Path should have been prepared by setting/storage.go and should be an absolute path, but not: %q", config.Path) } + storageRoot := util.FilePathJoinAbs(config.Path) - if config.TemporaryPath == "" { - config.TemporaryPath = filepath.Join(config.Path, "tmp") + // prepare storage temporary path + storageTmp := config.TemporaryPath + if storageTmp == "" { + storageTmp = filepath.Join(storageRoot, "tmp") } - if !filepath.IsAbs(config.TemporaryPath) { - return nil, fmt.Errorf("LocalStorageConfig.TemporaryPath should be an absolute path, but not: %q", config.TemporaryPath) + if !filepath.IsAbs(storageTmp) { + return nil, fmt.Errorf("LocalStorage config.TemporaryPath should be an absolute path, but not: %q", config.TemporaryPath) + } + storageTmp = util.FilePathJoinAbs(storageTmp) + + // create the storage root if not exist + log.Info("Creating new Local Storage at %s", storageRoot) + if err := os.MkdirAll(storageRoot, os.ModePerm); err != nil { + return nil, err } return &LocalStorage{ ctx: ctx, - dir: config.Path, - tmpdir: config.TemporaryPath, + dir: storageRoot, + tmpdir: storageTmp, }, nil } @@ -108,9 +116,21 @@ func (l *LocalStorage) Stat(path string) (os.FileInfo, error) { return os.Stat(l.buildLocalPath(path)) } -// Delete delete a file +func (l *LocalStorage) deleteEmptyParentDirs(localFullPath string) { + for parent := filepath.Dir(localFullPath); len(parent) > len(l.dir); parent = filepath.Dir(parent) { + if err := os.Remove(parent); err != nil { + // since the target file has been deleted, parent dir error is not related to the file deletion itself. + break + } + } +} + +// Delete deletes the file in storage and removes the empty parent directories (if possible) func (l *LocalStorage) Delete(path string) error { - return util.Remove(l.buildLocalPath(path)) + localFullPath := l.buildLocalPath(path) + err := util.Remove(localFullPath) + l.deleteEmptyParentDirs(localFullPath) + return err } // URL gets the redirect URL to a file @@ -118,34 +138,38 @@ func (l *LocalStorage) URL(path, name, _ string, reqParams url.Values) (*url.URL return nil, ErrURLNotSupported } +func (l *LocalStorage) normalizeWalkError(err error) error { + if errors.Is(err, os.ErrNotExist) { + // ignore it because the file may be deleted during the walk, and we don't care about it + return nil + } + return err +} + // IterateObjects iterates across the objects in the local storage func (l *LocalStorage) IterateObjects(dirName string, fn func(path string, obj Object) error) error { dir := l.buildLocalPath(dirName) - return filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { - if err != nil { + return filepath.WalkDir(dir, func(path string, d os.DirEntry, errWalk error) error { + if err := l.ctx.Err(); err != nil { return err } - select { - case <-l.ctx.Done(): - return l.ctx.Err() - default: + if errWalk != nil { + return l.normalizeWalkError(errWalk) } - if path == l.dir { - return nil - } - if d.IsDir() { + if path == l.dir || d.IsDir() { return nil } + relPath, err := filepath.Rel(l.dir, path) if err != nil { - return err + return l.normalizeWalkError(err) } obj, err := os.Open(path) if err != nil { - return err + return l.normalizeWalkError(err) } defer obj.Close() - return fn(relPath, obj) + return fn(filepath.ToSlash(relPath), obj) }) } diff --git a/modules/storage/local_test.go b/modules/storage/local_test.go index 0592fd716ba..29c13539d0e 100644 --- a/modules/storage/local_test.go +++ b/modules/storage/local_test.go @@ -4,11 +4,14 @@ package storage import ( + "os" + "strings" "testing" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestBuildLocalPath(t *testing.T) { @@ -53,6 +56,49 @@ func TestBuildLocalPath(t *testing.T) { } } +func TestLocalStorageDelete(t *testing.T) { + rootDir := t.TempDir() + st, err := NewLocalStorage(t.Context(), &setting.Storage{Path: rootDir}) + require.NoError(t, err) + + assertExists := func(t *testing.T, path string, exists bool) { + _, err = os.Stat(rootDir + "/" + path) + if exists { + require.NoError(t, err) + } else { + require.ErrorIs(t, err, os.ErrNotExist) + } + } + + _, err = st.Save("dir/sub1/1-a.txt", strings.NewReader(""), -1) + require.NoError(t, err) + _, err = st.Save("dir/sub1/1-b.txt", strings.NewReader(""), -1) + require.NoError(t, err) + _, err = st.Save("dir/sub2/2-a.txt", strings.NewReader(""), -1) + require.NoError(t, err) + + assertExists(t, "dir/sub1/1-a.txt", true) + assertExists(t, "dir/sub1/1-b.txt", true) + assertExists(t, "dir/sub2/2-a.txt", true) + + require.NoError(t, st.Delete("dir/sub1/1-a.txt")) + assertExists(t, "dir/sub1", true) + assertExists(t, "dir/sub1/1-a.txt", false) + assertExists(t, "dir/sub1/1-b.txt", true) + assertExists(t, "dir/sub2/2-a.txt", true) + + require.NoError(t, st.Delete("dir/sub1/1-b.txt")) + assertExists(t, ".", true) + assertExists(t, "dir/sub1", false) + assertExists(t, "dir/sub1/1-a.txt", false) + assertExists(t, "dir/sub1/1-b.txt", false) + assertExists(t, "dir/sub2/2-a.txt", true) + + require.NoError(t, st.Delete("dir/sub2/2-a.txt")) + assertExists(t, ".", true) + assertExists(t, "dir", false) +} + func TestLocalStorageIterator(t *testing.T) { testStorageIterator(t, setting.LocalStorageType, &setting.Storage{Path: t.TempDir()}) } diff --git a/modules/storage/storage.go b/modules/storage/storage.go index 1868817c057..74d0cd47c87 100644 --- a/modules/storage/storage.go +++ b/modules/storage/storage.go @@ -68,7 +68,12 @@ type ObjectStorage interface { Stat(path string) (os.FileInfo, error) Delete(path string) error URL(path, name, method string, reqParams url.Values) (*url.URL, error) - IterateObjects(path string, iterator func(path string, obj Object) error) error + + // IterateObjects calls the iterator function for each object in the storage with the given path as prefix + // The "fullPath" argument in callback is the full path in this storage. + // * IterateObjects("", ...): iterate all objects in this storage + // * IterateObjects("sub-path", ...): iterate all objects with "sub-path" as prefix in this storage, the "fullPath" will be like "sub-path/xxx" + IterateObjects(basePath string, iterator func(fullPath string, obj Object) error) error } // Copy copies a file from source ObjectStorage to dest ObjectStorage diff --git a/modules/structs/activitypub.go b/modules/structs/activitypub.go deleted file mode 100644 index 39a6c1ac2ac..00000000000 --- a/modules/structs/activitypub.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package structs - -// ActivityPub type -type ActivityPub struct { - // Context defines the JSON-LD context for ActivityPub - Context string `json:"@context"` -} diff --git a/modules/structs/repo.go b/modules/structs/repo.go index a08cf360371..3507cc410a1 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -73,6 +73,7 @@ type Repository struct { Stars int `json:"stars_count"` Forks int `json:"forks_count"` Watchers int `json:"watchers_count"` + BranchCount int `json:"branch_count"` OpenIssues int `json:"open_issues_count"` OpenPulls int `json:"open_pr_counter"` Releases int `json:"release_counter"` diff --git a/modules/structs/repo_actions.go b/modules/structs/repo_actions.go index b491d6ccce0..86bf4959d18 100644 --- a/modules/structs/repo_actions.go +++ b/modules/structs/repo_actions.go @@ -205,3 +205,10 @@ type ActionRunnersResponse struct { Entries []*ActionRunner `json:"runners"` TotalCount int64 `json:"total_count"` } + +// RunDetails returns workflow_dispatch runid and url +type RunDetails struct { + WorkflowRunID int64 `json:"workflow_run_id"` + RunURL string `json:"run_url"` + HTMLURL string `json:"html_url"` +} diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 11c52bd5a7f..82087568dfd 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -96,9 +96,6 @@ func NewFuncMap() template.FuncMap { "AssetVersion": func() string { return setting.AssetVersion }, - "DefaultShowFullName": func() bool { - return setting.UI.DefaultShowFullName - }, "ShowFooterTemplateLoadTime": func() bool { return setting.Other.ShowFooterTemplateLoadTime }, diff --git a/modules/templates/util_avatar.go b/modules/templates/util_avatar.go index ee9994ab0b8..524c64d0b6e 100644 --- a/modules/templates/util_avatar.go +++ b/modules/templates/util_avatar.go @@ -16,6 +16,7 @@ import ( user_model "code.gitea.io/gitea/models/user" gitea_html "code.gitea.io/gitea/modules/htmlutil" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" ) type AvatarUtils struct { @@ -29,13 +30,9 @@ func NewAvatarUtils(ctx context.Context) *AvatarUtils { // AvatarHTML creates the HTML for an avatar func AvatarHTML(src string, size int, class, name string) template.HTML { sizeStr := strconv.Itoa(size) - - if name == "" { - name = "avatar" - } - + name = util.IfZero(name, "avatar") // use empty alt, otherwise if the image fails to load, the width will follow the "alt" text's width - return template.HTML(``) + return template.HTML(``) } // Avatar renders user avatars. args: user, size (int), class (string) diff --git a/modules/util/path.go b/modules/util/path.go index 48447d7b905..baed03b443c 100644 --- a/modules/util/path.go +++ b/modules/util/path.go @@ -75,19 +75,21 @@ const filepathSeparator = string(os.PathSeparator) // {`/foo`, ``, `bar`} => `/foo/bar` // {`/foo`, `..`, `bar`} => `/foo/bar` func FilePathJoinAbs(base string, sub ...string) string { - elems := make([]string, 1, len(sub)+1) - // POSIX filesystem can have `\` in file names. Windows: `\` and `/` are both used for path separators // to keep the behavior consistent, we do not allow `\` in file names, replace all `\` with `/` - if isOSWindows() { - elems[0] = filepath.Clean(base) - } else { - elems[0] = filepath.Clean(strings.ReplaceAll(base, "\\", filepathSeparator)) + if !isOSWindows() { + base = strings.ReplaceAll(base, "\\", filepathSeparator) } - if !filepath.IsAbs(elems[0]) { - // This shouldn't happen. If there is really necessary to pass in relative path, return the full path with filepath.Abs() instead - panic(fmt.Sprintf("FilePathJoinAbs: %q (for path %v) is not absolute, do not guess a relative path based on current working directory", elems[0], elems)) + if !filepath.IsAbs(base) { + // This shouldn't happen. If it is really necessary to handle relative paths, use filepath.Abs() to get absolute paths first + panic(fmt.Sprintf("FilePathJoinAbs: %q (for path %v) is not absolute, do not guess a relative path based on current working directory", base, sub)) } + if len(sub) == 0 { + return filepath.Clean(base) + } + + elems := make([]string, 1, len(sub)+1) + elems[0] = base for _, s := range sub { if s == "" { continue @@ -98,7 +100,7 @@ func FilePathJoinAbs(base string, sub ...string) string { elems = append(elems, filepath.Clean(filepathSeparator+strings.ReplaceAll(s, "\\", filepathSeparator))) } } - // the elems[0] must be an absolute path, just join them together + // the elems[0] must be an absolute path, just join them together, and Join will also do Clean return filepath.Join(elems...) } diff --git a/modules/web/middleware/cookie.go b/modules/web/middleware/cookie.go index f98aceba101..336c276fe8f 100644 --- a/modules/web/middleware/cookie.go +++ b/modules/web/middleware/cookie.go @@ -14,7 +14,11 @@ import ( "code.gitea.io/gitea/modules/util" ) -const cookieRedirectTo = "redirect_to" +const ( + CookieWebBannerDismissed = "gitea_disbnr" + CookieTheme = "gitea_theme" + cookieRedirectTo = "redirect_to" +) func GetRedirectToCookie(req *http.Request) string { return GetSiteCookie(req, cookieRedirectTo) diff --git a/options/fileicon/material-icon-rules.json b/options/fileicon/material-icon-rules.json index 8ee5dde0b2e..9163732315d 100644 --- a/options/fileicon/material-icon-rules.json +++ b/options/fileicon/material-icon-rules.json @@ -3,7155 +3,9041 @@ "rust": "folder-rust", ".rust": "folder-rust", "_rust": "folder-rust", + "-rust": "folder-rust", "__rust__": "folder-rust", "bot": "folder-robot", ".bot": "folder-robot", "_bot": "folder-robot", + "-bot": "folder-robot", "__bot__": "folder-robot", "bots": "folder-robot", ".bots": "folder-robot", "_bots": "folder-robot", + "-bots": "folder-robot", "__bots__": "folder-robot", "robot": "folder-robot", ".robot": "folder-robot", "_robot": "folder-robot", + "-robot": "folder-robot", "__robot__": "folder-robot", "robots": "folder-robot", ".robots": "folder-robot", "_robots": "folder-robot", + "-robots": "folder-robot", "__robots__": "folder-robot", + "agent": "folder-robot", + ".agent": "folder-robot", + "_agent": "folder-robot", + "-agent": "folder-robot", + "__agent__": "folder-robot", + "agents": "folder-robot", + ".agents": "folder-robot", + "_agents": "folder-robot", + "-agents": "folder-robot", + "__agents__": "folder-robot", "src": "folder-src", ".src": "folder-src", "_src": "folder-src", + "-src": "folder-src", "__src__": "folder-src", "srcs": "folder-src", ".srcs": "folder-src", "_srcs": "folder-src", + "-srcs": "folder-src", "__srcs__": "folder-src", "source": "folder-src", ".source": "folder-src", "_source": "folder-src", + "-source": "folder-src", "__source__": "folder-src", "sources": "folder-src", ".sources": "folder-src", "_sources": "folder-src", + "-sources": "folder-src", "__sources__": "folder-src", "code": "folder-src", ".code": "folder-src", "_code": "folder-src", + "-code": "folder-src", "__code__": "folder-src", "dist": "folder-dist", ".dist": "folder-dist", "_dist": "folder-dist", + "-dist": "folder-dist", "__dist__": "folder-dist", "out": "folder-dist", ".out": "folder-dist", "_out": "folder-dist", + "-out": "folder-dist", "__out__": "folder-dist", "output": "folder-dist", ".output": "folder-dist", "_output": "folder-dist", + "-output": "folder-dist", "__output__": "folder-dist", + "outputs": "folder-dist", + ".outputs": "folder-dist", + "_outputs": "folder-dist", + "-outputs": "folder-dist", + "__outputs__": "folder-dist", "build": "folder-dist", ".build": "folder-dist", "_build": "folder-dist", + "-build": "folder-dist", "__build__": "folder-dist", "builds": "folder-dist", ".builds": "folder-dist", "_builds": "folder-dist", + "-builds": "folder-dist", "__builds__": "folder-dist", "release": "folder-dist", ".release": "folder-dist", "_release": "folder-dist", + "-release": "folder-dist", "__release__": "folder-dist", "bin": "folder-dist", ".bin": "folder-dist", "_bin": "folder-dist", + "-bin": "folder-dist", "__bin__": "folder-dist", "distribution": "folder-dist", ".distribution": "folder-dist", "_distribution": "folder-dist", + "-distribution": "folder-dist", "__distribution__": "folder-dist", "built": "folder-dist", ".built": "folder-dist", "_built": "folder-dist", + "-built": "folder-dist", "__built__": "folder-dist", "compiled": "folder-dist", ".compiled": "folder-dist", "_compiled": "folder-dist", + "-compiled": "folder-dist", "__compiled__": "folder-dist", "css": "folder-css", ".css": "folder-css", "_css": "folder-css", + "-css": "folder-css", "__css__": "folder-css", "stylesheet": "folder-css", ".stylesheet": "folder-css", "_stylesheet": "folder-css", + "-stylesheet": "folder-css", "__stylesheet__": "folder-css", "stylesheets": "folder-css", ".stylesheets": "folder-css", "_stylesheets": "folder-css", + "-stylesheets": "folder-css", "__stylesheets__": "folder-css", "style": "folder-css", ".style": "folder-css", "_style": "folder-css", + "-style": "folder-css", "__style__": "folder-css", "styles": "folder-css", ".styles": "folder-css", "_styles": "folder-css", + "-styles": "folder-css", "__styles__": "folder-css", "sass": "folder-sass", ".sass": "folder-sass", "_sass": "folder-sass", + "-sass": "folder-sass", "__sass__": "folder-sass", "scss": "folder-sass", ".scss": "folder-sass", "_scss": "folder-sass", + "-scss": "folder-sass", "__scss__": "folder-sass", "tv": "folder-television", ".tv": "folder-television", "_tv": "folder-television", + "-tv": "folder-television", "__tv__": "folder-television", "television": "folder-television", ".television": "folder-television", "_television": "folder-television", + "-television": "folder-television", "__television__": "folder-television", "desktop": "folder-desktop", ".desktop": "folder-desktop", "_desktop": "folder-desktop", + "-desktop": "folder-desktop", "__desktop__": "folder-desktop", "display": "folder-desktop", ".display": "folder-desktop", "_display": "folder-desktop", + "-display": "folder-desktop", "__display__": "folder-desktop", "console": "folder-console", ".console": "folder-console", "_console": "folder-console", + "-console": "folder-console", "__console__": "folder-console", "xbox": "folder-console", ".xbox": "folder-console", "_xbox": "folder-console", + "-xbox": "folder-console", "__xbox__": "folder-console", "ps4": "folder-console", ".ps4": "folder-console", "_ps4": "folder-console", + "-ps4": "folder-console", "__ps4__": "folder-console", "ps5": "folder-console", ".ps5": "folder-console", "_ps5": "folder-console", + "-ps5": "folder-console", "__ps5__": "folder-console", "switch": "folder-console", ".switch": "folder-console", "_switch": "folder-console", + "-switch": "folder-console", "__switch__": "folder-console", "game": "folder-console", ".game": "folder-console", "_game": "folder-console", + "-game": "folder-console", "__game__": "folder-console", "games": "folder-console", ".games": "folder-console", "_games": "folder-console", + "-games": "folder-console", "__games__": "folder-console", "images": "folder-images", ".images": "folder-images", "_images": "folder-images", + "-images": "folder-images", "__images__": "folder-images", "image": "folder-images", ".image": "folder-images", "_image": "folder-images", + "-image": "folder-images", "__image__": "folder-images", "imgs": "folder-images", ".imgs": "folder-images", "_imgs": "folder-images", + "-imgs": "folder-images", "__imgs__": "folder-images", "img": "folder-images", ".img": "folder-images", "_img": "folder-images", + "-img": "folder-images", "__img__": "folder-images", "icons": "folder-images", ".icons": "folder-images", "_icons": "folder-images", + "-icons": "folder-images", "__icons__": "folder-images", "icon": "folder-images", ".icon": "folder-images", "_icon": "folder-images", + "-icon": "folder-images", "__icon__": "folder-images", "icos": "folder-images", ".icos": "folder-images", "_icos": "folder-images", + "-icos": "folder-images", "__icos__": "folder-images", "ico": "folder-images", ".ico": "folder-images", "_ico": "folder-images", + "-ico": "folder-images", "__ico__": "folder-images", "figures": "folder-images", ".figures": "folder-images", "_figures": "folder-images", + "-figures": "folder-images", "__figures__": "folder-images", "figure": "folder-images", ".figure": "folder-images", "_figure": "folder-images", + "-figure": "folder-images", "__figure__": "folder-images", "figs": "folder-images", ".figs": "folder-images", "_figs": "folder-images", + "-figs": "folder-images", "__figs__": "folder-images", "fig": "folder-images", ".fig": "folder-images", "_fig": "folder-images", + "-fig": "folder-images", "__fig__": "folder-images", "screenshot": "folder-images", ".screenshot": "folder-images", "_screenshot": "folder-images", + "-screenshot": "folder-images", "__screenshot__": "folder-images", "screenshots": "folder-images", ".screenshots": "folder-images", "_screenshots": "folder-images", + "-screenshots": "folder-images", "__screenshots__": "folder-images", "screengrab": "folder-images", ".screengrab": "folder-images", "_screengrab": "folder-images", + "-screengrab": "folder-images", "__screengrab__": "folder-images", "screengrabs": "folder-images", ".screengrabs": "folder-images", "_screengrabs": "folder-images", + "-screengrabs": "folder-images", "__screengrabs__": "folder-images", "pic": "folder-images", ".pic": "folder-images", "_pic": "folder-images", + "-pic": "folder-images", "__pic__": "folder-images", "pics": "folder-images", ".pics": "folder-images", "_pics": "folder-images", + "-pics": "folder-images", "__pics__": "folder-images", "picture": "folder-images", ".picture": "folder-images", "_picture": "folder-images", + "-picture": "folder-images", "__picture__": "folder-images", "pictures": "folder-images", ".pictures": "folder-images", "_pictures": "folder-images", + "-pictures": "folder-images", "__pictures__": "folder-images", "photo": "folder-images", ".photo": "folder-images", "_photo": "folder-images", + "-photo": "folder-images", "__photo__": "folder-images", "photos": "folder-images", ".photos": "folder-images", "_photos": "folder-images", + "-photos": "folder-images", "__photos__": "folder-images", "photograph": "folder-images", ".photograph": "folder-images", "_photograph": "folder-images", + "-photograph": "folder-images", "__photograph__": "folder-images", "photographs": "folder-images", ".photographs": "folder-images", "_photographs": "folder-images", + "-photographs": "folder-images", "__photographs__": "folder-images", "texture": "folder-images", ".texture": "folder-images", "_texture": "folder-images", + "-texture": "folder-images", "__texture__": "folder-images", "textures": "folder-images", ".textures": "folder-images", "_textures": "folder-images", + "-textures": "folder-images", "__textures__": "folder-images", "script": "folder-scripts", ".script": "folder-scripts", "_script": "folder-scripts", + "-script": "folder-scripts", "__script__": "folder-scripts", "scripts": "folder-scripts", ".scripts": "folder-scripts", "_scripts": "folder-scripts", + "-scripts": "folder-scripts", "__scripts__": "folder-scripts", "scripting": "folder-scripts", ".scripting": "folder-scripts", "_scripting": "folder-scripts", + "-scripting": "folder-scripts", "__scripting__": "folder-scripts", "node": "folder-node", ".node": "folder-node", "_node": "folder-node", + "-node": "folder-node", "__node__": "folder-node", "nodejs": "folder-node", ".nodejs": "folder-node", "_nodejs": "folder-node", + "-nodejs": "folder-node", "__nodejs__": "folder-node", "node_modules": "folder-node", ".node_modules": "folder-node", "_node_modules": "folder-node", + "-node_modules": "folder-node", "__node_modules__": "folder-node", "js": "folder-javascript", ".js": "folder-javascript", "_js": "folder-javascript", + "-js": "folder-javascript", "__js__": "folder-javascript", "javascript": "folder-javascript", ".javascript": "folder-javascript", "_javascript": "folder-javascript", + "-javascript": "folder-javascript", "__javascript__": "folder-javascript", "javascripts": "folder-javascript", ".javascripts": "folder-javascript", "_javascripts": "folder-javascript", + "-javascripts": "folder-javascript", "__javascripts__": "folder-javascript", "cjs": "folder-javascript", ".cjs": "folder-javascript", "_cjs": "folder-javascript", + "-cjs": "folder-javascript", "__cjs__": "folder-javascript", "mjs": "folder-javascript", ".mjs": "folder-javascript", "_mjs": "folder-javascript", + "-mjs": "folder-javascript", "__mjs__": "folder-javascript", "json": "folder-json", ".json": "folder-json", "_json": "folder-json", + "-json": "folder-json", "__json__": "folder-json", "jsons": "folder-json", ".jsons": "folder-json", "_jsons": "folder-json", + "-jsons": "folder-json", "__jsons__": "folder-json", "jsonc": "folder-json", ".jsonc": "folder-json", "_jsonc": "folder-json", + "-jsonc": "folder-json", "__jsonc__": "folder-json", "jsonl": "folder-json", ".jsonl": "folder-json", "_jsonl": "folder-json", + "-jsonl": "folder-json", "__jsonl__": "folder-json", "font": "folder-font", ".font": "folder-font", "_font": "folder-font", + "-font": "folder-font", "__font__": "folder-font", "fonts": "folder-font", ".fonts": "folder-font", "_fonts": "folder-font", + "-fonts": "folder-font", "__fonts__": "folder-font", "typeface": "folder-font", ".typeface": "folder-font", "_typeface": "folder-font", + "-typeface": "folder-font", "__typeface__": "folder-font", "typefaces": "folder-font", ".typefaces": "folder-font", "_typefaces": "folder-font", + "-typefaces": "folder-font", "__typefaces__": "folder-font", "bower_components": "folder-bower", ".bower_components": "folder-bower", "_bower_components": "folder-bower", + "-bower_components": "folder-bower", "__bower_components__": "folder-bower", "test": "folder-test", ".test": "folder-test", "_test": "folder-test", + "-test": "folder-test", "__test__": "folder-test", "tests": "folder-test", ".tests": "folder-test", "_tests": "folder-test", + "-tests": "folder-test", "__tests__": "folder-test", "testing": "folder-test", ".testing": "folder-test", "_testing": "folder-test", + "-testing": "folder-test", "__testing__": "folder-test", "snapshots": "folder-test", ".snapshots": "folder-test", "_snapshots": "folder-test", + "-snapshots": "folder-test", "__snapshots__": "folder-test", "spec": "folder-test", ".spec": "folder-test", "_spec": "folder-test", + "-spec": "folder-test", "__spec__": "folder-test", "specs": "folder-test", ".specs": "folder-test", "_specs": "folder-test", + "-specs": "folder-test", "__specs__": "folder-test", "testfiles": "folder-test", ".testfiles": "folder-test", "_testfiles": "folder-test", + "-testfiles": "folder-test", "__testfiles__": "folder-test", "directive": "folder-directive", ".directive": "folder-directive", "_directive": "folder-directive", + "-directive": "folder-directive", "__directive__": "folder-directive", "directives": "folder-directive", ".directives": "folder-directive", "_directives": "folder-directive", + "-directives": "folder-directive", "__directives__": "folder-directive", "jinja": "folder-jinja", ".jinja": "folder-jinja", "_jinja": "folder-jinja", + "-jinja": "folder-jinja", "__jinja__": "folder-jinja", "jinja2": "folder-jinja", ".jinja2": "folder-jinja", "_jinja2": "folder-jinja", + "-jinja2": "folder-jinja", "__jinja2__": "folder-jinja", "j2": "folder-jinja", ".j2": "folder-jinja", "_j2": "folder-jinja", + "-j2": "folder-jinja", "__j2__": "folder-jinja", "markdown": "folder-markdown", ".markdown": "folder-markdown", "_markdown": "folder-markdown", + "-markdown": "folder-markdown", "__markdown__": "folder-markdown", "md": "folder-markdown", ".md": "folder-markdown", "_md": "folder-markdown", + "-md": "folder-markdown", "__md__": "folder-markdown", "pdm-plugins": "folder-pdm", ".pdm-plugins": "folder-pdm", "_pdm-plugins": "folder-pdm", + "-pdm-plugins": "folder-pdm", "__pdm-plugins__": "folder-pdm", "pdm-build": "folder-pdm", ".pdm-build": "folder-pdm", "_pdm-build": "folder-pdm", + "-pdm-build": "folder-pdm", "__pdm-build__": "folder-pdm", "php": "folder-php", ".php": "folder-php", "_php": "folder-php", + "-php": "folder-php", "__php__": "folder-php", "phpmailer": "folder-phpmailer", ".phpmailer": "folder-phpmailer", "_phpmailer": "folder-phpmailer", + "-phpmailer": "folder-phpmailer", "__phpmailer__": "folder-phpmailer", "sublime": "folder-sublime", ".sublime": "folder-sublime", "_sublime": "folder-sublime", + "-sublime": "folder-sublime", "__sublime__": "folder-sublime", "doc": "folder-docs", ".doc": "folder-docs", "_doc": "folder-docs", + "-doc": "folder-docs", "__doc__": "folder-docs", "docs": "folder-docs", ".docs": "folder-docs", "_docs": "folder-docs", + "-docs": "folder-docs", "__docs__": "folder-docs", "document": "folder-docs", ".document": "folder-docs", "_document": "folder-docs", + "-document": "folder-docs", "__document__": "folder-docs", "documents": "folder-docs", ".documents": "folder-docs", "_documents": "folder-docs", + "-documents": "folder-docs", "__documents__": "folder-docs", "documentation": "folder-docs", ".documentation": "folder-docs", "_documentation": "folder-docs", + "-documentation": "folder-docs", "__documentation__": "folder-docs", "post": "folder-docs", ".post": "folder-docs", "_post": "folder-docs", + "-post": "folder-docs", "__post__": "folder-docs", "posts": "folder-docs", ".posts": "folder-docs", "_posts": "folder-docs", + "-posts": "folder-docs", "__posts__": "folder-docs", "article": "folder-docs", ".article": "folder-docs", "_article": "folder-docs", + "-article": "folder-docs", "__article__": "folder-docs", "articles": "folder-docs", ".articles": "folder-docs", "_articles": "folder-docs", + "-articles": "folder-docs", "__articles__": "folder-docs", "wiki": "folder-docs", ".wiki": "folder-docs", "_wiki": "folder-docs", + "-wiki": "folder-docs", "__wiki__": "folder-docs", "news": "folder-docs", ".news": "folder-docs", "_news": "folder-docs", + "-news": "folder-docs", "__news__": "folder-docs", "blog": "folder-docs", ".blog": "folder-docs", "_blog": "folder-docs", + "-blog": "folder-docs", "__blog__": "folder-docs", "knowledge": "folder-docs", ".knowledge": "folder-docs", "_knowledge": "folder-docs", + "-knowledge": "folder-docs", "__knowledge__": "folder-docs", "diary": "folder-docs", ".diary": "folder-docs", "_diary": "folder-docs", + "-diary": "folder-docs", "__diary__": "folder-docs", "note": "folder-docs", ".note": "folder-docs", "_note": "folder-docs", + "-note": "folder-docs", "__note__": "folder-docs", "notes": "folder-docs", ".notes": "folder-docs", "_notes": "folder-docs", + "-notes": "folder-docs", "__notes__": "folder-docs", "github/workflows": "folder-gh-workflows", ".github/workflows": "folder-gh-workflows", "_github/workflows": "folder-gh-workflows", + "-github/workflows": "folder-gh-workflows", "__github/workflows__": "folder-gh-workflows", "git": "folder-git", ".git": "folder-git", "_git": "folder-git", + "-git": "folder-git", "__git__": "folder-git", "patches": "folder-git", ".patches": "folder-git", "_patches": "folder-git", + "-patches": "folder-git", "__patches__": "folder-git", "githooks": "folder-git", ".githooks": "folder-git", "_githooks": "folder-git", + "-githooks": "folder-git", "__githooks__": "folder-git", "submodules": "folder-git", ".submodules": "folder-git", "_submodules": "folder-git", + "-submodules": "folder-git", "__submodules__": "folder-git", "github": "folder-github", ".github": "folder-github", "_github": "folder-github", + "-github": "folder-github", "__github__": "folder-github", "gitea": "folder-gitea", ".gitea": "folder-gitea", "_gitea": "folder-gitea", + "-gitea": "folder-gitea", "__gitea__": "folder-gitea", "gitlab": "folder-gitlab", ".gitlab": "folder-gitlab", "_gitlab": "folder-gitlab", + "-gitlab": "folder-gitlab", "__gitlab__": "folder-gitlab", "forgejo": "folder-forgejo", ".forgejo": "folder-forgejo", "_forgejo": "folder-forgejo", + "-forgejo": "folder-forgejo", "__forgejo__": "folder-forgejo", "vscode": "folder-vscode", ".vscode": "folder-vscode", "_vscode": "folder-vscode", + "-vscode": "folder-vscode", "__vscode__": "folder-vscode", "vscode-test": "folder-vscode", ".vscode-test": "folder-vscode", "_vscode-test": "folder-vscode", + "-vscode-test": "folder-vscode", "__vscode-test__": "folder-vscode", "view": "folder-views", ".view": "folder-views", "_view": "folder-views", + "-view": "folder-views", "__view__": "folder-views", "views": "folder-views", ".views": "folder-views", "_views": "folder-views", + "-views": "folder-views", "__views__": "folder-views", "screen": "folder-views", ".screen": "folder-views", "_screen": "folder-views", + "-screen": "folder-views", "__screen__": "folder-views", "screens": "folder-views", ".screens": "folder-views", "_screens": "folder-views", + "-screens": "folder-views", "__screens__": "folder-views", "page": "folder-views", ".page": "folder-views", "_page": "folder-views", + "-page": "folder-views", "__page__": "folder-views", "pages": "folder-views", ".pages": "folder-views", "_pages": "folder-views", + "-pages": "folder-views", "__pages__": "folder-views", "public_html": "folder-views", ".public_html": "folder-views", "_public_html": "folder-views", + "-public_html": "folder-views", "__public_html__": "folder-views", "html": "folder-views", ".html": "folder-views", "_html": "folder-views", + "-html": "folder-views", "__html__": "folder-views", "vue": "folder-vue", ".vue": "folder-vue", "_vue": "folder-vue", + "-vue": "folder-vue", "__vue__": "folder-vue", "vuepress": "folder-vuepress", ".vuepress": "folder-vuepress", "_vuepress": "folder-vuepress", + "-vuepress": "folder-vuepress", "__vuepress__": "folder-vuepress", "expo": "folder-expo", ".expo": "folder-expo", "_expo": "folder-expo", + "-expo": "folder-expo", "__expo__": "folder-expo", "expo-shared": "folder-expo", ".expo-shared": "folder-expo", "_expo-shared": "folder-expo", + "-expo-shared": "folder-expo", "__expo-shared__": "folder-expo", "cfg": "folder-config", ".cfg": "folder-config", "_cfg": "folder-config", + "-cfg": "folder-config", "__cfg__": "folder-config", "cfgs": "folder-config", ".cfgs": "folder-config", "_cfgs": "folder-config", + "-cfgs": "folder-config", "__cfgs__": "folder-config", "conf": "folder-config", ".conf": "folder-config", "_conf": "folder-config", + "-conf": "folder-config", "__conf__": "folder-config", "confs": "folder-config", ".confs": "folder-config", "_confs": "folder-config", + "-confs": "folder-config", "__confs__": "folder-config", "config": "folder-config", ".config": "folder-config", "_config": "folder-config", + "-config": "folder-config", "__config__": "folder-config", "configs": "folder-config", ".configs": "folder-config", "_configs": "folder-config", + "-configs": "folder-config", "__configs__": "folder-config", "configuration": "folder-config", ".configuration": "folder-config", "_configuration": "folder-config", + "-configuration": "folder-config", "__configuration__": "folder-config", "configurations": "folder-config", ".configurations": "folder-config", "_configurations": "folder-config", + "-configurations": "folder-config", "__configurations__": "folder-config", "setting": "folder-config", ".setting": "folder-config", "_setting": "folder-config", + "-setting": "folder-config", "__setting__": "folder-config", "settings": "folder-config", ".settings": "folder-config", "_settings": "folder-config", + "-settings": "folder-config", "__settings__": "folder-config", "meta-inf": "folder-config", ".meta-inf": "folder-config", "_meta-inf": "folder-config", + "-meta-inf": "folder-config", "__meta-inf__": "folder-config", "option": "folder-config", ".option": "folder-config", "_option": "folder-config", + "-option": "folder-config", "__option__": "folder-config", "options": "folder-config", ".options": "folder-config", "_options": "folder-config", + "-options": "folder-config", "__options__": "folder-config", "pref": "folder-config", ".pref": "folder-config", "_pref": "folder-config", + "-pref": "folder-config", "__pref__": "folder-config", "prefs": "folder-config", ".prefs": "folder-config", "_prefs": "folder-config", + "-prefs": "folder-config", "__prefs__": "folder-config", "preference": "folder-config", ".preference": "folder-config", "_preference": "folder-config", + "-preference": "folder-config", "__preference__": "folder-config", "preferences": "folder-config", ".preferences": "folder-config", "_preferences": "folder-config", + "-preferences": "folder-config", "__preferences__": "folder-config", "props": "folder-config", ".props": "folder-config", "_props": "folder-config", + "-props": "folder-config", "__props__": "folder-config", "properties": "folder-config", ".properties": "folder-config", "_properties": "folder-config", + "-properties": "folder-config", "__properties__": "folder-config", "i18n": "folder-i18n", ".i18n": "folder-i18n", "_i18n": "folder-i18n", + "-i18n": "folder-i18n", "__i18n__": "folder-i18n", "internationalization": "folder-i18n", ".internationalization": "folder-i18n", "_internationalization": "folder-i18n", + "-internationalization": "folder-i18n", "__internationalization__": "folder-i18n", "lang": "folder-i18n", ".lang": "folder-i18n", "_lang": "folder-i18n", + "-lang": "folder-i18n", "__lang__": "folder-i18n", "langs": "folder-i18n", ".langs": "folder-i18n", "_langs": "folder-i18n", + "-langs": "folder-i18n", "__langs__": "folder-i18n", "language": "folder-i18n", ".language": "folder-i18n", "_language": "folder-i18n", + "-language": "folder-i18n", "__language__": "folder-i18n", "languages": "folder-i18n", ".languages": "folder-i18n", "_languages": "folder-i18n", + "-languages": "folder-i18n", "__languages__": "folder-i18n", "locale": "folder-i18n", ".locale": "folder-i18n", "_locale": "folder-i18n", + "-locale": "folder-i18n", "__locale__": "folder-i18n", "locales": "folder-i18n", ".locales": "folder-i18n", "_locales": "folder-i18n", + "-locales": "folder-i18n", "__locales__": "folder-i18n", "l10n": "folder-i18n", ".l10n": "folder-i18n", "_l10n": "folder-i18n", + "-l10n": "folder-i18n", "__l10n__": "folder-i18n", "localization": "folder-i18n", ".localization": "folder-i18n", "_localization": "folder-i18n", + "-localization": "folder-i18n", "__localization__": "folder-i18n", "translation": "folder-i18n", ".translation": "folder-i18n", "_translation": "folder-i18n", + "-translation": "folder-i18n", "__translation__": "folder-i18n", "translate": "folder-i18n", ".translate": "folder-i18n", "_translate": "folder-i18n", + "-translate": "folder-i18n", "__translate__": "folder-i18n", "translations": "folder-i18n", ".translations": "folder-i18n", "_translations": "folder-i18n", + "-translations": "folder-i18n", "__translations__": "folder-i18n", "tx": "folder-i18n", ".tx": "folder-i18n", "_tx": "folder-i18n", + "-tx": "folder-i18n", "__tx__": "folder-i18n", "components": "folder-components", ".components": "folder-components", "_components": "folder-components", + "-components": "folder-components", "__components__": "folder-components", "widget": "folder-components", ".widget": "folder-components", "_widget": "folder-components", + "-widget": "folder-components", "__widget__": "folder-components", "widgets": "folder-components", ".widgets": "folder-components", "_widgets": "folder-components", + "-widgets": "folder-components", "__widgets__": "folder-components", "fragments": "folder-components", ".fragments": "folder-components", "_fragments": "folder-components", + "-fragments": "folder-components", "__fragments__": "folder-components", "verdaccio": "folder-verdaccio", ".verdaccio": "folder-verdaccio", "_verdaccio": "folder-verdaccio", + "-verdaccio": "folder-verdaccio", "__verdaccio__": "folder-verdaccio", "aurelia_project": "folder-aurelia", ".aurelia_project": "folder-aurelia", "_aurelia_project": "folder-aurelia", + "-aurelia_project": "folder-aurelia", "__aurelia_project__": "folder-aurelia", "resource": "folder-resource", ".resource": "folder-resource", "_resource": "folder-resource", + "-resource": "folder-resource", "__resource__": "folder-resource", "resources": "folder-resource", ".resources": "folder-resource", "_resources": "folder-resource", + "-resources": "folder-resource", "__resources__": "folder-resource", "res": "folder-resource", ".res": "folder-resource", "_res": "folder-resource", + "-res": "folder-resource", "__res__": "folder-resource", "asset": "folder-resource", ".asset": "folder-resource", "_asset": "folder-resource", + "-asset": "folder-resource", "__asset__": "folder-resource", "assets": "folder-resource", ".assets": "folder-resource", "_assets": "folder-resource", + "-assets": "folder-resource", "__assets__": "folder-resource", "static": "folder-resource", ".static": "folder-resource", "_static": "folder-resource", + "-static": "folder-resource", "__static__": "folder-resource", "report": "folder-resource", ".report": "folder-resource", "_report": "folder-resource", + "-report": "folder-resource", "__report__": "folder-resource", "reports": "folder-resource", ".reports": "folder-resource", "_reports": "folder-resource", + "-reports": "folder-resource", "__reports__": "folder-resource", "lib": "folder-lib", ".lib": "folder-lib", "_lib": "folder-lib", + "-lib": "folder-lib", "__lib__": "folder-lib", "libs": "folder-lib", ".libs": "folder-lib", "_libs": "folder-lib", + "-libs": "folder-lib", "__libs__": "folder-lib", "library": "folder-lib", ".library": "folder-lib", "_library": "folder-lib", + "-library": "folder-lib", "__library__": "folder-lib", "libraries": "folder-lib", ".libraries": "folder-lib", "_libraries": "folder-lib", + "-libraries": "folder-lib", "__libraries__": "folder-lib", "vendor": "folder-lib", ".vendor": "folder-lib", "_vendor": "folder-lib", + "-vendor": "folder-lib", "__vendor__": "folder-lib", "vendors": "folder-lib", ".vendors": "folder-lib", "_vendors": "folder-lib", + "-vendors": "folder-lib", "__vendors__": "folder-lib", "third-party": "folder-lib", ".third-party": "folder-lib", "_third-party": "folder-lib", + "-third-party": "folder-lib", "__third-party__": "folder-lib", "lib64": "folder-lib", ".lib64": "folder-lib", "_lib64": "folder-lib", + "-lib64": "folder-lib", "__lib64__": "folder-lib", "themes": "folder-theme", ".themes": "folder-theme", "_themes": "folder-theme", + "-themes": "folder-theme", "__themes__": "folder-theme", "theme": "folder-theme", ".theme": "folder-theme", "_theme": "folder-theme", + "-theme": "folder-theme", "__theme__": "folder-theme", "color": "folder-theme", ".color": "folder-theme", "_color": "folder-theme", + "-color": "folder-theme", "__color__": "folder-theme", "colors": "folder-theme", ".colors": "folder-theme", "_colors": "folder-theme", + "-colors": "folder-theme", "__colors__": "folder-theme", "colour": "folder-theme", ".colour": "folder-theme", "_colour": "folder-theme", + "-colour": "folder-theme", "__colour__": "folder-theme", "colours": "folder-theme", ".colours": "folder-theme", "_colours": "folder-theme", + "-colours": "folder-theme", "__colours__": "folder-theme", "design": "folder-theme", ".design": "folder-theme", "_design": "folder-theme", + "-design": "folder-theme", "__design__": "folder-theme", "designs": "folder-theme", ".designs": "folder-theme", "_designs": "folder-theme", + "-designs": "folder-theme", "__designs__": "folder-theme", "palette": "folder-theme", ".palette": "folder-theme", "_palette": "folder-theme", + "-palette": "folder-theme", "__palette__": "folder-theme", "palettes": "folder-theme", ".palettes": "folder-theme", "_palettes": "folder-theme", + "-palettes": "folder-theme", "__palettes__": "folder-theme", "webpack": "folder-webpack", ".webpack": "folder-webpack", "_webpack": "folder-webpack", + "-webpack": "folder-webpack", "__webpack__": "folder-webpack", "global": "folder-global", ".global": "folder-global", "_global": "folder-global", + "-global": "folder-global", "__global__": "folder-global", "public": "folder-public", ".public": "folder-public", "_public": "folder-public", + "-public": "folder-public", "__public__": "folder-public", "www": "folder-public", ".www": "folder-public", "_www": "folder-public", + "-www": "folder-public", "__www__": "folder-public", "wwwroot": "folder-public", ".wwwroot": "folder-public", "_wwwroot": "folder-public", + "-wwwroot": "folder-public", "__wwwroot__": "folder-public", "web": "folder-public", ".web": "folder-public", "_web": "folder-public", + "-web": "folder-public", "__web__": "folder-public", "website": "folder-public", ".website": "folder-public", "_website": "folder-public", + "-website": "folder-public", "__website__": "folder-public", "websites": "folder-public", ".websites": "folder-public", "_websites": "folder-public", + "-websites": "folder-public", "__websites__": "folder-public", "site": "folder-public", ".site": "folder-public", "_site": "folder-public", + "-site": "folder-public", "__site__": "folder-public", "browser": "folder-public", ".browser": "folder-public", "_browser": "folder-public", + "-browser": "folder-public", "__browser__": "folder-public", "browsers": "folder-public", ".browsers": "folder-public", "_browsers": "folder-public", + "-browsers": "folder-public", "__browsers__": "folder-public", + "proxy": "folder-public", + ".proxy": "folder-public", + "_proxy": "folder-public", + "-proxy": "folder-public", + "__proxy__": "folder-public", "inc": "folder-include", ".inc": "folder-include", "_inc": "folder-include", + "-inc": "folder-include", "__inc__": "folder-include", "include": "folder-include", ".include": "folder-include", "_include": "folder-include", + "-include": "folder-include", "__include__": "folder-include", "includes": "folder-include", ".includes": "folder-include", "_includes": "folder-include", + "-includes": "folder-include", "__includes__": "folder-include", "partial": "folder-include", ".partial": "folder-include", "_partial": "folder-include", + "-partial": "folder-include", "__partial__": "folder-include", "partials": "folder-include", ".partials": "folder-include", "_partials": "folder-include", + "-partials": "folder-include", "__partials__": "folder-include", "inc64": "folder-include", ".inc64": "folder-include", "_inc64": "folder-include", + "-inc64": "folder-include", "__inc64__": "folder-include", "docker": "folder-docker", ".docker": "folder-docker", "_docker": "folder-docker", + "-docker": "folder-docker", "__docker__": "folder-docker", "dockerfiles": "folder-docker", ".dockerfiles": "folder-docker", "_dockerfiles": "folder-docker", + "-dockerfiles": "folder-docker", "__dockerfiles__": "folder-docker", "dockerhub": "folder-docker", ".dockerhub": "folder-docker", "_dockerhub": "folder-docker", + "-dockerhub": "folder-docker", "__dockerhub__": "folder-docker", "astro": "folder-astro", ".astro": "folder-astro", "_astro": "folder-astro", + "-astro": "folder-astro", "__astro__": "folder-astro", "db": "folder-database", ".db": "folder-database", "_db": "folder-database", + "-db": "folder-database", "__db__": "folder-database", "data": "folder-database", ".data": "folder-database", "_data": "folder-database", + "-data": "folder-database", "__data__": "folder-database", "database": "folder-database", ".database": "folder-database", "_database": "folder-database", + "-database": "folder-database", "__database__": "folder-database", "databases": "folder-database", ".databases": "folder-database", "_databases": "folder-database", + "-databases": "folder-database", "__databases__": "folder-database", "sql": "folder-database", ".sql": "folder-database", "_sql": "folder-database", + "-sql": "folder-database", "__sql__": "folder-database", "migrations": "folder-migrations", ".migrations": "folder-migrations", "_migrations": "folder-migrations", + "-migrations": "folder-migrations", "__migrations__": "folder-migrations", "migration": "folder-migrations", ".migration": "folder-migrations", "_migration": "folder-migrations", + "-migration": "folder-migrations", "__migration__": "folder-migrations", "log": "folder-log", ".log": "folder-log", "_log": "folder-log", + "-log": "folder-log", "__log__": "folder-log", "logs": "folder-log", ".logs": "folder-log", "_logs": "folder-log", + "-logs": "folder-log", "__logs__": "folder-log", "logging": "folder-log", ".logging": "folder-log", "_logging": "folder-log", + "-logging": "folder-log", "__logging__": "folder-log", "target": "folder-target", ".target": "folder-target", "_target": "folder-target", + "-target": "folder-target", "__target__": "folder-target", "temp": "folder-temp", ".temp": "folder-temp", "_temp": "folder-temp", + "-temp": "folder-temp", "__temp__": "folder-temp", "tmp": "folder-temp", ".tmp": "folder-temp", "_tmp": "folder-temp", + "-tmp": "folder-temp", "__tmp__": "folder-temp", "cached": "folder-temp", ".cached": "folder-temp", "_cached": "folder-temp", + "-cached": "folder-temp", "__cached__": "folder-temp", "cache": "folder-temp", ".cache": "folder-temp", "_cache": "folder-temp", + "-cache": "folder-temp", "__cache__": "folder-temp", "aws": "folder-aws", ".aws": "folder-aws", "_aws": "folder-aws", + "-aws": "folder-aws", "__aws__": "folder-aws", "azure": "folder-aws", ".azure": "folder-aws", "_azure": "folder-aws", + "-azure": "folder-aws", "__azure__": "folder-aws", "gcp": "folder-aws", ".gcp": "folder-aws", "_gcp": "folder-aws", + "-gcp": "folder-aws", "__gcp__": "folder-aws", "aud": "folder-audio", ".aud": "folder-audio", "_aud": "folder-audio", + "-aud": "folder-audio", "__aud__": "folder-audio", "auds": "folder-audio", ".auds": "folder-audio", "_auds": "folder-audio", + "-auds": "folder-audio", "__auds__": "folder-audio", "audio": "folder-audio", ".audio": "folder-audio", "_audio": "folder-audio", + "-audio": "folder-audio", "__audio__": "folder-audio", "audios": "folder-audio", ".audios": "folder-audio", "_audios": "folder-audio", + "-audios": "folder-audio", "__audios__": "folder-audio", "music": "folder-audio", ".music": "folder-audio", "_music": "folder-audio", + "-music": "folder-audio", "__music__": "folder-audio", "song": "folder-audio", ".song": "folder-audio", "_song": "folder-audio", + "-song": "folder-audio", "__song__": "folder-audio", "songs": "folder-audio", ".songs": "folder-audio", "_songs": "folder-audio", + "-songs": "folder-audio", "__songs__": "folder-audio", "sound": "folder-audio", ".sound": "folder-audio", "_sound": "folder-audio", + "-sound": "folder-audio", "__sound__": "folder-audio", "sounds": "folder-audio", ".sounds": "folder-audio", "_sounds": "folder-audio", + "-sounds": "folder-audio", "__sounds__": "folder-audio", "voice": "folder-audio", ".voice": "folder-audio", "_voice": "folder-audio", + "-voice": "folder-audio", "__voice__": "folder-audio", "voices": "folder-audio", ".voices": "folder-audio", "_voices": "folder-audio", + "-voices": "folder-audio", "__voices__": "folder-audio", "recordings": "folder-audio", ".recordings": "folder-audio", "_recordings": "folder-audio", + "-recordings": "folder-audio", "__recordings__": "folder-audio", "playlist": "folder-audio", ".playlist": "folder-audio", "_playlist": "folder-audio", + "-playlist": "folder-audio", "__playlist__": "folder-audio", "playlists": "folder-audio", ".playlists": "folder-audio", "_playlists": "folder-audio", + "-playlists": "folder-audio", "__playlists__": "folder-audio", "vid": "folder-video", ".vid": "folder-video", "_vid": "folder-video", + "-vid": "folder-video", "__vid__": "folder-video", "vids": "folder-video", ".vids": "folder-video", "_vids": "folder-video", + "-vids": "folder-video", "__vids__": "folder-video", "video": "folder-video", ".video": "folder-video", "_video": "folder-video", + "-video": "folder-video", "__video__": "folder-video", "videos": "folder-video", ".videos": "folder-video", "_videos": "folder-video", + "-videos": "folder-video", "__videos__": "folder-video", "movie": "folder-video", ".movie": "folder-video", "_movie": "folder-video", + "-movie": "folder-video", "__movie__": "folder-video", "movies": "folder-video", ".movies": "folder-video", "_movies": "folder-video", + "-movies": "folder-video", "__movies__": "folder-video", "media": "folder-video", ".media": "folder-video", "_media": "folder-video", + "-media": "folder-video", "__media__": "folder-video", "kubernetes": "folder-kubernetes", ".kubernetes": "folder-kubernetes", "_kubernetes": "folder-kubernetes", + "-kubernetes": "folder-kubernetes", "__kubernetes__": "folder-kubernetes", "k8s": "folder-kubernetes", ".k8s": "folder-kubernetes", "_k8s": "folder-kubernetes", + "-k8s": "folder-kubernetes", "__k8s__": "folder-kubernetes", "import": "folder-import", ".import": "folder-import", "_import": "folder-import", + "-import": "folder-import", "__import__": "folder-import", "imports": "folder-import", ".imports": "folder-import", "_imports": "folder-import", + "-imports": "folder-import", "__imports__": "folder-import", "imported": "folder-import", ".imported": "folder-import", "_imported": "folder-import", + "-imported": "folder-import", "__imported__": "folder-import", "export": "folder-export", ".export": "folder-export", "_export": "folder-export", + "-export": "folder-export", "__export__": "folder-export", "exports": "folder-export", ".exports": "folder-export", "_exports": "folder-export", + "-exports": "folder-export", "__exports__": "folder-export", "exported": "folder-export", ".exported": "folder-export", "_exported": "folder-export", + "-exported": "folder-export", "__exported__": "folder-export", "wakatime": "folder-wakatime", ".wakatime": "folder-wakatime", "_wakatime": "folder-wakatime", + "-wakatime": "folder-wakatime", "__wakatime__": "folder-wakatime", "circleci": "folder-circleci", ".circleci": "folder-circleci", "_circleci": "folder-circleci", + "-circleci": "folder-circleci", "__circleci__": "folder-circleci", "wordpress-org": "folder-wordpress", ".wordpress-org": "folder-wordpress", "_wordpress-org": "folder-wordpress", + "-wordpress-org": "folder-wordpress", "__wordpress-org__": "folder-wordpress", "wp-content": "folder-wordpress", ".wp-content": "folder-wordpress", "_wp-content": "folder-wordpress", + "-wp-content": "folder-wordpress", "__wp-content__": "folder-wordpress", "gradle": "folder-gradle", ".gradle": "folder-gradle", "_gradle": "folder-gradle", + "-gradle": "folder-gradle", "__gradle__": "folder-gradle", "coverage": "folder-coverage", ".coverage": "folder-coverage", "_coverage": "folder-coverage", + "-coverage": "folder-coverage", "__coverage__": "folder-coverage", "nyc-output": "folder-coverage", ".nyc-output": "folder-coverage", "_nyc-output": "folder-coverage", + "-nyc-output": "folder-coverage", "__nyc-output__": "folder-coverage", "nyc_output": "folder-coverage", ".nyc_output": "folder-coverage", "_nyc_output": "folder-coverage", + "-nyc_output": "folder-coverage", "__nyc_output__": "folder-coverage", "e2e": "folder-coverage", ".e2e": "folder-coverage", "_e2e": "folder-coverage", + "-e2e": "folder-coverage", "__e2e__": "folder-coverage", "it": "folder-coverage", ".it": "folder-coverage", "_it": "folder-coverage", + "-it": "folder-coverage", "__it__": "folder-coverage", "integration-test": "folder-coverage", ".integration-test": "folder-coverage", "_integration-test": "folder-coverage", + "-integration-test": "folder-coverage", "__integration-test__": "folder-coverage", "integration-tests": "folder-coverage", ".integration-tests": "folder-coverage", "_integration-tests": "folder-coverage", + "-integration-tests": "folder-coverage", "__integration-tests__": "folder-coverage", "class": "folder-class", ".class": "folder-class", "_class": "folder-class", + "-class": "folder-class", "__class__": "folder-class", "classes": "folder-class", ".classes": "folder-class", "_classes": "folder-class", + "-classes": "folder-class", "__classes__": "folder-class", "model": "folder-class", ".model": "folder-class", "_model": "folder-class", + "-model": "folder-class", "__model__": "folder-class", "models": "folder-class", ".models": "folder-class", "_models": "folder-class", + "-models": "folder-class", "__models__": "folder-class", "schemas": "folder-class", ".schemas": "folder-class", "_schemas": "folder-class", + "-schemas": "folder-class", "__schemas__": "folder-class", "schema": "folder-class", ".schema": "folder-class", "_schema": "folder-class", + "-schema": "folder-class", "__schema__": "folder-class", "other": "folder-other", ".other": "folder-other", "_other": "folder-other", + "-other": "folder-other", "__other__": "folder-other", "others": "folder-other", ".others": "folder-other", "_others": "folder-other", + "-others": "folder-other", "__others__": "folder-other", "misc": "folder-other", ".misc": "folder-other", "_misc": "folder-other", + "-misc": "folder-other", "__misc__": "folder-other", "miscellaneous": "folder-other", ".miscellaneous": "folder-other", "_miscellaneous": "folder-other", + "-miscellaneous": "folder-other", "__miscellaneous__": "folder-other", "extra": "folder-other", ".extra": "folder-other", "_extra": "folder-other", + "-extra": "folder-other", "__extra__": "folder-other", "extras": "folder-other", ".extras": "folder-other", "_extras": "folder-other", + "-extras": "folder-other", "__extras__": "folder-other", "etc": "folder-other", ".etc": "folder-other", "_etc": "folder-other", + "-etc": "folder-other", "__etc__": "folder-other", "lua": "folder-lua", ".lua": "folder-lua", "_lua": "folder-lua", + "-lua": "folder-lua", "__lua__": "folder-lua", "turbo": "folder-turborepo", ".turbo": "folder-turborepo", "_turbo": "folder-turborepo", + "-turbo": "folder-turborepo", "__turbo__": "folder-turborepo", "typescript": "folder-typescript", ".typescript": "folder-typescript", "_typescript": "folder-typescript", + "-typescript": "folder-typescript", "__typescript__": "folder-typescript", "ts": "folder-typescript", ".ts": "folder-typescript", "_ts": "folder-typescript", + "-ts": "folder-typescript", "__ts__": "folder-typescript", "typings": "folder-typescript", ".typings": "folder-typescript", "_typings": "folder-typescript", + "-typings": "folder-typescript", "__typings__": "folder-typescript", "@types": "folder-typescript", ".@types": "folder-typescript", "_@types": "folder-typescript", + "-@types": "folder-typescript", "__@types__": "folder-typescript", "types": "folder-typescript", ".types": "folder-typescript", "_types": "folder-typescript", + "-types": "folder-typescript", "__types__": "folder-typescript", "cts": "folder-typescript", ".cts": "folder-typescript", "_cts": "folder-typescript", + "-cts": "folder-typescript", "__cts__": "folder-typescript", "mts": "folder-typescript", ".mts": "folder-typescript", "_mts": "folder-typescript", + "-mts": "folder-typescript", "__mts__": "folder-typescript", "graphql": "folder-graphql", ".graphql": "folder-graphql", "_graphql": "folder-graphql", + "-graphql": "folder-graphql", "__graphql__": "folder-graphql", "gql": "folder-graphql", ".gql": "folder-graphql", "_gql": "folder-graphql", + "-gql": "folder-graphql", "__gql__": "folder-graphql", "routes": "folder-routes", ".routes": "folder-routes", "_routes": "folder-routes", + "-routes": "folder-routes", "__routes__": "folder-routes", "router": "folder-routes", ".router": "folder-routes", "_router": "folder-routes", + "-router": "folder-routes", "__router__": "folder-routes", "routers": "folder-routes", ".routers": "folder-routes", "_routers": "folder-routes", + "-routers": "folder-routes", "__routers__": "folder-routes", "navigation": "folder-routes", ".navigation": "folder-routes", "_navigation": "folder-routes", + "-navigation": "folder-routes", "__navigation__": "folder-routes", "navigations": "folder-routes", ".navigations": "folder-routes", "_navigations": "folder-routes", + "-navigations": "folder-routes", "__navigations__": "folder-routes", "routing": "folder-routes", ".routing": "folder-routes", "_routing": "folder-routes", + "-routing": "folder-routes", "__routing__": "folder-routes", "ci": "folder-ci", ".ci": "folder-ci", "_ci": "folder-ci", + "-ci": "folder-ci", "__ci__": "folder-ci", "eslint": "folder-eslint", ".eslint": "folder-eslint", "_eslint": "folder-eslint", + "-eslint": "folder-eslint", "__eslint__": "folder-eslint", "eslint-plugin": "folder-eslint", ".eslint-plugin": "folder-eslint", "_eslint-plugin": "folder-eslint", + "-eslint-plugin": "folder-eslint", "__eslint-plugin__": "folder-eslint", "eslint-plugins": "folder-eslint", ".eslint-plugins": "folder-eslint", "_eslint-plugins": "folder-eslint", + "-eslint-plugins": "folder-eslint", "__eslint-plugins__": "folder-eslint", "eslint-config": "folder-eslint", ".eslint-config": "folder-eslint", "_eslint-config": "folder-eslint", + "-eslint-config": "folder-eslint", "__eslint-config__": "folder-eslint", "eslint-configs": "folder-eslint", ".eslint-configs": "folder-eslint", "_eslint-configs": "folder-eslint", + "-eslint-configs": "folder-eslint", "__eslint-configs__": "folder-eslint", "benchmark": "folder-benchmark", ".benchmark": "folder-benchmark", "_benchmark": "folder-benchmark", + "-benchmark": "folder-benchmark", "__benchmark__": "folder-benchmark", "benchmarks": "folder-benchmark", ".benchmarks": "folder-benchmark", "_benchmarks": "folder-benchmark", + "-benchmarks": "folder-benchmark", "__benchmarks__": "folder-benchmark", "bench": "folder-benchmark", ".bench": "folder-benchmark", "_bench": "folder-benchmark", + "-bench": "folder-benchmark", "__bench__": "folder-benchmark", + "benches": "folder-benchmark", + ".benches": "folder-benchmark", + "_benches": "folder-benchmark", + "-benches": "folder-benchmark", + "__benches__": "folder-benchmark", "performance": "folder-benchmark", ".performance": "folder-benchmark", "_performance": "folder-benchmark", + "-performance": "folder-benchmark", "__performance__": "folder-benchmark", "perf": "folder-benchmark", ".perf": "folder-benchmark", "_perf": "folder-benchmark", + "-perf": "folder-benchmark", "__perf__": "folder-benchmark", "profiling": "folder-benchmark", ".profiling": "folder-benchmark", "_profiling": "folder-benchmark", + "-profiling": "folder-benchmark", "__profiling__": "folder-benchmark", "measure": "folder-benchmark", ".measure": "folder-benchmark", "_measure": "folder-benchmark", + "-measure": "folder-benchmark", "__measure__": "folder-benchmark", "measures": "folder-benchmark", ".measures": "folder-benchmark", "_measures": "folder-benchmark", + "-measures": "folder-benchmark", "__measures__": "folder-benchmark", "measurement": "folder-benchmark", ".measurement": "folder-benchmark", "_measurement": "folder-benchmark", + "-measurement": "folder-benchmark", "__measurement__": "folder-benchmark", "messages": "folder-messages", ".messages": "folder-messages", "_messages": "folder-messages", + "-messages": "folder-messages", "__messages__": "folder-messages", "messaging": "folder-messages", ".messaging": "folder-messages", "_messaging": "folder-messages", + "-messaging": "folder-messages", "__messaging__": "folder-messages", "forum": "folder-messages", ".forum": "folder-messages", "_forum": "folder-messages", + "-forum": "folder-messages", "__forum__": "folder-messages", "chat": "folder-messages", ".chat": "folder-messages", "_chat": "folder-messages", + "-chat": "folder-messages", "__chat__": "folder-messages", "chats": "folder-messages", ".chats": "folder-messages", "_chats": "folder-messages", + "-chats": "folder-messages", "__chats__": "folder-messages", "conversation": "folder-messages", ".conversation": "folder-messages", "_conversation": "folder-messages", + "-conversation": "folder-messages", "__conversation__": "folder-messages", "conversations": "folder-messages", ".conversations": "folder-messages", "_conversations": "folder-messages", + "-conversations": "folder-messages", "__conversations__": "folder-messages", "dialog": "folder-messages", ".dialog": "folder-messages", "_dialog": "folder-messages", + "-dialog": "folder-messages", "__dialog__": "folder-messages", "dialogs": "folder-messages", ".dialogs": "folder-messages", "_dialogs": "folder-messages", + "-dialogs": "folder-messages", "__dialogs__": "folder-messages", "less": "folder-less", ".less": "folder-less", "_less": "folder-less", + "-less": "folder-less", "__less__": "folder-less", "gulp": "folder-gulp", ".gulp": "folder-gulp", "_gulp": "folder-gulp", + "-gulp": "folder-gulp", "__gulp__": "folder-gulp", "gulp-tasks": "folder-gulp", ".gulp-tasks": "folder-gulp", "_gulp-tasks": "folder-gulp", + "-gulp-tasks": "folder-gulp", "__gulp-tasks__": "folder-gulp", "gulpfile.js": "folder-gulp", ".gulpfile.js": "folder-gulp", "_gulpfile.js": "folder-gulp", + "-gulpfile.js": "folder-gulp", "__gulpfile.js__": "folder-gulp", "gulpfile.mjs": "folder-gulp", ".gulpfile.mjs": "folder-gulp", "_gulpfile.mjs": "folder-gulp", + "-gulpfile.mjs": "folder-gulp", "__gulpfile.mjs__": "folder-gulp", "gulpfile.ts": "folder-gulp", ".gulpfile.ts": "folder-gulp", "_gulpfile.ts": "folder-gulp", + "-gulpfile.ts": "folder-gulp", "__gulpfile.ts__": "folder-gulp", "gulpfile.babel.js": "folder-gulp", ".gulpfile.babel.js": "folder-gulp", "_gulpfile.babel.js": "folder-gulp", + "-gulpfile.babel.js": "folder-gulp", "__gulpfile.babel.js__": "folder-gulp", "gulpfiles": "folder-gulp", ".gulpfiles": "folder-gulp", "_gulpfiles": "folder-gulp", + "-gulpfiles": "folder-gulp", "__gulpfiles__": "folder-gulp", "python": "folder-python", ".python": "folder-python", "_python": "folder-python", + "-python": "folder-python", "__python__": "folder-python", "pycache": "folder-python", ".pycache": "folder-python", "_pycache": "folder-python", + "-pycache": "folder-python", "__pycache__": "folder-python", "pytest_cache": "folder-python", ".pytest_cache": "folder-python", "_pytest_cache": "folder-python", + "-pytest_cache": "folder-python", "__pytest_cache__": "folder-python", "sandbox": "folder-sandbox", ".sandbox": "folder-sandbox", "_sandbox": "folder-sandbox", + "-sandbox": "folder-sandbox", "__sandbox__": "folder-sandbox", "sandboxes": "folder-sandbox", ".sandboxes": "folder-sandbox", "_sandboxes": "folder-sandbox", + "-sandboxes": "folder-sandbox", "__sandboxes__": "folder-sandbox", "playground": "folder-sandbox", ".playground": "folder-sandbox", "_playground": "folder-sandbox", + "-playground": "folder-sandbox", "__playground__": "folder-sandbox", "playgrounds": "folder-sandbox", ".playgrounds": "folder-sandbox", "_playgrounds": "folder-sandbox", + "-playgrounds": "folder-sandbox", "__playgrounds__": "folder-sandbox", "scons": "folder-scons", ".scons": "folder-scons", "_scons": "folder-scons", + "-scons": "folder-scons", "__scons__": "folder-scons", "sconf_temp": "folder-scons", ".sconf_temp": "folder-scons", "_sconf_temp": "folder-scons", + "-sconf_temp": "folder-scons", "__sconf_temp__": "folder-scons", "scons_cache": "folder-scons", ".scons_cache": "folder-scons", "_scons_cache": "folder-scons", + "-scons_cache": "folder-scons", "__scons_cache__": "folder-scons", "mojo": "folder-mojo", ".mojo": "folder-mojo", "_mojo": "folder-mojo", + "-mojo": "folder-mojo", "__mojo__": "folder-mojo", "moon": "folder-moon", ".moon": "folder-moon", "_moon": "folder-moon", + "-moon": "folder-moon", "__moon__": "folder-moon", "debug": "folder-debug", ".debug": "folder-debug", "_debug": "folder-debug", + "-debug": "folder-debug", "__debug__": "folder-debug", "debugger": "folder-debug", ".debugger": "folder-debug", "_debugger": "folder-debug", + "-debugger": "folder-debug", "__debugger__": "folder-debug", "debugging": "folder-debug", ".debugging": "folder-debug", "_debugging": "folder-debug", + "-debugging": "folder-debug", "__debugging__": "folder-debug", "fastlane": "folder-fastlane", ".fastlane": "folder-fastlane", "_fastlane": "folder-fastlane", + "-fastlane": "folder-fastlane", "__fastlane__": "folder-fastlane", "plugin": "folder-plugin", ".plugin": "folder-plugin", "_plugin": "folder-plugin", + "-plugin": "folder-plugin", "__plugin__": "folder-plugin", "plugins": "folder-plugin", ".plugins": "folder-plugin", "_plugins": "folder-plugin", + "-plugins": "folder-plugin", "__plugins__": "folder-plugin", "mod": "folder-plugin", ".mod": "folder-plugin", "_mod": "folder-plugin", + "-mod": "folder-plugin", "__mod__": "folder-plugin", "mods": "folder-plugin", ".mods": "folder-plugin", "_mods": "folder-plugin", + "-mods": "folder-plugin", "__mods__": "folder-plugin", "modding": "folder-plugin", ".modding": "folder-plugin", "_modding": "folder-plugin", + "-modding": "folder-plugin", "__modding__": "folder-plugin", "extension": "folder-plugin", ".extension": "folder-plugin", "_extension": "folder-plugin", + "-extension": "folder-plugin", "__extension__": "folder-plugin", "extensions": "folder-plugin", ".extensions": "folder-plugin", "_extensions": "folder-plugin", + "-extensions": "folder-plugin", "__extensions__": "folder-plugin", "addon": "folder-plugin", ".addon": "folder-plugin", "_addon": "folder-plugin", + "-addon": "folder-plugin", "__addon__": "folder-plugin", "addons": "folder-plugin", ".addons": "folder-plugin", "_addons": "folder-plugin", + "-addons": "folder-plugin", "__addons__": "folder-plugin", "addin": "folder-plugin", ".addin": "folder-plugin", "_addin": "folder-plugin", + "-addin": "folder-plugin", "__addin__": "folder-plugin", "addins": "folder-plugin", ".addins": "folder-plugin", "_addins": "folder-plugin", + "-addins": "folder-plugin", "__addins__": "folder-plugin", "module": "folder-plugin", ".module": "folder-plugin", "_module": "folder-plugin", + "-module": "folder-plugin", "__module__": "folder-plugin", "modules": "folder-plugin", ".modules": "folder-plugin", "_modules": "folder-plugin", + "-modules": "folder-plugin", "__modules__": "folder-plugin", "middleware": "folder-middleware", ".middleware": "folder-middleware", "_middleware": "folder-middleware", + "-middleware": "folder-middleware", "__middleware__": "folder-middleware", "middlewares": "folder-middleware", ".middlewares": "folder-middleware", "_middlewares": "folder-middleware", + "-middlewares": "folder-middleware", "__middlewares__": "folder-middleware", "controller": "folder-controller", ".controller": "folder-controller", "_controller": "folder-controller", + "-controller": "folder-controller", "__controller__": "folder-controller", "controllers": "folder-controller", ".controllers": "folder-controller", "_controllers": "folder-controller", + "-controllers": "folder-controller", "__controllers__": "folder-controller", "controls": "folder-controller", ".controls": "folder-controller", "_controls": "folder-controller", + "-controls": "folder-controller", "__controls__": "folder-controller", "service": "folder-controller", ".service": "folder-controller", "_service": "folder-controller", + "-service": "folder-controller", "__service__": "folder-controller", "services": "folder-controller", ".services": "folder-controller", "_services": "folder-controller", + "-services": "folder-controller", "__services__": "folder-controller", "provider": "folder-controller", ".provider": "folder-controller", "_provider": "folder-controller", + "-provider": "folder-controller", "__provider__": "folder-controller", "providers": "folder-controller", ".providers": "folder-controller", "_providers": "folder-controller", + "-providers": "folder-controller", "__providers__": "folder-controller", "handler": "folder-controller", ".handler": "folder-controller", "_handler": "folder-controller", + "-handler": "folder-controller", "__handler__": "folder-controller", "handlers": "folder-controller", ".handlers": "folder-controller", "_handlers": "folder-controller", + "-handlers": "folder-controller", "__handlers__": "folder-controller", "ansible": "folder-ansible", ".ansible": "folder-ansible", "_ansible": "folder-ansible", + "-ansible": "folder-ansible", "__ansible__": "folder-ansible", "server": "folder-server", ".server": "folder-server", "_server": "folder-server", + "-server": "folder-server", "__server__": "folder-server", "servers": "folder-server", ".servers": "folder-server", "_servers": "folder-server", + "-servers": "folder-server", "__servers__": "folder-server", "backend": "folder-server", ".backend": "folder-server", "_backend": "folder-server", + "-backend": "folder-server", "__backend__": "folder-server", "backends": "folder-server", ".backends": "folder-server", "_backends": "folder-server", + "-backends": "folder-server", "__backends__": "folder-server", "inventory": "folder-server", ".inventory": "folder-server", "_inventory": "folder-server", + "-inventory": "folder-server", "__inventory__": "folder-server", "inventories": "folder-server", ".inventories": "folder-server", "_inventories": "folder-server", + "-inventories": "folder-server", "__inventories__": "folder-server", "infrastructure": "folder-server", ".infrastructure": "folder-server", "_infrastructure": "folder-server", + "-infrastructure": "folder-server", "__infrastructure__": "folder-server", "infra": "folder-server", ".infra": "folder-server", "_infra": "folder-server", + "-infra": "folder-server", "__infra__": "folder-server", "client": "folder-client", ".client": "folder-client", "_client": "folder-client", + "-client": "folder-client", "__client__": "folder-client", "clients": "folder-client", ".clients": "folder-client", "_clients": "folder-client", + "-clients": "folder-client", "__clients__": "folder-client", "frontend": "folder-client", ".frontend": "folder-client", "_frontend": "folder-client", + "-frontend": "folder-client", "__frontend__": "folder-client", "frontends": "folder-client", ".frontends": "folder-client", "_frontends": "folder-client", + "-frontends": "folder-client", "__frontends__": "folder-client", "pwa": "folder-client", ".pwa": "folder-client", "_pwa": "folder-client", + "-pwa": "folder-client", "__pwa__": "folder-client", "spa": "folder-client", ".spa": "folder-client", "_spa": "folder-client", + "-spa": "folder-client", "__spa__": "folder-client", "tasks": "folder-tasks", ".tasks": "folder-tasks", "_tasks": "folder-tasks", + "-tasks": "folder-tasks", "__tasks__": "folder-tasks", "tickets": "folder-tasks", ".tickets": "folder-tasks", "_tickets": "folder-tasks", + "-tickets": "folder-tasks", "__tickets__": "folder-tasks", "android": "folder-android", ".android": "folder-android", "_android": "folder-android", + "-android": "folder-android", "__android__": "folder-android", "ios": "folder-ios", ".ios": "folder-ios", "_ios": "folder-ios", + "-ios": "folder-ios", "__ios__": "folder-ios", "presentation": "folder-ui", ".presentation": "folder-ui", "_presentation": "folder-ui", + "-presentation": "folder-ui", "__presentation__": "folder-ui", "gui": "folder-ui", ".gui": "folder-ui", "_gui": "folder-ui", + "-gui": "folder-ui", "__gui__": "folder-ui", "ui": "folder-ui", ".ui": "folder-ui", "_ui": "folder-ui", + "-ui": "folder-ui", "__ui__": "folder-ui", "ux": "folder-ui", ".ux": "folder-ui", "_ux": "folder-ui", + "-ux": "folder-ui", "__ux__": "folder-ui", "uploads": "folder-upload", ".uploads": "folder-upload", "_uploads": "folder-upload", + "-uploads": "folder-upload", "__uploads__": "folder-upload", "upload": "folder-upload", ".upload": "folder-upload", "_upload": "folder-upload", + "-upload": "folder-upload", "__upload__": "folder-upload", "downloads": "folder-download", ".downloads": "folder-download", "_downloads": "folder-download", + "-downloads": "folder-download", "__downloads__": "folder-download", "download": "folder-download", ".download": "folder-download", "_download": "folder-download", + "-download": "folder-download", "__download__": "folder-download", "downloader": "folder-download", ".downloader": "folder-download", "_downloader": "folder-download", + "-downloader": "folder-download", "__downloader__": "folder-download", "downloaders": "folder-download", ".downloaders": "folder-download", "_downloaders": "folder-download", + "-downloaders": "folder-download", "__downloaders__": "folder-download", "tools": "folder-tools", ".tools": "folder-tools", "_tools": "folder-tools", + "-tools": "folder-tools", "__tools__": "folder-tools", "toolkit": "folder-tools", ".toolkit": "folder-tools", "_toolkit": "folder-tools", + "-toolkit": "folder-tools", "__toolkit__": "folder-tools", "toolkits": "folder-tools", ".toolkits": "folder-tools", "_toolkits": "folder-tools", + "-toolkits": "folder-tools", "__toolkits__": "folder-tools", "toolbox": "folder-tools", ".toolbox": "folder-tools", "_toolbox": "folder-tools", + "-toolbox": "folder-tools", "__toolbox__": "folder-tools", "toolboxes": "folder-tools", ".toolboxes": "folder-tools", "_toolboxes": "folder-tools", + "-toolboxes": "folder-tools", "__toolboxes__": "folder-tools", "tooling": "folder-tools", ".tooling": "folder-tools", "_tooling": "folder-tools", + "-tooling": "folder-tools", "__tooling__": "folder-tools", "devtools": "folder-tools", ".devtools": "folder-tools", "_devtools": "folder-tools", + "-devtools": "folder-tools", "__devtools__": "folder-tools", "kit": "folder-tools", ".kit": "folder-tools", "_kit": "folder-tools", + "-kit": "folder-tools", "__kit__": "folder-tools", "kits": "folder-tools", ".kits": "folder-tools", "_kits": "folder-tools", + "-kits": "folder-tools", "__kits__": "folder-tools", "helpers": "folder-helper", ".helpers": "folder-helper", "_helpers": "folder-helper", + "-helpers": "folder-helper", "__helpers__": "folder-helper", "helper": "folder-helper", ".helper": "folder-helper", "_helper": "folder-helper", + "-helper": "folder-helper", "__helper__": "folder-helper", "serverless": "folder-serverless", ".serverless": "folder-serverless", "_serverless": "folder-serverless", + "-serverless": "folder-serverless", "__serverless__": "folder-serverless", "api": "folder-api", ".api": "folder-api", "_api": "folder-api", + "-api": "folder-api", "__api__": "folder-api", "apis": "folder-api", ".apis": "folder-api", "_apis": "folder-api", + "-apis": "folder-api", "__apis__": "folder-api", "restapi": "folder-api", ".restapi": "folder-api", "_restapi": "folder-api", + "-restapi": "folder-api", "__restapi__": "folder-api", "app": "folder-app", ".app": "folder-app", "_app": "folder-app", + "-app": "folder-app", "__app__": "folder-app", "apps": "folder-app", ".apps": "folder-app", "_apps": "folder-app", + "-apps": "folder-app", "__apps__": "folder-app", "application": "folder-app", ".application": "folder-app", "_application": "folder-app", + "-application": "folder-app", "__application__": "folder-app", "applications": "folder-app", ".applications": "folder-app", "_applications": "folder-app", + "-applications": "folder-app", "__applications__": "folder-app", "apollo": "folder-apollo", ".apollo": "folder-apollo", "_apollo": "folder-apollo", + "-apollo": "folder-apollo", "__apollo__": "folder-apollo", "apollo-client": "folder-apollo", ".apollo-client": "folder-apollo", "_apollo-client": "folder-apollo", + "-apollo-client": "folder-apollo", "__apollo-client__": "folder-apollo", "apollo-cache": "folder-apollo", ".apollo-cache": "folder-apollo", "_apollo-cache": "folder-apollo", + "-apollo-cache": "folder-apollo", "__apollo-cache__": "folder-apollo", "apollo-config": "folder-apollo", ".apollo-config": "folder-apollo", "_apollo-config": "folder-apollo", + "-apollo-config": "folder-apollo", "__apollo-config__": "folder-apollo", "arc": "folder-archive", ".arc": "folder-archive", "_arc": "folder-archive", + "-arc": "folder-archive", "__arc__": "folder-archive", "arcs": "folder-archive", ".arcs": "folder-archive", "_arcs": "folder-archive", + "-arcs": "folder-archive", "__arcs__": "folder-archive", "archive": "folder-archive", ".archive": "folder-archive", "_archive": "folder-archive", + "-archive": "folder-archive", "__archive__": "folder-archive", "archives": "folder-archive", ".archives": "folder-archive", "_archives": "folder-archive", + "-archives": "folder-archive", "__archives__": "folder-archive", "archival": "folder-archive", ".archival": "folder-archive", "_archival": "folder-archive", + "-archival": "folder-archive", "__archival__": "folder-archive", "bkp": "folder-backup", ".bkp": "folder-backup", "_bkp": "folder-backup", + "-bkp": "folder-backup", "__bkp__": "folder-backup", "bkps": "folder-backup", ".bkps": "folder-backup", "_bkps": "folder-backup", + "-bkps": "folder-backup", "__bkps__": "folder-backup", "bak": "folder-backup", ".bak": "folder-backup", "_bak": "folder-backup", + "-bak": "folder-backup", "__bak__": "folder-backup", "baks": "folder-backup", ".baks": "folder-backup", "_baks": "folder-backup", + "-baks": "folder-backup", "__baks__": "folder-backup", "backup": "folder-backup", ".backup": "folder-backup", "_backup": "folder-backup", + "-backup": "folder-backup", "__backup__": "folder-backup", "backups": "folder-backup", ".backups": "folder-backup", "_backups": "folder-backup", + "-backups": "folder-backup", "__backups__": "folder-backup", "back-up": "folder-backup", ".back-up": "folder-backup", "_back-up": "folder-backup", + "-back-up": "folder-backup", "__back-up__": "folder-backup", "back-ups": "folder-backup", ".back-ups": "folder-backup", "_back-ups": "folder-backup", + "-back-ups": "folder-backup", "__back-ups__": "folder-backup", "history": "folder-backup", ".history": "folder-backup", "_history": "folder-backup", + "-history": "folder-backup", "__history__": "folder-backup", "histories": "folder-backup", ".histories": "folder-backup", "_histories": "folder-backup", + "-histories": "folder-backup", "__histories__": "folder-backup", "batch": "folder-batch", ".batch": "folder-batch", "_batch": "folder-batch", + "-batch": "folder-batch", "__batch__": "folder-batch", "batchs": "folder-batch", ".batchs": "folder-batch", "_batchs": "folder-batch", + "-batchs": "folder-batch", "__batchs__": "folder-batch", "batches": "folder-batch", ".batches": "folder-batch", "_batches": "folder-batch", + "-batches": "folder-batch", "__batches__": "folder-batch", "buildkite": "folder-buildkite", ".buildkite": "folder-buildkite", "_buildkite": "folder-buildkite", + "-buildkite": "folder-buildkite", "__buildkite__": "folder-buildkite", "cluster": "folder-cluster", ".cluster": "folder-cluster", "_cluster": "folder-cluster", + "-cluster": "folder-cluster", "__cluster__": "folder-cluster", "clusters": "folder-cluster", ".clusters": "folder-cluster", "_clusters": "folder-cluster", + "-clusters": "folder-cluster", "__clusters__": "folder-cluster", "command": "folder-command", ".command": "folder-command", "_command": "folder-command", + "-command": "folder-command", "__command__": "folder-command", "commands": "folder-command", ".commands": "folder-command", "_commands": "folder-command", + "-commands": "folder-command", "__commands__": "folder-command", "commandline": "folder-command", ".commandline": "folder-command", "_commandline": "folder-command", + "-commandline": "folder-command", "__commandline__": "folder-command", "cmd": "folder-command", ".cmd": "folder-command", "_cmd": "folder-command", + "-cmd": "folder-command", "__cmd__": "folder-command", "cli": "folder-command", ".cli": "folder-command", "_cli": "folder-command", + "-cli": "folder-command", "__cli__": "folder-command", "clis": "folder-command", ".clis": "folder-command", "_clis": "folder-command", + "-clis": "folder-command", "__clis__": "folder-command", "constant": "folder-constant", ".constant": "folder-constant", "_constant": "folder-constant", + "-constant": "folder-constant", "__constant__": "folder-constant", "constants": "folder-constant", ".constants": "folder-constant", "_constants": "folder-constant", + "-constants": "folder-constant", "__constants__": "folder-constant", "const": "folder-constant", ".const": "folder-constant", "_const": "folder-constant", + "-const": "folder-constant", "__const__": "folder-constant", "consts": "folder-constant", ".consts": "folder-constant", "_consts": "folder-constant", + "-consts": "folder-constant", "__consts__": "folder-constant", "container": "folder-container", ".container": "folder-container", "_container": "folder-container", + "-container": "folder-container", "__container__": "folder-container", "containers": "folder-container", ".containers": "folder-container", "_containers": "folder-container", + "-containers": "folder-container", "__containers__": "folder-container", "devcontainer": "folder-container", ".devcontainer": "folder-container", "_devcontainer": "folder-container", + "-devcontainer": "folder-container", "__devcontainer__": "folder-container", "content": "folder-content", ".content": "folder-content", "_content": "folder-content", + "-content": "folder-content", "__content__": "folder-content", "contents": "folder-content", ".contents": "folder-content", "_contents": "folder-content", + "-contents": "folder-content", "__contents__": "folder-content", "context": "folder-context", ".context": "folder-context", "_context": "folder-context", + "-context": "folder-context", "__context__": "folder-context", "contexts": "folder-context", ".contexts": "folder-context", "_contexts": "folder-context", + "-contexts": "folder-context", "__contexts__": "folder-context", "core": "folder-core", ".core": "folder-core", "_core": "folder-core", + "-core": "folder-core", "__core__": "folder-core", "delta": "folder-delta", ".delta": "folder-delta", "_delta": "folder-delta", + "-delta": "folder-delta", "__delta__": "folder-delta", "deltas": "folder-delta", ".deltas": "folder-delta", "_deltas": "folder-delta", + "-deltas": "folder-delta", "__deltas__": "folder-delta", "changes": "folder-delta", ".changes": "folder-delta", "_changes": "folder-delta", + "-changes": "folder-delta", "__changes__": "folder-delta", "dump": "folder-dump", ".dump": "folder-dump", "_dump": "folder-dump", + "-dump": "folder-dump", "__dump__": "folder-dump", "dumps": "folder-dump", ".dumps": "folder-dump", "_dumps": "folder-dump", + "-dumps": "folder-dump", "__dumps__": "folder-dump", "demo": "folder-examples", ".demo": "folder-examples", "_demo": "folder-examples", + "-demo": "folder-examples", "__demo__": "folder-examples", "demos": "folder-examples", ".demos": "folder-examples", "_demos": "folder-examples", + "-demos": "folder-examples", "__demos__": "folder-examples", "example": "folder-examples", ".example": "folder-examples", "_example": "folder-examples", + "-example": "folder-examples", "__example__": "folder-examples", "examples": "folder-examples", ".examples": "folder-examples", "_examples": "folder-examples", + "-examples": "folder-examples", "__examples__": "folder-examples", "sample": "folder-examples", ".sample": "folder-examples", "_sample": "folder-examples", + "-sample": "folder-examples", "__sample__": "folder-examples", "samples": "folder-examples", ".samples": "folder-examples", "_samples": "folder-examples", + "-samples": "folder-examples", "__samples__": "folder-examples", "sample-data": "folder-examples", ".sample-data": "folder-examples", "_sample-data": "folder-examples", + "-sample-data": "folder-examples", "__sample-data__": "folder-examples", "env": "folder-environment", ".env": "folder-environment", "_env": "folder-environment", + "-env": "folder-environment", "__env__": "folder-environment", "envs": "folder-environment", ".envs": "folder-environment", "_envs": "folder-environment", + "-envs": "folder-environment", "__envs__": "folder-environment", "environment": "folder-environment", ".environment": "folder-environment", "_environment": "folder-environment", + "-environment": "folder-environment", "__environment__": "folder-environment", "environments": "folder-environment", ".environments": "folder-environment", "_environments": "folder-environment", + "-environments": "folder-environment", "__environments__": "folder-environment", "venv": "folder-environment", ".venv": "folder-environment", "_venv": "folder-environment", + "-venv": "folder-environment", "__venv__": "folder-environment", "func": "folder-functions", ".func": "folder-functions", "_func": "folder-functions", + "-func": "folder-functions", "__func__": "folder-functions", "funcs": "folder-functions", ".funcs": "folder-functions", "_funcs": "folder-functions", + "-funcs": "folder-functions", "__funcs__": "folder-functions", "function": "folder-functions", ".function": "folder-functions", "_function": "folder-functions", + "-function": "folder-functions", "__function__": "folder-functions", "functions": "folder-functions", ".functions": "folder-functions", "_functions": "folder-functions", + "-functions": "folder-functions", "__functions__": "folder-functions", "lambda": "folder-functions", ".lambda": "folder-functions", "_lambda": "folder-functions", + "-lambda": "folder-functions", "__lambda__": "folder-functions", "lambdas": "folder-functions", ".lambdas": "folder-functions", "_lambdas": "folder-functions", + "-lambdas": "folder-functions", "__lambdas__": "folder-functions", "logic": "folder-functions", ".logic": "folder-functions", "_logic": "folder-functions", + "-logic": "folder-functions", "__logic__": "folder-functions", "math": "folder-functions", ".math": "folder-functions", "_math": "folder-functions", + "-math": "folder-functions", "__math__": "folder-functions", "maths": "folder-functions", ".maths": "folder-functions", "_maths": "folder-functions", + "-maths": "folder-functions", "__maths__": "folder-functions", "calc": "folder-functions", ".calc": "folder-functions", "_calc": "folder-functions", + "-calc": "folder-functions", "__calc__": "folder-functions", "calcs": "folder-functions", ".calcs": "folder-functions", "_calcs": "folder-functions", + "-calcs": "folder-functions", "__calcs__": "folder-functions", "calculation": "folder-functions", ".calculation": "folder-functions", "_calculation": "folder-functions", + "-calculation": "folder-functions", "__calculation__": "folder-functions", "calculations": "folder-functions", ".calculations": "folder-functions", "_calculations": "folder-functions", + "-calculations": "folder-functions", "__calculations__": "folder-functions", "composable": "folder-functions", ".composable": "folder-functions", "_composable": "folder-functions", + "-composable": "folder-functions", "__composable__": "folder-functions", "composables": "folder-functions", ".composables": "folder-functions", "_composables": "folder-functions", + "-composables": "folder-functions", "__composables__": "folder-functions", "generator": "folder-generator", ".generator": "folder-generator", "_generator": "folder-generator", + "-generator": "folder-generator", "__generator__": "folder-generator", "generators": "folder-generator", ".generators": "folder-generator", "_generators": "folder-generator", + "-generators": "folder-generator", "__generators__": "folder-generator", "generated": "folder-generator", ".generated": "folder-generator", "_generated": "folder-generator", + "-generated": "folder-generator", "__generated__": "folder-generator", "cfn-gen": "folder-generator", ".cfn-gen": "folder-generator", "_cfn-gen": "folder-generator", + "-cfn-gen": "folder-generator", "__cfn-gen__": "folder-generator", "gen": "folder-generator", ".gen": "folder-generator", "_gen": "folder-generator", + "-gen": "folder-generator", "__gen__": "folder-generator", "gens": "folder-generator", ".gens": "folder-generator", "_gens": "folder-generator", + "-gens": "folder-generator", "__gens__": "folder-generator", "auto": "folder-generator", ".auto": "folder-generator", "_auto": "folder-generator", + "-auto": "folder-generator", "__auto__": "folder-generator", "hook": "folder-hook", ".hook": "folder-hook", "_hook": "folder-hook", + "-hook": "folder-hook", "__hook__": "folder-hook", "hooks": "folder-hook", ".hooks": "folder-hook", "_hooks": "folder-hook", + "-hooks": "folder-hook", "__hooks__": "folder-hook", "trigger": "folder-trigger", ".trigger": "folder-trigger", "_trigger": "folder-trigger", + "-trigger": "folder-trigger", "__trigger__": "folder-trigger", "triggers": "folder-trigger", ".triggers": "folder-trigger", "_triggers": "folder-trigger", + "-triggers": "folder-trigger", "__triggers__": "folder-trigger", "job": "folder-job", ".job": "folder-job", "_job": "folder-job", + "-job": "folder-job", "__job__": "folder-job", "jobs": "folder-job", ".jobs": "folder-job", "_jobs": "folder-job", + "-jobs": "folder-job", "__jobs__": "folder-job", "key": "folder-keys", ".key": "folder-keys", "_key": "folder-keys", + "-key": "folder-keys", "__key__": "folder-keys", "keys": "folder-keys", ".keys": "folder-keys", "_keys": "folder-keys", + "-keys": "folder-keys", "__keys__": "folder-keys", "token": "folder-keys", ".token": "folder-keys", "_token": "folder-keys", + "-token": "folder-keys", "__token__": "folder-keys", "tokens": "folder-keys", ".tokens": "folder-keys", "_tokens": "folder-keys", + "-tokens": "folder-keys", "__tokens__": "folder-keys", "jwt": "folder-keys", ".jwt": "folder-keys", "_jwt": "folder-keys", + "-jwt": "folder-keys", "__jwt__": "folder-keys", "secret": "folder-keys", ".secret": "folder-keys", "_secret": "folder-keys", + "-secret": "folder-keys", "__secret__": "folder-keys", "secrets": "folder-keys", ".secrets": "folder-keys", "_secrets": "folder-keys", + "-secrets": "folder-keys", "__secrets__": "folder-keys", "layout": "folder-layout", ".layout": "folder-layout", "_layout": "folder-layout", + "-layout": "folder-layout", "__layout__": "folder-layout", "layouts": "folder-layout", ".layouts": "folder-layout", "_layouts": "folder-layout", + "-layouts": "folder-layout", "__layouts__": "folder-layout", "mail": "folder-mail", ".mail": "folder-mail", "_mail": "folder-mail", + "-mail": "folder-mail", "__mail__": "folder-mail", "mails": "folder-mail", ".mails": "folder-mail", "_mails": "folder-mail", + "-mails": "folder-mail", "__mails__": "folder-mail", "email": "folder-mail", ".email": "folder-mail", "_email": "folder-mail", + "-email": "folder-mail", "__email__": "folder-mail", "emails": "folder-mail", ".emails": "folder-mail", "_emails": "folder-mail", + "-emails": "folder-mail", "__emails__": "folder-mail", "smtp": "folder-mail", ".smtp": "folder-mail", "_smtp": "folder-mail", + "-smtp": "folder-mail", "__smtp__": "folder-mail", "mailers": "folder-mail", ".mailers": "folder-mail", "_mailers": "folder-mail", + "-mailers": "folder-mail", "__mailers__": "folder-mail", "mappings": "folder-mappings", ".mappings": "folder-mappings", "_mappings": "folder-mappings", + "-mappings": "folder-mappings", "__mappings__": "folder-mappings", "mapping": "folder-mappings", ".mapping": "folder-mappings", "_mapping": "folder-mappings", + "-mapping": "folder-mappings", "__mapping__": "folder-mappings", "meta": "folder-meta", ".meta": "folder-meta", "_meta": "folder-meta", + "-meta": "folder-meta", "__meta__": "folder-meta", "metadata": "folder-meta", ".metadata": "folder-meta", "_metadata": "folder-meta", + "-metadata": "folder-meta", "__metadata__": "folder-meta", "changesets": "folder-changesets", ".changesets": "folder-changesets", "_changesets": "folder-changesets", + "-changesets": "folder-changesets", "__changesets__": "folder-changesets", "changeset": "folder-changesets", ".changeset": "folder-changesets", "_changeset": "folder-changesets", + "-changeset": "folder-changesets", "__changeset__": "folder-changesets", "package": "folder-packages", ".package": "folder-packages", "_package": "folder-packages", + "-package": "folder-packages", "__package__": "folder-packages", "packages": "folder-packages", ".packages": "folder-packages", "_packages": "folder-packages", + "-packages": "folder-packages", "__packages__": "folder-packages", "pkg": "folder-packages", ".pkg": "folder-packages", "_pkg": "folder-packages", + "-pkg": "folder-packages", "__pkg__": "folder-packages", "pkgs": "folder-packages", ".pkgs": "folder-packages", "_pkgs": "folder-packages", + "-pkgs": "folder-packages", "__pkgs__": "folder-packages", "serverpackages": "folder-packages", ".serverpackages": "folder-packages", "_serverpackages": "folder-packages", + "-serverpackages": "folder-packages", "__serverpackages__": "folder-packages", "devpackages": "folder-packages", ".devpackages": "folder-packages", "_devpackages": "folder-packages", + "-devpackages": "folder-packages", "__devpackages__": "folder-packages", "dependencies": "folder-packages", ".dependencies": "folder-packages", "_dependencies": "folder-packages", + "-dependencies": "folder-packages", "__dependencies__": "folder-packages", "shared": "folder-shared", ".shared": "folder-shared", "_shared": "folder-shared", + "-shared": "folder-shared", "__shared__": "folder-shared", "common": "folder-shared", ".common": "folder-shared", "_common": "folder-shared", + "-common": "folder-shared", "__common__": "folder-shared", "glsl": "folder-shader", ".glsl": "folder-shader", "_glsl": "folder-shader", + "-glsl": "folder-shader", "__glsl__": "folder-shader", "hlsl": "folder-shader", ".hlsl": "folder-shader", "_hlsl": "folder-shader", + "-hlsl": "folder-shader", "__hlsl__": "folder-shader", "shader": "folder-shader", ".shader": "folder-shader", "_shader": "folder-shader", + "-shader": "folder-shader", "__shader__": "folder-shader", "shaders": "folder-shader", ".shaders": "folder-shader", "_shaders": "folder-shader", + "-shaders": "folder-shader", "__shaders__": "folder-shader", "stack": "folder-stack", ".stack": "folder-stack", "_stack": "folder-stack", + "-stack": "folder-stack", "__stack__": "folder-stack", "stacks": "folder-stack", ".stacks": "folder-stack", "_stacks": "folder-stack", + "-stacks": "folder-stack", "__stacks__": "folder-stack", "template": "folder-template", ".template": "folder-template", "_template": "folder-template", + "-template": "folder-template", "__template__": "folder-template", "templates": "folder-template", ".templates": "folder-template", "_templates": "folder-template", + "-templates": "folder-template", "__templates__": "folder-template", "github/issue_template": "folder-template", ".github/issue_template": "folder-template", "_github/issue_template": "folder-template", + "-github/issue_template": "folder-template", "__github/issue_template__": "folder-template", "github/pull_request_template": "folder-template", ".github/pull_request_template": "folder-template", "_github/pull_request_template": "folder-template", + "-github/pull_request_template": "folder-template", "__github/pull_request_template__": "folder-template", "util": "folder-utils", ".util": "folder-utils", "_util": "folder-utils", + "-util": "folder-utils", "__util__": "folder-utils", "utils": "folder-utils", ".utils": "folder-utils", "_utils": "folder-utils", + "-utils": "folder-utils", "__utils__": "folder-utils", "utility": "folder-utils", ".utility": "folder-utils", "_utility": "folder-utils", + "-utility": "folder-utils", "__utility__": "folder-utils", "utilities": "folder-utils", ".utilities": "folder-utils", "_utilities": "folder-utils", + "-utilities": "folder-utils", "__utilities__": "folder-utils", "supabase": "folder-supabase", ".supabase": "folder-supabase", "_supabase": "folder-supabase", + "-supabase": "folder-supabase", "__supabase__": "folder-supabase", "private": "folder-private", ".private": "folder-private", "_private": "folder-private", + "-private": "folder-private", "__private__": "folder-private", "linux": "folder-linux", ".linux": "folder-linux", "_linux": "folder-linux", + "-linux": "folder-linux", "__linux__": "folder-linux", "linuxbsd": "folder-linux", ".linuxbsd": "folder-linux", "_linuxbsd": "folder-linux", + "-linuxbsd": "folder-linux", "__linuxbsd__": "folder-linux", "unix": "folder-linux", ".unix": "folder-linux", "_unix": "folder-linux", + "-unix": "folder-linux", "__unix__": "folder-linux", "wsl": "folder-linux", ".wsl": "folder-linux", "_wsl": "folder-linux", + "-wsl": "folder-linux", "__wsl__": "folder-linux", "ubuntu": "folder-linux", ".ubuntu": "folder-linux", "_ubuntu": "folder-linux", + "-ubuntu": "folder-linux", "__ubuntu__": "folder-linux", "deb": "folder-linux", ".deb": "folder-linux", "_deb": "folder-linux", + "-deb": "folder-linux", "__deb__": "folder-linux", "debian": "folder-linux", ".debian": "folder-linux", "_debian": "folder-linux", + "-debian": "folder-linux", "__debian__": "folder-linux", "deepin": "folder-linux", ".deepin": "folder-linux", "_deepin": "folder-linux", + "-deepin": "folder-linux", "__deepin__": "folder-linux", "centos": "folder-linux", ".centos": "folder-linux", "_centos": "folder-linux", + "-centos": "folder-linux", "__centos__": "folder-linux", "popos": "folder-linux", ".popos": "folder-linux", "_popos": "folder-linux", + "-popos": "folder-linux", "__popos__": "folder-linux", "mint": "folder-linux", ".mint": "folder-linux", "_mint": "folder-linux", + "-mint": "folder-linux", "__mint__": "folder-linux", "windows": "folder-windows", ".windows": "folder-windows", "_windows": "folder-windows", + "-windows": "folder-windows", "__windows__": "folder-windows", "win": "folder-windows", ".win": "folder-windows", "_win": "folder-windows", + "-win": "folder-windows", "__win__": "folder-windows", "win32": "folder-windows", ".win32": "folder-windows", "_win32": "folder-windows", + "-win32": "folder-windows", "__win32__": "folder-windows", "windows11": "folder-windows", ".windows11": "folder-windows", "_windows11": "folder-windows", + "-windows11": "folder-windows", "__windows11__": "folder-windows", "windows10": "folder-windows", ".windows10": "folder-windows", "_windows10": "folder-windows", + "-windows10": "folder-windows", "__windows10__": "folder-windows", "windowsxp": "folder-windows", ".windowsxp": "folder-windows", "_windowsxp": "folder-windows", + "-windowsxp": "folder-windows", "__windowsxp__": "folder-windows", "windowsnt": "folder-windows", ".windowsnt": "folder-windows", "_windowsnt": "folder-windows", + "-windowsnt": "folder-windows", "__windowsnt__": "folder-windows", "win11": "folder-windows", ".win11": "folder-windows", "_win11": "folder-windows", + "-win11": "folder-windows", "__win11__": "folder-windows", "win10": "folder-windows", ".win10": "folder-windows", "_win10": "folder-windows", + "-win10": "folder-windows", "__win10__": "folder-windows", "winxp": "folder-windows", ".winxp": "folder-windows", "_winxp": "folder-windows", + "-winxp": "folder-windows", "__winxp__": "folder-windows", "winnt": "folder-windows", ".winnt": "folder-windows", "_winnt": "folder-windows", + "-winnt": "folder-windows", "__winnt__": "folder-windows", "macos": "folder-macos", ".macos": "folder-macos", "_macos": "folder-macos", + "-macos": "folder-macos", "__macos__": "folder-macos", "mac": "folder-macos", ".mac": "folder-macos", "_mac": "folder-macos", + "-mac": "folder-macos", "__mac__": "folder-macos", "osx": "folder-macos", ".osx": "folder-macos", "_osx": "folder-macos", + "-osx": "folder-macos", "__osx__": "folder-macos", "ds_store": "folder-macos", ".ds_store": "folder-macos", "_ds_store": "folder-macos", + "-ds_store": "folder-macos", "__ds_store__": "folder-macos", "iphone": "folder-macos", ".iphone": "folder-macos", "_iphone": "folder-macos", + "-iphone": "folder-macos", "__iphone__": "folder-macos", "ipad": "folder-macos", ".ipad": "folder-macos", "_ipad": "folder-macos", + "-ipad": "folder-macos", "__ipad__": "folder-macos", "ipod": "folder-macos", ".ipod": "folder-macos", "_ipod": "folder-macos", + "-ipod": "folder-macos", "__ipod__": "folder-macos", "macbook": "folder-macos", ".macbook": "folder-macos", "_macbook": "folder-macos", + "-macbook": "folder-macos", "__macbook__": "folder-macos", "macbook-air": "folder-macos", ".macbook-air": "folder-macos", "_macbook-air": "folder-macos", + "-macbook-air": "folder-macos", "__macbook-air__": "folder-macos", "macosx": "folder-macos", ".macosx": "folder-macos", "_macosx": "folder-macos", + "-macosx": "folder-macos", "__macosx__": "folder-macos", "apple": "folder-macos", ".apple": "folder-macos", "_apple": "folder-macos", + "-apple": "folder-macos", "__apple__": "folder-macos", "error": "folder-error", ".error": "folder-error", "_error": "folder-error", + "-error": "folder-error", "__error__": "folder-error", "errors": "folder-error", ".errors": "folder-error", "_errors": "folder-error", + "-errors": "folder-error", "__errors__": "folder-error", "err": "folder-error", ".err": "folder-error", "_err": "folder-error", + "-err": "folder-error", "__err__": "folder-error", "errs": "folder-error", ".errs": "folder-error", "_errs": "folder-error", + "-errs": "folder-error", "__errs__": "folder-error", "crash": "folder-error", ".crash": "folder-error", "_crash": "folder-error", + "-crash": "folder-error", "__crash__": "folder-error", "crashes": "folder-error", ".crashes": "folder-error", "_crashes": "folder-error", + "-crashes": "folder-error", "__crashes__": "folder-error", "event": "folder-event", ".event": "folder-event", "_event": "folder-event", + "-event": "folder-event", "__event__": "folder-event", "events": "folder-event", ".events": "folder-event", "_events": "folder-event", + "-events": "folder-event", "__events__": "folder-event", "auth": "folder-secure", ".auth": "folder-secure", "_auth": "folder-secure", + "-auth": "folder-secure", "__auth__": "folder-secure", "authentication": "folder-secure", ".authentication": "folder-secure", "_authentication": "folder-secure", + "-authentication": "folder-secure", "__authentication__": "folder-secure", "secure": "folder-secure", ".secure": "folder-secure", "_secure": "folder-secure", + "-secure": "folder-secure", "__secure__": "folder-secure", "security": "folder-secure", ".security": "folder-secure", "_security": "folder-secure", + "-security": "folder-secure", "__security__": "folder-secure", "cert": "folder-secure", ".cert": "folder-secure", "_cert": "folder-secure", + "-cert": "folder-secure", "__cert__": "folder-secure", "certs": "folder-secure", ".certs": "folder-secure", "_certs": "folder-secure", + "-certs": "folder-secure", "__certs__": "folder-secure", "certificate": "folder-secure", ".certificate": "folder-secure", "_certificate": "folder-secure", + "-certificate": "folder-secure", "__certificate__": "folder-secure", "certificates": "folder-secure", ".certificates": "folder-secure", "_certificates": "folder-secure", + "-certificates": "folder-secure", "__certificates__": "folder-secure", "ssl": "folder-secure", ".ssl": "folder-secure", "_ssl": "folder-secure", + "-ssl": "folder-secure", "__ssl__": "folder-secure", "cipher": "folder-secure", ".cipher": "folder-secure", "_cipher": "folder-secure", + "-cipher": "folder-secure", "__cipher__": "folder-secure", "cypher": "folder-secure", ".cypher": "folder-secure", "_cypher": "folder-secure", + "-cypher": "folder-secure", "__cypher__": "folder-secure", "tls": "folder-secure", ".tls": "folder-secure", "_tls": "folder-secure", + "-tls": "folder-secure", "__tls__": "folder-secure", "custom": "folder-custom", ".custom": "folder-custom", "_custom": "folder-custom", + "-custom": "folder-custom", "__custom__": "folder-custom", "customs": "folder-custom", ".customs": "folder-custom", "_customs": "folder-custom", + "-customs": "folder-custom", "__customs__": "folder-custom", "draft": "folder-mock", ".draft": "folder-mock", "_draft": "folder-mock", + "-draft": "folder-mock", "__draft__": "folder-mock", "drafts": "folder-mock", ".drafts": "folder-mock", "_drafts": "folder-mock", + "-drafts": "folder-mock", "__drafts__": "folder-mock", "mock": "folder-mock", ".mock": "folder-mock", "_mock": "folder-mock", + "-mock": "folder-mock", "__mock__": "folder-mock", "mocks": "folder-mock", ".mocks": "folder-mock", "_mocks": "folder-mock", + "-mocks": "folder-mock", "__mocks__": "folder-mock", "fixture": "folder-mock", ".fixture": "folder-mock", "_fixture": "folder-mock", + "-fixture": "folder-mock", "__fixture__": "folder-mock", "fixtures": "folder-mock", ".fixtures": "folder-mock", "_fixtures": "folder-mock", + "-fixtures": "folder-mock", "__fixtures__": "folder-mock", "concept": "folder-mock", ".concept": "folder-mock", "_concept": "folder-mock", + "-concept": "folder-mock", "__concept__": "folder-mock", "concepts": "folder-mock", ".concepts": "folder-mock", "_concepts": "folder-mock", + "-concepts": "folder-mock", "__concepts__": "folder-mock", "sketch": "folder-mock", ".sketch": "folder-mock", "_sketch": "folder-mock", + "-sketch": "folder-mock", "__sketch__": "folder-mock", "sketches": "folder-mock", ".sketches": "folder-mock", "_sketches": "folder-mock", + "-sketches": "folder-mock", "__sketches__": "folder-mock", "syntax": "folder-syntax", ".syntax": "folder-syntax", "_syntax": "folder-syntax", + "-syntax": "folder-syntax", "__syntax__": "folder-syntax", "syntaxes": "folder-syntax", ".syntaxes": "folder-syntax", "_syntaxes": "folder-syntax", + "-syntaxes": "folder-syntax", "__syntaxes__": "folder-syntax", "spellcheck": "folder-syntax", ".spellcheck": "folder-syntax", "_spellcheck": "folder-syntax", + "-spellcheck": "folder-syntax", "__spellcheck__": "folder-syntax", "spellcheckers": "folder-syntax", ".spellcheckers": "folder-syntax", "_spellcheckers": "folder-syntax", + "-spellcheckers": "folder-syntax", "__spellcheckers__": "folder-syntax", "vm": "folder-vm", ".vm": "folder-vm", "_vm": "folder-vm", + "-vm": "folder-vm", "__vm__": "folder-vm", "vms": "folder-vm", ".vms": "folder-vm", "_vms": "folder-vm", + "-vms": "folder-vm", "__vms__": "folder-vm", "stylus": "folder-stylus", ".stylus": "folder-stylus", "_stylus": "folder-stylus", + "-stylus": "folder-stylus", "__stylus__": "folder-stylus", "flow-typed": "folder-flow", ".flow-typed": "folder-flow", "_flow-typed": "folder-flow", + "-flow-typed": "folder-flow", "__flow-typed__": "folder-flow", "rule": "folder-rules", ".rule": "folder-rules", "_rule": "folder-rules", + "-rule": "folder-rules", "__rule__": "folder-rules", "rules": "folder-rules", ".rules": "folder-rules", "_rules": "folder-rules", + "-rules": "folder-rules", "__rules__": "folder-rules", "validation": "folder-rules", ".validation": "folder-rules", "_validation": "folder-rules", + "-validation": "folder-rules", "__validation__": "folder-rules", "validations": "folder-rules", ".validations": "folder-rules", "_validations": "folder-rules", + "-validations": "folder-rules", "__validations__": "folder-rules", "validator": "folder-rules", ".validator": "folder-rules", "_validator": "folder-rules", + "-validator": "folder-rules", "__validator__": "folder-rules", "validators": "folder-rules", ".validators": "folder-rules", "_validators": "folder-rules", + "-validators": "folder-rules", "__validators__": "folder-rules", "review": "folder-review", ".review": "folder-review", "_review": "folder-review", + "-review": "folder-review", "__review__": "folder-review", "reviews": "folder-review", ".reviews": "folder-review", "_reviews": "folder-review", + "-reviews": "folder-review", "__reviews__": "folder-review", "revisal": "folder-review", ".revisal": "folder-review", "_revisal": "folder-review", + "-revisal": "folder-review", "__revisal__": "folder-review", "revisals": "folder-review", ".revisals": "folder-review", "_revisals": "folder-review", + "-revisals": "folder-review", "__revisals__": "folder-review", "reviewed": "folder-review", ".reviewed": "folder-review", "_reviewed": "folder-review", + "-reviewed": "folder-review", "__reviewed__": "folder-review", "preview": "folder-review", ".preview": "folder-review", "_preview": "folder-review", + "-preview": "folder-review", "__preview__": "folder-review", "previews": "folder-review", ".previews": "folder-review", "_previews": "folder-review", + "-previews": "folder-review", "__previews__": "folder-review", "anim": "folder-animation", ".anim": "folder-animation", "_anim": "folder-animation", + "-anim": "folder-animation", "__anim__": "folder-animation", "anims": "folder-animation", ".anims": "folder-animation", "_anims": "folder-animation", + "-anims": "folder-animation", "__anims__": "folder-animation", "animation": "folder-animation", ".animation": "folder-animation", "_animation": "folder-animation", + "-animation": "folder-animation", "__animation__": "folder-animation", "animations": "folder-animation", ".animations": "folder-animation", "_animations": "folder-animation", + "-animations": "folder-animation", "__animations__": "folder-animation", "animated": "folder-animation", ".animated": "folder-animation", "_animated": "folder-animation", + "-animated": "folder-animation", "__animated__": "folder-animation", "motion": "folder-animation", ".motion": "folder-animation", "_motion": "folder-animation", + "-motion": "folder-animation", "__motion__": "folder-animation", "motions": "folder-animation", ".motions": "folder-animation", "_motions": "folder-animation", + "-motions": "folder-animation", "__motions__": "folder-animation", "transition": "folder-animation", ".transition": "folder-animation", "_transition": "folder-animation", + "-transition": "folder-animation", "__transition__": "folder-animation", "transitions": "folder-animation", ".transitions": "folder-animation", "_transitions": "folder-animation", + "-transitions": "folder-animation", "__transitions__": "folder-animation", "easing": "folder-animation", ".easing": "folder-animation", "_easing": "folder-animation", + "-easing": "folder-animation", "__easing__": "folder-animation", "easings": "folder-animation", ".easings": "folder-animation", "_easings": "folder-animation", + "-easings": "folder-animation", "__easings__": "folder-animation", "guard": "folder-guard", ".guard": "folder-guard", "_guard": "folder-guard", + "-guard": "folder-guard", "__guard__": "folder-guard", "guards": "folder-guard", ".guards": "folder-guard", "_guards": "folder-guard", + "-guards": "folder-guard", "__guards__": "folder-guard", "prisma": "folder-prisma", ".prisma": "folder-prisma", "_prisma": "folder-prisma", + "-prisma": "folder-prisma", "__prisma__": "folder-prisma", "prisma/schema": "folder-prisma", ".prisma/schema": "folder-prisma", "_prisma/schema": "folder-prisma", + "-prisma/schema": "folder-prisma", "__prisma/schema__": "folder-prisma", "pipe": "folder-pipe", ".pipe": "folder-pipe", "_pipe": "folder-pipe", + "-pipe": "folder-pipe", "__pipe__": "folder-pipe", "pipes": "folder-pipe", ".pipes": "folder-pipe", "_pipes": "folder-pipe", + "-pipes": "folder-pipe", "__pipes__": "folder-pipe", "pipeline": "folder-pipe", ".pipeline": "folder-pipe", "_pipeline": "folder-pipe", + "-pipeline": "folder-pipe", "__pipeline__": "folder-pipe", "pipelines": "folder-pipe", ".pipelines": "folder-pipe", "_pipelines": "folder-pipe", + "-pipelines": "folder-pipe", "__pipelines__": "folder-pipe", "interceptor": "folder-interceptor", ".interceptor": "folder-interceptor", "_interceptor": "folder-interceptor", + "-interceptor": "folder-interceptor", "__interceptor__": "folder-interceptor", "interceptors": "folder-interceptor", ".interceptors": "folder-interceptor", "_interceptors": "folder-interceptor", + "-interceptors": "folder-interceptor", "__interceptors__": "folder-interceptor", "svg": "folder-svg", ".svg": "folder-svg", "_svg": "folder-svg", + "-svg": "folder-svg", "__svg__": "folder-svg", "svgs": "folder-svg", ".svgs": "folder-svg", "_svgs": "folder-svg", + "-svgs": "folder-svg", "__svgs__": "folder-svg", "vector": "folder-svg", ".vector": "folder-svg", "_vector": "folder-svg", + "-vector": "folder-svg", "__vector__": "folder-svg", "vectors": "folder-svg", ".vectors": "folder-svg", "_vectors": "folder-svg", + "-vectors": "folder-svg", "__vectors__": "folder-svg", "nuxt": "folder-nuxt", ".nuxt": "folder-nuxt", "_nuxt": "folder-nuxt", + "-nuxt": "folder-nuxt", "__nuxt__": "folder-nuxt", "terraform": "folder-terraform", ".terraform": "folder-terraform", "_terraform": "folder-terraform", + "-terraform": "folder-terraform", "__terraform__": "folder-terraform", "mobile": "folder-mobile", ".mobile": "folder-mobile", "_mobile": "folder-mobile", + "-mobile": "folder-mobile", "__mobile__": "folder-mobile", "mobiles": "folder-mobile", ".mobiles": "folder-mobile", "_mobiles": "folder-mobile", + "-mobiles": "folder-mobile", "__mobiles__": "folder-mobile", "portable": "folder-mobile", ".portable": "folder-mobile", "_portable": "folder-mobile", + "-portable": "folder-mobile", "__portable__": "folder-mobile", "portability": "folder-mobile", ".portability": "folder-mobile", "_portability": "folder-mobile", + "-portability": "folder-mobile", "__portability__": "folder-mobile", "phone": "folder-mobile", ".phone": "folder-mobile", "_phone": "folder-mobile", + "-phone": "folder-mobile", "__phone__": "folder-mobile", "phones": "folder-mobile", ".phones": "folder-mobile", "_phones": "folder-mobile", + "-phones": "folder-mobile", "__phones__": "folder-mobile", "stencil": "folder-stencil", ".stencil": "folder-stencil", "_stencil": "folder-stencil", + "-stencil": "folder-stencil", "__stencil__": "folder-stencil", "firebase": "folder-firebase", ".firebase": "folder-firebase", "_firebase": "folder-firebase", + "-firebase": "folder-firebase", "__firebase__": "folder-firebase", "firestore": "folder-firestore", ".firestore": "folder-firestore", "_firestore": "folder-firestore", + "-firestore": "folder-firestore", "__firestore__": "folder-firestore", "cloud-firestore": "folder-firestore", ".cloud-firestore": "folder-firestore", "_cloud-firestore": "folder-firestore", + "-cloud-firestore": "folder-firestore", "__cloud-firestore__": "folder-firestore", "firebase-firestore": "folder-firestore", ".firebase-firestore": "folder-firestore", "_firebase-firestore": "folder-firestore", + "-firebase-firestore": "folder-firestore", "__firebase-firestore__": "folder-firestore", "cloud-functions": "folder-cloud-functions", ".cloud-functions": "folder-cloud-functions", "_cloud-functions": "folder-cloud-functions", + "-cloud-functions": "folder-cloud-functions", "__cloud-functions__": "folder-cloud-functions", "cloudfunctions": "folder-cloud-functions", ".cloudfunctions": "folder-cloud-functions", "_cloudfunctions": "folder-cloud-functions", + "-cloudfunctions": "folder-cloud-functions", "__cloudfunctions__": "folder-cloud-functions", "firebase-cloud-functions": "folder-cloud-functions", ".firebase-cloud-functions": "folder-cloud-functions", "_firebase-cloud-functions": "folder-cloud-functions", + "-firebase-cloud-functions": "folder-cloud-functions", "__firebase-cloud-functions__": "folder-cloud-functions", "firebase-cloudfunctions": "folder-cloud-functions", ".firebase-cloudfunctions": "folder-cloud-functions", "_firebase-cloudfunctions": "folder-cloud-functions", + "-firebase-cloudfunctions": "folder-cloud-functions", "__firebase-cloudfunctions__": "folder-cloud-functions", "svelte": "folder-svelte", ".svelte": "folder-svelte", "_svelte": "folder-svelte", + "-svelte": "folder-svelte", "__svelte__": "folder-svelte", "svelte-kit": "folder-svelte", ".svelte-kit": "folder-svelte", "_svelte-kit": "folder-svelte", + "-svelte-kit": "folder-svelte", "__svelte-kit__": "folder-svelte", "update": "folder-update", ".update": "folder-update", "_update": "folder-update", + "-update": "folder-update", "__update__": "folder-update", "updates": "folder-update", ".updates": "folder-update", "_updates": "folder-update", + "-updates": "folder-update", "__updates__": "folder-update", "upgrade": "folder-update", ".upgrade": "folder-update", "_upgrade": "folder-update", + "-upgrade": "folder-update", "__upgrade__": "folder-update", "upgrades": "folder-update", ".upgrades": "folder-update", "_upgrades": "folder-update", + "-upgrades": "folder-update", "__upgrades__": "folder-update", "idea": "folder-intellij", ".idea": "folder-intellij", "_idea": "folder-intellij", + "-idea": "folder-intellij", "__idea__": "folder-intellij", "azure-pipelines": "folder-azure-pipelines", ".azure-pipelines": "folder-azure-pipelines", "_azure-pipelines": "folder-azure-pipelines", + "-azure-pipelines": "folder-azure-pipelines", "__azure-pipelines__": "folder-azure-pipelines", "azure-pipelines-ci": "folder-azure-pipelines", ".azure-pipelines-ci": "folder-azure-pipelines", "_azure-pipelines-ci": "folder-azure-pipelines", + "-azure-pipelines-ci": "folder-azure-pipelines", "__azure-pipelines-ci__": "folder-azure-pipelines", "mjml": "folder-mjml", ".mjml": "folder-mjml", "_mjml": "folder-mjml", + "-mjml": "folder-mjml", "__mjml__": "folder-mjml", "admin": "folder-admin", ".admin": "folder-admin", "_admin": "folder-admin", + "-admin": "folder-admin", "__admin__": "folder-admin", "admins": "folder-admin", ".admins": "folder-admin", "_admins": "folder-admin", + "-admins": "folder-admin", "__admins__": "folder-admin", "manager": "folder-admin", ".manager": "folder-admin", "_manager": "folder-admin", + "-manager": "folder-admin", "__manager__": "folder-admin", "managers": "folder-admin", ".managers": "folder-admin", "_managers": "folder-admin", + "-managers": "folder-admin", "__managers__": "folder-admin", "moderator": "folder-admin", ".moderator": "folder-admin", "_moderator": "folder-admin", + "-moderator": "folder-admin", "__moderator__": "folder-admin", "moderators": "folder-admin", ".moderators": "folder-admin", "_moderators": "folder-admin", + "-moderators": "folder-admin", "__moderators__": "folder-admin", "jupyter": "folder-jupyter", ".jupyter": "folder-jupyter", "_jupyter": "folder-jupyter", + "-jupyter": "folder-jupyter", "__jupyter__": "folder-jupyter", "notebook": "folder-jupyter", ".notebook": "folder-jupyter", "_notebook": "folder-jupyter", + "-notebook": "folder-jupyter", "__notebook__": "folder-jupyter", "notebooks": "folder-jupyter", ".notebooks": "folder-jupyter", "_notebooks": "folder-jupyter", + "-notebooks": "folder-jupyter", "__notebooks__": "folder-jupyter", "ipynb": "folder-jupyter", ".ipynb": "folder-jupyter", "_ipynb": "folder-jupyter", + "-ipynb": "folder-jupyter", "__ipynb__": "folder-jupyter", "scala": "folder-scala", ".scala": "folder-scala", "_scala": "folder-scala", + "-scala": "folder-scala", "__scala__": "folder-scala", "connection": "folder-connection", ".connection": "folder-connection", "_connection": "folder-connection", + "-connection": "folder-connection", "__connection__": "folder-connection", "connections": "folder-connection", ".connections": "folder-connection", "_connections": "folder-connection", + "-connections": "folder-connection", "__connections__": "folder-connection", "integration": "folder-connection", ".integration": "folder-connection", "_integration": "folder-connection", + "-integration": "folder-connection", "__integration__": "folder-connection", "integrations": "folder-connection", ".integrations": "folder-connection", "_integrations": "folder-connection", + "-integrations": "folder-connection", "__integrations__": "folder-connection", "remote": "folder-connection", ".remote": "folder-connection", "_remote": "folder-connection", + "-remote": "folder-connection", "__remote__": "folder-connection", "remotes": "folder-connection", ".remotes": "folder-connection", "_remotes": "folder-connection", + "-remotes": "folder-connection", "__remotes__": "folder-connection", "quasar": "folder-quasar", ".quasar": "folder-quasar", "_quasar": "folder-quasar", + "-quasar": "folder-quasar", "__quasar__": "folder-quasar", "next": "folder-next", ".next": "folder-next", "_next": "folder-next", + "-next": "folder-next", "__next__": "folder-next", "dal": "folder-dal", ".dal": "folder-dal", "_dal": "folder-dal", + "-dal": "folder-dal", "__dal__": "folder-dal", "data-access": "folder-dal", ".data-access": "folder-dal", "_data-access": "folder-dal", + "-data-access": "folder-dal", "__data-access__": "folder-dal", "data-access-layer": "folder-dal", ".data-access-layer": "folder-dal", "_data-access-layer": "folder-dal", + "-data-access-layer": "folder-dal", "__data-access-layer__": "folder-dal", "cobol": "folder-cobol", ".cobol": "folder-cobol", "_cobol": "folder-cobol", + "-cobol": "folder-cobol", "__cobol__": "folder-cobol", "yarn": "folder-yarn", ".yarn": "folder-yarn", "_yarn": "folder-yarn", + "-yarn": "folder-yarn", "__yarn__": "folder-yarn", "husky": "folder-husky", ".husky": "folder-husky", "_husky": "folder-husky", + "-husky": "folder-husky", "__husky__": "folder-husky", "storybook": "folder-storybook", ".storybook": "folder-storybook", "_storybook": "folder-storybook", + "-storybook": "folder-storybook", "__storybook__": "folder-storybook", "stories": "folder-storybook", ".stories": "folder-storybook", "_stories": "folder-storybook", + "-stories": "folder-storybook", "__stories__": "folder-storybook", "base": "folder-base", ".base": "folder-base", "_base": "folder-base", + "-base": "folder-base", "__base__": "folder-base", "bases": "folder-base", ".bases": "folder-base", "_bases": "folder-base", + "-bases": "folder-base", "__bases__": "folder-base", "cart": "folder-cart", ".cart": "folder-cart", "_cart": "folder-cart", + "-cart": "folder-cart", "__cart__": "folder-cart", "shopping-cart": "folder-cart", ".shopping-cart": "folder-cart", "_shopping-cart": "folder-cart", + "-shopping-cart": "folder-cart", "__shopping-cart__": "folder-cart", "shopping": "folder-cart", ".shopping": "folder-cart", "_shopping": "folder-cart", + "-shopping": "folder-cart", "__shopping__": "folder-cart", "shop": "folder-cart", ".shop": "folder-cart", "_shop": "folder-cart", + "-shop": "folder-cart", "__shop__": "folder-cart", "home": "folder-home", ".home": "folder-home", "_home": "folder-home", + "-home": "folder-home", "__home__": "folder-home", "start": "folder-home", ".start": "folder-home", "_start": "folder-home", + "-start": "folder-home", "__start__": "folder-home", "main": "folder-home", ".main": "folder-home", "_main": "folder-home", + "-main": "folder-home", "__main__": "folder-home", "landing": "folder-home", ".landing": "folder-home", "_landing": "folder-home", + "-landing": "folder-home", "__landing__": "folder-home", "project": "folder-project", ".project": "folder-project", "_project": "folder-project", + "-project": "folder-project", "__project__": "folder-project", "projects": "folder-project", ".projects": "folder-project", "_projects": "folder-project", + "-projects": "folder-project", "__projects__": "folder-project", "proj": "folder-project", ".proj": "folder-project", "_proj": "folder-project", + "-proj": "folder-project", "__proj__": "folder-project", "projs": "folder-project", ".projs": "folder-project", "_projs": "folder-project", + "-projs": "folder-project", "__projs__": "folder-project", "prompt": "folder-prompts", ".prompt": "folder-prompts", "_prompt": "folder-prompts", + "-prompt": "folder-prompts", "__prompt__": "folder-prompts", "prompts": "folder-prompts", ".prompts": "folder-prompts", "_prompts": "folder-prompts", + "-prompts": "folder-prompts", "__prompts__": "folder-prompts", "interface": "folder-interface", ".interface": "folder-interface", "_interface": "folder-interface", + "-interface": "folder-interface", "__interface__": "folder-interface", "interfaces": "folder-interface", ".interfaces": "folder-interface", "_interfaces": "folder-interface", + "-interfaces": "folder-interface", "__interfaces__": "folder-interface", "netlify": "folder-netlify", ".netlify": "folder-netlify", "_netlify": "folder-netlify", + "-netlify": "folder-netlify", "__netlify__": "folder-netlify", "enum": "folder-enum", ".enum": "folder-enum", "_enum": "folder-enum", + "-enum": "folder-enum", "__enum__": "folder-enum", "enums": "folder-enum", ".enums": "folder-enum", "_enums": "folder-enum", + "-enums": "folder-enum", "__enums__": "folder-enum", "pact": "folder-contract", ".pact": "folder-contract", "_pact": "folder-contract", + "-pact": "folder-contract", "__pact__": "folder-contract", "pacts": "folder-contract", ".pacts": "folder-contract", "_pacts": "folder-contract", + "-pacts": "folder-contract", "__pacts__": "folder-contract", "contract": "folder-contract", ".contract": "folder-contract", "_contract": "folder-contract", + "-contract": "folder-contract", "__contract__": "folder-contract", "contracts": "folder-contract", ".contracts": "folder-contract", "_contracts": "folder-contract", + "-contracts": "folder-contract", "__contracts__": "folder-contract", "contract-testing": "folder-contract", ".contract-testing": "folder-contract", "_contract-testing": "folder-contract", + "-contract-testing": "folder-contract", "__contract-testing__": "folder-contract", "contract-test": "folder-contract", ".contract-test": "folder-contract", "_contract-test": "folder-contract", + "-contract-test": "folder-contract", "__contract-test__": "folder-contract", "contract-tests": "folder-contract", ".contract-tests": "folder-contract", "_contract-tests": "folder-contract", + "-contract-tests": "folder-contract", "__contract-tests__": "folder-contract", "helm": "folder-helm", ".helm": "folder-helm", "_helm": "folder-helm", + "-helm": "folder-helm", "__helm__": "folder-helm", "helmchart": "folder-helm", ".helmchart": "folder-helm", "_helmchart": "folder-helm", + "-helmchart": "folder-helm", "__helmchart__": "folder-helm", "helmcharts": "folder-helm", ".helmcharts": "folder-helm", "_helmcharts": "folder-helm", + "-helmcharts": "folder-helm", "__helmcharts__": "folder-helm", "queue": "folder-queue", ".queue": "folder-queue", "_queue": "folder-queue", + "-queue": "folder-queue", "__queue__": "folder-queue", "queues": "folder-queue", ".queues": "folder-queue", "_queues": "folder-queue", + "-queues": "folder-queue", "__queues__": "folder-queue", "bull": "folder-queue", ".bull": "folder-queue", "_bull": "folder-queue", + "-bull": "folder-queue", "__bull__": "folder-queue", "mq": "folder-queue", ".mq": "folder-queue", "_mq": "folder-queue", + "-mq": "folder-queue", "__mq__": "folder-queue", "vercel": "folder-vercel", ".vercel": "folder-vercel", "_vercel": "folder-vercel", + "-vercel": "folder-vercel", "__vercel__": "folder-vercel", "now": "folder-vercel", ".now": "folder-vercel", "_now": "folder-vercel", + "-now": "folder-vercel", "__now__": "folder-vercel", "cypress": "folder-cypress", ".cypress": "folder-cypress", "_cypress": "folder-cypress", + "-cypress": "folder-cypress", "__cypress__": "folder-cypress", "decorator": "folder-decorators", ".decorator": "folder-decorators", "_decorator": "folder-decorators", + "-decorator": "folder-decorators", "__decorator__": "folder-decorators", "decorators": "folder-decorators", ".decorators": "folder-decorators", "_decorators": "folder-decorators", + "-decorators": "folder-decorators", "__decorators__": "folder-decorators", "java": "folder-java", ".java": "folder-java", "_java": "folder-java", + "-java": "folder-java", "__java__": "folder-java", "resolver": "folder-resolver", ".resolver": "folder-resolver", "_resolver": "folder-resolver", + "-resolver": "folder-resolver", "__resolver__": "folder-resolver", "resolvers": "folder-resolver", ".resolvers": "folder-resolver", "_resolvers": "folder-resolver", + "-resolvers": "folder-resolver", "__resolvers__": "folder-resolver", "angular": "folder-angular", ".angular": "folder-angular", "_angular": "folder-angular", + "-angular": "folder-angular", "__angular__": "folder-angular", "unity": "folder-unity", ".unity": "folder-unity", "_unity": "folder-unity", + "-unity": "folder-unity", "__unity__": "folder-unity", "pdf": "folder-pdf", ".pdf": "folder-pdf", "_pdf": "folder-pdf", + "-pdf": "folder-pdf", "__pdf__": "folder-pdf", "pdfs": "folder-pdf", ".pdfs": "folder-pdf", "_pdfs": "folder-pdf", + "-pdfs": "folder-pdf", "__pdfs__": "folder-pdf", "protobuf": "folder-proto", ".protobuf": "folder-proto", "_protobuf": "folder-proto", + "-protobuf": "folder-proto", "__protobuf__": "folder-proto", "protobufs": "folder-proto", ".protobufs": "folder-proto", "_protobufs": "folder-proto", + "-protobufs": "folder-proto", "__protobufs__": "folder-proto", "proto": "folder-proto", ".proto": "folder-proto", "_proto": "folder-proto", + "-proto": "folder-proto", "protos": "folder-proto", ".protos": "folder-proto", "_protos": "folder-proto", + "-protos": "folder-proto", "__protos__": "folder-proto", "plastic": "folder-plastic", ".plastic": "folder-plastic", "_plastic": "folder-plastic", + "-plastic": "folder-plastic", "__plastic__": "folder-plastic", "gamemaker": "folder-gamemaker", ".gamemaker": "folder-gamemaker", "_gamemaker": "folder-gamemaker", + "-gamemaker": "folder-gamemaker", "__gamemaker__": "folder-gamemaker", "gamemaker2": "folder-gamemaker", ".gamemaker2": "folder-gamemaker", "_gamemaker2": "folder-gamemaker", + "-gamemaker2": "folder-gamemaker", "__gamemaker2__": "folder-gamemaker", "hg": "folder-mercurial", ".hg": "folder-mercurial", "_hg": "folder-mercurial", + "-hg": "folder-mercurial", "__hg__": "folder-mercurial", "hghooks": "folder-mercurial", ".hghooks": "folder-mercurial", "_hghooks": "folder-mercurial", + "-hghooks": "folder-mercurial", "__hghooks__": "folder-mercurial", "hgext": "folder-mercurial", ".hgext": "folder-mercurial", "_hgext": "folder-mercurial", + "-hgext": "folder-mercurial", "__hgext__": "folder-mercurial", "godot": "folder-godot", ".godot": "folder-godot", "_godot": "folder-godot", + "-godot": "folder-godot", "__godot__": "folder-godot", "godot-cpp": "folder-godot", ".godot-cpp": "folder-godot", "_godot-cpp": "folder-godot", + "-godot-cpp": "folder-godot", "__godot-cpp__": "folder-godot", "lottie": "folder-lottie", ".lottie": "folder-lottie", "_lottie": "folder-lottie", + "-lottie": "folder-lottie", "__lottie__": "folder-lottie", "lotties": "folder-lottie", ".lotties": "folder-lottie", "_lotties": "folder-lottie", + "-lotties": "folder-lottie", "__lotties__": "folder-lottie", "lottiefiles": "folder-lottie", ".lottiefiles": "folder-lottie", "_lottiefiles": "folder-lottie", + "-lottiefiles": "folder-lottie", "__lottiefiles__": "folder-lottie", "taskfile": "folder-taskfile", ".taskfile": "folder-taskfile", "_taskfile": "folder-taskfile", + "-taskfile": "folder-taskfile", "__taskfile__": "folder-taskfile", "taskfiles": "folder-taskfile", ".taskfiles": "folder-taskfile", "_taskfiles": "folder-taskfile", + "-taskfiles": "folder-taskfile", "__taskfiles__": "folder-taskfile", "drizzle": "folder-drizzle", ".drizzle": "folder-drizzle", "_drizzle": "folder-drizzle", + "-drizzle": "folder-drizzle", "__drizzle__": "folder-drizzle", "cloudflare": "folder-cloudflare", ".cloudflare": "folder-cloudflare", "_cloudflare": "folder-cloudflare", + "-cloudflare": "folder-cloudflare", "__cloudflare__": "folder-cloudflare", "seeds": "folder-seeders", ".seeds": "folder-seeders", "_seeds": "folder-seeders", + "-seeds": "folder-seeders", "__seeds__": "folder-seeders", "seeders": "folder-seeders", ".seeders": "folder-seeders", "_seeders": "folder-seeders", + "-seeders": "folder-seeders", "__seeders__": "folder-seeders", "seed": "folder-seeders", ".seed": "folder-seeders", "_seed": "folder-seeders", + "-seed": "folder-seeders", "__seed__": "folder-seeders", "seeding": "folder-seeders", ".seeding": "folder-seeders", "_seeding": "folder-seeders", + "-seeding": "folder-seeders", "__seeding__": "folder-seeders", "store": "folder-store", ".store": "folder-store", "_store": "folder-store", + "-store": "folder-store", "__store__": "folder-store", "stores": "folder-store", ".stores": "folder-store", "_stores": "folder-store", + "-stores": "folder-store", "__stores__": "folder-store", "bicep": "folder-bicep", ".bicep": "folder-bicep", "_bicep": "folder-bicep", + "-bicep": "folder-bicep", "__bicep__": "folder-bicep", "snap": "folder-snapcraft", ".snap": "folder-snapcraft", "_snap": "folder-snapcraft", + "-snap": "folder-snapcraft", "__snap__": "folder-snapcraft", "snapcraft": "folder-snapcraft", ".snapcraft": "folder-snapcraft", "_snapcraft": "folder-snapcraft", + "-snapcraft": "folder-snapcraft", "__snapcraft__": "folder-snapcraft", "dev": "folder-development", ".dev": "folder-development", "_dev": "folder-development", + "-dev": "folder-development", "__dev__": "folder-development", "development": "folder-development", ".development": "folder-development", "_development": "folder-development", + "-development": "folder-development", "__development__": "folder-development", "flutter": "folder-flutter", ".flutter": "folder-flutter", "_flutter": "folder-flutter", + "-flutter": "folder-flutter", "__flutter__": "folder-flutter", "snippet": "folder-snippet", ".snippet": "folder-snippet", "_snippet": "folder-snippet", + "-snippet": "folder-snippet", "__snippet__": "folder-snippet", "snippets": "folder-snippet", ".snippets": "folder-snippet", "_snippets": "folder-snippet", + "-snippets": "folder-snippet", "__snippets__": "folder-snippet", "element": "folder-element", ".element": "folder-element", "_element": "folder-element", + "-element": "folder-element", "__element__": "folder-element", "elements": "folder-element", ".elements": "folder-element", "_elements": "folder-element", + "-elements": "folder-element", "__elements__": "folder-element", "src-tauri": "folder-src-tauri", ".src-tauri": "folder-src-tauri", "_src-tauri": "folder-src-tauri", + "-src-tauri": "folder-src-tauri", "__src-tauri__": "folder-src-tauri", "favicon": "folder-favicon", ".favicon": "folder-favicon", "_favicon": "folder-favicon", + "-favicon": "folder-favicon", "__favicon__": "folder-favicon", "favicons": "folder-favicon", ".favicons": "folder-favicon", "_favicons": "folder-favicon", + "-favicons": "folder-favicon", "__favicons__": "folder-favicon", "feature": "folder-features", ".feature": "folder-features", "_feature": "folder-features", + "-feature": "folder-features", "__feature__": "folder-features", "features": "folder-features", ".features": "folder-features", "_features": "folder-features", + "-features": "folder-features", "__features__": "folder-features", "feat": "folder-features", ".feat": "folder-features", "_feat": "folder-features", + "-feat": "folder-features", "__feat__": "folder-features", "feats": "folder-features", ".feats": "folder-features", "_feats": "folder-features", + "-feats": "folder-features", "__feats__": "folder-features", "lefthook": "folder-lefthook", ".lefthook": "folder-lefthook", "_lefthook": "folder-lefthook", + "-lefthook": "folder-lefthook", "__lefthook__": "folder-lefthook", "lefthook-local": "folder-lefthook", ".lefthook-local": "folder-lefthook", "_lefthook-local": "folder-lefthook", + "-lefthook-local": "folder-lefthook", "__lefthook-local__": "folder-lefthook", "bloc": "folder-bloc", ".bloc": "folder-bloc", "_bloc": "folder-bloc", + "-bloc": "folder-bloc", "__bloc__": "folder-bloc", "cubit": "folder-bloc", ".cubit": "folder-bloc", "_cubit": "folder-bloc", + "-cubit": "folder-bloc", "__cubit__": "folder-bloc", "blocs": "folder-bloc", ".blocs": "folder-bloc", "_blocs": "folder-bloc", + "-blocs": "folder-bloc", "__blocs__": "folder-bloc", "cubits": "folder-bloc", ".cubits": "folder-bloc", "_cubits": "folder-bloc", + "-cubits": "folder-bloc", "__cubits__": "folder-bloc", "powershell": "folder-powershell", ".powershell": "folder-powershell", "_powershell": "folder-powershell", + "-powershell": "folder-powershell", "__powershell__": "folder-powershell", "ps": "folder-powershell", ".ps": "folder-powershell", "_ps": "folder-powershell", + "-ps": "folder-powershell", "__ps__": "folder-powershell", "ps1": "folder-powershell", ".ps1": "folder-powershell", "_ps1": "folder-powershell", + "-ps1": "folder-powershell", "__ps1__": "folder-powershell", "repository": "folder-repository", ".repository": "folder-repository", "_repository": "folder-repository", + "-repository": "folder-repository", "__repository__": "folder-repository", "repositories": "folder-repository", ".repositories": "folder-repository", "_repositories": "folder-repository", + "-repositories": "folder-repository", "__repositories__": "folder-repository", "repo": "folder-repository", ".repo": "folder-repository", "_repo": "folder-repository", + "-repo": "folder-repository", "__repo__": "folder-repository", "repos": "folder-repository", ".repos": "folder-repository", "_repos": "folder-repository", + "-repos": "folder-repository", "__repos__": "folder-repository", "luau": "folder-luau", ".luau": "folder-luau", "_luau": "folder-luau", + "-luau": "folder-luau", "__luau__": "folder-luau", "obsidian": "folder-obsidian", ".obsidian": "folder-obsidian", "_obsidian": "folder-obsidian", + "-obsidian": "folder-obsidian", "__obsidian__": "folder-obsidian", "trash": "folder-trash", ".trash": "folder-trash", "_trash": "folder-trash", + "-trash": "folder-trash", "__trash__": "folder-trash", "cline_docs": "folder-cline", ".cline_docs": "folder-cline", "_cline_docs": "folder-cline", + "-cline_docs": "folder-cline", "__cline_docs__": "folder-cline", "liquibase": "folder-liquibase", ".liquibase": "folder-liquibase", "_liquibase": "folder-liquibase", + "-liquibase": "folder-liquibase", "__liquibase__": "folder-liquibase", "dart": "folder-dart", ".dart": "folder-dart", "_dart": "folder-dart", + "-dart": "folder-dart", "__dart__": "folder-dart", "dart_tool": "folder-dart", ".dart_tool": "folder-dart", "_dart_tool": "folder-dart", + "-dart_tool": "folder-dart", "__dart_tool__": "folder-dart", "dart_tools": "folder-dart", ".dart_tools": "folder-dart", "_dart_tools": "folder-dart", + "-dart_tools": "folder-dart", "__dart_tools__": "folder-dart", "zeabur": "folder-zeabur", ".zeabur": "folder-zeabur", "_zeabur": "folder-zeabur", + "-zeabur": "folder-zeabur", "__zeabur__": "folder-zeabur", "kusto": "folder-kusto", ".kusto": "folder-kusto", "_kusto": "folder-kusto", + "-kusto": "folder-kusto", "__kusto__": "folder-kusto", "kql": "folder-kusto", ".kql": "folder-kusto", "_kql": "folder-kusto", + "-kql": "folder-kusto", "__kql__": "folder-kusto", "policy": "folder-policy", ".policy": "folder-policy", "_policy": "folder-policy", + "-policy": "folder-policy", "__policy__": "folder-policy", "policies": "folder-policy", ".policies": "folder-policy", "_policies": "folder-policy", + "-policies": "folder-policy", "__policies__": "folder-policy", "attachment": "folder-attachment", ".attachment": "folder-attachment", "_attachment": "folder-attachment", + "-attachment": "folder-attachment", "__attachment__": "folder-attachment", "attachments": "folder-attachment", ".attachments": "folder-attachment", "_attachments": "folder-attachment", + "-attachments": "folder-attachment", "__attachments__": "folder-attachment", "bibliography": "folder-bibliography", ".bibliography": "folder-bibliography", "_bibliography": "folder-bibliography", + "-bibliography": "folder-bibliography", "__bibliography__": "folder-bibliography", "bibliographies": "folder-bibliography", ".bibliographies": "folder-bibliography", "_bibliographies": "folder-bibliography", + "-bibliographies": "folder-bibliography", "__bibliographies__": "folder-bibliography", "book": "folder-bibliography", ".book": "folder-bibliography", "_book": "folder-bibliography", + "-book": "folder-bibliography", "__book__": "folder-bibliography", "books": "folder-bibliography", ".books": "folder-bibliography", "_books": "folder-bibliography", + "-books": "folder-bibliography", "__books__": "folder-bibliography", "link": "folder-link", ".link": "folder-link", "_link": "folder-link", + "-link": "folder-link", "__link__": "folder-link", "links": "folder-link", ".links": "folder-link", "_links": "folder-link", + "-links": "folder-link", "__links__": "folder-link", "pytorch": "folder-pytorch", ".pytorch": "folder-pytorch", "_pytorch": "folder-pytorch", + "-pytorch": "folder-pytorch", "__pytorch__": "folder-pytorch", "torch": "folder-pytorch", ".torch": "folder-pytorch", "_torch": "folder-pytorch", + "-torch": "folder-pytorch", "__torch__": "folder-pytorch", "blender": "folder-blender", ".blender": "folder-blender", "_blender": "folder-blender", + "-blender": "folder-blender", "__blender__": "folder-blender", "blender-assets": "folder-blender", ".blender-assets": "folder-blender", "_blender-assets": "folder-blender", + "-blender-assets": "folder-blender", "__blender-assets__": "folder-blender", "blender-files": "folder-blender", ".blender-files": "folder-blender", "_blender-files": "folder-blender", + "-blender-files": "folder-blender", "__blender-files__": "folder-blender", "blender-project": "folder-blender", ".blender-project": "folder-blender", "_blender-project": "folder-blender", + "-blender-project": "folder-blender", "__blender-project__": "folder-blender", "blender-models": "folder-blender", ".blender-models": "folder-blender", "_blender-models": "folder-blender", + "-blender-models": "folder-blender", "__blender-models__": "folder-blender", "atoms": "folder-atom", ".atoms": "folder-atom", "_atoms": "folder-atom", + "-atoms": "folder-atom", "__atoms__": "folder-atom", "atom": "folder-atom", ".atom": "folder-atom", "_atom": "folder-atom", + "-atom": "folder-atom", "__atom__": "folder-atom", "molecules": "folder-molecule", ".molecules": "folder-molecule", "_molecules": "folder-molecule", + "-molecules": "folder-molecule", "__molecules__": "folder-molecule", "molecule": "folder-molecule", ".molecule": "folder-molecule", "_molecule": "folder-molecule", + "-molecule": "folder-molecule", "__molecule__": "folder-molecule", "organisms": "folder-organism", ".organisms": "folder-organism", "_organisms": "folder-organism", + "-organisms": "folder-organism", "__organisms__": "folder-organism", "organism": "folder-organism", ".organism": "folder-organism", "_organism": "folder-organism", + "-organism": "folder-organism", "__organism__": "folder-organism", ".claude": "folder-claude", "..claude": "folder-claude", "_.claude": "folder-claude", + "-.claude": "folder-claude", "__.claude__": "folder-claude", ".cursor": "folder-cursor", "..cursor": "folder-cursor", "_.cursor": "folder-cursor", + "-.cursor": "folder-cursor", "__.cursor__": "folder-cursor", ".gemini": "folder-gemini-ai", "..gemini": "folder-gemini-ai", "_.gemini": "folder-gemini-ai", + "-.gemini": "folder-gemini-ai", "__.gemini__": "folder-gemini-ai", + "gemini": "folder-gemini-ai", + "_gemini": "folder-gemini-ai", + "-gemini": "folder-gemini-ai", + "__gemini__": "folder-gemini-ai", + "gemini-ai": "folder-gemini-ai", + ".gemini-ai": "folder-gemini-ai", + "_gemini-ai": "folder-gemini-ai", + "-gemini-ai": "folder-gemini-ai", + "__gemini-ai__": "folder-gemini-ai", + "geminiai": "folder-gemini-ai", + ".geminiai": "folder-gemini-ai", + "_geminiai": "folder-gemini-ai", + "-geminiai": "folder-gemini-ai", + "__geminiai__": "folder-gemini-ai", "input": "folder-input", ".input": "folder-input", "_input": "folder-input", + "-input": "folder-input", "__input__": "folder-input", "inputs": "folder-input", ".inputs": "folder-input", "_inputs": "folder-input", + "-inputs": "folder-input", "__inputs__": "folder-input", "io": "folder-input", ".io": "folder-input", "_io": "folder-input", + "-io": "folder-input", "__io__": "folder-input", "in": "folder-input", ".in": "folder-input", "_in": "folder-input", + "-in": "folder-input", "__in__": "folder-input", "salt": "folder-salt", ".salt": "folder-salt", "_salt": "folder-salt", + "-salt": "folder-salt", "__salt__": "folder-salt", "saltstack": "folder-salt", ".saltstack": "folder-salt", "_saltstack": "folder-salt", + "-saltstack": "folder-salt", "__saltstack__": "folder-salt", "simulations": "folder-simulations", ".simulations": "folder-simulations", "_simulations": "folder-simulations", + "-simulations": "folder-simulations", "__simulations__": "folder-simulations", "simulation": "folder-simulations", ".simulation": "folder-simulations", "_simulation": "folder-simulations", + "-simulation": "folder-simulations", "__simulation__": "folder-simulations", "sim": "folder-simulations", ".sim": "folder-simulations", "_sim": "folder-simulations", + "-sim": "folder-simulations", "__sim__": "folder-simulations", "sims": "folder-simulations", ".sims": "folder-simulations", "_sims": "folder-simulations", + "-sims": "folder-simulations", "__sims__": "folder-simulations", "metro": "folder-metro", ".metro": "folder-metro", "_metro": "folder-metro", + "-metro": "folder-metro", "__metro__": "folder-metro", "filter": "folder-filter", ".filter": "folder-filter", "_filter": "folder-filter", + "-filter": "folder-filter", "__filter__": "folder-filter", "filters": "folder-filter", ".filters": "folder-filter", "_filters": "folder-filter", + "-filters": "folder-filter", "__filters__": "folder-filter", "toc": "folder-toc", ".toc": "folder-toc", "_toc": "folder-toc", + "-toc": "folder-toc", "__toc__": "folder-toc", "table-of-contents": "folder-toc", ".table-of-contents": "folder-toc", "_table-of-contents": "folder-toc", + "-table-of-contents": "folder-toc", "__table-of-contents__": "folder-toc", "cue": "folder-cue", ".cue": "folder-cue", "_cue": "folder-cue", + "-cue": "folder-cue", "__cue__": "folder-cue", "cues": "folder-cue", ".cues": "folder-cue", "_cues": "folder-cue", + "-cues": "folder-cue", "__cues__": "folder-cue", "license": "folder-license", ".license": "folder-license", "_license": "folder-license", + "-license": "folder-license", "__license__": "folder-license", "licenses": "folder-license", ".licenses": "folder-license", "_licenses": "folder-license", - "__licenses__": "folder-license" + "-licenses": "folder-license", + "__licenses__": "folder-license", + "form": "folder-form", + ".form": "folder-form", + "_form": "folder-form", + "-form": "folder-form", + "__form__": "folder-form", + "forms": "folder-form", + ".forms": "folder-form", + "_forms": "folder-form", + "-forms": "folder-form", + "__forms__": "folder-form" }, "folderNamesExpanded": { "rust": "folder-rust-open", ".rust": "folder-rust-open", "_rust": "folder-rust-open", + "-rust": "folder-rust-open", "__rust__": "folder-rust-open", "bot": "folder-robot-open", ".bot": "folder-robot-open", "_bot": "folder-robot-open", + "-bot": "folder-robot-open", "__bot__": "folder-robot-open", "bots": "folder-robot-open", ".bots": "folder-robot-open", "_bots": "folder-robot-open", + "-bots": "folder-robot-open", "__bots__": "folder-robot-open", "robot": "folder-robot-open", ".robot": "folder-robot-open", "_robot": "folder-robot-open", + "-robot": "folder-robot-open", "__robot__": "folder-robot-open", "robots": "folder-robot-open", ".robots": "folder-robot-open", "_robots": "folder-robot-open", + "-robots": "folder-robot-open", "__robots__": "folder-robot-open", + "agent": "folder-robot-open", + ".agent": "folder-robot-open", + "_agent": "folder-robot-open", + "-agent": "folder-robot-open", + "__agent__": "folder-robot-open", + "agents": "folder-robot-open", + ".agents": "folder-robot-open", + "_agents": "folder-robot-open", + "-agents": "folder-robot-open", + "__agents__": "folder-robot-open", "src": "folder-src-open", ".src": "folder-src-open", "_src": "folder-src-open", + "-src": "folder-src-open", "__src__": "folder-src-open", "srcs": "folder-src-open", ".srcs": "folder-src-open", "_srcs": "folder-src-open", + "-srcs": "folder-src-open", "__srcs__": "folder-src-open", "source": "folder-src-open", ".source": "folder-src-open", "_source": "folder-src-open", + "-source": "folder-src-open", "__source__": "folder-src-open", "sources": "folder-src-open", ".sources": "folder-src-open", "_sources": "folder-src-open", + "-sources": "folder-src-open", "__sources__": "folder-src-open", "code": "folder-src-open", ".code": "folder-src-open", "_code": "folder-src-open", + "-code": "folder-src-open", "__code__": "folder-src-open", "dist": "folder-dist-open", ".dist": "folder-dist-open", "_dist": "folder-dist-open", + "-dist": "folder-dist-open", "__dist__": "folder-dist-open", "out": "folder-dist-open", ".out": "folder-dist-open", "_out": "folder-dist-open", + "-out": "folder-dist-open", "__out__": "folder-dist-open", "output": "folder-dist-open", ".output": "folder-dist-open", "_output": "folder-dist-open", + "-output": "folder-dist-open", "__output__": "folder-dist-open", + "outputs": "folder-dist-open", + ".outputs": "folder-dist-open", + "_outputs": "folder-dist-open", + "-outputs": "folder-dist-open", + "__outputs__": "folder-dist-open", "build": "folder-dist-open", ".build": "folder-dist-open", "_build": "folder-dist-open", + "-build": "folder-dist-open", "__build__": "folder-dist-open", "builds": "folder-dist-open", ".builds": "folder-dist-open", "_builds": "folder-dist-open", + "-builds": "folder-dist-open", "__builds__": "folder-dist-open", "release": "folder-dist-open", ".release": "folder-dist-open", "_release": "folder-dist-open", + "-release": "folder-dist-open", "__release__": "folder-dist-open", "bin": "folder-dist-open", ".bin": "folder-dist-open", "_bin": "folder-dist-open", + "-bin": "folder-dist-open", "__bin__": "folder-dist-open", "distribution": "folder-dist-open", ".distribution": "folder-dist-open", "_distribution": "folder-dist-open", + "-distribution": "folder-dist-open", "__distribution__": "folder-dist-open", "built": "folder-dist-open", ".built": "folder-dist-open", "_built": "folder-dist-open", + "-built": "folder-dist-open", "__built__": "folder-dist-open", "compiled": "folder-dist-open", ".compiled": "folder-dist-open", "_compiled": "folder-dist-open", + "-compiled": "folder-dist-open", "__compiled__": "folder-dist-open", "css": "folder-css-open", ".css": "folder-css-open", "_css": "folder-css-open", + "-css": "folder-css-open", "__css__": "folder-css-open", "stylesheet": "folder-css-open", ".stylesheet": "folder-css-open", "_stylesheet": "folder-css-open", + "-stylesheet": "folder-css-open", "__stylesheet__": "folder-css-open", "stylesheets": "folder-css-open", ".stylesheets": "folder-css-open", "_stylesheets": "folder-css-open", + "-stylesheets": "folder-css-open", "__stylesheets__": "folder-css-open", "style": "folder-css-open", ".style": "folder-css-open", "_style": "folder-css-open", + "-style": "folder-css-open", "__style__": "folder-css-open", "styles": "folder-css-open", ".styles": "folder-css-open", "_styles": "folder-css-open", + "-styles": "folder-css-open", "__styles__": "folder-css-open", "sass": "folder-sass-open", ".sass": "folder-sass-open", "_sass": "folder-sass-open", + "-sass": "folder-sass-open", "__sass__": "folder-sass-open", "scss": "folder-sass-open", ".scss": "folder-sass-open", "_scss": "folder-sass-open", + "-scss": "folder-sass-open", "__scss__": "folder-sass-open", "tv": "folder-television-open", ".tv": "folder-television-open", "_tv": "folder-television-open", + "-tv": "folder-television-open", "__tv__": "folder-television-open", "television": "folder-television-open", ".television": "folder-television-open", "_television": "folder-television-open", + "-television": "folder-television-open", "__television__": "folder-television-open", "desktop": "folder-desktop-open", ".desktop": "folder-desktop-open", "_desktop": "folder-desktop-open", + "-desktop": "folder-desktop-open", "__desktop__": "folder-desktop-open", "display": "folder-desktop-open", ".display": "folder-desktop-open", "_display": "folder-desktop-open", + "-display": "folder-desktop-open", "__display__": "folder-desktop-open", "console": "folder-console-open", ".console": "folder-console-open", "_console": "folder-console-open", + "-console": "folder-console-open", "__console__": "folder-console-open", "xbox": "folder-console-open", ".xbox": "folder-console-open", "_xbox": "folder-console-open", + "-xbox": "folder-console-open", "__xbox__": "folder-console-open", "ps4": "folder-console-open", ".ps4": "folder-console-open", "_ps4": "folder-console-open", + "-ps4": "folder-console-open", "__ps4__": "folder-console-open", "ps5": "folder-console-open", ".ps5": "folder-console-open", "_ps5": "folder-console-open", + "-ps5": "folder-console-open", "__ps5__": "folder-console-open", "switch": "folder-console-open", ".switch": "folder-console-open", "_switch": "folder-console-open", + "-switch": "folder-console-open", "__switch__": "folder-console-open", "game": "folder-console-open", ".game": "folder-console-open", "_game": "folder-console-open", + "-game": "folder-console-open", "__game__": "folder-console-open", "games": "folder-console-open", ".games": "folder-console-open", "_games": "folder-console-open", + "-games": "folder-console-open", "__games__": "folder-console-open", "images": "folder-images-open", ".images": "folder-images-open", "_images": "folder-images-open", + "-images": "folder-images-open", "__images__": "folder-images-open", "image": "folder-images-open", ".image": "folder-images-open", "_image": "folder-images-open", + "-image": "folder-images-open", "__image__": "folder-images-open", "imgs": "folder-images-open", ".imgs": "folder-images-open", "_imgs": "folder-images-open", + "-imgs": "folder-images-open", "__imgs__": "folder-images-open", "img": "folder-images-open", ".img": "folder-images-open", "_img": "folder-images-open", + "-img": "folder-images-open", "__img__": "folder-images-open", "icons": "folder-images-open", ".icons": "folder-images-open", "_icons": "folder-images-open", + "-icons": "folder-images-open", "__icons__": "folder-images-open", "icon": "folder-images-open", ".icon": "folder-images-open", "_icon": "folder-images-open", + "-icon": "folder-images-open", "__icon__": "folder-images-open", "icos": "folder-images-open", ".icos": "folder-images-open", "_icos": "folder-images-open", + "-icos": "folder-images-open", "__icos__": "folder-images-open", "ico": "folder-images-open", ".ico": "folder-images-open", "_ico": "folder-images-open", + "-ico": "folder-images-open", "__ico__": "folder-images-open", "figures": "folder-images-open", ".figures": "folder-images-open", "_figures": "folder-images-open", + "-figures": "folder-images-open", "__figures__": "folder-images-open", "figure": "folder-images-open", ".figure": "folder-images-open", "_figure": "folder-images-open", + "-figure": "folder-images-open", "__figure__": "folder-images-open", "figs": "folder-images-open", ".figs": "folder-images-open", "_figs": "folder-images-open", + "-figs": "folder-images-open", "__figs__": "folder-images-open", "fig": "folder-images-open", ".fig": "folder-images-open", "_fig": "folder-images-open", + "-fig": "folder-images-open", "__fig__": "folder-images-open", "screenshot": "folder-images-open", ".screenshot": "folder-images-open", "_screenshot": "folder-images-open", + "-screenshot": "folder-images-open", "__screenshot__": "folder-images-open", "screenshots": "folder-images-open", ".screenshots": "folder-images-open", "_screenshots": "folder-images-open", + "-screenshots": "folder-images-open", "__screenshots__": "folder-images-open", "screengrab": "folder-images-open", ".screengrab": "folder-images-open", "_screengrab": "folder-images-open", + "-screengrab": "folder-images-open", "__screengrab__": "folder-images-open", "screengrabs": "folder-images-open", ".screengrabs": "folder-images-open", "_screengrabs": "folder-images-open", + "-screengrabs": "folder-images-open", "__screengrabs__": "folder-images-open", "pic": "folder-images-open", ".pic": "folder-images-open", "_pic": "folder-images-open", + "-pic": "folder-images-open", "__pic__": "folder-images-open", "pics": "folder-images-open", ".pics": "folder-images-open", "_pics": "folder-images-open", + "-pics": "folder-images-open", "__pics__": "folder-images-open", "picture": "folder-images-open", ".picture": "folder-images-open", "_picture": "folder-images-open", + "-picture": "folder-images-open", "__picture__": "folder-images-open", "pictures": "folder-images-open", ".pictures": "folder-images-open", "_pictures": "folder-images-open", + "-pictures": "folder-images-open", "__pictures__": "folder-images-open", "photo": "folder-images-open", ".photo": "folder-images-open", "_photo": "folder-images-open", + "-photo": "folder-images-open", "__photo__": "folder-images-open", "photos": "folder-images-open", ".photos": "folder-images-open", "_photos": "folder-images-open", + "-photos": "folder-images-open", "__photos__": "folder-images-open", "photograph": "folder-images-open", ".photograph": "folder-images-open", "_photograph": "folder-images-open", + "-photograph": "folder-images-open", "__photograph__": "folder-images-open", "photographs": "folder-images-open", ".photographs": "folder-images-open", "_photographs": "folder-images-open", + "-photographs": "folder-images-open", "__photographs__": "folder-images-open", "texture": "folder-images-open", ".texture": "folder-images-open", "_texture": "folder-images-open", + "-texture": "folder-images-open", "__texture__": "folder-images-open", "textures": "folder-images-open", ".textures": "folder-images-open", "_textures": "folder-images-open", + "-textures": "folder-images-open", "__textures__": "folder-images-open", "script": "folder-scripts-open", ".script": "folder-scripts-open", "_script": "folder-scripts-open", + "-script": "folder-scripts-open", "__script__": "folder-scripts-open", "scripts": "folder-scripts-open", ".scripts": "folder-scripts-open", "_scripts": "folder-scripts-open", + "-scripts": "folder-scripts-open", "__scripts__": "folder-scripts-open", "scripting": "folder-scripts-open", ".scripting": "folder-scripts-open", "_scripting": "folder-scripts-open", + "-scripting": "folder-scripts-open", "__scripting__": "folder-scripts-open", "node": "folder-node-open", ".node": "folder-node-open", "_node": "folder-node-open", + "-node": "folder-node-open", "__node__": "folder-node-open", "nodejs": "folder-node-open", ".nodejs": "folder-node-open", "_nodejs": "folder-node-open", + "-nodejs": "folder-node-open", "__nodejs__": "folder-node-open", "node_modules": "folder-node-open", ".node_modules": "folder-node-open", "_node_modules": "folder-node-open", + "-node_modules": "folder-node-open", "__node_modules__": "folder-node-open", "js": "folder-javascript-open", ".js": "folder-javascript-open", "_js": "folder-javascript-open", + "-js": "folder-javascript-open", "__js__": "folder-javascript-open", "javascript": "folder-javascript-open", ".javascript": "folder-javascript-open", "_javascript": "folder-javascript-open", + "-javascript": "folder-javascript-open", "__javascript__": "folder-javascript-open", "javascripts": "folder-javascript-open", ".javascripts": "folder-javascript-open", "_javascripts": "folder-javascript-open", + "-javascripts": "folder-javascript-open", "__javascripts__": "folder-javascript-open", "cjs": "folder-javascript-open", ".cjs": "folder-javascript-open", "_cjs": "folder-javascript-open", + "-cjs": "folder-javascript-open", "__cjs__": "folder-javascript-open", "mjs": "folder-javascript-open", ".mjs": "folder-javascript-open", "_mjs": "folder-javascript-open", + "-mjs": "folder-javascript-open", "__mjs__": "folder-javascript-open", "json": "folder-json-open", ".json": "folder-json-open", "_json": "folder-json-open", + "-json": "folder-json-open", "__json__": "folder-json-open", "jsons": "folder-json-open", ".jsons": "folder-json-open", "_jsons": "folder-json-open", + "-jsons": "folder-json-open", "__jsons__": "folder-json-open", "jsonc": "folder-json-open", ".jsonc": "folder-json-open", "_jsonc": "folder-json-open", + "-jsonc": "folder-json-open", "__jsonc__": "folder-json-open", "jsonl": "folder-json-open", ".jsonl": "folder-json-open", "_jsonl": "folder-json-open", + "-jsonl": "folder-json-open", "__jsonl__": "folder-json-open", "font": "folder-font-open", ".font": "folder-font-open", "_font": "folder-font-open", + "-font": "folder-font-open", "__font__": "folder-font-open", "fonts": "folder-font-open", ".fonts": "folder-font-open", "_fonts": "folder-font-open", + "-fonts": "folder-font-open", "__fonts__": "folder-font-open", "typeface": "folder-font-open", ".typeface": "folder-font-open", "_typeface": "folder-font-open", + "-typeface": "folder-font-open", "__typeface__": "folder-font-open", "typefaces": "folder-font-open", ".typefaces": "folder-font-open", "_typefaces": "folder-font-open", + "-typefaces": "folder-font-open", "__typefaces__": "folder-font-open", "bower_components": "folder-bower-open", ".bower_components": "folder-bower-open", "_bower_components": "folder-bower-open", + "-bower_components": "folder-bower-open", "__bower_components__": "folder-bower-open", "test": "folder-test-open", ".test": "folder-test-open", "_test": "folder-test-open", + "-test": "folder-test-open", "__test__": "folder-test-open", "tests": "folder-test-open", ".tests": "folder-test-open", "_tests": "folder-test-open", + "-tests": "folder-test-open", "__tests__": "folder-test-open", "testing": "folder-test-open", ".testing": "folder-test-open", "_testing": "folder-test-open", + "-testing": "folder-test-open", "__testing__": "folder-test-open", "snapshots": "folder-test-open", ".snapshots": "folder-test-open", "_snapshots": "folder-test-open", + "-snapshots": "folder-test-open", "__snapshots__": "folder-test-open", "spec": "folder-test-open", ".spec": "folder-test-open", "_spec": "folder-test-open", + "-spec": "folder-test-open", "__spec__": "folder-test-open", "specs": "folder-test-open", ".specs": "folder-test-open", "_specs": "folder-test-open", + "-specs": "folder-test-open", "__specs__": "folder-test-open", "testfiles": "folder-test-open", ".testfiles": "folder-test-open", "_testfiles": "folder-test-open", + "-testfiles": "folder-test-open", "__testfiles__": "folder-test-open", "directive": "folder-directive-open", ".directive": "folder-directive-open", "_directive": "folder-directive-open", + "-directive": "folder-directive-open", "__directive__": "folder-directive-open", "directives": "folder-directive-open", ".directives": "folder-directive-open", "_directives": "folder-directive-open", + "-directives": "folder-directive-open", "__directives__": "folder-directive-open", "jinja": "folder-jinja-open", ".jinja": "folder-jinja-open", "_jinja": "folder-jinja-open", + "-jinja": "folder-jinja-open", "__jinja__": "folder-jinja-open", "jinja2": "folder-jinja-open", ".jinja2": "folder-jinja-open", "_jinja2": "folder-jinja-open", + "-jinja2": "folder-jinja-open", "__jinja2__": "folder-jinja-open", "j2": "folder-jinja-open", ".j2": "folder-jinja-open", "_j2": "folder-jinja-open", + "-j2": "folder-jinja-open", "__j2__": "folder-jinja-open", "markdown": "folder-markdown-open", ".markdown": "folder-markdown-open", "_markdown": "folder-markdown-open", + "-markdown": "folder-markdown-open", "__markdown__": "folder-markdown-open", "md": "folder-markdown-open", ".md": "folder-markdown-open", "_md": "folder-markdown-open", + "-md": "folder-markdown-open", "__md__": "folder-markdown-open", "pdm-plugins": "folder-pdm-open", ".pdm-plugins": "folder-pdm-open", "_pdm-plugins": "folder-pdm-open", + "-pdm-plugins": "folder-pdm-open", "__pdm-plugins__": "folder-pdm-open", "pdm-build": "folder-pdm-open", ".pdm-build": "folder-pdm-open", "_pdm-build": "folder-pdm-open", + "-pdm-build": "folder-pdm-open", "__pdm-build__": "folder-pdm-open", "php": "folder-php-open", ".php": "folder-php-open", "_php": "folder-php-open", + "-php": "folder-php-open", "__php__": "folder-php-open", "phpmailer": "folder-phpmailer-open", ".phpmailer": "folder-phpmailer-open", "_phpmailer": "folder-phpmailer-open", + "-phpmailer": "folder-phpmailer-open", "__phpmailer__": "folder-phpmailer-open", "sublime": "folder-sublime-open", ".sublime": "folder-sublime-open", "_sublime": "folder-sublime-open", + "-sublime": "folder-sublime-open", "__sublime__": "folder-sublime-open", "doc": "folder-docs-open", ".doc": "folder-docs-open", "_doc": "folder-docs-open", + "-doc": "folder-docs-open", "__doc__": "folder-docs-open", "docs": "folder-docs-open", ".docs": "folder-docs-open", "_docs": "folder-docs-open", + "-docs": "folder-docs-open", "__docs__": "folder-docs-open", "document": "folder-docs-open", ".document": "folder-docs-open", "_document": "folder-docs-open", + "-document": "folder-docs-open", "__document__": "folder-docs-open", "documents": "folder-docs-open", ".documents": "folder-docs-open", "_documents": "folder-docs-open", + "-documents": "folder-docs-open", "__documents__": "folder-docs-open", "documentation": "folder-docs-open", ".documentation": "folder-docs-open", "_documentation": "folder-docs-open", + "-documentation": "folder-docs-open", "__documentation__": "folder-docs-open", "post": "folder-docs-open", ".post": "folder-docs-open", "_post": "folder-docs-open", + "-post": "folder-docs-open", "__post__": "folder-docs-open", "posts": "folder-docs-open", ".posts": "folder-docs-open", "_posts": "folder-docs-open", + "-posts": "folder-docs-open", "__posts__": "folder-docs-open", "article": "folder-docs-open", ".article": "folder-docs-open", "_article": "folder-docs-open", + "-article": "folder-docs-open", "__article__": "folder-docs-open", "articles": "folder-docs-open", ".articles": "folder-docs-open", "_articles": "folder-docs-open", + "-articles": "folder-docs-open", "__articles__": "folder-docs-open", "wiki": "folder-docs-open", ".wiki": "folder-docs-open", "_wiki": "folder-docs-open", + "-wiki": "folder-docs-open", "__wiki__": "folder-docs-open", "news": "folder-docs-open", ".news": "folder-docs-open", "_news": "folder-docs-open", + "-news": "folder-docs-open", "__news__": "folder-docs-open", "blog": "folder-docs-open", ".blog": "folder-docs-open", "_blog": "folder-docs-open", + "-blog": "folder-docs-open", "__blog__": "folder-docs-open", "knowledge": "folder-docs-open", ".knowledge": "folder-docs-open", "_knowledge": "folder-docs-open", + "-knowledge": "folder-docs-open", "__knowledge__": "folder-docs-open", "diary": "folder-docs-open", ".diary": "folder-docs-open", "_diary": "folder-docs-open", + "-diary": "folder-docs-open", "__diary__": "folder-docs-open", "note": "folder-docs-open", ".note": "folder-docs-open", "_note": "folder-docs-open", + "-note": "folder-docs-open", "__note__": "folder-docs-open", "notes": "folder-docs-open", ".notes": "folder-docs-open", "_notes": "folder-docs-open", + "-notes": "folder-docs-open", "__notes__": "folder-docs-open", "github/workflows": "folder-gh-workflows-open", ".github/workflows": "folder-gh-workflows-open", "_github/workflows": "folder-gh-workflows-open", + "-github/workflows": "folder-gh-workflows-open", "__github/workflows__": "folder-gh-workflows-open", "git": "folder-git-open", ".git": "folder-git-open", "_git": "folder-git-open", + "-git": "folder-git-open", "__git__": "folder-git-open", "patches": "folder-git-open", ".patches": "folder-git-open", "_patches": "folder-git-open", + "-patches": "folder-git-open", "__patches__": "folder-git-open", "githooks": "folder-git-open", ".githooks": "folder-git-open", "_githooks": "folder-git-open", + "-githooks": "folder-git-open", "__githooks__": "folder-git-open", "submodules": "folder-git-open", ".submodules": "folder-git-open", "_submodules": "folder-git-open", + "-submodules": "folder-git-open", "__submodules__": "folder-git-open", "github": "folder-github-open", ".github": "folder-github-open", "_github": "folder-github-open", + "-github": "folder-github-open", "__github__": "folder-github-open", "gitea": "folder-gitea-open", ".gitea": "folder-gitea-open", "_gitea": "folder-gitea-open", + "-gitea": "folder-gitea-open", "__gitea__": "folder-gitea-open", "gitlab": "folder-gitlab-open", ".gitlab": "folder-gitlab-open", "_gitlab": "folder-gitlab-open", + "-gitlab": "folder-gitlab-open", "__gitlab__": "folder-gitlab-open", "forgejo": "folder-forgejo-open", ".forgejo": "folder-forgejo-open", "_forgejo": "folder-forgejo-open", + "-forgejo": "folder-forgejo-open", "__forgejo__": "folder-forgejo-open", "vscode": "folder-vscode-open", ".vscode": "folder-vscode-open", "_vscode": "folder-vscode-open", + "-vscode": "folder-vscode-open", "__vscode__": "folder-vscode-open", "vscode-test": "folder-vscode-open", ".vscode-test": "folder-vscode-open", "_vscode-test": "folder-vscode-open", + "-vscode-test": "folder-vscode-open", "__vscode-test__": "folder-vscode-open", "view": "folder-views-open", ".view": "folder-views-open", "_view": "folder-views-open", + "-view": "folder-views-open", "__view__": "folder-views-open", "views": "folder-views-open", ".views": "folder-views-open", "_views": "folder-views-open", + "-views": "folder-views-open", "__views__": "folder-views-open", "screen": "folder-views-open", ".screen": "folder-views-open", "_screen": "folder-views-open", + "-screen": "folder-views-open", "__screen__": "folder-views-open", "screens": "folder-views-open", ".screens": "folder-views-open", "_screens": "folder-views-open", + "-screens": "folder-views-open", "__screens__": "folder-views-open", "page": "folder-views-open", ".page": "folder-views-open", "_page": "folder-views-open", + "-page": "folder-views-open", "__page__": "folder-views-open", "pages": "folder-views-open", ".pages": "folder-views-open", "_pages": "folder-views-open", + "-pages": "folder-views-open", "__pages__": "folder-views-open", "public_html": "folder-views-open", ".public_html": "folder-views-open", "_public_html": "folder-views-open", + "-public_html": "folder-views-open", "__public_html__": "folder-views-open", "html": "folder-views-open", ".html": "folder-views-open", "_html": "folder-views-open", + "-html": "folder-views-open", "__html__": "folder-views-open", "vue": "folder-vue-open", ".vue": "folder-vue-open", "_vue": "folder-vue-open", + "-vue": "folder-vue-open", "__vue__": "folder-vue-open", "vuepress": "folder-vuepress-open", ".vuepress": "folder-vuepress-open", "_vuepress": "folder-vuepress-open", + "-vuepress": "folder-vuepress-open", "__vuepress__": "folder-vuepress-open", "expo": "folder-expo-open", ".expo": "folder-expo-open", "_expo": "folder-expo-open", + "-expo": "folder-expo-open", "__expo__": "folder-expo-open", "expo-shared": "folder-expo-open", ".expo-shared": "folder-expo-open", "_expo-shared": "folder-expo-open", + "-expo-shared": "folder-expo-open", "__expo-shared__": "folder-expo-open", "cfg": "folder-config-open", ".cfg": "folder-config-open", "_cfg": "folder-config-open", + "-cfg": "folder-config-open", "__cfg__": "folder-config-open", "cfgs": "folder-config-open", ".cfgs": "folder-config-open", "_cfgs": "folder-config-open", + "-cfgs": "folder-config-open", "__cfgs__": "folder-config-open", "conf": "folder-config-open", ".conf": "folder-config-open", "_conf": "folder-config-open", + "-conf": "folder-config-open", "__conf__": "folder-config-open", "confs": "folder-config-open", ".confs": "folder-config-open", "_confs": "folder-config-open", + "-confs": "folder-config-open", "__confs__": "folder-config-open", "config": "folder-config-open", ".config": "folder-config-open", "_config": "folder-config-open", + "-config": "folder-config-open", "__config__": "folder-config-open", "configs": "folder-config-open", ".configs": "folder-config-open", "_configs": "folder-config-open", + "-configs": "folder-config-open", "__configs__": "folder-config-open", "configuration": "folder-config-open", ".configuration": "folder-config-open", "_configuration": "folder-config-open", + "-configuration": "folder-config-open", "__configuration__": "folder-config-open", "configurations": "folder-config-open", ".configurations": "folder-config-open", "_configurations": "folder-config-open", + "-configurations": "folder-config-open", "__configurations__": "folder-config-open", "setting": "folder-config-open", ".setting": "folder-config-open", "_setting": "folder-config-open", + "-setting": "folder-config-open", "__setting__": "folder-config-open", "settings": "folder-config-open", ".settings": "folder-config-open", "_settings": "folder-config-open", + "-settings": "folder-config-open", "__settings__": "folder-config-open", "META-INF": "folder-config-open", ".META-INF": "folder-config-open", "_META-INF": "folder-config-open", + "-META-INF": "folder-config-open", "__META-INF__": "folder-config-open", "option": "folder-config-open", ".option": "folder-config-open", "_option": "folder-config-open", + "-option": "folder-config-open", "__option__": "folder-config-open", "options": "folder-config-open", ".options": "folder-config-open", "_options": "folder-config-open", + "-options": "folder-config-open", "__options__": "folder-config-open", "pref": "folder-config-open", ".pref": "folder-config-open", "_pref": "folder-config-open", + "-pref": "folder-config-open", "__pref__": "folder-config-open", "prefs": "folder-config-open", ".prefs": "folder-config-open", "_prefs": "folder-config-open", + "-prefs": "folder-config-open", "__prefs__": "folder-config-open", "preference": "folder-config-open", ".preference": "folder-config-open", "_preference": "folder-config-open", + "-preference": "folder-config-open", "__preference__": "folder-config-open", "preferences": "folder-config-open", ".preferences": "folder-config-open", "_preferences": "folder-config-open", + "-preferences": "folder-config-open", "__preferences__": "folder-config-open", "props": "folder-config-open", ".props": "folder-config-open", "_props": "folder-config-open", + "-props": "folder-config-open", "__props__": "folder-config-open", "properties": "folder-config-open", ".properties": "folder-config-open", "_properties": "folder-config-open", + "-properties": "folder-config-open", "__properties__": "folder-config-open", "i18n": "folder-i18n-open", ".i18n": "folder-i18n-open", "_i18n": "folder-i18n-open", + "-i18n": "folder-i18n-open", "__i18n__": "folder-i18n-open", "internationalization": "folder-i18n-open", ".internationalization": "folder-i18n-open", "_internationalization": "folder-i18n-open", + "-internationalization": "folder-i18n-open", "__internationalization__": "folder-i18n-open", "lang": "folder-i18n-open", ".lang": "folder-i18n-open", "_lang": "folder-i18n-open", + "-lang": "folder-i18n-open", "__lang__": "folder-i18n-open", "langs": "folder-i18n-open", ".langs": "folder-i18n-open", "_langs": "folder-i18n-open", + "-langs": "folder-i18n-open", "__langs__": "folder-i18n-open", "language": "folder-i18n-open", ".language": "folder-i18n-open", "_language": "folder-i18n-open", + "-language": "folder-i18n-open", "__language__": "folder-i18n-open", "languages": "folder-i18n-open", ".languages": "folder-i18n-open", "_languages": "folder-i18n-open", + "-languages": "folder-i18n-open", "__languages__": "folder-i18n-open", "locale": "folder-i18n-open", ".locale": "folder-i18n-open", "_locale": "folder-i18n-open", + "-locale": "folder-i18n-open", "__locale__": "folder-i18n-open", "locales": "folder-i18n-open", ".locales": "folder-i18n-open", "_locales": "folder-i18n-open", + "-locales": "folder-i18n-open", "__locales__": "folder-i18n-open", "l10n": "folder-i18n-open", ".l10n": "folder-i18n-open", "_l10n": "folder-i18n-open", + "-l10n": "folder-i18n-open", "__l10n__": "folder-i18n-open", "localization": "folder-i18n-open", ".localization": "folder-i18n-open", "_localization": "folder-i18n-open", + "-localization": "folder-i18n-open", "__localization__": "folder-i18n-open", "translation": "folder-i18n-open", ".translation": "folder-i18n-open", "_translation": "folder-i18n-open", + "-translation": "folder-i18n-open", "__translation__": "folder-i18n-open", "translate": "folder-i18n-open", ".translate": "folder-i18n-open", "_translate": "folder-i18n-open", + "-translate": "folder-i18n-open", "__translate__": "folder-i18n-open", "translations": "folder-i18n-open", ".translations": "folder-i18n-open", "_translations": "folder-i18n-open", + "-translations": "folder-i18n-open", "__translations__": "folder-i18n-open", "tx": "folder-i18n-open", ".tx": "folder-i18n-open", "_tx": "folder-i18n-open", + "-tx": "folder-i18n-open", "__tx__": "folder-i18n-open", "components": "folder-components-open", ".components": "folder-components-open", "_components": "folder-components-open", + "-components": "folder-components-open", "__components__": "folder-components-open", "widget": "folder-components-open", ".widget": "folder-components-open", "_widget": "folder-components-open", + "-widget": "folder-components-open", "__widget__": "folder-components-open", "widgets": "folder-components-open", ".widgets": "folder-components-open", "_widgets": "folder-components-open", + "-widgets": "folder-components-open", "__widgets__": "folder-components-open", "fragments": "folder-components-open", ".fragments": "folder-components-open", "_fragments": "folder-components-open", + "-fragments": "folder-components-open", "__fragments__": "folder-components-open", "verdaccio": "folder-verdaccio-open", ".verdaccio": "folder-verdaccio-open", "_verdaccio": "folder-verdaccio-open", + "-verdaccio": "folder-verdaccio-open", "__verdaccio__": "folder-verdaccio-open", "aurelia_project": "folder-aurelia-open", ".aurelia_project": "folder-aurelia-open", "_aurelia_project": "folder-aurelia-open", + "-aurelia_project": "folder-aurelia-open", "__aurelia_project__": "folder-aurelia-open", "resource": "folder-resource-open", ".resource": "folder-resource-open", "_resource": "folder-resource-open", + "-resource": "folder-resource-open", "__resource__": "folder-resource-open", "resources": "folder-resource-open", ".resources": "folder-resource-open", "_resources": "folder-resource-open", + "-resources": "folder-resource-open", "__resources__": "folder-resource-open", "res": "folder-resource-open", ".res": "folder-resource-open", "_res": "folder-resource-open", + "-res": "folder-resource-open", "__res__": "folder-resource-open", "asset": "folder-resource-open", ".asset": "folder-resource-open", "_asset": "folder-resource-open", + "-asset": "folder-resource-open", "__asset__": "folder-resource-open", "assets": "folder-resource-open", ".assets": "folder-resource-open", "_assets": "folder-resource-open", + "-assets": "folder-resource-open", "__assets__": "folder-resource-open", "static": "folder-resource-open", ".static": "folder-resource-open", "_static": "folder-resource-open", + "-static": "folder-resource-open", "__static__": "folder-resource-open", "report": "folder-resource-open", ".report": "folder-resource-open", "_report": "folder-resource-open", + "-report": "folder-resource-open", "__report__": "folder-resource-open", "reports": "folder-resource-open", ".reports": "folder-resource-open", "_reports": "folder-resource-open", + "-reports": "folder-resource-open", "__reports__": "folder-resource-open", "lib": "folder-lib-open", ".lib": "folder-lib-open", "_lib": "folder-lib-open", + "-lib": "folder-lib-open", "__lib__": "folder-lib-open", "libs": "folder-lib-open", ".libs": "folder-lib-open", "_libs": "folder-lib-open", + "-libs": "folder-lib-open", "__libs__": "folder-lib-open", "library": "folder-lib-open", ".library": "folder-lib-open", "_library": "folder-lib-open", + "-library": "folder-lib-open", "__library__": "folder-lib-open", "libraries": "folder-lib-open", ".libraries": "folder-lib-open", "_libraries": "folder-lib-open", + "-libraries": "folder-lib-open", "__libraries__": "folder-lib-open", "vendor": "folder-lib-open", ".vendor": "folder-lib-open", "_vendor": "folder-lib-open", + "-vendor": "folder-lib-open", "__vendor__": "folder-lib-open", "vendors": "folder-lib-open", ".vendors": "folder-lib-open", "_vendors": "folder-lib-open", + "-vendors": "folder-lib-open", "__vendors__": "folder-lib-open", "third-party": "folder-lib-open", ".third-party": "folder-lib-open", "_third-party": "folder-lib-open", + "-third-party": "folder-lib-open", "__third-party__": "folder-lib-open", "lib64": "folder-lib-open", ".lib64": "folder-lib-open", "_lib64": "folder-lib-open", + "-lib64": "folder-lib-open", "__lib64__": "folder-lib-open", "themes": "folder-theme-open", ".themes": "folder-theme-open", "_themes": "folder-theme-open", + "-themes": "folder-theme-open", "__themes__": "folder-theme-open", "theme": "folder-theme-open", ".theme": "folder-theme-open", "_theme": "folder-theme-open", + "-theme": "folder-theme-open", "__theme__": "folder-theme-open", "color": "folder-theme-open", ".color": "folder-theme-open", "_color": "folder-theme-open", + "-color": "folder-theme-open", "__color__": "folder-theme-open", "colors": "folder-theme-open", ".colors": "folder-theme-open", "_colors": "folder-theme-open", + "-colors": "folder-theme-open", "__colors__": "folder-theme-open", "colour": "folder-theme-open", ".colour": "folder-theme-open", "_colour": "folder-theme-open", + "-colour": "folder-theme-open", "__colour__": "folder-theme-open", "colours": "folder-theme-open", ".colours": "folder-theme-open", "_colours": "folder-theme-open", + "-colours": "folder-theme-open", "__colours__": "folder-theme-open", "design": "folder-theme-open", ".design": "folder-theme-open", "_design": "folder-theme-open", + "-design": "folder-theme-open", "__design__": "folder-theme-open", "designs": "folder-theme-open", ".designs": "folder-theme-open", "_designs": "folder-theme-open", + "-designs": "folder-theme-open", "__designs__": "folder-theme-open", "palette": "folder-theme-open", ".palette": "folder-theme-open", "_palette": "folder-theme-open", + "-palette": "folder-theme-open", "__palette__": "folder-theme-open", "palettes": "folder-theme-open", ".palettes": "folder-theme-open", "_palettes": "folder-theme-open", + "-palettes": "folder-theme-open", "__palettes__": "folder-theme-open", "webpack": "folder-webpack-open", ".webpack": "folder-webpack-open", "_webpack": "folder-webpack-open", + "-webpack": "folder-webpack-open", "__webpack__": "folder-webpack-open", "global": "folder-global-open", ".global": "folder-global-open", "_global": "folder-global-open", + "-global": "folder-global-open", "__global__": "folder-global-open", "public": "folder-public-open", ".public": "folder-public-open", "_public": "folder-public-open", + "-public": "folder-public-open", "__public__": "folder-public-open", "www": "folder-public-open", ".www": "folder-public-open", "_www": "folder-public-open", + "-www": "folder-public-open", "__www__": "folder-public-open", "wwwroot": "folder-public-open", ".wwwroot": "folder-public-open", "_wwwroot": "folder-public-open", + "-wwwroot": "folder-public-open", "__wwwroot__": "folder-public-open", "web": "folder-public-open", ".web": "folder-public-open", "_web": "folder-public-open", + "-web": "folder-public-open", "__web__": "folder-public-open", "website": "folder-public-open", ".website": "folder-public-open", "_website": "folder-public-open", + "-website": "folder-public-open", "__website__": "folder-public-open", "websites": "folder-public-open", ".websites": "folder-public-open", "_websites": "folder-public-open", + "-websites": "folder-public-open", "__websites__": "folder-public-open", "site": "folder-public-open", ".site": "folder-public-open", "_site": "folder-public-open", + "-site": "folder-public-open", "__site__": "folder-public-open", "browser": "folder-public-open", ".browser": "folder-public-open", "_browser": "folder-public-open", + "-browser": "folder-public-open", "__browser__": "folder-public-open", "browsers": "folder-public-open", ".browsers": "folder-public-open", "_browsers": "folder-public-open", + "-browsers": "folder-public-open", "__browsers__": "folder-public-open", + "proxy": "folder-public-open", + ".proxy": "folder-public-open", + "_proxy": "folder-public-open", + "-proxy": "folder-public-open", + "__proxy__": "folder-public-open", "inc": "folder-include-open", ".inc": "folder-include-open", "_inc": "folder-include-open", + "-inc": "folder-include-open", "__inc__": "folder-include-open", "include": "folder-include-open", ".include": "folder-include-open", "_include": "folder-include-open", + "-include": "folder-include-open", "__include__": "folder-include-open", "includes": "folder-include-open", ".includes": "folder-include-open", "_includes": "folder-include-open", + "-includes": "folder-include-open", "__includes__": "folder-include-open", "partial": "folder-include-open", ".partial": "folder-include-open", "_partial": "folder-include-open", + "-partial": "folder-include-open", "__partial__": "folder-include-open", "partials": "folder-include-open", ".partials": "folder-include-open", "_partials": "folder-include-open", + "-partials": "folder-include-open", "__partials__": "folder-include-open", "inc64": "folder-include-open", ".inc64": "folder-include-open", "_inc64": "folder-include-open", + "-inc64": "folder-include-open", "__inc64__": "folder-include-open", "docker": "folder-docker-open", ".docker": "folder-docker-open", "_docker": "folder-docker-open", + "-docker": "folder-docker-open", "__docker__": "folder-docker-open", "dockerfiles": "folder-docker-open", ".dockerfiles": "folder-docker-open", "_dockerfiles": "folder-docker-open", + "-dockerfiles": "folder-docker-open", "__dockerfiles__": "folder-docker-open", "dockerhub": "folder-docker-open", ".dockerhub": "folder-docker-open", "_dockerhub": "folder-docker-open", + "-dockerhub": "folder-docker-open", "__dockerhub__": "folder-docker-open", "astro": "folder-astro-open", ".astro": "folder-astro-open", "_astro": "folder-astro-open", + "-astro": "folder-astro-open", "__astro__": "folder-astro-open", "db": "folder-database-open", ".db": "folder-database-open", "_db": "folder-database-open", + "-db": "folder-database-open", "__db__": "folder-database-open", "data": "folder-database-open", ".data": "folder-database-open", "_data": "folder-database-open", + "-data": "folder-database-open", "__data__": "folder-database-open", "database": "folder-database-open", ".database": "folder-database-open", "_database": "folder-database-open", + "-database": "folder-database-open", "__database__": "folder-database-open", "databases": "folder-database-open", ".databases": "folder-database-open", "_databases": "folder-database-open", + "-databases": "folder-database-open", "__databases__": "folder-database-open", "sql": "folder-database-open", ".sql": "folder-database-open", "_sql": "folder-database-open", + "-sql": "folder-database-open", "__sql__": "folder-database-open", "migrations": "folder-migrations-open", ".migrations": "folder-migrations-open", "_migrations": "folder-migrations-open", + "-migrations": "folder-migrations-open", "__migrations__": "folder-migrations-open", "migration": "folder-migrations-open", ".migration": "folder-migrations-open", "_migration": "folder-migrations-open", + "-migration": "folder-migrations-open", "__migration__": "folder-migrations-open", "log": "folder-log-open", ".log": "folder-log-open", "_log": "folder-log-open", + "-log": "folder-log-open", "__log__": "folder-log-open", "logs": "folder-log-open", ".logs": "folder-log-open", "_logs": "folder-log-open", + "-logs": "folder-log-open", "__logs__": "folder-log-open", "logging": "folder-log-open", ".logging": "folder-log-open", "_logging": "folder-log-open", + "-logging": "folder-log-open", "__logging__": "folder-log-open", "target": "folder-target-open", ".target": "folder-target-open", "_target": "folder-target-open", + "-target": "folder-target-open", "__target__": "folder-target-open", "temp": "folder-temp-open", ".temp": "folder-temp-open", "_temp": "folder-temp-open", + "-temp": "folder-temp-open", "__temp__": "folder-temp-open", "tmp": "folder-temp-open", ".tmp": "folder-temp-open", "_tmp": "folder-temp-open", + "-tmp": "folder-temp-open", "__tmp__": "folder-temp-open", "cached": "folder-temp-open", ".cached": "folder-temp-open", "_cached": "folder-temp-open", + "-cached": "folder-temp-open", "__cached__": "folder-temp-open", "cache": "folder-temp-open", ".cache": "folder-temp-open", "_cache": "folder-temp-open", + "-cache": "folder-temp-open", "__cache__": "folder-temp-open", "aws": "folder-aws-open", ".aws": "folder-aws-open", "_aws": "folder-aws-open", + "-aws": "folder-aws-open", "__aws__": "folder-aws-open", "azure": "folder-aws-open", ".azure": "folder-aws-open", "_azure": "folder-aws-open", + "-azure": "folder-aws-open", "__azure__": "folder-aws-open", "gcp": "folder-aws-open", ".gcp": "folder-aws-open", "_gcp": "folder-aws-open", + "-gcp": "folder-aws-open", "__gcp__": "folder-aws-open", "aud": "folder-audio-open", ".aud": "folder-audio-open", "_aud": "folder-audio-open", + "-aud": "folder-audio-open", "__aud__": "folder-audio-open", "auds": "folder-audio-open", ".auds": "folder-audio-open", "_auds": "folder-audio-open", + "-auds": "folder-audio-open", "__auds__": "folder-audio-open", "audio": "folder-audio-open", ".audio": "folder-audio-open", "_audio": "folder-audio-open", + "-audio": "folder-audio-open", "__audio__": "folder-audio-open", "audios": "folder-audio-open", ".audios": "folder-audio-open", "_audios": "folder-audio-open", + "-audios": "folder-audio-open", "__audios__": "folder-audio-open", "music": "folder-audio-open", ".music": "folder-audio-open", "_music": "folder-audio-open", + "-music": "folder-audio-open", "__music__": "folder-audio-open", "song": "folder-audio-open", ".song": "folder-audio-open", "_song": "folder-audio-open", + "-song": "folder-audio-open", "__song__": "folder-audio-open", "songs": "folder-audio-open", ".songs": "folder-audio-open", "_songs": "folder-audio-open", + "-songs": "folder-audio-open", "__songs__": "folder-audio-open", "sound": "folder-audio-open", ".sound": "folder-audio-open", "_sound": "folder-audio-open", + "-sound": "folder-audio-open", "__sound__": "folder-audio-open", "sounds": "folder-audio-open", ".sounds": "folder-audio-open", "_sounds": "folder-audio-open", + "-sounds": "folder-audio-open", "__sounds__": "folder-audio-open", "voice": "folder-audio-open", ".voice": "folder-audio-open", "_voice": "folder-audio-open", + "-voice": "folder-audio-open", "__voice__": "folder-audio-open", "voices": "folder-audio-open", ".voices": "folder-audio-open", "_voices": "folder-audio-open", + "-voices": "folder-audio-open", "__voices__": "folder-audio-open", "recordings": "folder-audio-open", ".recordings": "folder-audio-open", "_recordings": "folder-audio-open", + "-recordings": "folder-audio-open", "__recordings__": "folder-audio-open", "playlist": "folder-audio-open", ".playlist": "folder-audio-open", "_playlist": "folder-audio-open", + "-playlist": "folder-audio-open", "__playlist__": "folder-audio-open", "playlists": "folder-audio-open", ".playlists": "folder-audio-open", "_playlists": "folder-audio-open", + "-playlists": "folder-audio-open", "__playlists__": "folder-audio-open", "vid": "folder-video-open", ".vid": "folder-video-open", "_vid": "folder-video-open", + "-vid": "folder-video-open", "__vid__": "folder-video-open", "vids": "folder-video-open", ".vids": "folder-video-open", "_vids": "folder-video-open", + "-vids": "folder-video-open", "__vids__": "folder-video-open", "video": "folder-video-open", ".video": "folder-video-open", "_video": "folder-video-open", + "-video": "folder-video-open", "__video__": "folder-video-open", "videos": "folder-video-open", ".videos": "folder-video-open", "_videos": "folder-video-open", + "-videos": "folder-video-open", "__videos__": "folder-video-open", "movie": "folder-video-open", ".movie": "folder-video-open", "_movie": "folder-video-open", + "-movie": "folder-video-open", "__movie__": "folder-video-open", "movies": "folder-video-open", ".movies": "folder-video-open", "_movies": "folder-video-open", + "-movies": "folder-video-open", "__movies__": "folder-video-open", "media": "folder-video-open", ".media": "folder-video-open", "_media": "folder-video-open", + "-media": "folder-video-open", "__media__": "folder-video-open", "kubernetes": "folder-kubernetes-open", ".kubernetes": "folder-kubernetes-open", "_kubernetes": "folder-kubernetes-open", + "-kubernetes": "folder-kubernetes-open", "__kubernetes__": "folder-kubernetes-open", "k8s": "folder-kubernetes-open", ".k8s": "folder-kubernetes-open", "_k8s": "folder-kubernetes-open", + "-k8s": "folder-kubernetes-open", "__k8s__": "folder-kubernetes-open", "import": "folder-import-open", ".import": "folder-import-open", "_import": "folder-import-open", + "-import": "folder-import-open", "__import__": "folder-import-open", "imports": "folder-import-open", ".imports": "folder-import-open", "_imports": "folder-import-open", + "-imports": "folder-import-open", "__imports__": "folder-import-open", "imported": "folder-import-open", ".imported": "folder-import-open", "_imported": "folder-import-open", + "-imported": "folder-import-open", "__imported__": "folder-import-open", "export": "folder-export-open", ".export": "folder-export-open", "_export": "folder-export-open", + "-export": "folder-export-open", "__export__": "folder-export-open", "exports": "folder-export-open", ".exports": "folder-export-open", "_exports": "folder-export-open", + "-exports": "folder-export-open", "__exports__": "folder-export-open", "exported": "folder-export-open", ".exported": "folder-export-open", "_exported": "folder-export-open", + "-exported": "folder-export-open", "__exported__": "folder-export-open", "wakatime": "folder-wakatime-open", ".wakatime": "folder-wakatime-open", "_wakatime": "folder-wakatime-open", + "-wakatime": "folder-wakatime-open", "__wakatime__": "folder-wakatime-open", "circleci": "folder-circleci-open", ".circleci": "folder-circleci-open", "_circleci": "folder-circleci-open", + "-circleci": "folder-circleci-open", "__circleci__": "folder-circleci-open", "wordpress-org": "folder-wordpress-open", ".wordpress-org": "folder-wordpress-open", "_wordpress-org": "folder-wordpress-open", + "-wordpress-org": "folder-wordpress-open", "__wordpress-org__": "folder-wordpress-open", "wp-content": "folder-wordpress-open", ".wp-content": "folder-wordpress-open", "_wp-content": "folder-wordpress-open", + "-wp-content": "folder-wordpress-open", "__wp-content__": "folder-wordpress-open", "gradle": "folder-gradle-open", ".gradle": "folder-gradle-open", "_gradle": "folder-gradle-open", + "-gradle": "folder-gradle-open", "__gradle__": "folder-gradle-open", "coverage": "folder-coverage-open", ".coverage": "folder-coverage-open", "_coverage": "folder-coverage-open", + "-coverage": "folder-coverage-open", "__coverage__": "folder-coverage-open", "nyc-output": "folder-coverage-open", ".nyc-output": "folder-coverage-open", "_nyc-output": "folder-coverage-open", + "-nyc-output": "folder-coverage-open", "__nyc-output__": "folder-coverage-open", "nyc_output": "folder-coverage-open", ".nyc_output": "folder-coverage-open", "_nyc_output": "folder-coverage-open", + "-nyc_output": "folder-coverage-open", "__nyc_output__": "folder-coverage-open", "e2e": "folder-coverage-open", ".e2e": "folder-coverage-open", "_e2e": "folder-coverage-open", + "-e2e": "folder-coverage-open", "__e2e__": "folder-coverage-open", "it": "folder-coverage-open", ".it": "folder-coverage-open", "_it": "folder-coverage-open", + "-it": "folder-coverage-open", "__it__": "folder-coverage-open", "integration-test": "folder-coverage-open", ".integration-test": "folder-coverage-open", "_integration-test": "folder-coverage-open", + "-integration-test": "folder-coverage-open", "__integration-test__": "folder-coverage-open", "integration-tests": "folder-coverage-open", ".integration-tests": "folder-coverage-open", "_integration-tests": "folder-coverage-open", + "-integration-tests": "folder-coverage-open", "__integration-tests__": "folder-coverage-open", "class": "folder-class-open", ".class": "folder-class-open", "_class": "folder-class-open", + "-class": "folder-class-open", "__class__": "folder-class-open", "classes": "folder-class-open", ".classes": "folder-class-open", "_classes": "folder-class-open", + "-classes": "folder-class-open", "__classes__": "folder-class-open", "model": "folder-class-open", ".model": "folder-class-open", "_model": "folder-class-open", + "-model": "folder-class-open", "__model__": "folder-class-open", "models": "folder-class-open", ".models": "folder-class-open", "_models": "folder-class-open", + "-models": "folder-class-open", "__models__": "folder-class-open", "schemas": "folder-class-open", ".schemas": "folder-class-open", "_schemas": "folder-class-open", + "-schemas": "folder-class-open", "__schemas__": "folder-class-open", "schema": "folder-class-open", ".schema": "folder-class-open", "_schema": "folder-class-open", + "-schema": "folder-class-open", "__schema__": "folder-class-open", "other": "folder-other-open", ".other": "folder-other-open", "_other": "folder-other-open", + "-other": "folder-other-open", "__other__": "folder-other-open", "others": "folder-other-open", ".others": "folder-other-open", "_others": "folder-other-open", + "-others": "folder-other-open", "__others__": "folder-other-open", "misc": "folder-other-open", ".misc": "folder-other-open", "_misc": "folder-other-open", + "-misc": "folder-other-open", "__misc__": "folder-other-open", "miscellaneous": "folder-other-open", ".miscellaneous": "folder-other-open", "_miscellaneous": "folder-other-open", + "-miscellaneous": "folder-other-open", "__miscellaneous__": "folder-other-open", "extra": "folder-other-open", ".extra": "folder-other-open", "_extra": "folder-other-open", + "-extra": "folder-other-open", "__extra__": "folder-other-open", "extras": "folder-other-open", ".extras": "folder-other-open", "_extras": "folder-other-open", + "-extras": "folder-other-open", "__extras__": "folder-other-open", "etc": "folder-other-open", ".etc": "folder-other-open", "_etc": "folder-other-open", + "-etc": "folder-other-open", "__etc__": "folder-other-open", "lua": "folder-lua-open", ".lua": "folder-lua-open", "_lua": "folder-lua-open", + "-lua": "folder-lua-open", "__lua__": "folder-lua-open", "turbo": "folder-turborepo-open", ".turbo": "folder-turborepo-open", "_turbo": "folder-turborepo-open", + "-turbo": "folder-turborepo-open", "__turbo__": "folder-turborepo-open", "typescript": "folder-typescript-open", ".typescript": "folder-typescript-open", "_typescript": "folder-typescript-open", + "-typescript": "folder-typescript-open", "__typescript__": "folder-typescript-open", "ts": "folder-typescript-open", ".ts": "folder-typescript-open", "_ts": "folder-typescript-open", + "-ts": "folder-typescript-open", "__ts__": "folder-typescript-open", "typings": "folder-typescript-open", ".typings": "folder-typescript-open", "_typings": "folder-typescript-open", + "-typings": "folder-typescript-open", "__typings__": "folder-typescript-open", "@types": "folder-typescript-open", ".@types": "folder-typescript-open", "_@types": "folder-typescript-open", + "-@types": "folder-typescript-open", "__@types__": "folder-typescript-open", "types": "folder-typescript-open", ".types": "folder-typescript-open", "_types": "folder-typescript-open", + "-types": "folder-typescript-open", "__types__": "folder-typescript-open", "cts": "folder-typescript-open", ".cts": "folder-typescript-open", "_cts": "folder-typescript-open", + "-cts": "folder-typescript-open", "__cts__": "folder-typescript-open", "mts": "folder-typescript-open", ".mts": "folder-typescript-open", "_mts": "folder-typescript-open", + "-mts": "folder-typescript-open", "__mts__": "folder-typescript-open", "graphql": "folder-graphql-open", ".graphql": "folder-graphql-open", "_graphql": "folder-graphql-open", + "-graphql": "folder-graphql-open", "__graphql__": "folder-graphql-open", "gql": "folder-graphql-open", ".gql": "folder-graphql-open", "_gql": "folder-graphql-open", + "-gql": "folder-graphql-open", "__gql__": "folder-graphql-open", "routes": "folder-routes-open", ".routes": "folder-routes-open", "_routes": "folder-routes-open", + "-routes": "folder-routes-open", "__routes__": "folder-routes-open", "router": "folder-routes-open", ".router": "folder-routes-open", "_router": "folder-routes-open", + "-router": "folder-routes-open", "__router__": "folder-routes-open", "routers": "folder-routes-open", ".routers": "folder-routes-open", "_routers": "folder-routes-open", + "-routers": "folder-routes-open", "__routers__": "folder-routes-open", "navigation": "folder-routes-open", ".navigation": "folder-routes-open", "_navigation": "folder-routes-open", + "-navigation": "folder-routes-open", "__navigation__": "folder-routes-open", "navigations": "folder-routes-open", ".navigations": "folder-routes-open", "_navigations": "folder-routes-open", + "-navigations": "folder-routes-open", "__navigations__": "folder-routes-open", "routing": "folder-routes-open", ".routing": "folder-routes-open", "_routing": "folder-routes-open", + "-routing": "folder-routes-open", "__routing__": "folder-routes-open", "ci": "folder-ci-open", ".ci": "folder-ci-open", "_ci": "folder-ci-open", + "-ci": "folder-ci-open", "__ci__": "folder-ci-open", "eslint": "folder-eslint-open", ".eslint": "folder-eslint-open", "_eslint": "folder-eslint-open", + "-eslint": "folder-eslint-open", "__eslint__": "folder-eslint-open", "eslint-plugin": "folder-eslint-open", ".eslint-plugin": "folder-eslint-open", "_eslint-plugin": "folder-eslint-open", + "-eslint-plugin": "folder-eslint-open", "__eslint-plugin__": "folder-eslint-open", "eslint-plugins": "folder-eslint-open", ".eslint-plugins": "folder-eslint-open", "_eslint-plugins": "folder-eslint-open", + "-eslint-plugins": "folder-eslint-open", "__eslint-plugins__": "folder-eslint-open", "eslint-config": "folder-eslint-open", ".eslint-config": "folder-eslint-open", "_eslint-config": "folder-eslint-open", + "-eslint-config": "folder-eslint-open", "__eslint-config__": "folder-eslint-open", "eslint-configs": "folder-eslint-open", ".eslint-configs": "folder-eslint-open", "_eslint-configs": "folder-eslint-open", + "-eslint-configs": "folder-eslint-open", "__eslint-configs__": "folder-eslint-open", "benchmark": "folder-benchmark-open", ".benchmark": "folder-benchmark-open", "_benchmark": "folder-benchmark-open", + "-benchmark": "folder-benchmark-open", "__benchmark__": "folder-benchmark-open", "benchmarks": "folder-benchmark-open", ".benchmarks": "folder-benchmark-open", "_benchmarks": "folder-benchmark-open", + "-benchmarks": "folder-benchmark-open", "__benchmarks__": "folder-benchmark-open", "bench": "folder-benchmark-open", ".bench": "folder-benchmark-open", "_bench": "folder-benchmark-open", + "-bench": "folder-benchmark-open", "__bench__": "folder-benchmark-open", + "benches": "folder-benchmark-open", + ".benches": "folder-benchmark-open", + "_benches": "folder-benchmark-open", + "-benches": "folder-benchmark-open", + "__benches__": "folder-benchmark-open", "performance": "folder-benchmark-open", ".performance": "folder-benchmark-open", "_performance": "folder-benchmark-open", + "-performance": "folder-benchmark-open", "__performance__": "folder-benchmark-open", "perf": "folder-benchmark-open", ".perf": "folder-benchmark-open", "_perf": "folder-benchmark-open", + "-perf": "folder-benchmark-open", "__perf__": "folder-benchmark-open", "profiling": "folder-benchmark-open", ".profiling": "folder-benchmark-open", "_profiling": "folder-benchmark-open", + "-profiling": "folder-benchmark-open", "__profiling__": "folder-benchmark-open", "measure": "folder-benchmark-open", ".measure": "folder-benchmark-open", "_measure": "folder-benchmark-open", + "-measure": "folder-benchmark-open", "__measure__": "folder-benchmark-open", "measures": "folder-benchmark-open", ".measures": "folder-benchmark-open", "_measures": "folder-benchmark-open", + "-measures": "folder-benchmark-open", "__measures__": "folder-benchmark-open", "measurement": "folder-benchmark-open", ".measurement": "folder-benchmark-open", "_measurement": "folder-benchmark-open", + "-measurement": "folder-benchmark-open", "__measurement__": "folder-benchmark-open", "messages": "folder-messages-open", ".messages": "folder-messages-open", "_messages": "folder-messages-open", + "-messages": "folder-messages-open", "__messages__": "folder-messages-open", "messaging": "folder-messages-open", ".messaging": "folder-messages-open", "_messaging": "folder-messages-open", + "-messaging": "folder-messages-open", "__messaging__": "folder-messages-open", "forum": "folder-messages-open", ".forum": "folder-messages-open", "_forum": "folder-messages-open", + "-forum": "folder-messages-open", "__forum__": "folder-messages-open", "chat": "folder-messages-open", ".chat": "folder-messages-open", "_chat": "folder-messages-open", + "-chat": "folder-messages-open", "__chat__": "folder-messages-open", "chats": "folder-messages-open", ".chats": "folder-messages-open", "_chats": "folder-messages-open", + "-chats": "folder-messages-open", "__chats__": "folder-messages-open", "conversation": "folder-messages-open", ".conversation": "folder-messages-open", "_conversation": "folder-messages-open", + "-conversation": "folder-messages-open", "__conversation__": "folder-messages-open", "conversations": "folder-messages-open", ".conversations": "folder-messages-open", "_conversations": "folder-messages-open", + "-conversations": "folder-messages-open", "__conversations__": "folder-messages-open", "dialog": "folder-messages-open", ".dialog": "folder-messages-open", "_dialog": "folder-messages-open", + "-dialog": "folder-messages-open", "__dialog__": "folder-messages-open", "dialogs": "folder-messages-open", ".dialogs": "folder-messages-open", "_dialogs": "folder-messages-open", + "-dialogs": "folder-messages-open", "__dialogs__": "folder-messages-open", "less": "folder-less-open", ".less": "folder-less-open", "_less": "folder-less-open", + "-less": "folder-less-open", "__less__": "folder-less-open", "gulp": "folder-gulp-open", ".gulp": "folder-gulp-open", "_gulp": "folder-gulp-open", + "-gulp": "folder-gulp-open", "__gulp__": "folder-gulp-open", "gulp-tasks": "folder-gulp-open", ".gulp-tasks": "folder-gulp-open", "_gulp-tasks": "folder-gulp-open", + "-gulp-tasks": "folder-gulp-open", "__gulp-tasks__": "folder-gulp-open", "gulpfile.js": "folder-gulp-open", ".gulpfile.js": "folder-gulp-open", "_gulpfile.js": "folder-gulp-open", + "-gulpfile.js": "folder-gulp-open", "__gulpfile.js__": "folder-gulp-open", "gulpfile.mjs": "folder-gulp-open", ".gulpfile.mjs": "folder-gulp-open", "_gulpfile.mjs": "folder-gulp-open", + "-gulpfile.mjs": "folder-gulp-open", "__gulpfile.mjs__": "folder-gulp-open", "gulpfile.ts": "folder-gulp-open", ".gulpfile.ts": "folder-gulp-open", "_gulpfile.ts": "folder-gulp-open", + "-gulpfile.ts": "folder-gulp-open", "__gulpfile.ts__": "folder-gulp-open", "gulpfile.babel.js": "folder-gulp-open", ".gulpfile.babel.js": "folder-gulp-open", "_gulpfile.babel.js": "folder-gulp-open", + "-gulpfile.babel.js": "folder-gulp-open", "__gulpfile.babel.js__": "folder-gulp-open", "gulpfiles": "folder-gulp-open", ".gulpfiles": "folder-gulp-open", "_gulpfiles": "folder-gulp-open", + "-gulpfiles": "folder-gulp-open", "__gulpfiles__": "folder-gulp-open", "python": "folder-python-open", ".python": "folder-python-open", "_python": "folder-python-open", + "-python": "folder-python-open", "__python__": "folder-python-open", "pycache": "folder-python-open", ".pycache": "folder-python-open", "_pycache": "folder-python-open", + "-pycache": "folder-python-open", "__pycache__": "folder-python-open", "pytest_cache": "folder-python-open", ".pytest_cache": "folder-python-open", "_pytest_cache": "folder-python-open", + "-pytest_cache": "folder-python-open", "__pytest_cache__": "folder-python-open", "sandbox": "folder-sandbox-open", ".sandbox": "folder-sandbox-open", "_sandbox": "folder-sandbox-open", + "-sandbox": "folder-sandbox-open", "__sandbox__": "folder-sandbox-open", "sandboxes": "folder-sandbox-open", ".sandboxes": "folder-sandbox-open", "_sandboxes": "folder-sandbox-open", + "-sandboxes": "folder-sandbox-open", "__sandboxes__": "folder-sandbox-open", "playground": "folder-sandbox-open", ".playground": "folder-sandbox-open", "_playground": "folder-sandbox-open", + "-playground": "folder-sandbox-open", "__playground__": "folder-sandbox-open", "playgrounds": "folder-sandbox-open", ".playgrounds": "folder-sandbox-open", "_playgrounds": "folder-sandbox-open", + "-playgrounds": "folder-sandbox-open", "__playgrounds__": "folder-sandbox-open", "scons": "folder-scons-open", ".scons": "folder-scons-open", "_scons": "folder-scons-open", + "-scons": "folder-scons-open", "__scons__": "folder-scons-open", "sconf_temp": "folder-scons-open", ".sconf_temp": "folder-scons-open", "_sconf_temp": "folder-scons-open", + "-sconf_temp": "folder-scons-open", "__sconf_temp__": "folder-scons-open", "scons_cache": "folder-scons-open", ".scons_cache": "folder-scons-open", "_scons_cache": "folder-scons-open", + "-scons_cache": "folder-scons-open", "__scons_cache__": "folder-scons-open", "mojo": "folder-mojo-open", ".mojo": "folder-mojo-open", "_mojo": "folder-mojo-open", + "-mojo": "folder-mojo-open", "__mojo__": "folder-mojo-open", "moon": "folder-moon-open", ".moon": "folder-moon-open", "_moon": "folder-moon-open", + "-moon": "folder-moon-open", "__moon__": "folder-moon-open", "debug": "folder-debug-open", ".debug": "folder-debug-open", "_debug": "folder-debug-open", + "-debug": "folder-debug-open", "__debug__": "folder-debug-open", "debugger": "folder-debug-open", ".debugger": "folder-debug-open", "_debugger": "folder-debug-open", + "-debugger": "folder-debug-open", "__debugger__": "folder-debug-open", "debugging": "folder-debug-open", ".debugging": "folder-debug-open", "_debugging": "folder-debug-open", + "-debugging": "folder-debug-open", "__debugging__": "folder-debug-open", "fastlane": "folder-fastlane-open", ".fastlane": "folder-fastlane-open", "_fastlane": "folder-fastlane-open", + "-fastlane": "folder-fastlane-open", "__fastlane__": "folder-fastlane-open", "plugin": "folder-plugin-open", ".plugin": "folder-plugin-open", "_plugin": "folder-plugin-open", + "-plugin": "folder-plugin-open", "__plugin__": "folder-plugin-open", "plugins": "folder-plugin-open", ".plugins": "folder-plugin-open", "_plugins": "folder-plugin-open", + "-plugins": "folder-plugin-open", "__plugins__": "folder-plugin-open", "mod": "folder-plugin-open", ".mod": "folder-plugin-open", "_mod": "folder-plugin-open", + "-mod": "folder-plugin-open", "__mod__": "folder-plugin-open", "mods": "folder-plugin-open", ".mods": "folder-plugin-open", "_mods": "folder-plugin-open", + "-mods": "folder-plugin-open", "__mods__": "folder-plugin-open", "modding": "folder-plugin-open", ".modding": "folder-plugin-open", "_modding": "folder-plugin-open", + "-modding": "folder-plugin-open", "__modding__": "folder-plugin-open", "extension": "folder-plugin-open", ".extension": "folder-plugin-open", "_extension": "folder-plugin-open", + "-extension": "folder-plugin-open", "__extension__": "folder-plugin-open", "extensions": "folder-plugin-open", ".extensions": "folder-plugin-open", "_extensions": "folder-plugin-open", + "-extensions": "folder-plugin-open", "__extensions__": "folder-plugin-open", "addon": "folder-plugin-open", ".addon": "folder-plugin-open", "_addon": "folder-plugin-open", + "-addon": "folder-plugin-open", "__addon__": "folder-plugin-open", "addons": "folder-plugin-open", ".addons": "folder-plugin-open", "_addons": "folder-plugin-open", + "-addons": "folder-plugin-open", "__addons__": "folder-plugin-open", "addin": "folder-plugin-open", ".addin": "folder-plugin-open", "_addin": "folder-plugin-open", + "-addin": "folder-plugin-open", "__addin__": "folder-plugin-open", "addins": "folder-plugin-open", ".addins": "folder-plugin-open", "_addins": "folder-plugin-open", + "-addins": "folder-plugin-open", "__addins__": "folder-plugin-open", "module": "folder-plugin-open", ".module": "folder-plugin-open", "_module": "folder-plugin-open", + "-module": "folder-plugin-open", "__module__": "folder-plugin-open", "modules": "folder-plugin-open", ".modules": "folder-plugin-open", "_modules": "folder-plugin-open", + "-modules": "folder-plugin-open", "__modules__": "folder-plugin-open", "middleware": "folder-middleware-open", ".middleware": "folder-middleware-open", "_middleware": "folder-middleware-open", + "-middleware": "folder-middleware-open", "__middleware__": "folder-middleware-open", "middlewares": "folder-middleware-open", ".middlewares": "folder-middleware-open", "_middlewares": "folder-middleware-open", + "-middlewares": "folder-middleware-open", "__middlewares__": "folder-middleware-open", "controller": "folder-controller-open", ".controller": "folder-controller-open", "_controller": "folder-controller-open", + "-controller": "folder-controller-open", "__controller__": "folder-controller-open", "controllers": "folder-controller-open", ".controllers": "folder-controller-open", "_controllers": "folder-controller-open", + "-controllers": "folder-controller-open", "__controllers__": "folder-controller-open", "controls": "folder-controller-open", ".controls": "folder-controller-open", "_controls": "folder-controller-open", + "-controls": "folder-controller-open", "__controls__": "folder-controller-open", "service": "folder-controller-open", ".service": "folder-controller-open", "_service": "folder-controller-open", + "-service": "folder-controller-open", "__service__": "folder-controller-open", "services": "folder-controller-open", ".services": "folder-controller-open", "_services": "folder-controller-open", + "-services": "folder-controller-open", "__services__": "folder-controller-open", "provider": "folder-controller-open", ".provider": "folder-controller-open", "_provider": "folder-controller-open", + "-provider": "folder-controller-open", "__provider__": "folder-controller-open", "providers": "folder-controller-open", ".providers": "folder-controller-open", "_providers": "folder-controller-open", + "-providers": "folder-controller-open", "__providers__": "folder-controller-open", "handler": "folder-controller-open", ".handler": "folder-controller-open", "_handler": "folder-controller-open", + "-handler": "folder-controller-open", "__handler__": "folder-controller-open", "handlers": "folder-controller-open", ".handlers": "folder-controller-open", "_handlers": "folder-controller-open", + "-handlers": "folder-controller-open", "__handlers__": "folder-controller-open", "ansible": "folder-ansible-open", ".ansible": "folder-ansible-open", "_ansible": "folder-ansible-open", + "-ansible": "folder-ansible-open", "__ansible__": "folder-ansible-open", "server": "folder-server-open", ".server": "folder-server-open", "_server": "folder-server-open", + "-server": "folder-server-open", "__server__": "folder-server-open", "servers": "folder-server-open", ".servers": "folder-server-open", "_servers": "folder-server-open", + "-servers": "folder-server-open", "__servers__": "folder-server-open", "backend": "folder-server-open", ".backend": "folder-server-open", "_backend": "folder-server-open", + "-backend": "folder-server-open", "__backend__": "folder-server-open", "backends": "folder-server-open", ".backends": "folder-server-open", "_backends": "folder-server-open", + "-backends": "folder-server-open", "__backends__": "folder-server-open", "inventory": "folder-server-open", ".inventory": "folder-server-open", "_inventory": "folder-server-open", + "-inventory": "folder-server-open", "__inventory__": "folder-server-open", "inventories": "folder-server-open", ".inventories": "folder-server-open", "_inventories": "folder-server-open", + "-inventories": "folder-server-open", "__inventories__": "folder-server-open", "infrastructure": "folder-server-open", ".infrastructure": "folder-server-open", "_infrastructure": "folder-server-open", + "-infrastructure": "folder-server-open", "__infrastructure__": "folder-server-open", "infra": "folder-server-open", ".infra": "folder-server-open", "_infra": "folder-server-open", + "-infra": "folder-server-open", "__infra__": "folder-server-open", "client": "folder-client-open", ".client": "folder-client-open", "_client": "folder-client-open", + "-client": "folder-client-open", "__client__": "folder-client-open", "clients": "folder-client-open", ".clients": "folder-client-open", "_clients": "folder-client-open", + "-clients": "folder-client-open", "__clients__": "folder-client-open", "frontend": "folder-client-open", ".frontend": "folder-client-open", "_frontend": "folder-client-open", + "-frontend": "folder-client-open", "__frontend__": "folder-client-open", "frontends": "folder-client-open", ".frontends": "folder-client-open", "_frontends": "folder-client-open", + "-frontends": "folder-client-open", "__frontends__": "folder-client-open", "pwa": "folder-client-open", ".pwa": "folder-client-open", "_pwa": "folder-client-open", + "-pwa": "folder-client-open", "__pwa__": "folder-client-open", "spa": "folder-client-open", ".spa": "folder-client-open", "_spa": "folder-client-open", + "-spa": "folder-client-open", "__spa__": "folder-client-open", "tasks": "folder-tasks-open", ".tasks": "folder-tasks-open", "_tasks": "folder-tasks-open", + "-tasks": "folder-tasks-open", "__tasks__": "folder-tasks-open", "tickets": "folder-tasks-open", ".tickets": "folder-tasks-open", "_tickets": "folder-tasks-open", + "-tickets": "folder-tasks-open", "__tickets__": "folder-tasks-open", "android": "folder-android-open", ".android": "folder-android-open", "_android": "folder-android-open", + "-android": "folder-android-open", "__android__": "folder-android-open", "ios": "folder-ios-open", ".ios": "folder-ios-open", "_ios": "folder-ios-open", + "-ios": "folder-ios-open", "__ios__": "folder-ios-open", "presentation": "folder-ui-open", ".presentation": "folder-ui-open", "_presentation": "folder-ui-open", + "-presentation": "folder-ui-open", "__presentation__": "folder-ui-open", "gui": "folder-ui-open", ".gui": "folder-ui-open", "_gui": "folder-ui-open", + "-gui": "folder-ui-open", "__gui__": "folder-ui-open", "ui": "folder-ui-open", ".ui": "folder-ui-open", "_ui": "folder-ui-open", + "-ui": "folder-ui-open", "__ui__": "folder-ui-open", "ux": "folder-ui-open", ".ux": "folder-ui-open", "_ux": "folder-ui-open", + "-ux": "folder-ui-open", "__ux__": "folder-ui-open", "uploads": "folder-upload-open", ".uploads": "folder-upload-open", "_uploads": "folder-upload-open", + "-uploads": "folder-upload-open", "__uploads__": "folder-upload-open", "upload": "folder-upload-open", ".upload": "folder-upload-open", "_upload": "folder-upload-open", + "-upload": "folder-upload-open", "__upload__": "folder-upload-open", "downloads": "folder-download-open", ".downloads": "folder-download-open", "_downloads": "folder-download-open", + "-downloads": "folder-download-open", "__downloads__": "folder-download-open", "download": "folder-download-open", ".download": "folder-download-open", "_download": "folder-download-open", + "-download": "folder-download-open", "__download__": "folder-download-open", "downloader": "folder-download-open", ".downloader": "folder-download-open", "_downloader": "folder-download-open", + "-downloader": "folder-download-open", "__downloader__": "folder-download-open", "downloaders": "folder-download-open", ".downloaders": "folder-download-open", "_downloaders": "folder-download-open", + "-downloaders": "folder-download-open", "__downloaders__": "folder-download-open", "tools": "folder-tools-open", ".tools": "folder-tools-open", "_tools": "folder-tools-open", + "-tools": "folder-tools-open", "__tools__": "folder-tools-open", "toolkit": "folder-tools-open", ".toolkit": "folder-tools-open", "_toolkit": "folder-tools-open", + "-toolkit": "folder-tools-open", "__toolkit__": "folder-tools-open", "toolkits": "folder-tools-open", ".toolkits": "folder-tools-open", "_toolkits": "folder-tools-open", + "-toolkits": "folder-tools-open", "__toolkits__": "folder-tools-open", "toolbox": "folder-tools-open", ".toolbox": "folder-tools-open", "_toolbox": "folder-tools-open", + "-toolbox": "folder-tools-open", "__toolbox__": "folder-tools-open", "toolboxes": "folder-tools-open", ".toolboxes": "folder-tools-open", "_toolboxes": "folder-tools-open", + "-toolboxes": "folder-tools-open", "__toolboxes__": "folder-tools-open", "tooling": "folder-tools-open", ".tooling": "folder-tools-open", "_tooling": "folder-tools-open", + "-tooling": "folder-tools-open", "__tooling__": "folder-tools-open", "devtools": "folder-tools-open", ".devtools": "folder-tools-open", "_devtools": "folder-tools-open", + "-devtools": "folder-tools-open", "__devtools__": "folder-tools-open", "kit": "folder-tools-open", ".kit": "folder-tools-open", "_kit": "folder-tools-open", + "-kit": "folder-tools-open", "__kit__": "folder-tools-open", "kits": "folder-tools-open", ".kits": "folder-tools-open", "_kits": "folder-tools-open", + "-kits": "folder-tools-open", "__kits__": "folder-tools-open", "helpers": "folder-helper-open", ".helpers": "folder-helper-open", "_helpers": "folder-helper-open", + "-helpers": "folder-helper-open", "__helpers__": "folder-helper-open", "helper": "folder-helper-open", ".helper": "folder-helper-open", "_helper": "folder-helper-open", + "-helper": "folder-helper-open", "__helper__": "folder-helper-open", "serverless": "folder-serverless-open", ".serverless": "folder-serverless-open", "_serverless": "folder-serverless-open", + "-serverless": "folder-serverless-open", "__serverless__": "folder-serverless-open", "api": "folder-api-open", ".api": "folder-api-open", "_api": "folder-api-open", + "-api": "folder-api-open", "__api__": "folder-api-open", "apis": "folder-api-open", ".apis": "folder-api-open", "_apis": "folder-api-open", + "-apis": "folder-api-open", "__apis__": "folder-api-open", "restapi": "folder-api-open", ".restapi": "folder-api-open", "_restapi": "folder-api-open", + "-restapi": "folder-api-open", "__restapi__": "folder-api-open", "app": "folder-app-open", ".app": "folder-app-open", "_app": "folder-app-open", + "-app": "folder-app-open", "__app__": "folder-app-open", "apps": "folder-app-open", ".apps": "folder-app-open", "_apps": "folder-app-open", + "-apps": "folder-app-open", "__apps__": "folder-app-open", "application": "folder-app-open", ".application": "folder-app-open", "_application": "folder-app-open", + "-application": "folder-app-open", "__application__": "folder-app-open", "applications": "folder-app-open", ".applications": "folder-app-open", "_applications": "folder-app-open", + "-applications": "folder-app-open", "__applications__": "folder-app-open", "apollo": "folder-apollo-open", ".apollo": "folder-apollo-open", "_apollo": "folder-apollo-open", + "-apollo": "folder-apollo-open", "__apollo__": "folder-apollo-open", "apollo-client": "folder-apollo-open", ".apollo-client": "folder-apollo-open", "_apollo-client": "folder-apollo-open", + "-apollo-client": "folder-apollo-open", "__apollo-client__": "folder-apollo-open", "apollo-cache": "folder-apollo-open", ".apollo-cache": "folder-apollo-open", "_apollo-cache": "folder-apollo-open", + "-apollo-cache": "folder-apollo-open", "__apollo-cache__": "folder-apollo-open", "apollo-config": "folder-apollo-open", ".apollo-config": "folder-apollo-open", "_apollo-config": "folder-apollo-open", + "-apollo-config": "folder-apollo-open", "__apollo-config__": "folder-apollo-open", "arc": "folder-archive-open", ".arc": "folder-archive-open", "_arc": "folder-archive-open", + "-arc": "folder-archive-open", "__arc__": "folder-archive-open", "arcs": "folder-archive-open", ".arcs": "folder-archive-open", "_arcs": "folder-archive-open", + "-arcs": "folder-archive-open", "__arcs__": "folder-archive-open", "archive": "folder-archive-open", ".archive": "folder-archive-open", "_archive": "folder-archive-open", + "-archive": "folder-archive-open", "__archive__": "folder-archive-open", "archives": "folder-archive-open", ".archives": "folder-archive-open", "_archives": "folder-archive-open", + "-archives": "folder-archive-open", "__archives__": "folder-archive-open", "archival": "folder-archive-open", ".archival": "folder-archive-open", "_archival": "folder-archive-open", + "-archival": "folder-archive-open", "__archival__": "folder-archive-open", "bkp": "folder-backup-open", ".bkp": "folder-backup-open", "_bkp": "folder-backup-open", + "-bkp": "folder-backup-open", "__bkp__": "folder-backup-open", "bkps": "folder-backup-open", ".bkps": "folder-backup-open", "_bkps": "folder-backup-open", + "-bkps": "folder-backup-open", "__bkps__": "folder-backup-open", "bak": "folder-backup-open", ".bak": "folder-backup-open", "_bak": "folder-backup-open", + "-bak": "folder-backup-open", "__bak__": "folder-backup-open", "baks": "folder-backup-open", ".baks": "folder-backup-open", "_baks": "folder-backup-open", + "-baks": "folder-backup-open", "__baks__": "folder-backup-open", "backup": "folder-backup-open", ".backup": "folder-backup-open", "_backup": "folder-backup-open", + "-backup": "folder-backup-open", "__backup__": "folder-backup-open", "backups": "folder-backup-open", ".backups": "folder-backup-open", "_backups": "folder-backup-open", + "-backups": "folder-backup-open", "__backups__": "folder-backup-open", "back-up": "folder-backup-open", ".back-up": "folder-backup-open", "_back-up": "folder-backup-open", + "-back-up": "folder-backup-open", "__back-up__": "folder-backup-open", "back-ups": "folder-backup-open", ".back-ups": "folder-backup-open", "_back-ups": "folder-backup-open", + "-back-ups": "folder-backup-open", "__back-ups__": "folder-backup-open", "history": "folder-backup-open", ".history": "folder-backup-open", "_history": "folder-backup-open", + "-history": "folder-backup-open", "__history__": "folder-backup-open", "histories": "folder-backup-open", ".histories": "folder-backup-open", "_histories": "folder-backup-open", + "-histories": "folder-backup-open", "__histories__": "folder-backup-open", "batch": "folder-batch-open", ".batch": "folder-batch-open", "_batch": "folder-batch-open", + "-batch": "folder-batch-open", "__batch__": "folder-batch-open", "batchs": "folder-batch-open", ".batchs": "folder-batch-open", "_batchs": "folder-batch-open", + "-batchs": "folder-batch-open", "__batchs__": "folder-batch-open", "batches": "folder-batch-open", ".batches": "folder-batch-open", "_batches": "folder-batch-open", + "-batches": "folder-batch-open", "__batches__": "folder-batch-open", "buildkite": "folder-buildkite-open", ".buildkite": "folder-buildkite-open", "_buildkite": "folder-buildkite-open", + "-buildkite": "folder-buildkite-open", "__buildkite__": "folder-buildkite-open", "cluster": "folder-cluster-open", ".cluster": "folder-cluster-open", "_cluster": "folder-cluster-open", + "-cluster": "folder-cluster-open", "__cluster__": "folder-cluster-open", "clusters": "folder-cluster-open", ".clusters": "folder-cluster-open", "_clusters": "folder-cluster-open", + "-clusters": "folder-cluster-open", "__clusters__": "folder-cluster-open", "command": "folder-command-open", ".command": "folder-command-open", "_command": "folder-command-open", + "-command": "folder-command-open", "__command__": "folder-command-open", "commands": "folder-command-open", ".commands": "folder-command-open", "_commands": "folder-command-open", + "-commands": "folder-command-open", "__commands__": "folder-command-open", "commandline": "folder-command-open", ".commandline": "folder-command-open", "_commandline": "folder-command-open", + "-commandline": "folder-command-open", "__commandline__": "folder-command-open", "cmd": "folder-command-open", ".cmd": "folder-command-open", "_cmd": "folder-command-open", + "-cmd": "folder-command-open", "__cmd__": "folder-command-open", "cli": "folder-command-open", ".cli": "folder-command-open", "_cli": "folder-command-open", + "-cli": "folder-command-open", "__cli__": "folder-command-open", "clis": "folder-command-open", ".clis": "folder-command-open", "_clis": "folder-command-open", + "-clis": "folder-command-open", "__clis__": "folder-command-open", "constant": "folder-constant-open", ".constant": "folder-constant-open", "_constant": "folder-constant-open", + "-constant": "folder-constant-open", "__constant__": "folder-constant-open", "constants": "folder-constant-open", ".constants": "folder-constant-open", "_constants": "folder-constant-open", + "-constants": "folder-constant-open", "__constants__": "folder-constant-open", "const": "folder-constant-open", ".const": "folder-constant-open", "_const": "folder-constant-open", + "-const": "folder-constant-open", "__const__": "folder-constant-open", "consts": "folder-constant-open", ".consts": "folder-constant-open", "_consts": "folder-constant-open", + "-consts": "folder-constant-open", "__consts__": "folder-constant-open", "container": "folder-container-open", ".container": "folder-container-open", "_container": "folder-container-open", + "-container": "folder-container-open", "__container__": "folder-container-open", "containers": "folder-container-open", ".containers": "folder-container-open", "_containers": "folder-container-open", + "-containers": "folder-container-open", "__containers__": "folder-container-open", "devcontainer": "folder-container-open", ".devcontainer": "folder-container-open", "_devcontainer": "folder-container-open", + "-devcontainer": "folder-container-open", "__devcontainer__": "folder-container-open", "content": "folder-content-open", ".content": "folder-content-open", "_content": "folder-content-open", + "-content": "folder-content-open", "__content__": "folder-content-open", "contents": "folder-content-open", ".contents": "folder-content-open", "_contents": "folder-content-open", + "-contents": "folder-content-open", "__contents__": "folder-content-open", "context": "folder-context-open", ".context": "folder-context-open", "_context": "folder-context-open", + "-context": "folder-context-open", "__context__": "folder-context-open", "contexts": "folder-context-open", ".contexts": "folder-context-open", "_contexts": "folder-context-open", + "-contexts": "folder-context-open", "__contexts__": "folder-context-open", "core": "folder-core-open", ".core": "folder-core-open", "_core": "folder-core-open", + "-core": "folder-core-open", "__core__": "folder-core-open", "delta": "folder-delta-open", ".delta": "folder-delta-open", "_delta": "folder-delta-open", + "-delta": "folder-delta-open", "__delta__": "folder-delta-open", "deltas": "folder-delta-open", ".deltas": "folder-delta-open", "_deltas": "folder-delta-open", + "-deltas": "folder-delta-open", "__deltas__": "folder-delta-open", "changes": "folder-delta-open", ".changes": "folder-delta-open", "_changes": "folder-delta-open", + "-changes": "folder-delta-open", "__changes__": "folder-delta-open", "dump": "folder-dump-open", ".dump": "folder-dump-open", "_dump": "folder-dump-open", + "-dump": "folder-dump-open", "__dump__": "folder-dump-open", "dumps": "folder-dump-open", ".dumps": "folder-dump-open", "_dumps": "folder-dump-open", + "-dumps": "folder-dump-open", "__dumps__": "folder-dump-open", "demo": "folder-examples-open", ".demo": "folder-examples-open", "_demo": "folder-examples-open", + "-demo": "folder-examples-open", "__demo__": "folder-examples-open", "demos": "folder-examples-open", ".demos": "folder-examples-open", "_demos": "folder-examples-open", + "-demos": "folder-examples-open", "__demos__": "folder-examples-open", "example": "folder-examples-open", ".example": "folder-examples-open", "_example": "folder-examples-open", + "-example": "folder-examples-open", "__example__": "folder-examples-open", "examples": "folder-examples-open", ".examples": "folder-examples-open", "_examples": "folder-examples-open", + "-examples": "folder-examples-open", "__examples__": "folder-examples-open", "sample": "folder-examples-open", ".sample": "folder-examples-open", "_sample": "folder-examples-open", + "-sample": "folder-examples-open", "__sample__": "folder-examples-open", "samples": "folder-examples-open", ".samples": "folder-examples-open", "_samples": "folder-examples-open", + "-samples": "folder-examples-open", "__samples__": "folder-examples-open", "sample-data": "folder-examples-open", ".sample-data": "folder-examples-open", "_sample-data": "folder-examples-open", + "-sample-data": "folder-examples-open", "__sample-data__": "folder-examples-open", "env": "folder-environment-open", ".env": "folder-environment-open", "_env": "folder-environment-open", + "-env": "folder-environment-open", "__env__": "folder-environment-open", "envs": "folder-environment-open", ".envs": "folder-environment-open", "_envs": "folder-environment-open", + "-envs": "folder-environment-open", "__envs__": "folder-environment-open", "environment": "folder-environment-open", ".environment": "folder-environment-open", "_environment": "folder-environment-open", + "-environment": "folder-environment-open", "__environment__": "folder-environment-open", "environments": "folder-environment-open", ".environments": "folder-environment-open", "_environments": "folder-environment-open", + "-environments": "folder-environment-open", "__environments__": "folder-environment-open", "venv": "folder-environment-open", ".venv": "folder-environment-open", "_venv": "folder-environment-open", + "-venv": "folder-environment-open", "__venv__": "folder-environment-open", "func": "folder-functions-open", ".func": "folder-functions-open", "_func": "folder-functions-open", + "-func": "folder-functions-open", "__func__": "folder-functions-open", "funcs": "folder-functions-open", ".funcs": "folder-functions-open", "_funcs": "folder-functions-open", + "-funcs": "folder-functions-open", "__funcs__": "folder-functions-open", "function": "folder-functions-open", ".function": "folder-functions-open", "_function": "folder-functions-open", + "-function": "folder-functions-open", "__function__": "folder-functions-open", "functions": "folder-functions-open", ".functions": "folder-functions-open", "_functions": "folder-functions-open", + "-functions": "folder-functions-open", "__functions__": "folder-functions-open", "lambda": "folder-functions-open", ".lambda": "folder-functions-open", "_lambda": "folder-functions-open", + "-lambda": "folder-functions-open", "__lambda__": "folder-functions-open", "lambdas": "folder-functions-open", ".lambdas": "folder-functions-open", "_lambdas": "folder-functions-open", + "-lambdas": "folder-functions-open", "__lambdas__": "folder-functions-open", "logic": "folder-functions-open", ".logic": "folder-functions-open", "_logic": "folder-functions-open", + "-logic": "folder-functions-open", "__logic__": "folder-functions-open", "math": "folder-functions-open", ".math": "folder-functions-open", "_math": "folder-functions-open", + "-math": "folder-functions-open", "__math__": "folder-functions-open", "maths": "folder-functions-open", ".maths": "folder-functions-open", "_maths": "folder-functions-open", + "-maths": "folder-functions-open", "__maths__": "folder-functions-open", "calc": "folder-functions-open", ".calc": "folder-functions-open", "_calc": "folder-functions-open", + "-calc": "folder-functions-open", "__calc__": "folder-functions-open", "calcs": "folder-functions-open", ".calcs": "folder-functions-open", "_calcs": "folder-functions-open", + "-calcs": "folder-functions-open", "__calcs__": "folder-functions-open", "calculation": "folder-functions-open", ".calculation": "folder-functions-open", "_calculation": "folder-functions-open", + "-calculation": "folder-functions-open", "__calculation__": "folder-functions-open", "calculations": "folder-functions-open", ".calculations": "folder-functions-open", "_calculations": "folder-functions-open", + "-calculations": "folder-functions-open", "__calculations__": "folder-functions-open", "composable": "folder-functions-open", ".composable": "folder-functions-open", "_composable": "folder-functions-open", + "-composable": "folder-functions-open", "__composable__": "folder-functions-open", "composables": "folder-functions-open", ".composables": "folder-functions-open", "_composables": "folder-functions-open", + "-composables": "folder-functions-open", "__composables__": "folder-functions-open", "generator": "folder-generator-open", ".generator": "folder-generator-open", "_generator": "folder-generator-open", + "-generator": "folder-generator-open", "__generator__": "folder-generator-open", "generators": "folder-generator-open", ".generators": "folder-generator-open", "_generators": "folder-generator-open", + "-generators": "folder-generator-open", "__generators__": "folder-generator-open", "generated": "folder-generator-open", ".generated": "folder-generator-open", "_generated": "folder-generator-open", + "-generated": "folder-generator-open", "__generated__": "folder-generator-open", "cfn-gen": "folder-generator-open", ".cfn-gen": "folder-generator-open", "_cfn-gen": "folder-generator-open", + "-cfn-gen": "folder-generator-open", "__cfn-gen__": "folder-generator-open", "gen": "folder-generator-open", ".gen": "folder-generator-open", "_gen": "folder-generator-open", + "-gen": "folder-generator-open", "__gen__": "folder-generator-open", "gens": "folder-generator-open", ".gens": "folder-generator-open", "_gens": "folder-generator-open", + "-gens": "folder-generator-open", "__gens__": "folder-generator-open", "auto": "folder-generator-open", ".auto": "folder-generator-open", "_auto": "folder-generator-open", + "-auto": "folder-generator-open", "__auto__": "folder-generator-open", "hook": "folder-hook-open", ".hook": "folder-hook-open", "_hook": "folder-hook-open", + "-hook": "folder-hook-open", "__hook__": "folder-hook-open", "hooks": "folder-hook-open", ".hooks": "folder-hook-open", "_hooks": "folder-hook-open", + "-hooks": "folder-hook-open", "__hooks__": "folder-hook-open", "trigger": "folder-trigger-open", ".trigger": "folder-trigger-open", "_trigger": "folder-trigger-open", + "-trigger": "folder-trigger-open", "__trigger__": "folder-trigger-open", "triggers": "folder-trigger-open", ".triggers": "folder-trigger-open", "_triggers": "folder-trigger-open", + "-triggers": "folder-trigger-open", "__triggers__": "folder-trigger-open", "job": "folder-job-open", ".job": "folder-job-open", "_job": "folder-job-open", + "-job": "folder-job-open", "__job__": "folder-job-open", "jobs": "folder-job-open", ".jobs": "folder-job-open", "_jobs": "folder-job-open", + "-jobs": "folder-job-open", "__jobs__": "folder-job-open", "key": "folder-keys-open", ".key": "folder-keys-open", "_key": "folder-keys-open", + "-key": "folder-keys-open", "__key__": "folder-keys-open", "keys": "folder-keys-open", ".keys": "folder-keys-open", "_keys": "folder-keys-open", + "-keys": "folder-keys-open", "__keys__": "folder-keys-open", "token": "folder-keys-open", ".token": "folder-keys-open", "_token": "folder-keys-open", + "-token": "folder-keys-open", "__token__": "folder-keys-open", "tokens": "folder-keys-open", ".tokens": "folder-keys-open", "_tokens": "folder-keys-open", + "-tokens": "folder-keys-open", "__tokens__": "folder-keys-open", "jwt": "folder-keys-open", ".jwt": "folder-keys-open", "_jwt": "folder-keys-open", + "-jwt": "folder-keys-open", "__jwt__": "folder-keys-open", "secret": "folder-keys-open", ".secret": "folder-keys-open", "_secret": "folder-keys-open", + "-secret": "folder-keys-open", "__secret__": "folder-keys-open", "secrets": "folder-keys-open", ".secrets": "folder-keys-open", "_secrets": "folder-keys-open", + "-secrets": "folder-keys-open", "__secrets__": "folder-keys-open", "layout": "folder-layout-open", ".layout": "folder-layout-open", "_layout": "folder-layout-open", + "-layout": "folder-layout-open", "__layout__": "folder-layout-open", "layouts": "folder-layout-open", ".layouts": "folder-layout-open", "_layouts": "folder-layout-open", + "-layouts": "folder-layout-open", "__layouts__": "folder-layout-open", "mail": "folder-mail-open", ".mail": "folder-mail-open", "_mail": "folder-mail-open", + "-mail": "folder-mail-open", "__mail__": "folder-mail-open", "mails": "folder-mail-open", ".mails": "folder-mail-open", "_mails": "folder-mail-open", + "-mails": "folder-mail-open", "__mails__": "folder-mail-open", "email": "folder-mail-open", ".email": "folder-mail-open", "_email": "folder-mail-open", + "-email": "folder-mail-open", "__email__": "folder-mail-open", "emails": "folder-mail-open", ".emails": "folder-mail-open", "_emails": "folder-mail-open", + "-emails": "folder-mail-open", "__emails__": "folder-mail-open", "smtp": "folder-mail-open", ".smtp": "folder-mail-open", "_smtp": "folder-mail-open", + "-smtp": "folder-mail-open", "__smtp__": "folder-mail-open", "mailers": "folder-mail-open", ".mailers": "folder-mail-open", "_mailers": "folder-mail-open", + "-mailers": "folder-mail-open", "__mailers__": "folder-mail-open", "mappings": "folder-mappings-open", ".mappings": "folder-mappings-open", "_mappings": "folder-mappings-open", + "-mappings": "folder-mappings-open", "__mappings__": "folder-mappings-open", "mapping": "folder-mappings-open", ".mapping": "folder-mappings-open", "_mapping": "folder-mappings-open", + "-mapping": "folder-mappings-open", "__mapping__": "folder-mappings-open", "meta": "folder-meta-open", ".meta": "folder-meta-open", "_meta": "folder-meta-open", + "-meta": "folder-meta-open", "__meta__": "folder-meta-open", "metadata": "folder-meta-open", ".metadata": "folder-meta-open", "_metadata": "folder-meta-open", + "-metadata": "folder-meta-open", "__metadata__": "folder-meta-open", "changesets": "folder-changesets-open", ".changesets": "folder-changesets-open", "_changesets": "folder-changesets-open", + "-changesets": "folder-changesets-open", "__changesets__": "folder-changesets-open", "changeset": "folder-changesets-open", ".changeset": "folder-changesets-open", "_changeset": "folder-changesets-open", + "-changeset": "folder-changesets-open", "__changeset__": "folder-changesets-open", "package": "folder-packages-open", ".package": "folder-packages-open", "_package": "folder-packages-open", + "-package": "folder-packages-open", "__package__": "folder-packages-open", "packages": "folder-packages-open", ".packages": "folder-packages-open", "_packages": "folder-packages-open", + "-packages": "folder-packages-open", "__packages__": "folder-packages-open", "pkg": "folder-packages-open", ".pkg": "folder-packages-open", "_pkg": "folder-packages-open", + "-pkg": "folder-packages-open", "__pkg__": "folder-packages-open", "pkgs": "folder-packages-open", ".pkgs": "folder-packages-open", "_pkgs": "folder-packages-open", + "-pkgs": "folder-packages-open", "__pkgs__": "folder-packages-open", "serverpackages": "folder-packages-open", ".serverpackages": "folder-packages-open", "_serverpackages": "folder-packages-open", + "-serverpackages": "folder-packages-open", "__serverpackages__": "folder-packages-open", "devpackages": "folder-packages-open", ".devpackages": "folder-packages-open", "_devpackages": "folder-packages-open", + "-devpackages": "folder-packages-open", "__devpackages__": "folder-packages-open", "dependencies": "folder-packages-open", ".dependencies": "folder-packages-open", "_dependencies": "folder-packages-open", + "-dependencies": "folder-packages-open", "__dependencies__": "folder-packages-open", "shared": "folder-shared-open", ".shared": "folder-shared-open", "_shared": "folder-shared-open", + "-shared": "folder-shared-open", "__shared__": "folder-shared-open", "common": "folder-shared-open", ".common": "folder-shared-open", "_common": "folder-shared-open", + "-common": "folder-shared-open", "__common__": "folder-shared-open", "glsl": "folder-shader-open", ".glsl": "folder-shader-open", "_glsl": "folder-shader-open", + "-glsl": "folder-shader-open", "__glsl__": "folder-shader-open", "hlsl": "folder-shader-open", ".hlsl": "folder-shader-open", "_hlsl": "folder-shader-open", + "-hlsl": "folder-shader-open", "__hlsl__": "folder-shader-open", "shader": "folder-shader-open", ".shader": "folder-shader-open", "_shader": "folder-shader-open", + "-shader": "folder-shader-open", "__shader__": "folder-shader-open", "shaders": "folder-shader-open", ".shaders": "folder-shader-open", "_shaders": "folder-shader-open", + "-shaders": "folder-shader-open", "__shaders__": "folder-shader-open", "stack": "folder-stack-open", ".stack": "folder-stack-open", "_stack": "folder-stack-open", + "-stack": "folder-stack-open", "__stack__": "folder-stack-open", "stacks": "folder-stack-open", ".stacks": "folder-stack-open", "_stacks": "folder-stack-open", + "-stacks": "folder-stack-open", "__stacks__": "folder-stack-open", "template": "folder-template-open", ".template": "folder-template-open", "_template": "folder-template-open", + "-template": "folder-template-open", "__template__": "folder-template-open", "templates": "folder-template-open", ".templates": "folder-template-open", "_templates": "folder-template-open", + "-templates": "folder-template-open", "__templates__": "folder-template-open", "github/ISSUE_TEMPLATE": "folder-template-open", ".github/ISSUE_TEMPLATE": "folder-template-open", "_github/ISSUE_TEMPLATE": "folder-template-open", + "-github/ISSUE_TEMPLATE": "folder-template-open", "__github/ISSUE_TEMPLATE__": "folder-template-open", "github/PULL_REQUEST_TEMPLATE": "folder-template-open", ".github/PULL_REQUEST_TEMPLATE": "folder-template-open", "_github/PULL_REQUEST_TEMPLATE": "folder-template-open", + "-github/PULL_REQUEST_TEMPLATE": "folder-template-open", "__github/PULL_REQUEST_TEMPLATE__": "folder-template-open", "util": "folder-utils-open", ".util": "folder-utils-open", "_util": "folder-utils-open", + "-util": "folder-utils-open", "__util__": "folder-utils-open", "utils": "folder-utils-open", ".utils": "folder-utils-open", "_utils": "folder-utils-open", + "-utils": "folder-utils-open", "__utils__": "folder-utils-open", "utility": "folder-utils-open", ".utility": "folder-utils-open", "_utility": "folder-utils-open", + "-utility": "folder-utils-open", "__utility__": "folder-utils-open", "utilities": "folder-utils-open", ".utilities": "folder-utils-open", "_utilities": "folder-utils-open", + "-utilities": "folder-utils-open", "__utilities__": "folder-utils-open", "supabase": "folder-supabase-open", ".supabase": "folder-supabase-open", "_supabase": "folder-supabase-open", + "-supabase": "folder-supabase-open", "__supabase__": "folder-supabase-open", "private": "folder-private-open", ".private": "folder-private-open", "_private": "folder-private-open", + "-private": "folder-private-open", "__private__": "folder-private-open", "linux": "folder-linux-open", ".linux": "folder-linux-open", "_linux": "folder-linux-open", + "-linux": "folder-linux-open", "__linux__": "folder-linux-open", "linuxbsd": "folder-linux-open", ".linuxbsd": "folder-linux-open", "_linuxbsd": "folder-linux-open", + "-linuxbsd": "folder-linux-open", "__linuxbsd__": "folder-linux-open", "unix": "folder-linux-open", ".unix": "folder-linux-open", "_unix": "folder-linux-open", + "-unix": "folder-linux-open", "__unix__": "folder-linux-open", "wsl": "folder-linux-open", ".wsl": "folder-linux-open", "_wsl": "folder-linux-open", + "-wsl": "folder-linux-open", "__wsl__": "folder-linux-open", "ubuntu": "folder-linux-open", ".ubuntu": "folder-linux-open", "_ubuntu": "folder-linux-open", + "-ubuntu": "folder-linux-open", "__ubuntu__": "folder-linux-open", "deb": "folder-linux-open", ".deb": "folder-linux-open", "_deb": "folder-linux-open", + "-deb": "folder-linux-open", "__deb__": "folder-linux-open", "debian": "folder-linux-open", ".debian": "folder-linux-open", "_debian": "folder-linux-open", + "-debian": "folder-linux-open", "__debian__": "folder-linux-open", "deepin": "folder-linux-open", ".deepin": "folder-linux-open", "_deepin": "folder-linux-open", + "-deepin": "folder-linux-open", "__deepin__": "folder-linux-open", "centos": "folder-linux-open", ".centos": "folder-linux-open", "_centos": "folder-linux-open", + "-centos": "folder-linux-open", "__centos__": "folder-linux-open", "popos": "folder-linux-open", ".popos": "folder-linux-open", "_popos": "folder-linux-open", + "-popos": "folder-linux-open", "__popos__": "folder-linux-open", "mint": "folder-linux-open", ".mint": "folder-linux-open", "_mint": "folder-linux-open", + "-mint": "folder-linux-open", "__mint__": "folder-linux-open", "windows": "folder-windows-open", ".windows": "folder-windows-open", "_windows": "folder-windows-open", + "-windows": "folder-windows-open", "__windows__": "folder-windows-open", "win": "folder-windows-open", ".win": "folder-windows-open", "_win": "folder-windows-open", + "-win": "folder-windows-open", "__win__": "folder-windows-open", "win32": "folder-windows-open", ".win32": "folder-windows-open", "_win32": "folder-windows-open", + "-win32": "folder-windows-open", "__win32__": "folder-windows-open", "windows11": "folder-windows-open", ".windows11": "folder-windows-open", "_windows11": "folder-windows-open", + "-windows11": "folder-windows-open", "__windows11__": "folder-windows-open", "windows10": "folder-windows-open", ".windows10": "folder-windows-open", "_windows10": "folder-windows-open", + "-windows10": "folder-windows-open", "__windows10__": "folder-windows-open", "windowsxp": "folder-windows-open", ".windowsxp": "folder-windows-open", "_windowsxp": "folder-windows-open", + "-windowsxp": "folder-windows-open", "__windowsxp__": "folder-windows-open", "windowsnt": "folder-windows-open", ".windowsnt": "folder-windows-open", "_windowsnt": "folder-windows-open", + "-windowsnt": "folder-windows-open", "__windowsnt__": "folder-windows-open", "win11": "folder-windows-open", ".win11": "folder-windows-open", "_win11": "folder-windows-open", + "-win11": "folder-windows-open", "__win11__": "folder-windows-open", "win10": "folder-windows-open", ".win10": "folder-windows-open", "_win10": "folder-windows-open", + "-win10": "folder-windows-open", "__win10__": "folder-windows-open", "winxp": "folder-windows-open", ".winxp": "folder-windows-open", "_winxp": "folder-windows-open", + "-winxp": "folder-windows-open", "__winxp__": "folder-windows-open", "winnt": "folder-windows-open", ".winnt": "folder-windows-open", "_winnt": "folder-windows-open", + "-winnt": "folder-windows-open", "__winnt__": "folder-windows-open", "macos": "folder-macos-open", ".macos": "folder-macos-open", "_macos": "folder-macos-open", + "-macos": "folder-macos-open", "__macos__": "folder-macos-open", "mac": "folder-macos-open", ".mac": "folder-macos-open", "_mac": "folder-macos-open", + "-mac": "folder-macos-open", "__mac__": "folder-macos-open", "osx": "folder-macos-open", ".osx": "folder-macos-open", "_osx": "folder-macos-open", + "-osx": "folder-macos-open", "__osx__": "folder-macos-open", "DS_Store": "folder-macos-open", ".DS_Store": "folder-macos-open", "_DS_Store": "folder-macos-open", + "-DS_Store": "folder-macos-open", "__DS_Store__": "folder-macos-open", "iPhone": "folder-macos-open", ".iPhone": "folder-macos-open", "_iPhone": "folder-macos-open", + "-iPhone": "folder-macos-open", "__iPhone__": "folder-macos-open", "iPad": "folder-macos-open", ".iPad": "folder-macos-open", "_iPad": "folder-macos-open", + "-iPad": "folder-macos-open", "__iPad__": "folder-macos-open", "iPod": "folder-macos-open", ".iPod": "folder-macos-open", "_iPod": "folder-macos-open", + "-iPod": "folder-macos-open", "__iPod__": "folder-macos-open", "macbook": "folder-macos-open", ".macbook": "folder-macos-open", "_macbook": "folder-macos-open", + "-macbook": "folder-macos-open", "__macbook__": "folder-macos-open", "macbook-air": "folder-macos-open", ".macbook-air": "folder-macos-open", "_macbook-air": "folder-macos-open", + "-macbook-air": "folder-macos-open", "__macbook-air__": "folder-macos-open", "macosx": "folder-macos-open", ".macosx": "folder-macos-open", "_macosx": "folder-macos-open", + "-macosx": "folder-macos-open", "__macosx__": "folder-macos-open", "apple": "folder-macos-open", ".apple": "folder-macos-open", "_apple": "folder-macos-open", + "-apple": "folder-macos-open", "__apple__": "folder-macos-open", "error": "folder-error-open", ".error": "folder-error-open", "_error": "folder-error-open", + "-error": "folder-error-open", "__error__": "folder-error-open", "errors": "folder-error-open", ".errors": "folder-error-open", "_errors": "folder-error-open", + "-errors": "folder-error-open", "__errors__": "folder-error-open", "err": "folder-error-open", ".err": "folder-error-open", "_err": "folder-error-open", + "-err": "folder-error-open", "__err__": "folder-error-open", "errs": "folder-error-open", ".errs": "folder-error-open", "_errs": "folder-error-open", + "-errs": "folder-error-open", "__errs__": "folder-error-open", "crash": "folder-error-open", ".crash": "folder-error-open", "_crash": "folder-error-open", + "-crash": "folder-error-open", "__crash__": "folder-error-open", "crashes": "folder-error-open", ".crashes": "folder-error-open", "_crashes": "folder-error-open", + "-crashes": "folder-error-open", "__crashes__": "folder-error-open", "event": "folder-event-open", ".event": "folder-event-open", "_event": "folder-event-open", + "-event": "folder-event-open", "__event__": "folder-event-open", "events": "folder-event-open", ".events": "folder-event-open", "_events": "folder-event-open", + "-events": "folder-event-open", "__events__": "folder-event-open", "auth": "folder-secure-open", ".auth": "folder-secure-open", "_auth": "folder-secure-open", + "-auth": "folder-secure-open", "__auth__": "folder-secure-open", "authentication": "folder-secure-open", ".authentication": "folder-secure-open", "_authentication": "folder-secure-open", + "-authentication": "folder-secure-open", "__authentication__": "folder-secure-open", "secure": "folder-secure-open", ".secure": "folder-secure-open", "_secure": "folder-secure-open", + "-secure": "folder-secure-open", "__secure__": "folder-secure-open", "security": "folder-secure-open", ".security": "folder-secure-open", "_security": "folder-secure-open", + "-security": "folder-secure-open", "__security__": "folder-secure-open", "cert": "folder-secure-open", ".cert": "folder-secure-open", "_cert": "folder-secure-open", + "-cert": "folder-secure-open", "__cert__": "folder-secure-open", "certs": "folder-secure-open", ".certs": "folder-secure-open", "_certs": "folder-secure-open", + "-certs": "folder-secure-open", "__certs__": "folder-secure-open", "certificate": "folder-secure-open", ".certificate": "folder-secure-open", "_certificate": "folder-secure-open", + "-certificate": "folder-secure-open", "__certificate__": "folder-secure-open", "certificates": "folder-secure-open", ".certificates": "folder-secure-open", "_certificates": "folder-secure-open", + "-certificates": "folder-secure-open", "__certificates__": "folder-secure-open", "ssl": "folder-secure-open", ".ssl": "folder-secure-open", "_ssl": "folder-secure-open", + "-ssl": "folder-secure-open", "__ssl__": "folder-secure-open", "cipher": "folder-secure-open", ".cipher": "folder-secure-open", "_cipher": "folder-secure-open", + "-cipher": "folder-secure-open", "__cipher__": "folder-secure-open", "cypher": "folder-secure-open", ".cypher": "folder-secure-open", "_cypher": "folder-secure-open", + "-cypher": "folder-secure-open", "__cypher__": "folder-secure-open", "tls": "folder-secure-open", ".tls": "folder-secure-open", "_tls": "folder-secure-open", + "-tls": "folder-secure-open", "__tls__": "folder-secure-open", "custom": "folder-custom-open", ".custom": "folder-custom-open", "_custom": "folder-custom-open", + "-custom": "folder-custom-open", "__custom__": "folder-custom-open", "customs": "folder-custom-open", ".customs": "folder-custom-open", "_customs": "folder-custom-open", + "-customs": "folder-custom-open", "__customs__": "folder-custom-open", "draft": "folder-mock-open", ".draft": "folder-mock-open", "_draft": "folder-mock-open", + "-draft": "folder-mock-open", "__draft__": "folder-mock-open", "drafts": "folder-mock-open", ".drafts": "folder-mock-open", "_drafts": "folder-mock-open", + "-drafts": "folder-mock-open", "__drafts__": "folder-mock-open", "mock": "folder-mock-open", ".mock": "folder-mock-open", "_mock": "folder-mock-open", + "-mock": "folder-mock-open", "__mock__": "folder-mock-open", "mocks": "folder-mock-open", ".mocks": "folder-mock-open", "_mocks": "folder-mock-open", + "-mocks": "folder-mock-open", "__mocks__": "folder-mock-open", "fixture": "folder-mock-open", ".fixture": "folder-mock-open", "_fixture": "folder-mock-open", + "-fixture": "folder-mock-open", "__fixture__": "folder-mock-open", "fixtures": "folder-mock-open", ".fixtures": "folder-mock-open", "_fixtures": "folder-mock-open", + "-fixtures": "folder-mock-open", "__fixtures__": "folder-mock-open", "concept": "folder-mock-open", ".concept": "folder-mock-open", "_concept": "folder-mock-open", + "-concept": "folder-mock-open", "__concept__": "folder-mock-open", "concepts": "folder-mock-open", ".concepts": "folder-mock-open", "_concepts": "folder-mock-open", + "-concepts": "folder-mock-open", "__concepts__": "folder-mock-open", "sketch": "folder-mock-open", ".sketch": "folder-mock-open", "_sketch": "folder-mock-open", + "-sketch": "folder-mock-open", "__sketch__": "folder-mock-open", "sketches": "folder-mock-open", ".sketches": "folder-mock-open", "_sketches": "folder-mock-open", + "-sketches": "folder-mock-open", "__sketches__": "folder-mock-open", "syntax": "folder-syntax-open", ".syntax": "folder-syntax-open", "_syntax": "folder-syntax-open", + "-syntax": "folder-syntax-open", "__syntax__": "folder-syntax-open", "syntaxes": "folder-syntax-open", ".syntaxes": "folder-syntax-open", "_syntaxes": "folder-syntax-open", + "-syntaxes": "folder-syntax-open", "__syntaxes__": "folder-syntax-open", "spellcheck": "folder-syntax-open", ".spellcheck": "folder-syntax-open", "_spellcheck": "folder-syntax-open", + "-spellcheck": "folder-syntax-open", "__spellcheck__": "folder-syntax-open", "spellcheckers": "folder-syntax-open", ".spellcheckers": "folder-syntax-open", "_spellcheckers": "folder-syntax-open", + "-spellcheckers": "folder-syntax-open", "__spellcheckers__": "folder-syntax-open", "vm": "folder-vm-open", ".vm": "folder-vm-open", "_vm": "folder-vm-open", + "-vm": "folder-vm-open", "__vm__": "folder-vm-open", "vms": "folder-vm-open", ".vms": "folder-vm-open", "_vms": "folder-vm-open", + "-vms": "folder-vm-open", "__vms__": "folder-vm-open", "stylus": "folder-stylus-open", ".stylus": "folder-stylus-open", "_stylus": "folder-stylus-open", + "-stylus": "folder-stylus-open", "__stylus__": "folder-stylus-open", "flow-typed": "folder-flow-open", ".flow-typed": "folder-flow-open", "_flow-typed": "folder-flow-open", + "-flow-typed": "folder-flow-open", "__flow-typed__": "folder-flow-open", "rule": "folder-rules-open", ".rule": "folder-rules-open", "_rule": "folder-rules-open", + "-rule": "folder-rules-open", "__rule__": "folder-rules-open", "rules": "folder-rules-open", ".rules": "folder-rules-open", "_rules": "folder-rules-open", + "-rules": "folder-rules-open", "__rules__": "folder-rules-open", "validation": "folder-rules-open", ".validation": "folder-rules-open", "_validation": "folder-rules-open", + "-validation": "folder-rules-open", "__validation__": "folder-rules-open", "validations": "folder-rules-open", ".validations": "folder-rules-open", "_validations": "folder-rules-open", + "-validations": "folder-rules-open", "__validations__": "folder-rules-open", "validator": "folder-rules-open", ".validator": "folder-rules-open", "_validator": "folder-rules-open", + "-validator": "folder-rules-open", "__validator__": "folder-rules-open", "validators": "folder-rules-open", ".validators": "folder-rules-open", "_validators": "folder-rules-open", + "-validators": "folder-rules-open", "__validators__": "folder-rules-open", "review": "folder-review-open", ".review": "folder-review-open", "_review": "folder-review-open", + "-review": "folder-review-open", "__review__": "folder-review-open", "reviews": "folder-review-open", ".reviews": "folder-review-open", "_reviews": "folder-review-open", + "-reviews": "folder-review-open", "__reviews__": "folder-review-open", "revisal": "folder-review-open", ".revisal": "folder-review-open", "_revisal": "folder-review-open", + "-revisal": "folder-review-open", "__revisal__": "folder-review-open", "revisals": "folder-review-open", ".revisals": "folder-review-open", "_revisals": "folder-review-open", + "-revisals": "folder-review-open", "__revisals__": "folder-review-open", "reviewed": "folder-review-open", ".reviewed": "folder-review-open", "_reviewed": "folder-review-open", + "-reviewed": "folder-review-open", "__reviewed__": "folder-review-open", "preview": "folder-review-open", ".preview": "folder-review-open", "_preview": "folder-review-open", + "-preview": "folder-review-open", "__preview__": "folder-review-open", "previews": "folder-review-open", ".previews": "folder-review-open", "_previews": "folder-review-open", + "-previews": "folder-review-open", "__previews__": "folder-review-open", "anim": "folder-animation-open", ".anim": "folder-animation-open", "_anim": "folder-animation-open", + "-anim": "folder-animation-open", "__anim__": "folder-animation-open", "anims": "folder-animation-open", ".anims": "folder-animation-open", "_anims": "folder-animation-open", + "-anims": "folder-animation-open", "__anims__": "folder-animation-open", "animation": "folder-animation-open", ".animation": "folder-animation-open", "_animation": "folder-animation-open", + "-animation": "folder-animation-open", "__animation__": "folder-animation-open", "animations": "folder-animation-open", ".animations": "folder-animation-open", "_animations": "folder-animation-open", + "-animations": "folder-animation-open", "__animations__": "folder-animation-open", "animated": "folder-animation-open", ".animated": "folder-animation-open", "_animated": "folder-animation-open", + "-animated": "folder-animation-open", "__animated__": "folder-animation-open", "motion": "folder-animation-open", ".motion": "folder-animation-open", "_motion": "folder-animation-open", + "-motion": "folder-animation-open", "__motion__": "folder-animation-open", "motions": "folder-animation-open", ".motions": "folder-animation-open", "_motions": "folder-animation-open", + "-motions": "folder-animation-open", "__motions__": "folder-animation-open", "transition": "folder-animation-open", ".transition": "folder-animation-open", "_transition": "folder-animation-open", + "-transition": "folder-animation-open", "__transition__": "folder-animation-open", "transitions": "folder-animation-open", ".transitions": "folder-animation-open", "_transitions": "folder-animation-open", + "-transitions": "folder-animation-open", "__transitions__": "folder-animation-open", "easing": "folder-animation-open", ".easing": "folder-animation-open", "_easing": "folder-animation-open", + "-easing": "folder-animation-open", "__easing__": "folder-animation-open", "easings": "folder-animation-open", ".easings": "folder-animation-open", "_easings": "folder-animation-open", + "-easings": "folder-animation-open", "__easings__": "folder-animation-open", "guard": "folder-guard-open", ".guard": "folder-guard-open", "_guard": "folder-guard-open", + "-guard": "folder-guard-open", "__guard__": "folder-guard-open", "guards": "folder-guard-open", ".guards": "folder-guard-open", "_guards": "folder-guard-open", + "-guards": "folder-guard-open", "__guards__": "folder-guard-open", "prisma": "folder-prisma-open", ".prisma": "folder-prisma-open", "_prisma": "folder-prisma-open", + "-prisma": "folder-prisma-open", "__prisma__": "folder-prisma-open", "prisma/schema": "folder-prisma-open", ".prisma/schema": "folder-prisma-open", "_prisma/schema": "folder-prisma-open", + "-prisma/schema": "folder-prisma-open", "__prisma/schema__": "folder-prisma-open", "pipe": "folder-pipe-open", ".pipe": "folder-pipe-open", "_pipe": "folder-pipe-open", + "-pipe": "folder-pipe-open", "__pipe__": "folder-pipe-open", "pipes": "folder-pipe-open", ".pipes": "folder-pipe-open", "_pipes": "folder-pipe-open", + "-pipes": "folder-pipe-open", "__pipes__": "folder-pipe-open", "pipeline": "folder-pipe-open", ".pipeline": "folder-pipe-open", "_pipeline": "folder-pipe-open", + "-pipeline": "folder-pipe-open", "__pipeline__": "folder-pipe-open", "pipelines": "folder-pipe-open", ".pipelines": "folder-pipe-open", "_pipelines": "folder-pipe-open", + "-pipelines": "folder-pipe-open", "__pipelines__": "folder-pipe-open", "interceptor": "folder-interceptor-open", ".interceptor": "folder-interceptor-open", "_interceptor": "folder-interceptor-open", + "-interceptor": "folder-interceptor-open", "__interceptor__": "folder-interceptor-open", "interceptors": "folder-interceptor-open", ".interceptors": "folder-interceptor-open", "_interceptors": "folder-interceptor-open", + "-interceptors": "folder-interceptor-open", "__interceptors__": "folder-interceptor-open", "svg": "folder-svg-open", ".svg": "folder-svg-open", "_svg": "folder-svg-open", + "-svg": "folder-svg-open", "__svg__": "folder-svg-open", "svgs": "folder-svg-open", ".svgs": "folder-svg-open", "_svgs": "folder-svg-open", + "-svgs": "folder-svg-open", "__svgs__": "folder-svg-open", "vector": "folder-svg-open", ".vector": "folder-svg-open", "_vector": "folder-svg-open", + "-vector": "folder-svg-open", "__vector__": "folder-svg-open", "vectors": "folder-svg-open", ".vectors": "folder-svg-open", "_vectors": "folder-svg-open", + "-vectors": "folder-svg-open", "__vectors__": "folder-svg-open", "nuxt": "folder-nuxt-open", ".nuxt": "folder-nuxt-open", "_nuxt": "folder-nuxt-open", + "-nuxt": "folder-nuxt-open", "__nuxt__": "folder-nuxt-open", "terraform": "folder-terraform-open", ".terraform": "folder-terraform-open", "_terraform": "folder-terraform-open", + "-terraform": "folder-terraform-open", "__terraform__": "folder-terraform-open", "mobile": "folder-mobile-open", ".mobile": "folder-mobile-open", "_mobile": "folder-mobile-open", + "-mobile": "folder-mobile-open", "__mobile__": "folder-mobile-open", "mobiles": "folder-mobile-open", ".mobiles": "folder-mobile-open", "_mobiles": "folder-mobile-open", + "-mobiles": "folder-mobile-open", "__mobiles__": "folder-mobile-open", "portable": "folder-mobile-open", ".portable": "folder-mobile-open", "_portable": "folder-mobile-open", + "-portable": "folder-mobile-open", "__portable__": "folder-mobile-open", "portability": "folder-mobile-open", ".portability": "folder-mobile-open", "_portability": "folder-mobile-open", + "-portability": "folder-mobile-open", "__portability__": "folder-mobile-open", "phone": "folder-mobile-open", ".phone": "folder-mobile-open", "_phone": "folder-mobile-open", + "-phone": "folder-mobile-open", "__phone__": "folder-mobile-open", "phones": "folder-mobile-open", ".phones": "folder-mobile-open", "_phones": "folder-mobile-open", + "-phones": "folder-mobile-open", "__phones__": "folder-mobile-open", "stencil": "folder-stencil-open", ".stencil": "folder-stencil-open", "_stencil": "folder-stencil-open", + "-stencil": "folder-stencil-open", "__stencil__": "folder-stencil-open", "firebase": "folder-firebase-open", ".firebase": "folder-firebase-open", "_firebase": "folder-firebase-open", + "-firebase": "folder-firebase-open", "__firebase__": "folder-firebase-open", "firestore": "folder-firestore-open", ".firestore": "folder-firestore-open", "_firestore": "folder-firestore-open", + "-firestore": "folder-firestore-open", "__firestore__": "folder-firestore-open", "cloud-firestore": "folder-firestore-open", ".cloud-firestore": "folder-firestore-open", "_cloud-firestore": "folder-firestore-open", + "-cloud-firestore": "folder-firestore-open", "__cloud-firestore__": "folder-firestore-open", "firebase-firestore": "folder-firestore-open", ".firebase-firestore": "folder-firestore-open", "_firebase-firestore": "folder-firestore-open", + "-firebase-firestore": "folder-firestore-open", "__firebase-firestore__": "folder-firestore-open", "cloud-functions": "folder-cloud-functions-open", ".cloud-functions": "folder-cloud-functions-open", "_cloud-functions": "folder-cloud-functions-open", + "-cloud-functions": "folder-cloud-functions-open", "__cloud-functions__": "folder-cloud-functions-open", "cloudfunctions": "folder-cloud-functions-open", ".cloudfunctions": "folder-cloud-functions-open", "_cloudfunctions": "folder-cloud-functions-open", + "-cloudfunctions": "folder-cloud-functions-open", "__cloudfunctions__": "folder-cloud-functions-open", "firebase-cloud-functions": "folder-cloud-functions-open", ".firebase-cloud-functions": "folder-cloud-functions-open", "_firebase-cloud-functions": "folder-cloud-functions-open", + "-firebase-cloud-functions": "folder-cloud-functions-open", "__firebase-cloud-functions__": "folder-cloud-functions-open", "firebase-cloudfunctions": "folder-cloud-functions-open", ".firebase-cloudfunctions": "folder-cloud-functions-open", "_firebase-cloudfunctions": "folder-cloud-functions-open", + "-firebase-cloudfunctions": "folder-cloud-functions-open", "__firebase-cloudfunctions__": "folder-cloud-functions-open", "svelte": "folder-svelte-open", ".svelte": "folder-svelte-open", "_svelte": "folder-svelte-open", + "-svelte": "folder-svelte-open", "__svelte__": "folder-svelte-open", "svelte-kit": "folder-svelte-open", ".svelte-kit": "folder-svelte-open", "_svelte-kit": "folder-svelte-open", + "-svelte-kit": "folder-svelte-open", "__svelte-kit__": "folder-svelte-open", "update": "folder-update-open", ".update": "folder-update-open", "_update": "folder-update-open", + "-update": "folder-update-open", "__update__": "folder-update-open", "updates": "folder-update-open", ".updates": "folder-update-open", "_updates": "folder-update-open", + "-updates": "folder-update-open", "__updates__": "folder-update-open", "upgrade": "folder-update-open", ".upgrade": "folder-update-open", "_upgrade": "folder-update-open", + "-upgrade": "folder-update-open", "__upgrade__": "folder-update-open", "upgrades": "folder-update-open", ".upgrades": "folder-update-open", "_upgrades": "folder-update-open", + "-upgrades": "folder-update-open", "__upgrades__": "folder-update-open", "idea": "folder-intellij-open", ".idea": "folder-intellij-open", "_idea": "folder-intellij-open", + "-idea": "folder-intellij-open", "__idea__": "folder-intellij-open", "azure-pipelines": "folder-azure-pipelines-open", ".azure-pipelines": "folder-azure-pipelines-open", "_azure-pipelines": "folder-azure-pipelines-open", + "-azure-pipelines": "folder-azure-pipelines-open", "__azure-pipelines__": "folder-azure-pipelines-open", "azure-pipelines-ci": "folder-azure-pipelines-open", ".azure-pipelines-ci": "folder-azure-pipelines-open", "_azure-pipelines-ci": "folder-azure-pipelines-open", + "-azure-pipelines-ci": "folder-azure-pipelines-open", "__azure-pipelines-ci__": "folder-azure-pipelines-open", "mjml": "folder-mjml-open", ".mjml": "folder-mjml-open", "_mjml": "folder-mjml-open", + "-mjml": "folder-mjml-open", "__mjml__": "folder-mjml-open", "admin": "folder-admin-open", ".admin": "folder-admin-open", "_admin": "folder-admin-open", + "-admin": "folder-admin-open", "__admin__": "folder-admin-open", "admins": "folder-admin-open", ".admins": "folder-admin-open", "_admins": "folder-admin-open", + "-admins": "folder-admin-open", "__admins__": "folder-admin-open", "manager": "folder-admin-open", ".manager": "folder-admin-open", "_manager": "folder-admin-open", + "-manager": "folder-admin-open", "__manager__": "folder-admin-open", "managers": "folder-admin-open", ".managers": "folder-admin-open", "_managers": "folder-admin-open", + "-managers": "folder-admin-open", "__managers__": "folder-admin-open", "moderator": "folder-admin-open", ".moderator": "folder-admin-open", "_moderator": "folder-admin-open", + "-moderator": "folder-admin-open", "__moderator__": "folder-admin-open", "moderators": "folder-admin-open", ".moderators": "folder-admin-open", "_moderators": "folder-admin-open", + "-moderators": "folder-admin-open", "__moderators__": "folder-admin-open", "jupyter": "folder-jupyter-open", ".jupyter": "folder-jupyter-open", "_jupyter": "folder-jupyter-open", + "-jupyter": "folder-jupyter-open", "__jupyter__": "folder-jupyter-open", "notebook": "folder-jupyter-open", ".notebook": "folder-jupyter-open", "_notebook": "folder-jupyter-open", + "-notebook": "folder-jupyter-open", "__notebook__": "folder-jupyter-open", "notebooks": "folder-jupyter-open", ".notebooks": "folder-jupyter-open", "_notebooks": "folder-jupyter-open", + "-notebooks": "folder-jupyter-open", "__notebooks__": "folder-jupyter-open", "ipynb": "folder-jupyter-open", ".ipynb": "folder-jupyter-open", "_ipynb": "folder-jupyter-open", + "-ipynb": "folder-jupyter-open", "__ipynb__": "folder-jupyter-open", "scala": "folder-scala-open", ".scala": "folder-scala-open", "_scala": "folder-scala-open", + "-scala": "folder-scala-open", "__scala__": "folder-scala-open", "connection": "folder-connection-open", ".connection": "folder-connection-open", "_connection": "folder-connection-open", + "-connection": "folder-connection-open", "__connection__": "folder-connection-open", "connections": "folder-connection-open", ".connections": "folder-connection-open", "_connections": "folder-connection-open", + "-connections": "folder-connection-open", "__connections__": "folder-connection-open", "integration": "folder-connection-open", ".integration": "folder-connection-open", "_integration": "folder-connection-open", + "-integration": "folder-connection-open", "__integration__": "folder-connection-open", "integrations": "folder-connection-open", ".integrations": "folder-connection-open", "_integrations": "folder-connection-open", + "-integrations": "folder-connection-open", "__integrations__": "folder-connection-open", "remote": "folder-connection-open", ".remote": "folder-connection-open", "_remote": "folder-connection-open", + "-remote": "folder-connection-open", "__remote__": "folder-connection-open", "remotes": "folder-connection-open", ".remotes": "folder-connection-open", "_remotes": "folder-connection-open", + "-remotes": "folder-connection-open", "__remotes__": "folder-connection-open", "quasar": "folder-quasar-open", ".quasar": "folder-quasar-open", "_quasar": "folder-quasar-open", + "-quasar": "folder-quasar-open", "__quasar__": "folder-quasar-open", "next": "folder-next-open", ".next": "folder-next-open", "_next": "folder-next-open", + "-next": "folder-next-open", "__next__": "folder-next-open", "dal": "folder-dal-open", ".dal": "folder-dal-open", "_dal": "folder-dal-open", + "-dal": "folder-dal-open", "__dal__": "folder-dal-open", "data-access": "folder-dal-open", ".data-access": "folder-dal-open", "_data-access": "folder-dal-open", + "-data-access": "folder-dal-open", "__data-access__": "folder-dal-open", "data-access-layer": "folder-dal-open", ".data-access-layer": "folder-dal-open", "_data-access-layer": "folder-dal-open", + "-data-access-layer": "folder-dal-open", "__data-access-layer__": "folder-dal-open", "cobol": "folder-cobol-open", ".cobol": "folder-cobol-open", "_cobol": "folder-cobol-open", + "-cobol": "folder-cobol-open", "__cobol__": "folder-cobol-open", "yarn": "folder-yarn-open", ".yarn": "folder-yarn-open", "_yarn": "folder-yarn-open", + "-yarn": "folder-yarn-open", "__yarn__": "folder-yarn-open", "husky": "folder-husky-open", ".husky": "folder-husky-open", "_husky": "folder-husky-open", + "-husky": "folder-husky-open", "__husky__": "folder-husky-open", "storybook": "folder-storybook-open", ".storybook": "folder-storybook-open", "_storybook": "folder-storybook-open", + "-storybook": "folder-storybook-open", "__storybook__": "folder-storybook-open", "stories": "folder-storybook-open", ".stories": "folder-storybook-open", "_stories": "folder-storybook-open", + "-stories": "folder-storybook-open", "__stories__": "folder-storybook-open", "base": "folder-base-open", ".base": "folder-base-open", "_base": "folder-base-open", + "-base": "folder-base-open", "__base__": "folder-base-open", "bases": "folder-base-open", ".bases": "folder-base-open", "_bases": "folder-base-open", + "-bases": "folder-base-open", "__bases__": "folder-base-open", "cart": "folder-cart-open", ".cart": "folder-cart-open", "_cart": "folder-cart-open", + "-cart": "folder-cart-open", "__cart__": "folder-cart-open", "shopping-cart": "folder-cart-open", ".shopping-cart": "folder-cart-open", "_shopping-cart": "folder-cart-open", + "-shopping-cart": "folder-cart-open", "__shopping-cart__": "folder-cart-open", "shopping": "folder-cart-open", ".shopping": "folder-cart-open", "_shopping": "folder-cart-open", + "-shopping": "folder-cart-open", "__shopping__": "folder-cart-open", "shop": "folder-cart-open", ".shop": "folder-cart-open", "_shop": "folder-cart-open", + "-shop": "folder-cart-open", "__shop__": "folder-cart-open", "home": "folder-home-open", ".home": "folder-home-open", "_home": "folder-home-open", + "-home": "folder-home-open", "__home__": "folder-home-open", "start": "folder-home-open", ".start": "folder-home-open", "_start": "folder-home-open", + "-start": "folder-home-open", "__start__": "folder-home-open", "main": "folder-home-open", ".main": "folder-home-open", "_main": "folder-home-open", + "-main": "folder-home-open", "__main__": "folder-home-open", "landing": "folder-home-open", ".landing": "folder-home-open", "_landing": "folder-home-open", + "-landing": "folder-home-open", "__landing__": "folder-home-open", "project": "folder-project-open", ".project": "folder-project-open", "_project": "folder-project-open", + "-project": "folder-project-open", "__project__": "folder-project-open", "projects": "folder-project-open", ".projects": "folder-project-open", "_projects": "folder-project-open", + "-projects": "folder-project-open", "__projects__": "folder-project-open", "proj": "folder-project-open", ".proj": "folder-project-open", "_proj": "folder-project-open", + "-proj": "folder-project-open", "__proj__": "folder-project-open", "projs": "folder-project-open", ".projs": "folder-project-open", "_projs": "folder-project-open", + "-projs": "folder-project-open", "__projs__": "folder-project-open", "prompt": "folder-prompts-open", ".prompt": "folder-prompts-open", "_prompt": "folder-prompts-open", + "-prompt": "folder-prompts-open", "__prompt__": "folder-prompts-open", "prompts": "folder-prompts-open", ".prompts": "folder-prompts-open", "_prompts": "folder-prompts-open", + "-prompts": "folder-prompts-open", "__prompts__": "folder-prompts-open", "interface": "folder-interface-open", ".interface": "folder-interface-open", "_interface": "folder-interface-open", + "-interface": "folder-interface-open", "__interface__": "folder-interface-open", "interfaces": "folder-interface-open", ".interfaces": "folder-interface-open", "_interfaces": "folder-interface-open", + "-interfaces": "folder-interface-open", "__interfaces__": "folder-interface-open", "netlify": "folder-netlify-open", ".netlify": "folder-netlify-open", "_netlify": "folder-netlify-open", + "-netlify": "folder-netlify-open", "__netlify__": "folder-netlify-open", "enum": "folder-enum-open", ".enum": "folder-enum-open", "_enum": "folder-enum-open", + "-enum": "folder-enum-open", "__enum__": "folder-enum-open", "enums": "folder-enum-open", ".enums": "folder-enum-open", "_enums": "folder-enum-open", + "-enums": "folder-enum-open", "__enums__": "folder-enum-open", "pact": "folder-contract-open", ".pact": "folder-contract-open", "_pact": "folder-contract-open", + "-pact": "folder-contract-open", "__pact__": "folder-contract-open", "pacts": "folder-contract-open", ".pacts": "folder-contract-open", "_pacts": "folder-contract-open", + "-pacts": "folder-contract-open", "__pacts__": "folder-contract-open", "contract": "folder-contract-open", ".contract": "folder-contract-open", "_contract": "folder-contract-open", + "-contract": "folder-contract-open", "__contract__": "folder-contract-open", "contracts": "folder-contract-open", ".contracts": "folder-contract-open", "_contracts": "folder-contract-open", + "-contracts": "folder-contract-open", "__contracts__": "folder-contract-open", "contract-testing": "folder-contract-open", ".contract-testing": "folder-contract-open", "_contract-testing": "folder-contract-open", + "-contract-testing": "folder-contract-open", "__contract-testing__": "folder-contract-open", "contract-test": "folder-contract-open", ".contract-test": "folder-contract-open", "_contract-test": "folder-contract-open", + "-contract-test": "folder-contract-open", "__contract-test__": "folder-contract-open", "contract-tests": "folder-contract-open", ".contract-tests": "folder-contract-open", "_contract-tests": "folder-contract-open", + "-contract-tests": "folder-contract-open", "__contract-tests__": "folder-contract-open", "helm": "folder-helm-open", ".helm": "folder-helm-open", "_helm": "folder-helm-open", + "-helm": "folder-helm-open", "__helm__": "folder-helm-open", "helmchart": "folder-helm-open", ".helmchart": "folder-helm-open", "_helmchart": "folder-helm-open", + "-helmchart": "folder-helm-open", "__helmchart__": "folder-helm-open", "helmcharts": "folder-helm-open", ".helmcharts": "folder-helm-open", "_helmcharts": "folder-helm-open", + "-helmcharts": "folder-helm-open", "__helmcharts__": "folder-helm-open", "queue": "folder-queue-open", ".queue": "folder-queue-open", "_queue": "folder-queue-open", + "-queue": "folder-queue-open", "__queue__": "folder-queue-open", "queues": "folder-queue-open", ".queues": "folder-queue-open", "_queues": "folder-queue-open", + "-queues": "folder-queue-open", "__queues__": "folder-queue-open", "bull": "folder-queue-open", ".bull": "folder-queue-open", "_bull": "folder-queue-open", + "-bull": "folder-queue-open", "__bull__": "folder-queue-open", "mq": "folder-queue-open", ".mq": "folder-queue-open", "_mq": "folder-queue-open", + "-mq": "folder-queue-open", "__mq__": "folder-queue-open", "vercel": "folder-vercel-open", ".vercel": "folder-vercel-open", "_vercel": "folder-vercel-open", + "-vercel": "folder-vercel-open", "__vercel__": "folder-vercel-open", "now": "folder-vercel-open", ".now": "folder-vercel-open", "_now": "folder-vercel-open", + "-now": "folder-vercel-open", "__now__": "folder-vercel-open", "cypress": "folder-cypress-open", ".cypress": "folder-cypress-open", "_cypress": "folder-cypress-open", + "-cypress": "folder-cypress-open", "__cypress__": "folder-cypress-open", "decorator": "folder-decorators-open", ".decorator": "folder-decorators-open", "_decorator": "folder-decorators-open", + "-decorator": "folder-decorators-open", "__decorator__": "folder-decorators-open", "decorators": "folder-decorators-open", ".decorators": "folder-decorators-open", "_decorators": "folder-decorators-open", + "-decorators": "folder-decorators-open", "__decorators__": "folder-decorators-open", "java": "folder-java-open", ".java": "folder-java-open", "_java": "folder-java-open", + "-java": "folder-java-open", "__java__": "folder-java-open", "resolver": "folder-resolver-open", ".resolver": "folder-resolver-open", "_resolver": "folder-resolver-open", + "-resolver": "folder-resolver-open", "__resolver__": "folder-resolver-open", "resolvers": "folder-resolver-open", ".resolvers": "folder-resolver-open", "_resolvers": "folder-resolver-open", + "-resolvers": "folder-resolver-open", "__resolvers__": "folder-resolver-open", "angular": "folder-angular-open", ".angular": "folder-angular-open", "_angular": "folder-angular-open", + "-angular": "folder-angular-open", "__angular__": "folder-angular-open", "unity": "folder-unity-open", ".unity": "folder-unity-open", "_unity": "folder-unity-open", + "-unity": "folder-unity-open", "__unity__": "folder-unity-open", "pdf": "folder-pdf-open", ".pdf": "folder-pdf-open", "_pdf": "folder-pdf-open", + "-pdf": "folder-pdf-open", "__pdf__": "folder-pdf-open", "pdfs": "folder-pdf-open", ".pdfs": "folder-pdf-open", "_pdfs": "folder-pdf-open", + "-pdfs": "folder-pdf-open", "__pdfs__": "folder-pdf-open", "protobuf": "folder-proto-open", ".protobuf": "folder-proto-open", "_protobuf": "folder-proto-open", + "-protobuf": "folder-proto-open", "__protobuf__": "folder-proto-open", "protobufs": "folder-proto-open", ".protobufs": "folder-proto-open", "_protobufs": "folder-proto-open", + "-protobufs": "folder-proto-open", "__protobufs__": "folder-proto-open", "proto": "folder-proto-open", ".proto": "folder-proto-open", "_proto": "folder-proto-open", + "-proto": "folder-proto-open", "protos": "folder-proto-open", ".protos": "folder-proto-open", "_protos": "folder-proto-open", + "-protos": "folder-proto-open", "__protos__": "folder-proto-open", "plastic": "folder-plastic-open", ".plastic": "folder-plastic-open", "_plastic": "folder-plastic-open", + "-plastic": "folder-plastic-open", "__plastic__": "folder-plastic-open", "gamemaker": "folder-gamemaker-open", ".gamemaker": "folder-gamemaker-open", "_gamemaker": "folder-gamemaker-open", + "-gamemaker": "folder-gamemaker-open", "__gamemaker__": "folder-gamemaker-open", "gamemaker2": "folder-gamemaker-open", ".gamemaker2": "folder-gamemaker-open", "_gamemaker2": "folder-gamemaker-open", + "-gamemaker2": "folder-gamemaker-open", "__gamemaker2__": "folder-gamemaker-open", "hg": "folder-mercurial-open", ".hg": "folder-mercurial-open", "_hg": "folder-mercurial-open", + "-hg": "folder-mercurial-open", "__hg__": "folder-mercurial-open", "hghooks": "folder-mercurial-open", ".hghooks": "folder-mercurial-open", "_hghooks": "folder-mercurial-open", + "-hghooks": "folder-mercurial-open", "__hghooks__": "folder-mercurial-open", "hgext": "folder-mercurial-open", ".hgext": "folder-mercurial-open", "_hgext": "folder-mercurial-open", + "-hgext": "folder-mercurial-open", "__hgext__": "folder-mercurial-open", "godot": "folder-godot-open", ".godot": "folder-godot-open", "_godot": "folder-godot-open", + "-godot": "folder-godot-open", "__godot__": "folder-godot-open", "godot-cpp": "folder-godot-open", ".godot-cpp": "folder-godot-open", "_godot-cpp": "folder-godot-open", + "-godot-cpp": "folder-godot-open", "__godot-cpp__": "folder-godot-open", "lottie": "folder-lottie-open", ".lottie": "folder-lottie-open", "_lottie": "folder-lottie-open", + "-lottie": "folder-lottie-open", "__lottie__": "folder-lottie-open", "lotties": "folder-lottie-open", ".lotties": "folder-lottie-open", "_lotties": "folder-lottie-open", + "-lotties": "folder-lottie-open", "__lotties__": "folder-lottie-open", "lottiefiles": "folder-lottie-open", ".lottiefiles": "folder-lottie-open", "_lottiefiles": "folder-lottie-open", + "-lottiefiles": "folder-lottie-open", "__lottiefiles__": "folder-lottie-open", "taskfile": "folder-taskfile-open", ".taskfile": "folder-taskfile-open", "_taskfile": "folder-taskfile-open", + "-taskfile": "folder-taskfile-open", "__taskfile__": "folder-taskfile-open", "taskfiles": "folder-taskfile-open", ".taskfiles": "folder-taskfile-open", "_taskfiles": "folder-taskfile-open", + "-taskfiles": "folder-taskfile-open", "__taskfiles__": "folder-taskfile-open", "drizzle": "folder-drizzle-open", ".drizzle": "folder-drizzle-open", "_drizzle": "folder-drizzle-open", + "-drizzle": "folder-drizzle-open", "__drizzle__": "folder-drizzle-open", "cloudflare": "folder-cloudflare-open", ".cloudflare": "folder-cloudflare-open", "_cloudflare": "folder-cloudflare-open", + "-cloudflare": "folder-cloudflare-open", "__cloudflare__": "folder-cloudflare-open", "seeds": "folder-seeders-open", ".seeds": "folder-seeders-open", "_seeds": "folder-seeders-open", + "-seeds": "folder-seeders-open", "__seeds__": "folder-seeders-open", "seeders": "folder-seeders-open", ".seeders": "folder-seeders-open", "_seeders": "folder-seeders-open", + "-seeders": "folder-seeders-open", "__seeders__": "folder-seeders-open", "seed": "folder-seeders-open", ".seed": "folder-seeders-open", "_seed": "folder-seeders-open", + "-seed": "folder-seeders-open", "__seed__": "folder-seeders-open", "seeding": "folder-seeders-open", ".seeding": "folder-seeders-open", "_seeding": "folder-seeders-open", + "-seeding": "folder-seeders-open", "__seeding__": "folder-seeders-open", "store": "folder-store-open", ".store": "folder-store-open", "_store": "folder-store-open", + "-store": "folder-store-open", "__store__": "folder-store-open", "stores": "folder-store-open", ".stores": "folder-store-open", "_stores": "folder-store-open", + "-stores": "folder-store-open", "__stores__": "folder-store-open", "bicep": "folder-bicep-open", ".bicep": "folder-bicep-open", "_bicep": "folder-bicep-open", + "-bicep": "folder-bicep-open", "__bicep__": "folder-bicep-open", "snap": "folder-snapcraft-open", ".snap": "folder-snapcraft-open", "_snap": "folder-snapcraft-open", + "-snap": "folder-snapcraft-open", "__snap__": "folder-snapcraft-open", "snapcraft": "folder-snapcraft-open", ".snapcraft": "folder-snapcraft-open", "_snapcraft": "folder-snapcraft-open", + "-snapcraft": "folder-snapcraft-open", "__snapcraft__": "folder-snapcraft-open", "dev": "folder-development-open", ".dev": "folder-development-open", "_dev": "folder-development-open", + "-dev": "folder-development-open", "__dev__": "folder-development-open", "development": "folder-development-open", ".development": "folder-development-open", "_development": "folder-development-open", + "-development": "folder-development-open", "__development__": "folder-development-open", "flutter": "folder-flutter-open", ".flutter": "folder-flutter-open", "_flutter": "folder-flutter-open", + "-flutter": "folder-flutter-open", "__flutter__": "folder-flutter-open", "snippet": "folder-snippet-open", ".snippet": "folder-snippet-open", "_snippet": "folder-snippet-open", + "-snippet": "folder-snippet-open", "__snippet__": "folder-snippet-open", "snippets": "folder-snippet-open", ".snippets": "folder-snippet-open", "_snippets": "folder-snippet-open", + "-snippets": "folder-snippet-open", "__snippets__": "folder-snippet-open", "element": "folder-element-open", ".element": "folder-element-open", "_element": "folder-element-open", + "-element": "folder-element-open", "__element__": "folder-element-open", "elements": "folder-element-open", ".elements": "folder-element-open", "_elements": "folder-element-open", + "-elements": "folder-element-open", "__elements__": "folder-element-open", "src-tauri": "folder-src-tauri-open", ".src-tauri": "folder-src-tauri-open", "_src-tauri": "folder-src-tauri-open", + "-src-tauri": "folder-src-tauri-open", "__src-tauri__": "folder-src-tauri-open", "favicon": "folder-favicon-open", ".favicon": "folder-favicon-open", "_favicon": "folder-favicon-open", + "-favicon": "folder-favicon-open", "__favicon__": "folder-favicon-open", "favicons": "folder-favicon-open", ".favicons": "folder-favicon-open", "_favicons": "folder-favicon-open", + "-favicons": "folder-favicon-open", "__favicons__": "folder-favicon-open", "feature": "folder-features-open", ".feature": "folder-features-open", "_feature": "folder-features-open", + "-feature": "folder-features-open", "__feature__": "folder-features-open", "features": "folder-features-open", ".features": "folder-features-open", "_features": "folder-features-open", + "-features": "folder-features-open", "__features__": "folder-features-open", "feat": "folder-features-open", ".feat": "folder-features-open", "_feat": "folder-features-open", + "-feat": "folder-features-open", "__feat__": "folder-features-open", "feats": "folder-features-open", ".feats": "folder-features-open", "_feats": "folder-features-open", + "-feats": "folder-features-open", "__feats__": "folder-features-open", "lefthook": "folder-lefthook-open", ".lefthook": "folder-lefthook-open", "_lefthook": "folder-lefthook-open", + "-lefthook": "folder-lefthook-open", "__lefthook__": "folder-lefthook-open", "lefthook-local": "folder-lefthook-open", ".lefthook-local": "folder-lefthook-open", "_lefthook-local": "folder-lefthook-open", + "-lefthook-local": "folder-lefthook-open", "__lefthook-local__": "folder-lefthook-open", "bloc": "folder-bloc-open", ".bloc": "folder-bloc-open", "_bloc": "folder-bloc-open", + "-bloc": "folder-bloc-open", "__bloc__": "folder-bloc-open", "cubit": "folder-bloc-open", ".cubit": "folder-bloc-open", "_cubit": "folder-bloc-open", + "-cubit": "folder-bloc-open", "__cubit__": "folder-bloc-open", "blocs": "folder-bloc-open", ".blocs": "folder-bloc-open", "_blocs": "folder-bloc-open", + "-blocs": "folder-bloc-open", "__blocs__": "folder-bloc-open", "cubits": "folder-bloc-open", ".cubits": "folder-bloc-open", "_cubits": "folder-bloc-open", + "-cubits": "folder-bloc-open", "__cubits__": "folder-bloc-open", "powershell": "folder-powershell-open", ".powershell": "folder-powershell-open", "_powershell": "folder-powershell-open", + "-powershell": "folder-powershell-open", "__powershell__": "folder-powershell-open", "ps": "folder-powershell-open", ".ps": "folder-powershell-open", "_ps": "folder-powershell-open", + "-ps": "folder-powershell-open", "__ps__": "folder-powershell-open", "ps1": "folder-powershell-open", ".ps1": "folder-powershell-open", "_ps1": "folder-powershell-open", + "-ps1": "folder-powershell-open", "__ps1__": "folder-powershell-open", "repository": "folder-repository-open", ".repository": "folder-repository-open", "_repository": "folder-repository-open", + "-repository": "folder-repository-open", "__repository__": "folder-repository-open", "repositories": "folder-repository-open", ".repositories": "folder-repository-open", "_repositories": "folder-repository-open", + "-repositories": "folder-repository-open", "__repositories__": "folder-repository-open", "repo": "folder-repository-open", ".repo": "folder-repository-open", "_repo": "folder-repository-open", + "-repo": "folder-repository-open", "__repo__": "folder-repository-open", "repos": "folder-repository-open", ".repos": "folder-repository-open", "_repos": "folder-repository-open", + "-repos": "folder-repository-open", "__repos__": "folder-repository-open", "luau": "folder-luau-open", ".luau": "folder-luau-open", "_luau": "folder-luau-open", + "-luau": "folder-luau-open", "__luau__": "folder-luau-open", "obsidian": "folder-obsidian-open", ".obsidian": "folder-obsidian-open", "_obsidian": "folder-obsidian-open", + "-obsidian": "folder-obsidian-open", "__obsidian__": "folder-obsidian-open", "trash": "folder-trash-open", ".trash": "folder-trash-open", "_trash": "folder-trash-open", + "-trash": "folder-trash-open", "__trash__": "folder-trash-open", "cline_docs": "folder-cline-open", ".cline_docs": "folder-cline-open", "_cline_docs": "folder-cline-open", + "-cline_docs": "folder-cline-open", "__cline_docs__": "folder-cline-open", "liquibase": "folder-liquibase-open", ".liquibase": "folder-liquibase-open", "_liquibase": "folder-liquibase-open", + "-liquibase": "folder-liquibase-open", "__liquibase__": "folder-liquibase-open", "dart": "folder-dart-open", ".dart": "folder-dart-open", "_dart": "folder-dart-open", + "-dart": "folder-dart-open", "__dart__": "folder-dart-open", "dart_tool": "folder-dart-open", ".dart_tool": "folder-dart-open", "_dart_tool": "folder-dart-open", + "-dart_tool": "folder-dart-open", "__dart_tool__": "folder-dart-open", "dart_tools": "folder-dart-open", ".dart_tools": "folder-dart-open", "_dart_tools": "folder-dart-open", + "-dart_tools": "folder-dart-open", "__dart_tools__": "folder-dart-open", "zeabur": "folder-zeabur-open", ".zeabur": "folder-zeabur-open", "_zeabur": "folder-zeabur-open", + "-zeabur": "folder-zeabur-open", "__zeabur__": "folder-zeabur-open", "kusto": "folder-kusto-open", ".kusto": "folder-kusto-open", "_kusto": "folder-kusto-open", + "-kusto": "folder-kusto-open", "__kusto__": "folder-kusto-open", "kql": "folder-kusto-open", ".kql": "folder-kusto-open", "_kql": "folder-kusto-open", + "-kql": "folder-kusto-open", "__kql__": "folder-kusto-open", "policy": "folder-policy-open", ".policy": "folder-policy-open", "_policy": "folder-policy-open", + "-policy": "folder-policy-open", "__policy__": "folder-policy-open", "policies": "folder-policy-open", ".policies": "folder-policy-open", "_policies": "folder-policy-open", + "-policies": "folder-policy-open", "__policies__": "folder-policy-open", "attachment": "folder-attachment-open", ".attachment": "folder-attachment-open", "_attachment": "folder-attachment-open", + "-attachment": "folder-attachment-open", "__attachment__": "folder-attachment-open", "attachments": "folder-attachment-open", ".attachments": "folder-attachment-open", "_attachments": "folder-attachment-open", + "-attachments": "folder-attachment-open", "__attachments__": "folder-attachment-open", "bibliography": "folder-bibliography-open", ".bibliography": "folder-bibliography-open", "_bibliography": "folder-bibliography-open", + "-bibliography": "folder-bibliography-open", "__bibliography__": "folder-bibliography-open", "bibliographies": "folder-bibliography-open", ".bibliographies": "folder-bibliography-open", "_bibliographies": "folder-bibliography-open", + "-bibliographies": "folder-bibliography-open", "__bibliographies__": "folder-bibliography-open", "book": "folder-bibliography-open", ".book": "folder-bibliography-open", "_book": "folder-bibliography-open", + "-book": "folder-bibliography-open", "__book__": "folder-bibliography-open", "books": "folder-bibliography-open", ".books": "folder-bibliography-open", "_books": "folder-bibliography-open", + "-books": "folder-bibliography-open", "__books__": "folder-bibliography-open", "link": "folder-link-open", ".link": "folder-link-open", "_link": "folder-link-open", + "-link": "folder-link-open", "__link__": "folder-link-open", "links": "folder-link-open", ".links": "folder-link-open", "_links": "folder-link-open", + "-links": "folder-link-open", "__links__": "folder-link-open", "pytorch": "folder-pytorch-open", ".pytorch": "folder-pytorch-open", "_pytorch": "folder-pytorch-open", + "-pytorch": "folder-pytorch-open", "__pytorch__": "folder-pytorch-open", "torch": "folder-pytorch-open", ".torch": "folder-pytorch-open", "_torch": "folder-pytorch-open", + "-torch": "folder-pytorch-open", "__torch__": "folder-pytorch-open", "blender": "folder-blender-open", ".blender": "folder-blender-open", "_blender": "folder-blender-open", + "-blender": "folder-blender-open", "__blender__": "folder-blender-open", "blender-assets": "folder-blender-open", ".blender-assets": "folder-blender-open", "_blender-assets": "folder-blender-open", + "-blender-assets": "folder-blender-open", "__blender-assets__": "folder-blender-open", "blender-files": "folder-blender-open", ".blender-files": "folder-blender-open", "_blender-files": "folder-blender-open", + "-blender-files": "folder-blender-open", "__blender-files__": "folder-blender-open", "blender-project": "folder-blender-open", ".blender-project": "folder-blender-open", "_blender-project": "folder-blender-open", + "-blender-project": "folder-blender-open", "__blender-project__": "folder-blender-open", "blender-models": "folder-blender-open", ".blender-models": "folder-blender-open", "_blender-models": "folder-blender-open", + "-blender-models": "folder-blender-open", "__blender-models__": "folder-blender-open", "atoms": "folder-atom-open", ".atoms": "folder-atom-open", "_atoms": "folder-atom-open", + "-atoms": "folder-atom-open", "__atoms__": "folder-atom-open", "atom": "folder-atom-open", ".atom": "folder-atom-open", "_atom": "folder-atom-open", + "-atom": "folder-atom-open", "__atom__": "folder-atom-open", "molecules": "folder-molecule-open", ".molecules": "folder-molecule-open", "_molecules": "folder-molecule-open", + "-molecules": "folder-molecule-open", "__molecules__": "folder-molecule-open", "molecule": "folder-molecule-open", ".molecule": "folder-molecule-open", "_molecule": "folder-molecule-open", + "-molecule": "folder-molecule-open", "__molecule__": "folder-molecule-open", "organisms": "folder-organism-open", ".organisms": "folder-organism-open", "_organisms": "folder-organism-open", + "-organisms": "folder-organism-open", "__organisms__": "folder-organism-open", "organism": "folder-organism-open", ".organism": "folder-organism-open", "_organism": "folder-organism-open", + "-organism": "folder-organism-open", "__organism__": "folder-organism-open", ".claude": "folder-claude-open", "..claude": "folder-claude-open", "_.claude": "folder-claude-open", + "-.claude": "folder-claude-open", "__.claude__": "folder-claude-open", ".cursor": "folder-cursor-open", "..cursor": "folder-cursor-open", "_.cursor": "folder-cursor-open", + "-.cursor": "folder-cursor-open", "__.cursor__": "folder-cursor-open", ".gemini": "folder-gemini-ai-open", "..gemini": "folder-gemini-ai-open", "_.gemini": "folder-gemini-ai-open", + "-.gemini": "folder-gemini-ai-open", "__.gemini__": "folder-gemini-ai-open", + "gemini": "folder-gemini-ai-open", + "_gemini": "folder-gemini-ai-open", + "-gemini": "folder-gemini-ai-open", + "__gemini__": "folder-gemini-ai-open", + "gemini-ai": "folder-gemini-ai-open", + ".gemini-ai": "folder-gemini-ai-open", + "_gemini-ai": "folder-gemini-ai-open", + "-gemini-ai": "folder-gemini-ai-open", + "__gemini-ai__": "folder-gemini-ai-open", + "geminiai": "folder-gemini-ai-open", + ".geminiai": "folder-gemini-ai-open", + "_geminiai": "folder-gemini-ai-open", + "-geminiai": "folder-gemini-ai-open", + "__geminiai__": "folder-gemini-ai-open", "input": "folder-input-open", ".input": "folder-input-open", "_input": "folder-input-open", + "-input": "folder-input-open", "__input__": "folder-input-open", "inputs": "folder-input-open", ".inputs": "folder-input-open", "_inputs": "folder-input-open", + "-inputs": "folder-input-open", "__inputs__": "folder-input-open", "io": "folder-input-open", ".io": "folder-input-open", "_io": "folder-input-open", + "-io": "folder-input-open", "__io__": "folder-input-open", "in": "folder-input-open", ".in": "folder-input-open", "_in": "folder-input-open", + "-in": "folder-input-open", "__in__": "folder-input-open", "salt": "folder-salt-open", ".salt": "folder-salt-open", "_salt": "folder-salt-open", + "-salt": "folder-salt-open", "__salt__": "folder-salt-open", "saltstack": "folder-salt-open", ".saltstack": "folder-salt-open", "_saltstack": "folder-salt-open", + "-saltstack": "folder-salt-open", "__saltstack__": "folder-salt-open", "simulations": "folder-simulations-open", ".simulations": "folder-simulations-open", "_simulations": "folder-simulations-open", + "-simulations": "folder-simulations-open", "__simulations__": "folder-simulations-open", "simulation": "folder-simulations-open", ".simulation": "folder-simulations-open", "_simulation": "folder-simulations-open", + "-simulation": "folder-simulations-open", "__simulation__": "folder-simulations-open", "sim": "folder-simulations-open", ".sim": "folder-simulations-open", "_sim": "folder-simulations-open", + "-sim": "folder-simulations-open", "__sim__": "folder-simulations-open", "sims": "folder-simulations-open", ".sims": "folder-simulations-open", "_sims": "folder-simulations-open", + "-sims": "folder-simulations-open", "__sims__": "folder-simulations-open", "metro": "folder-metro-open", ".metro": "folder-metro-open", "_metro": "folder-metro-open", + "-metro": "folder-metro-open", "__metro__": "folder-metro-open", "filter": "folder-filter-open", ".filter": "folder-filter-open", "_filter": "folder-filter-open", + "-filter": "folder-filter-open", "__filter__": "folder-filter-open", "filters": "folder-filter-open", ".filters": "folder-filter-open", "_filters": "folder-filter-open", + "-filters": "folder-filter-open", "__filters__": "folder-filter-open", "toc": "folder-toc-open", ".toc": "folder-toc-open", "_toc": "folder-toc-open", + "-toc": "folder-toc-open", "__toc__": "folder-toc-open", "table-of-contents": "folder-toc-open", ".table-of-contents": "folder-toc-open", "_table-of-contents": "folder-toc-open", + "-table-of-contents": "folder-toc-open", "__table-of-contents__": "folder-toc-open", "cue": "folder-cue-open", ".cue": "folder-cue-open", "_cue": "folder-cue-open", + "-cue": "folder-cue-open", "__cue__": "folder-cue-open", "cues": "folder-cue-open", ".cues": "folder-cue-open", "_cues": "folder-cue-open", + "-cues": "folder-cue-open", "__cues__": "folder-cue-open", "license": "folder-license-open", ".license": "folder-license-open", "_license": "folder-license-open", + "-license": "folder-license-open", "__license__": "folder-license-open", "licenses": "folder-license-open", ".licenses": "folder-license-open", "_licenses": "folder-license-open", - "__licenses__": "folder-license-open" + "-licenses": "folder-license-open", + "__licenses__": "folder-license-open", + "form": "folder-form-open", + ".form": "folder-form-open", + "_form": "folder-form-open", + "-form": "folder-form-open", + "__form__": "folder-form-open", + "forms": "folder-form-open", + ".forms": "folder-form-open", + "_forms": "folder-form-open", + "-forms": "folder-form-open", + "__forms__": "folder-form-open" }, "rootFolderNames": {}, "rootFolderNamesExpanded": {}, @@ -7212,6 +9098,7 @@ "jrxml": "xml", "xmp": "xml", "toml": "toml", + "toon": "toon", "png": "image", "jpeg": "image", "jpg": "image", @@ -7934,6 +9821,7 @@ "story.tsx": "storybook", "stories.svelte": "storybook", "story.mdx": "storybook", + "stories.vue": "storybook", "wpy": "wepy", "hcl": "hcl", "san": "san", @@ -8317,6 +10205,7 @@ "cue": "cue", "lean": "lean", "sls": "salt", + "m2": "macaulay2", "cljx": "clojure", "clojure": "clojure", "edn": "clojure", @@ -8574,6 +10463,8 @@ ".clang-tidy": "settings", ".conf": "settings", "compile_flags.txt": "settings", + "typedoc.js": "typedoc", + "typedoc.json": "typedoc", "markdoc.config.js": "markdoc-config", "markdoc.config.mjs": "markdoc-config", "markdoc.config.cjs": "markdoc-config", @@ -10294,6 +12185,8 @@ "panda.config.cts": "panda", "biome.json": "biome", "biome.jsonc": "biome", + ".biome.json": "biome", + ".biome.jsonc": "biome", "esbuild.js": "esbuild", "esbuild.mjs": "esbuild", "esbuild.cjs": "esbuild", @@ -10533,6 +12426,7 @@ ".oxlintrc.jsonc": "oxc", ".oxfmtrc.json": "oxc", ".oxfmtrc.jsonc": "oxc", + "oxlint.config.ts": "oxc", "claude.md": "claude", "claude.local.md": "claude", ".cursorignore": "cursor", @@ -10979,44 +12873,54 @@ "jinja": "folder-jinja_light", ".jinja": "folder-jinja_light", "_jinja": "folder-jinja_light", + "-jinja": "folder-jinja_light", "__jinja__": "folder-jinja_light", "jinja2": "folder-jinja_light", ".jinja2": "folder-jinja_light", "_jinja2": "folder-jinja_light", + "-jinja2": "folder-jinja_light", "__jinja2__": "folder-jinja_light", "j2": "folder-jinja_light", ".j2": "folder-jinja_light", "_j2": "folder-jinja_light", + "-j2": "folder-jinja_light", "__j2__": "folder-jinja_light", "idea": "folder-intellij_light", ".idea": "folder-intellij_light", "_idea": "folder-intellij_light", + "-idea": "folder-intellij_light", "__idea__": "folder-intellij_light", ".cursor": "folder-cursor_light", "..cursor": "folder-cursor_light", "_.cursor": "folder-cursor_light", + "-.cursor": "folder-cursor_light", "__.cursor__": "folder-cursor_light" }, "folderNamesExpanded": { "jinja": "folder-jinja-open_light", ".jinja": "folder-jinja-open_light", "_jinja": "folder-jinja-open_light", + "-jinja": "folder-jinja-open_light", "__jinja__": "folder-jinja-open_light", "jinja2": "folder-jinja-open_light", ".jinja2": "folder-jinja-open_light", "_jinja2": "folder-jinja-open_light", + "-jinja2": "folder-jinja-open_light", "__jinja2__": "folder-jinja-open_light", "j2": "folder-jinja-open_light", ".j2": "folder-jinja-open_light", "_j2": "folder-jinja-open_light", + "-j2": "folder-jinja-open_light", "__j2__": "folder-jinja-open_light", "idea": "folder-intellij-open_light", ".idea": "folder-intellij-open_light", "_idea": "folder-intellij-open_light", + "-idea": "folder-intellij-open_light", "__idea__": "folder-intellij-open_light", ".cursor": "folder-cursor-open_light", "..cursor": "folder-cursor-open_light", "_.cursor": "folder-cursor-open_light", + "-.cursor": "folder-cursor-open_light", "__.cursor__": "folder-cursor-open_light" }, "rootFolderNames": {}, diff --git a/options/fileicon/material-icon-svgs.json b/options/fileicon/material-icon-svgs.json index 6713c4afa9c..4e476816212 100644 --- a/options/fileicon/material-icon-svgs.json +++ b/options/fileicon/material-icon-svgs.json @@ -375,6 +375,8 @@ "folder-font": "", "folder-forgejo-open": "", "folder-forgejo": "", + "folder-form-open": "", + "folder-form": "", "folder-functions-open": "", "folder-functions": "", "folder-gamemaker-open": "", @@ -590,6 +592,8 @@ "folder-redux-selector.clone": "", "folder-redux-store-open.clone": "", "folder-redux-store.clone": "", + "folder-redux-toolkit-open.clone": "", + "folder-redux-toolkit.clone": "", "folder-repository-open": "", "folder-repository": "", "folder-resolver-open": "", @@ -860,6 +864,7 @@ "luau": "", "lynx": "", "lyric": "", + "macaulay2": "", "makefile": "", "markdoc-config": "", "markdoc": "", @@ -935,7 +940,7 @@ "opentofu": "", "opentofu_light": "", "otne": "", - "oxc": "", + "oxc": "", "packship": "", "palette": "", "panda": "", @@ -1122,6 +1127,7 @@ "todo": "", "toml": "", "toml_light": "", + "toon": "", "travis": "", "tree": "", "trigger": "", @@ -1133,6 +1139,7 @@ "turborepo_light": "", "twig": "", "twine": "", + "typedoc": "", "typescript-def": "", "typescript": "", "typst": "", @@ -1163,8 +1170,8 @@ "vim": "", "virtual": "", "visualstudio": "", - "vite": "", - "vitest": "", + "vite": "", + "vitest": "", "vlang": "", "vscode": "", "vue-config": "", diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 97e2ebe0d1d..27695a8f5ac 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -84,6 +84,7 @@ "save": "Save", "add": "Add", "add_all": "Add All", + "dismiss": "Dismiss", "remove": "Remove", "remove_all": "Remove All", "remove_label_str": "Remove item \"%s\"", @@ -224,7 +225,7 @@ "startpage.lightweight": "Lightweight", "startpage.lightweight_desc": "Gitea has low minimal requirements and can run on an inexpensive Raspberry Pi. Save your machine energy!", "startpage.license": "Open Source", - "startpage.license_desc": "Go get %[2]s! Join us by contributing to make this project even better. Don't be shy to be a contributor!", + "startpage.license_desc": "Go get %[2]s! Join us by contributing to make this project even better. Don't hesitate to contribute!", "install.install": "Installation", "install.installing_desc": "Installing now, please wait…", "install.title": "Initial Configuration", @@ -284,12 +285,6 @@ "install.register_confirm": "Require Email Confirmation to Register", "install.mail_notify": "Enable Email Notifications", "install.server_service_title": "Server and Third-Party Service Settings", - "install.offline_mode": "Enable Local Mode", - "install.offline_mode_popup": "Disable third-party content delivery networks and serve all resources locally.", - "install.disable_gravatar": "Disable Gravatar", - "install.disable_gravatar_popup": "Disable Gravatar and third-party avatar sources. A default avatar will be used unless a user locally uploads an avatar.", - "install.federated_avatar_lookup": "Enable Federated Avatars", - "install.federated_avatar_lookup_popup": "Enable federated avatar lookup using Libravatar.", "install.disable_registration": "Disable Self-Registration", "install.disable_registration_popup": "Disable user self-registration. Only administrators will be able to create new user accounts.", "install.allow_only_external_registration_popup": "Allow Registration Only Through External Services", @@ -871,7 +866,7 @@ "settings.permissions_list": "Permissions:", "settings.manage_oauth2_applications": "Manage OAuth2 Applications", "settings.edit_oauth2_application": "Edit OAuth2 Application", - "settings.oauth2_applications_desc": "OAuth2 applications enables your third-party application to securely authenticate users at this Gitea instance.", + "settings.oauth2_applications_desc": "OAuth2 applications enable your third-party application to securely authenticate users at this Gitea instance.", "settings.remove_oauth2_application": "Remove OAuth2 Application", "settings.remove_oauth2_application_desc": "Removing an OAuth2 application will revoke access to all signed access tokens. Continue?", "settings.remove_oauth2_application_success": "The application has been deleted.", @@ -890,7 +885,7 @@ "settings.oauth2_regenerate_secret_hint": "Lost your secret?", "settings.oauth2_client_secret_hint": "The secret will not be shown again after you leave or refresh this page. Please ensure that you have saved it.", "settings.oauth2_application_edit": "Edit", - "settings.oauth2_application_create_description": "OAuth2 applications gives your third-party application access to user accounts on this instance.", + "settings.oauth2_application_create_description": "OAuth2 applications give your third-party application access to user accounts on this instance.", "settings.oauth2_application_remove_description": "Removing an OAuth2 application will prevent it from accessing authorized user accounts on this instance. Continue?", "settings.oauth2_application_locked": "Gitea pre-registers some OAuth2 applications on startup if enabled in config. To prevent unexpected behavior, these can neither be edited nor removed. Please refer to the OAuth2 documentation for more information.", "settings.authorized_oauth2_applications": "Authorized OAuth2 Applications", @@ -1524,6 +1519,7 @@ "repo.issues.commented_at": "commented %s", "repo.issues.delete_comment_confirm": "Are you sure you want to delete this comment?", "repo.issues.context.copy_link": "Copy Link", + "repo.issues.context.copy_source": "Copy Source", "repo.issues.context.quote_reply": "Quote Reply", "repo.issues.context.reference_issue": "Reference in New Issue", "repo.issues.context.edit": "Edit", @@ -3192,7 +3188,6 @@ "admin.config.custom_conf": "Configuration File Path", "admin.config.custom_file_root_path": "Custom File Root Path", "admin.config.domain": "Server Domain", - "admin.config.offline_mode": "Local Mode", "admin.config.disable_router_log": "Disable Router Log", "admin.config.run_user": "Run As Username", "admin.config.run_mode": "Run Mode", @@ -3278,6 +3273,13 @@ "admin.config.cache_test_failed": "Failed to probe the cache: %v.", "admin.config.cache_test_slow": "Cache test successful, but response is slow: %s.", "admin.config.cache_test_succeeded": "Cache test successful, got a response in %s.", + "admin.config.common.start_time": "Start time", + "admin.config.common.end_time": "End time", + "admin.config.common.skip_time_check": "Leave time empty (clear the field) to skip time check", + "admin.config.instance_maintenance": "Instance Maintenance", + "admin.config.instance_maintenance_mode.admin_web_access_only": "Only allow admin to access the web UI", + "admin.config.instance_web_banner.enabled": "Show banner", + "admin.config.instance_web_banner.message_placeholder": "Banner message (supports markdown)", "admin.config.session_config": "Session Configuration", "admin.config.session_provider": "Session Provider", "admin.config.provider_config": "Provider Config", @@ -3288,7 +3290,7 @@ "admin.config.cookie_life_time": "Cookie Life Time", "admin.config.picture_config": "Picture and Avatar Configuration", "admin.config.picture_service": "Picture Service", - "admin.config.disable_gravatar": "Disable Gravatar", + "admin.config.enable_gravatar": "Enable Gravatar", "admin.config.enable_federated_avatar": "Enable Federated Avatars", "admin.config.open_with_editor_app_help": "The \"Open with\" editors for the clone menu. If left empty, the default will be used. Expand to see the default.", "admin.config.git_guide_remote_name": "Repository remote name for git commands in the guide", @@ -3672,6 +3674,8 @@ "actions.runners.reset_registration_token_confirm": "Would you like to invalidate the current token and generate a new one?", "actions.runners.reset_registration_token_success": "Runner registration token reset successfully", "actions.runs.all_workflows": "All Workflows", + "actions.runs.workflow_run_count_1": "%d workflow run", + "actions.runs.workflow_run_count_n": "%d workflow runs", "actions.runs.commit": "Commit", "actions.runs.scheduled": "Scheduled", "actions.runs.pushed_by": "pushed by", diff --git a/options/locale/locale_ga-IE.json b/options/locale/locale_ga-IE.json index ad00325b02a..4669252a6e8 100644 --- a/options/locale/locale_ga-IE.json +++ b/options/locale/locale_ga-IE.json @@ -84,6 +84,7 @@ "save": "Sábháil", "add": "Cuir", "add_all": "Cuir Gach", + "dismiss": "Díbhe", "remove": "Bain", "remove_all": "Bain Gach", "remove_label_str": "Bain mír “%s”", @@ -224,7 +225,7 @@ "startpage.lightweight": "Éadrom", "startpage.lightweight_desc": "Tá íosta riachtanais íseal ag Gitea agus is féidir leo rith ar Raspberry Pi saor. Sábháil fuinneamh do mheaisín!", "startpage.license": "Foinse Oscailte", - "startpage.license_desc": "Téigh go bhfaighidh %[2]s! Bí linn trí cur leis chun an tionscadal seo a fheabhsú fós. Ná bíodh cúthail ort a bheith i do rannpháirtí!", + "startpage.license_desc": "Téigh agus faigh %[2]s! Bí linn trí cur leis chun an tionscadal seo a dhéanamh níos fearr fós. Ná bíodh leisce ort cur leis!", "install.install": "Suiteáil", "install.installing_desc": "Suiteáil anois, fan go fóill…", "install.title": "Cumraíocht Tosaigh", @@ -284,12 +285,6 @@ "install.register_confirm": "Deimhniú Ríomhphoist a cheangal le Clárú", "install.mail_notify": "Cumasaigh Fógraí Ríomhphoist", "install.server_service_title": "Socruithe Freastalaí agus Seirbhíse Tríú Páirtí", - "install.offline_mode": "Cumasaigh Mód Áitiúil", - "install.offline_mode_popup": "Díchumasaigh líonraí seachadta ábhair tríú páirtí agus freastal ar na hacmhainní go léir go háitiúil.", - "install.disable_gravatar": "Díchumasaigh Gravatar", - "install.disable_gravatar_popup": "Díchumasaigh foinsí abhatár Gravatar agus tríú páirtí. Úsáidfear abhatár réamhshocraithe mura n-uaslódálann úsáideoir abhatár go háitiúil.", - "install.federated_avatar_lookup": "Cumasaigh Abhatáir Chónaidhme", - "install.federated_avatar_lookup_popup": "Cumasaigh cuardach avatar cónaidhme ag baint úsáide as Libravatar.", "install.disable_registration": "Díchumasaigh Féin-Chlárú", "install.disable_registration_popup": "Díchumasaigh féinchlárú úsáideora. Ní bheidh ach riarthóirí in ann cuntais úsáideora nua a chruthú.", "install.allow_only_external_registration_popup": "Ceadaigh Clárú Trí Sheirbhísí Seachtracha amháin", @@ -871,7 +866,7 @@ "settings.permissions_list": "Ceadanna:", "settings.manage_oauth2_applications": "Bainistigh Feidhmchláir OAuth2", "settings.edit_oauth2_application": "Cuir Feidhmchlár OAuth2 in eagar", - "settings.oauth2_applications_desc": "Cumasaíonn feidhmchláir OAuth2 d’fheidhmchlár tríú páirtí úsáideoirí a fhíordheimhniú go slán ag an ásc Gitea seo.", + "settings.oauth2_applications_desc": "Cuireann feidhmchláir OAuth2 ar chumas d’fheidhmchlár tríú páirtí úsáideoirí a fhíordheimhniú go slán ag an gcás Gitea seo.", "settings.remove_oauth2_application": "Bain Feidhmchlár OAuth2", "settings.remove_oauth2_application_desc": "Ag baint feidhmchlár OAuth2, cúlghairfear rochtain ar gach comhartha rochtana sínithe. Lean ar aghaidh?", "settings.remove_oauth2_application_success": "Scriosadh an feidhmchlár.", @@ -890,7 +885,7 @@ "settings.oauth2_regenerate_secret_hint": "Chaill tú do rún?", "settings.oauth2_client_secret_hint": "Ní thaispeánfar an rún arís tar éis duit an leathanach seo a fhágáil nó a athnuachan. Déan cinnte le do thoil gur shábháil tú é.", "settings.oauth2_application_edit": "Cuir in eagar", - "settings.oauth2_application_create_description": "Tugann feidhmchláir OAuth2 rochtain d'iarratas tríú páirtí ar chuntais úsáideora ar an gcás seo.", + "settings.oauth2_application_create_description": "Tugann feidhmchláir OAuth2 rochtain do d’fheidhmchlár tríú páirtí ar chuntais úsáideora ar an gcás seo.", "settings.oauth2_application_remove_description": "Cuirfear feidhmchlár OAuth2 a bhaint cosc air rochtain a fháil ar chuntais úsáideora údaraithe ar an gcás seo. Lean ar aghaidh?", "settings.oauth2_application_locked": "Réamhchláraíonn Gitea roinnt feidhmchlár OAuth2 ar thosú má tá sé cumasaithe i gcumraíocht. Chun iompar gan choinne a chosc, ní féidir iad seo a chur in eagar ná a bhaint. Féach do thoil do dhoiciméadú OAuth2 le haghaidh tuilleadh faisnéise.", "settings.authorized_oauth2_applications": "Feidhmchláir Údaraithe OAuth2", @@ -1524,6 +1519,7 @@ "repo.issues.commented_at": "trácht %s ", "repo.issues.delete_comment_confirm": "An bhfuil tú cinnte gur mhaith leat an trácht seo a scriosadh?", "repo.issues.context.copy_link": "Cóipeáil Nasc", + "repo.issues.context.copy_source": "Cóipeáil Foinse", "repo.issues.context.quote_reply": "Luaigh Freagra", "repo.issues.context.reference_issue": "Tagairt in Eagrán Nua", "repo.issues.context.edit": "Cuir in eagar", @@ -3192,7 +3188,6 @@ "admin.config.custom_conf": "Cosán Comhad Cumraíochta", "admin.config.custom_file_root_path": "Cosán Fréamh Comhad Saincheaptha", "admin.config.domain": "Fearann ​​Freastalaí", - "admin.config.offline_mode": "Mód Áitiúil", "admin.config.disable_router_log": "Díchumasaigh Loga an Ródaire", "admin.config.run_user": "Rith Mar Ainm úsáideora", "admin.config.run_mode": "Mód Rith", @@ -3278,6 +3273,13 @@ "admin.config.cache_test_failed": "Theip ar an taisce a thaiscéaladh: %v.", "admin.config.cache_test_slow": "D'éirigh leis an tástáil taisce, ach tá an freagra mall: %s.", "admin.config.cache_test_succeeded": "D'éirigh leis an tástáil taisce, fuair sé freagra i %s.", + "admin.config.common.start_time": "Am tosaithe", + "admin.config.common.end_time": "Am deiridh", + "admin.config.common.skip_time_check": "Fág an t-am folamh (glan an réimse) chun seiceáil ama a scipeáil", + "admin.config.instance_maintenance": "Cothabháil Cásanna", + "admin.config.instance_maintenance_mode.admin_web_access_only": "Lig don riarthóir amháin rochtain a fháil ar chomhéadan gréasáin", + "admin.config.instance_web_banner.enabled": "Taispeáin meirge", + "admin.config.instance_web_banner.message_placeholder": "Teachtaireacht meirge (tacaíonn sé le Markdown)", "admin.config.session_config": "Cumraíocht Seisiúin", "admin.config.session_provider": "Soláthraí Seisiúin", "admin.config.provider_config": "Cumraíocht Soláthraí", @@ -3288,7 +3290,7 @@ "admin.config.cookie_life_time": "Am Saoil Fianán", "admin.config.picture_config": "Cumraíocht Pictiúr agus Avatar", "admin.config.picture_service": "Seirbhís Pictiúr", - "admin.config.disable_gravatar": "Díchumasaigh Gravatar", + "admin.config.enable_gravatar": "Cumasaigh Gravatar", "admin.config.enable_federated_avatar": "Cumasaigh Avatars Cónaidhme", "admin.config.open_with_editor_app_help": "Na heagarthóirí \"Oscailte le\" don roghchlár Clón. Má fhágtar folamh é, úsáidfear an réamhshocrú. Leathnaigh chun an réamhshocrú a fheiceáil.", "admin.config.git_guide_remote_name": "Ainm iargúlta stórais le haghaidh orduithe git sa treoir", @@ -3672,6 +3674,8 @@ "actions.runners.reset_registration_token_confirm": "Ar mhaith leat an comhartha reatha a neamhbhailiú agus ceann nua a ghiniúint?", "actions.runners.reset_registration_token_success": "D'éirigh le hathshocrú comhartha clárúcháin an dara háit", "actions.runs.all_workflows": "Gach Sreafaí Oibre", + "actions.runs.workflow_run_count_1": "%d rith sreabha oibre", + "actions.runs.workflow_run_count_n": "%d rith sreabha oibre", "actions.runs.commit": "Tiomantas", "actions.runs.scheduled": "Sceidealaithe", "actions.runs.pushed_by": "bhrú ag", diff --git a/options/locale/locale_zh-CN.json b/options/locale/locale_zh-CN.json index 861090cea71..2f0dced6e0b 100644 --- a/options/locale/locale_zh-CN.json +++ b/options/locale/locale_zh-CN.json @@ -84,6 +84,7 @@ "save": "保存", "add": "添加", "add_all": "添加所有", + "dismiss": "忽略", "remove": "移除", "remove_all": "移除所有", "remove_label_str": "删除标签「%s」", @@ -216,15 +217,15 @@ "error.report_message": "如果您确定这是一个 Gitea bug,请在 这里 搜索问题,或在必要时创建一个新工单。", "error.not_found": "找不到目标。", "error.network_error": "网络错误", - "startpage.app_desc": "一款极易搭建的自助 Git 服务", + "startpage.app_desc": "一款极易搭建的自托管 Git 服务", "startpage.install": "易安装", - "startpage.install_desc": "通过 二进制 来运行;或者通过 Docker 来运行;或者通过 安装包 来运行。", + "startpage.install_desc": "直接运行适合您平台的二进制文件,通过 Docker 部署,或使用软件包安装。", "startpage.platform": "跨平台", "startpage.platform_desc": "任何 Go 语言 支持的平台都可以运行 Gitea,包括 Windows、Mac、Linux 以及 ARM。挑一个您喜欢的就行!", "startpage.lightweight": "轻量级", - "startpage.lightweight_desc": "一个廉价的树莓派的配置足以满足 Gitea 的最低系统硬件要求。最大程度上节省您的服务器资源!", + "startpage.lightweight_desc": "一个廉价的树莓派就足以满足 Gitea 的最低系统硬件要求。最大程度节省您的服务器资源!", "startpage.license": "开源化", - "startpage.license_desc": "所有的代码都开源在 %[2]s 上,赶快加入我们来共同发展这个伟大的项目!还等什么?成为贡献者吧!", + "startpage.license_desc": "所有代码都开源在 %[2]s,还等什么?赶快加入我们来共同发展这个伟大的项目,成为贡献者吧!", "install.install": "安装页面", "install.installing_desc": "正在安装,请稍候…", "install.title": "初始配置", @@ -284,12 +285,6 @@ "install.register_confirm": "需要邮件确认注册", "install.mail_notify": "启用邮件通知提醒", "install.server_service_title": "服务器和第三方服务设置", - "install.offline_mode": "启用本地模式", - "install.offline_mode_popup": "禁用第三方 CDN 并在本地服务所有资源。", - "install.disable_gravatar": "禁用 Gravatar 头像", - "install.disable_gravatar_popup": "禁用 Gravatar 和第三方头像源。除非用户在本地上传头像,否则将使用默认头像。", - "install.federated_avatar_lookup": "启用 Federated 头像", - "install.federated_avatar_lookup_popup": "启用 Federated 头像查找以使用开源的 Libravatar 服务。", "install.disable_registration": "禁止用户自助注册", "install.disable_registration_popup": "禁用用户自助注册。只有管理员才能创建新的用户帐户。", "install.allow_only_external_registration_popup": "仅允许通过外部服务注册", @@ -1877,6 +1872,7 @@ "repo.pulls.update_not_allowed": "您无权更新分支", "repo.pulls.outdated_with_base_branch": "此分支相比基础分支已过期", "repo.pulls.close": "关闭合并请求", + "repo.pulls.reopen": "重新打开", "repo.pulls.closed_at": "于 %[2]s 关闭此合并请求 ", "repo.pulls.reopened_at": "于 %[2]s 重新打开此合并请求", "repo.pulls.cmd_instruction_hint": "查看命令行提示", @@ -3191,7 +3187,6 @@ "admin.config.custom_conf": "配置文件路径", "admin.config.custom_file_root_path": "自定义文件根路径", "admin.config.domain": "服务器域名", - "admin.config.offline_mode": "离线模式", "admin.config.disable_router_log": "关闭路由日志", "admin.config.run_user": "以用户名运行", "admin.config.run_mode": "运行模式", @@ -3277,6 +3272,13 @@ "admin.config.cache_test_failed": "缓存测试失败:%v。", "admin.config.cache_test_slow": "缓存测试成功,但响应缓慢:%s。", "admin.config.cache_test_succeeded": "缓存测试成功,在 %s 时间内得到响应。", + "admin.config.common.start_time": "开始时间", + "admin.config.common.end_time": "结束时间", + "admin.config.common.skip_time_check": "留空(清除)时间字段以跳过时间检查", + "admin.config.instance_maintenance": "实例维护", + "admin.config.instance_maintenance_mode.admin_web_access_only": "仅允许管理员访问 web 界面", + "admin.config.instance_web_banner.enabled": "显示横幅", + "admin.config.instance_web_banner.message_placeholder": "横幅消息(支持 Markdown)", "admin.config.session_config": "会话配置", "admin.config.session_provider": "会话提供者", "admin.config.provider_config": "提供者配置", @@ -3287,7 +3289,7 @@ "admin.config.cookie_life_time": "Cookie 生命周期", "admin.config.picture_config": "图片和头像配置", "admin.config.picture_service": "图片服务", - "admin.config.disable_gravatar": "禁用 Gravatar 头像", + "admin.config.enable_gravatar": "启用 Gravatar", "admin.config.enable_federated_avatar": "启用 Federated 头像", "admin.config.open_with_editor_app_help": "用于克隆菜单的编辑器。如果为空将使用默认值。展开可以查看默认值。", "admin.config.git_guide_remote_name": "指南中 git 命令使用的仓库远程名称", @@ -3694,6 +3696,7 @@ "actions.runs.delete.description": "您确定要永久删除此工作流运行吗?此操作无法撤消。", "actions.runs.not_done": "此工作流运行尚未完成。", "actions.runs.view_workflow_file": "查看工作流文件", + "actions.runs.workflow_graph": "工作流程图", "actions.workflow.disable": "禁用工作流", "actions.workflow.disable_success": "工作流「%s」已成功禁用。", "actions.workflow.enable": "启用工作流", diff --git a/package.json b/package.json index fac174242a4..d55223c9a8d 100644 --- a/package.json +++ b/package.json @@ -101,8 +101,8 @@ "globals": "17.3.0", "happy-dom": "20.7.0", "jiti": "2.6.1", - "markdownlint-cli": "0.47.0", - "material-icon-theme": "5.31.0", + "markdownlint-cli": "0.48.0", + "material-icon-theme": "5.32.0", "nolyfill": "1.0.44", "postcss-html": "1.8.1", "spectral-cli-bundle": "1.0.7", @@ -111,7 +111,7 @@ "stylelint-declaration-block-no-ignored-properties": "3.0.0", "stylelint-declaration-strict-value": "1.11.0", "stylelint-value-no-unknown-custom-properties": "6.1.1", - "svgo": "4.0.0", + "svgo": "4.0.1", "typescript": "5.9.3", "typescript-eslint": "8.56.0", "updates": "17.6.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 40f5def8550..c41bd3421cc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -307,11 +307,11 @@ importers: specifier: 2.6.1 version: 2.6.1 markdownlint-cli: - specifier: 0.47.0 - version: 0.47.0 + specifier: 0.48.0 + version: 0.48.0 material-icon-theme: - specifier: 5.31.0 - version: 5.31.0 + specifier: 5.32.0 + version: 5.32.0 nolyfill: specifier: 1.0.44 version: 1.0.44 @@ -337,8 +337,8 @@ importers: specifier: 6.1.1 version: 6.1.1(stylelint@17.3.0(typescript@5.9.3)) svgo: - specifier: 4.0.0 - version: 4.0.0 + specifier: 4.0.1 + version: 4.0.1 typescript: specifier: 5.9.3 version: 5.9.3 @@ -2007,6 +2007,10 @@ packages: resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + css-what@6.2.2: resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} engines: {node: '>= 6'} @@ -3117,8 +3121,8 @@ packages: resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==} hasBin: true - markdownlint-cli@0.47.0: - resolution: {integrity: sha512-HOcxeKFAdDoldvoYDofd85vI8LgNWy8vmYpCwnlLV46PJcodmGzD7COSSBlhHwsfT4o9KrAStGodImVBus31Bg==} + markdownlint-cli@0.48.0: + resolution: {integrity: sha512-NkZQNu2E0Q5qLEEHwWj674eYISTLD4jMHkBzDobujXd1kv+yCxi8jOaD/rZoQNW1FBBMMGQpuW5So8B51N/e0A==} engines: {node: '>=20'} hasBin: true @@ -3141,8 +3145,8 @@ packages: engines: {node: '>= 12'} hasBin: true - material-icon-theme@5.31.0: - resolution: {integrity: sha512-PPeGSRa+8stQEKvCr2Xym9KIqf2SPwl1chc7cxbK+aY6ORpwOcowtARQEXstZBjQwXTE5GnfE0zg0MFFy+XPzA==} + material-icon-theme@5.32.0: + resolution: {integrity: sha512-SxJxCcnk6cJIbd+AxmoeghXJ24joXGmUzjiGci16sX4mXZdXprGEzM6ZZ0VHGAofxNlMqznEbExINwFLsxf8eQ==} engines: {vscode: ^1.55.0} mathml-tag-names@4.0.0: @@ -3154,6 +3158,9 @@ packages: mdn-data@2.12.2: resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} @@ -3264,23 +3271,19 @@ packages: peerDependencies: webpack: ^5.0.0 - minimatch@10.1.3: - resolution: {integrity: sha512-IF6URNyBX7Z6XfvjpaNy5meRxPZiIf2OqtOoSLs+hLJ9pJAScnM1RjrFcbCaD85y42KcI+oZmKjFIJKYDFjQfg==} - engines: {node: 20 || >=22} - minimatch@10.2.1: resolution: {integrity: sha512-MClCe8IL5nRRmawL6ib/eT4oLyeKMGCghibcDWK+J0hh0Q8kqSdia6BvbRMVk6mPa6WqUa5uR2oxt6C5jd533A==} engines: {node: 20 || >=22} - minimatch@10.2.2: - resolution: {integrity: sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==} + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} - minimatch@3.1.3: - resolution: {integrity: sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - minimatch@9.0.6: - resolution: {integrity: sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==} + minimatch@9.0.8: + resolution: {integrity: sha512-reYkDYtj/b19TeqbNZCV4q9t+Yxylf/rYBsLb42SXJatTv4/ylq5lEiAmhA/IToxO7NI2UzNMghHoHuaqDkAjw==} engines: {node: '>=16 || 14 >=14.17'} minimist@1.2.8: @@ -3715,8 +3718,8 @@ packages: sax@1.2.4: resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} - sax@1.4.4: - resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} + sax@1.5.0: + resolution: {integrity: sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==} engines: {node: '>=11.0.0'} schema-utils@4.3.3: @@ -3776,8 +3779,8 @@ packages: resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} engines: {node: '>=10'} - smol-toml@1.5.2: - resolution: {integrity: sha512-QlaZEqcAH3/RtNyet1IPIYPsEWAaYyXXv1Krsi+1L/QHppjX4Ifm8MQsBISz9vE8cHicIq3clogsheili5vhaQ==} + smol-toml@1.6.0: + resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==} engines: {node: '>= 18'} solid-js@1.9.11: @@ -3870,6 +3873,10 @@ packages: resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} @@ -3955,8 +3962,8 @@ packages: svg-tags@1.0.0: resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} - svgo@4.0.0: - resolution: {integrity: sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==} + svgo@4.0.1: + resolution: {integrity: sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==} engines: {node: '>=16'} hasBin: true @@ -4654,7 +4661,7 @@ snapshots: dependencies: '@eslint/object-schema': 2.1.7 debug: 4.4.3 - minimatch: 3.1.3 + minimatch: 3.1.5 transitivePeerDependencies: - supports-color @@ -4675,7 +4682,7 @@ snapshots: ignore: 5.3.2 import-fresh: 3.3.1 js-yaml: 4.1.1 - minimatch: 3.1.3 + minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color @@ -4999,7 +5006,7 @@ snapshots: dependencies: glob: 7.2.3 lodash: 4.17.23 - minimatch: 3.1.3 + minimatch: 3.1.5 semver: 6.3.1 spdx-expression-validate: 2.0.0 spdx-satisfies: 5.0.1 @@ -5281,7 +5288,7 @@ snapshots: '@typescript-eslint/types': 8.56.0 '@typescript-eslint/visitor-keys': 8.56.0 debug: 4.4.3 - minimatch: 9.0.6 + minimatch: 9.0.8 semver: 7.7.4 tinyglobby: 0.2.15 ts-api-utils: 2.4.0(typescript@5.9.3) @@ -5917,6 +5924,11 @@ snapshots: mdn-data: 2.12.2 source-map-js: 1.2.1 + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + css-what@6.2.2: {} css@3.0.0: @@ -6395,7 +6407,7 @@ snapshots: eslint: 9.39.2(jiti@2.6.1) eslint-import-context: 0.1.9(unrs-resolver@1.11.1) is-glob: 4.0.3 - minimatch: 10.2.2 + minimatch: 10.2.4 semver: 7.7.4 stable-hash-x: 0.2.0 unrs-resolver: 1.11.1 @@ -6420,7 +6432,7 @@ snapshots: hasown: '@nolyfill/hasown@1.0.44' is-core-module: '@nolyfill/is-core-module@1.0.39' is-glob: 4.0.3 - minimatch: 3.1.3 + minimatch: 3.1.5 object.fromentries: '@nolyfill/object.fromentries@1.0.44' object.groupby: '@nolyfill/object.groupby@1.0.44' object.values: '@nolyfill/object.values@1.0.44' @@ -6448,7 +6460,7 @@ snapshots: hasown: '@nolyfill/hasown@1.0.44' jsx-ast-utils: 3.3.5 language-tags: 1.0.9 - minimatch: 3.1.3 + minimatch: 3.1.5 object.fromentries: '@nolyfill/object.fromentries@1.0.44' safe-regex-test: '@nolyfill/safe-regex-test@1.0.44' string.prototype.includes: '@nolyfill/string.prototype.includes@1.0.44' @@ -6610,7 +6622,7 @@ snapshots: is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 lodash.merge: 4.6.2 - minimatch: 3.1.3 + minimatch: 3.1.5 natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: @@ -6762,7 +6774,7 @@ snapshots: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.1.3 + minimatch: 3.1.5 once: 1.4.0 path-is-absolute: 1.0.1 @@ -7095,7 +7107,7 @@ snapshots: punycode.js: 2.3.1 uc.micro: 2.1.0 - markdownlint-cli@0.47.0: + markdownlint-cli@0.48.0: dependencies: commander: 14.0.3 deep-extend: 0.6.0 @@ -7105,9 +7117,9 @@ snapshots: jsonpointer: 5.0.1 markdown-it: 14.1.1 markdownlint: 0.40.0 - minimatch: 10.1.3 + minimatch: 10.2.4 run-con: 1.3.2 - smol-toml: 1.5.2 + smol-toml: 1.6.0 tinyglobby: 0.2.15 transitivePeerDependencies: - supports-color @@ -7132,7 +7144,7 @@ snapshots: marked@4.3.0: {} - material-icon-theme@5.31.0: + material-icon-theme@5.32.0: dependencies: chroma-js: 3.2.0 events: 3.3.0 @@ -7145,6 +7157,8 @@ snapshots: mdn-data@2.12.2: {} + mdn-data@2.27.1: {} + mdurl@2.0.0: {} meow@14.1.0: {} @@ -7365,23 +7379,19 @@ snapshots: tapable: 2.3.0 webpack: 5.105.2(webpack-cli@6.0.1) - minimatch@10.1.3: - dependencies: - brace-expansion: 5.0.3 - minimatch@10.2.1: dependencies: brace-expansion: 5.0.3 - minimatch@10.2.2: + minimatch@10.2.4: dependencies: brace-expansion: 5.0.3 - minimatch@3.1.3: + minimatch@3.1.5: dependencies: brace-expansion: 1.1.12 - minimatch@9.0.6: + minimatch@9.0.8: dependencies: brace-expansion: 5.0.3 @@ -7795,7 +7805,7 @@ snapshots: sax@1.2.4: {} - sax@1.4.4: {} + sax@1.5.0: {} schema-utils@4.3.3: dependencies: @@ -7846,7 +7856,7 @@ snapshots: astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 - smol-toml@1.5.2: {} + smol-toml@1.6.0: {} solid-js@1.9.11: dependencies: @@ -7926,7 +7936,7 @@ snapshots: string-width@8.1.0: dependencies: get-east-asian-width: 1.5.0 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 string-width@8.2.0: dependencies: @@ -7941,6 +7951,10 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + strip-bom@3.0.0: {} strip-indent@4.1.1: {} @@ -8058,15 +8072,15 @@ snapshots: svg-tags@1.0.0: {} - svgo@4.0.0: + svgo@4.0.1: dependencies: commander: 11.1.0 css-select: 5.2.2 - css-tree: 3.1.0 + css-tree: 3.2.1 css-what: 6.2.2 csso: 5.0.5 picocolors: 1.1.1 - sax: 1.4.4 + sax: 1.5.0 svgson@5.3.1: dependencies: diff --git a/routers/api/actions/artifacts.go b/routers/api/actions/artifacts.go index d71a6f487cb..6bef6c03c06 100644 --- a/routers/api/actions/artifacts.go +++ b/routers/api/actions/artifacts.go @@ -112,7 +112,7 @@ func ArtifactsRoutes(prefix string) *web.Router { m.Group(artifactRouteBase, func() { // retrieve, list and confirm artifacts - m.Combo("").Get(r.listArtifacts).Post(r.getUploadArtifactURL).Patch(r.comfirmUploadArtifact) + m.Combo("").Get(r.listArtifacts).Post(r.getUploadArtifactURL).Patch(r.confirmUploadArtifact) // handle container artifacts list and download m.Put("/{artifact_hash}/upload", r.uploadArtifact) // handle artifacts download @@ -241,7 +241,7 @@ func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) { } // get upload file size - fileRealTotalSize, contentLength := getUploadFileSize(ctx) + fileRealTotalSize := getUploadFileSize(ctx) // get artifact retention days expiredDays := setting.Actions.ArtifactRetentionDays @@ -265,17 +265,17 @@ func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) { return } - // save chunk to storage, if success, return chunk stotal size + // save chunk to storage, if success, return chunks total size // if artifact is not gzip when uploading, chunksTotalSize == fileRealTotalSize // if artifact is gzip when uploading, chunksTotalSize < fileRealTotalSize - chunksTotalSize, err := saveUploadChunk(ar.fs, ctx, artifact, contentLength, runID) + chunksTotalSize, err := saveUploadChunkV3GetTotalSize(ar.fs, ctx, artifact, runID) if err != nil { log.Error("Error save upload chunk: %v", err) ctx.HTTPError(http.StatusInternalServerError, "Error save upload chunk") return } - // update artifact size if zero or not match, over write artifact size + // update artifact size if zero or not match, overwrite artifact size if artifact.FileSize == 0 || artifact.FileCompressedSize == 0 || artifact.FileSize != fileRealTotalSize || @@ -297,9 +297,9 @@ func (ar artifactRoutes) uploadArtifact(ctx *ArtifactContext) { }) } -// comfirmUploadArtifact confirm upload artifact. +// confirmUploadArtifact confirm upload artifact. // if all chunks are uploaded, merge them to one file. -func (ar artifactRoutes) comfirmUploadArtifact(ctx *ArtifactContext) { +func (ar artifactRoutes) confirmUploadArtifact(ctx *ArtifactContext) { _, runID, ok := validateRunID(ctx) if !ok { return @@ -419,8 +419,8 @@ func (ar artifactRoutes) getDownloadArtifactURL(ctx *ArtifactContext) { } if itemPath != artifacts[0].ArtifactName { - log.Error("Error dismatch artifact name, itemPath: %v, artifact: %v", itemPath, artifacts[0].ArtifactName) - ctx.HTTPError(http.StatusBadRequest, "Error dismatch artifact name") + log.Error("Error mismatch artifact name, itemPath: %v, artifact: %v", itemPath, artifacts[0].ArtifactName) + ctx.HTTPError(http.StatusBadRequest, "Error mismatch artifact name") return } diff --git a/routers/api/actions/artifacts_chunks.go b/routers/api/actions/artifacts_chunks.go index 708931d1ac1..86a51d6ca64 100644 --- a/routers/api/actions/artifacts_chunks.go +++ b/routers/api/actions/artifacts_chunks.go @@ -12,7 +12,7 @@ import ( "fmt" "hash" "io" - "path/filepath" + "path" "sort" "strings" "time" @@ -20,18 +20,73 @@ import ( "code.gitea.io/gitea/models/actions" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" ) -func saveUploadChunkBase(st storage.ObjectStorage, ctx *ArtifactContext, - artifact *actions.ActionArtifact, - contentSize, runID, start, end, length int64, checkMd5 bool, -) (int64, error) { +type saveUploadChunkOptions struct { + start int64 + end *int64 + checkMd5 bool +} + +func makeTmpPathNameV3(runID int64) string { + return fmt.Sprintf("tmp-upload/run-%d", runID) +} + +func makeTmpPathNameV4(runID int64) string { + return fmt.Sprintf("tmp-upload/run-%d-v4", runID) +} + +func makeChunkFilenameV3(runID, artifactID, start int64, endPtr *int64) string { + var end int64 + if endPtr != nil { + end = *endPtr + } + return fmt.Sprintf("%d-%d-%d-%d.chunk", runID, artifactID, start, end) +} + +func parseChunkFileItemV3(st storage.ObjectStorage, fpath string) (*chunkFileItem, error) { + baseName := path.Base(fpath) + if !strings.HasSuffix(baseName, ".chunk") { + return nil, errSkipChunkFile + } + + var item chunkFileItem + var unusedRunID int64 + if _, err := fmt.Sscanf(baseName, "%d-%d-%d-%d.chunk", &unusedRunID, &item.ArtifactID, &item.Start, &item.End); err != nil { + return nil, err + } + + item.Path = fpath + if item.End == 0 { + fi, err := st.Stat(item.Path) + if err != nil { + return nil, err + } + item.Size = fi.Size() + item.End = item.Start + item.Size - 1 + } else { + item.Size = item.End - item.Start + 1 + } + return &item, nil +} + +func saveUploadChunkV3(st storage.ObjectStorage, ctx *ArtifactContext, artifact *actions.ActionArtifact, + runID int64, opts saveUploadChunkOptions, +) (writtenSize int64, retErr error) { // build chunk store path - storagePath := fmt.Sprintf("tmp%d/%d-%d-%d-%d.chunk", runID, runID, artifact.ID, start, end) + storagePath := fmt.Sprintf("%s/%s", makeTmpPathNameV3(runID), makeChunkFilenameV3(runID, artifact.ID, opts.start, opts.end)) + + // "end" is optional, so "contentSize=-1" means read until EOF + contentSize := int64(-1) + if opts.end != nil { + contentSize = *opts.end - opts.start + 1 + } + var r io.Reader = ctx.Req.Body var hasher hash.Hash - if checkMd5 { + if opts.checkMd5 { // use io.TeeReader to avoid reading all body to md5 sum. // it writes data to hasher after reading end // if hash is not matched, delete the read-end result @@ -41,76 +96,81 @@ func saveUploadChunkBase(st storage.ObjectStorage, ctx *ArtifactContext, // save chunk to storage writtenSize, err := st.Save(storagePath, r, contentSize) if err != nil { - return -1, fmt.Errorf("save chunk to storage error: %v", err) + return 0, fmt.Errorf("save chunk to storage error: %v", err) } - var checkErr error - if checkMd5 { + + defer func() { + if retErr != nil { + if err := st.Delete(storagePath); err != nil { + log.Error("Error deleting chunk: %s, %v", storagePath, err) + } + } + }() + + if contentSize != -1 && writtenSize != contentSize { + return writtenSize, fmt.Errorf("writtenSize %d does not match contentSize %d", writtenSize, contentSize) + } + if opts.checkMd5 { // check md5 reqMd5String := ctx.Req.Header.Get(artifactXActionsResultsMD5Header) chunkMd5String := base64.StdEncoding.EncodeToString(hasher.Sum(nil)) - log.Info("[artifact] check chunk md5, sum: %s, header: %s", chunkMd5String, reqMd5String) + log.Debug("[artifact] check chunk md5, sum: %s, header: %s", chunkMd5String, reqMd5String) // if md5 not match, delete the chunk if reqMd5String != chunkMd5String { - checkErr = errors.New("md5 not match") + return writtenSize, errors.New("md5 not match") } } - if writtenSize != contentSize { - checkErr = errors.Join(checkErr, fmt.Errorf("writtenSize %d not match contentSize %d", writtenSize, contentSize)) - } - if checkErr != nil { - if err := st.Delete(storagePath); err != nil { - log.Error("Error deleting chunk: %s, %v", storagePath, err) - } - return -1, checkErr - } - log.Info("[artifact] save chunk %s, size: %d, artifact id: %d, start: %d, end: %d", - storagePath, contentSize, artifact.ID, start, end) - // return chunk total size - return length, nil + log.Debug("[artifact] save chunk %s, size: %d, artifact id: %d, start: %d, size: %d", storagePath, writtenSize, artifact.ID, opts.start, contentSize) + return writtenSize, nil } -func saveUploadChunk(st storage.ObjectStorage, ctx *ArtifactContext, - artifact *actions.ActionArtifact, - contentSize, runID int64, -) (int64, error) { +func saveUploadChunkV3GetTotalSize(st storage.ObjectStorage, ctx *ArtifactContext, artifact *actions.ActionArtifact, runID int64) (totalSize int64, _ error) { // parse content-range header, format: bytes 0-1023/146515 contentRange := ctx.Req.Header.Get("Content-Range") - start, end, length := int64(0), int64(0), int64(0) - if _, err := fmt.Sscanf(contentRange, "bytes %d-%d/%d", &start, &end, &length); err != nil { - log.Warn("parse content range error: %v, content-range: %s", err, contentRange) - return -1, fmt.Errorf("parse content range error: %v", err) + var start, end int64 + if _, err := fmt.Sscanf(contentRange, "bytes %d-%d/%d", &start, &end, &totalSize); err != nil { + return 0, fmt.Errorf("parse content range error: %v", err) } - return saveUploadChunkBase(st, ctx, artifact, contentSize, runID, start, end, length, true) + _, err := saveUploadChunkV3(st, ctx, artifact, runID, saveUploadChunkOptions{start: start, end: &end, checkMd5: true}) + if err != nil { + return 0, err + } + return totalSize, nil } -func appendUploadChunk(st storage.ObjectStorage, ctx *ArtifactContext, - artifact *actions.ActionArtifact, - start, contentSize, runID int64, -) (int64, error) { - end := start + contentSize - 1 - return saveUploadChunkBase(st, ctx, artifact, contentSize, runID, start, end, contentSize, false) +// Returns uploaded length +func appendUploadChunkV3(st storage.ObjectStorage, ctx *ArtifactContext, artifact *actions.ActionArtifact, runID, start int64) (int64, error) { + opts := saveUploadChunkOptions{start: start} + if ctx.Req.ContentLength > 0 { + end := start + ctx.Req.ContentLength - 1 + opts.end = &end + } + return saveUploadChunkV3(st, ctx, artifact, runID, opts) } type chunkFileItem struct { - RunID int64 ArtifactID int64 - Start int64 - End int64 Path string + + // these offset/size related fields might be missing when parsing, they will be filled in the listing functions + Size int64 + Start int64 + End int64 // inclusive: Size=10, Start=0, End=9 + + ChunkName string // v4 only } -func listChunksByRunID(st storage.ObjectStorage, runID int64) (map[int64][]*chunkFileItem, error) { - storageDir := fmt.Sprintf("tmp%d", runID) +func listV3UnorderedChunksMapByRunID(st storage.ObjectStorage, runID int64) (map[int64][]*chunkFileItem, error) { + storageDir := makeTmpPathNameV3(runID) var chunks []*chunkFileItem if err := st.IterateObjects(storageDir, func(fpath string, obj storage.Object) error { - baseName := filepath.Base(fpath) - // when read chunks from storage, it only contains storage dir and basename, - // no matter the subdirectory setting in storage config - item := chunkFileItem{Path: storageDir + "/" + baseName} - if _, err := fmt.Sscanf(baseName, "%d-%d-%d-%d.chunk", &item.RunID, &item.ArtifactID, &item.Start, &item.End); err != nil { - return fmt.Errorf("parse content range error: %v", err) + item, err := parseChunkFileItemV3(st, fpath) + if errors.Is(err, errSkipChunkFile) { + return nil + } else if err != nil { + return fmt.Errorf("unable to parse chunk name: %v", fpath) } - chunks = append(chunks, &item) + chunks = append(chunks, item) return nil }); err != nil { return nil, err @@ -123,52 +183,78 @@ func listChunksByRunID(st storage.ObjectStorage, runID int64) (map[int64][]*chun return chunksMap, nil } -func listChunksByRunIDV4(st storage.ObjectStorage, runID, artifactID int64, blist *BlockList) ([]*chunkFileItem, error) { - storageDir := fmt.Sprintf("tmpv4%d", runID) - var chunks []*chunkFileItem - chunkMap := map[string]*chunkFileItem{} - dummy := &chunkFileItem{} - for _, name := range blist.Latest { - chunkMap[name] = dummy +func listOrderedChunksForArtifact(st storage.ObjectStorage, runID, artifactID int64, blist *BlockList) ([]*chunkFileItem, error) { + emptyListAsError := func(chunks []*chunkFileItem) ([]*chunkFileItem, error) { + if len(chunks) == 0 { + return nil, fmt.Errorf("no chunk found for artifact id: %d", artifactID) + } + return chunks, nil } + + storageDir := makeTmpPathNameV4(runID) + var chunks []*chunkFileItem + var chunkMapV4 map[string]*chunkFileItem + + if blist != nil { + // make a dummy map for quick lookup of chunk names, the values are nil now and will be filled after iterating storage objects + chunkMapV4 = map[string]*chunkFileItem{} + for _, name := range blist.Latest { + chunkMapV4[name] = nil + } + } + if err := st.IterateObjects(storageDir, func(fpath string, obj storage.Object) error { - baseName := filepath.Base(fpath) - if !strings.HasPrefix(baseName, "block-") { + item, err := parseChunkFileItemV4(st, artifactID, fpath) + if errors.Is(err, errSkipChunkFile) { return nil + } else if err != nil { + return fmt.Errorf("unable to parse chunk name: %v", fpath) } - // when read chunks from storage, it only contains storage dir and basename, - // no matter the subdirectory setting in storage config - item := chunkFileItem{Path: storageDir + "/" + baseName, ArtifactID: artifactID} - var size int64 - var b64chunkName string - if _, err := fmt.Sscanf(baseName, "block-%d-%d-%s", &item.RunID, &size, &b64chunkName); err != nil { - return fmt.Errorf("parse content range error: %v", err) - } - rchunkName, err := base64.URLEncoding.DecodeString(b64chunkName) - if err != nil { - return fmt.Errorf("failed to parse chunkName: %v", err) - } - chunkName := string(rchunkName) - item.End = item.Start + size - 1 - if _, ok := chunkMap[chunkName]; ok { - chunkMap[chunkName] = &item + + // Single chunk upload with block id + if _, ok := chunkMapV4[item.ChunkName]; ok { + chunkMapV4[item.ChunkName] = item + } else if chunkMapV4 == nil { + if chunks != nil { + return errors.New("blockmap is required for chunks > 1") + } + chunks = []*chunkFileItem{item} } return nil }); err != nil { return nil, err } - for i, name := range blist.Latest { - chunk, ok := chunkMap[name] - if !ok || chunk.Path == "" { - return nil, fmt.Errorf("missing Chunk (%d/%d): %s", i, len(blist.Latest), name) + + if blist == nil && chunks == nil { + chunkUnorderedItemsMapV3, err := listV3UnorderedChunksMapByRunID(st, runID) + if err != nil { + return nil, err } - chunks = append(chunks, chunk) - if i > 0 { - chunk.Start = chunkMap[blist.Latest[i-1]].End + 1 - chunk.End += chunk.Start + chunks = chunkUnorderedItemsMapV3[artifactID] + sort.Slice(chunks, func(i, j int) bool { + return chunks[i].Start < chunks[j].Start + }) + return emptyListAsError(chunks) + } + + if len(chunks) == 0 && blist != nil { + for i, name := range blist.Latest { + chunk := chunkMapV4[name] + if chunk == nil { + return nil, fmt.Errorf("missing chunk (%d/%d): %s", i, len(blist.Latest), name) + } + chunks = append(chunks, chunk) } } - return chunks, nil + for i, chunk := range chunks { + if i == 0 { + chunk.End += chunk.Size - 1 + } else { + chunk.Start = chunkMapV4[blist.Latest[i-1]].End + 1 + chunk.End = chunk.Start + chunk.Size - 1 + } + } + return emptyListAsError(chunks) } func mergeChunksForRun(ctx *ArtifactContext, st storage.ObjectStorage, runID int64, artifactName string) error { @@ -181,13 +267,13 @@ func mergeChunksForRun(ctx *ArtifactContext, st storage.ObjectStorage, runID int return err } // read all uploading chunks from storage - chunksMap, err := listChunksByRunID(st, runID) + unorderedChunksMap, err := listV3UnorderedChunksMapByRunID(st, runID) if err != nil { return err } // range db artifacts to merge chunks for _, art := range artifacts { - chunks, ok := chunksMap[art.ID] + chunks, ok := unorderedChunksMap[art.ID] if !ok { log.Debug("artifact %d chunks not found", art.ID) continue @@ -239,12 +325,14 @@ func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st st } mergedReader := io.MultiReader(readers...) shaPrefix := "sha256:" - var hash hash.Hash + var hashSha256 hash.Hash if strings.HasPrefix(checksum, shaPrefix) { - hash = sha256.New() + hashSha256 = sha256.New() + } else if checksum != "" { + setting.PanicInDevOrTesting("unsupported checksum format: %s, will skip the checksum verification", checksum) } - if hash != nil { - mergedReader = io.TeeReader(mergedReader, hash) + if hashSha256 != nil { + mergedReader = io.TeeReader(mergedReader, hashSha256) } // if chunk is gzip, use gz as extension @@ -274,8 +362,8 @@ func mergeChunksForArtifact(ctx *ArtifactContext, chunks []*chunkFileItem, st st } }() - if hash != nil { - rawChecksum := hash.Sum(nil) + if hashSha256 != nil { + rawChecksum := hashSha256.Sum(nil) actualChecksum := hex.EncodeToString(rawChecksum) if !strings.HasSuffix(checksum, actualChecksum) { return fmt.Errorf("update artifact error checksum is invalid %v vs %v", checksum, actualChecksum) diff --git a/routers/api/actions/artifacts_utils.go b/routers/api/actions/artifacts_utils.go index 35868c290e9..db62f0061b9 100644 --- a/routers/api/actions/artifacts_utils.go +++ b/routers/api/actions/artifacts_utils.go @@ -20,8 +20,8 @@ const ( artifactXActionsResultsMD5Header = "x-actions-results-md5" ) -// The rules are from https://github.com/actions/toolkit/blob/main/packages/artifact/src/internal/path-and-artifact-name-validation.ts#L32 -var invalidArtifactNameChars = strings.Join([]string{"\\", "/", "\"", ":", "<", ">", "|", "*", "?", "\r", "\n"}, "") +// The rules are from https://github.com/actions/toolkit/blob/main/packages/artifact/src/internal/upload/path-and-artifact-name-validation.ts +const invalidArtifactNameChars = "\\/\":<>|*?\r\n" func validateArtifactName(ctx *ArtifactContext, artifactName string) bool { if strings.ContainsAny(artifactName, invalidArtifactNameChars) { @@ -84,11 +84,10 @@ func parseArtifactItemPath(ctx *ArtifactContext) (string, string, bool) { // getUploadFileSize returns the size of the file to be uploaded. // The raw size is the size of the file as reported by the header X-TFS-FileLength. -func getUploadFileSize(ctx *ArtifactContext) (int64, int64) { - contentLength := ctx.Req.ContentLength +func getUploadFileSize(ctx *ArtifactContext) int64 { xTfsLength, _ := strconv.ParseInt(ctx.Req.Header.Get(artifactXTfsFileLengthHeader), 10, 64) if xTfsLength > 0 { - return xTfsLength, contentLength + return xTfsLength } - return contentLength, contentLength + return ctx.Req.ContentLength } diff --git a/routers/api/actions/artifactsv4.go b/routers/api/actions/artifactsv4.go index 6d274796285..d208785f638 100644 --- a/routers/api/actions/artifactsv4.go +++ b/routers/api/actions/artifactsv4.go @@ -90,10 +90,12 @@ import ( "crypto/sha256" "encoding/base64" "encoding/xml" + "errors" "fmt" "io" "net/http" "net/url" + "path" "strconv" "strings" "time" @@ -109,7 +111,7 @@ import ( "code.gitea.io/gitea/services/context" "google.golang.org/protobuf/encoding/protojson" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -157,33 +159,81 @@ func ArtifactsV4Routes(prefix string) *web.Router { return m } -func (r artifactV4Routes) buildSignature(endp, expires, artifactName string, taskID, artifactID int64) []byte { +func (r *artifactV4Routes) buildSignature(endpoint, expires, artifactName string, taskID, artifactID int64) []byte { mac := hmac.New(sha256.New, setting.GetGeneralTokenSigningSecret()) - mac.Write([]byte(endp)) + mac.Write([]byte(endpoint)) mac.Write([]byte(expires)) mac.Write([]byte(artifactName)) - fmt.Fprint(mac, taskID) - fmt.Fprint(mac, artifactID) + _, _ = fmt.Fprint(mac, taskID) + _, _ = fmt.Fprint(mac, artifactID) return mac.Sum(nil) } -func (r artifactV4Routes) buildArtifactURL(ctx *ArtifactContext, endp, artifactName string, taskID, artifactID int64) string { +func (r *artifactV4Routes) buildArtifactURL(ctx *ArtifactContext, endpoint, artifactName string, taskID, artifactID int64) string { expires := time.Now().Add(60 * time.Minute).Format("2006-01-02 15:04:05.999999999 -0700 MST") uploadURL := strings.TrimSuffix(httplib.GuessCurrentAppURL(ctx), "/") + strings.TrimSuffix(r.prefix, "/") + - "/" + endp + "?sig=" + base64.URLEncoding.EncodeToString(r.buildSignature(endp, expires, artifactName, taskID, artifactID)) + "&expires=" + url.QueryEscape(expires) + "&artifactName=" + url.QueryEscape(artifactName) + "&taskID=" + strconv.FormatInt(taskID, 10) + "&artifactID=" + strconv.FormatInt(artifactID, 10) + "/" + endpoint + + "?sig=" + base64.RawURLEncoding.EncodeToString(r.buildSignature(endpoint, expires, artifactName, taskID, artifactID)) + + "&expires=" + url.QueryEscape(expires) + + "&artifactName=" + url.QueryEscape(artifactName) + + "&taskID=" + strconv.FormatInt(taskID, 10) + + "&artifactID=" + strconv.FormatInt(artifactID, 10) return uploadURL } -func (r artifactV4Routes) verifySignature(ctx *ArtifactContext, endp string) (*actions.ActionTask, string, bool) { +func makeBlockFilenameV4(runID, artifactID, size int64, blockID string) string { + sizeInName := max(size, 0) // do not use "-1" in filename + return fmt.Sprintf("block-%d-%d-%d-%s", runID, artifactID, sizeInName, base64.URLEncoding.EncodeToString([]byte(blockID))) +} + +var errSkipChunkFile = errors.New("skip this chunk file") + +func parseChunkFileItemV4(st storage.ObjectStorage, artifactID int64, fpath string) (*chunkFileItem, error) { + baseName := path.Base(fpath) + if !strings.HasPrefix(baseName, "block-") { + return nil, errSkipChunkFile + } + var item chunkFileItem + var unusedRunID int64 + var b64chunkName string + _, err := fmt.Sscanf(baseName, "block-%d-%d-%d-%s", &unusedRunID, &item.ArtifactID, &item.Size, &b64chunkName) + if err != nil { + return nil, err + } + if item.ArtifactID != artifactID { + return nil, errSkipChunkFile + } + chunkName, err := base64.URLEncoding.DecodeString(b64chunkName) + if err != nil { + return nil, err + } + item.ChunkName = string(chunkName) + item.Path = fpath + if item.Size <= 0 { + fi, err := st.Stat(item.Path) + if err != nil { + return nil, err + } + item.Size = fi.Size() + } + return &item, nil +} + +func (r *artifactV4Routes) verifySignature(ctx *ArtifactContext, endp string) (*actions.ActionTask, string, bool) { rawTaskID := ctx.Req.URL.Query().Get("taskID") rawArtifactID := ctx.Req.URL.Query().Get("artifactID") sig := ctx.Req.URL.Query().Get("sig") expires := ctx.Req.URL.Query().Get("expires") artifactName := ctx.Req.URL.Query().Get("artifactName") - dsig, _ := base64.URLEncoding.DecodeString(sig) - taskID, _ := strconv.ParseInt(rawTaskID, 10, 64) - artifactID, _ := strconv.ParseInt(rawArtifactID, 10, 64) - + dsig, errSig := base64.RawURLEncoding.DecodeString(sig) + taskID, errTask := strconv.ParseInt(rawTaskID, 10, 64) + artifactID, errArtifactID := strconv.ParseInt(rawArtifactID, 10, 64) + err := errors.Join(errSig, errTask, errArtifactID) + if err != nil { + log.Error("Error decoding signature values: %v", err) + ctx.HTTPError(http.StatusBadRequest, "Error decoding signature values") + return nil, "", false + } expecedsig := r.buildSignature(endp, expires, artifactName, taskID, artifactID) if !hmac.Equal(dsig, expecedsig) { log.Error("Error unauthorized") @@ -226,7 +276,7 @@ func (r *artifactV4Routes) getArtifactByName(ctx *ArtifactContext, runID int64, return &art, nil } -func (r *artifactV4Routes) parseProtbufBody(ctx *ArtifactContext, req protoreflect.ProtoMessage) bool { +func (r *artifactV4Routes) parseProtobufBody(ctx *ArtifactContext, req protoreflect.ProtoMessage) bool { body, err := io.ReadAll(ctx.Req.Body) if err != nil { log.Error("Error decode request body: %v", err) @@ -242,7 +292,7 @@ func (r *artifactV4Routes) parseProtbufBody(ctx *ArtifactContext, req protorefle return true } -func (r *artifactV4Routes) sendProtbufBody(ctx *ArtifactContext, req protoreflect.ProtoMessage) { +func (r *artifactV4Routes) sendProtobufBody(ctx *ArtifactContext, req protoreflect.ProtoMessage) { resp, err := protojson.Marshal(req) if err != nil { log.Error("Error encode response body: %v", err) @@ -257,7 +307,7 @@ func (r *artifactV4Routes) sendProtbufBody(ctx *ArtifactContext, req protoreflec func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) { var req CreateArtifactRequest - if ok := r.parseProtbufBody(ctx, &req); !ok { + if ok := r.parseProtobufBody(ctx, &req); !ok { return } _, _, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId) @@ -267,12 +317,12 @@ func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) { artifactName := req.Name - rententionDays := setting.Actions.ArtifactRetentionDays + retentionDays := setting.Actions.ArtifactRetentionDays if req.ExpiresAt != nil { - rententionDays = int64(time.Until(req.ExpiresAt.AsTime()).Hours() / 24) + retentionDays = int64(time.Until(req.ExpiresAt.AsTime()).Hours() / 24) } // create or get artifact with name and path - artifact, err := actions.CreateArtifact(ctx, ctx.ActionTask, artifactName, artifactName+".zip", rententionDays) + artifact, err := actions.CreateArtifact(ctx, ctx.ActionTask, artifactName, artifactName+".zip", retentionDays) if err != nil { log.Error("Error create or get artifact: %v", err) ctx.HTTPError(http.StatusInternalServerError, "Error create or get artifact") @@ -291,7 +341,7 @@ func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) { Ok: true, SignedUploadUrl: r.buildArtifactURL(ctx, "UploadArtifact", artifactName, ctx.ActionTask.ID, artifact.ID), } - r.sendProtbufBody(ctx, &respData) + r.sendProtobufBody(ctx, &respData) } func (r *artifactV4Routes) uploadArtifact(ctx *ArtifactContext) { @@ -303,34 +353,34 @@ func (r *artifactV4Routes) uploadArtifact(ctx *ArtifactContext) { comp := ctx.Req.URL.Query().Get("comp") switch comp { case "block", "appendBlock": - blockid := ctx.Req.URL.Query().Get("blockid") - if blockid == "" { - // get artifact by name - artifact, err := r.getArtifactByName(ctx, task.Job.RunID, artifactName) + // get artifact by name + artifact, err := r.getArtifactByName(ctx, task.Job.RunID, artifactName) + if err != nil { + log.Error("Error artifact not found: %v", err) + ctx.HTTPError(http.StatusNotFound, "Error artifact not found") + return + } + blockID := ctx.Req.URL.Query().Get("blockid") + if blockID == "" { + uploadedLength, err := appendUploadChunkV3(r.fs, ctx, artifact, artifact.RunID, artifact.FileSize) if err != nil { - log.Error("Error artifact not found: %v", err) - ctx.HTTPError(http.StatusNotFound, "Error artifact not found") + log.Error("Error appending chunk %v", err) + ctx.HTTPError(http.StatusInternalServerError, "Error appending Chunk") return } - - _, err = appendUploadChunk(r.fs, ctx, artifact, artifact.FileSize, ctx.Req.ContentLength, artifact.RunID) - if err != nil { - log.Error("Error runner api getting task: task is not running") - ctx.HTTPError(http.StatusInternalServerError, "Error runner api getting task: task is not running") - return - } - artifact.FileCompressedSize += ctx.Req.ContentLength - artifact.FileSize += ctx.Req.ContentLength + artifact.FileCompressedSize += uploadedLength + artifact.FileSize += uploadedLength if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil { log.Error("Error UpdateArtifactByID: %v", err) ctx.HTTPError(http.StatusInternalServerError, "Error UpdateArtifactByID") return } } else { - _, err := r.fs.Save(fmt.Sprintf("tmpv4%d/block-%d-%d-%s", task.Job.RunID, task.Job.RunID, ctx.Req.ContentLength, base64.URLEncoding.EncodeToString([]byte(blockid))), ctx.Req.Body, -1) + blockFilename := makeBlockFilenameV4(task.Job.RunID, artifact.ID, ctx.Req.ContentLength, blockID) + _, err := r.fs.Save(fmt.Sprintf("%s/%s", makeTmpPathNameV4(task.Job.RunID), blockFilename), ctx.Req.Body, ctx.Req.ContentLength) if err != nil { - log.Error("Error runner api getting task: task is not running") - ctx.HTTPError(http.StatusInternalServerError, "Error runner api getting task: task is not running") + log.Error("Error uploading block blob %v", err) + ctx.HTTPError(http.StatusInternalServerError, "Error uploading block blob") return } } @@ -338,10 +388,10 @@ func (r *artifactV4Routes) uploadArtifact(ctx *ArtifactContext) { case "blocklist": rawArtifactID := ctx.Req.URL.Query().Get("artifactID") artifactID, _ := strconv.ParseInt(rawArtifactID, 10, 64) - _, err := r.fs.Save(fmt.Sprintf("tmpv4%d/%d-%d-blocklist", task.Job.RunID, task.Job.RunID, artifactID), ctx.Req.Body, -1) + _, err := r.fs.Save(fmt.Sprintf("%s/%d-%d-blocklist", makeTmpPathNameV4(task.Job.RunID), task.Job.RunID, artifactID), ctx.Req.Body, -1) if err != nil { - log.Error("Error runner api getting task: task is not running") - ctx.HTTPError(http.StatusInternalServerError, "Error runner api getting task: task is not running") + log.Error("Error uploading blocklist %v", err) + ctx.HTTPError(http.StatusInternalServerError, "Error uploading blocklist") return } ctx.JSON(http.StatusCreated, "created") @@ -357,7 +407,7 @@ type Latest struct { } func (r *artifactV4Routes) readBlockList(runID, artifactID int64) (*BlockList, error) { - blockListName := fmt.Sprintf("tmpv4%d/%d-%d-blocklist", runID, runID, artifactID) + blockListName := fmt.Sprintf("%s/%d-%d-blocklist", makeTmpPathNameV4(runID), runID, artifactID) s, err := r.fs.Open(blockListName) if err != nil { return nil, err @@ -367,17 +417,22 @@ func (r *artifactV4Routes) readBlockList(runID, artifactID int64) (*BlockList, e blockList := &BlockList{} err = xdec.Decode(blockList) + _ = s.Close() + delerr := r.fs.Delete(blockListName) if delerr != nil { log.Warn("Failed to delete blockList %s: %v", blockListName, delerr) } - return blockList, err + if err != nil { + return nil, err + } + return blockList, nil } func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) { var req FinalizeArtifactRequest - if ok := r.parseProtbufBody(ctx, &req); !ok { + if ok := r.parseProtobufBody(ctx, &req); !ok { return } _, runID, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId) @@ -394,30 +449,20 @@ func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) { } var chunks []*chunkFileItem - blockList, err := r.readBlockList(runID, artifact.ID) + blockList, blockListErr := r.readBlockList(runID, artifact.ID) + chunks, err = listOrderedChunksForArtifact(r.fs, runID, artifact.ID, blockList) if err != nil { - log.Warn("Failed to read BlockList, fallback to old behavior: %v", err) - chunkMap, err := listChunksByRunID(r.fs, runID) - if err != nil { - log.Error("Error merge chunks: %v", err) - ctx.HTTPError(http.StatusInternalServerError, "Error merge chunks") - return - } - chunks, ok = chunkMap[artifact.ID] - if !ok { - log.Error("Error merge chunks") - ctx.HTTPError(http.StatusInternalServerError, "Error merge chunks") - return - } - } else { - chunks, err = listChunksByRunIDV4(r.fs, runID, artifact.ID, blockList) - if err != nil { - log.Error("Error merge chunks: %v", err) - ctx.HTTPError(http.StatusInternalServerError, "Error merge chunks") - return - } - artifact.FileSize = chunks[len(chunks)-1].End + 1 - artifact.FileCompressedSize = chunks[len(chunks)-1].End + 1 + log.Error("Error list chunks: %v", errors.Join(blockListErr, err)) + ctx.HTTPError(http.StatusInternalServerError, "Error list chunks") + return + } + artifact.FileSize = chunks[len(chunks)-1].End + 1 + artifact.FileCompressedSize = chunks[len(chunks)-1].End + 1 + + if req.Size != artifact.FileSize { + log.Error("Error merge chunks size mismatch") + ctx.HTTPError(http.StatusInternalServerError, "Error merge chunks size mismatch") + return } checksum := "" @@ -434,13 +479,13 @@ func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) { Ok: true, ArtifactId: artifact.ID, } - r.sendProtbufBody(ctx, &respData) + r.sendProtobufBody(ctx, &respData) } func (r *artifactV4Routes) listArtifacts(ctx *ArtifactContext) { var req ListArtifactsRequest - if ok := r.parseProtbufBody(ctx, &req); !ok { + if ok := r.parseProtobufBody(ctx, &req); !ok { return } _, runID, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId) @@ -485,13 +530,13 @@ func (r *artifactV4Routes) listArtifacts(ctx *ArtifactContext) { respData := ListArtifactsResponse{ Artifacts: list, } - r.sendProtbufBody(ctx, &respData) + r.sendProtobufBody(ctx, &respData) } func (r *artifactV4Routes) getSignedArtifactURL(ctx *ArtifactContext) { var req GetSignedArtifactURLRequest - if ok := r.parseProtbufBody(ctx, &req); !ok { + if ok := r.parseProtobufBody(ctx, &req); !ok { return } _, runID, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId) @@ -525,7 +570,7 @@ func (r *artifactV4Routes) getSignedArtifactURL(ctx *ArtifactContext) { if respData.SignedUrl == "" { respData.SignedUrl = r.buildArtifactURL(ctx, "DownloadArtifact", artifactName, ctx.ActionTask.ID, artifact.ID) } - r.sendProtbufBody(ctx, &respData) + r.sendProtobufBody(ctx, &respData) } func (r *artifactV4Routes) downloadArtifact(ctx *ArtifactContext) { @@ -555,7 +600,7 @@ func (r *artifactV4Routes) downloadArtifact(ctx *ArtifactContext) { func (r *artifactV4Routes) deleteArtifact(ctx *ArtifactContext) { var req DeleteArtifactRequest - if ok := r.parseProtbufBody(ctx, &req); !ok { + if ok := r.parseProtobufBody(ctx, &req); !ok { return } _, runID, ok := validateRunIDV4(ctx, req.WorkflowRunBackendId) @@ -582,5 +627,5 @@ func (r *artifactV4Routes) deleteArtifact(ctx *ArtifactContext) { Ok: true, ArtifactId: artifact.ID, } - r.sendProtbufBody(ctx, &respData) + r.sendProtobufBody(ctx, &respData) } diff --git a/routers/api/packages/arch/arch.go b/routers/api/packages/arch/arch.go index 061484785da..5a124f6918d 100644 --- a/routers/api/packages/arch/arch.go +++ b/routers/api/packages/arch/arch.go @@ -91,7 +91,7 @@ func UploadPackageFile(ctx *context.Context) { return } - release, err := arch_service.AquireRegistryLock(ctx, ctx.Package.Owner.ID) + release, err := arch_service.AcquireRegistryLock(ctx, ctx.Package.Owner.ID) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return @@ -257,7 +257,7 @@ func DeletePackageVersion(ctx *context.Context) { name := ctx.PathParam("name") version := ctx.PathParam("version") - release, err := arch_service.AquireRegistryLock(ctx, ctx.Package.Owner.ID) + release, err := arch_service.AcquireRegistryLock(ctx, ctx.Package.Owner.ID) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/api/v1/activitypub/person.go b/routers/api/v1/activitypub/person.go index f2ba2baf7fa..2ee3368a87e 100644 --- a/routers/api/v1/activitypub/person.go +++ b/routers/api/v1/activitypub/person.go @@ -4,103 +4,11 @@ package activitypub import ( - "fmt" "net/http" - "strings" - "code.gitea.io/gitea/modules/activitypub" - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/context" - - ap "github.com/go-ap/activitypub" - "github.com/go-ap/jsonld" ) -// Person function returns the Person actor for a user -func Person(ctx *context.APIContext) { - // swagger:operation GET /activitypub/user-id/{user-id} activitypub activitypubPerson - // --- - // summary: Returns the Person actor for a user - // produces: - // - application/json - // parameters: - // - name: user-id - // in: path - // description: user ID of the user - // type: integer - // required: true - // responses: - // "200": - // "$ref": "#/responses/ActivityPub" - - // TODO: the setting.AppURL during the test doesn't follow the definition: "It always has a '/' suffix" - link := fmt.Sprintf("%s/api/v1/activitypub/user-id/%d", strings.TrimSuffix(setting.AppURL, "/"), ctx.ContextUser.ID) - person := ap.PersonNew(ap.IRI(link)) - - person.Name = ap.NaturalLanguageValuesNew() - err := person.Name.Set("en", ap.Content(ctx.ContextUser.FullName)) - if err != nil { - ctx.APIErrorInternal(err) - return - } - - person.PreferredUsername = ap.NaturalLanguageValuesNew() - err = person.PreferredUsername.Set("en", ap.Content(ctx.ContextUser.Name)) - if err != nil { - ctx.APIErrorInternal(err) - return - } - - person.URL = ap.IRI(ctx.ContextUser.HTMLURL(ctx)) - - person.Icon = ap.Image{ - Type: ap.ImageType, - MediaType: "image/png", - URL: ap.IRI(ctx.ContextUser.AvatarLink(ctx)), - } - - person.Inbox = ap.IRI(link + "/inbox") - person.Outbox = ap.IRI(link + "/outbox") - - person.PublicKey.ID = ap.IRI(link + "#main-key") - person.PublicKey.Owner = ap.IRI(link) - - publicKeyPem, err := activitypub.GetPublicKey(ctx, ctx.ContextUser) - if err != nil { - ctx.APIErrorInternal(err) - return - } - person.PublicKey.PublicKeyPem = publicKeyPem - - binary, err := jsonld.WithContext(jsonld.IRI(ap.ActivityBaseURI), jsonld.IRI(ap.SecurityContextURI)).Marshal(person) - if err != nil { - ctx.APIErrorInternal(err) - return - } - ctx.Resp.Header().Add("Content-Type", activitypub.ActivityStreamsContentType) - ctx.Resp.WriteHeader(http.StatusOK) - if _, err = ctx.Resp.Write(binary); err != nil { - log.Error("write to resp err: %v", err) - } -} - -// PersonInbox function handles the incoming data for a user inbox -func PersonInbox(ctx *context.APIContext) { - // swagger:operation POST /activitypub/user-id/{user-id}/inbox activitypub activitypubPersonInbox - // --- - // summary: Send to the inbox - // produces: - // - application/json - // parameters: - // - name: user-id - // in: path - // description: user ID of the user - // type: integer - // required: true - // responses: - // "204": - // "$ref": "#/responses/empty" - - ctx.Status(http.StatusNoContent) +func NotImplemented(ctx *context.APIContext) { + http.Error(ctx.Resp, "Not implemented", http.StatusNotImplemented) } diff --git a/routers/api/v1/activitypub/reqsignature.go b/routers/api/v1/activitypub/reqsignature.go deleted file mode 100644 index 4eff51782f0..00000000000 --- a/routers/api/v1/activitypub/reqsignature.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package activitypub - -import ( - "crypto" - "crypto/x509" - "encoding/pem" - "errors" - "fmt" - "io" - "net/http" - "net/url" - - "code.gitea.io/gitea/modules/activitypub" - "code.gitea.io/gitea/modules/httplib" - "code.gitea.io/gitea/modules/setting" - gitea_context "code.gitea.io/gitea/services/context" - - "github.com/42wim/httpsig" - ap "github.com/go-ap/activitypub" -) - -func getPublicKeyFromResponse(b []byte, keyID *url.URL) (p crypto.PublicKey, err error) { - person := ap.PersonNew(ap.IRI(keyID.String())) - err = person.UnmarshalJSON(b) - if err != nil { - return nil, fmt.Errorf("ActivityStreams type cannot be converted to one known to have publicKey property: %w", err) - } - pubKey := person.PublicKey - if pubKey.ID.String() != keyID.String() { - return nil, fmt.Errorf("cannot find publicKey with id: %s in %s", keyID, string(b)) - } - pubKeyPem := pubKey.PublicKeyPem - block, _ := pem.Decode([]byte(pubKeyPem)) - if block == nil || block.Type != "PUBLIC KEY" { - return nil, errors.New("could not decode publicKeyPem to PUBLIC KEY pem block type") - } - p, err = x509.ParsePKIXPublicKey(block.Bytes) - return p, err -} - -func fetch(iri *url.URL) (b []byte, err error) { - req := httplib.NewRequest(iri.String(), http.MethodGet) - req.Header("Accept", activitypub.ActivityStreamsContentType) - req.Header("User-Agent", "Gitea/"+setting.AppVer) - resp, err := req.Response() - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("url IRI fetch [%s] failed with status (%d): %s", iri, resp.StatusCode, resp.Status) - } - b, err = io.ReadAll(io.LimitReader(resp.Body, setting.Federation.MaxSize)) - return b, err -} - -func verifyHTTPSignatures(ctx *gitea_context.APIContext) (authenticated bool, err error) { - r := ctx.Req - - // 1. Figure out what key we need to verify - v, err := httpsig.NewVerifier(r) - if err != nil { - return false, err - } - ID := v.KeyId() - idIRI, err := url.Parse(ID) - if err != nil { - return false, err - } - // 2. Fetch the public key of the other actor - b, err := fetch(idIRI) - if err != nil { - return false, err - } - pubKey, err := getPublicKeyFromResponse(b, idIRI) - if err != nil { - return false, err - } - // 3. Verify the other actor's key - algo := httpsig.Algorithm(setting.Federation.Algorithms[0]) - authenticated = v.Verify(pubKey, algo) == nil - return authenticated, err -} - -// ReqHTTPSignature function -func ReqHTTPSignature() func(ctx *gitea_context.APIContext) { - return func(ctx *gitea_context.APIContext) { - if authenticated, err := verifyHTTPSignatures(ctx); err != nil { - ctx.APIErrorInternal(err) - } else if !authenticated { - ctx.APIError(http.StatusForbidden, "request signature verification failed") - } - } -} diff --git a/routers/api/v1/admin/runners.go b/routers/api/v1/admin/runners.go index 1e15b240761..f5aaea2c328 100644 --- a/routers/api/v1/admin/runners.go +++ b/routers/api/v1/admin/runners.go @@ -10,21 +10,6 @@ import ( // https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization -// GetRegistrationToken returns the token to register global runners -func GetRegistrationToken(ctx *context.APIContext) { - // swagger:operation GET /admin/runners/registration-token admin adminGetRunnerRegistrationToken - // --- - // summary: Get a global actions runner registration token - // produces: - // - application/json - // parameters: - // responses: - // "200": - // "$ref": "#/responses/RegistrationToken" - - shared.GetRegistrationToken(ctx, 0, 0) -} - // CreateRegistrationToken returns the token to register global runners func CreateRegistrationToken(ctx *context.APIContext) { // swagger:operation POST /admin/actions/runners/registration-token admin adminCreateRunnerRegistrationToken diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index cb88a0a5203..5a6da089dee 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -895,35 +895,35 @@ func Routes() *web.Router { addActionsRoutes := func( m *web.Router, - reqChecker func(ctx *context.APIContext), + reqReaderCheck func(ctx *context.APIContext), + reqOwnerCheck func(ctx *context.APIContext), act actions.API, ) { m.Group("/actions", func() { m.Group("/secrets", func() { - m.Get("", reqToken(), reqChecker, act.ListActionsSecrets) + m.Get("", reqToken(), reqOwnerCheck, act.ListActionsSecrets) m.Combo("/{secretname}"). - Put(reqToken(), reqChecker, bind(api.CreateOrUpdateSecretOption{}), act.CreateOrUpdateSecret). - Delete(reqToken(), reqChecker, act.DeleteSecret) + Put(reqToken(), reqOwnerCheck, bind(api.CreateOrUpdateSecretOption{}), act.CreateOrUpdateSecret). + Delete(reqToken(), reqOwnerCheck, act.DeleteSecret) }) m.Group("/variables", func() { - m.Get("", reqToken(), reqChecker, act.ListVariables) + m.Get("", reqToken(), reqOwnerCheck, act.ListVariables) m.Combo("/{variablename}"). - Get(reqToken(), reqChecker, act.GetVariable). - Delete(reqToken(), reqChecker, act.DeleteVariable). - Post(reqToken(), reqChecker, bind(api.CreateVariableOption{}), act.CreateVariable). - Put(reqToken(), reqChecker, bind(api.UpdateVariableOption{}), act.UpdateVariable) + Get(reqToken(), reqOwnerCheck, act.GetVariable). + Delete(reqToken(), reqOwnerCheck, act.DeleteVariable). + Post(reqToken(), reqOwnerCheck, bind(api.CreateVariableOption{}), act.CreateVariable). + Put(reqToken(), reqOwnerCheck, bind(api.UpdateVariableOption{}), act.UpdateVariable) }) m.Group("/runners", func() { - m.Get("", reqToken(), reqChecker, act.ListRunners) - m.Get("/registration-token", reqToken(), reqChecker, act.GetRegistrationToken) - m.Post("/registration-token", reqToken(), reqChecker, act.CreateRegistrationToken) - m.Get("/{runner_id}", reqToken(), reqChecker, act.GetRunner) - m.Delete("/{runner_id}", reqToken(), reqChecker, act.DeleteRunner) + m.Get("", reqToken(), reqOwnerCheck, act.ListRunners) + m.Post("/registration-token", reqToken(), reqOwnerCheck, act.CreateRegistrationToken) + m.Get("/{runner_id}", reqToken(), reqOwnerCheck, act.GetRunner) + m.Delete("/{runner_id}", reqToken(), reqOwnerCheck, act.DeleteRunner) }) - m.Get("/runs", reqToken(), reqChecker, act.ListWorkflowRuns) - m.Get("/jobs", reqToken(), reqChecker, act.ListWorkflowJobs) + m.Get("/runs", reqToken(), reqReaderCheck, act.ListWorkflowRuns) + m.Get("/jobs", reqToken(), reqReaderCheck, act.ListWorkflowJobs) }) } @@ -936,18 +936,8 @@ func Routes() *web.Router { } if setting.Federation.Enabled { - m.Get("/nodeinfo", misc.NodeInfo) - m.Group("/activitypub", func() { - // deprecated, remove in 1.20, use /user-id/{user-id} instead - m.Group("/user/{username}", func() { - m.Get("", activitypub.Person) - m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox) - }, context.UserAssignmentAPI(), checkTokenPublicOnly()) - m.Group("/user-id/{user-id}", func() { - m.Get("", activitypub.Person) - m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox) - }, context.UserIDAssignmentAPI(), checkTokenPublicOnly()) - }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub)) + m.Get("/nodeinfo", activitypub.NotImplemented) + m.Any("/activitypub/*", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub), activitypub.NotImplemented) } // Misc (public accessible) @@ -1055,7 +1045,6 @@ func Routes() *web.Router { m.Group("/runners", func() { m.Get("", reqToken(), user.ListRunners) - m.Get("/registration-token", reqToken(), user.GetRegistrationToken) m.Post("/registration-token", reqToken(), user.CreateRegistrationToken) m.Get("/{runner_id}", reqToken(), user.GetRunner) m.Delete("/{runner_id}", reqToken(), user.DeleteRunner) @@ -1176,7 +1165,8 @@ func Routes() *web.Router { m.Post("/reject", repo.RejectTransfer) }, reqToken()) - addActionsRoutes(m, reqOwner(), repo.NewAction()) // it adds the routes for secrets/variables and runner management + // Adds the routes for secrets/variables and runner management + addActionsRoutes(m, reqRepoReader(unit.TypeActions), reqOwner(), repo.NewAction()) m.Group("/actions/workflows", func() { m.Get("", repo.ActionsListRepositoryWorkflows) @@ -1271,7 +1261,9 @@ func Routes() *web.Router { m.Group("/{run}", func() { m.Get("", repo.GetWorkflowRun) m.Delete("", reqToken(), reqRepoWriter(unit.TypeActions), repo.DeleteActionRun) + m.Post("/rerun", reqToken(), reqRepoWriter(unit.TypeActions), repo.RerunWorkflowRun) m.Get("/jobs", repo.ListWorkflowRunJobs) + m.Post("/jobs/{job_id}/rerun", reqToken(), reqRepoWriter(unit.TypeActions), repo.RerunWorkflowJob) m.Get("/artifacts", repo.GetArtifactsOfRun) }) }) @@ -1630,6 +1622,7 @@ func Routes() *web.Router { }) addActionsRoutes( m, + reqOrgMembership(), reqOrgOwnership(), org.NewAction(), ) @@ -1745,9 +1738,6 @@ func Routes() *web.Router { m.Get("/runs", admin.ListWorkflowRuns) m.Get("/jobs", admin.ListWorkflowJobs) }) - m.Group("/runners", func() { - m.Get("/registration-token", admin.GetRegistrationToken) - }) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryAdmin), reqToken(), reqSiteAdmin()) m.Group("/topics", func() { diff --git a/routers/api/v1/misc/nodeinfo.go b/routers/api/v1/misc/nodeinfo.go deleted file mode 100644 index ffe50e9fda4..00000000000 --- a/routers/api/v1/misc/nodeinfo.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2021 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package misc - -import ( - "net/http" - "time" - - issues_model "code.gitea.io/gitea/models/issues" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/structs" - "code.gitea.io/gitea/services/context" -) - -const cacheKeyNodeInfoUsage = "API_NodeInfoUsage" - -// NodeInfo returns the NodeInfo for the Gitea instance to allow for federation -func NodeInfo(ctx *context.APIContext) { - // swagger:operation GET /nodeinfo miscellaneous getNodeInfo - // --- - // summary: Returns the nodeinfo of the Gitea application - // produces: - // - application/json - // responses: - // "200": - // "$ref": "#/responses/NodeInfo" - - nodeInfoUsage := structs.NodeInfoUsage{} - if setting.Federation.ShareUserStatistics { - cached, _ := ctx.Cache.GetJSON(cacheKeyNodeInfoUsage, &nodeInfoUsage) - if !cached { - usersTotal := int(user_model.CountUsers(ctx, nil)) - now := time.Now() - timeOneMonthAgo := now.AddDate(0, -1, 0).Unix() - timeHaveYearAgo := now.AddDate(0, -6, 0).Unix() - usersActiveMonth := int(user_model.CountUsers(ctx, &user_model.CountUserFilter{LastLoginSince: &timeOneMonthAgo})) - usersActiveHalfyear := int(user_model.CountUsers(ctx, &user_model.CountUserFilter{LastLoginSince: &timeHaveYearAgo})) - - allIssues, _ := issues_model.CountIssues(ctx, &issues_model.IssuesOptions{}) - allComments, _ := issues_model.CountComments(ctx, &issues_model.FindCommentsOptions{}) - - nodeInfoUsage = structs.NodeInfoUsage{ - Users: structs.NodeInfoUsageUsers{ - Total: usersTotal, - ActiveMonth: usersActiveMonth, - ActiveHalfyear: usersActiveHalfyear, - }, - LocalPosts: int(allIssues), - LocalComments: int(allComments), - } - - if err := ctx.Cache.PutJSON(cacheKeyNodeInfoUsage, nodeInfoUsage, 180); err != nil { - ctx.APIErrorInternal(err) - return - } - } - } - - nodeInfo := &structs.NodeInfo{ - Version: "2.1", - Software: structs.NodeInfoSoftware{ - Name: "gitea", - Version: setting.AppVer, - Repository: "https://github.com/go-gitea/gitea.git", - Homepage: "https://gitea.io/", - }, - Protocols: []string{"activitypub"}, - Services: structs.NodeInfoServices{ - Inbound: []string{}, - Outbound: []string{"rss2.0"}, - }, - OpenRegistrations: setting.Service.ShowRegistrationButton, - Usage: nodeInfoUsage, - } - ctx.JSON(http.StatusOK, nodeInfo) -} diff --git a/routers/api/v1/org/action.go b/routers/api/v1/org/action.go index 59d8d3f2b4d..d058964f52d 100644 --- a/routers/api/v1/org/action.go +++ b/routers/api/v1/org/action.go @@ -170,27 +170,6 @@ func (Action) DeleteSecret(ctx *context.APIContext) { ctx.Status(http.StatusNoContent) } -// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization -// GetRegistrationToken returns the token to register org runners -func (Action) GetRegistrationToken(ctx *context.APIContext) { - // swagger:operation GET /orgs/{org}/actions/runners/registration-token organization orgGetRunnerRegistrationToken - // --- - // summary: Get an organization's actions runner registration token - // produces: - // - application/json - // parameters: - // - name: org - // in: path - // description: name of the organization - // type: string - // required: true - // responses: - // "200": - // "$ref": "#/responses/RegistrationToken" - - shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0) -} - // https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization // CreateRegistrationToken returns the token to register org runners func (Action) CreateRegistrationToken(ctx *context.APIContext) { diff --git a/routers/api/v1/org/hook.go b/routers/api/v1/org/hook.go index f9e0684a97b..b07e902f6e0 100644 --- a/routers/api/v1/org/hook.go +++ b/routers/api/v1/org/hook.go @@ -13,7 +13,7 @@ import ( webhook_service "code.gitea.io/gitea/services/webhook" ) -// ListHooks list an organziation's webhooks +// ListHooks list an organization's webhooks func ListHooks(ctx *context.APIContext) { // swagger:operation GET /orgs/{org}/hooks organization orgListHooks // --- diff --git a/routers/api/v1/org/org.go b/routers/api/v1/org/org.go index e498c2c0de5..4d4e2ef269b 100644 --- a/routers/api/v1/org/org.go +++ b/routers/api/v1/org/org.go @@ -139,7 +139,7 @@ func GetUserOrgsPermissions(ctx *context.APIContext) { op := api.OrganizationPermissions{} - if !organization.HasOrgOrUserVisible(ctx, o, ctx.ContextUser) { + if !organization.HasOrgOrUserVisible(ctx, o, ctx.Doer) { ctx.APIErrorNotFound("HasOrgOrUserVisible", nil) return } diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index 7d43db1e9be..211b7a15b25 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -806,7 +806,7 @@ func SearchTeam(ctx *context.APIContext) { ListOptions: listOptions, } - // Only admin is allowd to search for all teams + // Only admin is allowed to search for all teams if !ctx.Doer.IsAdmin { opts.UserID = ctx.Doer.ID } diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go index 03ce0d3aab4..13da5aa8151 100644 --- a/routers/api/v1/repo/action.go +++ b/routers/api/v1/repo/action.go @@ -12,6 +12,7 @@ import ( "fmt" "net/http" "net/url" + "slices" "strconv" "strings" "time" @@ -510,31 +511,6 @@ func (Action) ListVariables(ctx *context.APIContext) { ctx.JSON(http.StatusOK, variables) } -// GetRegistrationToken returns the token to register repo runners -func (Action) GetRegistrationToken(ctx *context.APIContext) { - // swagger:operation GET /repos/{owner}/{repo}/actions/runners/registration-token repository repoGetRunnerRegistrationToken - // --- - // summary: Get a repository's actions runner registration token - // produces: - // - application/json - // parameters: - // - name: owner - // in: path - // description: owner of the repo - // type: string - // required: true - // - name: repo - // in: path - // description: name of the repo - // type: string - // required: true - // responses: - // "200": - // "$ref": "#/responses/RegistrationToken" - - shared.GetRegistrationToken(ctx, 0, ctx.Repo.Repository.ID) -} - // CreateRegistrationToken returns the token to register repo runners func (Action) CreateRegistrationToken(ctx *context.APIContext) { // swagger:operation POST /repos/{owner}/{repo}/actions/runners/registration-token repository repoCreateRunnerRegistrationToken @@ -1004,9 +980,15 @@ func ActionsDispatchWorkflow(ctx *context.APIContext) { // in: body // schema: // "$ref": "#/definitions/CreateActionWorkflowDispatch" + // - name: return_run_details + // description: Whether the response should include the workflow run ID and URLs. + // in: query + // type: boolean // responses: + // "200": + // "$ref": "#/responses/RunDetails" // "204": - // description: No Content + // description: No Content, if return_run_details is missing or false // "400": // "$ref": "#/responses/error" // "403": @@ -1023,7 +1005,7 @@ func ActionsDispatchWorkflow(ctx *context.APIContext) { return } - err := actions_service.DispatchActionWorkflow(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, workflowID, opt.Ref, func(workflowDispatch *model.WorkflowDispatch, inputs map[string]any) error { + runID, err := actions_service.DispatchActionWorkflow(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, workflowID, opt.Ref, func(workflowDispatch *model.WorkflowDispatch, inputs map[string]any) error { if strings.Contains(ctx.Req.Header.Get("Content-Type"), "form-urlencoded") { // The chi framework's "Binding" doesn't support to bind the form map values into a map[string]string // So we have to manually read the `inputs[key]` from the form @@ -1054,7 +1036,22 @@ func ActionsDispatchWorkflow(ctx *context.APIContext) { return } - ctx.Status(http.StatusNoContent) + if !ctx.FormBool("return_run_details") { + ctx.Status(http.StatusNoContent) + return + } + + workflowRun, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID) + if err != nil { + ctx.APIErrorInternal(err) + return + } + + ctx.JSON(http.StatusOK, &api.RunDetails{ + WorkflowRunID: runID, + HTMLURL: fmt.Sprintf("%s/actions/runs/%d", ctx.Repo.Repository.HTMLURL(ctx), workflowRun.Index), + RunURL: fmt.Sprintf("%s/actions/runs/%d", ctx.Repo.Repository.APIURL(), runID), + }) } func ActionsEnableWorkflow(ctx *context.APIContext) { @@ -1107,6 +1104,33 @@ func ActionsEnableWorkflow(ctx *context.APIContext) { ctx.Status(http.StatusNoContent) } +func getCurrentRepoActionRunByID(ctx *context.APIContext) *actions_model.ActionRun { + runID := ctx.PathParamInt64("run") + run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID) + if errors.Is(err, util.ErrNotExist) { + ctx.APIErrorNotFound(err) + return nil + } else if err != nil { + ctx.APIErrorInternal(err) + return nil + } + return run +} + +func getCurrentRepoActionRunJobsByID(ctx *context.APIContext) (*actions_model.ActionRun, actions_model.ActionJobList) { + run := getCurrentRepoActionRunByID(ctx) + if ctx.Written() { + return nil, nil + } + + jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID) + if err != nil { + ctx.APIErrorInternal(err) + return nil, nil + } + return run, jobs +} + // GetWorkflowRun Gets a specific workflow run. func GetWorkflowRun(ctx *context.APIContext) { // swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run} repository GetWorkflowRun @@ -1138,19 +1162,12 @@ func GetWorkflowRun(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - runID := ctx.PathParamInt64("run") - job, has, err := db.GetByID[actions_model.ActionRun](ctx, runID) - if err != nil { - ctx.APIErrorInternal(err) + run := getCurrentRepoActionRunByID(ctx) + if ctx.Written() { return } - if !has || job.RepoID != ctx.Repo.Repository.ID { - ctx.APIErrorNotFound(util.ErrNotExist) - return - } - - convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, job) + convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, run) if err != nil { ctx.APIErrorInternal(err) return @@ -1158,6 +1175,133 @@ func GetWorkflowRun(ctx *context.APIContext) { ctx.JSON(http.StatusOK, convertedRun) } +// RerunWorkflowRun Reruns an entire workflow run. +func RerunWorkflowRun(ctx *context.APIContext) { + // swagger:operation POST /repos/{owner}/{repo}/actions/runs/{run}/rerun repository rerunWorkflowRun + // --- + // summary: Reruns an entire workflow run + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repository + // type: string + // required: true + // - name: run + // in: path + // description: id of the run + // type: integer + // required: true + // responses: + // "201": + // "$ref": "#/responses/WorkflowRun" + // "400": + // "$ref": "#/responses/error" + // "403": + // "$ref": "#/responses/forbidden" + // "404": + // "$ref": "#/responses/notFound" + // "422": + // "$ref": "#/responses/validationError" + + run, jobs := getCurrentRepoActionRunJobsByID(ctx) + if ctx.Written() { + return + } + + if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, jobs, nil); err != nil { + handleWorkflowRerunError(ctx, err) + return + } + + convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, run) + if err != nil { + ctx.APIErrorInternal(err) + return + } + ctx.JSON(http.StatusCreated, convertedRun) +} + +// RerunWorkflowJob Reruns a specific workflow job in a run. +func RerunWorkflowJob(ctx *context.APIContext) { + // swagger:operation POST /repos/{owner}/{repo}/actions/runs/{run}/jobs/{job_id}/rerun repository rerunWorkflowJob + // --- + // summary: Reruns a specific workflow job in a run + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repository + // type: string + // required: true + // - name: run + // in: path + // description: id of the run + // type: integer + // required: true + // - name: job_id + // in: path + // description: id of the job + // type: integer + // required: true + // responses: + // "201": + // "$ref": "#/responses/WorkflowJob" + // "400": + // "$ref": "#/responses/error" + // "403": + // "$ref": "#/responses/forbidden" + // "404": + // "$ref": "#/responses/notFound" + // "422": + // "$ref": "#/responses/validationError" + + run, jobs := getCurrentRepoActionRunJobsByID(ctx) + if ctx.Written() { + return + } + + jobID := ctx.PathParamInt64("job_id") + jobIdx := slices.IndexFunc(jobs, func(job *actions_model.ActionRunJob) bool { return job.ID == jobID }) + if jobIdx == -1 { + ctx.APIErrorNotFound(util.NewNotExistErrorf("workflow job with id %d", jobID)) + return + } + + targetJob := jobs[jobIdx] + if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, jobs, targetJob); err != nil { + handleWorkflowRerunError(ctx, err) + return + } + + convertedJob, err := convert.ToActionWorkflowJob(ctx, ctx.Repo.Repository, nil, targetJob) + if err != nil { + ctx.APIErrorInternal(err) + return + } + ctx.JSON(http.StatusCreated, convertedJob) +} + +func handleWorkflowRerunError(ctx *context.APIContext, err error) { + if errors.Is(err, util.ErrInvalidArgument) { + ctx.APIError(http.StatusBadRequest, err) + return + } + ctx.APIErrorInternal(err) +} + // ListWorkflowRunJobs Lists all jobs for a workflow run. func ListWorkflowRunJobs(ctx *context.APIContext) { // swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run}/jobs repository listWorkflowRunJobs @@ -1202,9 +1346,7 @@ func ListWorkflowRunJobs(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - repoID := ctx.Repo.Repository.ID - - runID := ctx.PathParamInt64("run") + repoID, runID := ctx.Repo.Repository.ID, ctx.PathParamInt64("run") // Avoid the list all jobs functionality for this api route to be used with a runID == 0. if runID <= 0 { @@ -1304,10 +1446,8 @@ func GetArtifactsOfRun(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - repoID := ctx.Repo.Repository.ID artifactName := ctx.Req.URL.Query().Get("name") - - runID := ctx.PathParamInt64("run") + repoID, runID := ctx.Repo.Repository.ID, ctx.PathParamInt64("run") artifacts, total, err := db.FindAndCount[actions_model.ActionArtifact](ctx, actions_model.FindArtifactsOptions{ RepoID: repoID, @@ -1368,15 +1508,11 @@ func DeleteActionRun(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - runID := ctx.PathParamInt64("run") - run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID) - if errors.Is(err, util.ErrNotExist) { - ctx.APIErrorNotFound(err) - return - } else if err != nil { - ctx.APIErrorInternal(err) + run := getCurrentRepoActionRunByID(ctx) + if ctx.Written() { return } + if !run.Status.IsDone() { ctx.APIError(http.StatusBadRequest, "this workflow run is not done") return diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index 82fd68bdece..eaa8afb1e13 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -150,7 +150,7 @@ func DeleteBranch(ctx *context.APIContext) { } } - if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName, nil); err != nil { + if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil { switch { case git.IsErrBranchNotExist(err): ctx.APIErrorNotFound(err) diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index deb68963c20..e31bc24eef6 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -138,7 +138,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) { // LFS Pointer files are at most 1024 bytes - so any blob greater than 1024 bytes cannot be an LFS file if blob.Size() > lfs.MetaFileMaxSize { // First handle caching for the blob - if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) { + if httpcache.HandleGenericETagPrivateCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) { return } @@ -174,7 +174,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) { // if it's not a pointer, just serve the data directly if !pointer.IsValid() { // First handle caching for the blob - if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) { + if httpcache.HandleGenericETagPrivateCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) { return } @@ -189,7 +189,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) { // If there isn't one, just serve the data directly if errors.Is(err, git_model.ErrLFSObjectNotExist) { // Handle caching for the blob SHA (not the LFS object OID) - if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) { + if httpcache.HandleGenericETagPrivateCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) { return } @@ -201,7 +201,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) { } // Handle caching for the LFS object OID - if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+pointer.Oid+`"`) { + if httpcache.HandleGenericETagPrivateCache(ctx.Req, ctx.Resp, `"`+pointer.Oid+`"`, meta.UpdatedUnix.AsTimePtr()) { return } diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 41076fd99c8..22324e19233 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -157,6 +157,10 @@ func SearchIssues(ctx *context.APIContext) { // in: query // description: Filter by repository owner // type: string + // - name: created_by + // in: query + // description: Only show items which were created by the given user + // type: string // - name: team // in: query // description: Filter by team (requires organization owner parameter) @@ -257,6 +261,14 @@ func SearchIssues(ctx *context.APIContext) { searchOpt.UpdatedBeforeUnix = optional.Some(before) } + createdByID := getUserIDForFilter(ctx, "created_by") + if ctx.Written() { + return + } + if createdByID > 0 { + searchOpt.PosterID = strconv.FormatInt(createdByID, 10) + } + if ctx.IsSigned { ctxUserID := ctx.Doer.ID if ctx.FormBool("created") { diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index bb6bda587db..cfdcf7b374e 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -884,77 +884,44 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { } } - if opts.HasPullRequests != nil && !unit_model.TypePullRequests.UnitGlobalDisabled() { - if *opts.HasPullRequests { - // We do allow setting individual PR settings through the API, so - // we get the config settings and then set them - // if those settings were provided in the opts. - unit, err := repo.GetUnit(ctx, unit_model.TypePullRequests) - var config *repo_model.PullRequestsConfig - if err != nil { - // Unit type doesn't exist so we make a new config file with default values - config = &repo_model.PullRequestsConfig{ - IgnoreWhitespaceConflicts: false, - AllowMerge: true, - AllowRebase: true, - AllowRebaseMerge: true, - AllowSquash: true, - AllowFastForwardOnly: true, - AllowManualMerge: true, - AutodetectManualMerge: false, - AllowRebaseUpdate: true, - DefaultDeleteBranchAfterMerge: false, - DefaultMergeStyle: repo_model.MergeStyleMerge, - DefaultAllowMaintainerEdit: false, - } - } else { - config = unit.PullRequestsConfig() - } - - if opts.IgnoreWhitespaceConflicts != nil { - config.IgnoreWhitespaceConflicts = *opts.IgnoreWhitespaceConflicts - } - if opts.AllowMerge != nil { - config.AllowMerge = *opts.AllowMerge - } - if opts.AllowRebase != nil { - config.AllowRebase = *opts.AllowRebase - } - if opts.AllowRebaseMerge != nil { - config.AllowRebaseMerge = *opts.AllowRebaseMerge - } - if opts.AllowSquash != nil { - config.AllowSquash = *opts.AllowSquash - } - if opts.AllowFastForwardOnly != nil { - config.AllowFastForwardOnly = *opts.AllowFastForwardOnly - } - if opts.AllowManualMerge != nil { - config.AllowManualMerge = *opts.AllowManualMerge - } - if opts.AutodetectManualMerge != nil { - config.AutodetectManualMerge = *opts.AutodetectManualMerge - } - if opts.AllowRebaseUpdate != nil { - config.AllowRebaseUpdate = *opts.AllowRebaseUpdate - } - if opts.DefaultDeleteBranchAfterMerge != nil { - config.DefaultDeleteBranchAfterMerge = *opts.DefaultDeleteBranchAfterMerge - } - if opts.DefaultMergeStyle != nil { - config.DefaultMergeStyle = repo_model.MergeStyle(*opts.DefaultMergeStyle) - } - if opts.DefaultAllowMaintainerEdit != nil { - config.DefaultAllowMaintainerEdit = *opts.DefaultAllowMaintainerEdit - } - - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypePullRequests, - Config: config, - }) - } else { + if !unit_model.TypePullRequests.UnitGlobalDisabled() { + mustDeletePullRequestUnit := opts.HasPullRequests != nil && !*opts.HasPullRequests + mustInsertPullRequestUnit := opts.HasPullRequests != nil && *opts.HasPullRequests + if mustDeletePullRequestUnit { deleteUnitTypes = append(deleteUnitTypes, unit_model.TypePullRequests) + } else { + // We do allow setting individual PR settings through the API, + // so we get the config settings and then set them if those settings were provided in the opts. + unit, err := repo.GetUnit(ctx, unit_model.TypePullRequests) + if err != nil && !errors.Is(err, util.ErrNotExist) { + return err + } + if unit == nil { + // Unit doesn't exist yet but is being enabled, create with defaults + unit = new(repo_model.DefaultPullRequestsUnit(repo.ID)) + } + + changed := new(false) + config := unit.PullRequestsConfig() + optional.AssignPtrValue(changed, &config.IgnoreWhitespaceConflicts, opts.IgnoreWhitespaceConflicts) + optional.AssignPtrValue(changed, &config.AllowMerge, opts.AllowMerge) + optional.AssignPtrValue(changed, &config.AllowRebase, opts.AllowRebase) + optional.AssignPtrValue(changed, &config.AllowRebaseMerge, opts.AllowRebaseMerge) + optional.AssignPtrValue(changed, &config.AllowSquash, opts.AllowSquash) + optional.AssignPtrValue(changed, &config.AllowFastForwardOnly, opts.AllowFastForwardOnly) + optional.AssignPtrValue(changed, &config.AllowManualMerge, opts.AllowManualMerge) + optional.AssignPtrValue(changed, &config.AutodetectManualMerge, opts.AutodetectManualMerge) + optional.AssignPtrValue(changed, &config.AllowRebaseUpdate, opts.AllowRebaseUpdate) + optional.AssignPtrValue(changed, &config.DefaultDeleteBranchAfterMerge, opts.DefaultDeleteBranchAfterMerge) + optional.AssignPtrValue(changed, &config.DefaultAllowMaintainerEdit, opts.DefaultAllowMaintainerEdit) + optional.AssignPtrString(changed, &config.DefaultMergeStyle, opts.DefaultMergeStyle) + if *changed || mustInsertPullRequestUnit { + units = append(units, repo_model.RepoUnit{ + RepoID: repo.ID, + Type: unit_model.TypePullRequests, + Config: config, + }) + } } } diff --git a/routers/api/v1/swagger/action.go b/routers/api/v1/swagger/action.go index 06065059502..328998db211 100644 --- a/routers/api/v1/swagger/action.go +++ b/routers/api/v1/swagger/action.go @@ -46,3 +46,10 @@ type swaggerResponseActionWorkflowList struct { // in:body Body api.ActionWorkflowResponse `json:"body"` } + +// RunDetails +// swagger:response RunDetails +type swaggerResponseRunDetails struct { + // in:body + Body api.RunDetails `json:"body"` +} diff --git a/routers/api/v1/swagger/activitypub.go b/routers/api/v1/swagger/activitypub.go deleted file mode 100644 index 91341669da9..00000000000 --- a/routers/api/v1/swagger/activitypub.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2022 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package swagger - -import ( - api "code.gitea.io/gitea/modules/structs" -) - -// ActivityPub -// swagger:response ActivityPub -type swaggerResponseActivityPub struct { - // in:body - Body api.ActivityPub `json:"body"` -} diff --git a/routers/api/v1/user/runners.go b/routers/api/v1/user/runners.go index be3f63cc5e1..77b0f59c1ed 100644 --- a/routers/api/v1/user/runners.go +++ b/routers/api/v1/user/runners.go @@ -10,21 +10,6 @@ import ( // https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization -// GetRegistrationToken returns the token to register user runners -func GetRegistrationToken(ctx *context.APIContext) { - // swagger:operation GET /user/actions/runners/registration-token user userGetRunnerRegistrationToken - // --- - // summary: Get an user's actions runner registration token - // produces: - // - application/json - // parameters: - // responses: - // "200": - // "$ref": "#/responses/RegistrationToken" - - shared.GetRegistrationToken(ctx, ctx.Doer.ID, 0) -} - // CreateRegistrationToken returns the token to register user runners func CreateRegistrationToken(ctx *context.APIContext) { // swagger:operation POST /user/actions/runners/registration-token user userCreateRunnerRegistrationToken diff --git a/routers/common/errpage.go b/routers/common/errpage.go index b14ab8bcf80..2406cf443fa 100644 --- a/routers/common/errpage.go +++ b/routers/common/errpage.go @@ -13,6 +13,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/reqctx" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/web/middleware" @@ -36,9 +37,7 @@ func renderServerErrorPage(w http.ResponseWriter, req *http.Request, respCode in w.Header().Set(`X-Frame-Options`, setting.Security.XFrameOptions) } - tmplCtx := context.NewTemplateContext(req.Context(), req) - tmplCtx["Locale"] = middleware.Locale(w, req) - + tmplCtx := context.NewTemplateContextForWeb(reqctx.FromContext(req.Context()), req, middleware.Locale(w, req)) w.WriteHeader(respCode) outBuf := &bytes.Buffer{} diff --git a/routers/common/maintenancemode.go b/routers/common/maintenancemode.go new file mode 100644 index 00000000000..b5827ac94f4 --- /dev/null +++ b/routers/common/maintenancemode.go @@ -0,0 +1,43 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package common + +import ( + "net/http" + "strings" + + "code.gitea.io/gitea/modules/setting" +) + +func isMaintenanceModeAllowedRequest(req *http.Request) bool { + if strings.HasPrefix(req.URL.Path, "/-/") { + // URLs like "/-/admin", "/-/fetch-redirect" and "/-/markup" are still accessible in maintenance mode + return true + } + if strings.HasPrefix(req.URL.Path, "/api/internal/") { + // internal APIs should be allowed + return true + } + if strings.HasPrefix(req.URL.Path, "/user/") { + // URLs like "/user/signin" and "/user/signup" are still accessible in maintenance mode + return true + } + if strings.HasPrefix(req.URL.Path, "/assets/") { + return true + } + return false +} + +func MaintenanceModeHandler() func(h http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + maintenanceMode := setting.Config().Instance.MaintenanceMode.Value(req.Context()) + if maintenanceMode.IsActive() && !isMaintenanceModeAllowedRequest(req) { + renderServiceUnavailable(resp, req) + return + } + next.ServeHTTP(resp, req) + }) + } +} diff --git a/routers/common/markup.go b/routers/common/markup.go index e189bcdecf4..27c95eedb86 100644 --- a/routers/common/markup.go +++ b/routers/common/markup.go @@ -46,7 +46,7 @@ func RenderMarkup(ctx *context.Base, ctxRepo *context.Repository, mode, text, ur return } - // Ideally, this handler should be called with RepoAssigment and get the related repo from context "/owner/repo/markup" + // Ideally, this handler should be called with RepoAssignment and get the related repo from context "/owner/repo/markup" // then render could use the repo to do various things (the permission check has passed) // // However, this handler is also exposed as "/markup" without any repo context, diff --git a/routers/common/serve.go b/routers/common/serve.go index 862230b30f0..4bb1a48b0da 100644 --- a/routers/common/serve.go +++ b/routers/common/serve.go @@ -20,7 +20,7 @@ import ( // ServeBlob download a git.Blob func ServeBlob(ctx *context.Base, repo *repo_model.Repository, filePath string, blob *git.Blob, lastModified *time.Time) error { - if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) { + if httpcache.HandleGenericETagPrivateCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) { return nil } diff --git a/routers/init.go b/routers/init.go index 82a5378263d..8874236a607 100644 --- a/routers/init.go +++ b/routers/init.go @@ -181,6 +181,7 @@ func InitWebInstalled(ctx context.Context) { func NormalRoutes() *web.Router { r := web.NewRouter() r.Use(common.ProtocolMiddlewares()...) + r.Use(common.MaintenanceModeHandler()) r.Mount("/", web_routers.Routes()) r.Mount("/api/v1", apiv1.Routes()) diff --git a/routers/install/install.go b/routers/install/install.go index 399128b6ed4..81fcdfa384c 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -17,7 +17,6 @@ import ( "code.gitea.io/gitea/models/db" db_install "code.gitea.io/gitea/models/db/install" - system_model "code.gitea.io/gitea/models/system" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/auth/password/hash" "code.gitea.io/gitea/modules/generate" @@ -114,11 +113,6 @@ func Install(ctx *context.Context) { form.RegisterConfirm = setting.Service.RegisterEmailConfirm form.MailNotify = setting.Service.EnableNotifyMail - // Server and other services settings - form.OfflineMode = setting.OfflineMode - form.DisableGravatar = setting.DisableGravatar // when installing, there is no database connection so that given a default value - form.EnableFederatedAvatar = setting.EnableFederatedAvatar // when installing, there is no database connection so that given a default value - form.EnableOpenIDSignIn = setting.Service.EnableOpenIDSignIn form.EnableOpenIDSignUp = setting.Service.EnableOpenIDSignUp form.DisableRegistration = setting.Service.DisableRegistration @@ -141,7 +135,7 @@ func checkDatabase(ctx *context.Context, form *forms.InstallForm) bool { if (setting.Database.Type == "sqlite3") && len(setting.Database.Path) == 0 { ctx.Data["Err_DbPath"] = true - ctx.RenderWithErr(ctx.Tr("install.err_empty_db_path"), tplInstall, form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.err_empty_db_path"), tplInstall, form) return false } @@ -152,10 +146,10 @@ func checkDatabase(ctx *context.Context, form *forms.InstallForm) bool { if err = db.InitEngine(ctx); err != nil { if strings.Contains(err.Error(), `Unknown database type: sqlite3`) { ctx.Data["Err_DbType"] = true - ctx.RenderWithErr(ctx.Tr("install.sqlite3_not_available", "https://docs.gitea.com/installation/install-from-binary"), tplInstall, form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.sqlite3_not_available", "https://docs.gitea.com/installation/install-from-binary"), tplInstall, form) } else { ctx.Data["Err_DbSetting"] = true - ctx.RenderWithErr(ctx.Tr("install.invalid_db_setting", err), tplInstall, form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_db_setting", err), tplInstall, form) } return false } @@ -163,20 +157,20 @@ func checkDatabase(ctx *context.Context, form *forms.InstallForm) bool { err = db_install.CheckDatabaseConnection(ctx) if err != nil { ctx.Data["Err_DbSetting"] = true - ctx.RenderWithErr(ctx.Tr("install.invalid_db_setting", err), tplInstall, form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_db_setting", err), tplInstall, form) return false } hasPostInstallationUser, err := db_install.HasPostInstallationUsers(ctx) if err != nil { ctx.Data["Err_DbSetting"] = true - ctx.RenderWithErr(ctx.Tr("install.invalid_db_table", "user", err), tplInstall, form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_db_table", "user", err), tplInstall, form) return false } dbMigrationVersion, err := db_install.GetMigrationVersion(ctx) if err != nil { ctx.Data["Err_DbSetting"] = true - ctx.RenderWithErr(ctx.Tr("install.invalid_db_table", "version", err), tplInstall, form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_db_table", "version", err), tplInstall, form) return false } @@ -185,7 +179,7 @@ func checkDatabase(ctx *context.Context, form *forms.InstallForm) bool { confirmed := form.ReinstallConfirmFirst && form.ReinstallConfirmSecond && form.ReinstallConfirmThird if !confirmed { ctx.Data["Err_DbInstalledBefore"] = true - ctx.RenderWithErr(ctx.Tr("install.reinstall_error"), tplInstall, form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.reinstall_error"), tplInstall, form) return false } @@ -225,7 +219,7 @@ func SubmitInstall(ctx *context.Context) { } if _, err = exec.LookPath("git"); err != nil { - ctx.RenderWithErr(ctx.Tr("install.test_git_failed", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.test_git_failed", err), tplInstall, &form) return } @@ -248,7 +242,7 @@ func SubmitInstall(ctx *context.Context) { // Prepare AppDataPath, it is very important for Gitea if err = setting.PrepareAppDataPath(); err != nil { - ctx.RenderWithErr(ctx.Tr("install.invalid_app_data_path", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_app_data_path", err), tplInstall, &form) return } @@ -256,7 +250,7 @@ func SubmitInstall(ctx *context.Context) { form.RepoRootPath = strings.ReplaceAll(form.RepoRootPath, "\\", "/") if err = os.MkdirAll(form.RepoRootPath, os.ModePerm); err != nil { ctx.Data["Err_RepoRootPath"] = true - ctx.RenderWithErr(ctx.Tr("install.invalid_repo_path", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_repo_path", err), tplInstall, &form) return } @@ -265,7 +259,7 @@ func SubmitInstall(ctx *context.Context) { form.LFSRootPath = strings.ReplaceAll(form.LFSRootPath, "\\", "/") if err := os.MkdirAll(form.LFSRootPath, os.ModePerm); err != nil { ctx.Data["Err_LFSRootPath"] = true - ctx.RenderWithErr(ctx.Tr("install.invalid_lfs_path", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_lfs_path", err), tplInstall, &form) return } } @@ -274,14 +268,14 @@ func SubmitInstall(ctx *context.Context) { form.LogRootPath = strings.ReplaceAll(form.LogRootPath, "\\", "/") if err = os.MkdirAll(form.LogRootPath, os.ModePerm); err != nil { ctx.Data["Err_LogRootPath"] = true - ctx.RenderWithErr(ctx.Tr("install.invalid_log_root_path", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_log_root_path", err), tplInstall, &form) return } currentUser, match := setting.IsRunUserMatchCurrentUser(form.RunUser) if !match { ctx.Data["Err_RunUser"] = true - ctx.RenderWithErr(ctx.Tr("install.run_user_not_match", form.RunUser, currentUser), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.run_user_not_match", form.RunUser, currentUser), tplInstall, &form) return } @@ -289,7 +283,7 @@ func SubmitInstall(ctx *context.Context) { if form.DisableRegistration && len(form.AdminName) == 0 { ctx.Data["Err_Services"] = true ctx.Data["Err_Admin"] = true - ctx.RenderWithErr(ctx.Tr("install.no_admin_and_disable_registration"), tplInstall, form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.no_admin_and_disable_registration"), tplInstall, form) return } @@ -300,33 +294,33 @@ func SubmitInstall(ctx *context.Context) { ctx.Data["Err_Admin"] = true ctx.Data["Err_AdminName"] = true if db.IsErrNameReserved(err) { - ctx.RenderWithErr(ctx.Tr("install.err_admin_name_is_reserved"), tplInstall, form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.err_admin_name_is_reserved"), tplInstall, form) return } else if db.IsErrNamePatternNotAllowed(err) { - ctx.RenderWithErr(ctx.Tr("install.err_admin_name_pattern_not_allowed"), tplInstall, form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.err_admin_name_pattern_not_allowed"), tplInstall, form) return } - ctx.RenderWithErr(ctx.Tr("install.err_admin_name_is_invalid"), tplInstall, form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.err_admin_name_is_invalid"), tplInstall, form) return } // Check Admin email if len(form.AdminEmail) == 0 { ctx.Data["Err_Admin"] = true ctx.Data["Err_AdminEmail"] = true - ctx.RenderWithErr(ctx.Tr("install.err_empty_admin_email"), tplInstall, form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.err_empty_admin_email"), tplInstall, form) return } // Check admin password. if len(form.AdminPasswd) == 0 { ctx.Data["Err_Admin"] = true ctx.Data["Err_AdminPasswd"] = true - ctx.RenderWithErr(ctx.Tr("install.err_empty_admin_password"), tplInstall, form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.err_empty_admin_password"), tplInstall, form) return } if form.AdminPasswd != form.AdminConfirmPasswd { ctx.Data["Err_Admin"] = true ctx.Data["Err_AdminPasswd"] = true - ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplInstall, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.password_not_match"), tplInstall, form) return } } @@ -335,7 +329,7 @@ func SubmitInstall(ctx *context.Context) { if err = db.InitEngineWithMigration(ctx, versioned_migration.Migrate); err != nil { db.UnsetDefaultEngine() ctx.Data["Err_DbSetting"] = true - ctx.RenderWithErr(ctx.Tr("install.invalid_db_setting", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_db_setting", err), tplInstall, &form) return } @@ -379,7 +373,7 @@ func SubmitInstall(ctx *context.Context) { cfg.Section("lfs").Key("PATH").SetValue(form.LFSRootPath) var lfsJwtSecret string if _, lfsJwtSecret, err = generate.NewJwtSecretWithBase64(); err != nil { - ctx.RenderWithErr(ctx.Tr("install.lfs_jwt_secret_failed", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.lfs_jwt_secret_failed", err), tplInstall, &form) return } cfg.Section("server").Key("LFS_JWT_SECRET").SetValue(lfsJwtSecret) @@ -389,7 +383,7 @@ func SubmitInstall(ctx *context.Context) { if len(strings.TrimSpace(form.SMTPAddr)) > 0 { if _, err := mail.ParseAddress(form.SMTPFrom); err != nil { - ctx.RenderWithErr(ctx.Tr("install.smtp_from_invalid"), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.smtp_from_invalid"), tplInstall, &form) return } @@ -405,15 +399,6 @@ func SubmitInstall(ctx *context.Context) { cfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").SetValue(strconv.FormatBool(form.RegisterConfirm)) cfg.Section("service").Key("ENABLE_NOTIFY_MAIL").SetValue(strconv.FormatBool(form.MailNotify)) - cfg.Section("server").Key("OFFLINE_MODE").SetValue(strconv.FormatBool(form.OfflineMode)) - if err := system_model.SetSettings(ctx, map[string]string{ - setting.Config().Picture.DisableGravatar.DynKey(): strconv.FormatBool(form.DisableGravatar), - setting.Config().Picture.EnableFederatedAvatar.DynKey(): strconv.FormatBool(form.EnableFederatedAvatar), - }); err != nil { - ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form) - return - } - cfg.Section("openid").Key("ENABLE_OPENID_SIGNIN").SetValue(strconv.FormatBool(form.EnableOpenIDSignIn)) cfg.Section("openid").Key("ENABLE_OPENID_SIGNUP").SetValue(strconv.FormatBool(form.EnableOpenIDSignUp)) cfg.Section("service").Key("DISABLE_REGISTRATION").SetValue(strconv.FormatBool(form.DisableRegistration)) @@ -443,7 +428,7 @@ func SubmitInstall(ctx *context.Context) { if setting.InternalToken == "" { var internalToken string if internalToken, err = generate.NewInternalToken(); err != nil { - ctx.RenderWithErr(ctx.Tr("install.internal_token_failed", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.internal_token_failed", err), tplInstall, &form) return } cfg.Section("security").Key("INTERNAL_TOKEN").SetValue(internalToken) @@ -454,7 +439,7 @@ func SubmitInstall(ctx *context.Context) { if !cfg.Section("oauth2").HasKey("JWT_SECRET") && !cfg.Section("oauth2").HasKey("JWT_SECRET_URI") { _, jwtSecretBase64, err := generate.NewJwtSecretWithBase64() if err != nil { - ctx.RenderWithErr(ctx.Tr("install.secret_key_failed", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.secret_key_failed", err), tplInstall, &form) return } cfg.Section("oauth2").Key("JWT_SECRET").SetValue(jwtSecretBase64) @@ -464,7 +449,7 @@ func SubmitInstall(ctx *context.Context) { if setting.SecretKey == "" { var secretKey string if secretKey, err = generate.NewSecretKey(); err != nil { - ctx.RenderWithErr(ctx.Tr("install.secret_key_failed", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.secret_key_failed", err), tplInstall, &form) return } cfg.Section("security").Key("SECRET_KEY").SetValue(secretKey) @@ -474,7 +459,7 @@ func SubmitInstall(ctx *context.Context) { var algorithm *hash.PasswordHashAlgorithm setting.PasswordHashAlgo, algorithm = hash.SetDefaultPasswordHashAlgorithm(form.PasswordAlgorithm) if algorithm == nil { - ctx.RenderWithErr(ctx.Tr("install.invalid_password_algorithm"), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_password_algorithm"), tplInstall, &form) return } cfg.Section("security").Key("PASSWORD_HASH_ALGO").SetValue(form.PasswordAlgorithm) @@ -484,14 +469,14 @@ func SubmitInstall(ctx *context.Context) { err = os.MkdirAll(filepath.Dir(setting.CustomConf), os.ModePerm) if err != nil { - ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.save_config_failed", err), tplInstall, &form) return } setting.EnvironmentToConfig(cfg, os.Environ()) if err = cfg.SaveTo(setting.CustomConf); err != nil { - ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.save_config_failed", err), tplInstall, &form) return } @@ -527,7 +512,7 @@ func SubmitInstall(ctx *context.Context) { setting.InstallLock = false ctx.Data["Err_AdminName"] = true ctx.Data["Err_AdminEmail"] = true - ctx.RenderWithErr(ctx.Tr("install.invalid_admin_setting", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_admin_setting", err), tplInstall, &form) return } log.Info("Admin account already exist") @@ -544,16 +529,16 @@ func SubmitInstall(ctx *context.Context) { // Auto-login for admin if err = ctx.Session.Set("uid", u.ID); err != nil { - ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.save_config_failed", err), tplInstall, &form) return } if err = ctx.Session.Set("uname", u.Name); err != nil { - ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.save_config_failed", err), tplInstall, &form) return } if err = ctx.Session.Release(); err != nil { - ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.save_config_failed", err), tplInstall, &form) return } } diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go index e8bef7d6c14..b595a95b23f 100644 --- a/routers/private/hook_post_receive.go +++ b/routers/private/hook_post_receive.go @@ -106,10 +106,10 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { } if update.IsDelRef() { - if err := git_model.AddDeletedBranch(ctx, repo.ID, update.RefFullName.BranchName(), update.PusherID); err != nil { - log.Error("Failed to add deleted branch: %s/%s Error: %v", ownerName, repoName, err) + if err := git_model.MarkBranchAsDeleted(ctx, repo.ID, update.RefFullName.BranchName(), update.PusherID); err != nil { + log.Error("Failed to mark branch as deleted: %s/%s Error: %v", ownerName, repoName, err) ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{ - Err: fmt.Sprintf("Failed to add deleted branch: %s/%s Error: %v", ownerName, repoName, err), + Err: fmt.Sprintf("Failed to mark branch as deleted: %s/%s Error: %v", ownerName, repoName, err), }) return } diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index 39955116c42..704b777dbf7 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -151,7 +151,11 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r gitRepo := ctx.Repo.GitRepo objectFormat := ctx.Repo.GetObjectFormat() - if branchName == repo.DefaultBranch && newCommitID == objectFormat.EmptyObjectID().String() { + defaultBranch := repo.DefaultBranch + if ctx.opts.IsWiki && repo.DefaultWikiBranch != "" { + defaultBranch = repo.DefaultWikiBranch + } + if branchName == defaultBranch && newCommitID == objectFormat.EmptyObjectID().String() { log.Warn("Forbidden: Branch: %s is the default branch in %-v and cannot be deleted", branchName, repo) ctx.JSON(http.StatusForbidden, private.Response{ UserMsg: fmt.Sprintf("branch %s is the default branch and cannot be deleted", branchName), diff --git a/routers/private/internal.go b/routers/private/internal.go index 55a11aa3dda..2d5436468b6 100644 --- a/routers/private/internal.go +++ b/routers/private/internal.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/common" + "code.gitea.io/gitea/routers/web/misc" "code.gitea.io/gitea/services/context" "gitea.com/go-chi/binding" @@ -59,6 +60,7 @@ func Routes() *web.Router { // Since internal API will be sent only from Gitea sub commands and it's under control (checked by InternalToken), we can trust the headers. r.Use(chi_middleware.RealIP) + r.Get("/dummy", misc.DummyOK) r.Post("/ssh/authorized_keys", AuthorizedPublicKeyByContent) r.Post("/ssh/{id}/update/{repoid}", UpdatePublicKeyInRepo) r.Post("/ssh/log", bind(private.SSHLogOption{}), SSHLog) diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index 3407789f2f9..e29aca127c8 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -273,7 +273,7 @@ func NewAuthSourcePost(ctx *context.Context) { discoveryURL, err := url.Parse(oauth2Config.OpenIDConnectAutoDiscoveryURL) if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") { ctx.Data["Err_DiscoveryURL"] = true - ctx.RenderWithErr(ctx.Tr("admin.auths.invalid_openIdConnectAutoDiscoveryURL"), tplAuthNew, form) + ctx.RenderWithErrDeprecated(ctx.Tr("admin.auths.invalid_openIdConnectAutoDiscoveryURL"), tplAuthNew, form) return } } @@ -281,13 +281,13 @@ func NewAuthSourcePost(ctx *context.Context) { var err error config, err = parseSSPIConfig(ctx, form) if err != nil { - ctx.RenderWithErr(err.Error(), tplAuthNew, form) + ctx.RenderWithErrDeprecated(err.Error(), tplAuthNew, form) return } existing, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{LoginType: auth.SSPI}) if err != nil || len(existing) > 0 { ctx.Data["Err_Type"] = true - ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_of_type_exist"), tplAuthNew, form) + ctx.RenderWithErrDeprecated(ctx.Tr("admin.auths.login_source_of_type_exist"), tplAuthNew, form) return } default: @@ -311,11 +311,11 @@ func NewAuthSourcePost(ctx *context.Context) { }); err != nil { if auth.IsErrSourceAlreadyExist(err) { ctx.Data["Err_Name"] = true - ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_exist", err.(auth.ErrSourceAlreadyExist).Name), tplAuthNew, form) + ctx.RenderWithErrDeprecated(ctx.Tr("admin.auths.login_source_exist", err.(auth.ErrSourceAlreadyExist).Name), tplAuthNew, form) } else if oauth2.IsErrOpenIDConnectInitialize(err) { ctx.Data["Err_DiscoveryURL"] = true unwrapped := err.(oauth2.ErrOpenIDConnectInitialize).Unwrap() - ctx.RenderWithErr(ctx.Tr("admin.auths.unable_to_initialize_openid", unwrapped), tplAuthNew, form) + ctx.RenderWithErrDeprecated(ctx.Tr("admin.auths.unable_to_initialize_openid", unwrapped), tplAuthNew, form) } else { ctx.ServerError("auth.CreateSource", err) } @@ -403,14 +403,14 @@ func EditAuthSourcePost(ctx *context.Context) { discoveryURL, err := url.Parse(oauth2Config.OpenIDConnectAutoDiscoveryURL) if err != nil || (discoveryURL.Scheme != "http" && discoveryURL.Scheme != "https") { ctx.Data["Err_DiscoveryURL"] = true - ctx.RenderWithErr(ctx.Tr("admin.auths.invalid_openIdConnectAutoDiscoveryURL"), tplAuthEdit, form) + ctx.RenderWithErrDeprecated(ctx.Tr("admin.auths.invalid_openIdConnectAutoDiscoveryURL"), tplAuthEdit, form) return } } case auth.SSPI: config, err = parseSSPIConfig(ctx, form) if err != nil { - ctx.RenderWithErr(err.Error(), tplAuthEdit, form) + ctx.RenderWithErrDeprecated(err.Error(), tplAuthEdit, form) return } default: @@ -426,7 +426,7 @@ func EditAuthSourcePost(ctx *context.Context) { if err := auth.UpdateSource(ctx, source); err != nil { if auth.IsErrSourceAlreadyExist(err) { ctx.Data["Err_Name"] = true - ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_exist", err.(auth.ErrSourceAlreadyExist).Name), tplAuthEdit, form) + ctx.RenderWithErrDeprecated(ctx.Tr("admin.auths.login_source_exist", err.(auth.ErrSourceAlreadyExist).Name), tplAuthEdit, form) } else if oauth2.IsErrOpenIDConnectInitialize(err) { ctx.Flash.Error(err.Error(), true) ctx.Data["Err_DiscoveryURL"] = true diff --git a/routers/web/admin/config.go b/routers/web/admin/config.go index 774b31ab984..a449796ec12 100644 --- a/routers/web/admin/config.go +++ b/routers/web/admin/config.go @@ -5,9 +5,9 @@ package admin import ( + "errors" "net/http" "net/url" - "strconv" "strings" system_model "code.gitea.io/gitea/models/system" @@ -126,7 +126,6 @@ func Config(ctx *context.Context) { ctx.Data["AppUrl"] = setting.AppURL ctx.Data["AppBuiltWith"] = setting.AppBuiltWith ctx.Data["Domain"] = setting.Domain - ctx.Data["OfflineMode"] = setting.OfflineMode ctx.Data["RunUser"] = setting.RunUser ctx.Data["RunMode"] = util.ToTitleCase(setting.RunMode) ctx.Data["GitVersion"] = git.DefaultFeatures().VersionInfo() @@ -145,7 +144,6 @@ func Config(ctx *context.Context) { ctx.Data["Service"] = setting.Service ctx.Data["DbCfg"] = setting.Database ctx.Data["Webhook"] = setting.Webhook - ctx.Data["MailerEnabled"] = false if setting.MailService != nil { ctx.Data["MailerEnabled"] = true @@ -191,52 +189,27 @@ func ConfigSettings(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("admin.config_settings") ctx.Data["PageIsAdminConfig"] = true ctx.Data["PageIsAdminConfigSettings"] = true - ctx.Data["DefaultOpenWithEditorAppsString"] = setting.DefaultOpenWithEditorApps().ToTextareaString() ctx.HTML(http.StatusOK, tplConfigSettings) } +func validateConfigKeyValue(dynKey, input string) error { + opt := config.GetConfigOption(dynKey) + if opt == nil { + return util.NewInvalidArgumentErrorf("unknown config key: %s", dynKey) + } + + const limit = 64 * 1024 + if len(input) > limit { + return util.NewInvalidArgumentErrorf("value length exceeds limit of %d", limit) + } + + if !json.Valid([]byte(input)) { + return util.NewInvalidArgumentErrorf("invalid json value for key: %s", dynKey) + } + return nil +} + func ChangeConfig(ctx *context.Context) { - cfg := setting.Config() - - marshalBool := func(v string) ([]byte, error) { - b, _ := strconv.ParseBool(v) - return json.Marshal(b) - } - - marshalString := func(emptyDefault string) func(v string) ([]byte, error) { - return func(v string) ([]byte, error) { - return json.Marshal(util.IfZero(v, emptyDefault)) - } - } - - marshalOpenWithApps := func(value string) ([]byte, error) { - // TODO: move the block alongside OpenWithEditorAppsType.ToTextareaString - lines := strings.Split(value, "\n") - var openWithEditorApps setting.OpenWithEditorAppsType - for _, line := range lines { - line = strings.TrimSpace(line) - if line == "" { - continue - } - displayName, openURL, ok := strings.Cut(line, "=") - displayName, openURL = strings.TrimSpace(displayName), strings.TrimSpace(openURL) - if !ok || displayName == "" || openURL == "" { - continue - } - openWithEditorApps = append(openWithEditorApps, setting.OpenWithEditorApp{ - DisplayName: strings.TrimSpace(displayName), - OpenURL: strings.TrimSpace(openURL), - }) - } - return json.Marshal(openWithEditorApps) - } - marshallers := map[string]func(string) ([]byte, error){ - cfg.Picture.DisableGravatar.DynKey(): marshalBool, - cfg.Picture.EnableFederatedAvatar.DynKey(): marshalBool, - cfg.Repository.OpenWithEditorApps.DynKey(): marshalOpenWithApps, - cfg.Repository.GitGuideRemoteName.DynKey(): marshalString(cfg.Repository.GitGuideRemoteName.DefaultValue()), - } - _ = ctx.Req.ParseForm() configKeys := ctx.Req.Form["key"] configValues := ctx.Req.Form["value"] @@ -249,18 +222,16 @@ loop: } value := configValues[i] - marshaller, hasMarshaller := marshallers[key] - if !hasMarshaller { - ctx.JSONError(ctx.Tr("admin.config.set_setting_failed", key)) - break loop - } - - marshaledValue, err := marshaller(value) + err := validateConfigKeyValue(key, value) if err != nil { - ctx.JSONError(ctx.Tr("admin.config.set_setting_failed", key)) + if errors.Is(err, util.ErrInvalidArgument) { + ctx.JSONError(err.Error()) + } else { + ctx.JSONError(ctx.Tr("admin.config.set_setting_failed", key)) + } break loop } - configSettings[key] = string(marshaledValue) + configSettings[key] = value } if ctx.Written() { return diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index ed0eecf90a6..19499fdab56 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -151,12 +151,12 @@ func NewUserPost(ctx *context.Context) { if u.LoginType == auth.NoType || u.LoginType == auth.Plain { if len(form.Password) < setting.MinPasswordLength { ctx.Data["Err_Password"] = true - ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplUserNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplUserNew, &form) return } if !password.IsComplexEnough(form.Password) { ctx.Data["Err_Password"] = true - ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplUserNew, &form) + ctx.RenderWithErrDeprecated(password.BuildComplexityError(ctx.Locale), tplUserNew, &form) return } if err := password.IsPwned(ctx, form.Password); err != nil { @@ -166,7 +166,7 @@ func NewUserPost(ctx *context.Context) { log.Error(err.Error()) errMsg = ctx.Tr("auth.password_pwned_err") } - ctx.RenderWithErr(errMsg, tplUserNew, &form) + ctx.RenderWithErrDeprecated(errMsg, tplUserNew, &form) return } u.MustChangePassword = form.MustChangePassword @@ -176,22 +176,22 @@ func NewUserPost(ctx *context.Context) { switch { case user_model.IsErrUserAlreadyExist(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplUserNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.username_been_taken"), tplUserNew, &form) case user_model.IsErrEmailAlreadyUsed(err): ctx.Data["Err_Email"] = true - ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.email_been_used"), tplUserNew, &form) case user_model.IsErrEmailInvalid(err), user_model.IsErrEmailCharIsNotSupported(err): ctx.Data["Err_Email"] = true - ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.email_invalid"), tplUserNew, &form) case db.IsErrNameReserved(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", err.(db.ErrNameReserved).Name), tplUserNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("user.form.name_reserved", err.(db.ErrNameReserved).Name), tplUserNew, &form) case db.IsErrNamePatternNotAllowed(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplUserNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("user.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplUserNew, &form) case db.IsErrNameCharsNotAllowed(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_chars_not_allowed", err.(db.ErrNameCharsNotAllowed).Name), tplUserNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("user.form.name_chars_not_allowed", err.(db.ErrNameCharsNotAllowed).Name), tplUserNew, &form) default: ctx.ServerError("CreateUser", err) } @@ -349,19 +349,19 @@ func EditUserPost(ctx *context.Context) { switch { case user_model.IsErrUserIsNotLocal(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("form.username_change_not_local_user"), tplUserEdit, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.username_change_not_local_user"), tplUserEdit, &form) case user_model.IsErrUserAlreadyExist(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tplUserEdit, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.username_been_taken"), tplUserEdit, &form) case db.IsErrNameReserved(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", form.UserName), tplUserEdit, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("user.form.name_reserved", form.UserName), tplUserEdit, &form) case db.IsErrNamePatternNotAllowed(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", form.UserName), tplUserEdit, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("user.form.name_pattern_not_allowed", form.UserName), tplUserEdit, &form) case db.IsErrNameCharsNotAllowed(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_chars_not_allowed", form.UserName), tplUserEdit, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("user.form.name_chars_not_allowed", form.UserName), tplUserEdit, &form) default: ctx.ServerError("RenameUser", err) } @@ -392,16 +392,16 @@ func EditUserPost(ctx *context.Context) { switch { case errors.Is(err, password.ErrMinLength): ctx.Data["Err_Password"] = true - ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplUserEdit, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplUserEdit, &form) case errors.Is(err, password.ErrComplexity): ctx.Data["Err_Password"] = true - ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplUserEdit, &form) + ctx.RenderWithErrDeprecated(password.BuildComplexityError(ctx.Locale), tplUserEdit, &form) case errors.Is(err, password.ErrIsPwned): ctx.Data["Err_Password"] = true - ctx.RenderWithErr(ctx.Tr("auth.password_pwned", "https://haveibeenpwned.com/Passwords"), tplUserEdit, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.password_pwned", "https://haveibeenpwned.com/Passwords"), tplUserEdit, &form) case password.IsErrIsPwnedRequest(err): ctx.Data["Err_Password"] = true - ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplUserEdit, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.password_pwned_err"), tplUserEdit, &form) default: ctx.ServerError("UpdateUser", err) } @@ -413,10 +413,10 @@ func EditUserPost(ctx *context.Context) { switch { case user_model.IsErrEmailCharIsNotSupported(err), user_model.IsErrEmailInvalid(err): ctx.Data["Err_Email"] = true - ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplUserEdit, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.email_invalid"), tplUserEdit, &form) case user_model.IsErrEmailAlreadyUsed(err): ctx.Data["Err_Email"] = true - ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplUserEdit, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.email_been_used"), tplUserEdit, &form) default: ctx.ServerError("AddOrSetPrimaryEmailAddress", err) } @@ -444,7 +444,7 @@ func EditUserPost(ctx *context.Context) { if err := user_service.UpdateUser(ctx, u, opts); err != nil { if user_model.IsErrDeleteLastAdminUser(err) { - ctx.RenderWithErr(ctx.Tr("auth.last_admin"), tplUserEdit, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.last_admin"), tplUserEdit, &form) } else { ctx.ServerError("UpdateUser", err) } diff --git a/routers/web/auth/2fa.go b/routers/web/auth/2fa.go index a19c9d7aca3..73b218d92df 100644 --- a/routers/web/auth/2fa.go +++ b/routers/web/auth/2fa.go @@ -92,7 +92,7 @@ func TwoFactorPost(ctx *context.Context) { return } - ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplTwofa, forms.TwoFactorAuthForm{}) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.twofa_passcode_incorrect"), tplTwofa, forms.TwoFactorAuthForm{}) } // TwoFactorScratch shows the scratch code form for two-factor authentication. @@ -160,5 +160,5 @@ func TwoFactorScratchPost(ctx *context.Context) { return } - ctx.RenderWithErr(ctx.Tr("auth.twofa_scratch_token_incorrect"), tplTwofaScratch, forms.TwoFactorScratchAuthForm{}) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.twofa_scratch_token_incorrect"), tplTwofaScratch, forms.TwoFactorScratchAuthForm{}) } diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index bc0939d92a1..fbc3f9f07db 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -162,6 +162,11 @@ func consumeAuthRedirectLink(ctx *context.Context) string { } func redirectAfterAuth(ctx *context.Context) { + if setting.Config().Instance.MaintenanceMode.Value(ctx).IsActive() { + // in maintenance mode, redirect to admin dashboard, it is the only accessible page + ctx.Redirect(setting.AppSubURL + "/-/admin") + return + } ctx.RedirectToCurrentSite(consumeAuthRedirectLink(ctx)) } @@ -240,10 +245,10 @@ func SignInPost(ctx *context.Context) { u, source, err := auth_service.UserSignIn(ctx, form.UserName, form.Password) if err != nil { if errors.Is(err, util.ErrNotExist) || errors.Is(err, util.ErrInvalidArgument) { - ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form) log.Warn("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err) } else if user_model.IsErrEmailAlreadyUsed(err) { - ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tplSignIn, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.email_been_used"), tplSignIn, &form) log.Warn("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err) } else if user_model.IsErrUserProhibitLogin(err) { log.Warn("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err) @@ -420,8 +425,21 @@ func SignOut(ctx *context.Context) { Data: ctx.Session.ID(), }) } + + // prepare the sign-out URL before destroying the session + redirectTo := buildSignOutRedirectURL(ctx) HandleSignOut(ctx) - ctx.JSONRedirect(setting.AppSubURL + "/") + ctx.Redirect(redirectTo) +} + +func buildSignOutRedirectURL(ctx *context.Context) string { + // TODO: can also support REVERSE_PROXY_AUTHENTICATION logout URL in the future + if ctx.Doer != nil && ctx.Doer.LoginType == auth.OAuth2 { + if s := buildOIDCEndSessionURL(ctx, ctx.Doer); s != "" { + return s + } + } + return setting.AppSubURL + "/" } // SignUp render the register page @@ -486,23 +504,23 @@ func SignUpPost(ctx *context.Context) { } if !form.IsEmailDomainAllowed() { - ctx.RenderWithErr(ctx.Tr("auth.email_domain_blacklisted"), tplSignUp, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.email_domain_blacklisted"), tplSignUp, &form) return } if form.Password != form.Retype { ctx.Data["Err_Password"] = true - ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplSignUp, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.password_not_match"), tplSignUp, &form) return } if len(form.Password) < setting.MinPasswordLength { ctx.Data["Err_Password"] = true - ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplSignUp, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplSignUp, &form) return } if !password.IsComplexEnough(form.Password) { ctx.Data["Err_Password"] = true - ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplSignUp, &form) + ctx.RenderWithErrDeprecated(password.BuildComplexityError(ctx.Locale), tplSignUp, &form) return } if err := password.IsPwned(ctx, form.Password); err != nil { @@ -512,7 +530,7 @@ func SignUpPost(ctx *context.Context) { errMsg = ctx.Tr("auth.password_pwned_err") } ctx.Data["Err_Password"] = true - ctx.RenderWithErr(errMsg, tplSignUp, &form) + ctx.RenderWithErrDeprecated(errMsg, tplSignUp, &form) return } @@ -582,25 +600,25 @@ func createUserInContext(ctx *context.Context, tpl templates.TplName, form any, switch { case user_model.IsErrUserAlreadyExist(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("form.username_been_taken"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.username_been_taken"), tpl, form) case user_model.IsErrEmailAlreadyUsed(err): ctx.Data["Err_Email"] = true - ctx.RenderWithErr(ctx.Tr("form.email_been_used"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.email_been_used"), tpl, form) case user_model.IsErrEmailCharIsNotSupported(err): ctx.Data["Err_Email"] = true - ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.email_invalid"), tpl, form) case user_model.IsErrEmailInvalid(err): ctx.Data["Err_Email"] = true - ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.email_invalid"), tpl, form) case db.IsErrNameReserved(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_reserved", err.(db.ErrNameReserved).Name), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("user.form.name_reserved", err.(db.ErrNameReserved).Name), tpl, form) case db.IsErrNamePatternNotAllowed(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("user.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tpl, form) case db.IsErrNameCharsNotAllowed(err): ctx.Data["Err_UserName"] = true - ctx.RenderWithErr(ctx.Tr("user.form.name_chars_not_allowed", err.(db.ErrNameCharsNotAllowed).Name), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("user.form.name_chars_not_allowed", err.(db.ErrNameCharsNotAllowed).Name), tpl, form) default: ctx.ServerError("CreateUser", err) } diff --git a/routers/web/auth/auth_test.go b/routers/web/auth/auth_test.go index 5ccffe0d505..d1f808181aa 100644 --- a/routers/web/auth/auth_test.go +++ b/routers/web/auth/auth_test.go @@ -5,10 +5,12 @@ package auth import ( "net/http" + "net/http/httptest" "net/url" "testing" auth_model "code.gitea.io/gitea/models/auth" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/session" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" @@ -19,6 +21,7 @@ import ( "github.com/markbates/goth" "github.com/markbates/goth/gothic" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func addOAuth2Source(t *testing.T, authName string, cfg oauth2.Source) { @@ -29,10 +32,10 @@ func addOAuth2Source(t *testing.T, authName string, cfg oauth2.Source) { IsActive: true, Cfg: &cfg, }) - assert.NoError(t, err) + require.NoError(t, err) } -func TestUserLogin(t *testing.T) { +func TestWebAuthUserLogin(t *testing.T) { ctx, resp := contexttest.MockContext(t, "/user/login") SignIn(ctx) assert.Equal(t, http.StatusOK, resp.Code) @@ -60,7 +63,7 @@ func TestUserLogin(t *testing.T) { assert.Equal(t, "/", test.RedirectURL(resp)) } -func TestSignUpOAuth2Login(t *testing.T) { +func TestWebAuthOAuth2(t *testing.T) { defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)() _ = oauth2.Init(t.Context()) @@ -92,4 +95,43 @@ func TestSignUpOAuth2Login(t *testing.T) { assert.Equal(t, "/user/login", test.RedirectURL(resp)) assert.Contains(t, ctx.Flash.ErrorMsg, "auth.oauth.signin.error.general") }) + + t.Run("OIDCLogout", func(t *testing.T) { + var mockServer *httptest.Server + mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/.well-known/openid-configuration": + _, _ = w.Write([]byte(`{ + "issuer": "` + mockServer.URL + `", + "authorization_endpoint": "` + mockServer.URL + `/authorize", + "token_endpoint": "` + mockServer.URL + `/token", + "userinfo_endpoint": "` + mockServer.URL + `/userinfo", + "end_session_endpoint": "https://example.com/oidc-logout?oidc-key=oidc-val" + }`)) + default: + http.NotFound(w, r) + } + })) + defer mockServer.Close() + + addOAuth2Source(t, "oidc-auth-source", oauth2.Source{ + Provider: "openidConnect", + ClientID: "mock-client-id", + OpenIDConnectAutoDiscoveryURL: mockServer.URL + "/.well-known/openid-configuration", + }) + authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), "oidc-auth-source") + require.NoError(t, err) + + mockOpt := contexttest.MockContextOption{SessionStore: session.NewMockMemStore("dummy-sid")} + ctx, resp := contexttest.MockContext(t, "/user/logout", mockOpt) + ctx.Doer = &user_model.User{ID: 1, LoginType: auth_model.OAuth2, LoginSource: authSource.ID} + SignOut(ctx) + assert.Equal(t, http.StatusSeeOther, resp.Code) + u, err := url.Parse(test.RedirectURL(resp)) + require.NoError(t, err) + expectedValues := url.Values{"oidc-key": []string{"oidc-val"}, "post_logout_redirect_uri": []string{setting.AppURL}, "client_id": []string{"mock-client-id"}} + assert.Equal(t, expectedValues, u.Query()) + u.RawQuery = "" + assert.Equal(t, "https://example.com/oidc-logout", u.String()) + }) } diff --git a/routers/web/auth/linkaccount.go b/routers/web/auth/linkaccount.go index c624d896ca7..faa712471f4 100644 --- a/routers/web/auth/linkaccount.go +++ b/routers/web/auth/linkaccount.go @@ -99,10 +99,10 @@ func LinkAccount(ctx *context.Context) { func handleSignInError(ctx *context.Context, userName string, ptrForm any, tmpl templates.TplName, invoker string, err error) { if errors.Is(err, util.ErrNotExist) { - ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tmpl, ptrForm) + ctx.RenderWithErrDeprecated(ctx.Tr("form.username_password_incorrect"), tmpl, ptrForm) } else if errors.Is(err, util.ErrInvalidArgument) { ctx.Data["user_exists"] = true - ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tmpl, ptrForm) + ctx.RenderWithErrDeprecated(ctx.Tr("form.username_password_incorrect"), tmpl, ptrForm) } else if user_model.IsErrUserProhibitLogin(err) { ctx.Data["user_exists"] = true log.Info("Failed authentication attempt for %s from %s: %v", userName, ctx.RemoteAddr(), err) @@ -266,7 +266,7 @@ func LinkAccountPostRegister(ctx *context.Context) { } if !form.IsEmailDomainAllowed() { - ctx.RenderWithErr(ctx.Tr("auth.email_domain_blacklisted"), tplLinkAccount, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.email_domain_blacklisted"), tplLinkAccount, &form) return } @@ -280,12 +280,12 @@ func LinkAccountPostRegister(ctx *context.Context) { } else { if (len(strings.TrimSpace(form.Password)) > 0 || len(strings.TrimSpace(form.Retype)) > 0) && form.Password != form.Retype { ctx.Data["Err_Password"] = true - ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplLinkAccount, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.password_not_match"), tplLinkAccount, &form) return } if len(strings.TrimSpace(form.Password)) > 0 && len(form.Password) < setting.MinPasswordLength { ctx.Data["Err_Password"] = true - ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplLinkAccount, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplLinkAccount, &form) return } } diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index b96ea17bc34..bb0784e434e 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -10,6 +10,7 @@ import ( "html" "io" "net/http" + "net/url" "sort" "strings" @@ -17,6 +18,7 @@ import ( user_model "code.gitea.io/gitea/models/user" auth_module "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/container" + "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/session" @@ -506,3 +508,38 @@ func oAuth2UserLoginCallback(ctx *context.Context, authSource *auth.Source, requ // no user found to login return nil, gothUser, nil } + +// buildOIDCEndSessionURL constructs an OIDC RP-Initiated Logout URL for the +// given user. Returns "" if the user's auth source is not OIDC or doesn't +// advertise an end_session_endpoint. +func buildOIDCEndSessionURL(ctx *context.Context, doer *user_model.User) string { + authSource, err := auth.GetSourceByID(ctx, doer.LoginSource) + if err != nil { + log.Error("Failed to get auth source for OIDC logout (source=%d): %v", doer.LoginSource, err) + return "" + } + + oauth2Cfg, ok := authSource.Cfg.(*oauth2.Source) + if !ok { + return "" + } + + endSessionEndpoint := oauth2.GetOIDCEndSessionEndpoint(authSource.Name) + if endSessionEndpoint == "" { + return "" + } + + endSessionURL, err := url.Parse(endSessionEndpoint) + if err != nil { + log.Error("Failed to parse end_session_endpoint %q: %v", endSessionEndpoint, err) + return "" + } + + // RP-Initiated Logout 1.0: use client_id to identify the client to the IdP. + // https://openid.net/specs/openid-connect-rpinitiated-1_0.html#RPLogout + params := endSessionURL.Query() + params.Set("client_id", oauth2Cfg.ClientID) + params.Set("post_logout_redirect_uri", httplib.GuessCurrentAppURL(ctx)) + endSessionURL.RawQuery = params.Encode() + return endSessionURL.String() +} diff --git a/routers/web/auth/oauth2_provider.go b/routers/web/auth/oauth2_provider.go index e09469526e9..05931d8f596 100644 --- a/routers/web/auth/oauth2_provider.go +++ b/routers/web/auth/oauth2_provider.go @@ -4,6 +4,7 @@ package auth import ( + "errors" "fmt" "html" "html/template" @@ -613,6 +614,14 @@ func handleAuthorizationCode(ctx *context.Context, form forms.AccessTokenForm, s }) return } + if authorizationCode.IsExpired() { + _ = authorizationCode.Invalidate(ctx) + handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ + ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidGrant, + ErrorDescription: "authorization code expired", + }) + return + } // check if code verifier authorizes the client, PKCE support if !authorizationCode.ValidateCodeChallenge(form.CodeVerifier) { handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ @@ -631,9 +640,15 @@ func handleAuthorizationCode(ctx *context.Context, form forms.AccessTokenForm, s } // remove token from database to deny duplicate usage if err := authorizationCode.Invalidate(ctx); err != nil { + errDescription := "cannot process your request" + errCode := oauth2_provider.AccessTokenErrorCodeInvalidRequest + if errors.Is(err, auth.ErrOAuth2AuthorizationCodeInvalidated) { + errDescription = "authorization code already used" + errCode = oauth2_provider.AccessTokenErrorCodeInvalidGrant + } handleAccessTokenError(ctx, oauth2_provider.AccessTokenError{ - ErrorCode: oauth2_provider.AccessTokenErrorCodeInvalidRequest, - ErrorDescription: "cannot proceed your request", + ErrorCode: errCode, + ErrorDescription: errDescription, }) return } diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go index 948e65366e5..c9843146d45 100644 --- a/routers/web/auth/openid.go +++ b/routers/web/auth/openid.go @@ -82,7 +82,7 @@ func SignInOpenIDPost(ctx *context.Context) { id, err := openid.Normalize(form.Openid) if err != nil { - ctx.RenderWithErr(err.Error(), tplSignInOpenID, &form) + ctx.RenderWithErrDeprecated(err.Error(), tplSignInOpenID, &form) return } form.Openid = id @@ -91,7 +91,7 @@ func SignInOpenIDPost(ctx *context.Context) { err = allowedOpenIDURI(id) if err != nil { - ctx.RenderWithErr(err.Error(), tplSignInOpenID, &form) + ctx.RenderWithErrDeprecated(err.Error(), tplSignInOpenID, &form) return } @@ -99,7 +99,7 @@ func SignInOpenIDPost(ctx *context.Context) { url, err := openid.RedirectURL(id, redirectTo, setting.AppURL) if err != nil { log.Error("Error in OpenID redirect URL: %s, %v", redirectTo, err.Error()) - ctx.RenderWithErr("Unable to find OpenID provider in "+redirectTo, tplSignInOpenID, &form) + ctx.RenderWithErrDeprecated("Unable to find OpenID provider in "+redirectTo, tplSignInOpenID, &form) return } @@ -129,7 +129,7 @@ func signInOpenIDVerify(ctx *context.Context) { id, err := openid.Verify(fullURL) if err != nil { - ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{ + ctx.RenderWithErrDeprecated(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{ Openid: id, }) return @@ -143,7 +143,7 @@ func signInOpenIDVerify(ctx *context.Context) { u, err := user_model.GetUserByOpenID(ctx, id) if err != nil { if !user_model.IsErrUserNotExist(err) { - ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{ + ctx.RenderWithErrDeprecated(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{ Openid: id, }) return @@ -162,14 +162,14 @@ func signInOpenIDVerify(ctx *context.Context) { parsedURL, err := url.Parse(fullURL) if err != nil { - ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{ + ctx.RenderWithErrDeprecated(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{ Openid: id, }) return } values, err := url.ParseQuery(parsedURL.RawQuery) if err != nil { - ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{ + ctx.RenderWithErrDeprecated(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{ Openid: id, }) return @@ -183,7 +183,7 @@ func signInOpenIDVerify(ctx *context.Context) { u, err = user_model.GetUserByEmail(ctx, email) if err != nil { if !user_model.IsErrUserNotExist(err) { - ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{ + ctx.RenderWithErrDeprecated(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{ Openid: id, }) return @@ -199,7 +199,7 @@ func signInOpenIDVerify(ctx *context.Context) { u, _ = user_model.GetUserByName(ctx, nickname) if err != nil { if !user_model.IsErrUserNotExist(err) { - ctx.RenderWithErr(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{ + ctx.RenderWithErrDeprecated(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{ Openid: id, }) return @@ -273,7 +273,7 @@ func ConnectOpenIDPost(ctx *context.Context) { userOID := &user_model.UserOpenID{UID: u.ID, URI: oid} if err = user_model.AddUserOpenID(ctx, userOID); err != nil { if user_model.IsErrOpenIDAlreadyUsed(err) { - ctx.RenderWithErr(ctx.Tr("form.openid_been_used", oid), tplConnectOID, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.openid_been_used", oid), tplConnectOID, &form) return } ctx.ServerError("AddUserOpenID", err) @@ -352,7 +352,7 @@ func RegisterOpenIDPost(ctx *context.Context) { length := max(setting.MinPasswordLength, 256) password, err := util.CryptoRandomString(int64(length)) if err != nil { - ctx.RenderWithErr(err.Error(), tplSignUpOID, form) + ctx.RenderWithErrDeprecated(err.Error(), tplSignUpOID, form) return } @@ -370,7 +370,7 @@ func RegisterOpenIDPost(ctx *context.Context) { userOID := &user_model.UserOpenID{UID: u.ID, URI: oid} if err = user_model.AddUserOpenID(ctx, userOID); err != nil { if user_model.IsErrOpenIDAlreadyUsed(err) { - ctx.RenderWithErr(ctx.Tr("form.openid_been_used", oid), tplSignUpOID, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.openid_been_used", oid), tplSignUpOID, &form) return } ctx.ServerError("AddUserOpenID", err) diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go index 61c6119470d..11e085f5b1e 100644 --- a/routers/web/auth/password.go +++ b/routers/web/auth/password.go @@ -74,7 +74,7 @@ func ForgotPasswdPost(ctx *context.Context) { if !u.IsLocal() && !u.IsOAuth2() { ctx.Data["Err_Email"] = true - ctx.RenderWithErr(ctx.Tr("auth.non_local_account"), tplForgotPassword, nil) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.non_local_account"), tplForgotPassword, nil) return } @@ -171,7 +171,7 @@ func ResetPasswdPost(ctx *context.Context) { if !twofa.VerifyScratchToken(ctx.FormString("token")) { ctx.Data["IsResetForm"] = true ctx.Data["Err_Token"] = true - ctx.RenderWithErr(ctx.Tr("auth.twofa_scratch_token_incorrect"), tplResetPassword, nil) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.twofa_scratch_token_incorrect"), tplResetPassword, nil) return } regenerateScratchToken = true @@ -185,7 +185,7 @@ func ResetPasswdPost(ctx *context.Context) { if !ok || twofa.LastUsedPasscode == passcode { ctx.Data["IsResetForm"] = true ctx.Data["Err_Passcode"] = true - ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplResetPassword, nil) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.twofa_passcode_incorrect"), tplResetPassword, nil) return } @@ -206,13 +206,13 @@ func ResetPasswdPost(ctx *context.Context) { ctx.Data["Err_Password"] = true switch { case errors.Is(err, password.ErrMinLength): - ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplResetPassword, nil) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplResetPassword, nil) case errors.Is(err, password.ErrComplexity): - ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplResetPassword, nil) + ctx.RenderWithErrDeprecated(password.BuildComplexityError(ctx.Locale), tplResetPassword, nil) case errors.Is(err, password.ErrIsPwned): - ctx.RenderWithErr(ctx.Tr("auth.password_pwned", "https://haveibeenpwned.com/Passwords"), tplResetPassword, nil) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.password_pwned", "https://haveibeenpwned.com/Passwords"), tplResetPassword, nil) case password.IsErrIsPwnedRequest(err): - ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplResetPassword, nil) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.password_pwned_err"), tplResetPassword, nil) default: ctx.ServerError("UpdateAuth", err) } @@ -275,7 +275,7 @@ func MustChangePasswordPost(ctx *context.Context) { if form.Password != form.Retype { ctx.Data["Err_Password"] = true - ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplMustChangePassword, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.password_not_match"), tplMustChangePassword, &form) return } @@ -287,16 +287,16 @@ func MustChangePasswordPost(ctx *context.Context) { switch { case errors.Is(err, password.ErrMinLength): ctx.Data["Err_Password"] = true - ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplMustChangePassword, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplMustChangePassword, &form) case errors.Is(err, password.ErrComplexity): ctx.Data["Err_Password"] = true - ctx.RenderWithErr(password.BuildComplexityError(ctx.Locale), tplMustChangePassword, &form) + ctx.RenderWithErrDeprecated(password.BuildComplexityError(ctx.Locale), tplMustChangePassword, &form) case errors.Is(err, password.ErrIsPwned): ctx.Data["Err_Password"] = true - ctx.RenderWithErr(ctx.Tr("auth.password_pwned", "https://haveibeenpwned.com/Passwords"), tplMustChangePassword, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.password_pwned", "https://haveibeenpwned.com/Passwords"), tplMustChangePassword, &form) case password.IsErrIsPwnedRequest(err): ctx.Data["Err_Password"] = true - ctx.RenderWithErr(ctx.Tr("auth.password_pwned_err"), tplMustChangePassword, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("auth.password_pwned_err"), tplMustChangePassword, &form) default: ctx.ServerError("UpdateAuth", err) } diff --git a/routers/web/base.go b/routers/web/base.go index 0f06cb5e4b9..db96432bedc 100644 --- a/routers/web/base.go +++ b/routers/web/base.go @@ -6,11 +6,14 @@ package web import ( "errors" "fmt" + "image/png" "net/http" "os" "path" "strings" + "code.gitea.io/gitea/modules/assetfs" + "code.gitea.io/gitea/modules/avatar" "code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -22,79 +25,70 @@ import ( func avatarStorageHandler(storageSetting *setting.Storage, prefix string, objStore storage.ObjectStorage) http.HandlerFunc { prefix = strings.Trim(prefix, "/") funcInfo := routing.GetFuncInfo(avatarStorageHandler, prefix) + exeModTime := assetfs.GetExecutableModTime() + fallbackEtag := fmt.Sprintf(`"avatar-%s"`, exeModTime.Format("20060102150405")) - if storageSetting.ServeDirect() { - return func(w http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet && req.Method != http.MethodHead { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - if !strings.HasPrefix(req.URL.Path, "/"+prefix+"/") { - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) - return - } - defer routing.RecordFuncInfo(req.Context(), funcInfo)() - - rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/") - rPath = util.PathJoinRelX(rPath) - - u, err := objStore.URL(rPath, path.Base(rPath), req.Method, nil) - if err != nil { - if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) { - log.Warn("Unable to find %s %s", prefix, rPath) - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) - return - } - log.Error("Error whilst getting URL for %s %s. Error: %v", prefix, rPath, err) - http.Error(w, fmt.Sprintf("Error whilst getting URL for %s %s", prefix, rPath), http.StatusInternalServerError) - return - } - - http.Redirect(w, req, u.String(), http.StatusTemporaryRedirect) + handleError := func(w http.ResponseWriter, req *http.Request, avatarPath string, err error) bool { + if err == nil { + return false } + + if errors.Is(err, os.ErrNotExist) || errors.Is(err, util.ErrNotExist) { + // if avatar doesn't exist, generate a random one and serve it with proper cache control headers + w.Header().Set("Content-Type", "image/png") + if !httpcache.HandleGenericETagPublicCache(req, w, fallbackEtag, &exeModTime) { + if req.Method == http.MethodGet { + img := avatar.RandomImageWithSize(96, []byte(avatarPath)) + _ = png.Encode(w, img) + } // else: for HEAD request, just return the headers without body + } + } else { + // for internal errors, log the error and return 500 + log.Error("Error when serving avatar %s: %s", req.URL.Path, err) + http.Error(w, "unable to serve avatar image", http.StatusInternalServerError) + } + return true } return func(w http.ResponseWriter, req *http.Request) { - if req.Method != http.MethodGet && req.Method != http.MethodHead { - http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) - return - } - - if !strings.HasPrefix(req.URL.Path, "/"+prefix+"/") { - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) - return - } defer routing.RecordFuncInfo(req.Context(), funcInfo)() - rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/") - rPath = util.PathJoinRelX(rPath) - if rPath == "" || rPath == "." { - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + avatarPath, ok := strings.CutPrefix(req.URL.Path, "/"+prefix+"/") + if !ok { + http.Error(w, "invalid avatar path", http.StatusBadRequest) + return + } + avatarPath = util.PathJoinRelX(avatarPath) + if avatarPath == "" || avatarPath == "." { + http.Error(w, "not found", http.StatusNotFound) return } - fi, err := objStore.Stat(rPath) - if err != nil { - if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) { - log.Warn("Unable to find %s %s", prefix, rPath) - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + if storageSetting.ServeDirect() { + // Old logic: no check for existence by Stat, so old code's "errors.Is(err, os.ErrNotExist)" didn't work. + // So in theory, it doesn't work with the non-existing avatar fallback, it just gets the URL and redirects to it. + // Checking "stat" requires one more request to the storage, which is inefficient. + // Workaround: disable "SERVE_DIRECT". Leave the problem to the future. + u, err := objStore.URL(avatarPath, path.Base(avatarPath), req.Method, nil) + if handleError(w, req, avatarPath, err) { return } - log.Error("Error whilst opening %s %s. Error: %v", prefix, rPath, err) - http.Error(w, fmt.Sprintf("Error whilst opening %s %s", prefix, rPath), http.StatusInternalServerError) + http.Redirect(w, req, u.String(), http.StatusTemporaryRedirect) return } - fr, err := objStore.Open(rPath) - if err != nil { - log.Error("Error whilst opening %s %s. Error: %v", prefix, rPath, err) - http.Error(w, fmt.Sprintf("Error whilst opening %s %s", prefix, rPath), http.StatusInternalServerError) + fr, err := objStore.Open(avatarPath) + if handleError(w, req, avatarPath, err) { return } defer fr.Close() + fi, err := fr.Stat() + if handleError(w, req, avatarPath, err) { + return + } + httpcache.SetCacheControlInHeader(w.Header(), httpcache.CacheControlForPublicStatic()) - http.ServeContent(w, req, path.Base(rPath), fi.ModTime(), fr) + http.ServeContent(w, req, path.Base(avatarPath), fi.ModTime(), fr) } } diff --git a/routers/web/healthcheck/check.go b/routers/web/healthcheck/check.go index 0eea1e1ff7b..de9b2c8ec17 100644 --- a/routers/web/healthcheck/check.go +++ b/routers/web/healthcheck/check.go @@ -72,7 +72,7 @@ type componentStatus struct { // And keep in mind: this health check should NEVER be used as a "restart" trigger, for example: Docker's "HEALTHCHECK". // * If Gitea is upgrading and migrating database, there will be a long time before this endpoint starts to return "pass" status. // In this case, if the checker restarts Gitea just because it doesn't get "pass" status in short time, -// the instance will just be restarted again and again before the migation finishes and the sitution just goes worse. +// the instance will just be restarted again and again before the migration finishes and the situation just goes worse. func Check(w http.ResponseWriter, r *http.Request) { rsp := response{ Status: pass, diff --git a/routers/web/misc/misc.go b/routers/web/misc/misc.go index 59b97c17175..3d2f624263b 100644 --- a/routers/web/misc/misc.go +++ b/routers/web/misc/misc.go @@ -6,12 +6,15 @@ package misc import ( "net/http" "path" + "strconv" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/web/middleware" + "code.gitea.io/gitea/services/context" ) func SSHInfo(rw http.ResponseWriter, req *http.Request) { @@ -47,3 +50,9 @@ func StaticRedirect(target string) func(w http.ResponseWriter, req *http.Request http.Redirect(w, req, path.Join(setting.StaticURLPrefix, target), http.StatusMovedPermanently) } } + +func WebBannerDismiss(ctx *context.Context) { + _, rev, _ := setting.Config().Instance.WebBanner.ValueRevision(ctx) + middleware.SetSiteCookie(ctx.Resp, middleware.CookieWebBannerDismissed, strconv.Itoa(rev), 48*3600) + ctx.JSONOK() +} diff --git a/routers/web/misc/webtheme.go b/routers/web/misc/webtheme.go index 076bdf8fda2..76ddf4b567a 100644 --- a/routers/web/misc/webtheme.go +++ b/routers/web/misc/webtheme.go @@ -37,6 +37,6 @@ func WebThemeApply(ctx *context.Context) { opts := &user_service.UpdateOptions{Theme: optional.Some(themeName)} _ = user_service.UpdateUser(ctx, ctx.Doer, opts) } else { - middleware.SetSiteCookie(ctx.Resp, "gitea_theme", themeName, 0) + middleware.SetSiteCookie(ctx.Resp, middleware.CookieTheme, themeName, 0) } } diff --git a/routers/web/org/org.go b/routers/web/org/org.go index 0540d5c591e..36978ba65bd 100644 --- a/routers/web/org/org.go +++ b/routers/web/org/org.go @@ -65,13 +65,13 @@ func CreatePost(ctx *context.Context) { ctx.Data["Err_OrgName"] = true switch { case user_model.IsErrUserAlreadyExist(err): - ctx.RenderWithErr(ctx.Tr("form.org_name_been_taken"), tplCreateOrg, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.org_name_been_taken"), tplCreateOrg, &form) case db.IsErrNameReserved(err): - ctx.RenderWithErr(ctx.Tr("org.form.name_reserved", err.(db.ErrNameReserved).Name), tplCreateOrg, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("org.form.name_reserved", err.(db.ErrNameReserved).Name), tplCreateOrg, &form) case db.IsErrNamePatternNotAllowed(err): - ctx.RenderWithErr(ctx.Tr("org.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplCreateOrg, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("org.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplCreateOrg, &form) case organization.IsErrUserNotAllowedCreateOrg(err): - ctx.RenderWithErr(ctx.Tr("org.form.create_org_not_allowed"), tplCreateOrg, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("org.form.create_org_not_allowed"), tplCreateOrg, &form) default: ctx.ServerError("CreateOrganization", err) } diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index 0e4dab8fb68..04baa58b734 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -73,7 +73,7 @@ func SettingsPost(ctx *context.Context) { if form.Email != "" { if err := user_service.ReplacePrimaryEmailAddress(ctx, org.AsUser(), form.Email); err != nil { ctx.Data["Err_Email"] = true - ctx.RenderWithErr(ctx.Tr("form.email_invalid"), tplSettingsOptions, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.email_invalid"), tplSettingsOptions, &form) return } } diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index 0ec7cfddc5f..1e22a670320 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -359,7 +359,7 @@ func NewTeamPost(ctx *context.Context) { } if t.AccessMode < perm.AccessModeAdmin && len(unitPerms) == 0 { - ctx.RenderWithErr(ctx.Tr("form.team_no_units_error"), tplTeamNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.team_no_units_error"), tplTeamNew, &form) return } @@ -367,7 +367,7 @@ func NewTeamPost(ctx *context.Context) { ctx.Data["Err_TeamName"] = true switch { case org_model.IsErrTeamAlreadyExist(err): - ctx.RenderWithErr(ctx.Tr("form.team_name_been_taken"), tplTeamNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.team_name_been_taken"), tplTeamNew, &form) default: ctx.ServerError("NewTeam", err) } @@ -536,7 +536,7 @@ func EditTeamPost(ctx *context.Context) { } if t.AccessMode < perm.AccessModeAdmin && len(unitPerms) == 0 { - ctx.RenderWithErr(ctx.Tr("form.team_no_units_error"), tplTeamNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.team_no_units_error"), tplTeamNew, &form) return } @@ -544,7 +544,7 @@ func EditTeamPost(ctx *context.Context) { ctx.Data["Err_TeamName"] = true switch { case org_model.IsErrTeamAlreadyExist(err): - ctx.RenderWithErr(ctx.Tr("form.team_name_been_taken"), tplTeamNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.team_name_been_taken"), tplTeamNew, &form) default: ctx.ServerError("UpdateTeam", err) } diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go index 33c1e73aa43..0eaa6cab41b 100644 --- a/routers/web/repo/actions/view.go +++ b/routers/web/repo/actions/view.go @@ -36,8 +36,6 @@ import ( notify_service "code.gitea.io/gitea/services/notify" "github.com/nektos/act/pkg/model" - "go.yaml.in/yaml/v4" - "xorm.io/builder" ) func getRunIndex(ctx *context_module.Context) int64 { @@ -53,7 +51,7 @@ func getRunIndex(ctx *context_module.Context) int64 { func View(ctx *context_module.Context) { ctx.Data["PageIsActions"] = true runIndex := getRunIndex(ctx) - jobIndex := ctx.PathParamInt64("job") + jobIndex := ctx.PathParamInt("job") ctx.Data["RunIndex"] = runIndex ctx.Data["JobIndex"] = jobIndex ctx.Data["ActionsURL"] = ctx.Repo.RepoLink + "/actions" @@ -211,7 +209,7 @@ func getActionsViewArtifacts(ctx context.Context, repoID, runIndex int64) (artif func ViewPost(ctx *context_module.Context) { req := web.GetForm(ctx).(*ViewRequest) runIndex := getRunIndex(ctx) - jobIndex := ctx.PathParamInt64("job") + jobIndex := ctx.PathParamInt("job") current, jobs := getRunJobs(ctx, runIndex, jobIndex) if ctx.Written() { @@ -405,11 +403,8 @@ func convertToViewModel(ctx context.Context, locale translation.Locale, cursors // If jobIndexStr is a blank string, it means rerun all jobs func Rerun(ctx *context_module.Context) { runIndex := getRunIndex(ctx) - jobIndexStr := ctx.PathParam("job") - var jobIndex int64 - if jobIndexStr != "" { - jobIndex, _ = strconv.ParseInt(jobIndexStr, 10, 64) - } + jobIndexHas := ctx.PathParam("job") != "" + jobIndex := ctx.PathParamInt("job") run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex) if err != nil { @@ -431,130 +426,29 @@ func Rerun(ctx *context_module.Context) { return } - // reset run's start and stop time - run.PreviousDuration = run.Duration() - run.Started = 0 - run.Stopped = 0 - run.Status = actions_model.StatusWaiting - - vars, err := actions_model.GetVariablesOfRun(ctx, run) + jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID) if err != nil { - ctx.ServerError("GetVariablesOfRun", fmt.Errorf("get run %d variables: %w", run.ID, err)) + ctx.ServerError("GetRunJobsByRunID", err) return } - if run.RawConcurrency != "" { - var rawConcurrency model.RawConcurrency - if err := yaml.Unmarshal([]byte(run.RawConcurrency), &rawConcurrency); err != nil { - ctx.ServerError("UnmarshalRawConcurrency", fmt.Errorf("unmarshal raw concurrency: %w", err)) - return - } - - err = actions_service.EvaluateRunConcurrencyFillModel(ctx, run, &rawConcurrency, vars, nil) - if err != nil { - ctx.ServerError("EvaluateRunConcurrencyFillModel", err) - return - } - - run.Status, err = actions_service.PrepareToStartRunWithConcurrency(ctx, run) - if err != nil { - ctx.ServerError("PrepareToStartRunWithConcurrency", err) + var targetJob *actions_model.ActionRunJob // nil means rerun all jobs + if jobIndexHas { + if jobIndex < 0 || jobIndex >= len(jobs) { + ctx.JSONError(ctx.Locale.Tr("error.not_found")) return } + targetJob = jobs[jobIndex] // only rerun the selected job } - if err := actions_model.UpdateRun(ctx, run, "started", "stopped", "previous_duration", "status", "concurrency_group", "concurrency_cancel"); err != nil { - ctx.ServerError("UpdateRun", err) + + if err := actions_service.RerunWorkflowRunJobs(ctx, ctx.Repo.Repository, run, jobs, targetJob); err != nil { + ctx.ServerError("RerunWorkflowRunJobs", err) return } - if err := run.LoadAttributes(ctx); err != nil { - ctx.ServerError("run.LoadAttributes", err) - return - } - notify_service.WorkflowRunStatusUpdate(ctx, run.Repo, run.TriggerUser, run) - - job, jobs := getRunJobs(ctx, runIndex, jobIndex) - if ctx.Written() { - return - } - - isRunBlocked := run.Status == actions_model.StatusBlocked - if jobIndexStr == "" { // rerun all jobs - for _, j := range jobs { - // if the job has needs, it should be set to "blocked" status to wait for other jobs - shouldBlockJob := len(j.Needs) > 0 || isRunBlocked - if err := rerunJob(ctx, j, shouldBlockJob); err != nil { - ctx.ServerError("RerunJob", err) - return - } - } - ctx.JSONOK() - return - } - - rerunJobs := actions_service.GetAllRerunJobs(job, jobs) - - for _, j := range rerunJobs { - // jobs other than the specified one should be set to "blocked" status - shouldBlockJob := j.JobID != job.JobID || isRunBlocked - if err := rerunJob(ctx, j, shouldBlockJob); err != nil { - ctx.ServerError("RerunJob", err) - return - } - } - ctx.JSONOK() } -func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob, shouldBlock bool) error { - status := job.Status - if !status.IsDone() { - return nil - } - - job.TaskID = 0 - job.Status = util.Iif(shouldBlock, actions_model.StatusBlocked, actions_model.StatusWaiting) - job.Started = 0 - job.Stopped = 0 - - job.ConcurrencyGroup = "" - job.ConcurrencyCancel = false - job.IsConcurrencyEvaluated = false - if err := job.LoadRun(ctx); err != nil { - return err - } - - vars, err := actions_model.GetVariablesOfRun(ctx, job.Run) - if err != nil { - return fmt.Errorf("get run %d variables: %w", job.Run.ID, err) - } - - if job.RawConcurrency != "" && !shouldBlock { - err = actions_service.EvaluateJobConcurrencyFillModel(ctx, job.Run, job, vars, nil) - if err != nil { - return fmt.Errorf("evaluate job concurrency: %w", err) - } - - job.Status, err = actions_service.PrepareToStartJobWithConcurrency(ctx, job) - if err != nil { - return err - } - } - - if err := db.WithTx(ctx, func(ctx context.Context) error { - updateCols := []string{"task_id", "status", "started", "stopped", "concurrency_group", "concurrency_cancel", "is_concurrency_evaluated"} - _, err := actions_model.UpdateRunJob(ctx, job, builder.Eq{"status": status}, updateCols...) - return err - }); err != nil { - return err - } - - actions_service.CreateCommitStatusForRunJobs(ctx, job.Run, job) - notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil) - - return nil -} - func Logs(ctx *context_module.Context) { runIndex := getRunIndex(ctx) jobIndex := ctx.PathParamInt64("job") @@ -715,7 +609,7 @@ func Delete(ctx *context_module.Context) { // getRunJobs gets the jobs of runIndex, and returns jobs[jobIndex], jobs. // Any error will be written to the ctx. // It never returns a nil job of an empty jobs, if the jobIndex is out of range, it will be treated as 0. -func getRunJobs(ctx *context_module.Context, runIndex, jobIndex int64) (*actions_model.ActionRunJob, []*actions_model.ActionRunJob) { +func getRunJobs(ctx *context_module.Context, runIndex int64, jobIndex int) (*actions_model.ActionRunJob, []*actions_model.ActionRunJob) { run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex) if err != nil { if errors.Is(err, util.ErrNotExist) { @@ -740,7 +634,7 @@ func getRunJobs(ctx *context_module.Context, runIndex, jobIndex int64) (*actions v.Run = run } - if jobIndex >= 0 && jobIndex < int64(len(jobs)) { + if jobIndex >= 0 && jobIndex < len(jobs) { return jobs[jobIndex], jobs } return jobs[0], jobs @@ -936,7 +830,7 @@ func Run(ctx *context_module.Context) { ctx.ServerError("ref", nil) return } - err := actions_service.DispatchActionWorkflow(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, workflowID, ref, func(workflowDispatch *model.WorkflowDispatch, inputs map[string]any) error { + _, err := actions_service.DispatchActionWorkflow(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, workflowID, ref, func(workflowDispatch *model.WorkflowDispatch, inputs map[string]any) error { for name, config := range workflowDispatch.Inputs { value := ctx.Req.PostFormValue(name) if config.Type == "boolean" { diff --git a/routers/web/repo/attachment.go b/routers/web/repo/attachment.go index a83f0f40ec8..be711afe6dd 100644 --- a/routers/web/repo/attachment.go +++ b/routers/web/repo/attachment.go @@ -187,7 +187,7 @@ func ServeAttachment(ctx *context.Context, uuid string) { } } - if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+attach.UUID+`"`) { + if httpcache.HandleGenericETagPrivateCache(ctx.Req, ctx.Resp, `"`+attach.UUID+`"`, attach.CreatedUnix.AsTimePtr()) { return } diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index f5630356004..1d6961f6fc8 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -95,7 +95,7 @@ func DeleteBranchPost(ctx *context.Context) { defer jsonRedirectBranches(ctx) branchName := ctx.FormString("name") - if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName, nil); err != nil { + if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil { switch { case git.IsErrBranchNotExist(err): log.Debug("DeleteBranch: Can't delete non existing branch '%s'", branchName) diff --git a/routers/web/repo/download.go b/routers/web/repo/download.go index 6f394aae27d..9412d2045d4 100644 --- a/routers/web/repo/download.go +++ b/routers/web/repo/download.go @@ -20,7 +20,7 @@ import ( // ServeBlobOrLFS download a git.Blob redirecting to LFS if necessary func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob, lastModified *time.Time) error { - if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) { + if httpcache.HandleGenericETagPrivateCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) { return nil } @@ -48,7 +48,7 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob, lastModified *time.Tim closed = true return common.ServeBlob(ctx.Base, ctx.Repo.Repository, ctx.Repo.TreePath, blob, lastModified) } - if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+pointer.Oid+`"`) { + if httpcache.HandleGenericETagPrivateCache(ctx.Req, ctx.Resp, `"`+pointer.Oid+`"`, meta.UpdatedUnix.AsTimePtr()) { return nil } diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index eaec3b57890..a295a3c9036 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -21,6 +21,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/htmlutil" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/optional" @@ -369,7 +370,7 @@ func UpdateIssueContent(ctx *context.Context) { } ctx.JSON(http.StatusOK, map[string]any{ - "content": content, + "content": commentContentHTML(ctx, content), "contentVersion": issue.ContentVersion, "attachments": attachmentsHTML(ctx, issue.Attachments, issue.Content), }) @@ -629,6 +630,13 @@ func updateAttachments(ctx *context.Context, item any, files []string) error { return err } +func commentContentHTML(ctx *context.Context, content template.HTML) template.HTML { + if strings.TrimSpace(string(content)) == "" { + return htmlutil.HTMLFormat(`%s`, ctx.Tr("repo.issues.no_content")) + } + return content +} + func attachmentsHTML(ctx *context.Context, attachments []*repo_model.Attachment, content string) template.HTML { attachHTML, err := ctx.RenderToHTML(tplAttachment, map[string]any{ "ctxData": ctx.Data, diff --git a/routers/web/repo/issue_comment.go b/routers/web/repo/issue_comment.go index a3cb88e76a6..7f8cc23a3f9 100644 --- a/routers/web/repo/issue_comment.go +++ b/routers/web/repo/issue_comment.go @@ -9,7 +9,6 @@ import ( "html/template" "net/http" "strconv" - "strings" git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" @@ -17,7 +16,6 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/gitrepo" - "code.gitea.io/gitea/modules/htmlutil" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup/markdown" repo_module "code.gitea.io/gitea/modules/repository" @@ -291,12 +289,8 @@ func UpdateCommentContent(ctx *context.Context) { } } - if strings.TrimSpace(string(renderedContent)) == "" { - renderedContent = htmlutil.HTMLFormat(`%s`, ctx.Tr("repo.issues.no_content")) - } - ctx.JSON(http.StatusOK, map[string]any{ - "content": renderedContent, + "content": commentContentHTML(ctx, renderedContent), "contentVersion": comment.ContentVersion, "attachments": attachmentsHTML(ctx, comment.Attachments, comment.Content), }) diff --git a/routers/web/repo/issue_content_history.go b/routers/web/repo/issue_content_history.go index a56df78163d..23cedfcb80a 100644 --- a/routers/web/repo/issue_content_history.go +++ b/routers/web/repo/issue_content_history.go @@ -6,13 +6,14 @@ package repo import ( "bytes" "html" + "html/template" "net/http" "strings" "code.gitea.io/gitea/models/avatars" issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/modules/htmlutil" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/services/context" @@ -53,29 +54,25 @@ func GetContentHistoryList(ctx *context.Context) { // value is historyId var results []map[string]any for _, item := range items { - var actionText string + var actionHTML template.HTML if item.IsDeleted { - actionTextDeleted := ctx.Locale.TrString("repo.issues.content_history.deleted") - actionText = "" + actionTextDeleted + "" + actionHTML = htmlutil.HTMLFormat(`%s`, ctx.Locale.TrString("repo.issues.content_history.deleted")) } else if item.IsFirstCreated { - actionText = ctx.Locale.TrString("repo.issues.content_history.created") + actionHTML = ctx.Locale.Tr("repo.issues.content_history.created") } else { - actionText = ctx.Locale.TrString("repo.issues.content_history.edited") + actionHTML = ctx.Locale.Tr("repo.issues.content_history.edited") } - username := item.UserName - if setting.UI.DefaultShowFullName && strings.TrimSpace(item.UserFullName) != "" { - username = strings.TrimSpace(item.UserFullName) + var fullNameHTML template.HTML + userName, fullName := item.UserName, strings.TrimSpace(item.UserFullName) + if fullName != "" { + fullNameHTML = htmlutil.HTMLFormat(` (%s)`, fullName) } - src := html.EscapeString(item.UserAvatarLink) - class := avatars.DefaultAvatarClass + " tw-mr-2" - name := html.EscapeString(username) - avatarHTML := string(templates.AvatarHTML(src, 28, class, username)) - timeSinceHTML := string(templates.TimeSince(item.EditedUnix)) - + avatarHTML := templates.AvatarHTML(item.UserAvatarLink, 24, avatars.DefaultAvatarClass+" tw-mr-2", userName) + timeSinceHTML := templates.TimeSince(item.EditedUnix) results = append(results, map[string]any{ - "name": avatarHTML + "" + name + " " + actionText + " " + timeSinceHTML, + "name": htmlutil.HTMLFormat("%s %s%s %s %s", avatarHTML, userName, fullNameHTML, actionHTML, timeSinceHTML), "value": item.HistoryID, }) } diff --git a/routers/web/repo/issue_dependency.go b/routers/web/repo/issue_dependency.go index 73298958c05..bb2dc5b0fdb 100644 --- a/routers/web/repo/issue_dependency.go +++ b/routers/web/repo/issue_dependency.go @@ -119,7 +119,7 @@ func RemoveDependency(ctx *context.Context) { case "blocking": depType = issues_model.DependencyTypeBlocking default: - ctx.HTTPError(http.StatusBadRequest, "GetDependecyType") + ctx.HTTPError(http.StatusBadRequest, "GetDependencyType") return } diff --git a/routers/web/repo/issue_poster.go b/routers/web/repo/issue_poster.go index 07059b9b7bf..4f00f40a91f 100644 --- a/routers/web/repo/issue_poster.go +++ b/routers/web/repo/issue_poster.go @@ -10,7 +10,6 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/setting" shared_user "code.gitea.io/gitea/routers/web/shared/user" "code.gitea.io/gitea/services/context" ) @@ -34,7 +33,7 @@ func IssuePullPosters(ctx *context.Context) { func issuePosters(ctx *context.Context, isPullList bool) { repo := ctx.Repo.Repository search := strings.TrimSpace(ctx.FormString("q")) - posters, err := repo_model.GetIssuePostersWithSearch(ctx, repo, isPullList, search, setting.UI.DefaultShowFullName) + posters, err := repo_model.GetIssuePostersWithSearch(ctx, repo, isPullList, search) if err != nil { ctx.JSON(http.StatusInternalServerError, err) return @@ -54,9 +53,7 @@ func issuePosters(ctx *context.Context, isPullList bool) { resp.Results = make([]*userSearchInfo, len(posters)) for i, user := range posters { resp.Results[i] = &userSearchInfo{UserID: user.ID, UserName: user.Name, AvatarLink: user.AvatarLink(ctx)} - if setting.UI.DefaultShowFullName { - resp.Results[i].FullName = user.FullName - } + resp.Results[i].FullName = user.FullName } ctx.JSON(http.StatusOK, resp) } diff --git a/routers/web/repo/issue_view.go b/routers/web/repo/issue_view.go index 1354c2d6f9b..2cd8be45332 100644 --- a/routers/web/repo/issue_view.go +++ b/routers/web/repo/issue_view.go @@ -905,9 +905,8 @@ func preparePullViewReviewAndMerge(ctx *context.Context, issue *issues_model.Iss // Check correct values and select default if ms, ok := ctx.Data["MergeStyle"].(repo_model.MergeStyle); !ok || !prConfig.IsMergeStyleAllowed(ms) { - defaultMergeStyle := prConfig.GetDefaultMergeStyle() - if prConfig.IsMergeStyleAllowed(defaultMergeStyle) && !ok { - mergeStyle = defaultMergeStyle + if prConfig.IsMergeStyleAllowed(prConfig.DefaultMergeStyle) && !ok { + mergeStyle = prConfig.DefaultMergeStyle } else if prConfig.AllowMerge { mergeStyle = repo_model.MergeStyleMerge } else if prConfig.AllowRebase { diff --git a/routers/web/repo/migrate.go b/routers/web/repo/migrate.go index 8f4adb2ad22..bb6f1e6b7eb 100644 --- a/routers/web/repo/migrate.go +++ b/routers/web/repo/migrate.go @@ -79,44 +79,44 @@ func handleMigrateError(ctx *context.Context, owner *user_model.User, err error, switch { case migrations.IsRateLimitError(err): - ctx.RenderWithErr(ctx.Tr("form.visit_rate_limit"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.visit_rate_limit"), tpl, form) case migrations.IsTwoFactorAuthError(err): - ctx.RenderWithErr(ctx.Tr("form.2fa_auth_required"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.2fa_auth_required"), tpl, form) case repo_model.IsErrReachLimitOfRepo(err): maxCreationLimit := owner.MaxCreationLimit() msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit) - ctx.RenderWithErr(msg, tpl, form) + ctx.RenderWithErrDeprecated(msg, tpl, form) case repo_model.IsErrRepoAlreadyExist(err): ctx.Data["Err_RepoName"] = true - ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.repo_name_been_taken"), tpl, form) case repo_model.IsErrRepoFilesAlreadyExist(err): ctx.Data["Err_RepoName"] = true switch { case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories): - ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tpl, form) case setting.Repository.AllowAdoptionOfUnadoptedRepositories: - ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.repository_files_already_exist.adopt"), tpl, form) case setting.Repository.AllowDeleteOfUnadoptedRepositories: - ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.repository_files_already_exist.delete"), tpl, form) default: - ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.repository_files_already_exist"), tpl, form) } case db.IsErrNameReserved(err): ctx.Data["Err_RepoName"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tpl, form) case db.IsErrNamePatternNotAllowed(err): ctx.Data["Err_RepoName"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tpl, form) default: err = util.SanitizeErrorCredentialURLs(err) if strings.Contains(err.Error(), "Authentication failed") || strings.Contains(err.Error(), "Bad credentials") || strings.Contains(err.Error(), "could not read Username") { ctx.Data["Err_Auth"] = true - ctx.RenderWithErr(ctx.Tr("form.auth_failed", err.Error()), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.auth_failed", err.Error()), tpl, form) } else if strings.Contains(err.Error(), "fatal:") { ctx.Data["Err_CloneAddr"] = true - ctx.RenderWithErr(ctx.Tr("repo.migrate.failed", err.Error()), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.migrate.failed", err.Error()), tpl, form) } else { ctx.ServerError(name, err) } @@ -128,24 +128,24 @@ func handleMigrateRemoteAddrError(ctx *context.Context, err error, tpl templates addrErr := err.(*git.ErrInvalidCloneAddr) switch { case addrErr.IsProtocolInvalid: - ctx.RenderWithErr(ctx.Tr("repo.mirror_address_protocol_invalid"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.mirror_address_protocol_invalid"), tpl, form) case addrErr.IsURLError: - ctx.RenderWithErr(ctx.Tr("form.url_error", addrErr.Host), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.url_error", addrErr.Host), tpl, form) case addrErr.IsPermissionDenied: if addrErr.LocalPath { - ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.migrate.permission_denied"), tpl, form) } else { - ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied_blocked"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.migrate.permission_denied_blocked"), tpl, form) } case addrErr.IsInvalidPath: - ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_local_path"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.migrate.invalid_local_path"), tpl, form) default: log.Error("Error whilst updating url: %v", err) - ctx.RenderWithErr(ctx.Tr("form.url_error", "unknown"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.url_error", "unknown"), tpl, form) } } else { log.Error("Error whilst updating url: %v", err) - ctx.RenderWithErr(ctx.Tr("form.url_error", "unknown"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.url_error", "unknown"), tpl, form) } } @@ -193,7 +193,7 @@ func MigratePost(ctx *context.Context) { ep := lfs.DetermineEndpoint("", form.LFSEndpoint) if ep == nil { ctx.Data["Err_LFSEndpoint"] = true - ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_lfs_endpoint"), tpl, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.migrate.invalid_lfs_endpoint"), tpl, &form) return } err = migrations.IsMigrateURLAllowed(ep.String(), ctx.Doer) diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go index dd53b1d3f10..09196d4bc20 100644 --- a/routers/web/repo/milestone.go +++ b/routers/web/repo/milestone.go @@ -118,7 +118,7 @@ func NewMilestonePost(ctx *context.Context) { deadlineUnix, err := common.ParseDeadlineDateToEndOfDay(form.Deadline) if err != nil { ctx.Data["Err_Deadline"] = true - ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form) return } @@ -174,7 +174,7 @@ func EditMilestonePost(ctx *context.Context) { deadlineUnix, err := common.ParseDeadlineDateToEndOfDay(form.Deadline) if err != nil { ctx.Data["Err_Deadline"] = true - ctx.RenderWithErr(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.milestones.invalid_due_date_format"), tplMilestoneNew, &form) return } diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index 1b36dc4d442..891af4c2d5c 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -452,13 +452,13 @@ func NewReleasePost(ctx *context.Context) { } if exist, _ := git_model.IsBranchExist(ctx, ctx.Repo.Repository.ID, form.Target); !exist { - ctx.RenderWithErr(ctx.Tr("form.target_branch_not_exist"), tplReleaseNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.target_branch_not_exist"), tplReleaseNew, &form) return } if !form.TagOnly && form.Title == "" { // if not "tag only", then the title of the release cannot be empty - ctx.RenderWithErr(ctx.Tr("repo.release.title_empty"), tplReleaseNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.release.title_empty"), tplReleaseNew, &form) return } @@ -466,13 +466,13 @@ func NewReleasePost(ctx *context.Context) { ctx.Data["Err_TagName"] = true switch { case release_service.IsErrTagAlreadyExists(err): - ctx.RenderWithErr(ctx.Tr("repo.branch.tag_collision", form.TagName), tplReleaseNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.branch.tag_collision", form.TagName), tplReleaseNew, &form) case repo_model.IsErrReleaseAlreadyExist(err): - ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form) case release_service.IsErrInvalidTagName(err): - ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_invalid"), tplReleaseNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.release.tag_name_invalid"), tplReleaseNew, &form) case release_service.IsErrProtectedTagName(err): - ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_protected"), tplReleaseNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.release.tag_name_protected"), tplReleaseNew, &form) default: ctx.ServerError("handleTagReleaseError", err) } @@ -525,7 +525,7 @@ func NewReleasePost(ctx *context.Context) { // add new logic: if tag-only, do not convert the tag to a release if form.TagOnly || !rel.IsTag { ctx.Data["Err_TagName"] = true - ctx.RenderWithErr(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.release.tag_name_already_exist"), tplReleaseNew, &form) return } diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index bc2b0264c0a..57937be83e2 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -186,28 +186,28 @@ func handleCreateError(ctx *context.Context, owner *user_model.User, err error, case repo_model.IsErrReachLimitOfRepo(err): maxCreationLimit := owner.MaxCreationLimit() msg := ctx.TrN(maxCreationLimit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", maxCreationLimit) - ctx.RenderWithErr(msg, tpl, form) + ctx.RenderWithErrDeprecated(msg, tpl, form) case repo_model.IsErrRepoAlreadyExist(err): ctx.Data["Err_RepoName"] = true - ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.repo_name_been_taken"), tpl, form) case repo_model.IsErrRepoFilesAlreadyExist(err): ctx.Data["Err_RepoName"] = true switch { case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories): - ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tpl, form) case setting.Repository.AllowAdoptionOfUnadoptedRepositories: - ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.repository_files_already_exist.adopt"), tpl, form) case setting.Repository.AllowDeleteOfUnadoptedRepositories: - ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.repository_files_already_exist.delete"), tpl, form) default: - ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.repository_files_already_exist"), tpl, form) } case db.IsErrNameReserved(err): ctx.Data["Err_RepoName"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tpl, form) case db.IsErrNamePatternNotAllowed(err): ctx.Data["Err_RepoName"] = true - ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tpl, form) default: ctx.ServerError(name, err) } @@ -254,7 +254,7 @@ func CreatePost(ctx *context.Context) { } if !opts.IsValid() { - ctx.RenderWithErr(ctx.Tr("repo.template.one_item"), tplCreate, form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.template.one_item"), tplCreate, form) return } @@ -264,7 +264,7 @@ func CreatePost(ctx *context.Context) { } if !templateRepo.IsTemplate { - ctx.RenderWithErr(ctx.Tr("repo.template.invalid"), tplCreate, form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.template.invalid"), tplCreate, form) return } @@ -526,9 +526,9 @@ func SearchRepo(ctx *context.Context) { ctx.SetTotalCountHeader(count) - latestCommitStatuses, err := commitstatus_service.FindReposLastestCommitStatuses(ctx, repos) + latestCommitStatuses, err := commitstatus_service.FindReposLatestCommitStatuses(ctx, repos) if err != nil { - log.Error("FindReposLastestCommitStatuses: %v", err) + log.Error("FindReposLatestCommitStatuses: %v", err) ctx.JSON(http.StatusInternalServerError, nil) return } diff --git a/routers/web/repo/setting/collaboration.go b/routers/web/repo/setting/collaboration.go index df7cc5e39b6..dbfd6e08b61 100644 --- a/routers/web/repo/setting/collaboration.go +++ b/routers/web/repo/setting/collaboration.go @@ -208,7 +208,7 @@ func DeleteTeam(ctx *context.Context) { } if err = repo_service.RemoveRepositoryFromTeam(ctx, team, ctx.Repo.Repository.ID); err != nil { - ctx.ServerError("team.RemoveRepositorys", err) + ctx.ServerError("team.RemoveRepositories", err) return } diff --git a/routers/web/repo/setting/deploy_key.go b/routers/web/repo/setting/deploy_key.go index 193562528bf..ab54b8ccf5a 100644 --- a/routers/web/repo/setting/deploy_key.go +++ b/routers/web/repo/setting/deploy_key.go @@ -76,16 +76,16 @@ func DeployKeysPost(ctx *context.Context) { switch { case asymkey_model.IsErrDeployKeyAlreadyExist(err): ctx.Data["Err_Content"] = true - ctx.RenderWithErr(ctx.Tr("repo.settings.key_been_used"), tplDeployKeys, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.settings.key_been_used"), tplDeployKeys, &form) case asymkey_model.IsErrKeyAlreadyExist(err): ctx.Data["Err_Content"] = true - ctx.RenderWithErr(ctx.Tr("settings.ssh_key_been_used"), tplDeployKeys, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("settings.ssh_key_been_used"), tplDeployKeys, &form) case asymkey_model.IsErrKeyNameAlreadyUsed(err): ctx.Data["Err_Title"] = true - ctx.RenderWithErr(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form) case asymkey_model.IsErrDeployKeyNameAlreadyUsed(err): ctx.Data["Err_Title"] = true - ctx.RenderWithErr(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form) default: ctx.ServerError("AddDeployKey", err) } diff --git a/routers/web/repo/setting/lfs.go b/routers/web/repo/setting/lfs.go index 8a8015035f4..a3a60963d43 100644 --- a/routers/web/repo/setting/lfs.go +++ b/routers/web/repo/setting/lfs.go @@ -301,13 +301,13 @@ func LFSFileGet(ctx *context.Context) { if index != len(lines)-1 { line += "\n" } - output.WriteString(fmt.Sprintf(`
  • %s
  • `, index+1, index+1, line)) + fmt.Fprintf(&output, `
  • %s
  • `, index+1, index+1, line) } ctx.Data["FileContent"] = gotemplate.HTML(output.String()) output.Reset() for i := 0; i < len(lines); i++ { - output.WriteString(fmt.Sprintf(`%d`, i+1, i+1)) + fmt.Fprintf(&output, `%d`, i+1, i+1) } ctx.Data["LineNums"] = gotemplate.HTML(output.String()) diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index f9e80a72e02..8475c8e21e2 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -181,23 +181,23 @@ func handleSettingsPostUpdate(ctx *context.Context) { ctx.Data["Err_RepoName"] = true switch { case repo_model.IsErrRepoAlreadyExist(err): - ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tplSettingsOptions, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.repo_name_been_taken"), tplSettingsOptions, &form) case db.IsErrNameReserved(err): - ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.form.name_reserved", err.(db.ErrNameReserved).Name), tplSettingsOptions, &form) case repo_model.IsErrRepoFilesAlreadyExist(err): ctx.Data["Err_RepoName"] = true switch { case ctx.IsUserSiteAdmin() || (setting.Repository.AllowAdoptionOfUnadoptedRepositories && setting.Repository.AllowDeleteOfUnadoptedRepositories): - ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tplSettingsOptions, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.repository_files_already_exist.adopt_or_delete"), tplSettingsOptions, form) case setting.Repository.AllowAdoptionOfUnadoptedRepositories: - ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.adopt"), tplSettingsOptions, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.repository_files_already_exist.adopt"), tplSettingsOptions, form) case setting.Repository.AllowDeleteOfUnadoptedRepositories: - ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist.delete"), tplSettingsOptions, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.repository_files_already_exist.delete"), tplSettingsOptions, form) default: - ctx.RenderWithErr(ctx.Tr("form.repository_files_already_exist"), tplSettingsOptions, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.repository_files_already_exist"), tplSettingsOptions, form) } case db.IsErrNamePatternNotAllowed(err): - ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.form.name_pattern_not_allowed", err.(db.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form) default: ctx.ServerError("ChangeRepositoryName", err) } @@ -247,7 +247,7 @@ func handleSettingsPostMirror(ctx *context.Context) { interval, err := time.ParseDuration(form.Interval) if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) { ctx.Data["Err_Interval"] = true - ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form) return } @@ -298,7 +298,7 @@ func handleSettingsPostMirror(ctx *context.Context) { ep := lfs.DetermineEndpoint("", form.LFSEndpoint) if ep == nil { ctx.Data["Err_LFSEndpoint"] = true - ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_lfs_endpoint"), tplSettingsOptions, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.migrate.invalid_lfs_endpoint"), tplSettingsOptions, &form) return } err = migrations.IsMigrateURLAllowed(ep.String(), ctx.Doer) @@ -369,7 +369,7 @@ func handleSettingsPostPushMirrorUpdate(ctx *context.Context) { interval, err := time.ParseDuration(form.PushMirrorInterval) if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) { - ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &forms.RepoSettingForm{}) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &forms.RepoSettingForm{}) return } @@ -445,7 +445,7 @@ func handleSettingsPostPushMirrorAdd(ctx *context.Context) { interval, err := time.ParseDuration(form.PushMirrorInterval) if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) { ctx.Data["Err_PushMirrorInterval"] = true - ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form) return } @@ -733,7 +733,7 @@ func handleSettingsPostConvert(ctx *context.Context) { return } if repo.Name != form.RepoName { - ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) + ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) return } @@ -767,7 +767,7 @@ func handleSettingsPostConvertFork(ctx *context.Context) { return } if repo.Name != form.RepoName { - ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) + ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) return } @@ -803,14 +803,14 @@ func handleSettingsPostTransfer(ctx *context.Context) { return } if repo.Name != form.RepoName { - ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) + ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) return } newOwner, err := user_model.GetUserByName(ctx, ctx.FormString("new_owner_name")) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil) + ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil) return } ctx.ServerError("IsUserExist", err) @@ -820,7 +820,7 @@ func handleSettingsPostTransfer(ctx *context.Context) { if newOwner.Type == user_model.UserTypeOrganization { if !ctx.Doer.IsAdmin && newOwner.Visibility == structs.VisibleTypePrivate && !organization.OrgFromUser(newOwner).HasMemberWithUserID(ctx, ctx.Doer.ID) { // The user shouldn't know about this organization - ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil) + ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_owner_name"), tplSettingsOptions, nil) return } } @@ -834,14 +834,14 @@ func handleSettingsPostTransfer(ctx *context.Context) { oldFullname := repo.FullName() if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, repo, nil); err != nil { if repo_model.IsErrRepoAlreadyExist(err) { - ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil) } else if repo_model.IsErrRepoTransferInProgress(err) { - ctx.RenderWithErr(ctx.Tr("repo.settings.transfer_in_progress"), tplSettingsOptions, nil) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.settings.transfer_in_progress"), tplSettingsOptions, nil) } else if repo_service.IsRepositoryLimitReached(err) { limit := err.(repo_service.LimitReachedError).Limit - ctx.RenderWithErr(ctx.TrN(limit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", limit), tplSettingsOptions, nil) + ctx.RenderWithErrDeprecated(ctx.TrN(limit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", limit), tplSettingsOptions, nil) } else if errors.Is(err, user_model.ErrBlockedUser) { - ctx.RenderWithErr(ctx.Tr("repo.settings.transfer.blocked_user"), tplSettingsOptions, nil) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.settings.transfer.blocked_user"), tplSettingsOptions, nil) } else { ctx.ServerError("TransferOwnership", err) } @@ -895,7 +895,7 @@ func handleSettingsPostDelete(ctx *context.Context) { return } if repo.Name != form.RepoName { - ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) + ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) return } @@ -922,7 +922,7 @@ func handleSettingsPostDeleteWiki(ctx *context.Context) { return } if repo.Name != form.RepoName { - ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) + ctx.RenderWithErrDeprecated(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) return } @@ -1011,7 +1011,7 @@ func handleSettingsPostVisibility(ctx *context.Context) { // when ForcePrivate enabled, you could change public repo to private, but only admin users can change private to public if setting.Repository.ForcePrivate && repo.IsPrivate && !ctx.Doer.IsAdmin { - ctx.RenderWithErr(ctx.Tr("form.repository_force_private"), tplSettingsOptions, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.repository_force_private"), tplSettingsOptions, form) return } @@ -1039,21 +1039,21 @@ func handleSettingRemoteAddrError(ctx *context.Context, err error, form *forms.R addrErr := err.(*git.ErrInvalidCloneAddr) switch { case addrErr.IsProtocolInvalid: - ctx.RenderWithErr(ctx.Tr("repo.mirror_address_protocol_invalid"), tplSettingsOptions, form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.mirror_address_protocol_invalid"), tplSettingsOptions, form) case addrErr.IsURLError: - ctx.RenderWithErr(ctx.Tr("form.url_error", addrErr.Host), tplSettingsOptions, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.url_error", addrErr.Host), tplSettingsOptions, form) case addrErr.IsPermissionDenied: if addrErr.LocalPath { - ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied"), tplSettingsOptions, form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.migrate.permission_denied"), tplSettingsOptions, form) } else { - ctx.RenderWithErr(ctx.Tr("repo.migrate.permission_denied_blocked"), tplSettingsOptions, form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.migrate.permission_denied_blocked"), tplSettingsOptions, form) } case addrErr.IsInvalidPath: - ctx.RenderWithErr(ctx.Tr("repo.migrate.invalid_local_path"), tplSettingsOptions, form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.migrate.invalid_local_path"), tplSettingsOptions, form) default: ctx.ServerError("Unknown error", err) } return } - ctx.RenderWithErr(ctx.Tr("repo.mirror_address_url_invalid"), tplSettingsOptions, form) + ctx.RenderWithErrDeprecated(ctx.Tr("repo.mirror_address_url_invalid"), tplSettingsOptions, form) } diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go index 00d30bedef5..d1a969cf2d7 100644 --- a/routers/web/repo/view_home.go +++ b/routers/web/repo/view_home.go @@ -69,9 +69,6 @@ func prepareHomeSidebarRepoTopics(ctx *context.Context) { func prepareOpenWithEditorApps(ctx *context.Context) { var tmplApps []map[string]any apps := setting.Config().Repository.OpenWithEditorApps.Value(ctx) - if len(apps) == 0 { - apps = setting.DefaultOpenWithEditorApps() - } for _, app := range apps { schema, _, _ := strings.Cut(app.OpenURL, ":") diff --git a/routers/web/repo/view_readme.go b/routers/web/repo/view_readme.go index 830709422ee..eba3ffc36fd 100644 --- a/routers/web/repo/view_readme.go +++ b/routers/web/repo/view_readme.go @@ -102,7 +102,7 @@ func findReadmeFileInEntries(ctx *context.Context, parentDir string, entries []* return "", nil, err } - subfolder, readmeFile, err := findReadmeFileInEntries(ctx, parentDir, childEntries, false) + subfolder, readmeFile, err := findReadmeFileInEntries(ctx, path.Join(parentDir, subTreeEntry.Name()), childEntries, false) if err != nil && !git.IsErrNotExist(err) { return "", nil, err } diff --git a/routers/web/repo/view_readme_test.go b/routers/web/repo/view_readme_test.go new file mode 100644 index 00000000000..5f61b03fb80 --- /dev/null +++ b/routers/web/repo/view_readme_test.go @@ -0,0 +1,70 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "fmt" + "path" + "testing" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/git/gitcmd" + "code.gitea.io/gitea/services/contexttest" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestFindReadmeFileInEntriesWithSymlinkInSubfolder(t *testing.T) { + for _, subdir := range []string{".github", ".gitea", "docs"} { + t.Run(subdir, func(t *testing.T) { + repoPath := t.TempDir() + stdin := fmt.Sprintf(`commit refs/heads/master +author Test 1700000000 +0000 +committer Test 1700000000 +0000 +data < 0 || isRunBlocked + if err := rerunWorkflowJob(ctx, job, shouldBlockJob); err != nil { + return err + } + } + return nil + } + + rerunJobs := GetAllRerunJobs(targetJob, jobs) + for _, job := range rerunJobs { + // Jobs other than the selected one should wait for dependencies. + shouldBlockJob := job.JobID != targetJob.JobID || isRunBlocked + if err := rerunWorkflowJob(ctx, job, shouldBlockJob); err != nil { + return err + } + } + + return nil +} + +func rerunWorkflowJob(ctx context.Context, job *actions_model.ActionRunJob, shouldBlock bool) error { + status := job.Status + if !status.IsDone() { + return nil + } + + job.TaskID = 0 + job.Status = util.Iif(shouldBlock, actions_model.StatusBlocked, actions_model.StatusWaiting) + job.Started = 0 + job.Stopped = 0 + job.ConcurrencyGroup = "" + job.ConcurrencyCancel = false + job.IsConcurrencyEvaluated = false + + if err := job.LoadRun(ctx); err != nil { + return err + } + + vars, err := actions_model.GetVariablesOfRun(ctx, job.Run) + if err != nil { + return fmt.Errorf("get run %d variables: %w", job.Run.ID, err) + } + + if job.RawConcurrency != "" && !shouldBlock { + if err := EvaluateJobConcurrencyFillModel(ctx, job.Run, job, vars, nil); err != nil { + return fmt.Errorf("evaluate job concurrency: %w", err) + } + + job.Status, err = PrepareToStartJobWithConcurrency(ctx, job) + if err != nil { + return err + } + } + + if err := db.WithTx(ctx, func(ctx context.Context) error { + updateCols := []string{"task_id", "status", "started", "stopped", "concurrency_group", "concurrency_cancel", "is_concurrency_evaluated"} + _, err := actions_model.UpdateRunJob(ctx, job, builder.Eq{"status": status}, updateCols...) + return err + }); err != nil { + return err + } + + CreateCommitStatusForRunJobs(ctx, job.Run, job) + notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil) + return nil +} diff --git a/services/actions/workflow.go b/services/actions/workflow.go index 305963cc002..faa540421fb 100644 --- a/services/actions/workflow.go +++ b/services/actions/workflow.go @@ -44,16 +44,16 @@ func EnableOrDisableWorkflow(ctx *context.APIContext, workflowID string, isEnabl return repo_model.UpdateRepoUnit(ctx, cfgUnit) } -func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, workflowID, ref string, processInputs func(model *model.WorkflowDispatch, inputs map[string]any) error) error { +func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, workflowID, ref string, processInputs func(model *model.WorkflowDispatch, inputs map[string]any) error) (runID int64, _ error) { if workflowID == "" { - return util.ErrorWrapTranslatable( + return 0, util.ErrorWrapTranslatable( util.NewNotExistErrorf("workflowID is empty"), "actions.workflow.not_found", workflowID, ) } if ref == "" { - return util.ErrorWrapTranslatable( + return 0, util.ErrorWrapTranslatable( util.NewNotExistErrorf("ref is empty"), "form.target_ref_not_exist", ref, ) @@ -63,7 +63,7 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re cfgUnit := repo.MustGetUnit(ctx, unit.TypeActions) cfg := cfgUnit.ActionsConfig() if cfg.IsWorkflowDisabled(workflowID) { - return util.ErrorWrapTranslatable( + return 0, util.ErrorWrapTranslatable( util.NewPermissionDeniedErrorf("workflow is disabled"), "actions.workflow.disabled", ) @@ -82,7 +82,7 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re runTargetCommit, err = gitRepo.GetBranchCommit(ref) } if err != nil { - return util.ErrorWrapTranslatable( + return 0, util.ErrorWrapTranslatable( util.NewNotExistErrorf("ref %q doesn't exist", ref), "form.target_ref_not_exist", ref, ) @@ -91,7 +91,7 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re // get workflow entry from runTargetCommit _, entries, err := actions.ListWorkflows(runTargetCommit) if err != nil { - return err + return 0, err } // find workflow from commit @@ -122,7 +122,7 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re } if entry == nil { - return util.ErrorWrapTranslatable( + return 0, util.ErrorWrapTranslatable( util.NewNotExistErrorf("workflow %q doesn't exist", workflowID), "actions.workflow.not_found", workflowID, ) @@ -130,12 +130,12 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re content, err := actions.GetContentFromEntry(entry) if err != nil { - return err + return 0, err } singleWorkflow := &jobparser.SingleWorkflow{} if err := yaml.Unmarshal(content, singleWorkflow); err != nil { - return fmt.Errorf("failed to unmarshal workflow content: %w", err) + return 0, fmt.Errorf("failed to unmarshal workflow content: %w", err) } // get inputs from post workflow := &model.Workflow{ @@ -144,7 +144,7 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re inputsWithDefaults := make(map[string]any) if workflowDispatch := workflow.WorkflowDispatchConfig(); workflowDispatch != nil { if err = processInputs(workflowDispatch, inputsWithDefaults); err != nil { - return err + return 0, err } } @@ -161,13 +161,13 @@ func DispatchActionWorkflow(ctx reqctx.RequestContext, doer *user_model.User, re var eventPayload []byte if eventPayload, err = workflowDispatchPayload.JSONPayload(); err != nil { - return fmt.Errorf("JSONPayload: %w", err) + return 0, fmt.Errorf("JSONPayload: %w", err) } run.EventPayload = string(eventPayload) // Insert the action run and its associated jobs into the database if err := PrepareRunAndInsert(ctx, content, run, inputsWithDefaults); err != nil { - return fmt.Errorf("PrepareRun: %w", err) + return 0, fmt.Errorf("PrepareRun: %w", err) } - return nil + return run.ID, nil } diff --git a/services/auth/source/oauth2/providers.go b/services/auth/source/oauth2/providers.go index bb21cb53fe6..68bd4a1d4c2 100644 --- a/services/auth/source/oauth2/providers.go +++ b/services/auth/source/oauth2/providers.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/setting" "github.com/markbates/goth" + "github.com/markbates/goth/providers/openidConnect" ) // Provider is an interface for describing a single OAuth2 provider @@ -197,6 +198,26 @@ func ClearProviders() { goth.ClearProviders() } +// GetOIDCEndSessionEndpoint returns the OIDC end_session_endpoint for the +// given provider name. Returns "" if the provider is not OIDC or doesn't +// advertise an end_session_endpoint in its discovery document. +func GetOIDCEndSessionEndpoint(providerName string) string { + gothRWMutex.RLock() + defer gothRWMutex.RUnlock() + + provider, ok := goth.GetProviders()[providerName] + if !ok { + return "" + } + + oidcProvider, ok := provider.(*openidConnect.Provider) + if !ok || oidcProvider.OpenIDConfig == nil { + return "" + } + + return oidcProvider.OpenIDConfig.EndSessionEndpoint +} + var ErrAuthSourceNotActivated = errors.New("auth source is not activated") // used to create different types of goth providers diff --git a/services/automerge/automerge.go b/services/automerge/automerge.go index e145f93f044..10a31f4d7bd 100644 --- a/services/automerge/automerge.go +++ b/services/automerge/automerge.go @@ -245,9 +245,9 @@ func handlePullRequestAutoMerge(pullID int64, sha string) { return } - perm, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, doer) + perm, err := access_model.GetUserRepoPermission(ctx, pr.BaseRepo, doer) if err != nil { - log.Error("GetUserRepoPermission %-v: %v", pr.HeadRepo, err) + log.Error("GetUserRepoPermission %-v: %v", pr.BaseRepo, err) return } diff --git a/services/context/captcha.go b/services/context/captcha.go index b4c3a92907d..79278180b76 100644 --- a/services/context/captcha.go +++ b/services/context/captcha.go @@ -98,6 +98,6 @@ func VerifyCaptcha(ctx *Context, tpl templates.TplName, form any) { if !valid { ctx.Data["Err_Captcha"] = true - ctx.RenderWithErr(ctx.Tr("form.captcha_incorrect"), tpl, form) + ctx.RenderWithErrDeprecated(ctx.Tr("form.captcha_incorrect"), tpl, form) } } diff --git a/services/context/context.go b/services/context/context.go index ccd0057f597..97b9890f436 100644 --- a/services/context/context.go +++ b/services/context/context.go @@ -100,12 +100,12 @@ func GetValidateContext(req *http.Request) (ctx *ValidateContext) { return ctx } -func NewTemplateContextForWeb(ctx *Context) TemplateContext { - tmplCtx := NewTemplateContext(ctx, ctx.Req) - tmplCtx["Locale"] = ctx.Base.Locale +func NewTemplateContextForWeb(ctx reqctx.RequestContext, req *http.Request, locale translation.Locale) TemplateContext { + tmplCtx := NewTemplateContext(ctx, req) + tmplCtx["Locale"] = locale tmplCtx["AvatarUtils"] = templates.NewAvatarUtils(ctx) tmplCtx["RenderUtils"] = templates.NewRenderUtils(ctx) - tmplCtx["RootData"] = ctx.Data + tmplCtx["RootData"] = ctx.GetData() tmplCtx["Consts"] = map[string]any{ "RepoUnitTypeCode": unit.TypeCode, "RepoUnitTypeIssues": unit.TypeIssues, @@ -132,7 +132,7 @@ func NewWebContext(base *Base, render Render, session session.Store) *Context { Repo: &Repository{}, Org: &Organization{}, } - ctx.TemplateContext = NewTemplateContextForWeb(ctx) + ctx.TemplateContext = NewTemplateContextForWeb(ctx, ctx.Base.Req, ctx.Base.Locale) ctx.Flash = &middleware.Flash{DataStore: ctx, Values: url.Values{}} ctx.SetContextValue(WebContextKey, ctx) return ctx diff --git a/services/context/context_response.go b/services/context/context_response.go index bb896024b1a..d057f8d41eb 100644 --- a/services/context/context_response.go +++ b/services/context/context_response.go @@ -124,8 +124,12 @@ func (ctx *Context) RenderToHTML(name templates.TplName, data any) (template.HTM return template.HTML(buf.String()), err } -// RenderWithErr used for page has form validation but need to prompt error to users. -func (ctx *Context) RenderWithErr(msg any, tpl templates.TplName, form any) { +// RenderWithErrDeprecated render the page with form validation when it needs to prompt error to users. +// Deprecated: use "form-fetch-action" and JSON response instead. +// WARNING: in many cases, this function is not able to render the page or recover the form fields correctly. +// And it is very difficult to test the page rendered by this function. +// DO NOT USE IT ANYMORE. +func (ctx *Context) RenderWithErrDeprecated(msg any, tpl templates.TplName, form any) { if form != nil { middleware.AssignForm(form, ctx.Data) } diff --git a/services/context/context_template.go b/services/context/context_template.go index c1045136ee9..52c74611878 100644 --- a/services/context/context_template.go +++ b/services/context/context_template.go @@ -6,8 +6,11 @@ package context import ( "context" "net/http" + "strconv" "time" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/services/webtheme" ) @@ -17,6 +20,10 @@ func NewTemplateContext(ctx context.Context, req *http.Request) TemplateContext return TemplateContext{"_ctx": ctx, "_req": req} } +func (c TemplateContext) req() *http.Request { + return c["_req"].(*http.Request) +} + func (c TemplateContext) parentContext() context.Context { return c["_ctx"].(context.Context) } @@ -38,7 +45,6 @@ func (c TemplateContext) Value(key any) any { } func (c TemplateContext) CurrentWebTheme() *webtheme.ThemeMetaInfo { - req := c["_req"].(*http.Request) var themeName string if webCtx := GetWebContext(c); webCtx != nil { if webCtx.Doer != nil { @@ -46,9 +52,20 @@ func (c TemplateContext) CurrentWebTheme() *webtheme.ThemeMetaInfo { } } if themeName == "" { - if cookieTheme, _ := req.Cookie("gitea_theme"); cookieTheme != nil { - themeName = cookieTheme.Value - } + themeName = middleware.GetSiteCookie(c.req(), middleware.CookieTheme) } return webtheme.GuaranteeGetThemeMetaInfo(themeName) } + +func (c TemplateContext) CurrentWebBanner() *setting.WebBannerType { + // Using revision as a simple approach to determine if the banner has been changed after the user dismissed it. + // There could be some false-positives because revision can be changed even if the banner isn't. + // While it should be still good enough (no admin would keep changing the settings) and doesn't really harm end users (just a few more times to see the banner) + // So it doesn't need to make it more complicated by allocating unique IDs or using hashes. + dismissedBannerRevision, _ := strconv.Atoi(middleware.GetSiteCookie(c.req(), middleware.CookieWebBannerDismissed)) + banner, revision, _ := setting.Config().Instance.WebBanner.ValueRevision(c) + if banner.ShouldDisplay() && dismissedBannerRevision != revision { + return &banner + } + return nil +} diff --git a/services/convert/repository.go b/services/convert/repository.go index 150c952b15d..3c9cc83ccba 100644 --- a/services/convert/repository.go +++ b/services/convert/repository.go @@ -8,6 +8,7 @@ import ( "time" "code.gitea.io/gitea/models/db" + git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" @@ -117,7 +118,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR allowManualMerge = config.AllowManualMerge autodetectManualMerge = config.AutodetectManualMerge defaultDeleteBranchAfterMerge = config.DefaultDeleteBranchAfterMerge - defaultMergeStyle = config.GetDefaultMergeStyle() + defaultMergeStyle = config.DefaultMergeStyle defaultAllowMaintainerEdit = config.DefaultAllowMaintainerEdit defaultTargetBranch = config.DefaultTargetBranch } @@ -144,6 +145,11 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR RepoID: repo.ID, }) + branchCount, err := git_model.CountBranches(ctx, repo.ID, false) + if err != nil { + log.Error("CountBranches [%d]: %v", repo.ID, err) + } + mirrorInterval := "" var mirrorUpdated time.Time if repo.IsMirror { @@ -205,6 +211,7 @@ func innerToRepo(ctx context.Context, repo *repo_model.Repository, permissionInR Stars: repo.NumStars, Forks: repo.NumForks, Watchers: repo.NumWatches, + BranchCount: int(branchCount), OpenIssues: repo.NumOpenIssues, OpenPulls: repo.NumOpenPulls, Releases: int(numReleases), diff --git a/services/forms/user_form.go b/services/forms/user_form.go index 618294d4341..cc514a2e279 100644 --- a/services/forms/user_form.go +++ b/services/forms/user_form.go @@ -45,9 +45,6 @@ type InstallForm struct { RegisterConfirm bool MailNotify bool - OfflineMode bool - DisableGravatar bool - EnableFederatedAvatar bool EnableOpenIDSignIn bool EnableOpenIDSignUp bool DisableRegistration bool diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 7777cf4a1c3..b23e5b1b1cc 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -1594,10 +1594,10 @@ func generatePatchForUnchangedLineFromReader(reader io.Reader, treePath string, // Generate synthetic patch var patchBuilder strings.Builder - patchBuilder.WriteString(fmt.Sprintf("diff --git a/%s b/%s\n", treePath, treePath)) - patchBuilder.WriteString(fmt.Sprintf("--- a/%s\n", treePath)) - patchBuilder.WriteString(fmt.Sprintf("+++ b/%s\n", treePath)) - patchBuilder.WriteString(fmt.Sprintf("@@ -%d,%d +%d,%d @@\n", startLine, len(lines), startLine, len(lines))) + fmt.Fprintf(&patchBuilder, "diff --git a/%s b/%s\n", treePath, treePath) + fmt.Fprintf(&patchBuilder, "--- a/%s\n", treePath) + fmt.Fprintf(&patchBuilder, "+++ b/%s\n", treePath) + fmt.Fprintf(&patchBuilder, "@@ -%d,%d +%d,%d @@\n", startLine, len(lines), startLine, len(lines)) for _, lineContent := range lines { patchBuilder.WriteString(" ") diff --git a/services/issue/assignee.go b/services/issue/assignee.go index ae4b7138eed..9b76687c461 100644 --- a/services/issue/assignee.go +++ b/services/issue/assignee.go @@ -20,10 +20,10 @@ import ( // DeleteNotPassedAssignee deletes all assignees who aren't passed via the "assignees" array func DeleteNotPassedAssignee(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assignees []*user_model.User) (err error) { var found bool - oriAssignes := make([]*user_model.User, len(issue.Assignees)) - _ = copy(oriAssignes, issue.Assignees) + oriAssignees := make([]*user_model.User, len(issue.Assignees)) + _ = copy(oriAssignees, issue.Assignees) - for _, assignee := range oriAssignes { + for _, assignee := range oriAssignees { found = false for _, alreadyAssignee := range assignees { if assignee.ID == alreadyAssignee.ID { diff --git a/services/issue/pull.go b/services/issue/pull.go index 2fcf3860d03..778bbefc28a 100644 --- a/services/issue/pull.go +++ b/services/issue/pull.go @@ -113,7 +113,7 @@ func PullRequestCodeOwnersReview(ctx context.Context, pr *issues_model.PullReque } // load all reviews from database - latestReivews, _, err := issues_model.GetReviewsByIssueID(ctx, pr.IssueID) + latestReviews, _, err := issues_model.GetReviewsByIssueID(ctx, pr.IssueID) if err != nil { return nil, err } @@ -128,7 +128,7 @@ func PullRequestCodeOwnersReview(ctx context.Context, pr *issues_model.PullReque } for _, u := range uniqUsers { - if u.ID != issue.Poster.ID && !contain(latestReivews, u) { + if u.ID != issue.Poster.ID && !contain(latestReviews, u) { comment, err := issues_model.AddReviewRequest(ctx, issue, u, issue.Poster, true) if err != nil { log.Warn("Failed add assignee user: %s to PR review: %s#%d, error: %s", u.Name, pr.BaseRepo.Name, pr.ID, err) diff --git a/services/mailer/mail.go b/services/mailer/mail.go index 8f831f89adb..a08ed71480c 100644 --- a/services/mailer/mail.go +++ b/services/mailer/mail.go @@ -158,18 +158,23 @@ func (b64embedder *mailAttachmentBase64Embedder) AttachmentSrcToBase64DataURI(ct func fromDisplayName(u *user_model.User) string { if setting.MailService.FromDisplayNameFormatTemplate != nil { - var ctx bytes.Buffer - err := setting.MailService.FromDisplayNameFormatTemplate.Execute(&ctx, map[string]any{ + var buf bytes.Buffer + err := setting.MailService.FromDisplayNameFormatTemplate.Execute(&buf, map[string]any{ "DisplayName": u.DisplayName(), "AppName": setting.AppName, "Domain": setting.Domain, }) if err == nil { - return mime.QEncoding.Encode("utf-8", ctx.String()) + return mime.QEncoding.Encode("utf-8", buf.String()) } log.Error("fromDisplayName: %w", err) } - return u.GetCompleteName() + def := u.Name + if fullName := strings.TrimSpace(u.FullName); fullName != "" { + // use "Full Name (username)" for email's sender name if Full Name is not empty + def = fullName + " (" + u.Name + ")" + } + return def } func generateMetadataHeaders(repo *repo_model.Repository) map[string]string { diff --git a/services/migrations/dump.go b/services/migrations/dump.go index 04a4569a120..eb0367e9f94 100644 --- a/services/migrations/dump.go +++ b/services/migrations/dump.go @@ -288,12 +288,13 @@ func (g *RepositoryDumper) CreateLabels(_ context.Context, labels ...*base.Label func (g *RepositoryDumper) CreateReleases(_ context.Context, releases ...*base.Release) error { if g.opts.ReleaseAssets { for _, release := range releases { - attachDir := filepath.Join("release_assets", release.TagName) + attachDir := filepath.Join("release_assets", uuid.New().String()) if err := os.MkdirAll(filepath.Join(g.baseDir, attachDir), os.ModePerm); err != nil { return err } for _, asset := range release.Assets { - attachLocalPath := filepath.Join(attachDir, asset.Name) + // we cannot use asset.Name because it might contains special characters. + attachLocalPath := filepath.Join(attachDir, uuid.New().String()) // SECURITY: We cannot check the DownloadURL and DownloadFunc are safe here // ... we must assume that they are safe and simply download the attachment diff --git a/services/migrations/main_test.go b/services/migrations/main_test.go index 9893b4e7d1b..8c14b072d61 100644 --- a/services/migrations/main_test.go +++ b/services/migrations/main_test.go @@ -218,7 +218,7 @@ func assertRepositoryEqual(t *testing.T, expected, actual *base.Repository) { func assertReviewEqual(t *testing.T, expected, actual *base.Review) { assert.Equal(t, expected.ID, actual.ID, "ID") - assert.Equal(t, expected.IssueIndex, actual.IssueIndex, "IsssueIndex") + assert.Equal(t, expected.IssueIndex, actual.IssueIndex, "IssueIndex") assert.Equal(t, expected.ReviewerID, actual.ReviewerID, "ReviewerID") assert.Equal(t, expected.ReviewerName, actual.ReviewerName, "ReviewerName") assert.Equal(t, expected.Official, actual.Official, "Official") diff --git a/services/oauth2_provider/jwtsigningkey.go b/services/oauth2_provider/jwtsigningkey.go index 03c7403f75c..4898d54166a 100644 --- a/services/oauth2_provider/jwtsigningkey.go +++ b/services/oauth2_provider/jwtsigningkey.go @@ -214,13 +214,20 @@ func (key ecdsaSingingKey) VerifyKey() any { func (key ecdsaSingingKey) ToJWK() (map[string]string, error) { pubKey := key.key.Public().(*ecdsa.PublicKey) + // PublicKey.Bytes returns the uncompressed SEC 1 format: 0x04 || X || Y + pubKeyBytes, err := pubKey.Bytes() + if err != nil { + return nil, err + } + + coordLen := (len(pubKeyBytes) - 1) / 2 return map[string]string{ "kty": "EC", "alg": key.SigningMethod().Alg(), "kid": key.id, "crv": pubKey.Params().Name, - "x": base64.RawURLEncoding.EncodeToString(pubKey.X.Bytes()), - "y": base64.RawURLEncoding.EncodeToString(pubKey.Y.Bytes()), + "x": base64.RawURLEncoding.EncodeToString(pubKeyBytes[1 : 1+coordLen]), + "y": base64.RawURLEncoding.EncodeToString(pubKeyBytes[1+coordLen:]), }, nil } diff --git a/services/oauth2_provider/jwtsigningkey_test.go b/services/oauth2_provider/jwtsigningkey_test.go new file mode 100644 index 00000000000..55de81dfd88 --- /dev/null +++ b/services/oauth2_provider/jwtsigningkey_test.go @@ -0,0 +1,61 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package oauth2_provider + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "encoding/base64" + "math/big" + "testing" + + "github.com/golang-jwt/jwt/v5" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestECDSASigningKeyToJWK(t *testing.T) { + for _, tc := range []struct { + curve elliptic.Curve + signingMethod jwt.SigningMethod + expectedAlg string + expectedCrv string + coordLen int + }{ + {elliptic.P256(), jwt.SigningMethodES256, "ES256", "P-256", 32}, + {elliptic.P384(), jwt.SigningMethodES384, "ES384", "P-384", 48}, + {elliptic.P521(), jwt.SigningMethodES512, "ES512", "P-521", 66}, + } { + t.Run(tc.expectedCrv, func(t *testing.T) { + privKey, err := ecdsa.GenerateKey(tc.curve, rand.Reader) + require.NoError(t, err) + + signingKey, err := newECDSASingingKey(tc.signingMethod, privKey) + require.NoError(t, err) + + jwk, err := signingKey.ToJWK() + require.NoError(t, err) + + assert.Equal(t, "EC", jwk["kty"]) + assert.Equal(t, tc.expectedAlg, jwk["alg"]) + assert.Equal(t, tc.expectedCrv, jwk["crv"]) + assert.NotEmpty(t, jwk["kid"]) + + // Verify coordinates are the correct fixed length per RFC 7518 / SEC 1 + xBytes, err := base64.RawURLEncoding.DecodeString(jwk["x"]) + require.NoError(t, err) + assert.Len(t, xBytes, tc.coordLen) + + yBytes, err := base64.RawURLEncoding.DecodeString(jwk["y"]) + require.NoError(t, err) + assert.Len(t, yBytes, tc.coordLen) + + // Verify the decoded coordinates reconstruct the original public key point + pubKey := privKey.Public().(*ecdsa.PublicKey) + assert.Equal(t, 0, new(big.Int).SetBytes(xBytes).Cmp(pubKey.X)) + assert.Equal(t, 0, new(big.Int).SetBytes(yBytes).Cmp(pubKey.Y)) + }) + } +} diff --git a/services/org/team.go b/services/org/team.go index 633056cf95f..f62e638514e 100644 --- a/services/org/team.go +++ b/services/org/team.go @@ -312,12 +312,12 @@ func removeTeamMember(ctx context.Context, team *organization.Team, user *user_m return err } - // Remove watches from now unaccessible + // Remove watches from now inaccessible if err := repo_service.ReconsiderWatches(ctx, repo, user); err != nil { return err } - // Remove issue assignments from now unaccessible + // Remove issue assignments from now inaccessible if err := repo_service.ReconsiderRepoIssuesAssignee(ctx, repo, user); err != nil { return err } diff --git a/services/packages/arch/repository.go b/services/packages/arch/repository.go index 438bb10837d..3a22db1b198 100644 --- a/services/packages/arch/repository.go +++ b/services/packages/arch/repository.go @@ -36,7 +36,7 @@ const ( IndexArchiveFilename = "packages.db" ) -func AquireRegistryLock(ctx context.Context, ownerID int64) (globallock.ReleaseFunc, error) { +func AcquireRegistryLock(ctx context.Context, ownerID int64) (globallock.ReleaseFunc, error) { return globallock.Lock(ctx, fmt.Sprintf("packages_arch_%d", ownerID)) } diff --git a/services/packages/cleanup/cleanup.go b/services/packages/cleanup/cleanup.go index ec860db1bbd..f2b5bce4f6f 100644 --- a/services/packages/cleanup/cleanup.go +++ b/services/packages/cleanup/cleanup.go @@ -134,7 +134,7 @@ func executeCleanupOneRule(ctx context.Context, pcr *packages_model.PackageClean return fmt.Errorf("CleanupRule [%d]: rpm.BuildAllRepositoryFiles failed: %w", pcr.ID, err) } case packages_model.TypeArch: - release, err := arch_service.AquireRegistryLock(ctx, pcr.OwnerID) + release, err := arch_service.AcquireRegistryLock(ctx, pcr.OwnerID) if err != nil { return err } diff --git a/services/packages/container/blob_uploader.go b/services/packages/container/blob_uploader.go index 17139c37068..d2df2c814ad 100644 --- a/services/packages/container/blob_uploader.go +++ b/services/packages/container/blob_uploader.go @@ -18,8 +18,8 @@ import ( var ( // errWriteAfterRead occurs if Write is called after a read operation errWriteAfterRead = errors.New("write is unsupported after a read operation") - // errOffsetMissmatch occurs if the file offset is different than the model - errOffsetMissmatch = errors.New("offset mismatch between file and model") + // errOffsetMismatch occurs if the file offset is different than the model + errOffsetMismatch = errors.New("offset mismatch between file and model") ) // BlobUploader handles chunked blob uploads @@ -86,7 +86,7 @@ func (u *BlobUploader) Append(ctx context.Context, r io.Reader) error { return err } if offset != u.BytesReceived { - return errOffsetMissmatch + return errOffsetMismatch } n, err := io.Copy(io.MultiWriter(u.file, u.MultiHasher), r) diff --git a/services/pull/check.go b/services/pull/check.go index f6e8433cf26..6486ca79df3 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -333,6 +333,28 @@ func getMergeCommit(ctx context.Context, pr *issues_model.PullRequest) (*git.Com return commit, nil } +func getMergerForManuallyMergedPullRequest(ctx context.Context, pr *issues_model.PullRequest) (*user_model.User, error) { + var errs []error + if branch, err := git_model.GetBranch(ctx, pr.BaseRepoID, pr.BaseBranch); err != nil { + errs = append(errs, err) + } else { + err := branch.LoadPusher(ctx) // LoadPusher uses ghost for non-existing user + if branch.Pusher != nil && branch.Pusher.ID > 0 { + return branch.Pusher, nil + } else if err != nil { + errs = append(errs, err) + } + } + + // When the doer (pusher) is unknown set the BaseRepo owner as merger + err := pr.BaseRepo.LoadOwner(ctx) + if err == nil { + return pr.BaseRepo.Owner, nil + } + errs = append(errs, err) + return nil, fmt.Errorf("unable to find merger for manually merged pull request: %w", errors.Join(errs...)) +} + // manuallyMerged checks if a pull request got manually merged // When a pull request got manually merged mark the pull request as merged func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool { @@ -362,17 +384,10 @@ func manuallyMerged(ctx context.Context, pr *issues_model.PullRequest) bool { return false } - merger, _ := user_model.GetUserByEmail(ctx, commit.Author.Email) - - // When the commit author is unknown set the BaseRepo owner as merger - if merger == nil { - if pr.BaseRepo.Owner == nil { - if err = pr.BaseRepo.LoadOwner(ctx); err != nil { - log.Error("%-v BaseRepo.LoadOwner: %v", pr, err) - return false - } - } - merger = pr.BaseRepo.Owner + merger, err := getMergerForManuallyMergedPullRequest(ctx, pr) + if err != nil { + log.Error("%-v getMergerForManuallyMergedPullRequest: %v", pr, err) + return false } if merged, err := SetMerged(ctx, pr, commit.ID.String(), timeutil.TimeStamp(commit.Author.When.Unix()), merger, issues_model.PullRequestStatusManuallyMerged); err != nil { diff --git a/services/pull/update.go b/services/pull/update.go index 462bbec55af..c9dbb34ef6a 100644 --- a/services/pull/update.go +++ b/services/pull/update.go @@ -100,78 +100,105 @@ func Update(ctx context.Context, pr *issues_model.PullRequest, doer *user_model. return err } -// IsUserAllowedToUpdate check if user is allowed to update PR with given permissions and branch protections -// update PR means send new commits to PR head branch from base branch -func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest, user *user_model.User) (mergeAllowed, rebaseAllowed bool, err error) { - if pull.Flow == issues_model.PullRequestFlowAGit { - return false, false, nil - } +// isUserAllowedToPushOrForcePushInRepoBranch checks whether user is allowed to push or force push in the given repo and branch +// it will check both user permission and branch protection rules +func isUserAllowedToPushOrForcePushInRepoBranch(ctx context.Context, user *user_model.User, repo *repo_model.Repository, branch string) (pushAllowed, forcePushAllowed bool, err error) { if user == nil { return false, false, nil } - headRepoPerm, err := access_model.GetUserRepoPermission(ctx, pull.HeadRepo, user) + + // 1. check user push permission on the given repository + repoPerm, err := access_model.GetUserRepoPermission(ctx, repo, user) if err != nil { if repo_model.IsErrUnitTypeNotExist(err) { return false, false, nil } return false, false, err } + pushAllowed = repoPerm.CanWrite(unit.TypeCode) + forcePushAllowed = pushAllowed + // 2. check branch protection whether user can push or force push + pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, repo.ID, branch) + if err != nil { + return false, false, err + } + if pb != nil { // override previous results if there is a branch protection rule + pb.Repo = repo + pushAllowed = pb.CanUserPush(ctx, user) + forcePushAllowed = pb.CanUserForcePush(ctx, user) + } + return pushAllowed, forcePushAllowed, nil +} + +// IsUserAllowedToUpdate check if user is allowed to update PR with given permissions and branch protections +// update PR means send new commits to PR head branch from base branch +func IsUserAllowedToUpdate(ctx context.Context, pull *issues_model.PullRequest, user *user_model.User) (pushAllowed, rebaseAllowed bool, err error) { + if user == nil { + return false, false, nil + } if err := pull.LoadBaseRepo(ctx); err != nil { return false, false, err } + if err := pull.LoadHeadRepo(ctx); err != nil { + return false, false, err + } - // 1. check base repository's AllowRebaseUpdate configuration + // 1. check whether pull request enabled. + prBaseUnit, err := pull.BaseRepo.GetUnit(ctx, unit.TypePullRequests) + if repo_model.IsErrUnitTypeNotExist(err) { + return false, false, nil // the PR unit is disabled in base repo means no update allowed + } else if err != nil { + return false, false, fmt.Errorf("get base repo unit: %v", err) + } + + // 2. only support Github style pull request + if pull.Flow == issues_model.PullRequestFlowAGit { + return false, false, nil + } + + // 3. check user push permission on head repository + pushAllowed, rebaseAllowed, err = isUserAllowedToPushOrForcePushInRepoBranch(ctx, user, pull.HeadRepo, pull.HeadBranch) + if err != nil { + return false, false, err + } + + // 4. if the pull creator allows maintainer to edit, we need to check whether + // user is a maintainer (has permission to merge into base branch) and inherit pull request poster's permission + if pull.AllowMaintainerEdit && (!pushAllowed || !rebaseAllowed) { + baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, pull.BaseRepo, user) + if err != nil { + return false, false, err + } + userAllowedToMergePR, err := isUserAllowedToMergeInRepoBranch(ctx, pull.BaseRepoID, pull.BaseBranch, baseRepoPerm, user) + if err != nil { + return false, false, err + } + if userAllowedToMergePR { + // the user is maintainer (can merge PR), and this PR is allowed to be edited by maintainers, + // then the user should inherit the PR poster's push/rebase permission for the head branch + if err := pull.LoadIssue(ctx); err != nil { + return false, false, err + } + if err := pull.Issue.LoadPoster(ctx); err != nil { + return false, false, err + } + posterPushAllowed, posterRebaseAllowed, err := isUserAllowedToPushOrForcePushInRepoBranch(ctx, pull.Issue.Poster, pull.HeadRepo, pull.HeadBranch) + if err != nil { + return false, false, err + } + if !pushAllowed { + pushAllowed = posterPushAllowed + } + if !rebaseAllowed { + rebaseAllowed = posterRebaseAllowed + } + } + } + + // 5. check base repository's AllowRebaseUpdate configuration // it is a config in base repo but controls the head (fork) repo's "Update" behavior - { - prBaseUnit, err := pull.BaseRepo.GetUnit(ctx, unit.TypePullRequests) - if repo_model.IsErrUnitTypeNotExist(err) { - return false, false, nil // the PR unit is disabled in base repo - } else if err != nil { - return false, false, fmt.Errorf("get base repo unit: %v", err) - } - rebaseAllowed = prBaseUnit.PullRequestsConfig().AllowRebaseUpdate - } - - // 2. check head branch protection whether rebase is allowed, if pb not found then rebase depends on the above setting - { - pb, err := git_model.GetFirstMatchProtectedBranchRule(ctx, pull.HeadRepoID, pull.HeadBranch) - if err != nil { - return false, false, err - } - // If branch protected, disable rebase unless user is whitelisted to force push (which extends regular push) - if pb != nil { - pb.Repo = pull.HeadRepo - rebaseAllowed = rebaseAllowed && pb.CanUserForcePush(ctx, user) - } - } - - // 3. check whether user has write access to head branch - baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, pull.BaseRepo, user) - if err != nil { - return false, false, err - } - - mergeAllowed, err = isUserAllowedToMergeInRepoBranch(ctx, pull.HeadRepoID, pull.HeadBranch, headRepoPerm, user) - if err != nil { - return false, false, err - } - - // 4. if the pull creator allows maintainer to edit, it means the write permissions of the head branch has been - // granted to the user with write permission of the base repository - if pull.AllowMaintainerEdit { - mergeAllowedMaintainer, err := isUserAllowedToMergeInRepoBranch(ctx, pull.BaseRepoID, pull.BaseBranch, baseRepoPerm, user) - if err != nil { - return false, false, err - } - - mergeAllowed = mergeAllowed || mergeAllowedMaintainer - } - - // if merge is not allowed, rebase is also not allowed - rebaseAllowed = rebaseAllowed && mergeAllowed - - return mergeAllowed, rebaseAllowed, nil + return pushAllowed, rebaseAllowed && prBaseUnit.PullRequestsConfig().AllowRebaseUpdate, nil } func syncCommitDivergence(ctx context.Context, pr *issues_model.PullRequest) error { diff --git a/services/pull/update_test.go b/services/pull/update_test.go new file mode 100644 index 00000000000..4b5772e35d5 --- /dev/null +++ b/services/pull/update_test.go @@ -0,0 +1,172 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package pull + +import ( + "testing" + + "code.gitea.io/gitea/models/db" + git_model "code.gitea.io/gitea/models/git" + issues_model "code.gitea.io/gitea/models/issues" + "code.gitea.io/gitea/models/perm" + access_model "code.gitea.io/gitea/models/perm/access" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestIsUserAllowedToUpdate(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + + setRepoAllowRebaseUpdate := func(t *testing.T, repoID int64, allow bool) { + repoUnit := unittest.AssertExistsAndLoadBean(t, &repo_model.RepoUnit{RepoID: repoID, Type: unit.TypePullRequests}) + repoUnit.PullRequestsConfig().AllowRebaseUpdate = allow + require.NoError(t, repo_model.UpdateRepoUnit(t.Context(), repoUnit)) + } + + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + t.Run("RespectsProtectedBranch", func(t *testing.T) { + pr2 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}) + protectedBranch := &git_model.ProtectedBranch{ + RepoID: pr2.HeadRepoID, + RuleName: pr2.HeadBranch, + CanPush: false, + CanForcePush: false, + } + _, err := db.GetEngine(t.Context()).Insert(protectedBranch) + require.NoError(t, err) + defer db.DeleteByBean(t.Context(), protectedBranch) + + pushAllowed, rebaseAllowed, err := IsUserAllowedToUpdate(t.Context(), pr2, user2) + assert.NoError(t, err) + assert.False(t, pushAllowed) + assert.False(t, rebaseAllowed) + }) + + t.Run("DisallowRebaseWhenConfigDisabled", func(t *testing.T) { + pr2 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}) + setRepoAllowRebaseUpdate(t, pr2.BaseRepoID, false) + pushAllowed, rebaseAllowed, err := IsUserAllowedToUpdate(t.Context(), pr2, user2) + assert.NoError(t, err) + assert.True(t, pushAllowed) + assert.False(t, rebaseAllowed) + }) + + t.Run("ReadOnlyAccessDenied", func(t *testing.T) { + pr2 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}) + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + + collaboration := &repo_model.Collaboration{ + RepoID: pr2.HeadRepoID, + UserID: user4.ID, + Mode: perm.AccessModeRead, + } + require.NoError(t, db.Insert(t.Context(), collaboration)) + defer db.DeleteByBean(t.Context(), collaboration) + + require.NoError(t, pr2.LoadHeadRepo(t.Context())) + assert.NoError(t, access_model.RecalculateUserAccess(t.Context(), pr2.HeadRepo, user4.ID)) + + pushAllowed, rebaseAllowed, err := IsUserAllowedToUpdate(t.Context(), pr2, user4) + assert.NoError(t, err) + assert.False(t, pushAllowed) + assert.False(t, rebaseAllowed) + }) + + t.Run("ProtectedBranchAllowsPushWithoutRebase", func(t *testing.T) { + pr2 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 2}) + protectedBranch := &git_model.ProtectedBranch{ + RepoID: pr2.HeadRepoID, + RuleName: pr2.HeadBranch, + CanPush: true, + CanForcePush: false, + } + _, err := db.GetEngine(t.Context()).Insert(protectedBranch) + require.NoError(t, err) + defer db.DeleteByBean(t.Context(), protectedBranch) + + pushAllowed, rebaseAllowed, err := IsUserAllowedToUpdate(t.Context(), pr2, user2) + assert.NoError(t, err) + assert.True(t, pushAllowed) + assert.False(t, rebaseAllowed) + }) + + pr3Poster := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 12}) + + t.Run("MaintainerEditRespectsPosterPermissions", func(t *testing.T) { + pr3 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 3}) + pr3.AllowMaintainerEdit = true + pushAllowed, rebaseAllowed, err := IsUserAllowedToUpdate(t.Context(), pr3, pr3Poster) + assert.NoError(t, err) + assert.False(t, pushAllowed) + assert.False(t, rebaseAllowed) + }) + + t.Run("MaintainerEditInheritsPosterPermissions", func(t *testing.T) { + pr3 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 3}) + pr3.AllowMaintainerEdit = true + protectedBranch := &git_model.ProtectedBranch{ + RepoID: pr3.HeadRepoID, + RuleName: pr3.HeadBranch, + CanPush: true, + CanForcePush: true, + } + _, err := db.GetEngine(t.Context()).Insert(protectedBranch) + require.NoError(t, err) + defer db.DeleteByBean(t.Context(), protectedBranch) + + collaboration := &repo_model.Collaboration{ + RepoID: pr3.HeadRepoID, + UserID: pr3Poster.ID, + Mode: perm.AccessModeWrite, + } + require.NoError(t, db.Insert(t.Context(), collaboration)) + defer db.DeleteByBean(t.Context(), collaboration) + + require.NoError(t, pr3.LoadHeadRepo(t.Context())) + assert.NoError(t, access_model.RecalculateUserAccess(t.Context(), pr3.HeadRepo, pr3Poster.ID)) + + pushAllowed, rebaseAllowed, err := IsUserAllowedToUpdate(t.Context(), pr3, pr3Poster) + assert.NoError(t, err) + assert.True(t, pushAllowed) + assert.True(t, rebaseAllowed) + }) + + t.Run("MaintainerEditInheritsPosterPermissionsRebaseDisabled", func(t *testing.T) { + pr3 := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 3}) + pr3.AllowMaintainerEdit = true + protectedBranch := &git_model.ProtectedBranch{ + RepoID: pr3.HeadRepoID, + RuleName: pr3.HeadBranch, + CanPush: true, + CanForcePush: true, + } + _, err := db.GetEngine(t.Context()).Insert(protectedBranch) + require.NoError(t, err) + defer db.DeleteByBean(t.Context(), protectedBranch) + + collaboration := &repo_model.Collaboration{ + RepoID: pr3.HeadRepoID, + UserID: pr3Poster.ID, + Mode: perm.AccessModeWrite, + } + require.NoError(t, db.Insert(t.Context(), collaboration)) + defer db.DeleteByBean(t.Context(), collaboration) + + require.NoError(t, pr3.LoadHeadRepo(t.Context())) + assert.NoError(t, access_model.RecalculateUserAccess(t.Context(), pr3.HeadRepo, pr3Poster.ID)) + + setRepoAllowRebaseUpdate(t, pr3.BaseRepoID, false) + + pushAllowed, rebaseAllowed, err := IsUserAllowedToUpdate(t.Context(), pr3, pr3Poster) + assert.NoError(t, err) + assert.True(t, pushAllowed) + assert.False(t, rebaseAllowed) + }) +} diff --git a/services/release/notes.go b/services/release/notes.go index c9dc75af70d..92ee22e6043 100644 --- a/services/release/notes.go +++ b/services/release/notes.go @@ -113,7 +113,7 @@ func buildReleaseNotesContent(ctx context.Context, repo *repo_model.Repository, for _, pr := range prs { prURL := pr.Issue.HTMLURL(ctx) - builder.WriteString(fmt.Sprintf("* %s in [#%d](%s)\n", pr.Issue.Title, pr.Issue.Index, prURL)) + fmt.Fprintf(&builder, "* %s in [#%d](%s)\n", pr.Issue.Title, pr.Issue.Index, prURL) } builder.WriteString("\n") @@ -121,7 +121,7 @@ func buildReleaseNotesContent(ctx context.Context, repo *repo_model.Repository, if len(contributors) > 0 { builder.WriteString("## Contributors\n") for _, contributor := range contributors { - builder.WriteString(fmt.Sprintf("* @%s\n", contributor.Name)) + fmt.Fprintf(&builder, "* @%s\n", contributor.Name) } builder.WriteString("\n") } @@ -130,14 +130,14 @@ func buildReleaseNotesContent(ctx context.Context, repo *repo_model.Repository, builder.WriteString("## New Contributors\n") for _, contributor := range newContributors { prURL := contributor.Issue.HTMLURL(ctx) - builder.WriteString(fmt.Sprintf("* @%s made their first contribution in [#%d](%s)\n", contributor.Issue.Poster.Name, contributor.Issue.Index, prURL)) + fmt.Fprintf(&builder, "* @%s made their first contribution in [#%d](%s)\n", contributor.Issue.Poster.Name, contributor.Issue.Index, prURL) } builder.WriteString("\n") } builder.WriteString("**Full Changelog**: ") compareURL := fmt.Sprintf("%s/compare/%s...%s", repo.HTMLURL(ctx), util.PathEscapeSegments(baseRef), util.PathEscapeSegments(tagName)) - builder.WriteString(fmt.Sprintf("[%s...%s](%s)", baseRef, tagName, compareURL)) + fmt.Fprintf(&builder, "[%s...%s](%s)", baseRef, tagName, compareURL) builder.WriteByte('\n') return builder.String() } diff --git a/services/repository/branch.go b/services/repository/branch.go index b3310b2e68c..87aa3bbc330 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -573,8 +573,38 @@ func CanDeleteBranch(ctx context.Context, repo *repo_model.Repository, branchNam return nil } +func deleteBranchInternal(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, branchName string, branchCommit *git.Commit) (branchExisted bool, err error) { + activeInDB, err := git_model.IsBranchExist(ctx, repo.ID, branchName) + if err != nil { + return false, fmt.Errorf("IsBranchExist: %w", err) + } + + // process the branch in db + if activeInDB { + if err := git_model.MarkBranchAsDeleted(ctx, repo.ID, branchName, doer.ID); err != nil { + return false, err + } + } + + // process the branch in git + if branchCommit != nil { + err := gitrepo.DeleteBranch(ctx, repo, branchName, true) + if err != nil { + return false, fmt.Errorf("DeleteBranch: %w", err) + } + // since the branch existed in git, return branchExisted=true + branchExisted = true + } else { + // the branch didn't exist in git, return activeInDB to indicate whether the branch was active in DB, + // for consistency with that the user had seen on the web ui or in the branch list API response. + branchExisted = activeInDB + } + + return branchExisted, nil +} + // DeleteBranch delete branch -func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branchName string, pr *issues_model.PullRequest) error { +func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branchName string) error { err := repo.MustNotBeArchived() if err != nil { return err @@ -584,46 +614,31 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R return err } - rawBranch, err := git_model.GetBranch(ctx, repo.ID, branchName) - if err != nil && !git_model.IsErrBranchNotExist(err) { - return fmt.Errorf("GetBranch: %vc", err) - } - - // database branch record not exist or it's a deleted branch - notExist := git_model.IsErrBranchNotExist(err) || rawBranch.IsDeleted - branchCommit, err := gitRepo.GetBranchCommit(branchName) + // branchCommit can be nil if the branch doesn't exist in git if err != nil && !errors.Is(err, util.ErrNotExist) { return err } - if err := db.WithTx(ctx, func(ctx context.Context) error { - if !notExist { - if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, doer.ID); err != nil { - return err - } - } - - if pr != nil { - if err := issues_model.AddDeletePRBranchComment(ctx, doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil { - return fmt.Errorf("DeleteBranch: %v", err) - } - } - if branchCommit == nil { - return nil - } - - return gitrepo.DeleteBranch(ctx, repo, branchName, true) - }); err != nil { + branchExisted, err := db.WithTx2(ctx, func(ctx context.Context) (bool, error) { + return deleteBranchInternal(ctx, doer, repo, branchName, branchCommit) + }) + if err != nil { return err } - if branchCommit == nil { - return nil + if !branchExisted { + return git.ErrBranchNotExist{Name: branchName} } - // Don't return error below this + // Don't return error below this since the deletion has succeeded + if branchCommit != nil { + deleteBranchSuccessPostProcess(doer, repo, branchName, branchCommit) + } + return nil +} +func deleteBranchSuccessPostProcess(doer *user_model.User, repo *repo_model.Repository, branchName string, branchCommit *git.Commit) { objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName) if err := PushUpdate( &repo_module.PushUpdateOptions{ @@ -637,8 +652,6 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R }); err != nil { log.Error("PushUpdateOptions: %v", err) } - - return nil } type BranchSyncOptions struct { @@ -886,9 +899,17 @@ func DeleteBranchAfterMerge(ctx context.Context, doer *user_model.User, prID int return util.ErrorWrapTranslatable(util.ErrUnprocessableContent, "repo.branch.delete_branch_has_new_commits", fullBranchName) } - err = DeleteBranch(ctx, doer, pr.HeadRepo, gitHeadRepo, pr.HeadBranch, pr) + err = DeleteBranch(ctx, doer, pr.HeadRepo, gitHeadRepo, pr.HeadBranch) if errors.Is(err, util.ErrPermissionDenied) || errors.Is(err, util.ErrNotExist) { return errFailedToDelete(err) } - return err + if err != nil { + return err + } + + // intentionally ignore the following error, since the branch has already been deleted successfully + if err := issues_model.AddDeletePRBranchComment(ctx, doer, pr.BaseRepo, pr.Issue.ID, pr.HeadBranch); err != nil { + log.Error("AddDeletePRBranchComment: %v", err) + } + return nil } diff --git a/services/repository/commitstatus/commitstatus.go b/services/repository/commitstatus/commitstatus.go index a1534223c3b..26a8c6140dd 100644 --- a/services/repository/commitstatus/commitstatus.go +++ b/services/repository/commitstatus/commitstatus.go @@ -118,8 +118,8 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato return nil } -// FindReposLastestCommitStatuses loading repository default branch latest combined commit status with cache -func FindReposLastestCommitStatuses(ctx context.Context, repos []*repo_model.Repository) ([]*git_model.CommitStatus, error) { +// FindReposLatestCommitStatuses loading repository default branch latest combined commit status with cache +func FindReposLatestCommitStatuses(ctx context.Context, repos []*repo_model.Repository) ([]*git_model.CommitStatus, error) { results := make([]*git_model.CommitStatus, len(repos)) allCached := true for i, repo := range repos { diff --git a/services/repository/create.go b/services/repository/create.go index e027d3b9790..a8b57b67071 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -386,15 +386,7 @@ func createRepositoryInDB(ctx context.Context, doer, u *user_model.User, repo *r }, }) case unit.TypePullRequests: - units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: tp, - Config: &repo_model.PullRequestsConfig{ - AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true, AllowFastForwardOnly: true, - DefaultMergeStyle: repo_model.MergeStyle(setting.Repository.PullRequest.DefaultMergeStyle), - AllowRebaseUpdate: true, - }, - }) + units = append(units, repo_model.DefaultPullRequestsUnit(repo.ID)) case unit.TypeProjects: units = append(units, repo_model.RepoUnit{ RepoID: repo.ID, diff --git a/services/repository/repo_team.go b/services/repository/repo_team.go index 8ea186f8cc6..9a9df290f4b 100644 --- a/services/repository/repo_team.go +++ b/services/repository/repo_team.go @@ -108,7 +108,7 @@ func removeAllRepositoriesFromTeam(ctx context.Context, t *organization.Team) (e return err } - // Remove watches from all users and now unaccessible repos + // Remove watches from all users and now inaccessible repos for _, user := range t.Members { has, err := access_model.HasAnyUnitAccess(ctx, user.ID, repo) if err != nil { diff --git a/services/webhook/discord.go b/services/webhook/discord.go index 19af779120d..c0af7c02433 100644 --- a/services/webhook/discord.go +++ b/services/webhook/discord.go @@ -169,7 +169,7 @@ func (d discordConvertor) Push(p *api.PushPayload) (DiscordPayload, error) { if utf8.RuneCountInString(message) > 50 { message = fmt.Sprintf("%.47s...", message) } - text.WriteString(fmt.Sprintf("[%s](%s) %s - %s", commit.ID[:7], commit.URL, message, commit.Author.Name)) + fmt.Fprintf(&text, "[%s](%s) %s - %s", commit.ID[:7], commit.URL, message, commit.Author.Name) // add linebreak to each commit but the last if i < len(p.Commits)-1 { text.WriteString("\n") diff --git a/services/webhook/feishu.go b/services/webhook/feishu.go index ecce9acc436..ac581df85a3 100644 --- a/services/webhook/feishu.go +++ b/services/webhook/feishu.go @@ -77,7 +77,7 @@ func (fc feishuConvertor) Push(p *api.PushPayload) (FeishuPayload, error) { ) var text strings.Builder - text.WriteString(fmt.Sprintf("[%s:%s] %s\r\n", p.Repo.FullName, branchName, commitDesc)) + fmt.Fprintf(&text, "[%s:%s] %s\r\n", p.Repo.FullName, branchName, commitDesc) // for each commit, generate attachment text for i, commit := range p.Commits { var authorName string diff --git a/services/webhook/matrix.go b/services/webhook/matrix.go index 63fbbf40a96..fa01ecd0b19 100644 --- a/services/webhook/matrix.go +++ b/services/webhook/matrix.go @@ -174,11 +174,11 @@ func (m matrixConvertor) Push(p *api.PushPayload) (MatrixPayload, error) { repoLink := htmlLinkFormatter(p.Repo.HTMLURL, p.Repo.FullName) branchLink := MatrixLinkToRef(p.Repo.HTMLURL, p.Ref) var text strings.Builder - text.WriteString(fmt.Sprintf("[%s] %s pushed %s to %s:
    ", repoLink, p.Pusher.UserName, commitDesc, branchLink)) + fmt.Fprintf(&text, "[%s] %s pushed %s to %s:
    ", repoLink, p.Pusher.UserName, commitDesc, branchLink) // for each commit, generate a new line text for i, commit := range p.Commits { - text.WriteString(fmt.Sprintf("%s: %s - %s", htmlLinkFormatter(commit.URL, commit.ID[:7]), commit.Message, commit.Author.Name)) + fmt.Fprintf(&text, "%s: %s - %s", htmlLinkFormatter(commit.URL, commit.ID[:7]), commit.Message, commit.Author.Name) // add linebreak to each commit but the last if i < len(p.Commits)-1 { text.WriteString("
    ") diff --git a/services/webhook/msteams.go b/services/webhook/msteams.go index fa39e7228e3..34db9037121 100644 --- a/services/webhook/msteams.go +++ b/services/webhook/msteams.go @@ -134,8 +134,8 @@ func (m msteamsConvertor) Push(p *api.PushPayload) (MSTeamsPayload, error) { var text strings.Builder // for each commit, generate attachment text for i, commit := range p.Commits { - text.WriteString(fmt.Sprintf("[%s](%s) %s - %s", commit.ID[:7], commit.URL, - strings.TrimRight(commit.Message, "\r\n"), commit.Author.Name)) + fmt.Fprintf(&text, "[%s](%s) %s - %s", commit.ID[:7], commit.URL, + strings.TrimRight(commit.Message, "\r\n"), commit.Author.Name) // add linebreak to each commit but the last if i < len(p.Commits)-1 { text.WriteString("\n\n") diff --git a/services/webhook/slack.go b/services/webhook/slack.go index 0b3dda467cc..94d41d21790 100644 --- a/services/webhook/slack.go +++ b/services/webhook/slack.go @@ -211,7 +211,7 @@ func (s slackConvertor) Push(p *api.PushPayload) (SlackPayload, error) { var attachmentText strings.Builder // for each commit, generate attachment text for i, commit := range p.Commits { - attachmentText.WriteString(fmt.Sprintf("%s: %s - %s", SlackLinkFormatter(commit.URL, commit.ID[:7]), SlackShortTextFormatter(commit.Message), SlackTextFormatter(commit.Author.Name))) + fmt.Fprintf(&attachmentText, "%s: %s - %s", SlackLinkFormatter(commit.URL, commit.ID[:7]), SlackShortTextFormatter(commit.Message), SlackTextFormatter(commit.Author.Name)) // add linebreak to each commit but the last if i < len(p.Commits)-1 { attachmentText.WriteString("\n") diff --git a/services/webhook/telegram.go b/services/webhook/telegram.go index 2abc743fabd..8e9a53a5de5 100644 --- a/services/webhook/telegram.go +++ b/services/webhook/telegram.go @@ -96,7 +96,7 @@ func (t telegramConvertor) Push(p *api.PushPayload) (TelegramPayload, error) { var htmlCommits strings.Builder for _, commit := range p.Commits { - htmlCommits.WriteString(fmt.Sprintf("\n[%s] %s", htmlLinkFormatter(commit.URL, commit.ID[:7]), html.EscapeString(strings.TrimRight(commit.Message, "\r\n")))) + fmt.Fprintf(&htmlCommits, "\n[%s] %s", htmlLinkFormatter(commit.URL, commit.ID[:7]), html.EscapeString(strings.TrimRight(commit.Message, "\r\n"))) if commit.Author != nil { htmlCommits.WriteString(" - " + html.EscapeString(commit.Author.Name)) } diff --git a/services/webhook/wechatwork.go b/services/webhook/wechatwork.go index da9c6b584c2..cac6a700c02 100644 --- a/services/webhook/wechatwork.go +++ b/services/webhook/wechatwork.go @@ -86,8 +86,8 @@ func (wc wechatworkConvertor) Push(p *api.PushPayload) (WechatworkPayload, error } message := strings.ReplaceAll(commit.Message, "\n\n", "\r\n") - text.WriteString(fmt.Sprintf(" > [%s](%s) \r\n >%s \n >%s", commit.ID[:7], commit.URL, - message, authorName)) + fmt.Fprintf(&text, " > [%s](%s) \r\n >%s \n >%s", commit.ID[:7], commit.URL, + message, authorName) // add linebreak to each commit but the last if i < len(p.Commits)-1 { diff --git a/stylelint.config.js b/stylelint.config.js index 42edf76f434..3e6be3c2487 100644 --- a/stylelint.config.js +++ b/stylelint.config.js @@ -125,7 +125,7 @@ export default { 'csstools/value-no-unknown-custom-properties': [true, {importFrom: cssVarFiles}], 'declaration-block-no-duplicate-properties': [true, {ignore: ['consecutive-duplicates-with-different-values']}], 'declaration-block-no-redundant-longhand-properties': [true, {ignoreShorthands: ['flex-flow', 'overflow', 'grid-template']}], - 'declaration-property-unit-disallowed-list': {'line-height': ['em']}, + 'declaration-property-unit-disallowed-list': null, 'declaration-property-value-disallowed-list': {'word-break': ['break-word']}, 'font-family-name-quotes': 'always-where-recommended', 'function-name-case': 'lower', diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 728746713c0..6dc6e0d5ea1 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -15,8 +15,6 @@
    {{.AppUrl}}
    {{ctx.Locale.Tr "admin.config.domain"}}
    {{.Domain}}
    -
    {{ctx.Locale.Tr "admin.config.offline_mode"}}
    -
    {{svg (Iif .OfflineMode "octicon-check" "octicon-x")}}
    {{ctx.Locale.Tr "admin.config.disable_router_log"}}
    {{svg (Iif .DisableRouterLog "octicon-check" "octicon-x")}}
    @@ -225,7 +223,7 @@
    {{ctx.Locale.Tr "admin.config.mailer_user"}}
    {{if .Mailer.User}}{{.Mailer.User}}{{else}}(empty){{end}}
    -
    {{ctx.Locale.Tr "admin.config.send_test_mail"}}
    +
    {{ctx.Locale.Tr "admin.config.send_test_mail"}}
    @@ -256,7 +254,7 @@
    {{.CacheItemTTL}}
    {{end}}
    -
    {{ctx.Locale.Tr "admin.config.cache_test"}}
    +
    {{ctx.Locale.Tr "admin.config.cache_test"}}
    diff --git a/templates/admin/config_settings/avatars.tmpl b/templates/admin/config_settings/avatars.tmpl index 1fc761034d4..7a2e40a68ee 100644 --- a/templates/admin/config_settings/avatars.tmpl +++ b/templates/admin/config_settings/avatars.tmpl @@ -3,17 +3,21 @@
    -
    {{ctx.Locale.Tr "admin.config.disable_gravatar"}}
    + {{$cfgOpt := .SystemConfig.Picture.DisableGravatar}} +
    {{ctx.Locale.Tr "admin.config.enable_gravatar"}}
    -
    - +
    +
    +
    + + {{$cfgOpt = .SystemConfig.Picture.EnableFederatedAvatar}}
    {{ctx.Locale.Tr "admin.config.enable_federated_avatar"}}
    - +
    diff --git a/templates/admin/config_settings/config_settings.tmpl b/templates/admin/config_settings/config_settings.tmpl index 1ef764a58ba..6d1db4f89fd 100644 --- a/templates/admin/config_settings/config_settings.tmpl +++ b/templates/admin/config_settings/config_settings.tmpl @@ -1,7 +1,7 @@ -{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin config")}} +{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin config" "dataGlobalInit" "initAdminConfigSettings")}} -{{template "admin/config_settings/avatars" .}} - -{{template "admin/config_settings/repository" .}} + {{template "admin/config_settings/avatars" .}} + {{template "admin/config_settings/repository" .}} + {{template "admin/config_settings/instance" .}} {{template "admin/layout_footer" .}} diff --git a/templates/admin/config_settings/instance.tmpl b/templates/admin/config_settings/instance.tmpl new file mode 100644 index 00000000000..da28fffddb4 --- /dev/null +++ b/templates/admin/config_settings/instance.tmpl @@ -0,0 +1,63 @@ +

    {{ctx.Locale.Tr "admin.config.instance_maintenance"}}

    +
    + + {{$cfgOpt := $.SystemConfig.Instance.MaintenanceMode}} + {{$cfgKey := $cfgOpt.DynKey}} + {{$maintenanceMode := $cfgOpt.Value ctx}} + +
    +
    + + +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    {{ctx.Locale.Tr "admin.config.common.skip_time_check"}}
    +
    + +
    + + {{$cfgOpt = $.SystemConfig.Instance.WebBanner}} + {{$cfgKey = $cfgOpt.DynKey}} + {{$banner := $cfgOpt.Value ctx}} + +
    +
    + + +
    + {{template "shared/combomarkdowneditor" (dict + "ContainerClasses" "web-banner-content-editor" + "TextareaName" (print $cfgKey ".ContentMessage") + "TextareaContent" $banner.ContentMessage + "TextareaPlaceholder" (ctx.Locale.Tr "admin.config.instance_web_banner.message_placeholder") + )}} +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    {{ctx.Locale.Tr "admin.config.common.skip_time_check"}}
    +
    +
    + +
    + +
    diff --git a/templates/admin/config_settings/repository.tmpl b/templates/admin/config_settings/repository.tmpl index 9a377078356..2d5845ba4ed 100644 --- a/templates/admin/config_settings/repository.tmpl +++ b/templates/admin/config_settings/repository.tmpl @@ -2,24 +2,23 @@ {{ctx.Locale.Tr "repository"}}
    -
    + + {{$cfg := .SystemConfig.Repository.OpenWithEditorApps}}
    {{ctx.Locale.Tr "admin.config.open_with_editor_app_help"}} -
    {{.DefaultOpenWithEditorAppsString}}
    +
    {{$cfg.DefaultValue.ToTextareaString}}
    - {{$cfg := .SystemConfig.Repository.OpenWithEditorApps}} - - + {{/* TODO: OPEN-WITH-EDITOR-APP-JSON: use a simple textarea */}} +
    + {{$cfg = .SystemConfig.Repository.GitGuideRemoteName}}
    - {{$cfg = .SystemConfig.Repository.GitGuideRemoteName}} - - +
    diff --git a/templates/admin/layout_head.tmpl b/templates/admin/layout_head.tmpl index 7cc6624d504..397516da5da 100644 --- a/templates/admin/layout_head.tmpl +++ b/templates/admin/layout_head.tmpl @@ -1,5 +1,5 @@ {{template "base/head" .ctxData}} -
    +
    {{template "admin/navbar" .ctxData}}
    diff --git a/templates/admin/org/list.tmpl b/templates/admin/org/list.tmpl index a4317b8c4e8..e6de93c5f81 100644 --- a/templates/admin/org/list.tmpl +++ b/templates/admin/org/list.tmpl @@ -48,11 +48,14 @@ - {{range .Users}} + {{range $org := .Users}} {{.ID}} - {{if and DefaultShowFullName .FullName}}{{.FullName}} ({{.Name}}){{else}}{{.Name}}{{end}} + + {{$org.Name}} + {{if $org.FullName}}({{$org.FullName}}){{end}} + {{if .Visibility.IsPrivate}} {{svg "octicon-lock"}} {{end}} diff --git a/templates/admin/repo/unadopted.tmpl b/templates/admin/repo/unadopted.tmpl index 54b76c08bca..e66add6ce8e 100644 --- a/templates/admin/repo/unadopted.tmpl +++ b/templates/admin/repo/unadopted.tmpl @@ -20,7 +20,7 @@ {{if .Dirs}}
    {{range $dirI, $dir := .Dirs}} -
    +
    {{svg "octicon-file-directory-fill"}} {{$dir}}
    diff --git a/templates/base/head_banner.tmpl b/templates/base/head_banner.tmpl new file mode 100644 index 00000000000..d237161622a --- /dev/null +++ b/templates/base/head_banner.tmpl @@ -0,0 +1,11 @@ +{{$banner := ctx.CurrentWebBanner}} +{{if $banner}} +
    +
    + {{ctx.RenderUtils.MarkdownToHtml $banner.ContentMessage}} +
    + +
    +{{end}} diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index cda1f377b40..447f78565e0 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -55,7 +55,7 @@
    - + {{svg "octicon-sign-out"}} {{ctx.Locale.Tr "sign_out"}} @@ -87,8 +87,11 @@