diff --git a/.deadcode-out b/.deadcode-out index e63e4a3dc3..61c5bcb055 100644 --- a/.deadcode-out +++ b/.deadcode-out @@ -13,6 +13,13 @@ forgejo.org/models IsErrSHANotFound IsErrMergeDivergingFastForwardOnly +forgejo.org/models/activities + GetActivityByID + NewFederatedUserActivity + CreateUserActivity + GetFollowingFeeds + FederatedUserActivity.loadActor + forgejo.org/models/auth WebAuthnCredentials @@ -54,9 +61,17 @@ forgejo.org/models/user IsErrExternalLoginUserAlreadyExist IsErrExternalLoginUserNotExist NewFederatedUser + NewFederatedUserFollower IsErrUserSettingIsNotExist GetUserAllSettings DeleteUserSetting + GetFederatedUser + GetFederatedUserByUserID + UpdateFederatedUser + GetFollowersForUser + AddFollower + RemoveFollower + IsFollowingAp forgejo.org/modules/activitypub NewContext diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 9d8f54ee13..28fa9e4555 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "ghcr.io/devcontainers/features/node:1": { "version": "22" }, - "ghcr.io/devcontainers/features/git-lfs:1.2.3": {}, + "ghcr.io/devcontainers/features/git-lfs:1.2.4": {}, "ghcr.io/warrenbuckley/codespace-features/sqlite:1": {} }, "customizations": { diff --git a/.dockerignore b/.dockerignore index 5e7a893014..807c70b000 100644 --- a/.dockerignore +++ b/.dockerignore @@ -37,13 +37,9 @@ coverage.all coverage/ cpu.out -/modules/migration/bindata.go /modules/migration/bindata.go.hash -/modules/options/bindata.go /modules/options/bindata.go.hash -/modules/public/bindata.go /modules/public/bindata.go.hash -/modules/templates/bindata.go /modules/templates/bindata.go.hash *.db diff --git a/.forgejo/issue_template/bug-report-ui.yaml b/.forgejo/issue_template/bug-report-ui.yaml index 57d578b232..8bb7bf1d49 100644 --- a/.forgejo/issue_template/bug-report-ui.yaml +++ b/.forgejo/issue_template/bug-report-ui.yaml @@ -6,7 +6,7 @@ body: - type: markdown attributes: value: | - **NOTE: If your issue is a security concern, please email (GPG: `A4676E79`) instead of opening a public issue.** + **NOTE: If your issue is a security concern, please email ([security.txt](https://forgejo.org/.well-known/security.txt)) instead of opening a public issue.** - type: markdown attributes: value: | diff --git a/.forgejo/issue_template/bug-report.yaml b/.forgejo/issue_template/bug-report.yaml index 6e9b116e60..a2b50dbca2 100644 --- a/.forgejo/issue_template/bug-report.yaml +++ b/.forgejo/issue_template/bug-report.yaml @@ -6,7 +6,7 @@ body: - type: markdown attributes: value: | - **NOTE: If your issue is a security concern, please email (GPG: `A4676E79`) instead of opening a public issue.** + **NOTE: If your issue is a security concern, please email ([security.txt](https://forgejo.org/.well-known/security.txt)) instead of opening a public issue.** - type: markdown attributes: value: | diff --git a/.forgejo/testdata/build-release/Dockerfile b/.forgejo/testdata/build-release/Dockerfile index d10564359e..09cce06c47 100644 --- a/.forgejo/testdata/build-release/Dockerfile +++ b/.forgejo/testdata/build-release/Dockerfile @@ -1,4 +1,4 @@ -FROM data.forgejo.org/oci/alpine:3.21 +FROM data.forgejo.org/oci/alpine:3.22 ARG RELEASE_VERSION=unkown LABEL maintainer="contact@forgejo.org" \ org.opencontainers.image.version="${RELEASE_VERSION}" diff --git a/.forgejo/workflows-composite/apt-install-from/action.yaml b/.forgejo/workflows-composite/apt-install-from/action.yaml index 615e7cb184..c1c8c950e3 100644 --- a/.forgejo/workflows-composite/apt-install-from/action.yaml +++ b/.forgejo/workflows-composite/apt-install-from/action.yaml @@ -18,7 +18,7 @@ runs: - name: install packages run: | apt-get update -qq - apt-get -q install -qq -y ${PACKAGES} + apt-get -q install --allow-downgrades -qq -y ${PACKAGES} env: PACKAGES: ${{inputs.packages}} - name: remove temporary package list to prevent using it in other steps diff --git a/.forgejo/workflows/build-release.yml b/.forgejo/workflows/build-release.yml index a34f3533fd..3ab63b0589 100644 --- a/.forgejo/workflows/build-release.yml +++ b/.forgejo/workflows/build-release.yml @@ -164,7 +164,7 @@ jobs: - name: build container & release if: ${{ secrets.TOKEN != '' }} - uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.4 + uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.5 with: forgejo: "${{ env.GITHUB_SERVER_URL }}" owner: "${{ env.GITHUB_REPOSITORY_OWNER }}" @@ -183,7 +183,7 @@ jobs: - name: build rootless container if: ${{ secrets.TOKEN != '' }} - uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.4 + uses: https://data.forgejo.org/forgejo/forgejo-build-publish/build@v5.3.5 with: forgejo: "${{ env.GITHUB_SERVER_URL }}" owner: "${{ env.GITHUB_REPOSITORY_OWNER }}" diff --git a/.forgejo/workflows/publish-release.yml b/.forgejo/workflows/publish-release.yml index 27d3b9383e..3aec46fb03 100644 --- a/.forgejo/workflows/publish-release.yml +++ b/.forgejo/workflows/publish-release.yml @@ -44,7 +44,7 @@ jobs: - uses: https://data.forgejo.org/actions/checkout@v4 - name: copy & sign - uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.3.4 + uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.3.5 with: from-forgejo: ${{ vars.FORGEJO }} to-forgejo: ${{ vars.FORGEJO }} diff --git a/.forgejo/workflows/renovate.yml b/.forgejo/workflows/renovate.yml index b584f94cf8..5aa6c8cd98 100644 --- a/.forgejo/workflows/renovate.yml +++ b/.forgejo/workflows/renovate.yml @@ -28,7 +28,7 @@ jobs: runs-on: docker container: - image: data.forgejo.org/renovate/renovate:40.48.4 + image: data.forgejo.org/renovate/renovate:41.1.4 steps: - name: Load renovate repo cache diff --git a/.forgejo/workflows/testing-integration.yml b/.forgejo/workflows/testing-integration.yml new file mode 100644 index 0000000000..9e5cfb92ed --- /dev/null +++ b/.forgejo/workflows/testing-integration.yml @@ -0,0 +1,71 @@ +# +# Additional integration tests designed to run once a day when +# `mirror.yml` pushes to https://codeberg.org/forgejo-integration/forgejo +# and send a notification via email should they fail. +# +# For debug purposes: +# +# - uncomment [on].pull_request +# - swap 'forgejo-integration' and 'forgejo-coding' +# - open a pull request at https://codeberg.org/forgejo/forgejo and fix things +# - swap 'forgejo-integration' and 'forgejo-coding' +# - comment [on].pull_request +# + +name: testing-integration + +on: +# pull_request: + push: + tags: 'v[0-9]+.[0-9]+.*' + branches: + - 'forgejo' + - 'v*/forgejo' + +jobs: + test-unit: +# if: vars.ROLE == 'forgejo-coding' + if: vars.ROLE == 'forgejo-integration' + runs-on: docker + container: + image: 'data.forgejo.org/oci/node:22-bookworm' + options: --tmpfs /tmp:exec,noatime + steps: + - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: ./.forgejo/workflows-composite/setup-env + - name: install git 2.30 + uses: ./.forgejo/workflows-composite/apt-install-from + with: + packages: git/bullseye git-lfs/bullseye + release: bullseye + - uses: ./.forgejo/workflows-composite/build-backend + - run: | + su forgejo -c 'make test-backend test-check' + timeout-minutes: 120 + env: + RACE_ENABLED: 'true' + TAGS: bindata + test-sqlite: +# if: vars.ROLE == 'forgejo-coding' + if: vars.ROLE == 'forgejo-integration' + runs-on: docker + container: + image: 'data.forgejo.org/oci/node:22-bookworm' + options: --tmpfs /tmp:exec,noatime + steps: + - uses: https://data.forgejo.org/actions/checkout@v4 + - uses: ./.forgejo/workflows-composite/setup-env + - name: install git 2.30 + uses: ./.forgejo/workflows-composite/apt-install-from + with: + packages: git/bullseye git-lfs/bullseye + release: bullseye + - uses: ./.forgejo/workflows-composite/build-backend + - run: | + su forgejo -c 'make test-sqlite-migration test-sqlite' + timeout-minutes: 120 + env: + TAGS: sqlite sqlite_unlock_notify + RACE_ENABLED: true + TEST_TAGS: sqlite sqlite_unlock_notify + USE_REPO_TEST_DIR: 1 diff --git a/.forgejo/workflows/testing.yml b/.forgejo/workflows/testing.yml index 4d88d3efb0..7a93bb66a8 100644 --- a/.forgejo/workflows/testing.yml +++ b/.forgejo/workflows/testing.yml @@ -91,6 +91,7 @@ jobs: RACE_ENABLED: 'true' TAGS: bindata TEST_ELASTICSEARCH_URL: http://elasticsearch:9200 + TEST_MINIO_ENDPOINT: minio:9000 test-e2e: if: vars.ROLE == 'forgejo-coding' || vars.ROLE == 'forgejo-testing' runs-on: docker @@ -114,6 +115,11 @@ jobs: run: | su forgejo -c 'make deps-frontend frontend' - uses: ./.forgejo/workflows-composite/build-backend + - name: Decide to run all tests + id: run-all + if: contains(github.event.pull_request.labels.*.name, 'run-all-playwright-tests') || contains(github.event.pull_request.title, 'playwright') + run: | + echo "all=1" >> "$GITHUB_OUTPUT" - name: Get changed files id: changed-files uses: https://data.forgejo.org/tj-actions/changed-files@v46 @@ -126,6 +132,7 @@ jobs: USE_REPO_TEST_DIR: 1 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 CHANGED_FILES: ${{steps.changed-files.outputs.all_changed_files}} + RUN_ALL: ${{steps.run-all.all}} - name: Upload test artifacts on failure if: failure() uses: https://data.forgejo.org/forgejo/upload-artifact@v4 diff --git a/CODEOWNERS b/CODEOWNERS index 03b0d8753d..34cdceca09 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -10,6 +10,7 @@ # Javascript and CSS code. web_src/.* @beowulf @gusted +web_src/css/.* @0ko # HTML templates used by the backend. templates/.* @beowulf @gusted diff --git a/Dockerfile b/Dockerfile index a94f4d2b46..322e2c61a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx -FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.21 AS build-env +FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.22 AS build-env ARG GOPROXY ENV GOPROXY=${GOPROXY:-https://proxy.golang.org,direct} @@ -33,10 +33,10 @@ RUN apk --no-cache add build-base git nodejs npm COPY . ${GOPATH}/src/forgejo.org WORKDIR ${GOPATH}/src/forgejo.org -RUN make clean +RUN make clean-no-bindata RUN make frontend RUN go build contrib/environment-to-ini/environment-to-ini.go && xx-verify environment-to-ini -RUN LDFLAGS="-buildid=" make RELEASE_VERSION=$RELEASE_VERSION GOFLAGS="-trimpath" go-check generate-backend static-executable && xx-verify gitea +RUN LDFLAGS="-buildid=" make FORGEJO_GENERATE_SKIP_HASH=true RELEASE_VERSION=$RELEASE_VERSION GOFLAGS="-trimpath" go-check generate-backend static-executable && xx-verify gitea # Copy local files COPY docker/root /tmp/local @@ -51,7 +51,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \ /go/src/forgejo.org/environment-to-ini RUN chmod 644 /go/src/forgejo.org/contrib/autocompletion/bash_autocomplete -FROM data.forgejo.org/oci/alpine:3.21 +FROM data.forgejo.org/oci/alpine:3.22 ARG RELEASE_VERSION LABEL maintainer="contact@forgejo.org" \ org.opencontainers.image.authors="Forgejo" \ diff --git a/Dockerfile.rootless b/Dockerfile.rootless index 36df26c042..6a3abaa4b9 100644 --- a/Dockerfile.rootless +++ b/Dockerfile.rootless @@ -1,6 +1,6 @@ FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx -FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.21 AS build-env +FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.24-alpine3.22 AS build-env ARG GOPROXY ENV GOPROXY=${GOPROXY:-https://proxy.golang.org,direct} @@ -33,10 +33,10 @@ RUN apk --no-cache add build-base git nodejs npm COPY . ${GOPATH}/src/forgejo.org WORKDIR ${GOPATH}/src/forgejo.org -RUN make clean +RUN make clean-no-bindata RUN make frontend RUN go build contrib/environment-to-ini/environment-to-ini.go && xx-verify environment-to-ini -RUN make RELEASE_VERSION=$RELEASE_VERSION go-check generate-backend static-executable && xx-verify gitea +RUN make FORGEJO_GENERATE_SKIP_HASH=true RELEASE_VERSION=$RELEASE_VERSION go-check generate-backend static-executable && xx-verify gitea # Copy local files COPY docker/rootless /tmp/local @@ -49,7 +49,7 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \ /go/src/forgejo.org/environment-to-ini RUN chmod 644 /go/src/forgejo.org/contrib/autocompletion/bash_autocomplete -FROM data.forgejo.org/oci/alpine:3.21 +FROM data.forgejo.org/oci/alpine:3.22 ARG RELEASE_VERSION LABEL maintainer="contact@forgejo.org" \ org.opencontainers.image.authors="Forgejo" \ diff --git a/Makefile b/Makefile index 9774200b06..e770f2a989 100644 --- a/Makefile +++ b/Makefile @@ -47,8 +47,7 @@ GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasour GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.34.0 # renovate: datasource=go GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.5.2 # renovate: datasource=go -GOPLS_PACKAGE ?= golang.org/x/tools/gopls@v0.18.1 # renovate: datasource=go -RENOVATE_NPM_PACKAGE ?= renovate@40.48.4 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate +RENOVATE_NPM_PACKAGE ?= renovate@41.1.4 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate # https://github.com/disposable-email-domains/disposable-email-domains/commits/main/ DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ... @@ -129,7 +128,7 @@ WEBPACK_CONFIGS := webpack.config.js tailwind.config.js WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts -BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go +BINDATA_DEST := modules/migration/bindata.go modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST)) GENERATED_GO_DEST := modules/charset/invisible_gen.go modules/charset/ambiguous_gen.go @@ -222,7 +221,6 @@ help: @echo " - lint-go lint go files" @echo " - lint-go-fix lint go files and fix issues" @echo " - lint-go-vet lint go files with vet" - @echo " - lint-go-gopls lint go files with gopls" @echo " - lint-js lint js files" @echo " - lint-js-fix lint js files and fix issues" @echo " - lint-css lint css files" @@ -325,8 +323,12 @@ clean-all: clean rm -rf $(WEBPACK_DEST_ENTRIES) node_modules .PHONY: clean -clean: - rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST) $(BINDATA_HASH) \ +clean: clean-no-bindata + rm -rf $(BINDATA_DEST) $(BINDATA_HASH) + +.PHONY: clean-no-bindata +clean-no-bindata: + rm -rf $(EXECUTABLE) $(DIST) \ integrations*.test \ e2e*.test \ tests/integration/gitea-integration-* \ @@ -483,11 +485,6 @@ lint-go-vet: @echo "Running go vet..." @$(GO) vet ./... -.PHONY: lint-go-gopls -lint-go-gopls: - @echo "Running gopls check..." - @GO=$(GO) GOPLS_PACKAGE=$(GOPLS_PACKAGE) tools/lint-go-gopls.sh $(GO_SOURCES_NO_BINDATA) - .PHONY: lint-editorconfig lint-editorconfig: $(GO) run $(EDITORCONFIG_CHECKER_PACKAGE) templates .forgejo/workflows @@ -928,7 +925,6 @@ deps-tools: $(GO) install $(GO_LICENSES_PACKAGE) $(GO) install $(GOVULNCHECK_PACKAGE) $(GO) install $(GOMOCK_PACKAGE) - $(GO) install $(GOPLS_PACKAGE) node_modules: package-lock.json npm install --no-save diff --git a/assets/go-licenses.json b/assets/go-licenses.json index d177bc5653..fb6c201a5e 100644 --- a/assets/go-licenses.json +++ b/assets/go-licenses.json @@ -289,11 +289,6 @@ "path": "github.com/cloudflare/circl/LICENSE", "licenseText": "Copyright (c) 2019 Cloudflare. 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 Cloudflare 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\n========================================================================\n\nCopyright (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": "github.com/cpuguy83/go-md2man/v2/md2man", - "path": "github.com/cpuguy83/go-md2man/v2/md2man/LICENSE.md", - "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2014 Brian Goff\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/cyphar/filepath-securejoin", "path": "github.com/cyphar/filepath-securejoin/LICENSE", @@ -809,6 +804,11 @@ "path": "github.com/opencontainers/image-spec/specs-go/LICENSE", "licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n Copyright 2016 The Linux Foundation.\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n" }, + { + "name": "github.com/philhofer/fwd", + "path": "github.com/philhofer/fwd/LICENSE.md", + "licenseText": "Copyright (c) 2014-2015, Philip Hofer\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + }, { "name": "github.com/pierrec/lz4/v4", "path": "github.com/pierrec/lz4/v4/LICENSE", @@ -884,11 +884,6 @@ "path": "github.com/rs/xid/LICENSE", "licenseText": "Copyright (c) 2015 Olivier Poitrey \u003crs@dailymotion.com\u003e\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 furnished\nto 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\nTHE SOFTWARE.\n" }, - { - "name": "github.com/russross/blackfriday/v2", - "path": "github.com/russross/blackfriday/v2/LICENSE.txt", - "licenseText": "Blackfriday is distributed under the Simplified BSD License:\n\n\u003e Copyright © 2011 Russ Ross\n\u003e All rights reserved.\n\u003e\n\u003e Redistribution and use in source and binary forms, with or without\n\u003e modification, are permitted provided that the following conditions\n\u003e are met:\n\u003e\n\u003e 1. Redistributions of source code must retain the above copyright\n\u003e notice, this list of conditions and the following disclaimer.\n\u003e\n\u003e 2. Redistributions in binary form must reproduce the above\n\u003e copyright notice, this list of conditions and the following\n\u003e disclaimer in the documentation and/or other materials provided with\n\u003e the distribution.\n\u003e\n\u003e THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\u003e \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n\u003e LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\n\u003e FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\n\u003e COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n\u003e INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n\u003e BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n\u003e LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n\u003e CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n\u003e LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\n\u003e ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n\u003e POSSIBILITY OF SUCH DAMAGE.\n" - }, { "name": "github.com/santhosh-tekuri/jsonschema/v6", "path": "github.com/santhosh-tekuri/jsonschema/v6/LICENSE", @@ -924,16 +919,16 @@ "path": "github.com/syndtr/goleveldb/leveldb/LICENSE", "licenseText": "Copyright 2012 Suryandaru Triandana \u003csyndtr@gmail.com\u003e\nAll 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 copyright\nnotice, this list of conditions and the following disclaimer in the\ndocumentation and/or other materials provided with the distribution.\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\nHOLDER 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/tinylib/msgp/msgp", + "path": "github.com/tinylib/msgp/msgp/LICENSE", + "licenseText": "Copyright (c) 2014 Philip Hofer\nPortions Copyright (c) 2009 The Go Authors (license at http://golang.org) where indicated\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + }, { "name": "github.com/ulikunitz/xz", "path": "github.com/ulikunitz/xz/LICENSE", "licenseText": "Copyright (c) 2014-2022 Ulrich Kunitz\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n* My name, Ulrich Kunitz, may not be used to endorse or promote products\n derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (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/urfave/cli/v2", - "path": "github.com/urfave/cli/v2/LICENSE", - "licenseText": "MIT License\n\nCopyright (c) 2022 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/urfave/cli/v3", "path": "github.com/urfave/cli/v3/LICENSE", @@ -954,11 +949,6 @@ "path": "github.com/xanzy/ssh-agent/LICENSE", "licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"{}\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright {yyyy} {name of copyright owner}\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n\n" }, - { - "name": "github.com/xrash/smetrics", - "path": "github.com/xrash/smetrics/LICENSE", - "licenseText": "Copyright (C) 2016 Felipe da Cunha Gonçalves\nAll Rights Reserved.\n\nMIT LICENSE\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject 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, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n" - }, { "name": "github.com/yohcop/openid-go", "path": "github.com/yohcop/openid-go/LICENSE", diff --git a/build.go b/build.go deleted file mode 100644 index d410e171c7..0000000000 --- a/build.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -//go:build vendor - -package main - -// Libraries that are included to vendor utilities used during build. -// These libraries will not be included in a normal compilation. - -import ( - // for embed - _ "github.com/shurcooL/vfsgen" -) diff --git a/build/generate-bindata.go b/build/generate-bindata.go index 2fcb7c2f2a..67d3776847 100644 --- a/build/generate-bindata.go +++ b/build/generate-bindata.go @@ -1,5 +1,6 @@ // Copyright 2020 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later //go:build ignore @@ -7,30 +8,40 @@ package main import ( "bytes" - "crypto/sha1" + "crypto/sha256" "fmt" + "io" + "io/fs" "log" - "net/http" "os" + "path" "path/filepath" "strconv" + "text/template" - "github.com/shurcooL/vfsgen" + "github.com/klauspost/compress/zstd" ) -func needsUpdate(dir, filename string) (bool, []byte) { - needRegen := false +func fileExists(filename string) bool { _, err := os.Stat(filename) - if err != nil { - needRegen = true + if err == nil { + return true } + if os.IsNotExist(err) { + return false + } + panic(err) +} + +func needsUpdate(dir, filename string) (bool, []byte) { + needRegen := !fileExists(filename) oldHash, err := os.ReadFile(filename + ".hash") if err != nil { oldHash = []byte{} } - hasher := sha1.New() + hasher := sha256.New() err = filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { if err != nil { @@ -51,7 +62,7 @@ func needsUpdate(dir, filename string) (bool, []byte) { newHash := hasher.Sum([]byte{}) - if bytes.Compare(oldHash, newHash) != 0 { + if !bytes.Equal(oldHash, newHash) { return true, newHash } @@ -69,24 +80,280 @@ func main() { useGlobalModTime, _ = strconv.ParseBool(os.Args[4]) } - update, newHash := needsUpdate(dir, filename) + if os.Getenv("FORGEJO_GENERATE_SKIP_HASH") == "true" && fileExists(filename) { + fmt.Printf("bindata %s already exists and FORGEJO_GENERATE_SKIP_HASH=true\n", packageName) + return + } + update, newHash := needsUpdate(dir, filename) if !update { - fmt.Printf("bindata for %s already up-to-date\n", packageName) + fmt.Printf("bindata %s already exists and the checksum is a match\n", packageName) return } fmt.Printf("generating bindata for %s\n", packageName) - var fsTemplates http.FileSystem = http.Dir(dir) - err := vfsgen.Generate(fsTemplates, vfsgen.Options{ - PackageName: packageName, - BuildTags: "bindata", - VariableName: "Assets", - Filename: filename, - UseGlobalModTime: useGlobalModTime, - }) + + root, err := os.OpenRoot(dir) if err != nil { - log.Fatalf("%v\n", err) + log.Fatal(err) + } + + out, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644) + if err != nil { + log.Fatal(err) + } + defer out.Close() + + if err := generate(root.FS(), packageName, useGlobalModTime, out); err != nil { + log.Fatal(err) } _ = os.WriteFile(filename+".hash", newHash, 0o666) } + +type file struct { + Path string + Name string + UncompressedSize int + CompressedData []byte + UncompressedData []byte +} + +type direntry struct { + Name string + IsDir bool +} + +func generate(fsRoot fs.FS, packageName string, globalTime bool, output io.Writer) error { + enc, err := zstd.NewWriter(nil, zstd.WithEncoderLevel(zstd.SpeedBestCompression)) + if err != nil { + return err + } + + files := []file{} + + dirs := map[string][]direntry{} + + if err := fs.WalkDir(fsRoot, ".", func(filePath string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + entries, err := fs.ReadDir(fsRoot, filePath) + if err != nil { + return err + } + dirEntries := make([]direntry, 0, len(entries)) + for _, entry := range entries { + dirEntries = append(dirEntries, direntry{Name: entry.Name(), IsDir: entry.IsDir()}) + } + dirs[filePath] = dirEntries + return nil + } + + src, err := fs.ReadFile(fsRoot, filePath) + if err != nil { + return err + } + + dst := enc.EncodeAll(src, nil) + if len(dst) < len(src) { + files = append(files, file{ + Path: filePath, + Name: path.Base(filePath), + UncompressedSize: len(src), + CompressedData: dst, + }) + } else { + files = append(files, file{ + Path: filePath, + Name: path.Base(filePath), + UncompressedData: src, + }) + } + return nil + }); err != nil { + return err + } + + return generatedTmpl.Execute(output, map[string]any{ + "Packagename": packageName, + "GlobalTime": globalTime, + "Files": files, + "Dirs": dirs, + }) +} + +var generatedTmpl = template.Must(template.New("").Parse(`// Code generated by efs-gen. DO NOT EDIT. + +//go:build bindata + +package {{.Packagename}} + +import ( + "bytes" + "time" + "io" + "io/fs" + + "github.com/klauspost/compress/zstd" +) + +type normalFile struct { + name string + content []byte +} + +type compressedFile struct { + name string + uncompressedSize int64 + data []byte +} + +var files = map[string]any{ +{{- range .Files}} + "{{.Path}}": {{if .CompressedData}}compressedFile{"{{.Name}}", {{.UncompressedSize}}, []byte({{printf "%+q" .CompressedData}})}{{else}}normalFile{"{{.Name}}", []byte({{printf "%+q" .UncompressedData}})}{{end}}, +{{- end}} +} + +var dirs = map[string][]fs.DirEntry{ +{{- range $key, $entry := .Dirs}} + "{{$key}}": { +{{- range $entry}} + direntry{"{{.Name}}", {{.IsDir}}}, +{{- end}} + }, +{{- end}} +} + +type assets struct{} + +var Assets = assets{} + +func (a assets) Open(name string) (fs.File, error) { + f, ok := files[name] + if !ok { + return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} + } + + switch f := f.(type) { + case normalFile: + return file{name: f.name, size: int64(len(f.content)), data: bytes.NewReader(f.content)}, nil + case compressedFile: + r, _ := zstd.NewReader(bytes.NewReader(f.data)) + return &compressFile{name: f.name, size: f.uncompressedSize, data: r, content: f.data}, nil + default: + panic("unknown file type") + } +} + +func (a assets) ReadDir(name string) ([]fs.DirEntry, error) { + d, ok := dirs[name] + if !ok { + return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist} + } + return d, nil +} + +type file struct { + name string + size int64 + data io.ReadSeeker +} + +var _ io.ReadSeeker = (*file)(nil) + +func (f file) Stat() (fs.FileInfo, error) { + return fileinfo{name: f.name, size: f.size}, nil +} + +func (f file) Read(p []byte) (int, error) { + return f.data.Read(p) +} + +func (f file) Seek(offset int64, whence int) (int64, error) { + return f.data.Seek(offset, whence) +} + +func (f file) Close() error { return nil } + +type compressFile struct { + name string + size int64 + data *zstd.Decoder + content []byte + zstdPos int64 + seekPos int64 +} + +var _ io.ReadSeeker = (*compressFile)(nil) + +func (f *compressFile) Stat() (fs.FileInfo, error) { + return fileinfo{name: f.name, size: f.size}, nil +} + +func (f *compressFile) Read(p []byte) (int, error) { + if f.zstdPos > f.seekPos { + if err := f.data.Reset(bytes.NewReader(f.content)); err != nil { + return 0, err + } + f.zstdPos = 0 + } + if f.zstdPos < f.seekPos { + if _, err := io.CopyN(io.Discard, f.data, f.seekPos - f.zstdPos); err != nil { + return 0, err + } + f.zstdPos = f.seekPos + } + n, err := f.data.Read(p) + f.zstdPos += int64(n) + f.seekPos = f.zstdPos + return n, err +} + +func (f *compressFile) Seek(offset int64, whence int) (int64, error) { + switch whence { + case io.SeekStart: + f.seekPos = 0 + offset + case io.SeekCurrent: + f.seekPos += offset + case io.SeekEnd: + f.seekPos = f.size + offset + } + return f.seekPos, nil +} + +func (f *compressFile) Close() error { + f.data.Close() + return nil +} + +func (f *compressFile) ZstdBytes() []byte { return f.content } + +type fileinfo struct { + name string + size int64 +} + +func (f fileinfo) Name() string { return f.name } +func (f fileinfo) Size() int64 { return f.size } +func (f fileinfo) Mode() fs.FileMode { return 0o444 } +func (f fileinfo) ModTime() time.Time { return {{if .GlobalTime}}GlobalModTime(f.name){{else}}time.Unix(0, 0){{end}} } +func (f fileinfo) IsDir() bool { return false } +func (f fileinfo) Sys() any { return nil } + +type direntry struct { + name string + isDir bool +} + +func (d direntry) Name() string { return d.name } +func (d direntry) IsDir() bool { return d.isDir } +func (d direntry) Type() fs.FileMode { + if d.isDir { + return 0o755 | fs.ModeDir + } + return 0o444 +} +func (direntry) Info() (fs.FileInfo, error) { return nil, fs.ErrNotExist } +`)) diff --git a/build/lint-locale/lint-locale_test.go b/build/lint-locale/lint-locale_test.go index 9e9a931feb..dd146c0d70 100644 --- a/build/lint-locale/lint-locale_test.go +++ b/build/lint-locale/lint-locale_test.go @@ -37,6 +37,7 @@ func TestLocalizationPolicy(t *testing.T) { assert.Empty(t, checkLocaleContent([]byte("teams.specific_repositories_helper = Members will only have access to repositories explicitly added to the team. Selecting this will not automatically remove repositories already added with All repositories."))) assert.Empty(t, checkLocaleContent([]byte("sqlite_helper = File path for the SQLite3 database.
Enter an absolute path if you run Forgejo as a service."))) assert.Empty(t, checkLocaleContent([]byte("hi_user_x = Hi %s,"))) + assert.Empty(t, checkLocaleContent([]byte("key = Press Shift"))) assert.Equal(t, []string{"error404: The page you are trying to reach either does not exist or you are not authorized to view it."}, checkLocaleContent([]byte("error404 = The page you are trying to reach either does not exist or you are not authorized to view it."))) }) diff --git a/contrib/autocompletion/bash_autocomplete b/contrib/autocompletion/bash_autocomplete index 5cb62f26a7..58844938a6 100755 --- a/contrib/autocompletion/bash_autocomplete +++ b/contrib/autocompletion/bash_autocomplete @@ -1,4 +1,3 @@ -#! /bin/bash # Heavily inspired by https://github.com/urfave/cli _cli_bash_autocomplete() { @@ -7,9 +6,9 @@ _cli_bash_autocomplete() { COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" if [[ "$cur" == "-"* ]]; then - opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion ) + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-shell-completion ) else - opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-shell-completion ) fi COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 diff --git a/contrib/autocompletion/zsh_autocomplete b/contrib/autocompletion/zsh_autocomplete index b3b40df503..0fd1a0b175 100644 --- a/contrib/autocompletion/zsh_autocomplete +++ b/contrib/autocompletion/zsh_autocomplete @@ -9,9 +9,9 @@ _cli_zsh_autocomplete() { local cur cur=${words[-1]} if [[ "$cur" == "-"* ]]; then - opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") + opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-shell-completion)}") else - opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}") + opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-shell-completion)}") fi if [[ "${opts[1]}" != "" ]]; then diff --git a/contrib/environment-to-ini/README b/contrib/environment-to-ini/README index f1d3f2ae83..e4caf25666 100644 --- a/contrib/environment-to-ini/README +++ b/contrib/environment-to-ini/README @@ -1,45 +1,46 @@ Environment To Ini ================== -Multiple docker users have requested that the Gitea docker is changed -to permit arbitrary configuration via environment variables. +This tool allows defining Forgejo's entire configuration via environment +variables, mostly geared towards usage in Docker. -Gitea needs to use an ini file for configuration because the running -environment that starts the docker may not be the same as that used -by the hooks. An ini file also gives a good default and means that -users do not have to completely provide a full environment. +Forgejo needs to use an INI file for configuration because the running +environment that starts the container may not be the same as the one used +by the hooks. An INI file also gives a good default and means that +users do not have to provide the entire set of environment variables. With those caveats above, this command provides a generic way of converting suitably structured environment variables into any ini value. -To use the command is very simple just run it and the default gitea -app.ini will be rewritten to take account of the variables provided, -however there are various options to give slightly different -behavior and these can be interrogated with the `-h` option. +When run, `environment-to-ini` will write the config files based on the +environment variables provided. +Check with the `-h` flag for several options to alter this behaviour. -The environment variables should be of the form: +Environment variables of the form "FORGEJO__SECTION_NAME__KEY_NAME" +will be mapped to the ini section "[section_name]" and the key +"KEY_NAME" with the value as provided. - GITEA__SECTION_NAME__KEY_NAME - -Note, SECTION_NAME in the notation above is case-insensitive. +Environment variables of the form "FORGEJO__SECTION_NAME__KEY_NAME__FILE" +will be mapped to the ini section "[section_name]" and the key +"KEY_NAME" with the value loaded from the specified file. Environment variables are usually restricted to a reduced character set "0-9A-Z_" - in order to allow the setting of sections with characters outside of that set, they should be escaped as following: -"_0X2E_" for "." and "_0X2D_" for "-". The entire section and key names -can be escaped as a UTF8 byte string if necessary. E.g. to configure: +"_0X2E_" for ".". The entire section and key names can be escaped as +a UTF8 byte string if necessary. E.g. to configure: - """ - ... - [log.console] - COLORIZE=false - STDERR=true - ... - """ + """ + ... + [log.console] + COLORIZE=false + STDERR=true + ... + """ -You would set the environment variables: "GITEA__LOG_0x2E_CONSOLE__COLORIZE=false" -and "GITEA__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found +You would set the environment variables: "FORGEJO__LOG_0x2E_CONSOLE__COLORIZE=false" +and "FORGEJO__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found on the configuration cheat sheet. To build locally, run: diff --git a/eslint.config.mjs b/eslint.config.mjs index 5737bed623..28cfa80089 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,5 +1,5 @@ import eslintCommunityEslintPluginEslintComments from '@eslint-community/eslint-plugin-eslint-comments'; -import stylisticEslintPluginJs from '@stylistic/eslint-plugin-js'; +import stylisticEslintPlugin from '@stylistic/eslint-plugin'; import vitest from '@vitest/eslint-plugin'; import arrayFunc from 'eslint-plugin-array-func'; import eslintPluginImportX from 'eslint-plugin-import-x'; @@ -26,7 +26,7 @@ export default tseslint.config( { plugins: { '@eslint-community/eslint-comments': eslintCommunityEslintPluginEslintComments, - '@stylistic/js': stylisticEslintPluginJs, + '@stylistic': stylisticEslintPlugin, '@vitest': vitest, 'array-func': arrayFunc, 'no-jquery': noJquery, @@ -69,62 +69,62 @@ export default tseslint.config( '@eslint-community/eslint-comments/no-unused-enable': [2], '@eslint-community/eslint-comments/no-use': [0], '@eslint-community/eslint-comments/require-description': [0], - '@stylistic/js/array-bracket-newline': [0], - '@stylistic/js/array-bracket-spacing': [2, 'never'], - '@stylistic/js/array-element-newline': [0], - '@stylistic/js/arrow-parens': [2, 'always'], + '@stylistic/array-bracket-newline': [0], + '@stylistic/array-bracket-spacing': [2, 'never'], + '@stylistic/array-element-newline': [0], + '@stylistic/arrow-parens': [2, 'always'], - '@stylistic/js/arrow-spacing': [2, { + '@stylistic/arrow-spacing': [2, { before: true, after: true, }], - '@stylistic/js/block-spacing': [0], + '@stylistic/block-spacing': [0], - '@stylistic/js/brace-style': [2, '1tbs', { + '@stylistic/brace-style': [2, '1tbs', { allowSingleLine: true, }], - '@stylistic/js/comma-dangle': [2, 'always-multiline'], + '@stylistic/comma-dangle': [2, 'always-multiline'], - '@stylistic/js/comma-spacing': [2, { + '@stylistic/comma-spacing': [2, { before: false, after: true, }], - '@stylistic/js/comma-style': [2, 'last'], - '@stylistic/js/computed-property-spacing': [2, 'never'], - '@stylistic/js/dot-location': [2, 'property'], - '@stylistic/js/eol-last': [2], - '@stylistic/js/function-call-spacing': [2, 'never'], - '@stylistic/js/function-call-argument-newline': [0], - '@stylistic/js/function-paren-newline': [0], - '@stylistic/js/generator-star-spacing': [0], - '@stylistic/js/implicit-arrow-linebreak': [0], + '@stylistic/comma-style': [2, 'last'], + '@stylistic/computed-property-spacing': [2, 'never'], + '@stylistic/dot-location': [2, 'property'], + '@stylistic/eol-last': [2], + '@stylistic/function-call-spacing': [2, 'never'], + '@stylistic/function-call-argument-newline': [0], + '@stylistic/function-paren-newline': [0], + '@stylistic/generator-star-spacing': [0], + '@stylistic/implicit-arrow-linebreak': [0], - '@stylistic/js/indent': [2, 2, { + '@stylistic/indent': [2, 2, { ignoreComments: true, SwitchCase: 1, }], - '@stylistic/js/key-spacing': [2], - '@stylistic/js/keyword-spacing': [2], - '@stylistic/js/linebreak-style': [2, 'unix'], - '@stylistic/js/lines-around-comment': [0], - '@stylistic/js/lines-between-class-members': [0], - '@stylistic/js/max-len': [0], - '@stylistic/js/max-statements-per-line': [0], - '@stylistic/js/multiline-ternary': [0], - '@stylistic/js/new-parens': [2], - '@stylistic/js/newline-per-chained-call': [0], - '@stylistic/js/no-confusing-arrow': [0], - '@stylistic/js/no-extra-parens': [0], - '@stylistic/js/no-extra-semi': [2], - '@stylistic/js/no-floating-decimal': [0], - '@stylistic/js/no-mixed-operators': [0], - '@stylistic/js/no-mixed-spaces-and-tabs': [2], + '@stylistic/key-spacing': [2], + '@stylistic/keyword-spacing': [2], + '@stylistic/linebreak-style': [2, 'unix'], + '@stylistic/lines-around-comment': [0], + '@stylistic/lines-between-class-members': [0], + '@stylistic/max-len': [0], + '@stylistic/max-statements-per-line': [0], + '@stylistic/multiline-ternary': [0], + '@stylistic/new-parens': [2], + '@stylistic/newline-per-chained-call': [0], + '@stylistic/no-confusing-arrow': [0], + '@stylistic/no-extra-parens': [0], + '@stylistic/no-extra-semi': [2], + '@stylistic/no-floating-decimal': [0], + '@stylistic/no-mixed-operators': [0], + '@stylistic/no-mixed-spaces-and-tabs': [2], - '@stylistic/js/no-multi-spaces': [2, { + '@stylistic/no-multi-spaces': [2, { ignoreEOLComments: true, exceptions: { @@ -132,60 +132,60 @@ export default tseslint.config( }, }], - '@stylistic/js/no-multiple-empty-lines': [2, { + '@stylistic/no-multiple-empty-lines': [2, { max: 1, maxEOF: 0, maxBOF: 0, }], - '@stylistic/js/no-tabs': [2], - '@stylistic/js/no-trailing-spaces': [2], - '@stylistic/js/no-whitespace-before-property': [2], - '@stylistic/js/nonblock-statement-body-position': [2], - '@stylistic/js/object-curly-newline': [0], - '@stylistic/js/object-curly-spacing': [2, 'never'], - '@stylistic/js/object-property-newline': [0], - '@stylistic/js/one-var-declaration-per-line': [0], - '@stylistic/js/operator-linebreak': [2, 'after'], - '@stylistic/js/padded-blocks': [2, 'never'], - '@stylistic/js/padding-line-between-statements': [0], - '@stylistic/js/quote-props': [0], + '@stylistic/no-tabs': [2], + '@stylistic/no-trailing-spaces': [2], + '@stylistic/no-whitespace-before-property': [2], + '@stylistic/nonblock-statement-body-position': [2], + '@stylistic/object-curly-newline': [0], + '@stylistic/object-curly-spacing': [2, 'never'], + '@stylistic/object-property-newline': [0], + '@stylistic/one-var-declaration-per-line': [0], + '@stylistic/operator-linebreak': [2, 'after'], + '@stylistic/padded-blocks': [2, 'never'], + '@stylistic/padding-line-between-statements': [0], + '@stylistic/quote-props': [0], - '@stylistic/js/quotes': [2, 'single', { + '@stylistic/quotes': [2, 'single', { avoidEscape: true, allowTemplateLiterals: true, }], - '@stylistic/js/rest-spread-spacing': [2, 'never'], + '@stylistic/rest-spread-spacing': [2, 'never'], - '@stylistic/js/semi': [2, 'always', { + '@stylistic/semi': [2, 'always', { omitLastInOneLineBlock: true, }], - '@stylistic/js/semi-spacing': [2, { + '@stylistic/semi-spacing': [2, { before: false, after: true, }], - '@stylistic/js/semi-style': [2, 'last'], - '@stylistic/js/space-before-blocks': [2, 'always'], + '@stylistic/semi-style': [2, 'last'], + '@stylistic/space-before-blocks': [2, 'always'], - '@stylistic/js/space-before-function-paren': [2, { + '@stylistic/space-before-function-paren': [2, { anonymous: 'ignore', named: 'never', asyncArrow: 'always', }], - '@stylistic/js/space-in-parens': [2, 'never'], - '@stylistic/js/space-infix-ops': [2], - '@stylistic/js/space-unary-ops': [2], - '@stylistic/js/spaced-comment': [2, 'always'], - '@stylistic/js/switch-colon-spacing': [2], - '@stylistic/js/template-curly-spacing': [2, 'never'], - '@stylistic/js/template-tag-spacing': [2, 'never'], - '@stylistic/js/wrap-iife': [2, 'inside'], - '@stylistic/js/wrap-regex': [0], - '@stylistic/js/yield-star-spacing': [2, 'after'], + '@stylistic/space-in-parens': [2, 'never'], + '@stylistic/space-infix-ops': [2], + '@stylistic/space-unary-ops': [2], + '@stylistic/spaced-comment': [2, 'always'], + '@stylistic/switch-colon-spacing': [2], + '@stylistic/template-curly-spacing': [2, 'never'], + '@stylistic/template-tag-spacing': [2, 'never'], + '@stylistic/wrap-iife': [2, 'inside'], + '@stylistic/wrap-regex': [0], + '@stylistic/yield-star-spacing': [2, 'after'], 'accessor-pairs': [2], 'array-callback-return': [2, { diff --git a/flake.lock b/flake.lock index 90672733d5..dcf7755013 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1733392399, - "narHash": "sha256-kEsTJTUQfQFIJOcLYFt/RvNxIK653ZkTBIs4DG+cBns=", + "lastModified": 1749285348, + "narHash": "sha256-frdhQvPbmDYaScPFiCnfdh3B/Vh81Uuoo0w5TkWmmjU=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d0797a04b81caeae77bcff10a9dde78bc17f5661", + "rev": "3e3afe5174c561dee0df6f2c2b2236990146329f", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index daa66d1d04..01b23258b9 100644 --- a/flake.nix +++ b/flake.nix @@ -3,35 +3,20 @@ nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; }; - outputs = { - nixpkgs, - flake-utils, - ... - }: + outputs = + { + nixpkgs, + flake-utils, + ... + }: flake-utils.lib.eachDefaultSystem ( - system: let + system: + let pkgs = nixpkgs.legacyPackages.${system}; - in { - devShells.default = pkgs.mkShell { - buildInputs = with pkgs; [ - # generic - git - git-lfs - gnumake - gnused - gnutar - gzip - - # frontend - nodejs_20 - - # backend - gofumpt - sqlite - go - gopls - ]; - }; + in + { + devShells.default = import ./shell.nix { inherit pkgs; }; + formatter = pkgs.nixfmt-rfc-style; } ); } diff --git a/go.mod b/go.mod index 7ed17b0b7a..bb2be827eb 100644 --- a/go.mod +++ b/go.mod @@ -41,14 +41,14 @@ require ( github.com/gliderlabs/ssh v0.3.8 github.com/go-ap/activitypub v0.0.0-20231114162308-e219254dc5c9 github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 - github.com/go-chi/chi/v5 v5.2.1 + github.com/go-chi/chi/v5 v5.2.2 github.com/go-chi/cors v1.2.1 github.com/go-co-op/gocron v1.37.0 github.com/go-enry/go-enry/v2 v2.9.2 github.com/go-git/go-git/v5 v5.13.2 github.com/go-ldap/ldap/v3 v3.4.6 github.com/go-openapi/spec v0.21.0 - github.com/go-sql-driver/mysql v1.9.2 + github.com/go-sql-driver/mysql v1.9.3 github.com/go-webauthn/webauthn v0.13.0 github.com/gobwas/glob v0.2.3 github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f @@ -76,7 +76,7 @@ require ( github.com/meilisearch/meilisearch-go v0.31.0 github.com/mholt/archiver/v3 v3.5.1 github.com/microcosm-cc/bluemonday v1.0.27 - github.com/minio/minio-go/v7 v7.0.91 + github.com/minio/minio-go/v7 v7.0.94 github.com/msteinert/pam/v2 v2.1.0 github.com/nektos/act v0.2.52 github.com/niklasfasching/go-org v1.8.0 @@ -89,17 +89,15 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 github.com/sergi/go-diff v1.4.0 - github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 github.com/stretchr/testify v1.10.0 github.com/syndtr/goleveldb v1.0.0 github.com/ulikunitz/xz v0.5.12 - github.com/urfave/cli/v2 v2.27.6 github.com/urfave/cli/v3 v3.3.3 github.com/valyala/fastjson v1.6.4 github.com/yohcop/openid-go v1.0.1 github.com/yuin/goldmark v1.7.12 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc - gitlab.com/gitlab-org/api/client-go v0.129.0 + gitlab.com/gitlab-org/api/client-go v0.130.1 go.uber.org/mock v0.5.2 golang.org/x/crypto v0.39.0 golang.org/x/image v0.27.0 @@ -153,7 +151,6 @@ require ( 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/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/cyphar/filepath-securejoin v0.3.6 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidmz/go-pageant v1.0.2 // indirect @@ -210,6 +207,7 @@ require ( github.com/nwaples/rardecode v1.1.3 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onsi/ginkgo v1.16.5 // indirect + github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -221,15 +219,13 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/rs/xid v1.6.0 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/skeema/knownhosts v1.3.0 // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect + github.com/tinylib/msgp v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect - github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/zeebo/assert v1.3.0 // indirect github.com/zeebo/blake3 v0.2.4 // indirect go.etcd.io/bbolt v1.4.0 // indirect @@ -246,9 +242,7 @@ require ( replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1 -replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 - -replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.26.0 +replace github.com/nektos/act => code.forgejo.org/forgejo/act v1.28.0 replace github.com/mholt/archiver/v3 => code.forgejo.org/forgejo/archiver/v3 v3.5.1 diff --git a/go.sum b/go.sum index 3b462ccab7..639880e2ce 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ code.forgejo.org/f3/gof3/v3 v3.11.0 h1:f/xToKwqTgxG6PYxvewywjDQyCcyHEEJ6sZqUitFs code.forgejo.org/f3/gof3/v3 v3.11.0/go.mod h1:4FaRUNSQGBiD1M0DuB0yNv+Z2wMtlOeckgygHSSq4KQ= code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251 h1:HTZl3CBk3ABNYtFI6TPLvJgGKFIhKT5CBk0sbOtkDKU= code.forgejo.org/forgejo-contrib/go-libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:PphB88CPbx601QrWPMZATeorACeVmQlyv3u+uUMbSaM= -code.forgejo.org/forgejo/act v1.26.0 h1:6mTmoaw7d/WpYiw/Pw6AaypxFdgJog5OFi/PMEgEbxs= -code.forgejo.org/forgejo/act v1.26.0/go.mod h1:HFDFrXPrqfM9aH2RCnMiBdo/3ThxDmZjp58InPjGOfo= +code.forgejo.org/forgejo/act v1.28.0 h1:96njNC7C1YNyjWq5OWvLZMF/nw0PMthzIA8Nwbnn7jo= +code.forgejo.org/forgejo/act v1.28.0/go.mod h1:dFuiwAmD5vyrzecysHB2kL/GM3wRpoVPl+WdbCTC8Bs= code.forgejo.org/forgejo/archiver/v3 v3.5.1 h1:UmmbA7D5550uf71SQjarmrn6yKwOGxtEjb3jaYYtmSE= code.forgejo.org/forgejo/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= code.forgejo.org/forgejo/go-rpmutils v1.0.0 h1:RZGGeKt70p/WaIEL97pyT6uiiEIoN8/aLmS5Z6WmX0M= @@ -152,8 +152,6 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= -github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= -github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM= github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= @@ -215,8 +213,8 @@ github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73/go.mod h1:jyveZeGw5La github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-chi/chi/v5 v5.0.1/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= -github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= -github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= +github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= @@ -247,8 +245,8 @@ github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9Z github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= -github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= @@ -384,8 +382,6 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libdns/libdns v1.0.0-beta.1 h1:KIf4wLfsrEpXpZ3vmc/poM8zCATXT2klbdPe6hyOBjQ= github.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= -github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0 h1:F/3FfGmKdiKFa8kL3YrpZ7pe9H4l4AzA1pbaOUnRvPI= -github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0/go.mod h1:JEfTc3+2DF9Z4PXhLLvXL42zexJyh8rIq3OzUj/0rAk= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= @@ -415,8 +411,8 @@ github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY github.com/minio/crc64nvme v1.0.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.91 h1:tWLZnEfo3OZl5PoXQwcwTAPNNrjyWwOh6cbZitW5JQc= -github.com/minio/minio-go/v7 v7.0.91/go.mod h1:uvMUcGrpgeSAAI6+sD3818508nUyMULw94j2Nxku/Go= +github.com/minio/minio-go/v7 v7.0.94 h1:1ZoksIKPyaSt64AVOyaQvhDOgVC3MfZsWM6mZXRUGtM= +github.com/minio/minio-go/v7 v7.0.94/go.mod h1:71t2CqDt3ThzESgZUlU1rBN54mksGGlkLcFgguDnnAc= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -459,6 +455,8 @@ github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3I github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= @@ -497,15 +495,11 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs= -github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= @@ -529,12 +523,12 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww= +github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g= -github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I= github.com/urfave/cli/v3 v3.3.3/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= @@ -545,8 +539,6 @@ github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= -github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= -github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js= @@ -564,8 +556,8 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= -gitlab.com/gitlab-org/api/client-go v0.129.0 h1:o9KLn6fezmxBQWYnQrnilwyuOjlx4206KP0bUn3HuBE= -gitlab.com/gitlab-org/api/client-go v0.129.0/go.mod h1:ZhSxLAWadqP6J9lMh40IAZOlOxBLPRh7yFOXR/bMJWM= +gitlab.com/gitlab-org/api/client-go v0.130.1 h1:1xF5C5Zq3sFeNg3PzS2z63oqrxifne3n/OnbI7nptRc= +gitlab.com/gitlab-org/api/client-go v0.130.1/go.mod h1:ZhSxLAWadqP6J9lMh40IAZOlOxBLPRh7yFOXR/bMJWM= go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= diff --git a/main.go b/main.go index 3f0283db7f..ade43881cf 100644 --- a/main.go +++ b/main.go @@ -21,7 +21,7 @@ import ( _ "forgejo.org/modules/markup/markdown" _ "forgejo.org/modules/markup/orgmode" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // these flags will be set by the build flags diff --git a/models/actions/run.go b/models/actions/run.go index 48756b7a08..55def805ed 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -55,6 +55,7 @@ type ActionRun struct { PreviousDuration time.Duration Created timeutil.TimeStamp `xorm:"created"` Updated timeutil.TimeStamp `xorm:"updated"` + NotifyEmail bool } func init() { diff --git a/models/activities/action.go b/models/activities/action.go index a309637d04..8592f81414 100644 --- a/models/activities/action.go +++ b/models/activities/action.go @@ -442,6 +442,12 @@ func (a *Action) GetIssueContent(ctx context.Context) string { return a.Issue.Content } +func GetActivityByID(ctx context.Context, id int64) (*Action, error) { + var act Action + _, err := db.GetEngine(ctx).ID(id).Get(&act) + return &act, err +} + // GetFeedsOptions options for retrieving feeds type GetFeedsOptions struct { db.ListOptions @@ -467,11 +473,8 @@ func GetFeeds(ctx context.Context, opts GetFeedsOptions) (ActionList, int64, err return nil, 0, err } - sess := db.GetEngine(ctx).Where(cond). - Select("`action`.*"). // this line will avoid select other joined table's columns - Join("INNER", "repository", "`repository`.id = `action`.repo_id") - opts.SetDefaultValues() + sess := db.GetEngine(ctx).Where(cond) sess = db.SetSessionPagination(sess, &opts) actions := make([]*Action, 0, opts.PageSize) @@ -598,13 +601,14 @@ func DeleteOldActions(ctx context.Context, olderThan time.Duration) (err error) } // NotifyWatchers creates batch of actions for every watcher. -func NotifyWatchers(ctx context.Context, actions ...*Action) error { +func NotifyWatchers(ctx context.Context, actions ...*Action) ([]Action, error) { var watchers []*repo_model.Watch var repo *repo_model.Repository var err error var permCode []bool var permIssue []bool var permPR []bool + var out []Action e := db.GetEngine(ctx) @@ -615,14 +619,14 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error { // Add feeds for user self and all watchers. watchers, err = repo_model.GetWatchers(ctx, act.RepoID) if err != nil { - return fmt.Errorf("get watchers: %w", err) + return nil, fmt.Errorf("get watchers: %w", err) } // Be aware that optimizing this correctly into the `GetWatchers` SQL // query is for most cases less performant than doing this. blockedDoerUserIDs, err := user_model.ListBlockedByUsersID(ctx, act.ActUserID) if err != nil { - return fmt.Errorf("user_model.ListBlockedByUsersID: %w", err) + return nil, fmt.Errorf("user_model.ListBlockedByUsersID: %w", err) } if len(blockedDoerUserIDs) > 0 { @@ -637,8 +641,9 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error { // Add feed for actioner. act.UserID = act.ActUserID if _, err = e.Insert(act); err != nil { - return fmt.Errorf("insert new actioner: %w", err) + return nil, fmt.Errorf("insert new actioner: %w", err) } + out = append(out, *act) if repoChanged { act.loadRepo(ctx) @@ -646,7 +651,7 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error { // check repo owner exist. if err := act.Repo.LoadOwner(ctx); err != nil { - return fmt.Errorf("can't get repo owner: %w", err) + return nil, fmt.Errorf("can't get repo owner: %w", err) } } else if act.Repo == nil { act.Repo = repo @@ -657,7 +662,7 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error { act.ID = 0 act.UserID = act.Repo.Owner.ID if err = db.Insert(ctx, act); err != nil { - return fmt.Errorf("insert new actioner: %w", err) + return nil, fmt.Errorf("insert new actioner: %w", err) } } @@ -710,26 +715,29 @@ func NotifyWatchers(ctx context.Context, actions ...*Action) error { } if err = db.Insert(ctx, act); err != nil { - return fmt.Errorf("insert new action: %w", err) + return nil, fmt.Errorf("insert new action: %w", err) } } } - return nil + return out, nil } // NotifyWatchersActions creates batch of actions for every watcher. -func NotifyWatchersActions(ctx context.Context, acts []*Action) error { +func NotifyWatchersActions(ctx context.Context, acts []*Action) ([]Action, error) { ctx, committer, err := db.TxContext(ctx) if err != nil { - return err + return nil, err } defer committer.Close() + var out []Action for _, act := range acts { - if err := NotifyWatchers(ctx, act); err != nil { - return err + as, err := NotifyWatchers(ctx, act) + if err != nil { + return nil, err } + out = append(out, as...) } - return committer.Commit() + return out, committer.Commit() } // DeleteIssueActions delete all actions related with issueID diff --git a/models/activities/action_test.go b/models/activities/action_test.go index 8b9b2f6929..47dbd8ac2d 100644 --- a/models/activities/action_test.go +++ b/models/activities/action_test.go @@ -197,7 +197,8 @@ func TestNotifyWatchers(t *testing.T) { RepoID: 1, OpType: activities_model.ActionStarRepo, } - require.NoError(t, activities_model.NotifyWatchers(db.DefaultContext, action)) + _, err := activities_model.NotifyWatchers(db.DefaultContext, action) + require.NoError(t, err) // One watchers are inactive, thus action is only created for user 8, 1, 4, 11 unittest.AssertExistsAndLoadBean(t, &activities_model.Action{ @@ -226,24 +227,6 @@ func TestNotifyWatchers(t *testing.T) { }) } -func TestGetFeedsCorrupted(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - unittest.AssertExistsAndLoadBean(t, &activities_model.Action{ - ID: 8, - RepoID: 1700, - }) - - actions, count, err := activities_model.GetFeeds(db.DefaultContext, activities_model.GetFeedsOptions{ - RequestedUser: user, - Actor: user, - IncludePrivate: true, - }) - require.NoError(t, err) - assert.Empty(t, actions) - assert.Equal(t, int64(0), count) -} - func TestConsistencyUpdateAction(t *testing.T) { if !setting.Database.Type.IsSQLite3() { t.Skip("Test is only for SQLite database.") diff --git a/models/activities/federated_user_activity.go b/models/activities/federated_user_activity.go new file mode 100644 index 0000000000..1ff3a855d0 --- /dev/null +++ b/models/activities/federated_user_activity.go @@ -0,0 +1,106 @@ +// Copyright 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package activities + +import ( + "context" + "fmt" + + "forgejo.org/models/db" + user_model "forgejo.org/models/user" + "forgejo.org/modules/json" + "forgejo.org/modules/log" + "forgejo.org/modules/timeutil" + "forgejo.org/modules/validation" + + ap "github.com/go-ap/activitypub" +) + +type FederatedUserActivity struct { + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"NOT NULL"` + ActorID int64 + ActorURI string + Actor *user_model.User `xorm:"-"` // transient + NoteContent string `xorm:"TEXT"` + NoteURL string `xorm:"VARCHAR(255)"` + OriginalNote string `xorm:"TEXT"` + Created timeutil.TimeStamp `xorm:"created"` +} + +func init() { + db.RegisterModel(new(FederatedUserActivity)) +} + +func NewFederatedUserActivity(userID, actorID int64, actorURI, noteContent, noteURL string, originalNote ap.Activity) (FederatedUserActivity, error) { + jsonString, err := json.Marshal(originalNote) + if err != nil { + return FederatedUserActivity{}, err + } + result := FederatedUserActivity{ + UserID: userID, + ActorID: actorID, + ActorURI: actorURI, + NoteContent: noteContent, + NoteURL: noteURL, + OriginalNote: string(jsonString), + } + if valid, err := validation.IsValid(result); !valid { + return FederatedUserActivity{}, err + } + return result, nil +} + +func (federatedUserActivity FederatedUserActivity) Validate() []string { + var result []string + result = append(result, validation.ValidateNotEmpty(federatedUserActivity.UserID, "UserID")...) + result = append(result, validation.ValidateNotEmpty(federatedUserActivity.ActorID, "ActorID")...) + result = append(result, validation.ValidateNotEmpty(federatedUserActivity.ActorURI, "ActorURI")...) + result = append(result, validation.ValidateNotEmpty(federatedUserActivity.NoteContent, "NoteContent")...) + result = append(result, validation.ValidateNotEmpty(federatedUserActivity.NoteURL, "NoteURL")...) + result = append(result, validation.ValidateNotEmpty(federatedUserActivity.OriginalNote, "OriginalNote")...) + return result +} + +func CreateUserActivity(ctx context.Context, federatedUserActivity *FederatedUserActivity) error { + if valid, err := validation.IsValid(federatedUserActivity); !valid { + return err + } + _, err := db.GetEngine(ctx).Insert(federatedUserActivity) + return err +} + +type GetFollowingFeedsOptions struct { + db.ListOptions +} + +func GetFollowingFeeds(ctx context.Context, actorID int64, opts GetFollowingFeedsOptions) ([]*FederatedUserActivity, int64, error) { + log.Debug("user_id = %s", actorID) + sess := db.GetEngine(ctx).Where("user_id = ?", actorID) + opts.SetDefaultValues() + sess = db.SetSessionPagination(sess, &opts) + + actions := make([]*FederatedUserActivity, 0, opts.PageSize) + count, err := sess.FindAndCount(&actions) + if err != nil { + return nil, 0, fmt.Errorf("FindAndCount: %w", err) + } + for _, act := range actions { + if err := act.loadActor(ctx); err != nil { + return nil, 0, err + } + } + return actions, count, err +} + +func (federatedUserActivity *FederatedUserActivity) loadActor(ctx context.Context) error { + log.Debug("for activity %s", federatedUserActivity) + actorUser, _, err := user_model.GetFederatedUserByUserID(ctx, federatedUserActivity.ActorID) + if err != nil { + return err + } + federatedUserActivity.Actor = actorUser + + return nil +} diff --git a/models/activities/federated_user_activity_test.go b/models/activities/federated_user_activity_test.go new file mode 100644 index 0000000000..9bf4f77984 --- /dev/null +++ b/models/activities/federated_user_activity_test.go @@ -0,0 +1,24 @@ +// Copyright 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package activities + +import ( + "testing" + + "forgejo.org/modules/validation" +) + +func Test_FederatedUserActivityValidation(t *testing.T) { + sut := FederatedUserActivity{} + sut.UserID = 13 + sut.ActorID = 33 + sut.ActorURI = "33" + sut.NoteContent = "Any content!" + sut.NoteURL = "https://example.org/note/17" + sut.OriginalNote = "federatedUserActivityNote-17" + + if res, _ := validation.IsValid(sut); !res { + t.Errorf("sut expected to be valid: %v\n", sut.Validate()) + } +} diff --git a/models/error.go b/models/error.go index e8962f386b..ebaa8a135d 100644 --- a/models/error.go +++ b/models/error.go @@ -414,7 +414,7 @@ func IsErrSHAOrCommitIDNotProvided(err error) bool { } func (err ErrSHAOrCommitIDNotProvided) Error() string { - return "a SHA or commit ID must be proved when updating a file" + return "a SHA or commit ID must be provided when updating a file" } // ErrInvalidMergeStyle represents an error if merging with disabled merge strategy diff --git a/models/fixtures/action.yml b/models/fixtures/action.yml index f1592d4569..a97e94fbf4 100644 --- a/models/fixtures/action.yml +++ b/models/fixtures/action.yml @@ -59,14 +59,6 @@ created_unix: 1603011540 # grouped with id:7 - id: 8 - user_id: 1 - op_type: 12 # close issue - act_user_id: 1 - repo_id: 1700 # dangling intentional - is_private: false - created_unix: 1603011541 - -- id: 9 user_id: 34 op_type: 12 # close issue act_user_id: 34 diff --git a/models/fixtures/comment.yml b/models/fixtures/comment.yml index f4121284a6..34407d6f81 100644 --- a/models/fixtures/comment.yml +++ b/models/fixtures/comment.yml @@ -113,3 +113,344 @@ review_id: 22 assignee_id: 5 created_unix: 946684817 + +- + id: 13 + type: 29 # push + poster_id: 2 + issue_id: 19 # in repo_id 58 + content: '{"is_force_push":false,"commit_ids":["4ca8bcaf27e28504df7bf996819665986b01c847","96cef4a7b72b3c208340ae6f0cf55a93e9077c93","c5626fc9eff57eb1bb7b796b01d4d0f2f3f792a2"]}' + created_unix: 1688672373 + +- + id: 14 + type: 29 # push + poster_id: 2 + issue_id: 19 # in repo_id 58 + content: '{"is_force_push":false,"commit_ids":["23576dd018294e476c06e569b6b0f170d0558705"]}' + created_unix: 1688672374 + +- + id: 15 + type: 29 # push + poster_id: 2 + issue_id: 19 # in repo_id 58 + content: '{"is_force_push":false,"commit_ids":["3e64625bd6eb5bcba69ac97de6c8f507402df861", "c704db5794097441aa2d9dd834d5b7e2f8f08108"]}' + created_unix: 1688672375 + +- + id: 16 + type: 29 # push + poster_id: 2 + issue_id: 19 # in repo_id 58 + content: '{"is_force_push":false,"commit_ids":["811d46c7e518f4f180afb862c0db5cb8c80529ce", "747ddb3506a4fa04a7747808eb56ae16f9e933dc", "837d5c8125633d7d258f93b998e867eab0145520", "1978192d98bb1b65e11c2cf37da854fbf94bffd6"]}' + created_unix: 1688672376 + +- + id: 17 + type: 29 # push + poster_id: 2 + issue_id: 19 # in repo_id 58 + content: '{"is_force_push":true,"commit_ids":["1978192d98bb1b65e11c2cf37da854fbf94bffd6", "9b93963cf6de4dc33f915bb67f192d099c301f43"]}' + created_unix: 1749734240 + +- + id: 2000 + type: 8 # milestone + poster_id: 1 + issue_id: 1 # in repo_id 1 + milestone_id: 1 + old_milestone_id: 0 + created_unix: 946684820 + +- + id: 2001 + type: 8 # milestone + poster_id: 1 + issue_id: 1 # in repo_id 1 + milestone_id: 2 + old_milestone_id: 1 + created_unix: 946684920 + +- + id: 2002 + type: 8 # milestone + poster_id: 1 + issue_id: 1 # in repo_id 1 + milestone_id: 0 + old_milestone_id: 2 + created_unix: 946685020 + +- + id: 2003 + type: 8 # milestone + poster_id: 1 + issue_id: 1 # in repo_id 1 + milestone_id: 10 # not exsting milestone + old_milestone_id: 0 + created_unix: 946685080 + +- + id: 2010 + type: 30 # project + poster_id: 1 + issue_id: 1 # in repo_id 1 + project_id: 1 + old_project_id: 0 + created_unix: 946685120 + +- + id: 2011 + type: 30 # project + poster_id: 1 + issue_id: 1 # in repo_id 1 + project_id: 2 + old_project_id: 1 + created_unix: 946685220 + +- + id: 2012 + type: 30 # project + poster_id: 1 + issue_id: 1 # in repo_id 1 + project_id: 0 + old_project_id: 2 + created_unix: 946685320 + +- + id: 2013 + type: 30 # project + poster_id: 1 + issue_id: 1 # in repo_id 1 + project_id: 10 # not existing project + old_project_id: 0 + created_unix: 946685420 + +- + id: 2020 + type: 7 # label + poster_id: 1 + issue_id: 1 # in repo_id 1 + label_id: 1 + content: 1 # add label + created_unix: 946685520 + +- + id: 2021 + type: 7 # label + poster_id: 1 + issue_id: 1 + label_id: 2 + content: 1 # add label + created_unix: 946685620 + +- + id: 2022 + type: 7 # label + poster_id: 2 + issue_id: 1 # in repo_id 1 + label_id: 1 + content: 0 # remove label + created_unix: 946685720 + +- + id: 2023 + type: 7 # label + poster_id: 1 + issue_id: 1 # in repo_id 1 + label_id: 1 + content: 1 # add label + created_unix: 946685720 + +- + id: 2024 + type: 7 # label + poster_id: 1 + issue_id: 1 # in repo_id 1 + label_id: 2 + content: 0 # remove label + created_unix: 946685720 + +- + id: 2025 + type: 7 # label + poster_id: 2 + issue_id: 1 # in repo_id 1 + label_id: 2 + content: 1 # add label + created_unix: 946685820 + +- + id: 2026 + type: 7 # label + poster_id: 1 + issue_id: 1 # in repo_id 1 + label_id: 1 + content: 0 # remove label + created_unix: 946685920 + +- + id: 2027 + type: 7 # label + poster_id: 1 + issue_id: 1 # in repo_id 1 + label_id: 2 + content: 0 # remove label + created_unix: 946685920 + +- id: 2040 + type: 9 # assignee + poster_id: 1 + issue_id: 1 # in repo_id 1 + assignee_id: 1 # self + removed_assignee: false # add assignee + created_unix: 946688020 + +- id: 2041 + type: 9 # assignee + poster_id: 2 + issue_id: 1 # in repo_id 1 + assignee_id: 1 + removed_assignee: true # remove assignee + created_unix: 946688120 + +- id: 2042 + type: 9 # assignee + poster_id: 1 + issue_id: 1 # in repo_id 1 + assignee_id: 2 + removed_assignee: false # add assignee + created_unix: 946688220 + +- id: 2043 + type: 9 # assignee + poster_id: 2 + issue_id: 1 # in repo_id 1 + assignee_id: 2 # self + removed_assignee: true # remove assignee + created_unix: 946688320 + +- id: 2050 + type: 23 # lock + poster_id: 1 + issue_id: 1 # in repo_id 1 + created_unix: 946688420 + +- id: 2051 + type: 24 # unlock + poster_id: 1 + issue_id: 1 # in repo_id 1 + created_unix: 946688520 + +- id: 2052 + type: 23 # lock + poster_id: 1 + issue_id: 1 # in repo_id 1 + content: "Too heated" + created_unix: 946688620 + +- id: 2053 + type: 24 # unlock + poster_id: 1 + issue_id: 1 # in repo_id 1 + created_unix: 946688720 + +- id: 2060 + type: 36 # pin + poster_id: 1 + issue_id: 1 # in repo_id 1 + created_unix: 946688820 + +- id: 2061 + type: 37 # unpin + poster_id: 1 + issue_id: 1 # in repo_id 1 + created_unix: 946688920 + +- id: 2070 + type: 2 # close + poster_id: 1 + issue_id: 1 # in repo_id 1 + created_unix: 946689020 + +- id: 2071 + type: 1 # reopen + poster_id: 2 + issue_id: 1 # in repo_id 1 + created_unix: 946689220 + +- id: 2072 + type: 2 # close + poster_id: 1 + issue_id: 2 # pull in repo_id 1 + created_unix: 946689320 + +- id: 2073 + type: 1 # reopen + poster_id: 2 + issue_id: 2 # pull in repo_id 1 + created_unix: 946689420 + +- id: 2080 + type: 3 # issue reference + poster_id: 1 + issue_id: 1 # issue in repo_id 1 + ref_repo_id: 1 + ref_issue_id: 5 # issue in repo_id 1 + created_unix: 946689500 + +- id: 2081 + type: 3 # issue reference + poster_id: 1 + issue_id: 1 # issue in repo_id 1 + ref_repo_id: 1 + ref_issue_id: 2 # pull in repo_id 1 + created_unix: 946689600 + +- id: 2082 + type: 3 # issue reference + poster_id: 1 + issue_id: 1 # issue in repo_id 1 + ref_repo_id: 32 + ref_issue_id: 16 # issue in repo_id 32 + created_unix: 946689700 + +- id: 2083 + type: 3 # issue reference + poster_id: 1 + issue_id: 1 # issue in repo_id 1 + ref_repo_id: 10 + ref_issue_id: 8 # pull in repo_id 10 + created_unix: 946689800 + +- id: 2090 + type: 6 # pull reference + poster_id: 1 + issue_id: 2 # pull in repo_id 1 + ref_repo_id: 1 + ref_issue_id: 1 # issue in repo_id 1 + created_unix: 946689900 + +- id: 2091 + type: 6 # pull reference + poster_id: 1 + issue_id: 2 # pull in repo_id 1 + ref_repo_id: 1 + ref_issue_id: 2 # pull in repo_id 1 + created_unix: 946690000 + +- id: 2092 + type: 6 # pull reference + poster_id: 1 + issue_id: 2 # pull in repo_id 1 + ref_repo_id: 32 + ref_issue_id: 16 # issue in repo_id 32 + created_unix: 946690050 + +- id: 2093 + type: 6 # pull reference + poster_id: 1 + issue_id: 2 # pull in repo_id 1 + ref_repo_id: 10 + ref_issue_id: 8 # pull in repo_id 10 + created_unix: 946690100 diff --git a/models/fixtures/pull_auto_merge.yml b/models/fixtures/pull_auto_merge.yml new file mode 100644 index 0000000000..ca780a73aa --- /dev/null +++ b/models/fixtures/pull_auto_merge.yml @@ -0,0 +1 @@ +[] # empty diff --git a/models/forgefed/federationhost.go b/models/forgefed/federationhost.go index 29f1b7d28e..978847bd95 100644 --- a/models/forgefed/federationhost.go +++ b/models/forgefed/federationhost.go @@ -6,6 +6,7 @@ package forgefed import ( "database/sql" "fmt" + "net/url" "strings" "time" @@ -17,9 +18,9 @@ import ( // swagger:model type FederationHost struct { ID int64 `xorm:"pk autoincr"` - HostFqdn string `xorm:"host_fqdn UNIQUE INDEX VARCHAR(255) NOT NULL"` + HostFqdn string `xorm:"host_fqdn UNIQUE(federation_host) INDEX VARCHAR(255) NOT NULL"` + HostPort uint16 `xorm:" UNIQUE(federation_host) INDEX NOT NULL DEFAULT 443"` NodeInfo NodeInfo `xorm:"extends NOT NULL"` - HostPort uint16 `xorm:"NOT NULL DEFAULT 443"` HostSchema string `xorm:"NOT NULL DEFAULT 'https'"` LatestActivity time.Time `xorm:"NOT NULL"` KeyID sql.NullString `xorm:"key_id UNIQUE"` @@ -42,6 +43,13 @@ func NewFederationHost(hostFqdn string, nodeInfo NodeInfo, port uint16, schema s return result, nil } +func (host FederationHost) AsURL() url.URL { + return url.URL{ + Scheme: host.HostSchema, + Host: fmt.Sprintf("%v:%v", host.HostFqdn, host.HostPort), + } +} + // Validate collects error strings in a slice and returns this func (host FederationHost) Validate() []string { var result []string diff --git a/models/forgefed/nodeinfo.go b/models/forgefed/nodeinfo.go index 2461b5e499..38f51304c5 100644 --- a/models/forgefed/nodeinfo.go +++ b/models/forgefed/nodeinfo.go @@ -17,12 +17,14 @@ type ( ) const ( - ForgejoSourceType SoftwareNameType = "forgejo" - GiteaSourceType SoftwareNameType = "gitea" + ForgejoSourceType SoftwareNameType = "forgejo" + GiteaSourceType SoftwareNameType = "gitea" + MastodonSourceType SoftwareNameType = "mastodon" + GoToSocialSourceType SoftwareNameType = "gotosocial" ) var KnownSourceTypes = []any{ - ForgejoSourceType, GiteaSourceType, + ForgejoSourceType, GiteaSourceType, MastodonSourceType, GoToSocialSourceType, } // ------------------------------------------------ NodeInfoWellKnown ------------------------------------------------ diff --git a/models/forgejo_migrations/migrate.go b/models/forgejo_migrations/migrate.go index 21a2077d06..737350b019 100644 --- a/models/forgejo_migrations/migrate.go +++ b/models/forgejo_migrations/migrate.go @@ -103,6 +103,12 @@ var migrations = []*Migration{ NewMigration("Normalize repository.topics to empty slice instead of null", SetTopicsAsEmptySlice), // v31 -> v32 NewMigration("Migrate maven package name concatenation", ChangeMavenArtifactConcatenation), + // v32 -> v33 + NewMigration("Add federated user activity tables, update the `federated_user` table & add indexes", FederatedUserActivityMigration), + // v33 -> v34 + NewMigration("Add `notify-email` column to `action_run` table", AddNotifyEmailToActionRun), + // v34 -> v35 + NewMigration("Add index to `stopped` column in `action_run` table", AddIndexToActionRunStopped), } // GetCurrentDBVersion returns the current Forgejo database version. diff --git a/models/forgejo_migrations/v33.go b/models/forgejo_migrations/v33.go new file mode 100644 index 0000000000..272035fc23 --- /dev/null +++ b/models/forgejo_migrations/v33.go @@ -0,0 +1,126 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package forgejo_migrations //nolint:revive + +import ( + "fmt" + + "forgejo.org/modules/log" + "forgejo.org/modules/timeutil" + + "xorm.io/xorm" +) + +func dropOldFederationHostIndexes(x *xorm.Engine) { + // drop unique index on HostFqdn + type FederationHost struct { + ID int64 `xorm:"pk autoincr"` + HostFqdn string `xorm:"host_fqdn UNIQUE INDEX VARCHAR(255) NOT NULL"` + } + + err := x.DropIndexes(FederationHost{}) + if err != nil { + log.Warn("migration[33]: There was an issue: %v", err) + return + } +} + +func addFederatedUserActivityTables(x *xorm.Engine) { + type FederatedUserActivity struct { + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"NOT NULL INDEX user_id"` + ActorID int64 + ActorURI string + NoteContent string `xorm:"TEXT"` + NoteURL string `xorm:"VARCHAR(255)"` + OriginalNote string `xorm:"TEXT"` + Created timeutil.TimeStamp `xorm:"created"` + } + + // add unique index on HostFqdn+HostPort + type FederationHost struct { + ID int64 `xorm:"pk autoincr"` + HostFqdn string `xorm:"host_fqdn UNIQUE(federation_host) INDEX VARCHAR(255) NOT NULL"` + HostPort uint16 `xorm:"UNIQUE(federation_host) INDEX NOT NULL DEFAULT 443"` + } + + type FederatedUserFollower struct { + ID int64 `xorm:"pk autoincr"` + + FollowedUserID int64 `xorm:"NOT NULL unique(fuf_rel)"` + FollowingUserID int64 `xorm:"NOT NULL unique(fuf_rel)"` + } + + // Add InboxPath to FederatedUser & add index fo UserID + type FederatedUser struct { + ID int64 `xorm:"pk autoincr"` + UserID int64 `xorm:"NOT NULL INDEX user_id"` + InboxPath string + } + + var err error + + err = x.Sync(&FederationHost{}) + if err != nil { + log.Warn("migration[33]: There was an issue: %v", err) + return + } + + err = x.Sync(&FederatedUserActivity{}) + if err != nil { + log.Warn("migration[33]: There was an issue: %v", err) + return + } + + err = x.Sync(&FederatedUserFollower{}) + if err != nil { + log.Warn("migration[33]: There was an issue: %v", err) + return + } + + err = x.Sync(&FederatedUser{}) + if err != nil { + log.Warn("migration[33]: There was an issue: %v", err) + return + } + + // Migrate + sessMigration := x.NewSession() + defer sessMigration.Close() + if err := sessMigration.Begin(); err != nil { + log.Warn("migration[33]: There was an issue: %v", err) + return + } + federatedUsers := make([]*FederatedUser, 0) + err = sessMigration.OrderBy("id").Find(&federatedUsers) + if err != nil { + log.Warn("migration[33]: There was an issue: %v", err) + return + } + + for _, federatedUser := range federatedUsers { + if federatedUser.InboxPath != "" { + log.Info("migration[33]: This user was already migrated: %v", federatedUser) + } else { + // Migrate User.InboxPath + sql := "UPDATE `federated_user` SET `inbox_path` = ? WHERE `id` = ?" + if _, err := sessMigration.Exec(sql, fmt.Sprintf("/api/v1/activitypub/user-id/%v/inbox", federatedUser.UserID), federatedUser.ID); err != nil { + log.Warn("migration[33]: There was an issue: %v", err) + return + } + } + } + + err = sessMigration.Commit() + if err != nil { + log.Warn("migration[33]: There was an issue: %v", err) + return + } +} + +func FederatedUserActivityMigration(x *xorm.Engine) error { + dropOldFederationHostIndexes(x) + addFederatedUserActivityTables(x) + return nil +} diff --git a/models/forgejo_migrations/v33_test.go b/models/forgejo_migrations/v33_test.go new file mode 100644 index 0000000000..664c704bbc --- /dev/null +++ b/models/forgejo_migrations/v33_test.go @@ -0,0 +1,46 @@ +// Copyright 2025 The Forgejo Authors. +// SPDX-License-Identifier: GPL-3.0-or-later + +package forgejo_migrations //nolint:revive + +import ( + "testing" + "time" + + migration_tests "forgejo.org/models/migrations/test" + "forgejo.org/modules/log" + ft "forgejo.org/modules/test" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_FederatedUserActivityMigration(t *testing.T) { + lc, cl := ft.NewLogChecker(log.DEFAULT, log.WARN) + lc.Filter("migration[33]") + defer cl() + + // intentionally conflicting definition + type FederatedUser struct { + ID int64 `xorm:"pk autoincr"` + UserID string + } + + // Prepare TestEnv + x, deferable := migration_tests.PrepareTestEnv(t, 0, + new(FederatedUser), + ) + sessTest := x.NewSession() + sessTest.Insert(FederatedUser{UserID: "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" + + "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" + + "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"}) + sessTest.Commit() + defer deferable() + if x == nil || t.Failed() { + return + } + + require.NoError(t, FederatedUserActivityMigration(x)) + logFiltered, _ := lc.Check(5 * time.Second) + assert.NotEmpty(t, logFiltered) +} diff --git a/models/forgejo_migrations/v34.go b/models/forgejo_migrations/v34.go new file mode 100644 index 0000000000..9e958b934f --- /dev/null +++ b/models/forgejo_migrations/v34.go @@ -0,0 +1,14 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package forgejo_migrations //nolint:revive + +import "xorm.io/xorm" + +func AddNotifyEmailToActionRun(x *xorm.Engine) error { + type ActionRun struct { + ID int64 + NotifyEmail bool + } + return x.Sync(new(ActionRun)) +} diff --git a/models/forgejo_migrations/v35.go b/models/forgejo_migrations/v35.go new file mode 100644 index 0000000000..0fb3b43e2c --- /dev/null +++ b/models/forgejo_migrations/v35.go @@ -0,0 +1,19 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package forgejo_migrations //nolint:revive + +import ( + "forgejo.org/modules/timeutil" + + "xorm.io/xorm" +) + +func AddIndexToActionRunStopped(x *xorm.Engine) error { + type ActionRun struct { + ID int64 + Stopped timeutil.TimeStamp `xorm:"index"` + } + + return x.Sync(&ActionRun{}) +} diff --git a/models/git/commit_status.go b/models/git/commit_status.go index a679703ffd..60a0aa5a4f 100644 --- a/models/git/commit_status.go +++ b/models/git/commit_status.go @@ -179,25 +179,6 @@ func (status *CommitStatus) LocaleString(lang translation.Locale) string { return lang.TrString("repo.commitstatus." + status.State.String()) } -// HideActionsURL set `TargetURL` to an empty string if the status comes from Gitea Actions -func (status *CommitStatus) HideActionsURL(ctx context.Context) { - if status.RepoID == 0 { - return - } - - if status.Repo == nil { - if err := status.loadRepository(ctx); err != nil { - log.Error("loadRepository: %v", err) - return - } - } - - prefix := fmt.Sprintf("%s/actions", status.Repo.Link()) - if strings.HasPrefix(status.TargetURL, prefix) { - status.TargetURL = "" - } -} - // CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc func CalcCommitStatus(statuses []*CommitStatus) *CommitStatus { if len(statuses) == 0 { @@ -453,11 +434,19 @@ type SignCommitWithStatuses struct { *asymkey_model.SignCommit } -// ParseCommitsWithStatus checks commits latest statuses and calculates its worst status state -func ParseCommitsWithStatus(ctx context.Context, oldCommits []*asymkey_model.SignCommit, repo *repo_model.Repository) []*SignCommitWithStatuses { - newCommits := make([]*SignCommitWithStatuses, 0, len(oldCommits)) +// ParseCommitsWithStatus converts git commits into SignCommitWithStatuses (checks signature and calculates its worst status state) +func ParseCommitsWithStatus(ctx context.Context, commits []*git.Commit, repo *repo_model.Repository) []*SignCommitWithStatuses { + commitsWithSignature := asymkey_model.ParseCommitsWithSignature( + ctx, + user_model.ValidateCommitsWithEmails(ctx, commits), + repo.GetTrustModel(), + func(user *user_model.User) (bool, error) { + return repo_model.IsOwnerMemberCollaborator(ctx, repo, user.ID) + }, + ) - for _, c := range oldCommits { + commitsWithStatus := make([]*SignCommitWithStatuses, 0, len(commitsWithSignature)) + for _, c := range commitsWithSignature { commit := &SignCommitWithStatuses{ SignCommit: c, } @@ -469,43 +458,12 @@ func ParseCommitsWithStatus(ctx context.Context, oldCommits []*asymkey_model.Sig commit.Status = CalcCommitStatus(statuses) } - newCommits = append(newCommits, commit) + commitsWithStatus = append(commitsWithStatus, commit) } - return newCommits + return commitsWithStatus } // hashCommitStatusContext hash context func hashCommitStatusContext(context string) string { return fmt.Sprintf("%x", sha1.Sum([]byte(context))) } - -// ConvertFromGitCommit converts git commits into SignCommitWithStatuses -func ConvertFromGitCommit(ctx context.Context, commits []*git.Commit, repo *repo_model.Repository) []*SignCommitWithStatuses { - return ParseCommitsWithStatus(ctx, - asymkey_model.ParseCommitsWithSignature( - ctx, - user_model.ValidateCommitsWithEmails(ctx, commits), - repo.GetTrustModel(), - func(user *user_model.User) (bool, error) { - return repo_model.IsOwnerMemberCollaborator(ctx, repo, user.ID) - }, - ), - repo, - ) -} - -// CommitStatusesHideActionsURL hide Gitea Actions urls -func CommitStatusesHideActionsURL(ctx context.Context, statuses []*CommitStatus) { - idToRepos := make(map[int64]*repo_model.Repository) - for _, status := range statuses { - if status == nil { - continue - } - - if status.Repo == nil { - status.Repo = idToRepos[status.RepoID] - } - status.HideActionsURL(ctx) - idToRepos[status.RepoID] = status.Repo - } -} diff --git a/models/git/commit_status_test.go b/models/git/commit_status_test.go index c062bbbbb9..ce6c0d4673 100644 --- a/models/git/commit_status_test.go +++ b/models/git/commit_status_test.go @@ -4,11 +4,9 @@ package git_test import ( - "fmt" "testing" "time" - actions_model "forgejo.org/models/actions" "forgejo.org/models/db" git_model "forgejo.org/models/git" repo_model "forgejo.org/models/repo" @@ -246,26 +244,3 @@ func TestFindRepoRecentCommitStatusContexts(t *testing.T) { assert.Equal(t, "compliance/lint-backend", contexts[0]) } } - -func TestCommitStatusesHideActionsURL(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - - repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) - run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 791, RepoID: repo.ID}) - require.NoError(t, run.LoadAttributes(db.DefaultContext)) - - statuses := []*git_model.CommitStatus{ - { - RepoID: repo.ID, - TargetURL: fmt.Sprintf("%s/jobs/%d", run.Link(), run.Index), - }, - { - RepoID: repo.ID, - TargetURL: "https://mycicd.org/1", - }, - } - - git_model.CommitStatusesHideActionsURL(db.DefaultContext, statuses) - assert.Empty(t, statuses[0].TargetURL) - assert.Equal(t, "https://mycicd.org/1", statuses[1].TargetURL) -} diff --git a/models/issues/comment.go b/models/issues/comment.go index c44f65e29c..a81221caf4 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -802,7 +802,7 @@ func (c *Comment) LoadPushCommits(ctx context.Context) (err error) { } defer closer.Close() - c.Commits = git_model.ConvertFromGitCommit(ctx, gitRepo.GetCommitsFromIDs(data.CommitIDs), c.Issue.Repo) + c.Commits = git_model.ParseCommitsWithStatus(ctx, gitRepo.GetCommitsFromIDs(data.CommitIDs), c.Issue.Repo) c.CommitsNum = int64(len(c.Commits)) } diff --git a/models/issues/issue_search.go b/models/issues/issue_search.go index bf4b89ee0b..91a69c26a7 100644 --- a/models/issues/issue_search.go +++ b/models/issues/issue_search.go @@ -66,6 +66,8 @@ func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) { sess.Asc("issue.created_unix").Asc("issue.id") case "recentupdate": sess.Desc("issue.updated_unix").Desc("issue.created_unix").Desc("issue.id") + case "recentclose": + sess.Desc("issue.closed_unix").Desc("issue.created_unix").Desc("issue.id") case "leastupdate": sess.Asc("issue.updated_unix").Asc("issue.created_unix").Asc("issue.id") case "mostcomment": diff --git a/models/issues/milestone.go b/models/issues/milestone.go index 52433e735d..67a23246cf 100644 --- a/models/issues/milestone.go +++ b/models/issues/milestone.go @@ -67,6 +67,13 @@ type Milestone struct { TotalTrackedTime int64 `xorm:"-"` } +// Ghost milestone is a milestone which has been deleted +const GhostMilestoneID = -1 + +func (m *Milestone) IsGhost() bool { + return m.ID == GhostMilestoneID +} + func init() { db.RegisterModel(new(Milestone)) } diff --git a/models/issues/pull.go b/models/issues/pull.go index 0781fd0a2d..188ef00814 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -5,6 +5,7 @@ package issues import ( + "bufio" "context" "errors" "fmt" @@ -923,31 +924,30 @@ func MergeBlockedByOutdatedBranch(protectBranch *git_model.ProtectedBranch, pr * return protectBranch.BlockOnOutdatedBranch && pr.CommitsBehind > 0 } -// GetCodeOwnersFromContent returns the code owners configuration -// Return empty slice if files missing +// GetCodeOwnersFromReader returns the code owners configuration // Return warning messages on parsing errors // We're trying to do the best we can when parsing a file. // Invalid lines are skipped. Non-existent users and teams too. -func GetCodeOwnersFromContent(ctx context.Context, data string) ([]*CodeOwnerRule, []string) { - if len(data) == 0 { - return nil, nil - } +func GetCodeOwnersFromReader(ctx context.Context, rc io.ReadCloser, truncated bool) ([]*CodeOwnerRule, []string) { + defer rc.Close() + scanner := bufio.NewScanner(rc) - rules := make([]*CodeOwnerRule, 0) - lines := strings.Split(data, "\n") - warnings := make([]string, 0) + var rules []*CodeOwnerRule + var warnings []string + line := 0 + for scanner.Scan() { + line++ - for i, line := range lines { - tokens := TokenizeCodeOwnersLine(line) + tokens := TokenizeCodeOwnersLine(scanner.Text()) if len(tokens) == 0 { continue } else if len(tokens) < 2 { - warnings = append(warnings, fmt.Sprintf("Line: %d: incorrect format", i+1)) + warnings = append(warnings, fmt.Sprintf("Line: %d: incorrect format", line)) continue } rule, wr := ParseCodeOwnersLine(ctx, tokens) for _, w := range wr { - warnings = append(warnings, fmt.Sprintf("Line: %d: %s", i+1, w)) + warnings = append(warnings, fmt.Sprintf("Line: %d: %s", line, w)) } if rule == nil { continue @@ -955,6 +955,12 @@ func GetCodeOwnersFromContent(ctx context.Context, data string) ([]*CodeOwnerRul rules = append(rules, rule) } + if err := scanner.Err(); err != nil { + warnings = append(warnings, err.Error()) + } + if truncated { + warnings = append(warnings, fmt.Sprintf("File too big: truncated while on line %d", line)) + } return rules, warnings } diff --git a/models/issues/pull_list.go b/models/issues/pull_list.go index a448673454..8fc0491026 100644 --- a/models/issues/pull_list.go +++ b/models/issues/pull_list.go @@ -152,7 +152,8 @@ func PullRequests(ctx context.Context, baseRepoID int64, opts *PullRequestsOptio applySorts(findSession, opts.SortType, 0) findSession = db.SetSessionPagination(findSession, opts) prs := make([]*PullRequest, 0, opts.PageSize) - return prs, maxResults, findSession.Find(&prs) + found := findSession.Find(&prs) + return prs, maxResults, found } // PullRequestList defines a list of pull requests diff --git a/models/issues/pull_test.go b/models/issues/pull_test.go index 5a8e8d8aaf..3682f6fd25 100644 --- a/models/issues/pull_test.go +++ b/models/issues/pull_test.go @@ -79,6 +79,47 @@ func TestPullRequestsNewest(t *testing.T) { } } +func TestPullRequests_Closed_RecentSortType(t *testing.T) { + // Issue ID | Closed At. | Updated At + // 2 | 1707270001 | 1707270001 + // 3 | 1707271000 | 1707279999 + // 11 | 1707279999 | 1707275555 + tests := []struct { + sortType string + expectedIssueIDOrder []int64 + }{ + {"recentupdate", []int64{3, 11, 2}}, + {"recentclose", []int64{11, 3, 2}}, + } + + require.NoError(t, unittest.PrepareTestDatabase()) + _, err := db.Exec(db.DefaultContext, "UPDATE issue SET closed_unix = 1707270001, updated_unix = 1707270001, is_closed = true WHERE id = 2") + require.NoError(t, err) + _, err = db.Exec(db.DefaultContext, "UPDATE issue SET closed_unix = 1707271000, updated_unix = 1707279999, is_closed = true WHERE id = 3") + require.NoError(t, err) + _, err = db.Exec(db.DefaultContext, "UPDATE issue SET closed_unix = 1707279999, updated_unix = 1707275555, is_closed = true WHERE id = 11") + require.NoError(t, err) + + for _, test := range tests { + t.Run(test.sortType, func(t *testing.T) { + prs, _, err := issues_model.PullRequests(db.DefaultContext, 1, &issues_model.PullRequestsOptions{ + ListOptions: db.ListOptions{ + Page: 1, + }, + State: "closed", + SortType: test.sortType, + }) + require.NoError(t, err) + + if assert.Len(t, prs, len(test.expectedIssueIDOrder)) { + for i := range test.expectedIssueIDOrder { + assert.Equal(t, test.expectedIssueIDOrder[i], prs[i].IssueID) + } + } + }) + } +} + func TestLoadRequestedReviewers(t *testing.T) { require.NoError(t, unittest.PrepareTestDatabase()) diff --git a/models/pull/automerge.go b/models/pull/automerge.go index 63f572309b..dcc1f39271 100644 --- a/models/pull/automerge.go +++ b/models/pull/automerge.go @@ -10,6 +10,7 @@ import ( "forgejo.org/models/db" repo_model "forgejo.org/models/repo" user_model "forgejo.org/models/user" + "forgejo.org/modules/log" "forgejo.org/modules/timeutil" ) @@ -58,13 +59,15 @@ func ScheduleAutoMerge(ctx context.Context, doer *user_model.User, pullID int64, return ErrAlreadyScheduledToAutoMerge{PullID: pullID} } - _, err := db.GetEngine(ctx).Insert(&AutoMerge{ + scheduledPRM, err := db.GetEngine(ctx).Insert(&AutoMerge{ DoerID: doer.ID, PullID: pullID, MergeStyle: style, Message: message, DeleteBranchAfterMerge: deleteBranch, }) + log.Trace("ScheduleAutoMerge %+v for PR %d", scheduledPRM, pullID) + return err } @@ -81,6 +84,8 @@ func GetScheduledMergeByPullID(ctx context.Context, pullID int64) (bool, *AutoMe return false, nil, err } + log.Trace("GetScheduledMergeByPullID found %+v for PR %d", scheduledPRM, pullID) + scheduledPRM.Doer = doer return true, scheduledPRM, nil } @@ -94,6 +99,8 @@ func DeleteScheduledAutoMerge(ctx context.Context, pullID int64) error { return db.ErrNotExist{Resource: "auto_merge", ID: pullID} } + log.Trace("DeleteScheduledAutoMerge %+v for PR %d", scheduledPRM, pullID) + _, err = db.GetEngine(ctx).ID(scheduledPRM.ID).Delete(&AutoMerge{}) return err } diff --git a/models/user/federated_user.go b/models/user/federated_user.go index c1833c7de3..d2a9c34c9e 100644 --- a/models/user/federated_user.go +++ b/models/user/federated_user.go @@ -11,19 +11,21 @@ import ( type FederatedUser struct { ID int64 `xorm:"pk autoincr"` - UserID int64 `xorm:"NOT NULL"` + UserID int64 `xorm:"NOT NULL INDEX user_id"` ExternalID string `xorm:"UNIQUE(federation_user_mapping) NOT NULL"` FederationHostID int64 `xorm:"UNIQUE(federation_user_mapping) NOT NULL"` KeyID sql.NullString `xorm:"key_id UNIQUE"` PublicKey sql.Null[sql.RawBytes] `xorm:"BLOB"` - NormalizedOriginalURL string // This field is just to keep original information. Pls. do not use for search or as ID! + InboxPath string + NormalizedOriginalURL string // This field is just to keep original information. Pls. do not use for search or as ID! } -func NewFederatedUser(userID int64, externalID string, federationHostID int64, normalizedOriginalURL string) (FederatedUser, error) { +func NewFederatedUser(userID int64, externalID string, federationHostID int64, inboxPath, normalizedOriginalURL string) (FederatedUser, error) { result := FederatedUser{ UserID: userID, ExternalID: externalID, FederationHostID: federationHostID, + InboxPath: inboxPath, NormalizedOriginalURL: normalizedOriginalURL, } if valid, err := validation.IsValid(result); !valid { @@ -32,10 +34,11 @@ func NewFederatedUser(userID int64, externalID string, federationHostID int64, n return result, nil } -func (user FederatedUser) Validate() []string { +func (federatedUser FederatedUser) Validate() []string { var result []string - result = append(result, validation.ValidateNotEmpty(user.UserID, "UserID")...) - result = append(result, validation.ValidateNotEmpty(user.ExternalID, "ExternalID")...) - result = append(result, validation.ValidateNotEmpty(user.FederationHostID, "FederationHostID")...) + result = append(result, validation.ValidateNotEmpty(federatedUser.UserID, "UserID")...) + result = append(result, validation.ValidateNotEmpty(federatedUser.ExternalID, "ExternalID")...) + result = append(result, validation.ValidateNotEmpty(federatedUser.FederationHostID, "FederationHostID")...) + result = append(result, validation.ValidateNotEmpty(federatedUser.InboxPath, "InboxPath")...) return result } diff --git a/models/user/federated_user_follower.go b/models/user/federated_user_follower.go new file mode 100644 index 0000000000..db72c9b5ce --- /dev/null +++ b/models/user/federated_user_follower.go @@ -0,0 +1,30 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package user + +import "forgejo.org/modules/validation" + +type FederatedUserFollower struct { + ID int64 `xorm:"pk autoincr"` + FollowedUserID int64 `xorm:"NOT NULL unique(fuf_rel)"` + FollowingUserID int64 `xorm:"NOT NULL unique(fuf_rel)"` +} + +func NewFederatedUserFollower(followedUserID, federatedUserID int64) (FederatedUserFollower, error) { + result := FederatedUserFollower{ + FollowedUserID: followedUserID, + FollowingUserID: federatedUserID, + } + if valid, err := validation.IsValid(result); !valid { + return FederatedUserFollower{}, err + } + return result, nil +} + +func (user FederatedUserFollower) Validate() []string { + var result []string + result = append(result, validation.ValidateNotEmpty(user.FollowedUserID, "FollowedUserID")...) + result = append(result, validation.ValidateNotEmpty(user.FollowingUserID, "FollowingUserID")...) + return result +} diff --git a/models/user/federated_user_follower_test.go b/models/user/federated_user_follower_test.go new file mode 100644 index 0000000000..e57ba01308 --- /dev/null +++ b/models/user/federated_user_follower_test.go @@ -0,0 +1,27 @@ +// Copyright 2024 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package user + +import ( + "testing" + + "forgejo.org/modules/validation" + + "github.com/stretchr/testify/assert" +) + +func Test_FederatedUserFollowerValidation(t *testing.T) { + sut := FederatedUserFollower{ + FollowedUserID: 12, + FollowingUserID: 1, + } + res, err := validation.IsValid(sut) + assert.Truef(t, res, "sut should be valid but was %q", err) + + sut = FederatedUserFollower{ + FollowedUserID: 1, + } + res, _ = validation.IsValid(sut) + assert.False(t, res, "sut should be invalid") +} diff --git a/models/user/federated_user_test.go b/models/user/federated_user_test.go index 542798c9bc..be18339670 100644 --- a/models/user/federated_user_test.go +++ b/models/user/federated_user_test.go @@ -14,6 +14,7 @@ func Test_FederatedUserValidation(t *testing.T) { UserID: 12, ExternalID: "12", FederationHostID: 1, + InboxPath: "/api/v1/activitypub/user-id/12/inbox", } if res, err := validation.IsValid(sut); !res { t.Errorf("sut should be valid but was %q", err) @@ -22,6 +23,7 @@ func Test_FederatedUserValidation(t *testing.T) { sut = FederatedUser{ ExternalID: "12", FederationHostID: 1, + InboxPath: "/api/v1/activitypub/user-id/12/inbox", } if res, _ := validation.IsValid(sut); res { t.Error("sut should be invalid") diff --git a/models/user/follow.go b/models/user/follow.go index 5be0f73c35..e32c226385 100644 --- a/models/user/follow.go +++ b/models/user/follow.go @@ -11,6 +11,7 @@ import ( ) // Follow represents relations of user and their followers. +// TODO: We should unify Activity-pub-following and classical following (see models/user/user_repository.go#IsFollowingAp) type Follow struct { ID int64 `xorm:"pk autoincr"` UserID int64 `xorm:"UNIQUE(follow)"` diff --git a/models/user/user_repository.go b/models/user/user_repository.go index 299d3af64a..3f24efb1fb 100644 --- a/models/user/user_repository.go +++ b/models/user/user_repository.go @@ -1,4 +1,4 @@ -// Copyright 2024 The Forgejo Authors. All rights reserved. +// Copyright 2024, 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package user @@ -8,12 +8,14 @@ import ( "fmt" "forgejo.org/models/db" + "forgejo.org/modules/log" "forgejo.org/modules/optional" "forgejo.org/modules/validation" ) func init() { db.RegisterModel(new(FederatedUser)) + db.RegisterModel(new(FederatedUserFollower)) } func CreateFederatedUser(ctx context.Context, user *User, federatedUser *FederatedUser) error { @@ -30,7 +32,12 @@ func CreateFederatedUser(ctx context.Context, user *User, federatedUser *Federat if err != nil { return err } - defer committer.Close() + defer func() { + err := committer.Close() + if err != nil { + log.Error("Error closing committer: %v", err) + } + }() if err := CreateUser(ctx, user, &overwrite); err != nil { return err @@ -50,6 +57,14 @@ func CreateFederatedUser(ctx context.Context, user *User, federatedUser *Federat return committer.Commit() } +func (federatedUser *FederatedUser) UpdateFederatedUser(ctx context.Context) error { + if _, err := validation.IsValid(federatedUser); err != nil { + return err + } + _, err := db.GetEngine(ctx).ID(federatedUser.ID).Cols("inbox_path").Update(federatedUser) + return err +} + func FindFederatedUser(ctx context.Context, externalID string, federationHostID int64) (*User, *FederatedUser, error) { federatedUser := new(FederatedUser) user := new(User) @@ -75,6 +90,41 @@ func FindFederatedUser(ctx context.Context, externalID string, federationHostID return user, federatedUser, nil } +func GetFederatedUser(ctx context.Context, externalID string, federationHostID int64) (*User, *FederatedUser, error) { + user, federatedUser, err := FindFederatedUser(ctx, externalID, federationHostID) + if err != nil { + return nil, nil, err + } else if federatedUser == nil { + return nil, nil, fmt.Errorf("FederatedUser for externalId = %v and federationHostId = %v does not exist", externalID, federationHostID) + } + return user, federatedUser, nil +} + +func GetFederatedUserByUserID(ctx context.Context, userID int64) (*User, *FederatedUser, error) { + federatedUser := new(FederatedUser) + user := new(User) + has, err := db.GetEngine(ctx).Where("user_id=?", userID).Get(federatedUser) + if err != nil { + return nil, nil, err + } else if !has { + return nil, nil, fmt.Errorf("Federated user %v does not exist", federatedUser.UserID) + } + has, err = db.GetEngine(ctx).ID(federatedUser.UserID).Get(user) + if err != nil { + return nil, nil, err + } else if !has { + return nil, nil, fmt.Errorf("User %v for federated user is missing", federatedUser.UserID) + } + + if res, err := validation.IsValid(*user); !res { + return nil, nil, err + } + if res, err := validation.IsValid(*federatedUser); !res { + return nil, nil, err + } + return user, federatedUser, nil +} + func FindFederatedUserByKeyID(ctx context.Context, keyID string) (*User, *FederatedUser, error) { federatedUser := new(FederatedUser) user := new(User) @@ -101,7 +151,85 @@ func FindFederatedUserByKeyID(ctx context.Context, keyID string) (*User, *Federa return user, federatedUser, nil } +func UpdateFederatedUser(ctx context.Context, federatedUser *FederatedUser) error { + if res, err := validation.IsValid(federatedUser); !res { + return err + } + _, err := db.GetEngine(ctx).ID(federatedUser.ID).Update(federatedUser) + return err +} + func DeleteFederatedUser(ctx context.Context, userID int64) error { _, err := db.GetEngine(ctx).Delete(&FederatedUser{UserID: userID}) return err } + +func GetFollowersForUser(ctx context.Context, user *User) ([]*FederatedUserFollower, error) { + if res, err := validation.IsValid(user); !res { + return nil, err + } + followers := make([]*FederatedUserFollower, 0, 8) + + err := db.GetEngine(ctx). + Where("followed_user_id = ?", user.ID). + Find(&followers) + if err != nil { + return nil, err + } + for _, element := range followers { + if res, err := validation.IsValid(*element); !res { + return nil, err + } + } + return followers, nil +} + +func AddFollower(ctx context.Context, followedUser *User, followingUser *FederatedUser) (*FederatedUserFollower, error) { + if res, err := validation.IsValid(followedUser); !res { + return nil, err + } + if res, err := validation.IsValid(followingUser); !res { + return nil, err + } + + federatedUserFollower, err := NewFederatedUserFollower(followedUser.ID, followingUser.UserID) + if err != nil { + return nil, err + } + _, err = db.GetEngine(ctx).Insert(&federatedUserFollower) + if err != nil { + return nil, err + } + + return &federatedUserFollower, err +} + +func RemoveFollower(ctx context.Context, followedUser *User, followingUser *FederatedUser) error { + if res, err := validation.IsValid(followedUser); !res { + return err + } + if res, err := validation.IsValid(followingUser); !res { + return err + } + + _, err := db.GetEngine(ctx).Delete(&FederatedUserFollower{ + FollowedUserID: followedUser.ID, + FollowingUserID: followingUser.UserID, + }) + return err +} + +// TODO: We should unify Activity-pub-following and classical following (see models/user/follow.go) +func IsFollowingAp(ctx context.Context, followedUser *User, followingUser *FederatedUser) (bool, error) { + if res, err := validation.IsValid(followedUser); !res { + return false, err + } + if res, err := validation.IsValid(followingUser); !res { + return false, err + } + + return db.GetEngine(ctx).Get(&FederatedUserFollower{ + FollowedUserID: followedUser.ID, + FollowingUserID: followingUser.UserID, + }) +} diff --git a/models/user/user_system.go b/models/user/user_system.go index 82805cc8ee..11f54591b7 100644 --- a/models/user/user_system.go +++ b/models/user/user_system.go @@ -12,6 +12,13 @@ import ( "forgejo.org/modules/structs" ) +// IsSystem returns true if the user has a fixed +// negative ID, is never stored in the database and +// is generated on the fly when needed. +func (u *User) IsSystem() bool { + return u.IsGhost() || u.IsActions() +} + const ( GhostUserID = -1 GhostUserName = "Ghost" diff --git a/models/user/user_test.go b/models/user/user_test.go index 2a9e652a35..fd9d05653f 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -148,7 +148,7 @@ func TestAPActorID_APActorID(t *testing.T) { assert.Equal(t, expected, url) } -func TestAPActorKeyID(t *testing.T) { +func TestKeyID(t *testing.T) { user := user_model.User{ID: 1} url := user.APActorKeyID() expected := "https://try.gitea.io/api/v1/activitypub/user-id/1#main-key" diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index daf453fb47..7ae4557ed6 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -323,6 +323,10 @@ func matchPushEvent(commit *git.Commit, pushPayload *api.PushPayload, evt *jobpa matchTimes++ } case "paths": + if refName.IsTag() { + matchTimes++ + break + } filesChanged, err := commit.GetFilesChangedSinceCommit(pushPayload.Before) if err != nil { log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err) @@ -336,6 +340,10 @@ func matchPushEvent(commit *git.Commit, pushPayload *api.PushPayload, evt *jobpa } } case "paths-ignore": + if refName.IsTag() { + matchTimes++ + break + } filesChanged, err := commit.GetFilesChangedSinceCommit(pushPayload.Before) if err != nil { log.Error("GetFilesChangedSinceCommit [commit_sha1: %s]: %v", commit.ID.String(), err) diff --git a/modules/actions/workflows_test.go b/modules/actions/workflows_test.go index b85ed7fd56..9068ce31c3 100644 --- a/modules/actions/workflows_test.go +++ b/modules/actions/workflows_test.go @@ -150,6 +150,24 @@ func TestDetectMatched(t *testing.T) { yamlOn: "on: workflow_dispatch", expected: true, }, + { + desc: "push to tag matches workflow with paths condition (should skip paths check)", + triggeredEvent: webhook_module.HookEventPush, + payload: &api.PushPayload{ + Ref: "refs/tags/v1.0.0", + Before: "0000000", + Commits: []*api.PayloadCommit{ + { + ID: "abcdef123456", + Added: []string{"src/main.go"}, + Message: "Release v1.0.0", + }, + }, + }, + commit: nil, + yamlOn: "on:\n push:\n paths:\n - src/**", + expected: true, + }, } for _, tc := range testCases { diff --git a/modules/assetfs/layered.go b/modules/assetfs/layered.go index 8d54ae5e4a..48c6728f43 100644 --- a/modules/assetfs/layered.go +++ b/modules/assetfs/layered.go @@ -5,10 +5,10 @@ package assetfs import ( "context" + "errors" "fmt" "io" "io/fs" - "net/http" "os" "path/filepath" "slices" @@ -25,7 +25,7 @@ import ( // Layer represents a layer in a layered asset file-system. It has a name and works like http.FileSystem type Layer struct { name string - fs http.FileSystem + fs fs.FS localPath string } @@ -34,10 +34,18 @@ func (l *Layer) Name() string { } // Open opens the named file. The caller is responsible for closing the file. -func (l *Layer) Open(name string) (http.File, error) { +func (l *Layer) Open(name string) (fs.File, error) { return l.fs.Open(name) } +func (l *Layer) ReadDir(name string) ([]fs.DirEntry, error) { + dirEntries, err := fs.ReadDir(l.fs, name) + if err != nil && errors.Is(err, fs.ErrNotExist) { + err = nil + } + return dirEntries, err +} + // Local returns a new Layer with the given name, it serves files from the given local path. func Local(name, base string, sub ...string) *Layer { // TODO: the old behavior (StaticRootPath might not be absolute), not ideal, just keep the same as before @@ -48,11 +56,18 @@ func Local(name, base string, sub ...string) *Layer { panic(fmt.Sprintf("Unable to get absolute path for %q: %v", base, err)) } root := util.FilePathJoinAbs(base, sub...) - return &Layer{name: name, fs: http.Dir(root), localPath: root} + fsRoot, err := os.OpenRoot(root) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return nil + } + panic(fmt.Sprintf("Unable to open layer %q", err)) + } + return &Layer{name: name, fs: fsRoot.FS(), localPath: root} } // Bindata returns a new Layer with the given name, it serves files from the given bindata asset. -func Bindata(name string, fs http.FileSystem) *Layer { +func Bindata(name string, fs fs.FS) *Layer { return &Layer{name: name, fs: fs} } @@ -65,11 +80,11 @@ type LayeredFS struct { // Layered returns a new LayeredFS with the given layers. The first layer is the top layer. func Layered(layers ...*Layer) *LayeredFS { - return &LayeredFS{layers: layers} + return &LayeredFS{layers: slices.DeleteFunc(layers, func(layer *Layer) bool { return layer == nil })} } // Open opens the named file. The caller is responsible for closing the file. -func (l *LayeredFS) Open(name string) (http.File, error) { +func (l *LayeredFS) Open(name string) (fs.File, error) { for _, layer := range l.layers { f, err := layer.Open(name) if err == nil || !os.IsNotExist(err) { @@ -102,29 +117,18 @@ func (l *LayeredFS) ReadLayeredFile(elems ...string) ([]byte, string, error) { return nil, "", fs.ErrNotExist } -func shouldInclude(info fs.FileInfo, fileMode ...bool) bool { +func shouldInclude(info fs.DirEntry, fileMode ...bool) bool { if util.CommonSkip(info.Name()) { return false } if len(fileMode) == 0 { return true } else if len(fileMode) == 1 { - return fileMode[0] == !info.Mode().IsDir() + return fileMode[0] == !info.IsDir() } panic("too many arguments for fileMode in shouldInclude") } -func readDir(layer *Layer, name string) ([]fs.FileInfo, error) { - f, err := layer.Open(name) - if os.IsNotExist(err) { - return nil, nil - } else if err != nil { - return nil, err - } - defer f.Close() - return f.Readdir(-1) -} - // ListFiles lists files/directories in the given directory. The fileMode controls the returned files. // * omitted: all files and directories will be returned. // * true: only files will be returned. @@ -133,7 +137,7 @@ func readDir(layer *Layer, name string) ([]fs.FileInfo, error) { func (l *LayeredFS) ListFiles(name string, fileMode ...bool) ([]string, error) { fileSet := make(container.Set[string]) for _, layer := range l.layers { - infos, err := readDir(layer, name) + infos, err := layer.ReadDir(name) if err != nil { return nil, err } @@ -162,7 +166,7 @@ func listAllFiles(layers []*Layer, name string, fileMode ...bool) ([]string, err var list func(dir string) error list = func(dir string) error { for _, layer := range layers { - infos, err := readDir(layer, dir) + infos, err := layer.ReadDir(dir) if err != nil { return err } diff --git a/modules/git/blob.go b/modules/git/blob.go index 3fda358938..8c5c275146 100644 --- a/modules/git/blob.go +++ b/modules/git/blob.go @@ -12,7 +12,6 @@ import ( "forgejo.org/modules/log" "forgejo.org/modules/typesniffer" - "forgejo.org/modules/util" ) // Blob represents a Git object. @@ -25,42 +24,25 @@ type Blob struct { repo *Repository } -// DataAsync gets a ReadCloser for the contents of a blob without reading it all. -// Calling the Close function on the result will discard all unread output. -func (b *Blob) DataAsync() (io.ReadCloser, error) { +func (b *Blob) newReader() (*bufio.Reader, int64, func(), error) { wr, rd, cancel, err := b.repo.CatFileBatch(b.repo.Ctx) if err != nil { - return nil, err + return nil, 0, nil, err } _, err = wr.Write([]byte(b.ID.String() + "\n")) if err != nil { cancel() - return nil, err + return nil, 0, nil, err } _, _, size, err := ReadBatchLine(rd) if err != nil { cancel() - return nil, err + return nil, 0, nil, err } b.gotSize = true b.size = size - - if size < 4096 { - bs, err := io.ReadAll(io.LimitReader(rd, size)) - defer cancel() - if err != nil { - return nil, err - } - _, err = rd.Discard(1) - return io.NopCloser(bytes.NewReader(bs)), err - } - - return &blobReader{ - rd: rd, - n: size, - cancel: cancel, - }, nil + return rd, size, cancel, err } // Size returns the uncompressed size of the blob @@ -91,10 +73,36 @@ func (b *Blob) Size() int64 { return b.size } +// DataAsync gets a ReadCloser for the contents of a blob without reading it all. +// Calling the Close function on the result will discard all unread output. +func (b *Blob) DataAsync() (io.ReadCloser, error) { + rd, size, cancel, err := b.newReader() + if err != nil { + return nil, err + } + + if size < 4096 { + bs, err := io.ReadAll(io.LimitReader(rd, size)) + defer cancel() + if err != nil { + return nil, err + } + _, err = rd.Discard(1) + return io.NopCloser(bytes.NewReader(bs)), err + } + + return &blobReader{ + rd: rd, + n: size, + cancel: cancel, + }, nil +} + type blobReader struct { - rd *bufio.Reader - n int64 - cancel func() + rd *bufio.Reader + n int64 // number of bytes to read + additionalDiscard int64 // additional number of bytes to discard + cancel func() } func (b *blobReader) Read(p []byte) (n int, err error) { @@ -117,7 +125,8 @@ func (b *blobReader) Close() error { defer b.cancel() - if err := DiscardFull(b.rd, b.n+1); err != nil { + // discard the unread bytes, the truncated bytes and the trailing newline + if err := DiscardFull(b.rd, b.n+b.additionalDiscard+1); err != nil { return err } @@ -131,17 +140,35 @@ func (b *Blob) Name() string { return b.name } -// GetBlobContent Gets the limited content of the blob as raw text +// NewTruncatedReader return a blob-reader which silently truncates when the limit is reached (io.EOF will be returned) +func (b *Blob) NewTruncatedReader(limit int64) (rc io.ReadCloser, fullSize int64, err error) { + r, fullSize, cancel, err := b.newReader() + if err != nil { + return nil, fullSize, err + } + + limit = min(limit, fullSize) + return &blobReader{ + rd: r, + n: limit, + additionalDiscard: fullSize - limit, + cancel: cancel, + }, fullSize, nil +} + +// GetBlobContent Gets the truncated content of the blob as raw text func (b *Blob) GetBlobContent(limit int64) (string, error) { if limit <= 0 { return "", nil } - dataRc, err := b.DataAsync() + rc, fullSize, err := b.NewTruncatedReader(limit) if err != nil { return "", err } - defer dataRc.Close() - buf, err := util.ReadWithLimit(dataRc, int(limit)) + defer rc.Close() + + buf := make([]byte, min(fullSize, limit)) + _, err = io.ReadFull(rc, buf) return string(buf), err } diff --git a/modules/git/blob_test.go b/modules/git/blob_test.go index 810964b33d..54115013d3 100644 --- a/modules/git/blob_test.go +++ b/modules/git/blob_test.go @@ -35,6 +35,106 @@ func TestBlob_Data(t *testing.T) { assert.Equal(t, output, string(data)) } +func TestBlob(t *testing.T) { + bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") + repo, err := openRepositoryWithDefaultContext(bareRepo1Path) + require.NoError(t, err) + + defer repo.Close() + + testBlob, err := repo.GetBlob("6c493ff740f9380390d5c9ddef4af18697ac9375") + require.NoError(t, err) + + t.Run("GetBlobContent", func(t *testing.T) { + r, err := testBlob.GetBlobContent(100) + require.NoError(t, err) + require.Equal(t, "file2\n", r) + + r, err = testBlob.GetBlobContent(-1) + require.NoError(t, err) + require.Empty(t, r) + + r, err = testBlob.GetBlobContent(4) + require.NoError(t, err) + require.Equal(t, "file", r) + + r, err = testBlob.GetBlobContent(6) + require.NoError(t, err) + require.Equal(t, "file2\n", r) + }) + + t.Run("NewTruncatedReader", func(t *testing.T) { + // read fewer than available + rc, size, err := testBlob.NewTruncatedReader(100) + require.NoError(t, err) + require.Equal(t, int64(6), size) + + buf := make([]byte, 1) + n, err := rc.Read(buf) + require.NoError(t, err) + require.Equal(t, 1, n) + require.Equal(t, "f", string(buf)) + n, err = rc.Read(buf) + require.NoError(t, err) + require.Equal(t, 1, n) + require.Equal(t, "i", string(buf)) + + require.NoError(t, rc.Close()) + + // read more than available + rc, size, err = testBlob.NewTruncatedReader(100) + require.NoError(t, err) + require.Equal(t, int64(6), size) + + buf = make([]byte, 100) + n, err = rc.Read(buf) + require.NoError(t, err) + require.Equal(t, 6, n) + require.Equal(t, "file2\n", string(buf[:n])) + + n, err = rc.Read(buf) + require.Error(t, err) + require.Equal(t, io.EOF, err) + require.Equal(t, 0, n) + + require.NoError(t, rc.Close()) + + // read more than truncated + rc, size, err = testBlob.NewTruncatedReader(4) + require.NoError(t, err) + require.Equal(t, int64(6), size) + + buf = make([]byte, 10) + n, err = rc.Read(buf) + require.NoError(t, err) + require.Equal(t, 4, n) + require.Equal(t, "file", string(buf[:n])) + + n, err = rc.Read(buf) + require.Error(t, err) + require.Equal(t, io.EOF, err) + require.Equal(t, 0, n) + + require.NoError(t, rc.Close()) + }) + + t.Run("NonExisting", func(t *testing.T) { + nonExistingBlob, err := repo.GetBlob("00003ff740f9380390d5c9ddef4af18690000000") + require.NoError(t, err) + + r, err := nonExistingBlob.GetBlobContent(100) + require.Error(t, err) + require.IsType(t, ErrNotExist{}, err) + require.Empty(t, r) + + rc, size, err := nonExistingBlob.NewTruncatedReader(100) + require.Error(t, err) + require.IsType(t, ErrNotExist{}, err) + require.Empty(t, rc) + require.Empty(t, size) + }) +} + func Benchmark_Blob_Data(b *testing.B) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") repo, err := openRepositoryWithDefaultContext(bareRepo1Path) diff --git a/modules/git/git.go b/modules/git/git.go index 743ff59ddd..1dfd0b5134 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -38,6 +38,7 @@ var ( InvertedGitFlushEnv bool // 2.43.1 SupportCheckAttrOnBare bool // >= 2.40 SupportGitMergeTree bool // >= 2.38 + SupportGrepMaxCount bool // >= 2.38 HasSSHExecutable bool @@ -191,6 +192,7 @@ func InitFull(ctx context.Context) (err error) { InvertedGitFlushEnv = CheckGitVersionEqual("2.43.1") == nil SupportGitMergeTree = CheckGitVersionAtLeast("2.38") == nil + SupportGrepMaxCount = CheckGitVersionAtLeast("2.38") == nil if setting.LFS.StartServer { if CheckGitVersionAtLeast("2.1.2") != nil { diff --git a/modules/git/git_test.go b/modules/git/git_test.go index 1f9060247f..01200dba68 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -105,6 +105,10 @@ func TestSyncConfigGPGFormat(t *testing.T) { }) t.Run("SSH format", func(t *testing.T) { + if CheckGitVersionAtLeast("2.34.0") != nil { + t.SkipNow() + } + r, err := os.OpenRoot(t.TempDir()) require.NoError(t, err) f, err := r.OpenFile("ssh-keygen", os.O_CREATE|os.O_TRUNC, 0o700) diff --git a/modules/git/grep.go b/modules/git/grep.go index 117b09fc83..3703b13660 100644 --- a/modules/git/grep.go +++ b/modules/git/grep.go @@ -98,8 +98,7 @@ func GrepSearch(ctx context.Context, repo *Repository, search string, opts GrepO cmd.AddOptionValues("--context", fmt.Sprint(opts.ContextLineNumber)) - // --max-count requires at least git 2.38 - if CheckGitVersionAtLeast("2.38.0") == nil { + if SupportGrepMaxCount { cmd.AddOptionValues("--max-count", fmt.Sprint(opts.MatchesPerFile)) } else { log.Warn("git-grep: --max-count requires at least git 2.38") diff --git a/modules/git/grep_test.go b/modules/git/grep_test.go index 534468e268..83ddb766af 100644 --- a/modules/git/grep_test.go +++ b/modules/git/grep_test.go @@ -59,48 +59,55 @@ func TestGrepSearch(t *testing.T) { }, }, res) - res, err = GrepSearch(t.Context(), repo, "world", GrepOptions{MatchesPerFile: 1}) - require.NoError(t, err) - assert.Equal(t, []*GrepResult{ - { - Filename: "i-am-a-python.p", - LineNumbers: []int{1}, - LineCodes: []string{"## This is a simple file to do a hello world"}, - HighlightedRanges: [][3]int{{0, 39, 44}}, - }, - { - Filename: "java-hello/main.java", - LineNumbers: []int{1}, - LineCodes: []string{"public class HelloWorld"}, - HighlightedRanges: [][3]int{{0, 18, 23}}, - }, - { - Filename: "main.vendor.java", - LineNumbers: []int{1}, - LineCodes: []string{"public class HelloWorld"}, - HighlightedRanges: [][3]int{{0, 18, 23}}, - }, - { - Filename: "python-hello/hello.py", - LineNumbers: []int{1}, - LineCodes: []string{"## This is a simple file to do a hello world"}, - HighlightedRanges: [][3]int{{0, 39, 44}}, - }, - }, res) + t.Run("Max count", func(t *testing.T) { + if !SupportGrepMaxCount { + t.Skip("Skipping, git grep --max-count is not supported") + return + } - res, err = GrepSearch(t.Context(), repo, "world", GrepOptions{ - MatchesPerFile: 1, - Filename: "java-hello/", + res, err = GrepSearch(t.Context(), repo, "world", GrepOptions{MatchesPerFile: 1}) + require.NoError(t, err) + assert.Equal(t, []*GrepResult{ + { + Filename: "i-am-a-python.p", + LineNumbers: []int{1}, + LineCodes: []string{"## This is a simple file to do a hello world"}, + HighlightedRanges: [][3]int{{0, 39, 44}}, + }, + { + Filename: "java-hello/main.java", + LineNumbers: []int{1}, + LineCodes: []string{"public class HelloWorld"}, + HighlightedRanges: [][3]int{{0, 18, 23}}, + }, + { + Filename: "main.vendor.java", + LineNumbers: []int{1}, + LineCodes: []string{"public class HelloWorld"}, + HighlightedRanges: [][3]int{{0, 18, 23}}, + }, + { + Filename: "python-hello/hello.py", + LineNumbers: []int{1}, + LineCodes: []string{"## This is a simple file to do a hello world"}, + HighlightedRanges: [][3]int{{0, 39, 44}}, + }, + }, res) + + res, err = GrepSearch(t.Context(), repo, "world", GrepOptions{ + MatchesPerFile: 1, + Filename: "java-hello/", + }) + require.NoError(t, err) + assert.Equal(t, []*GrepResult{ + { + Filename: "java-hello/main.java", + LineNumbers: []int{1}, + LineCodes: []string{"public class HelloWorld"}, + HighlightedRanges: [][3]int{{0, 18, 23}}, + }, + }, res) }) - require.NoError(t, err) - assert.Equal(t, []*GrepResult{ - { - Filename: "java-hello/main.java", - LineNumbers: []int{1}, - LineCodes: []string{"public class HelloWorld"}, - HighlightedRanges: [][3]int{{0, 18, 23}}, - }, - }, res) res, err = GrepSearch(t.Context(), repo, "no-such-content", GrepOptions{}) require.NoError(t, err) diff --git a/modules/git/repo_attribute_test.go b/modules/git/repo_attribute_test.go index 0006559709..c69382e245 100644 --- a/modules/git/repo_attribute_test.go +++ b/modules/git/repo_attribute_test.go @@ -251,10 +251,14 @@ func TestGitAttributeCheckerError(t *testing.T) { cancel() ac, err := gitRepo.GitAttributeChecker("8fee858da5796dfb37704761701bb8e800ad9ef3", "linguist-language") - require.NoError(t, err) + if SupportCheckAttrOnBare { + require.NoError(t, err) - _, err = ac.CheckPath("i-am-a-python.p") - require.Error(t, err) + _, err = ac.CheckPath("i-am-a-python.p") + require.Error(t, err) + } else { + require.Error(t, err) + } }) t.Run("Cancelled/DuringRun", func(t *testing.T) { diff --git a/modules/indexer/issues/elasticsearch/elasticsearch_test.go b/modules/indexer/issues/elasticsearch/elasticsearch_test.go index 0c2635fd0c..1ad01352d3 100644 --- a/modules/indexer/issues/elasticsearch/elasticsearch_test.go +++ b/modules/indexer/issues/elasticsearch/elasticsearch_test.go @@ -16,16 +16,10 @@ import ( ) func TestElasticsearchIndexer(t *testing.T) { - // The elasticsearch instance started by testing.yml > test-unit > services > elasticsearch - url := "http://elastic:changeme@elasticsearch:9200" - - if os.Getenv("CI") == "" { - // Make it possible to run tests against a local elasticsearch instance - url = os.Getenv("TEST_ELASTICSEARCH_URL") - if url == "" { - t.Skip("TEST_ELASTICSEARCH_URL not set and not running in CI") - return - } + url := os.Getenv("TEST_ELASTICSEARCH_URL") + if url == "" { + t.Skip("TEST_ELASTICSEARCH_URL not set") + return } require.Eventually(t, func() bool { diff --git a/modules/indexer/issues/internal/qstring.go b/modules/indexer/issues/internal/qstring.go index 8115fc904f..6b60b4c5f6 100644 --- a/modules/indexer/issues/internal/qstring.go +++ b/modules/indexer/issues/internal/qstring.go @@ -25,7 +25,7 @@ type Token struct { func (tk *Token) ParseIssueReference() (int64, error) { term := tk.Term - if term[0] == '#' || term[0] == '!' { + if len(term) > 1 && (term[0] == '#' || term[0] == '!') { term = term[1:] } return strconv.ParseInt(term, 10, 64) diff --git a/modules/indexer/issues/internal/qstring_test.go b/modules/indexer/issues/internal/qstring_test.go index a911b86e2f..835491707c 100644 --- a/modules/indexer/issues/internal/qstring_test.go +++ b/modules/indexer/issues/internal/qstring_test.go @@ -169,3 +169,35 @@ func TestIssueQueryString(t *testing.T) { }) } } + +func TestToken_ParseIssueReference(t *testing.T) { + var tk Token + { + tk.Term = "123" + id, err := tk.ParseIssueReference() + require.NoError(t, err) + assert.Equal(t, int64(123), id) + } + { + tk.Term = "#123" + id, err := tk.ParseIssueReference() + require.NoError(t, err) + assert.Equal(t, int64(123), id) + } + { + tk.Term = "!123" + id, err := tk.ParseIssueReference() + require.NoError(t, err) + assert.Equal(t, int64(123), id) + } + { + tk.Term = "text" + _, err := tk.ParseIssueReference() + require.Error(t, err) + } + { + tk.Term = "" + _, err := tk.ParseIssueReference() + require.Error(t, err) + } +} diff --git a/modules/markup/html.go b/modules/markup/html.go index c13ebab98a..7961c5c930 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -69,6 +69,10 @@ var ( // https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type%3Demail) emailRegex = regexp.MustCompile("(?:\\s|^|\\(|\\[)([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)(?:\\s|$|\\)|\\]|;|,|\\?|!|\\.(\\s|$))") + // Fediverse handle regex (same as emailRegex but with additonal @ or ! + // at start) + fediRegex = regexp.MustCompile("(?:\\s|^|\\(|\\[)([@!]([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+)@([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+))(?:\\s|$|\\)|\\]|;|,|\\?|!|\\.(\\s|$))") + // blackfriday extensions create IDs like fn:user-content-footnote blackfridayExtRegex = regexp.MustCompile(`[^:]*:user-content-`) @@ -153,6 +157,7 @@ var defaultProcessors = []processor{ issueIndexPatternProcessor, commitCrossReferencePatternProcessor, hashCurrentPatternProcessor, + fediAddressProcessor, emailAddressProcessor, emojiProcessor, emojiShortCodeProcessor, @@ -1237,6 +1242,21 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) { } } +// fediAddressProcessor replaces raw fediverse handles with toolforge links +func fediAddressProcessor(ctx *RenderContext, node *html.Node) { + next := node.NextSibling + for node != nil && node != next { + m := fediRegex.FindStringSubmatchIndex(node.Data) + if m == nil { + return + } + + fedihandle := node.Data[m[2]:m[3]] + replaceContent(node, m[2], m[3], createLink("https://fedirect.toolforge.org/?id="+url.QueryEscape(fedihandle), fedihandle, "fedihandle")) + node = node.NextSibling.NextSibling + } +} + // emailAddressProcessor replaces raw email addresses with a mailto: link. func emailAddressProcessor(ctx *RenderContext, node *html.Node) { next := node.NextSibling diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index 9d0c40c9e8..2bc929bb04 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -307,6 +307,19 @@ func TestRender_email(t *testing.T) { test( "email@domain..com", `

email@domain..com

`) + + // Test fediverse handle + test( + "@forgejo@floss.social", + `

@forgejo@floss.social

`) + + test( + "!forgejo@programming.dev", + `

!forgejo@programming.dev

`) + + test( + "@#&@forgejo.org", + `

@#&@forgejo.org

`) } func TestRender_emoji(t *testing.T) { diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index 717da464b9..e811d29994 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -267,8 +267,13 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error // RenderString renders Markdown string to HTML with all specific handling stuff and return string func RenderString(ctx *markup.RenderContext, content string) (template.HTML, error) { + return RenderReader(ctx, strings.NewReader(content)) +} + +// RenderReader renders Markdown io.Reader to HTML with all specific handling stuff and return string +func RenderReader(ctx *markup.RenderContext, input io.Reader) (template.HTML, error) { var buf strings.Builder - if err := Render(ctx, strings.NewReader(content), &buf); err != nil { + if err := Render(ctx, input, &buf); err != nil { return "", err } return template.HTML(buf.String()), nil diff --git a/modules/public/mime_types.go b/modules/public/mime_types.go index 32bdf3bfa2..87ee2854ae 100644 --- a/modules/public/mime_types.go +++ b/modules/public/mime_types.go @@ -23,6 +23,11 @@ var wellKnownMimeTypesLower = map[string]string{ ".wasm": "application/wasm", ".webp": "image/webp", ".xml": "text/xml; charset=utf-8", + ".glb": "model/gltf-binary", + ".gltf": "model/gltf+json", + ".obj": "model/obj", + ".stl": "model/stl", + ".3mf": "model/3mf", // well, there are some types missing from the builtin list ".txt": "text/plain; charset=utf-8", diff --git a/modules/public/public.go b/modules/public/public.go index 174936fd4a..a7db5b62e9 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -6,6 +6,7 @@ package public import ( "bytes" "io" + "io/fs" "net/http" "os" "path" @@ -59,7 +60,7 @@ func setWellKnownContentType(w http.ResponseWriter, file string) { } } -func handleRequest(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) { +func handleRequest(w http.ResponseWriter, req *http.Request, fs fs.FS, file string) { // actually, fs (http.FileSystem) is designed to be a safe interface, relative paths won't bypass its parent directory, it's also fine to do a clean here f, err := fs.Open(util.PathJoinRelX(file)) if err != nil { @@ -86,33 +87,31 @@ func handleRequest(w http.ResponseWriter, req *http.Request, fs http.FileSystem, return } - serveContent(w, req, fi, fi.ModTime(), f) + serveContent(w, req, fi.Name(), fi.ModTime(), f.(io.ReadSeeker)) } -type GzipBytesProvider interface { - GzipBytes() []byte +type ZstdBytesProvider interface { + ZstdBytes() []byte } // serveContent serve http content -func serveContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modtime time.Time, content io.ReadSeeker) { - setWellKnownContentType(w, fi.Name()) +func serveContent(w http.ResponseWriter, req *http.Request, name string, modtime time.Time, content io.ReadSeeker) { + setWellKnownContentType(w, name) encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding")) - if encodings.Contains("gzip") { - // try to provide gzip content directly from bindata (provided by vfsgen۰CompressedFileInfo) - if compressed, ok := fi.(GzipBytesProvider); ok { - rdGzip := bytes.NewReader(compressed.GzipBytes()) - // all gzipped static files (from bindata) are managed by Gitea, so we can make sure every file has the correct ext name - // then we can get the correct Content-Type, we do not need to do http.DetectContentType on the decompressed data + if encodings.Contains("zstd") { + // If the file was compressed, use the bytes directly. + if compressed, ok := content.(ZstdBytesProvider); ok { + rdZstd := bytes.NewReader(compressed.ZstdBytes()) if w.Header().Get("Content-Type") == "" { w.Header().Set("Content-Type", "application/octet-stream") } - w.Header().Set("Content-Encoding", "gzip") - httpcache.ServeContentWithCacheControl(w, req, fi.Name(), modtime, rdGzip) + w.Header().Set("Content-Encoding", "zstd") + httpcache.ServeContentWithCacheControl(w, req, name, modtime, rdZstd) return } } - httpcache.ServeContentWithCacheControl(w, req, fi.Name(), modtime, content) + httpcache.ServeContentWithCacheControl(w, req, name, modtime, content) return } diff --git a/modules/public/serve_static.go b/modules/public/serve_static.go index e19bd976eb..148d789bba 100644 --- a/modules/public/serve_static.go +++ b/modules/public/serve_static.go @@ -12,8 +12,6 @@ import ( "forgejo.org/modules/timeutil" ) -var _ GzipBytesProvider = (*vfsgen۰CompressedFileInfo)(nil) - // GlobalModTime provide a global mod time for embedded asset files func GlobalModTime(filename string) time.Time { return timeutil.GetExecutableModTime() diff --git a/modules/storage/minio_test.go b/modules/storage/minio_test.go index e168a2efbb..ec1b2fc77a 100644 --- a/modules/storage/minio_test.go +++ b/modules/storage/minio_test.go @@ -18,13 +18,14 @@ import ( ) func TestMinioStorageIterator(t *testing.T) { - if os.Getenv("CI") == "" { - t.Skip("minioStorage not present outside of CI") + endpoint := os.Getenv("TEST_MINIO_ENDPOINT") + if endpoint == "" { + t.Skip("TEST_MINIO_ENDPOINT not set") return } testStorageIterator(t, setting.MinioStorageType, &setting.Storage{ MinioConfig: setting.MinioStorageConfig{ - Endpoint: "minio:9000", + Endpoint: endpoint, AccessKeyID: "123456", SecretAccessKey: "12345678", Bucket: "gitea", @@ -34,13 +35,14 @@ func TestMinioStorageIterator(t *testing.T) { } func TestVirtualHostMinioStorage(t *testing.T) { - if os.Getenv("CI") == "" { - t.Skip("minioStorage not present outside of CI") + endpoint := os.Getenv("TEST_MINIO_ENDPOINT") + if endpoint == "" { + t.Skip("TEST_MINIO_ENDPOINT not set") return } testStorageIterator(t, setting.MinioStorageType, &setting.Storage{ MinioConfig: setting.MinioStorageConfig{ - Endpoint: "minio:9000", + Endpoint: endpoint, AccessKeyID: "123456", SecretAccessKey: "12345678", Bucket: "gitea", @@ -85,13 +87,14 @@ func TestMinioStoragePath(t *testing.T) { } func TestS3StorageBadRequest(t *testing.T) { - if os.Getenv("CI") == "" { - t.Skip("S3Storage not present outside of CI") + endpoint := os.Getenv("TEST_MINIO_ENDPOINT") + if endpoint == "" { + t.Skip("TEST_MINIO_ENDPOINT not set") return } cfg := &setting.Storage{ MinioConfig: setting.MinioStorageConfig{ - Endpoint: "minio:9000", + Endpoint: endpoint, AccessKeyID: "123456", SecretAccessKey: "12345678", Bucket: "bucket", diff --git a/modules/structs/action.go b/modules/structs/action.go index 2c42365c19..f47b228d75 100644 --- a/modules/structs/action.go +++ b/modules/structs/action.go @@ -78,3 +78,9 @@ type ActionRun struct { // the url of this action run HTMLURL string `json:"html_url"` } + +// ListActionRunResponse return a list of ActionRun +type ListActionRunResponse struct { + Entries []*ActionRun `json:"workflow_runs"` + TotalCount int64 `json:"total_count"` +} diff --git a/modules/structs/git_blob.go b/modules/structs/git_blob.go index 96c7a271a9..ef06693905 100644 --- a/modules/structs/git_blob.go +++ b/modules/structs/git_blob.go @@ -3,8 +3,8 @@ package structs -// GitBlobResponse represents a git blob -type GitBlobResponse struct { +// GitBlob represents a git blob +type GitBlob struct { Content string `json:"content"` Encoding string `json:"encoding"` URL string `json:"url"` diff --git a/modules/structs/repo_actions.go b/modules/structs/repo_actions.go index 505367336c..b13f344738 100644 --- a/modules/structs/repo_actions.go +++ b/modules/structs/repo_actions.go @@ -32,23 +32,3 @@ type ActionTaskResponse struct { Entries []*ActionTask `json:"workflow_runs"` TotalCount int64 `json:"total_count"` } - -// ActionRun represents an ActionRun -type RepoActionRun struct { - ID int64 `json:"id"` - Name string `json:"name"` - RunNumber int64 `json:"run_number"` - Event string `json:"event"` - Status string `json:"status"` - HeadBranch string `json:"head_branch"` - HeadSHA string `json:"head_sha"` - WorkflowID string `json:"workflow_id"` - URL string `json:"url"` - TriggeringActor *User `json:"triggering_actor"` -} - -// ListActionRunResponse return a list of ActionRun -type ListRepoActionRunResponse struct { - Entries []*RepoActionRun `json:"workflow_runs"` - TotalCount int64 `json:"total_count"` -} diff --git a/modules/typesniffer/typesniffer.go b/modules/typesniffer/typesniffer.go index a8fc70e54c..262feb2b05 100644 --- a/modules/typesniffer/typesniffer.go +++ b/modules/typesniffer/typesniffer.go @@ -24,6 +24,16 @@ const ( AvifMimeType = "image/avif" // ApplicationOctetStream MIME type of binary files. ApplicationOctetStream = "application/octet-stream" + // GLTFMimeType MIME type of GLTF files. + GLTFMimeType = "model/gltf+json" + // GLBMimeType MIME type of GLB files. + GLBMimeType = "model/gltf-binary" + // OBJMimeType MIME type of OBJ files. + OBJMimeType = "model/obj" + // STLMimeType MIME type of STL files. + STLMimeType = "model/stl" + // 3MFMimeType MIME type of 3MF files. + ThreeMFMimeType = "model/3mf" ) var ( @@ -67,6 +77,36 @@ func (ct SniffedType) IsAudio() bool { return strings.Contains(ct.contentType, "audio/") } +// Is3DModel detects if data is a 3D format +func (ct SniffedType) Is3DModel() bool { + return strings.Contains(ct.contentType, "model/") +} + +// IsGLTFFile detects if data is an SVG image format +func (ct SniffedType) IsGLTF() bool { + return strings.Contains(ct.contentType, GLTFMimeType) +} + +// IsGLBFile detects if data is an GLB image format +func (ct SniffedType) IsGLB() bool { + return strings.Contains(ct.contentType, GLBMimeType) +} + +// IsOBJFile detects if data is an OBJ image format +func (ct SniffedType) IsOBJ() bool { + return strings.Contains(ct.contentType, OBJMimeType) +} + +// IsSTLTextFile detects if data is an STL text format +func (ct SniffedType) IsSTL() bool { + return strings.Contains(ct.contentType, STLMimeType) +} + +// Is3MFFile detects if data is an 3MF image format +func (ct SniffedType) Is3MF() bool { + return strings.Contains(ct.contentType, ThreeMFMimeType) +} + // IsRepresentableAsText returns true if file content can be represented as // plain text or is empty. func (ct SniffedType) IsRepresentableAsText() bool { @@ -75,7 +115,7 @@ func (ct SniffedType) IsRepresentableAsText() bool { // IsBrowsableBinaryType returns whether a non-text type can be displayed in a browser func (ct SniffedType) IsBrowsableBinaryType() bool { - return ct.IsImage() || ct.IsSvgImage() || ct.IsPDF() || ct.IsVideo() || ct.IsAudio() + return ct.IsImage() || ct.IsSvgImage() || ct.IsPDF() || ct.IsVideo() || ct.IsAudio() || ct.Is3DModel() } // GetMimeType returns the mime type @@ -135,6 +175,13 @@ func DetectContentType(data []byte) SniffedType { ct = "audio/ogg" // for most cases, it is used as an audio container } } + + // GLTF is unsupported by http.DetectContentType + // hexdump -n 4 -C glTF.glb + if bytes.HasPrefix(data, []byte("glTF")) { + ct = GLBMimeType + } + return SniffedType{ct} } diff --git a/modules/typesniffer/typesniffer_test.go b/modules/typesniffer/typesniffer_test.go index 8d80b4ddb4..176d3658bb 100644 --- a/modules/typesniffer/typesniffer_test.go +++ b/modules/typesniffer/typesniffer_test.go @@ -117,6 +117,14 @@ func TestIsAudio(t *testing.T) { assert.True(t, DetectContentType([]byte("ID3Toy\n====\t* hi 🌞, ..."+"🌛"[0:2])).IsText()) // test ID3 tag with incomplete UTF8 char } +func TestIsGLB(t *testing.T) { + glb, _ := hex.DecodeString("676c5446") + assert.True(t, DetectContentType(glb).IsGLB()) + assert.True(t, DetectContentType(glb).Is3DModel()) + assert.False(t, DetectContentType([]byte("plain text")).IsGLB()) + assert.False(t, DetectContentType([]byte("plain text")).Is3DModel()) +} + func TestDetectContentTypeFromReader(t *testing.T) { mp3, _ := base64.StdEncoding.DecodeString("SUQzBAAAAAABAFRYWFgAAAASAAADbWFqb3JfYnJhbmQAbXA0MgBUWFhYAAAAEQAAA21pbm9yX3Zl") st, err := DetectContentTypeFromReader(bytes.NewReader(mp3)) @@ -145,3 +153,15 @@ func TestDetectContentTypeAvif(t *testing.T) { assert.True(t, st.IsImage()) } + +func TestDetectContentTypeModelGLB(t *testing.T) { + glb, err := hex.DecodeString("676c5446") + require.NoError(t, err) + + st, err := DetectContentTypeFromReader(bytes.NewReader(glb)) + require.NoError(t, err) + + // print st for debugging + assert.Equal(t, "model/gltf-binary", st.GetMimeType()) + assert.True(t, st.IsGLB()) +} diff --git a/modules/util/io.go b/modules/util/io.go index 1559b019a0..4c99004c0c 100644 --- a/modules/util/io.go +++ b/modules/util/io.go @@ -4,7 +4,6 @@ package util import ( - "bytes" "errors" "io" ) @@ -20,42 +19,6 @@ func ReadAtMost(r io.Reader, buf []byte) (n int, err error) { return n, err } -// ReadWithLimit reads at most "limit" bytes from r into buf. -// If EOF or ErrUnexpectedEOF occurs while reading, err will be nil. -func ReadWithLimit(r io.Reader, n int) (buf []byte, err error) { - return readWithLimit(r, 1024, n) -} - -func readWithLimit(r io.Reader, batch, limit int) ([]byte, error) { - if limit <= batch { - buf := make([]byte, limit) - n, err := ReadAtMost(r, buf) - if err != nil { - return nil, err - } - return buf[:n], nil - } - res := bytes.NewBuffer(make([]byte, 0, batch)) - bufFix := make([]byte, batch) - eof := false - for res.Len() < limit && !eof { - bufTmp := bufFix - if res.Len()+batch > limit { - bufTmp = bufFix[:limit-res.Len()] - } - n, err := io.ReadFull(r, bufTmp) - if err == io.EOF || err == io.ErrUnexpectedEOF { - eof = true - } else if err != nil { - return nil, err - } - if _, err = res.Write(bufTmp[:n]); err != nil { - return nil, err - } - } - return res.Bytes(), nil -} - // ErrNotEmpty is an error reported when there is a non-empty reader var ErrNotEmpty = errors.New("not-empty") diff --git a/modules/util/io_test.go b/modules/util/io_test.go deleted file mode 100644 index 870e713646..0000000000 --- a/modules/util/io_test.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package util - -import ( - "bytes" - "errors" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type readerWithError struct { - buf *bytes.Buffer -} - -func (r *readerWithError) Read(p []byte) (n int, err error) { - if r.buf.Len() < 2 { - return 0, errors.New("test error") - } - return r.buf.Read(p) -} - -func TestReadWithLimit(t *testing.T) { - bs := []byte("0123456789abcdef") - - // normal test - buf, err := readWithLimit(bytes.NewBuffer(bs), 5, 2) - require.NoError(t, err) - assert.Equal(t, []byte("01"), buf) - - buf, err = readWithLimit(bytes.NewBuffer(bs), 5, 5) - require.NoError(t, err) - assert.Equal(t, []byte("01234"), buf) - - buf, err = readWithLimit(bytes.NewBuffer(bs), 5, 6) - require.NoError(t, err) - assert.Equal(t, []byte("012345"), buf) - - buf, err = readWithLimit(bytes.NewBuffer(bs), 5, len(bs)) - require.NoError(t, err) - assert.Equal(t, []byte("0123456789abcdef"), buf) - - buf, err = readWithLimit(bytes.NewBuffer(bs), 5, 100) - require.NoError(t, err) - assert.Equal(t, []byte("0123456789abcdef"), buf) - - // test with error - buf, err = readWithLimit(&readerWithError{bytes.NewBuffer(bs)}, 5, 10) - require.NoError(t, err) - assert.Equal(t, []byte("0123456789"), buf) - - buf, err = readWithLimit(&readerWithError{bytes.NewBuffer(bs)}, 5, 100) - require.ErrorContains(t, err, "test error") - assert.Empty(t, buf) - - // test public function - buf, err = ReadWithLimit(bytes.NewBuffer(bs), 2) - require.NoError(t, err) - assert.Equal(t, []byte("01"), buf) - - buf, err = ReadWithLimit(bytes.NewBuffer(bs), 9999999) - require.NoError(t, err) - assert.Equal(t, []byte("0123456789abcdef"), buf) -} diff --git a/modules/util/truncate.go b/modules/util/truncate.go index f2edbdc673..7207a89177 100644 --- a/modules/util/truncate.go +++ b/modules/util/truncate.go @@ -54,3 +54,12 @@ func SplitTrimSpace(input, sep string) []string { return stringList } + +// TruncateRunes returns a truncated string with given rune limit, +// it returns input string if its rune length doesn't exceed the limit. +func TruncateRunes(str string, limit int) string { + if utf8.RuneCountInString(str) < limit { + return str + } + return string([]rune(str)[:limit]) +} diff --git a/modules/util/truncate_test.go b/modules/util/truncate_test.go index dfe1230fd4..8187b13eb2 100644 --- a/modules/util/truncate_test.go +++ b/modules/util/truncate_test.go @@ -44,3 +44,18 @@ func TestSplitString(t *testing.T) { } test(tc, SplitStringAtByteN) } + +func TestTruncateRunes(t *testing.T) { + assert.Empty(t, TruncateRunes("", 0)) + assert.Empty(t, TruncateRunes("", 1)) + + assert.Empty(t, TruncateRunes("ab", 0)) + assert.Equal(t, "a", TruncateRunes("ab", 1)) + assert.Equal(t, "ab", TruncateRunes("ab", 2)) + assert.Equal(t, "ab", TruncateRunes("ab", 3)) + + assert.Empty(t, TruncateRunes("测试", 0)) + assert.Equal(t, "测", TruncateRunes("测试", 1)) + assert.Equal(t, "测试", TruncateRunes("测试", 2)) + assert.Equal(t, "测试", TruncateRunes("测试", 3)) +} diff --git a/options/locale/locale_bg.ini b/options/locale/locale_bg.ini index 6fc4b55eae..abce4f1133 100644 --- a/options/locale/locale_bg.ini +++ b/options/locale/locale_bg.ini @@ -11,7 +11,7 @@ copy_content = Копиране на съдържанието user_profile_and_more = Профил и настройки… view = Преглед your_settings = Настройки -mirrors = Огледала +mirrors = Огледални explore = Разглеждане write = Писане twofa = Двуфакторно удостоверяване @@ -36,7 +36,7 @@ dashboard = Табло logo = Лого toc = Съдържание copy_url = Копиране на URL -new_mirror = Ново огледало +new_mirror = Ново огледално re_type = Потвърдете паролата copy = Копиране enabled = Включено @@ -61,7 +61,7 @@ ok = Добре manage_org = Управление на организациите new_repo = Ново хранилище register = Регистрация -mirror = Огледало +mirror = Огледално username = Потребителско име password = Парола template = Шаблон @@ -69,7 +69,7 @@ signed_in_as = Влезли сте като sign_up = Регистриране enable_javascript = Този сайт изисква JavaScript. home = Начало -email = Адрес на ел. поща +email = Адрес за ел. поща issues = Задачи retry = Повторен опит remove = Премахване @@ -93,8 +93,8 @@ filter.not_fork = Не разклонения filter.is_template = Шаблони filter.not_template = Не шаблони filter.private = Частни -filter.is_mirror = Огледала -filter.not_mirror = Не огледала +filter.is_mirror = Огледални +filter.not_mirror = Не огледални copy_hash = Копиране на контролната сума artifacts = Артефакти show_log_seconds = Показване на секундите @@ -102,7 +102,7 @@ remove_all = Премахване на всичко test = Проба remove_label_str = Премахване на елемента „%s“ copy_branch = Копиране на името на клона -error404 = Страницата, която се опитвате да отворите, или не съществува или не сте упълномощени да я видите. +error404 = Страницата, която се опитвате да отворите, или не съществува, или е премахната, или не сте упълномощени да я видите. new_repo.link = Ново хранилище new_migrate.title = Нова миграция new_repo.title = Ново хранилище @@ -112,6 +112,35 @@ new_org.link = Нова организация copy_generic = Копиране в клипборда copy_error = Неуспешно копиране copy_path = Копиране на пътя +toggle_menu = Превключване на менюто +confirm_delete_artifact = Сигурни ли сте, че искате да изтриете артефакта „%s“? +more_items = Още елементи +twofa_scratch = Резервен код за двуфакторно удостоверяване +webauthn_use_twofa = Използвайте двуфакторен код от телефона си +webauthn_error_insecure = WebAuthn поддържа само сигурни връзки. За тестване през HTTP можете да използвате произход „localhost“ или „127.0.0.1“ +error413 = Изчерпали сте квотата си. +go_back = Връщане +invalid_data = Невалидни данни: %v +archived = Архивирано +concept_system_global = Глобално +concept_user_individual = Индивидуално +show_full_screen = Показване на цял екран +show_timestamps = Показване на времеви отпечатъци +rerun = Повторно изпълнение +copy_type_unsupported = Този тип файл не може да бъде копиран +webauthn_error_unknown = Възникна неизвестна грешка. Моля, опитайте отново. +webauthn_error_unable_to_process = Сървърът не можа да обработи заявката ви. +webauthn_error_empty = Трябва да зададете име за този ключ. +webauthn_error_timeout = Времето за изчакване изтече преди ключът ви да бъде прочетен. Моля, презаредете страницата и опитайте отново. +return_to_forgejo = Връщане към Forgejo +unknown = Неизвестно +confirm_delete_selected = Потвърждавате ли изтриването на всички избрани елементи? +webauthn_insert_key = Поставете вашия ключ за сигурност +webauthn_press_button = Моля, натиснете бутона на вашия ключ за сигурност… +webauthn_sign_in = Натиснете бутона на вашия ключ за сигурност. Ако ключът ви за сигурност няма бутон, поставете го отново. +webauthn_error = Неуспешно прочитане на вашия ключ за сигурност. +webauthn_unsupported_browser = Вашият браузър в момента не поддържа WebAuthn. +webauthn_error_duplicated = Ключът за сигурност не е разрешен за тази заявка. Моля, уверете се, че ключът не е вече регистриран. [settings] ui = Тема @@ -157,7 +186,7 @@ account = Акаунт update_avatar = Обновяване на профилната снимка ssh_gpg_keys = SSH / GPG ключове comment_type_group_milestone = Етап -manage_emails = Управление на адресите на ел. поща +manage_emails = Управление на адресите за ел. поща permission_read = Четене update_password = Обновяване на паролата biography_placeholder = Разкажете на другите малко за себе си! (Можете да използвате Маркдаун) @@ -183,7 +212,7 @@ user_block_success = Потребителят е блокиран успешно update_profile_success = Профилът ви е обновен. update_user_avatar_success = Профилната снимка на потребителя е обновена. remove_oauth2_application_success = Приложението е изтрито. -email_deletion_success = Адресът на ел. поща е премахнат. +email_deletion_success = Адресът за ел. поща е премахнат. update_avatar_success = Профилната ви снимка е обновена. change_username = Потребителското ви име е променено. comment_type_group_assignee = Изпълнител @@ -191,22 +220,22 @@ enable_custom_avatar = Използване на персонализирана requires_activation = Изисква активиране activated = Активиран primary = Основен -email_deletion = Премахване на адреса на ел. поща -add_new_email = Добавяне на нов адрес на ел. поща -add_email = Добавяне на адрес на ел. поща +email_deletion = Премахване на адреса за ел. поща +add_new_email = Добавяне на нов адрес за ел. поща +add_email = Добавяне на адрес за ел. поща key_content_gpg_placeholder = Започва с „-----BEGIN PGP PUBLIC KEY BLOCK-----“ comment_type_group_title = Заглавие comment_type_group_label = Етикет -change_username_prompt = Забележка: Промяната на потребителското ви име променя също URL на вашия акаунт. +change_username_prompt = Бележка: Промяната на потребителското ви име променя също URL на вашия акаунт. update_language_not_found = Езикът „%s“ не е наличен. keep_activity_private_popup = Вашата дейност ще бъде видима само за вас и администраторите на сайта uploaded_avatar_not_a_image = Каченият файл не е изображение. uploaded_avatar_is_too_big = Размерът на качения файл (%d KiB) надвишава максималния размер (%d KiB). -change_password_success = Паролата ви е обновена. Влизайте с новата си парола от сега нататък. +change_password_success = Паролата ви е обновена. Отсега нататък използвайте новата си парола, за да влезете. manage_themes = Тема по подразбиране manage_openid = OpenID адреси primary_email = Да е основен -keep_email_private = Скриване на адреса на ел. поща +keep_email_private = Скриване на адреса за ел. поща theme_update_error = Избраната тема не съществува. theme_update_success = Темата ви е обновена. key_content_ssh_placeholder = Започва с „ssh-ed25519“, „ssh-rsa“, „ecdsa-sha2-nistp256“, „ecdsa-sha2-nistp384“, „ecdsa-sha2-nistp521“, „sk-ecdsa-sha2-nistp256@openssh.com“, или „sk-ssh-ed25519@openssh.com“ @@ -227,7 +256,7 @@ saved_successfully = Настройките бяха запазени успеш no_activity = Няма скорошна дейност theme_desc = Тази тема ще се използва за уеб интерфейса, когато сте влезли. keep_activity_private = Скриване на дейността от профилната страница -lookup_avatar_by_mail = Търсене на профилна снимка по адреса на ел. поща +lookup_avatar_by_mail = Търсене на профилна снимка по адреса за ел. поща password_incorrect = Текущата парола е неправилна. change_username_redirect_prompt = Старото потребителско име ще се пренасочва, докато някой не го вземе. principal_content = Съдържание @@ -246,7 +275,7 @@ delete_prompt = Тази операция ще изтрие перманентн email_notifications.disable = Изключване на известията по ел. поща delete_account = Изтриване на акаунта ви confirm_delete_account = Потвърждаване на изтриването -email_notifications.onmention = Ел. поща само при споменаване +email_notifications.onmention = Ел. писмо само при споменаване pronouns_unspecified = Непосочени pronouns = Местоимения gpg_token_code = echo "%s" | gpg -a --default-key %s --detach-sig @@ -254,8 +283,103 @@ language.title = Език по подразбиране language.localization_project = Помогнете ни да преведем Forgejo на вашия език! Научете повече. language.description = Този език ще бъде запазен във вашия акаунт и ще се използва като език по подразбиране, след като влезете. pronouns_custom = Персонализирани -visibility.limited_tooltip = Видимо само за влезли потребители +visibility.limited_tooltip = Видим само за влезли потребители pronouns_custom_label = Персонализирани местоимения +comment_type_group_review_request = Искане за рецензия +ssh_key_been_used = Този SSH ключ вече е добавен към сървъра. +create_oauth2_application = Създаване на ново OAuth2 приложение +update_oauth2_application_success = Успешно обновихте OAuth2 приложението. +authorized_oauth2_applications = Упълномощени OAuth2 приложения +manage_account_links = Свързани акаунти +revoke_oauth2_grant = Отнемане на достъпа +added_on = Добавен на %s +comment_type_group_dependency = Зависимост +update_hints_success = Подсказките са обновени. +manage_oauth2_applications = Управление на OAuth2 приложения +gpg_key_id_used = Вече съществува публичен GPG ключ със същото ID. +oauth2_applications_desc = OAuth2 приложенията позволяват на вашето приложение от трета страна да удостоверява сигурно потребители в тази инстанция на Forgejo. +blocked_since = Блокиран от %s +hidden_comment_types.ref_tooltip = Коментари, в които тази задача е спомената от друга задача/подаване/… +create_oauth2_application_success = Успешно създадохте ново OAuth2 приложение. +quota.applies_to_org = Следните правила за квота се прилагат за тази организация +keep_activity_private.description = Вашата публична дейност ще бъде видима само за вас и администраторите на инстанцията. +ssh_helper = Нуждаете се от помощ? Разгледайте ръководството за създаване на собствени SSH ключове или за решаване на често срещани проблеми, които може да срещнете при използване на SSH. +twofa_desc = За да защитите акаунта си от кражба на парола, можете да използвате смартфон или друго устройство за получаване на еднократни пароли, базирани на време („TOTP“). +scan_this_image = Сканирайте това изображение с вашето приложение за удостоверяване: +quota.rule.exceeded.helper = Общият размер на обектите за това правило надвиши квотата. +password_change_disabled = Нелокални потребители не могат да обновяват паролата си през уеб интерфейса на Forgejo. +twofa_disable_note = Можете да изключите двуфакторното удостоверяване, ако е необходимо. +hooks.desc = Добавете уеб-куки, които ще се задействат за всички хранилища, които притежавате. +delete_account_desc = Сигурни ли сте, че искате да изтриете перманентно този потребителски акаунт? +last_used = Последно използван на +revoke_oauth2_grant_description = Отнемането на достъпа за това приложение от трета страна ще му попречи да има достъп до вашите данни. Сигурни ли сте? +password_username_disabled = Нелокални потребители не могат да променят потребителското си име. Моля, свържете се с администратора на сайта за повече подробности. +change_username_redirect_prompt.with_cooldown.one = Старото потребителско име ще бъде достъпно за всички след период на изчакване от %[1]d ден. Все още можете да си върнете старото потребителско име по време на периода на изчакване. +change_username_redirect_prompt.with_cooldown.few = Старото потребителско име ще бъде достъпно за всички след период на изчакване от %[1]d дни. Все още можете да си върнете старото потребителско име по време на периода на изчакване. +generate_token_name_duplicate = %s вече е използвано като име на приложение. Моля, използвайте ново. +quota.rule.exceeded = Надвишена +repo_and_org_access = Достъп до хранилища и организации +permissions_public_only = Само публични +permissions_list = Разрешения: +edit_oauth2_application = Редактиране на OAuth2 приложение +remove_oauth2_application = Премахване на OAuth2 приложение +twofa_recovery_tip = Ако загубите устройството си, ще можете да използвате ключ за еднократно възстановяване, за да си върнете достъпа до акаунта. +visibility.private_tooltip = Видим само за членове на организации, в които участвате +quota.applies_to_user = Следните правила за квота се прилагат за вашия акаунт +quota.rule.no_limit = Неограничена +hints = Подсказки +comment_type_group_issue_ref = Препратка към задача +activate_email = Изпращане на активация +ssh_disabled = SSH е изключен +twofa_disable_desc = Изключването на двуфакторното удостоверяване ще направи акаунта ви по-малко сигурен. Продължаване? +keep_pronouns_private = Показване на местоименията само на удостоверени потребители +keep_pronouns_private.description = Това ще скрие вашите местоимения от посетители, които не са влезли в системата. +gpg_helper = Нуждаете се от помощ? Разгледайте ръководството относно GPG. +valid_until_date = Валиден до %s +ssh_externally_managed = Този SSH ключ се управлява външно за този потребител +regenerate_scratch_token_desc = Ако сте загубили ключа си за възстановяване или вече сте го използвали, за да влезете, можете да го нулирате тук. +create_oauth2_application_button = Създаване на приложение +revoke_oauth2_grant_success = Достъпът е отнет успешно. +comment_type_group_deadline = Краен срок +comment_type_group_time_tracking = Проследяване на времето +activations_pending = Чакащи активации +valid_forever = Валиден завинаги +key_state_desc = Този ключ е използван през последните 7 дни +revoke_key = Отнемане +delete_account_title = Изтриване на потребителския акаунт +update_hints = Обновяване на подсказките +permissions_access_all = Всички (публични, частни и ограничени) +oauth2_application_name = Име на приложението +visibility.public_tooltip = Видим за всички +user_block_yourself = Не можете да блокирате себе си. +hidden_comment_types.issue_ref_tooltip = Коментари, в които потребителят променя клона/маркера, свързан със задачата +comment_type_group_reference = Препратка +comment_type_group_branch = Клон +comment_type_group_pull_request_push = Добавени подавания +quota = Квота +webauthn_delete_key = Премахване на ключ за сигурност +webauthn_register_key = Добавяне на ключ за сигурност +webauthn_nickname = Прякор +webauthn_delete_key_desc = Ако премахнете ключ за сигурност, вече няма да можете да влизате с него. Продължаване? +additional_repo_units_hint = Предлагане за включване на допълнителни елементи на хранилището +twofa_is_enrolled = Вашият акаунт в момента е включен в двуфакторно удостоверяване. +twofa_not_enrolled = Вашият акаунт в момента не е включен в двуфакторно удостоверяване. +webauthn_key_loss_warning = Ако загубите ключовете си за сигурност, ще загубите достъп до акаунта си. +email_desc = Вашият основен адрес за ел. поща ще се използва за известия, възстановяване на парола и, при условие че не е скрит, за уеб-базирани Git операции. +email_preference_set_success = Предпочитанията за ел. поща са зададени успешно. +add_email_confirmation_sent = Изпратено е ел. писмо за потвърждение до „%s“. За да потвърдите адреса си за ел. поща, моля, проверете входящата си кутия и последвайте предоставената връзка в рамките на следващите %s. +additional_repo_units_hint_description = Показване на подсказка „Включване на повече“ за хранилища, които нямат включени всички налични елементи. +email_notifications.submit = Задаване на предпочит. за ел. поща +email_notifications.andyourown = И вашите собствени известия +email_deletion_desc = Адресът за ел. поща и свързаната информация ще бъдат премахнати от вашия акаунт. Git подаванията от този адрес за ел. поща ще останат непроменени. Продължаване? +add_email_success = Новият адрес за ел. поща е добавен. +remove_account_link = Премахване на свързан акаунт +webauthn_alternative_tip = Може да искате да конфигурирате допълнителен метод за удостоверяване. +hidden_comment_types_description = Типовете коментари, отметнати тук, няма да се показват в страниците на задачите. Например, отмятането на „Етикет“ премахва всички коментари от типа „<потребител> добави/премахна <етикет>“. +hidden_comment_types = Скрити типове коментари +comment_type_group_lock = Състояние на заключване +can_not_add_email_activations_pending = Има чакаща активация, опитайте отново след няколко минути, ако искате да добавите нова ел. поща. +storage_overview = Преглед на съхранението [packages] container.labels.value = Стойност @@ -287,6 +411,33 @@ generic.download = Изтеглете пакета от командния ре container.details.type = Тип образ alpine.repository = За хранилището container.images.title = Образи +arch.version.description = Описание +search_in_external_registry = Търсене в %s +filter.type = Тип +filter.container.untagged = Без маркер +filter.type.all = Всички +registry.documentation = За повече информация относно регистъра %s, вижте документацията. +filter.no_result = Вашият филтър не даде резултати. +filter.container.tagged = С маркер +arch.pacman.repo.multi = %s има същата версия в различни дистрибуции. +arch.pacman.helper.gpg = Добавете доверителен сертификат за pacman: +alpine.repository.architectures = Архитектури +arch.version.provides = Доставя +arch.version.groups = Група +details.project_site = Уебсайт на проекта +arch.pacman.conf = Добавете сървър със свързаната дистрибуция и архитектура към /etc/pacman.conf : +arch.pacman.sync = Синхронизирайте пакета с pacman: +details.repository_site = Уебсайт на хранилището +arch.version.depends = Зависимости +arch.version.optdepends = Допълнителни зависимости +arch.version.replaces = Заменя +go.install = Инсталирайте пакета от командния ред: +cargo.registry = Настройте този регистър в конфигурационния файл на Cargo (например ~/.cargo/config.toml): +cargo.install = За да инсталирате пакета с Cargo, изпълнете следната команда: +details.documentation_site = Уебсайт на документацията +arch.version.conflicts = В конфликт +alpine.repository.branches = Клонове +arch.pacman.repo.multi.item = Конфигурация за %s [tool] hours = %d часа @@ -447,7 +598,7 @@ projects.template.desc = Шаблон projects.card_type.text_only = Само текст projects.card_type.images_and_text = Изображения и текст wiki = Уики -wiki.welcome = Добре дошли в Уикито. +wiki.welcome = Добре дошли в уикито. wiki.create_first_page = Създаване на първата страница editor.upload_file = Качване на файл projects.column.color = Цвят @@ -644,7 +795,7 @@ milestones.filter_sort.latest_due_date = Най-далечен краен сро diff.view_file = Преглед на файла release.deletion_success = Изданието е изтрито. projects.column.delete = Изтриване на колоната -migrate.migrating = Мигриране от %s ... +migrate.migrating = Мигриране от %s … escape_control_characters = Екраниране issues.label_deletion_success = Етикетът е изтрит. pulls.is_closed = Заявката за сливане е затворена. @@ -1025,7 +1176,7 @@ issues.content_history.edited = редактирано pulls.title_desc_one = иска да слее %[1]d подаване от %[2]s в %[3]s pulls.showing_specified_commit_range = Показани са само промените между %[1]s..%[2]s pulls.merged_title_desc_one = сля %[1]d подаване от %[2]s в %[3]s %[4]s -pulls.no_merge_access = Не сте упълномощени за сливане на тази заявка за сливане. +pulls.no_merge_access = Не сте упълномощени да слеете тази заявка за сливане. activity.navbar.code_frequency = Честота на промените activity.git_stats_pushed_1 = е изтласкал activity.git_stats_push_to_branch = към %s и @@ -1139,7 +1290,7 @@ issues.review.review = Рецензия issues.review.comment = рецензира %s branch.deleted_by = Изтрит от %s branch.restore = Възстановяване на клона „%s“ -archive.title_date = Това хранилище е архивирано на %s. Можете да преглеждате файлове и да го клонирате, но не можете да изтласквате или отваряте задачи или заявки за сливане. +archive.title_date = Това хранилище е архивирано на %s. Можете да преглеждате файлове и да го клонирате, но не можете да правите промени в състоянието му, като изтласкване и създаване на нови задачи, заявки за сливане или коментари. release.download_count_one = %s изтегляне release.download_count_few = %s изтегляния branch.restore_success = Клонът „%s“ е възстановен. @@ -1156,7 +1307,7 @@ pulls.reopen_to_merge = Моля, отворете наново тази зая pulls.cant_reopen_deleted_branch = Тази заявка за сливане не може да бъде отворена наново, защото клонът е изтрит. pulls.status_checks_hide_all = Скриване на всички проверки pulls.status_checks_failure = Някои проверки са неуспешни -issues.review.add_review_request = поиска рецензия от %s %s +issues.review.add_review_request = поиска рецензия от %[1]s %[2]s wiki.no_search_results = Няма резултати wiki.search = Търсене в уикито issues.author.tooltip.pr = Този потребител е авторът на тази заявка за сливане. @@ -1290,6 +1441,140 @@ issues.reaction.alt_few = %[1]s реагира с %[2]s. issues.reaction.alt_many = %[1]s и още %[2]d реагираха с %[3]s. issues.reaction.alt_add = Добавяне на реакция %[1]s към коментара. issues.reaction.alt_remove = Премахване на реакция %[1]s от коментара. +already_forked = Вече сте разклонили %s +generated_from = генерирано от +clear_ref = `Изчистване на текущата препратка` +file_follow = Последване на символната връзка +commitstatus.failure = Неуспех +issues.filter_label_exclude = `Използвайте alt + click/enter, за да изключите етикети` +migrate.migrating_failed = Мигрирането от %s е неуспешно. +migrate.migrating_issues = Мигриране на задачи +mirror_from = огледално на +fork_from_self = Не можете да разклоните хранилище, което притежавате. +commit_graph.hide_pr_refs = Скриване на заявките за сливане +generated = Генерирано +broken_message = Git данните, лежащи в основата на това хранилище, не могат да бъдат прочетени. Свържете се с администратора на тази инстанция или изтрийте това хранилище. +editor.file_is_a_symlink = `„%s“ е символна връзка. Символните връзки не могат да се редактират в уеб редактора` +commits.browse_further = Разглеждане нататък +commits.older = По-стари +form.reach_limit_of_creation_n = Притежателят вече е достигнал лимита от %d хранилища. +issues.edit.already_changed = Неуспешно запазване на промените в задачата. Изглежда съдържанието вече е променено от друг потребител. Моля, презаредете страницата и опитайте да редактирате отново, за да избегнете презаписването на техните промени +transfer.accept_desc = Прехвърляне към „%s“ +archive.title = Това хранилище е архивирано. Можете да преглеждате файлове и да го клонирате, но не можете да правите промени в състоянието му, като изтласкване и създаване на нови задачи, заявки за сливане или коментари. +form.reach_limit_of_creation_1 = Притежателят вече е достигнал лимита от %d хранилище. +editor.patching = Прилагане на кръпка: +editor.fail_to_apply_patch = Неуспешно прилагане на кръпка „%s“ +commits.no_commits = Няма общи подавания. „%s“ и „%s“ имат напълно различни истории. +migrate.migrating_pulls = Мигриране на заявки за сливане +migrate.migrating_topics = Мигриране на теми +projects.desc = Управлявайте задачи и заявки за сливане в проектни табла. +issues.choose.invalid_templates = %v невалидни шаблона са намерени +pulls.edit.already_changed = Неуспешно запазване на промените в заявката за сливане. Изглежда съдържанието вече е променено от друг потребител. Моля, презаредете страницата и опитайте да редактирате отново, за да избегнете презаписването на техните промени +migrate.gitbucket.description = Мигриране на данни от GitBucket инстанции. +migrate.migrating_git = Мигриране на Git данни +commits.newer = По-нови +issues.choose.blank_about = Създаване на задача от стандартен шаблон. +issues.filter_no_results = Няма резултати +issues.filter_no_results_placeholder = Опитайте да коригирате филтрите си за търсене. +archive.nocomment = Коментирането не е възможно, тъй като хранилището е архивирано. +migrate.gitlab.description = Мигриране на данни от gitlab.com или други GitLab инстанции. +transfer.no_permission_to_accept = Нямате разрешение да приемете това прехвърляне. +transfer.no_permission_to_reject = Нямате разрешение да отхвърлите това прехвърляне. +editor.file_changed_while_editing = Съдържанието на файла е променено, откакто сте го отворили. Щракнете тук, за да го видите, или Подайте промените отново, за да ги презапишете. +sync_fork.button = Синхронизиране +migrate.onedev.description = Мигриране на данни от code.onedev.io или други OneDev инстанции. +migrate.codebase.description = Мигриране на данни от codebasehq.com. +migrate.migrating_labels = Мигриране на етикети +migrate.migrating_releases = Мигриране на издания +editor.push_rejected_no_message = Промяната беше отхвърлена от сървъра без съобщение. Моля, проверете Git куките. +issues.choose.open_external_link = Отваряне +comments.edit.already_changed = Неуспешно запазване на промените в коментара. Изглежда съдържанието вече е променено от друг потребител. Моля, презаредете страницата и опитайте да редактирате отново, за да избегнете презаписването на техните промени +commits.nothing_to_compare = Тези клонове са равни. +transfer.reject_desc = Отказ от прехвърляне към „%s“ +subscribe.pull.guest.tooltip = Влезте, за да се абонирате за тази заявка за сливане. +commit.contained_in_default_branch = Това подаване е част от стандартния клон +normal_view = Нормален изглед +issues.context.menu = Меню за коментара +form.name_reserved = Името на хранилището „%s“ е резервирано. +need_auth = Упълномощаване +subscribe.issue.guest.tooltip = Влезте, за да се абонирате за тази задача. +commitstatus.pending = В очакване +commitstatus.success = Успех +editor.cannot_commit_to_protected_branch = Не може да се подава в защитения клон „%s“. +editor.no_commit_to_branch = Не може да се подава директно в клона, защото: +editor.push_rejected = Промяната беше отхвърлена от сървъра. Моля, проверете Git куките. +cite_this_repo = Цитиране на това хранилище +migrate.gitea.description = Мигриране на данни от gitea.com или други Gitea инстанции. +editor.push_rejected_summary = Пълно съобщение на отхвърлянето: +sync_fork.branch_behind_one = Този клон е %[1]d подаване зад %[2]s +sync_fork.branch_behind_few = Този клон е %[1]d подавания зад %[2]s +form.string_too_long = Даденият низ е по-дълъг от %d знака. +editor.commit_id_not_matching = Файлът е променен, докато сте го редактирали. Подайте в нов клон и след това слейте. +editor.user_no_push_to_branch = Потребителят не може да изтласква в клона +archive.pull.noreview = Това хранилище е архивирано. Не можете да рецензирате заявки за сливане. +migrate.migrating_failed.error = Неуспешно мигриране: %s +migrate.github.description = Мигриране на данни от github.com или GitHub Enterprise сървър. +migrate.forgejo.description = Мигриране на данни от codeberg.org или други Forgejo инстанции. +migrate.gogs.description = Мигриране на данни от notabug.org или други Gogs инстанции. +migrate.migrating_milestones = Мигриране на етапи +migrate.failed = Мигрирането е неуспешно: %v +pulls.nothing_to_compare_and_allow_empty_pr = Тези клонове са равни. Тази заявка за сливане ще бъде празна. +pulls.has_pull_request = `Вече съществува заявка за сливане между тези клонове: %[2]s#%[3]d` +pulls.is_checking = Проверката за конфликти при сливане е в ход. Опитайте отново след няколко минути. +pulls.cannot_merge_work_in_progress = Тази заявка за сливане е отбелязана като в процес на работа. +pulls.blocked_by_approvals = Тази заявка за сливане все още няма достатъчно одобрения. Дадени са %d от %d одобрения. +pulls.blocked_by_rejection = Тази заявка за сливане има поискани промени от официален рецензент. +pulls.waiting_count_1 = %d чакаща рецензия +pulls.status_checks_requested = Задължително +pulls.update_branch_success = Обновяването на клона е успешно +pulls.cannot_auto_merge_helper = Слейте ръчно, за да разрешите конфликтите. +migrate.clone_address_desc = HTTP(S) или Git „clone“ URL на съществуващо хранилище +pulls.add_prefix = Добавете префикс %s +pulls.merge_pull_request = Създаване на подаване със сливане +pulls.waiting_count_n = %d чакащи рецензии +pulls.is_ancestor = Този клон вече е включен в целевия клон. Няма какво да се слива. +pulls.required_status_check_missing = Някои задължителни проверки липсват. +pulls.change_target_branch_at = `промени целевия клон от %s на %s %s` +issues.time_spent_total = Общо изразходвано време +issues.del_time_history = `изтри изразходваното време %s` +pulls.nothing_to_compare_have_tag = Избраните клон/маркер са равни. +pulls.cannot_auto_merge_desc = Тази заявка за сливане не може да бъде слята автоматично поради конфликти. +issues.tracker_auto_close = Таймерът ще бъде спрян автоматично, когато тази задача бъде затворена +issues.force_push_codes = `изтласка принудително %[1]s от %[2]s към %[4]s %[6]s` +pulls.blocked_by_official_review_requests = Тази заявка за сливане е блокирана, защото липсва одобрение от един или повече официални рецензенти. +issues.tracker = Проследяване на времето +issues.add_time_history = `добави изразходвано време %s` +migrate.repo_desc_helper = Оставете празно, за да внесете съществуващото описание +migrate.git.description = Мигриране само на хранилище от всяка Git услуга. +mirror_sync = синхронизирано +migrate_repo = Мигриране на хранилище +migrate_options = Опции за мигрирането +editor.fork_before_edit = Трябва да разклоните това хранилище, за да направите или предложите промени в този файл. +editor.must_have_write_access = Трябва да имате право на запис, за да правите или предлагате промени в този файл. +editor.new_branch_name = Дайте име на новия клон за това подаване +editor.invalid_commit_mail = Невалидна ел. поща за създаване на подаване. +pulls.required_status_check_failed = Някои задължителни проверки не са успешни. +issues.time_spent_from_all_authors = `Общо изразходвано време: %s` +issues.attachment.download = `Щракнете, за да изтеглите „%s“` +issues.attachment.open_tab = `Щракнете, за да видите „%s“ в нов раздел` +pulls.update_branch = Обновяване на клона чрез сливане +migrate_items = Елементи за мигриране +commit.load_referencing_branches_and_tags = Зареждане на клонове и маркери, препращащи към това подаване +pulls.files_conflicted = Тази заявка за сливане има промени, които са в конфликт с целевия клон. +pulls.still_in_progress = Все още е в процес на работа? +pulls.ready_for_review = Готово е за рецензиране? +pulls.is_empty = Промените в този клон вече са в целевия клон. Това ще бъде празно подаване. +issues.start_tracking = Започване на проследяване на времето +migrate_options_mirror_helper = Това хранилище ще бъде огледално +migrate_options_lfs = Мигриране на LFS файлове +editor.upload_file_is_locked = Файлът „%s“ е заключен от %s. +issues.tracking_already_started = `Вече сте започнали проследяване на времето по друга задача!` +pulls.remove_prefix = Премахнете префикса %s +author_search_tooltip = Показва максимум 30 потребители +migrate.migrating_failed_no_addr = Мигрирането е неуспешно. +issues.force_push_compare = Сравняване +pulls.status_checking = Някои проверки са в очакване +pulls.nothing_to_compare = Тези клонове са равни. Не е нужно да създавате заявка за сливане. [modal] confirm = Потвърждаване @@ -1319,6 +1604,11 @@ table_modal.placeholder.content = Съдържание table_modal.placeholder.header = Заглавка buttons.new_table.tooltip = Добавяне на таблица table_modal.header = Добавяне на таблица +link_modal.description = Описание +link_modal.header = Добавяне на връзка +buttons.indent.tooltip = Вмъкване на елементи с едно ниво +buttons.unindent.tooltip = Изваждане на елементи с едно ниво +link_modal.paste_reminder = Подсказка: С URL адрес в клипборда можете да поставите директно в редактора, за да създадете връзка. [org] teams.write_access = Писане @@ -1393,11 +1683,12 @@ members.private_helper = Да е видим teams.no_desc = Този екип няма описание settings.delete_org_desc = Тази организация ще бъде изтрита перманентно. Продължаване? open_dashboard = Отваряне на таблото +settings.change_orgname_prompt = Бележка: Промяната на името на организацията ще промени и URL адреса на вашата организация и ще освободи старото име. [install] admin_password = Парола user = Потребителско име -admin_email = Адрес на ел. поща +admin_email = Адрес за ел. поща path = Път password = Парола host = Хост @@ -1427,13 +1718,17 @@ admin_title = Настройки на администраторския ака err_empty_admin_password = Администраторската парола не може да бъде празна. docker_helper = Ако стартирате Forgejo в Docker, моля, прочетете документацията преди да промените настройки. sqlite_helper = Път на файла за SQLite3 базата данни.
Въведете абсолютен път, ако стартирате Forgejo като service. -err_empty_admin_email = Администраторският адрес на ел. поща не може да бъде празен. +err_empty_admin_email = Администраторският адрес за ел. поща не може да бъде празен. password_algorithm = Алгоритъм за хеш. на паролите -default_keep_email_private = Скриване на адресите на ел. поща по подразбиране +default_keep_email_private = Скриване на адресите за ел. поща по подразбиране invalid_password_algorithm = Невалиден алгоритъм за хеш. на паролите err_admin_name_is_reserved = Потребителското име на администратора е невалидно, потребителското име е резервирано err_admin_name_pattern_not_allowed = Потребителското име на администратора е невалидно, потребителското име съответства с резервиран шаблон err_admin_name_is_invalid = Потребителското име на администратора е невалидно +db_schema_helper = Оставете празно за схемата по подразбиране на базата данни („public“). +reinstall_error = Опитвате се да инсталирате върху съществуваща Forgejo база данни +reinstall_confirm_message = Преинсталирането със съществуваща Forgejo база данни може да причини множество проблеми. В повечето случаи трябва да използвате съществуващия си „app.ini“, за да стартирате Forgejo. Ако знаете какво правите, потвърдете следното: +app_slogan = Слоган на инстанцията [filter] string.asc = А - Я @@ -1462,8 +1757,8 @@ link_not_working_do_paste = Ако връзката не работи, опит activate_account = Моля, активирайте своя акаунт admin.new_user.subject = Нов потребител %s току-що се регистрира activate_account.text_1 = Здравейте, %[1]s, благодарим ви за регистрацията в %[2]s! -activate_email.text = Моля, щракнете върху следната връзка, за да потвърдите своя адрес на ел. поща в рамките на %s: -activate_email = Потвърдете своя адрес на ел. поща +activate_email.text = Моля, щракнете върху следната връзка, за да потвърдите своя адрес за ел. поща в рамките на %s: +activate_email = Потвърдете своя адрес за ел. поща activate_account.text_2 = Моля, щракнете върху следната връзка, за да активирате своя акаунт в рамките на %s: issue_assigned.issue = @%[1]s ви възложи задача %[2]s в хранилище %[3]s. issue.action.push_n = @%[1]s изтласка %[3]d подавания към %[2]s @@ -1473,6 +1768,28 @@ issue.action.merge = @%[1]s сля #%[2]d в %[3]s. issue_assigned.pull = @%[1]s ви възложи заявката за сливане %[2]s в хранилище %[3]s. issue.action.ready_for_review = @%[1]s отбеляза тази заявка за сливане като готова за рецензиране. repo.transfer.subject_to = %s иска да прехвърли хранилище "%s" към %s +password_change.subject = Вашата парола е променена +admin.new_user.text = Моля, щракнете тук, за да управлявате този потребител от администраторския панел. +password_change.text_1 = Паролата за вашия акаунт току-що беше променена. +reset_password = Възстановете своя акаунт +account_security_caution.text_1 = Ако това сте били вие, можете спокойно да игнорирате това ел. писмо. +issue.action.force_push = %[1]s изтласка принудително %[2]s от %[3]s към %[4]s. +team_invite.text_3 = Бележка: Тази покана е предназначена за %[1]s. Ако не сте очаквали тази покана, можете да игнорирате това ел. писмо. +view_it_on = Вижте го на %s +register_notify.text_1 = това е ел. писмо за потвърждение на вашата регистрация в %s! +register_notify.text_2 = Можете да влезете в акаунта си с потребителско име: %s +register_notify.text_3 = Ако някой друг е създал този акаунт за вас, първо ще трябва да зададете парола. +repo.collaborator.added.subject = %s ви добави към %s като сътрудник +primary_mail_change.text_1 = Основният адрес за ел. поща на вашия акаунт току-що беше променен на %[1]s. Това означава, че този адрес за ел. поща повече няма да получава известия по ел. поща за вашия акаунт. +team_invite.text_2 = Моля, щракнете върху следната връзка, за да се присъедините към екипа: +repo.transfer.body = За да го приемете или отхвърлите, посетете %s или просто го игнорирайте. +repo.collaborator.added.text = Бяхте добавени като сътрудник в хранилище: +team_invite.subject = %[1]s ви покани да се присъедините към организацията %[2]s +team_invite.text_1 = %[1]s ви покани да се присъедините към екип %[2]s в организация %[3]s. +reply = или отговорете директно на това ел. писмо +reset_password.text = Ако това сте вие, моля, щракнете върху следната връзка, за да възстановите акаунта си в рамките на %s: +primary_mail_change.subject = Основният ви адрес за ел. поща е променен +account_security_caution.text_2 = Ако това не сте били вие, акаунтът ви е компрометиран. Моля, свържете се с администраторите на този сайт. [user] joined_on = Присъединени на %s @@ -1493,7 +1810,7 @@ follow = Последване followers_few = %d последователи block_user = Блокиране на потребителя change_avatar = Променете профилната си снимка… -email_visibility.limited = Вашият адрес на ел. поща е видим за всички удостоверени потребители +email_visibility.limited = Вашият адрес за ел. поща е видим за всички удостоверени потребители disabled_public_activity = Този потребител е изключил публичната видимост на дейността. email_visibility.private = Вашият адрес на ел. поща е видим само за вас и администраторите show_on_map = Показване на това място на картата @@ -1507,6 +1824,15 @@ public_activity.visibility_hint.self_public = Вашата дейност е в form.name_pattern_not_allowed = Шаблонът "%s" не е разрешен в потребителско име. form.name_reserved = Потребителското име "%s" е резервирано. public_activity.visibility_hint.self_private_profile = Вашата дейност е видима само за вас и администраторите на инстанцията, тъй като вашият профил е частен. Конфигуриране. +block_user.detail = Моля, имайте предвид, че блокирането на потребител има и други ефекти, като например: +block_user.detail_2 = Този потребител няма да може да взаимодейства с хранилищата, които притежавате, или със задачите и коментарите, които сте създали. +block_user.detail_3 = Няма да можете да се добавяте един друг като сътрудници на хранилище. +public_activity.visibility_hint.self_private = Вашата дейност е видима само за вас и администраторите на инстанцията. Конфигуриране. +form.name_chars_not_allowed = Потребителското име „%s“ съдържа невалидни знаци. +public_activity.visibility_hint.admin_private = Тази дейност е видима за вас, защото сте администратор, но потребителят иска тя да остане частна. +public_activity.visibility_hint.admin_public = Тази дейност е видима за всички, но като администратор можете да виждате и взаимодействия в частни пространства. +follow_blocked_user = Не можете да последвате този потребител, защото сте го блокирали или той ви е блокирал. +block_user.detail_1 = Ще спрете да се следвате един друг и няма да можете да се последвате отново. [home] filter = Други филтри @@ -1530,6 +1856,7 @@ view_home = Преглед на %s collaborative_repos = Съвместни хранилища switch_dashboard_context = Превключване на контекста на таблото show_only_public = Показване само на публични +filter_by_team_repositories = Филтриране по хранилища на екипа [admin] packages.version = Версия @@ -1587,7 +1914,7 @@ config.server_config = Сървърна конфигурация packages.size = Размер settings = Админ. настройки users = Потребителски акаунти -emails.duplicate_active = Този адрес на ел. поща вече е активен за друг потребител. +emails.duplicate_active = Този адрес за ел. поща вече е активен за друг потребител. config.app_ver = Forgejo версия config.custom_conf = Път на конфигурационния файл config.git_version = Git версия @@ -1606,16 +1933,20 @@ users.details = Потребителски данни packages.total_size = Общ размер: %s dashboard.new_version_hint = Forgejo %s вече е наличен, вие изпълнявате %s. Проверете блога за повече подробности. total = Общо: %d +config.db_type = Тип +monitor.queue.type = Тип +notices.type = Тип [error] not_found = Целта не може да бъде намерена. report_message = Ако смятате, че това е грешка на Forgejo, моля, потърсете в задачите на Codeberg или отворете нова задача, ако е необходимо. network_error = Мрежова грешка occurred = Възникна грешка +server_internal = Вътрешна грешка на сървъра [form] UserName = Потребителско име -Email = Адрес на ел. поща +Email = Адрес за ел. поща Password = Парола RepoName = Име на хранилището username_been_taken = Потребителското име вече е заето. @@ -1633,8 +1964,8 @@ url_error = `„%s“ не е валиден URL.` Content = Съдържание team_not_exist = Екипът не съществува. TeamName = Име на екипа -email_error = ` не е валиден адрес на ел. поща.` -email_invalid = Адресът на ел. поща е невалиден. +email_error = ` не е валиден адрес за ел. поща.` +email_invalid = Адресът за ел. поща е невалиден. SSHTitle = Име на SSH ключ repo_name_been_taken = Името на хранилището вече е използвано. team_name_been_taken = Името на екипа вече е заето. @@ -1647,6 +1978,44 @@ Pronouns = Местоимения Biography = Биография Website = Уебсайт Location = Местоположение +cannot_add_org_to_team = Организация не може да бъде добавена като член на екип. +auth_failed = Неуспешно удостоверяване: %v +team_no_units_error = Разрешете достъп до поне една секция на хранилището. +password_uppercase_one = Поне един голям знак +CommitSummary = Обобщение на подаването +username_error = ` може да съдържа само буквено-цифрови знаци („0-9“, „a-z“, „A-Z“), тире („-“), долна черта („_“) и точка („.“). Не може да започва или завършва с не-буквено-цифрови знаци, като също така са забранени и последователни не-буквено-цифрови знаци.` +username_error_no_dots = ` може да съдържа само буквено-цифрови знаци („0-9“, „a-z“, „A-Z“), тире („-“) и долна черта („_“). Не може да започва или завършва с не-буквено-цифрови знаци, като също така са забранени и последователни не-буквено-цифрови знаци.` +duplicate_invite_to_team = Потребителят вече е поканен като член на екипа. +must_use_public_key = Ключът, който предоставихте, е частен ключ. Моля, не качвайте частния си ключ никъде. Вместо това използвайте публичния си ключ. +org_still_own_packages = Тази организация все още притежава един или повече пакети, първо ги изтрийте. +admin_cannot_delete_self = Не можете да изтриете себе си, когато сте администратор. Моля, първо премахнете администраторските си привилегии. +To = Име на клон +CommitMessage = Съобщение на подаването +include_error = ` трябва да съдържа подниз „%s“.` +alpha_dash_error = ` трябва да съдържа само буквено-цифрови знаци, тире („-“) и долна черта („_“).` +alpha_dash_dot_error = ` трябва да съдържа само буквено-цифрови знаци, тире („-“), долна черта („_“) и точка („.“).` +size_error = ` трябва да е с размер %s.` +min_size_error = ` трябва да съдържа поне %s знака.` +max_size_error = ` трябва да съдържа най-много %s знака.` +invalid_group_team_map_error = ` съпоставянето е невалидно: %s` +password_complexity = Паролата не отговаря на изискванията за сложност: +password_lowercase_one = Поне един малък знак +password_digit_one = Поне една цифра +password_special_one = Поне един специален знак (препинателни знаци, скоби, кавички и др.) +enterred_invalid_repo_name = Името на хранилището, което въведохте, е неправилно. +enterred_invalid_org_name = Името на организацията, което въведохте, е неправилно. +enterred_invalid_password = Паролата, която въведохте, е неправилна. +organization_leave_success = Успешно напуснахте организацията %s. +still_has_org = Вашият акаунт е член на една или повече организации, първо ги напуснете. +org_still_own_repo = Тази организация все още притежава едно или повече хранилища, първо ги изтрийте или прехвърлете. +target_branch_not_exist = Целевият клон не съществува. +glob_pattern_error = ` glob шаблонът е невалиден: %s.` +openid_been_used = OpenID адресът „%s“ вече е използван. +unknown_error = Неизвестна грешка: +TreeName = Път до файла +AdminEmail = Администраторски адрес за ел. поща +email_domain_is_not_allowed = Домейнът на адреса за ел. поща на потребителя %s е в конфликт с EMAIL_DOMAIN_ALLOWLIST или EMAIL_DOMAIN_BLOCKLIST. Уверете се, че сте въвели правилно адреса за ел. поща. +email_been_used = Адресът за ел. поща вече се използва. [action] close_issue = `затвори задача %[3]s#%[2]s` @@ -1709,11 +2078,38 @@ sign_up_button = Регистрирайте се. back_to_sign_in = Назад към Вход sign_in_openid = Продължаване с OpenID send_reset_mail = Изпращане на ел. писмо за възстановяване +authorize_application = Упълномощаване на приложение +password_pwned_err = Неуспешно завършване на заявката към HaveIBeenPwned +last_admin = Не можете да премахнете последния администратор. Трябва да има поне един администратор. +allow_password_change = Изискване потребителят да смени паролата си (препоръчително) +authorize_title = Упълномощавате ли „%s“ да има достъп до вашия акаунт? +reset_password_mail_sent_prompt = Изпратено е ел. писмо за потвърждение до %s. За да завършите процеса по възстановяване на акаунта, моля, проверете входящата си поща и последвайте предоставената връзка в рамките на следващите %s. +reset_password_wrong_user = Вие сте влезли като %s, но връзката за възстановяване на акаунта е предназначена за %s +authorize_redirect_notice = Ще бъдете пренасочени към %s, ако упълномощите това приложение. +authorize_application_description = Ако предоставите достъп, то ще може да осъществява достъп и да записва цялата информация за вашия акаунт, включително частни хранилища и организации. +twofa_scratch_used = Използвали сте своя резервен код. Пренасочени сте към страницата с настройки за двуфакторно удостоверяване, за да можете да премахнете регистрацията на устройството си или да генерирате нов резервен код. +reset_password_helper = Възстановяване на акаунт +invalid_password = Вашата парола не съвпада с паролата, използвана за създаване на акаунта. +invalid_code = Вашият код за потвърждение е невалиден или е изтекъл. +invalid_code_forgot_password = Вашият код за потвърждение е невалиден или е изтекъл. Щракнете тук, за да започнете нова сесия. +scratch_code = Резервен код +use_scratch_code = Използвайте резервен код +use_onetime_code = Използвайте еднократен код +twofa_scratch_token_incorrect = Вашият резервен код е неправилен. +authorize_application_created_by = Това приложение е създадено от %s. +authorization_failed = Неуспешно упълномощаване +resent_limit_prompt = Вече сте поискали ел. писмо за активация наскоро. Моля, изчакайте 3 минути и опитайте отново. +has_unconfirmed_mail = Здравейте, %s, имате непотвърден адрес за ел. поща (%s). Ако не сте получили ел. писмо за потвърждение или трябва да изпратите ново, моля, щракнете върху бутона по-долу. +change_unconfirmed_email_error = Неуспешна промяна на адреса за ел. поща: %v +resend_mail = Щракнете тук, за повторно изпращане на ел. писмо за активация +change_unconfirmed_email_summary = Промяна на адреса, на който се изпраща ел. писмо за активация. +change_unconfirmed_email = Ако сте въвели грешен адрес за ел. поща по време на регистрацията, можете да го промените по-долу и потвърждение ще бъде изпратено на новия адрес. [aria] footer.software = Относно този софтуер footer.links = Връзки footer = Долен колонтитул +navbar = Навигационна лента [startpage] install = Лесен за инсталиране @@ -1780,6 +2176,7 @@ runs.no_workflows.help_no_write_access = За да научите повече variables.management = Управление на променливи variables.not_found = Променливата не е открита. variables.id_not_exist = Променлива с идентификатор %d не съществува. +runners.owner_type = Тип [heatmap] less = По-малко @@ -1809,9 +2206,10 @@ invalid_input_type = Не можете да качвате файлове от component_loading_failed = Неуспешно зареждане на %s contributors.what = приноси recent_commits.what = скорошни подавания -component_loading = Зареждане на %s... +component_loading = Зареждане на %s… component_loading_info = Това може да отнеме известно време… code_frequency.what = честота на промените +component_failed_to_load = Възникна неочаквана грешка. [projects] type-1.display_name = Индивидуален проект @@ -1820,20 +2218,29 @@ deleted.display_name = Изтрит проект [search] no_results = Няма намерени съответстващи резултати. -team_kind = Търсене на екипи... -repo_kind = Търсене на хранилища... -org_kind = Търсене на организации... -user_kind = Търсене на потребители... -code_kind = Търсене на код... -commit_kind = Търсене на подавания... -project_kind = Търсене на проекти... -package_kind = Търсене на пакети... -search = Търсене... -branch_kind = Търсене на клонове... -pull_kind = Търсене на заявки за сливане... -issue_kind = Търсене на задачи... +team_kind = Търсене на екипи… +repo_kind = Търсене на хранилища… +org_kind = Търсене на организации… +user_kind = Търсене на потребители… +code_kind = Търсене на код… +commit_kind = Търсене на подавания… +project_kind = Търсене на проекти… +package_kind = Търсене на пакети… +search = Търсене… +branch_kind = Търсене на клонове… +pull_kind = Търсене на заявки за сливане… +issue_kind = Търсене на задачи… fuzzy = Приблизително exact = Прецизно +regexp = Регекс +regexp_tooltip = Третиране на термина за търсене като регулярен израз +fuzzy_tooltip = Включване на резултати, които също съвпадат приблизително с термина за търсене +exact_tooltip = Включване само на резултати, които съвпадат точно с термина за търсене +code_search_unavailable = Търсенето на код в момента не е достъпно. Моля, свържете се с администратора на сайта. +keyword_search_unavailable = Търсенето по ключова дума в момента не е достъпно. Моля, свържете се с администратора на сайта. +union_tooltip = Включване на резултати, които съвпадат с някоя от ключовите думи, разделени с интервал +union = Обединение +type_tooltip = Тип търсене [markup] filepreview.lines = Редове от %[1]d до %[2]d в %[3]s diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 7a2562094d..9448dd8e7e 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -493,11 +493,11 @@ use_onetime_code = Použít jednorázový kód view_it_on=Zobrazit na %s reply=nebo přímo odpovědět na tento e-mail link_not_working_do_paste=Odkaz nefunguje? Zkuste jej zkopírovat a vložit do adresního řádku svého prohlížeče. -hi_user_x=Ahoj %s, +hi_user_x=Dobrý den, uživateli %s, activate_account=Prosíme, aktivujte si váš účet activate_account.title=%s, prosím aktivujte si váš účet -activate_account.text_1=Ahoj %[1]s, děkujeme za registraci na %[2]s! +activate_account.text_1=Dobrý den, uživateli %[1]s, děkujeme za registraci ve službě %[2]s! activate_account.text_2=Pro aktivaci vašeho účtu klikněte %s na následující odkaz : activate_email=Ověřte vaši e-mailovou adresu @@ -932,7 +932,7 @@ generate_new_token=Vygenerovat nový token tokens_desc=Tyto tokeny umožňují přístup k vašemu účtu pomocí Forgejo API. token_name=Název tokenu generate_token=Vygenerovat token -generate_token_success=Nový token byl vygenerován. Zkopírujte jej nyní, jelikož již nebude znovu zobrazen. +generate_token_success=Nový token byl vygenerován. Zkopírujte si jej nyní, jelikož již nebude znovu zobrazen. generate_token_name_duplicate=%s byl již použit jako název aplikace. Použijte prosím nový. delete_token=Smazat access_token_deletion=Odstranit přístupový token @@ -2922,6 +2922,7 @@ settings.event_action_success = Úspěch settings.event_action_success_desc = Běh akce byl úspěšný. settings.event_header_action = Události běhu akce settings.event_action_recover_desc = Běh akce byl úspěšný, předchozí běh akce ve stejném workflow selhal. +issues.filter_type.all_pull_requests = Všechny žádosti o sloučení [graphs] component_loading_info = Tohle může chvíli trvat… diff --git a/options/locale/locale_da.ini b/options/locale/locale_da.ini index 6ec3a10e28..ea22f49e77 100644 --- a/options/locale/locale_da.ini +++ b/options/locale/locale_da.ini @@ -2735,6 +2735,7 @@ settings.event_action_success = Success settings.event_action_recover_desc = Handlingskørsel lykkedes efter at den sidste handlingskørsel i samme arbejdsgang mislykkedes. settings.event_action_failure_desc = Handlingskørsel sluttede som en fejl. settings.event_action_recover = Gendan +issues.filter_type.all_pull_requests = Alle pull-anmodninger [notification] watching = Overvåger diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index dffa9eb3d4..829912b3cc 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -2917,6 +2917,14 @@ comment.blocked_by_user = Kommentieren ist nicht möglich, da du vom Repository- sync_fork.branch_behind_one = Dieser Branch ist %[1]d Commit hinter %[2]s sync_fork.branch_behind_few = Dieser Branch ist %[1]d Commits hinter %[2]s sync_fork.button = Sync +settings.event_action_failure_desc = Action-Run endete im Fehlschlag. +settings.event_action_success_desc = Action-Run war erfolgreich. +settings.event_action_failure = Fehlschlag +settings.event_action_success = Erfolg +settings.event_header_action = Action-Run-Ereignisse +settings.event_action_recover_desc = Action-Run war erfolgreich, nachdem der letzte Action-Run im selben Arbeitsablauf fehlgeschlagen ist. +settings.event_action_recover = Wiederherstellen +issues.filter_type.all_pull_requests = Alle Pull-Requests [graphs] component_loading_failed = Konnte %s nicht laden diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 7479ab80af..5fd2ebd163 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -768,8 +768,8 @@ update_profile_success = Your profile has been updated. change_username = Your username has been changed. change_username_prompt = Note: Changing your username also changes your account URL. change_username_redirect_prompt = The old username will redirect until someone claims it. -change_username_redirect_prompt.with_cooldown.one = The old username will be available to everyone after a cooldown period of %[1]d day, you can still reclaim the old username during the cooldown period. -change_username_redirect_prompt.with_cooldown.few = The old username will be available to everyone after a cooldown period of %[1]d days, you can still reclaim the old username during the cooldown period. +change_username_redirect_prompt.with_cooldown.one = The old username will be available to everyone after a cooldown period of %[1]d day. You can still reclaim the old username during the cooldown period. +change_username_redirect_prompt.with_cooldown.few = The old username will be available to everyone after a cooldown period of %[1]d days. You can still reclaim the old username during the cooldown period. continue = Continue cancel = Cancel language = Language @@ -1610,7 +1610,7 @@ issues.remove_ref_at = `removed reference %s %s` issues.add_ref_at = `added reference %s %s` issues.delete_branch_at = `deleted branch %s %s` issues.filter_label = Label -issues.filter_label_exclude = `Use alt + click/enter to exclude labels` +issues.filter_label_exclude = Use Alt + Click to exclude labels issues.filter_label_no_select = All labels issues.filter_label_select_no_label = No label issues.filter_milestone = Milestone @@ -1628,6 +1628,7 @@ issues.filter_poster = Author issues.filter_poster_no_select = All authors issues.filter_type = Type issues.filter_type.all_issues = All issues +issues.filter_type.all_pull_requests = All pull requests issues.filter_type.assigned_to_you = Assigned to you issues.filter_type.created_by_you = Created by you issues.filter_type.mentioning_you = Mentioning you @@ -1693,15 +1694,13 @@ issues.close_comment_issue = Close with comment issues.reopen_issue = Reopen issues.reopen_comment_issue = Reopen with comment issues.create_comment = Comment -issues.closed_at = `closed this issue %[2]s` -issues.reopened_at = `reopened this issue %[2]s` -issues.commit_ref_at = `referenced this issue from a commit %[2]s` -issues.ref_issue_from = `referenced this issue %[4]s %[2]s` -issues.ref_pull_from = `referenced this pull request %[4]s %[2]s` -issues.ref_closing_from = `referenced this issue from a pull request %[4]s that will close it, %[2]s` -issues.ref_reopening_from = `referenced this issue from a pull request %[4]s that will reopen it, %[2]s` -issues.ref_closed_from = `closed this issue %[4]s %[2]s` -issues.ref_reopened_from = `reopened this issue %[4]s %[2]s` +issues.closed_at = `closed this issue %s` +issues.reopened_at = `reopened this issue %s` +issues.commit_ref_at = `referenced this issue from a commit %s` +issues.ref_issue_from = `referenced this issue %[3]s %[1]s` +issues.ref_pull_from = `referenced this pull request %[3]s %[1]s` +issues.ref_closing_from = `referenced this issue from a pull request %[3]s that will close it, %[1]s` +issues.ref_reopening_from = `referenced this issue from a pull request %[3]s that will reopen it, %[1]s` issues.ref_from = `from %[1]s` issues.author = Author issues.author.tooltip.issue = This user is the author of this issue. @@ -2013,9 +2012,9 @@ pulls.update_branch_success = Branch update was successful pulls.update_not_allowed = You are not allowed to update branch pulls.outdated_with_base_branch = This branch is out-of-date with the base branch pulls.close = Close pull request -pulls.closed_at = `closed this pull request %[2]s` -pulls.reopened_at = `reopened this pull request %[2]s` -pulls.commit_ref_at = `referenced this pull request from a commit %[2]s` +pulls.closed_at = `closed this pull request %s` +pulls.reopened_at = `reopened this pull request %s` +pulls.commit_ref_at = `referenced this pull request from a commit %s` pulls.cmd_instruction_hint = View command line instructions pulls.cmd_instruction_checkout_title = Checkout pulls.cmd_instruction_checkout_desc = From your project repository, check out a new branch and test the changes. @@ -2932,8 +2931,8 @@ settings.update_settings = Update settings settings.update_setting_success = Organization settings have been updated. settings.change_orgname_prompt = Note: Changing the organization name will also change your organization's URL and free the old name. settings.change_orgname_redirect_prompt = The old name will redirect until it is claimed. -settings.change_orgname_redirect_prompt.with_cooldown.one = The old organization name will be available to everyone after a cooldown period of %[1]d day, you can still reclaim the old name during the cooldown period. -settings.change_orgname_redirect_prompt.with_cooldown.few = The old organization name will be available to everyone after a cooldown period of %[1]d days, you can still reclaim the old name during the cooldown period. +settings.change_orgname_redirect_prompt.with_cooldown.one = The old organization name will be available to everyone after a cooldown period of %[1]d day. You can still reclaim the old name during the cooldown period. +settings.change_orgname_redirect_prompt.with_cooldown.few = The old organization name will be available to everyone after a cooldown period of %[1]d days. You can still reclaim the old name during the cooldown period. settings.update_avatar_success = The organization's avatar has been updated. settings.delete = Delete organization settings.delete_account = Delete this organization diff --git a/options/locale/locale_fil.ini b/options/locale/locale_fil.ini index 6d91cc7877..7d1405f633 100644 --- a/options/locale/locale_fil.ini +++ b/options/locale/locale_fil.ini @@ -1500,7 +1500,7 @@ issues.content_history.created = ginawa editor.patching = Pina-patch: editor.fail_to_apply_patch = Hindi malapat ang patch na "%s" settings.danger_zone = Mapanganib na lugar -issues.closed_at = `isinara ang isyung %[2]s` +issues.closed_at = `isinara ang isyung ito %[2]s` settings.collaboration.admin = Tagapangasiwa settings.admin_settings = Mga setting ng tagapangasiwa issues.start_tracking_history = `sinimulan ang trabaho %s` @@ -2773,6 +2773,13 @@ comment.blocked_by_user = Hindi posible ang pagkomento dahil hinarang ka ng may- sync_fork.button = I-sync sync_fork.branch_behind_one = Ang branch na ito ay %[1]d commit sa likod ng %[2]s sync_fork.branch_behind_few = Ang branch na ito ay %[1]d mga commit sa likod ng %[2]s +settings.event_header_action = Mga event sa run ng aksyon +settings.event_action_failure = Pagkabigo +settings.event_action_failure_desc = Natapos ang action run bilang pagkabigo. +settings.event_action_recover = I-recover +settings.event_action_success = Matagumpay +settings.event_action_success_desc = Matagumpay na natapos ang Action Run. +settings.event_action_recover_desc = Matagumpay na natapos ang Action Run pagkatapos na nabigo ang huling Action Run sa katulad na workflow. [search] commit_kind = Maghanap ng mga commit… diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index 3deaee8483..3fcfda18bd 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -9,7 +9,7 @@ sign_in_with_provider=Se connecter avec %s sign_in_or=ou sign_out=Déconnexion sign_up=S'inscrire -link_account=Lier un Compte +link_account=Lier un compte register=S'inscrire version=Version powered_by=Propulsé par %s @@ -1516,7 +1516,7 @@ issues.desc=Organiser les rapports de bug, les tâches et les jalons. issues.filter_assignees=Filtrer par assignation issues.filter_milestones=Filtrer le jalon issues.filter_projects=Filtrer par projet -issues.filter_labels=Filtrer par labels +issues.filter_labels=Filtrer par étiquettes issues.filter_reviewers=Filtrer par évaluateur issues.new=Nouveau ticket issues.new.title_empty=Le titre ne peut pas être vide @@ -2920,6 +2920,7 @@ settings.event_header_action = Événements d'exécution d'action settings.event_action_success_desc = L'exécution de l'action a réussi. settings.event_action_failure_desc = L'exécution de l'action a échoué. settings.event_action_recover_desc = L'exécution de l'action a réussi après l'échec de la dernière exécution de l'action dans le même workflow. +issues.filter_type.all_pull_requests = Toutes les demandes d'ajout [graphs] component_loading = Chargement %s… @@ -4090,4 +4091,4 @@ issues.write = Écrire : Fermer des tickets et gérer les métadonnées t pulls.read = Lire : Lire et créer des demandes de tirage. [translation_meta] -test = Ceci est une chaîne de test. Elle n'est pas affichée dans l'interface de Forgejo mais est utilisée à des fins de test. N'hésitez pas à entrer 'ok' pour gagner du temps (ou un fait amusant de votre choix) pour atteindre ce doux 100 % de complétion. :-) +test = Ceci est une chaîne de test. Elle n'est pas affichée dans l'interface de Forgejo mais est utilisée à des fins de test. N'hésitez pas à entrer 'ok' pour gagner du temps (ou un fait amusant de votre choix) pour atteindre ce difficile 100 % de complétion. :-) diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 68e2f978ac..789871f3c3 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -2913,6 +2913,14 @@ comment.blocked_by_user = Piebilžu pievienošana nav iespējama, jo glabātavas sync_fork.branch_behind_one = Šis zars ir %[1]d iesūtījumu aiz %[2]s sync_fork.button = Sinhronizēt sync_fork.branch_behind_few = Šis zars ir %[1]d iesūtījumus aiz %[2]s +settings.event_action_failure = Kļūme +settings.event_action_failure_desc = Darbības izpilde beidzās ar kļūmi. +settings.event_header_action = Darbības izpildes notikumi +settings.event_action_recover = Atgūt +settings.event_action_recover_desc = Darbības izpilde bija sekmīga pēc kļūmes iepriekšējā darbības izpildē tajā pašā darbplūsmā. +settings.event_action_success = Sekmīgi +settings.event_action_success_desc = Darbības izpilde bija sekmīga. +issues.filter_type.all_pull_requests = Visi izmaiņu pieprasījumi [graphs] component_loading=Ielādē %s… diff --git a/options/locale/locale_nds.ini b/options/locale/locale_nds.ini index 2175e11b02..57985942ed 100644 --- a/options/locale/locale_nds.ini +++ b/options/locale/locale_nds.ini @@ -2621,6 +2621,7 @@ settings.event_action_recover = Verhaalt settings.event_header_action = Aktioons-Loop-Vörfallen settings.event_action_failure_desc = Aktioons-Loop is as fehlslagen ennt. settings.event_action_recover_desc = Aktioons-Loop is daankregen worden, nadeem de leste Aktioons-Loop in de sülven Warkwies fehlslagen is. +issues.filter_type.all_pull_requests = All Haalvörslagen [repo.permissions] code.read = Lesen: De Quelltext vun deesem Repositorium ankieken un klonen. diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 70dc813782..26bdd35420 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -2913,6 +2913,13 @@ comment.blocked_by_user = Não é possível comentar pois você foi bloqueado pe sync_fork.branch_behind_few = Este branch está %[1]d commits atrás de %[2]s sync_fork.branch_behind_one = Este branch está %[1]d commit atrás de %[2]s sync_fork.button = Sincronizar +settings.event_header_action = Eventos de execução de Actions +settings.event_action_failure = Falha +settings.event_action_failure_desc = Execução da Action terminou com falha. +settings.event_action_recover = Recuperar +settings.event_action_recover_desc = A execução da Action teve sucesso após a última execução no mesmo workflow ter falhado. +settings.event_action_success = Sucesso +settings.event_action_success_desc = A execução da Action foi bem sucedida. [graphs] component_loading = Carregando %s… diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index f353dcca77..7f36d164b3 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -31,7 +31,7 @@ username=Nome de utilizador email=Endereço de email password=Senha access_token=Código de acesso -re_type=Confirme a senha +re_type=Confirme a palavra-passe captcha=CAPTCHA twofa=Autenticação com dois passos twofa_scratch=Código de uso único em dois passos @@ -2915,6 +2915,13 @@ comment.blocked_by_user = Não é possível comentar porque está bloqueado pelo sync_fork.branch_behind_few = Este ramo está %[1]d cometimentos atrás de %[2]s sync_fork.button = Sincronizar sync_fork.branch_behind_one = Este ramo está %[1]d cometimento atrás de %[2]s +settings.event_action_failure = Falha +settings.event_action_failure_desc = A execução da ação terminou com falha. +settings.event_action_recover = Recuperar +settings.event_header_action = Eventos da execução de ações +settings.event_action_recover_desc = A execução de ação foi bem sucedida depois da última execução de ação na mesma sequência de trabalho ter falhado. +settings.event_action_success = Sucesso +settings.event_action_success_desc = A Execução de ação foi bem sucedida. [graphs] component_loading=A carregar %s… diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 8e310eaf28..9158329978 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -1408,7 +1408,7 @@ editor.fail_to_update_file=Не удалось обновить/создать editor.fail_to_update_file_summary=Ошибка: editor.push_rejected_no_message=Изменение отклонено сервером без сообщения. Пожалуйста, проверьте Git-хуки. editor.push_rejected=Изменение отклонено сервером. Пожалуйста, проверьте Git-хуки. -editor.push_rejected_summary=Причина отклонения: +editor.push_rejected_summary=Полная причина отклонения: editor.add_subdir=Добавить каталог… editor.unable_to_upload_files=Не удалось загрузить файлы в «%s» из-за ошибки: %v editor.upload_file_is_locked=Файл «%s» заблокирован %s. @@ -1450,7 +1450,7 @@ commit.cherry-pick-content=Выбрать ветвь для переноса: commitstatus.error=Ошибка commitstatus.failure=Неудача commitstatus.pending=Ожидание -commitstatus.success=Успешно +commitstatus.success=Успех ext_issues=Внешние задачи ext_issues.desc=Ссылка на внешнюю систему отслеживания задач. @@ -1545,27 +1545,27 @@ issues.add_labels=добавлены метки %s %s issues.remove_label=удалил(а) метку %s %s issues.remove_labels=удалил(а) метки %s %s issues.add_remove_labels=добавлены метки %s и убраны метки %s %s -issues.add_milestone_at=`добавлено в этап %s %s` -issues.add_project_at=`добавлено в проект %s %s` -issues.change_milestone_at=`изменил(а) целевой этап с %s на %s %s` -issues.change_project_at=`изменил(а) проект с %s на %s %s` -issues.remove_milestone_at=`удалил(а) это из этапа %s %s` -issues.remove_project_at=`удалил(а) это из проекта %s %s` +issues.add_milestone_at=`добавление в этап %s %s` +issues.add_project_at=`добавление в проект %s %s` +issues.change_milestone_at=`этап изменён с %s на %s %s` +issues.change_project_at=`проект изменён с %s на %s %s` +issues.remove_milestone_at=`удаление из этапа %s %s` +issues.remove_project_at=`удаление из проекта %s %s` issues.deleted_milestone=`(удалено)` issues.deleted_project=`(удалено)` -issues.self_assign_at=`назначил(а) на себя %s` -issues.add_assignee_at=`был(а) назначен(а) %s %s` -issues.remove_assignee_at=`был снят с назначения %s %s` -issues.remove_self_assignment=`убрал(а) их назначение %s` -issues.change_title_at=`изменил(а) заголовок с %s на %s %s` -issues.change_ref_at=`изменил(а) ссылку с %s на %s %s` -issues.remove_ref_at=`убрал(а) ссылку %s %s` -issues.add_ref_at=`добавлена ссылка %s %s` +issues.self_assign_at=`назначение себя %s` +issues.add_assignee_at=`назначение %s %s` +issues.remove_assignee_at=`снятие с назначения %s %s` +issues.remove_self_assignment=`снято назначение с себя %s` +issues.change_title_at=`заголовок изменён с %s на %s %s` +issues.change_ref_at=`изменена ссылка с %s на %s %s` +issues.remove_ref_at=`убрана ссылка на %s %s` +issues.add_ref_at=`добавлена ссылка на %s %s` issues.delete_branch_at=`удалена ветвь %s %s` -issues.filter_label=Метка -issues.filter_label_exclude=`Используйте alt + click/enter, чтобы исключить метки` -issues.filter_label_no_select=Все метки -issues.filter_label_select_no_label=Нет метки +issues.filter_label=Метки +issues.filter_label_exclude=`Исключайте метки с помощью alt + лкм/enter` +issues.filter_label_no_select=Любые метки +issues.filter_label_select_no_label=Без меток issues.filter_milestone=Этап issues.filter_milestone_all=Все этапы issues.filter_milestone_none=Нет этапов @@ -2916,6 +2916,14 @@ comment.blocked_by_user = Комментирование невозможно, sync_fork.branch_behind_few = Эта ветвь отстаёт от %[2]s на %[1]d коммитов sync_fork.button = Синхронизировать sync_fork.branch_behind_one = Эта ветвь отстаёт от %[2]s на %[1]d коммит +settings.event_header_action = События выполнений Действий +settings.event_action_failure = Неудача +settings.event_action_failure_desc = Выполнение завершилось неудачно. +settings.event_action_recover = Восстановлен +settings.event_action_recover_desc = После неудачи повторное выполнение рабочего потока было успешно. +settings.event_action_success = Успех +settings.event_action_success_desc = Выполнение завершилось успешно. +issues.filter_type.all_pull_requests = Все запросы на слияние [graphs] component_loading_failed = Не удалось загрузить %s @@ -3914,10 +3922,10 @@ unit.desc=Управление встроенными конвейерами CI/ status.unknown=Неизвестно status.waiting=Ожидает status.running=Запущено -status.success=Успешно +status.success=Успех status.failure=Неудача status.cancelled=Отменено -status.skipped=Пропущено +status.skipped=Пропущен status.blocked=Заблокировано runners=Исполнители @@ -3938,7 +3946,7 @@ runners.task_list.run=Запуск runners.task_list.status=Состояние runners.task_list.repository=Репозиторий runners.task_list.commit=Коммит -runners.task_list.done_at=Время завершения +runners.task_list.done_at=Завершено runners.edit_runner=Изменить исполнитель runners.update_runner=Обновить изменения runners.update_runner_success=Исполнитель успешно обновлён @@ -3960,7 +3968,7 @@ runners.reset_registration_token_success=Токен регистрации ис runs.all_workflows=Все рабочие потоки runs.commit=коммит runs.scheduled=Запланировано -runs.pushed_by=отправлено +runs.pushed_by=отправлен runs.invalid_workflow_helper=Файл конфигурации рабочего потока некорректен. Пожалуйста, проверьте конфигурационный файл: %s runs.actor=Автор runs.status=Состояние diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index a4999e9751..541acbf408 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -201,6 +201,9 @@ table_modal.placeholder.content = Innehåll table_modal.label.rows = Rader table_modal.label.columns = Kolumner buttons.switch_to_legacy.tooltip = Använd legacy-redigeraren istället +link_modal.url = Url +link_modal.description = Beskrivning +link_modal.header = Lägg till en länk [filter] string.asc = A - Ö @@ -329,6 +332,7 @@ invalid_app_data_path = Sökvägen för appdata är ogiltig: %v internal_token_failed = Misslyckades att generera intern token: %v password_algorithm = Hashalgoritm för lösenord invalid_password_algorithm = Ogiltig hashalgoritm för lösenord +env_config_keys_prompt = Följande miljövariabler kommer också att tillämpas på din konfigurationsfil: [home] uname_holder=Användarnamn eller e-postadress @@ -462,6 +466,41 @@ reply = eller svara på detta e-postmeddelande direkt hi_user_x = Hej %s, admin.new_user.user_info = Användarinformation admin.new_user.text = Vänligen klicka här för att hantera denna användare från administratörspanelen. +admin.new_user.subject = Ny användare %s har just registrerat sig +totp_disabled.no_2fa = Det finns inga andra 2FA-metoder konfigurerade längre, vilket innebär att det inte längre är nödvändigt att logga in på ditt konto med 2FA. +removed_security_key.text_1 = Säkerhetsnyckeln ”%[1]s” har just tagits bort från ditt konto. +repo.transfer.to_you = dig +repo.transfer.body = För att acceptera eller avvisa det, besök %s eller ignorera det helt enkelt. +removed_security_key.no_2fa = Det finns inga andra 2FA-metoder konfigurerade längre, vilket innebär att det inte längre är nödvändigt att logga in på ditt konto med 2FA. +release.note = Notera: +totp_enrolled.subject = Du har aktiverat TOTP som 2FA-metod +totp_enrolled.text_1.no_webauthn = Du har just aktiverat TOTP för ditt konto. Det innebär att du måste använda TOTP som 2FA-metod vid alla framtida inloggningar på ditt konto. +totp_enrolled.text_1.has_webauthn = Du har just aktiverat TOTP för ditt konto. Det innebär att du vid alla framtida inloggningar på ditt konto kan använda TOTP som 2FA-metod eller någon av dina säkerhetsnycklar. +link_not_working_do_paste = Fungerar inte länken? Prova att kopiera och klistra in den i webbläsarens adressfält. +primary_mail_change.text_1 = Den primära e-postadressen för ditt konto har just ändrats till %[1]s. Det innebär att denna e-postadress inte längre kommer att ta emot e-postmeddelanden för ditt konto. +totp_disabled.subject = TOTP har inaktiverats +totp_disabled.text_1 = Tidsbaserat engångslösenord (TOTP) på ditt konto har just inaktiverats. +account_security_caution.text_2 = Om detta inte var du, har ditt konto blivit kompromitterat. Kontakta administratören för denna webbplats. +account_security_caution.text_1 = Om detta var du, kan du tryggt ignorera detta meddelande. +activate_account.text_2 = Klicka på följande länk för att aktivera ditt konto inom %s: +activate_email.text = Klicka på följande länk för att verifiera din e-postadress inom %s: +register_notify.text_3 = Om någon annan har skapat det här kontot åt dig måste du först ställa in ditt lösenord. +issue.x_mentioned_you = @%s2 nämnde dig: +repo.collaborator.added.subject = %s har lagt till dig som medarbetare i %s +repo.collaborator.added.text = Du har lagts till som medarbetare i förrådet: +team_invite.subject = %[1]s har bjudit in dig att gå med i organisationen %[2]s +register_notify.text_1 = detta är din registreringsbekräftelse via e-post för %s! +release.downloads = Hämtningar: +release.download.zip = Källkod (ZIP) +release.download.targz = Källkod (TAR.GZ) +repo.transfer.subject_to = %s vill överföra förrådet ”%s” till %s +removed_security_key.subject = En säkerhetsnyckel har tagits bort +issue_assigned.pull = @%[1] har tilldelat dig pull-begäran %[2]s i förrådet %[3]s. +issue_assigned.issue = @%[1] har tilldelat dig ärendet %[2] i förrådet %[3]. +register_notify.text_2 = Du kan logga in på ditt konto med ditt användarnamn: %s +reset_password.text = Om detta var du, klicka på följande länk för att återställa ditt konto inom %s: +issue.action.force_push = %[1]s2 gjorde en force-push av %[2]s från %[3]s till %[4]s. +repo.transfer.subject_to_you = %s vill överföra förrådet ”%s” till dig @@ -545,6 +584,12 @@ auth_failed=Autentisering misslyckades: %v target_branch_not_exist=Målgrenen finns inte. +org_still_own_repo = Denna organisation äger fortfarande ett eller flera förråd, ta bort eller överför dem först. +must_use_public_key = Den nyckel du angav är en privat nyckel. Skicka inte upp din privata nyckel någonstans. Använd istället din publika nyckel. +unable_verify_ssh_key = SSH-nyckeln kan inte verifieras. Kontrollera att den är korrekt. +still_own_repo = Ditt konto äger ett eller flera förråd, ta bort eller överför dem först. +still_has_org = Ditt konto är medlem i en eller flera organisationer. Lämna dem först. +still_own_packages = Ditt konto har ett eller flera paket, ta bort dem först. [user] @@ -560,6 +605,13 @@ follow=Följ unfollow=Sluta följa user_bio=Biografi disabled_public_activity=Den här användaren har inaktiverat den publika synligheten av aktiviteten. +code = Kod +watched = Övervakade förråd +unblock = Avblockera +email_visibility.limited = Din e-postadress är synlig för alla autentiserade användare +show_on_map = Visa denna plats på en karta +settings = Användarinställningar +block = Blockera [settings] @@ -760,6 +812,17 @@ email_notifications.submit=Ställ in e-postpreferenser visibility.public=Offentlig visibility.private=Privat change_password = Byt lösenord +user_block_success = Användaren har blockerats. +blocked_since = Blockerad sedan %s +user_unblock_success = Användaren har blivit avblockerad. +visibility.limited = Begränsad +visibility.limited_tooltip = Synlig endast för inloggade användare +visibility.private_tooltip = Synlig endast för medlemmar i organisationer som du har gått med i +select_permissions = Välj behörigheter +permission_no_access = Ingen åtkomst +permission_write = Läs och skriv +user_block_yourself = Du kan inte blockera dig själv. +gpg_token_help = Du kan skapa en signatur med hjälp av: [repo] owner=Ägare @@ -1693,6 +1756,21 @@ topic.manage_topics=Hantera ämnen topic.done=Klar topic.count_prompt=Du kan inte välja fler än 25 ämnen settings.enter_repo_name = Ange ägar- och utvecklingskatalog-namnet exakt som det visas: +release = Utgåva +commitstatus.success = Lyckades +visibility_helper = Gör förrådet privat +download_bundle = Hämta BUNDLE +download_zip = Hämta ZIP +download_tar = Hämta TAR.GZ +repo_desc_helper = Ange kort beskrivning (valfritt) +all_branches = Alla grenar +fork_no_valid_owners = Detta förråd kan inte förgrenas eftersom det inte finns några giltiga ägare. +fork_to_different_account = Förgrena till ett annat konto +size_format = %[1]s: %[2]s, %[3]s: %[4]s +already_forked = Du har redan förgrenat %s +commitstatus.failure = Fel +ext_issues = Externa fel +open_with_editor = Öppna med %s @@ -2255,7 +2333,7 @@ project_kind = Sök projekt... search = Sök… type_tooltip = Söktyp team_kind = Sök lag... -org_kind = Sök organisationer... +org_kind = Sök organisationer… issue_kind = Sök ärenden... regexp_tooltip = Tolka söktermen som ett reguljärt uttryck code_search_unavailable = Kodsökning är för närvarande inte tillgänglig. Vänligen kontakta webbplatsadministratören. diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 2f749dfd1e..4f51ddcc7e 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -668,6 +668,7 @@ username_error_no_dots = ` sadece alfanumerik karakterler ("0-9","a-z","A-Z"), t unset_password = Oturum açma kullanıcısı parola belirlemedi. unsupported_login_type = Oturum açma türü hesap silmeyi desteklemiyor. +email_domain_is_not_allowed = Kullanıcı e-posta adresi %s alan adı EMAIL_DOMAIN_ALLOWLIST veya EMAIL_DOMAIN_BLOCKLIST ile çelişiyor. Lütfen işleminizin beklendiğinden emin olun. [user] change_avatar=Profil resmini değiştir… @@ -2163,7 +2164,7 @@ settings.pulls.allow_rebase_update=Değişiklik isteği dalının yeniden yapıl settings.pulls.default_delete_branch_after_merge=Varsayılan olarak birleştirmeden sonra değişiklik isteği dalını sil settings.pulls.default_allow_edits_from_maintainers=Bakımcıların düzenlemelerine izin ver settings.releases_desc=Depo Sürümlerini Etkinleştir -settings.packages_desc=Depo Paket Kütüğünü Etkinleştir +settings.packages_desc=Depo paket kütüğünü etkinleştir settings.projects_desc=Depo Projelerini Etkinleştir settings.actions_desc=Depo İşlemlerini Etkinleştir settings.admin_settings=Yönetici Ayarları @@ -3492,7 +3493,7 @@ error.unit_not_allowed=Bu depo bölümüne erişme izniniz yok. title=Paketler desc=Depo paketlerini yönet. empty=Henüz hiçbir paket yok. -empty.documentation=Paket kütüğü hakkında daha fazla bilgi için, belgeye bakabilirsiniz. +empty.documentation=Paket deposu hakkında daha fazla bilgi için, belgeye bakabilirsiniz. empty.repo=Bir paket yüklediniz ama burada gösterilmiyor mu? Paket ayarlarına gidin ve bu depoya bağlantı verin. registry.documentation=%s kütüğü hakkında daha fazla bilgi için, belgeye bakabilirsiniz. filter.type=Tür @@ -3635,9 +3636,9 @@ owner.settings.cleanuprules.remove.days=Şundan eski sürümleri kaldır owner.settings.cleanuprules.remove.pattern=Eşleşen sürümlari kaldır owner.settings.cleanuprules.success.update=Temizleme kuralı güncellendi. owner.settings.cleanuprules.success.delete=Temizleme kuralı silindi. -owner.settings.chef.title=Chef Kütüğü +owner.settings.chef.title=Chef deposu owner.settings.chef.keypair=Anahtar çifti üret -owner.settings.chef.keypair.description=Chef kütüğünde kimlik doğrulaması için bir anahtar çifti gereklidir. Eğer daha önce bir anahtar çifti ürettiyseniz, yeni bir anahtar çifti üretmek eski anahtar çiftini ıskartaya çıkartacaktır. +owner.settings.chef.keypair.description=Chef kayıt defterine gönderilen istekler kimlik doğrulama yöntemi olarak kriptografik olarak imzalanmalıdır. Bir anahtar çifti oluştururken, yalnızca genel anahtar Forgejo'da saklanır. Özel anahtar size knife ile kullanılmak üzere sağlanır. Yeni bir anahtar çifti oluşturmak öncekini geçersiz kılar. npm.dependencies.bundle = Paketlenmiş Bağımlılıklar rpm.repository.multiple_groups = Bu paket birçok grupta mevcut. diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 569392fafa..2e536c3d1a 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -2668,6 +2668,7 @@ settings.event_action_success = Успіх settings.event_action_recover = Відновлено commitstatus.success = Успіх commitstatus.failure = Збій +issues.filter_type.all_pull_requests = Усі запити на злиття [graphs] contributors.what = внески @@ -2856,12 +2857,12 @@ dashboard.current_memory_usage=Поточне використання пам'я dashboard.total_memory_allocated=Виділено пам'яті загалом dashboard.memory_obtained=Отримано пам'яті dashboard.pointer_lookup_times=Пошуків вказівника -dashboard.memory_allocate_times=Виділення пам'яті +dashboard.memory_allocate_times=Виділень пам'яті dashboard.memory_free_times=Звільнень пам'яті dashboard.current_heap_usage=Поточне використання динамічної пам'яті dashboard.heap_memory_obtained=Отримано динамічної пам'яті -dashboard.heap_memory_idle=Не використовується динамічною пам'яттю -dashboard.heap_memory_in_use=Використовується динамічною пам'яттю +dashboard.heap_memory_idle=Динамічної пам'яті простоює +dashboard.heap_memory_in_use=Динамічної пам'яті використовується dashboard.heap_memory_released=Звільнено динамічної пам'яті dashboard.heap_objects=Об'єктів динамічної пам'яті dashboard.bootstrap_stack_usage=Використання стеку Bootstrap @@ -3231,7 +3232,7 @@ notices.view_detail_header=Переглянути деталі повідомл notices.select_all=Вибрати все notices.deselect_all=Скасувати виділення notices.inverse_selection=Інвертувати виділене -notices.delete_selected=Видалити обране +notices.delete_selected=Видалити вибране notices.delete_all=Видалити всі cповіщення notices.type=Тип notices.type_1=Репозиторій @@ -3625,6 +3626,8 @@ runners.runner_title = Ранер runners.task_list = Нещодавні завдання ранера runners.update_runner_success = Ранер оновлено runners.delete_runner_header = Підтвердіть видалення ранера +runners.status.offline = Неактивний +runners.status.idle = Простоює diff --git a/options/locale/locale_vi.ini b/options/locale/locale_vi.ini index 57e592a209..28577bc3f7 100644 --- a/options/locale/locale_vi.ini +++ b/options/locale/locale_vi.ini @@ -9,7 +9,7 @@ sign_up = Đăng ký link_account = Liên kết tài khoản register = Đăng ký version = Phiên bản -powered_by = Sử dụng %s +powered_by = Được cung cấp bởi %s page = Trang template = Mẫu language = Ngôn ngữ @@ -25,7 +25,7 @@ access_token = Mã truy cập captcha = CAPTCHA twofa = Xác thực hai lớp webauthn_insert_key = Cắm khóa bảo mật của bạn vào -copy_hash = Chép chuỗi băm +copy_hash = Sao chép chuỗi băm sign_in_with_provider = Đăng nhập bằng %s webauthn_press_button = Hãy nhấn nút trên khóa bảo mật… webauthn_use_twofa = Dùng mã xác thực hai lớp ở trên điện thoại @@ -36,7 +36,7 @@ webauthn_error_insecure = WebAuthn chỉ hỗ trợ kết nối mã hóa. Nếu webauthn_error_unable_to_process = Máy chủ không thể xử lý yêu cầu của bạn. webauthn_error_empty = Bạn phải đặt tên cho khóa này. webauthn_error_timeout = Hết thời gian đọc khóa mất rồi. Hãy tải lại trang và thử lại. -copy_type_unsupported = Không chép được +copy_type_unsupported = Không thể sao chép loại tệp này repository = Kho mã organization = Tổ chức new_fork = Tạo một nhánh mới @@ -55,17 +55,17 @@ all = Tất cả sources = Nguồn forks = Các phân nhánh activities = Hoạt động -pull_requests = Yêu cầu thêm mã +pull_requests = Yêu cầu kéo mã save = Lưu -issues = +issues =Vấn đề enabled = Bật disabled = Tắt -copy = Chép -copy_generic = Chép vào bộ nhớ tạm -copy_url = Chép URL -copy_content = Chép nội dung -copy_success = Đã chép! -copy_error = Không chép được +copy = Sao chép +copy_generic = Sao chép vào bộ nhớ tạm +copy_url = Sao chép URL +copy_content = Sao chép nội dung +copy_success = Đã sao chép! +copy_error = Sao chép thất bại write = Viết preview = Xem trước error = Lỗi @@ -73,7 +73,7 @@ error413 = Bạn đã dùng hết định mức. go_back = Quay lại invalid_data = Dữ liệu không hợp lệ: %v never = Không bao giờ -unknown = Không biết +unknown = Không xác định unpin = Bỏ ghim pin = Ghim archived = Đã lưu trữ @@ -81,6 +81,60 @@ signed_in_as = Đăng nhập bằng re_type = Xác nhận mật khẩu webauthn_sign_in = Nhấn nút trên khóa bảo mật, nếu không có nút thì bạn hãy rút ra rồi cắm lại. new_org.link = Tạo tổ chức -error404 = Trang bạn đang tìm không tồn tại hoặc bạn không có quyền xem. +error404 = Trang bạn đang tìm không tồn tại, đã bị xoá hoặc bạn không có quyền để xem nó. edit = Chỉnh sửa -filter = Lọc \ No newline at end of file +filter = Bộ lọc +dashboard = Trang quản lý +logo = Logo +toc = Mục lục +user_profile_and_more = Hồ sơ và cài đặt… +passcode = Mã xác thực +webauthn_error_duplicated = Khóa bảo mật không được phép cho yêu cầu này. Vui lòng đảm bảo rằng khóa chưa được đăng ký trước đó. +mirror = Bản sao +new_mirror = Tạo bản sao mới +your_starred = Đã đánh sao +mirrors = Các bản sao +concept_system_global = Chung +concept_user_individual = Cá nhân +show_log_seconds = Hiện giây +show_full_screen = Toàn màn hình +download_logs = Tải xuống nhật ký +confirm_delete_selected = Xác nhận xoá tất cả mục được chọn? +name = Tên +filter.clear = Xoá bộ lọc +filter.not_fork = Không phải phân nhánh +filter.not_archived = Không bị lưu trữ +filter.is_archived = Bị lưu trữ +filter.is_fork = Phân nhánh +filter.is_mirror = Bản sao +filter.is_template = Mẫu +filter.not_template = Không phải mẫu +filter.public = Công khai +filter.private = Riêng tư +twofa_scratch = Mã xác thực 2 lớp dự phòng +collaborative = Cộng tác +milestones = Cột mốc +cancel = Huỷ bỏ +retry = Thử lại +rerun = Chạy lại +rerun_all = Chạy lại tất cả +ok = Đồng ý +add = Thêm +add_all = Thêm tất cả +remove = Xoá +remove_all = Xoá tất cả +remove_label_str = Xoá "%s" +locked = Bị khoá +copy_branch = Sao chép tên nhánh +loading = Đang tải… +rss_feed = Nguồn RSS +confirm_delete_artifact = Bạn có chắc muốn xoá "%s" ? +value = Giá trị +copy_path = Sao chép đường dẫn +filter.not_mirror = Không phải bản sao +show_timestamps = Hiện mốc thời gian +concept_code_repository = Kho mã +concept_user_organization = Tổ chức + +[search] +search = Tìm kiếm… \ No newline at end of file diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 99ae720866..acdd4c0ced 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -2915,6 +2915,13 @@ comment.blocked_by_user = 您无法评论,因为您已被仓库所有者或作 sync_fork.button = 同步 sync_fork.branch_behind_one = 此分支落后于 %[2]s %[1]d 个提交 sync_fork.branch_behind_few = 此分支落后于 %[2]s %[1]d 个提交 +settings.event_action_failure = 失败 +settings.event_action_recover = 恢复 +settings.event_action_recover_desc = Action运行在同一工作流上次失败后成功。 +settings.event_action_success = 成功 +settings.event_action_success_desc = Action运行以成功结束。 +settings.event_action_failure_desc = Action运行以失败结束。 +settings.event_header_action = Action运行事件 [graphs] component_loading=正在加载 %s… diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini index 7d5852a4dd..b21e4c8f79 100644 --- a/options/locale/locale_zh-TW.ini +++ b/options/locale/locale_zh-TW.ini @@ -209,6 +209,7 @@ buttons.unindent.tooltip = 使項目取消縮排一層 link_modal.header = 新增連結 link_modal.url = 網址 link_modal.description = 描述 +link_modal.paste_reminder = 提示:當您的剪貼簿中有網址時,可以直接貼到編輯器中來建立連結。 [filter] string.asc=A - Z @@ -220,7 +221,7 @@ missing_csrf=錯誤的請求:未提供 CSRF 符記 invalid_csrf=錯誤的請求:無效的 CSRF 符記 not_found=找不到目標。 network_error=網路錯誤 -report_message = 如果您相信這是一個 Forgejo 的錯誤,請在 Codeberg 上搜尋相關問題,或在必要時提出一個新問題。 +report_message = 如果您相信這是一個 Forgejo 的錯誤,請在 Codeberg 上搜尋相關議題,或在必要時提出一個新議題。 server_internal = 伺服器內部錯誤 [startpage] @@ -713,7 +714,7 @@ show_on_map = 在地圖上顯示這個地點 settings = 使用者設定 block_user = 封鎖使用者 block_user.detail_1 = 你們將停止互相關注,並且無法互相關注。 -block_user.detail_2 = 此使用者將無法與你擁有的儲存庫或由你建立的問題和評論進行互動。 +block_user.detail_2 = 此使用者將無法與你擁有的儲存庫或由你建立的議題和評論進行互動。 followers_one = %d 位追蹤者 following_one = 追蹤 %d 個人 block_user.detail_3 = 你們將無法互相新增為儲存庫協作者。 @@ -1013,7 +1014,7 @@ uploaded_avatar_is_too_big = 上傳檔案的大小 (%d KiB)超過了上限 select_permissions = 選擇權限 permission_write = 讀寫 permissions_list = 權限: -add_email_confirmation_sent = 我們已發送一封確認信至 「%s」。請檢查您的信箱並在 %s 內確認您的信箱地址。 +add_email_confirmation_sent = 確認信已發送至「%s」。請在接下來的 %s 內前往收件匣查看該郵件,並點擊其中的連結以完成電子郵件地址的確認。 repo_and_org_access = 儲存庫和組織存取權 permissions_public_only = 僅公開 permissions_access_all = 全部(公開、私有和受限) @@ -1027,9 +1028,9 @@ unbind_success = 已成功移除該社群帳號。 create_oauth2_application_success = 您已成功建立一個新的 OAuth2 應用程式。 change_username_prompt = 註:更改您的使用者名稱也會更改您的帳號 URL。 change_username_redirect_prompt = 舊的使用者名稱在被其他使用者認領之前將會轉址到新的使用者名稱。 -visibility.limited_tooltip = 只有已登入的使用者能看見 +visibility.limited_tooltip = 僅對已登入的使用者可見 visibility.private_tooltip = 只有您加入的組織之成員能看見 -keep_email_private_popup = 這將在您的個人資料頁面、合併請求或網頁檔案編輯器中隱藏您的電子信箱地址。已推送的提交不會被修改。在提交中使用 %s 來將其連結至您的帳號。 +keep_email_private_popup = 您的電子郵件地址不會顯示在個人資料頁面中,也不會成為透過網頁介面(例如上傳檔案、編輯或合併提交)所建立的提交紀錄的預設地址。取而代之的是,可以使用特殊地址 %s 將這些提交關聯到您的帳號。此設定不會影響既有的提交紀錄。 ssh_signonly = 因為目前 SSH 已被停用,這個金鑰只被用來校驗提交簽署。 email_desc = 您的主要電子信箱將被用於通知、密碼復原、和網頁 Git 操作(如果您的信箱不是隱藏的)。 pronouns_custom = 自訂 @@ -1076,6 +1077,20 @@ quota.sizes.assets.attachments.all = 附件 quota.sizes.assets.artifacts = 製品 quota.sizes.wiki = 百科 quota = 配額 +access_token_regeneration = 重新產生存取符記 +access_token_regeneration_desc = 重新產生存取符記將會撤銷使用該符記的應用程式對您帳號的存取權限,此操作無法還原。是否繼續? +regenerate_token_success = 符記已重新產生。使用該符記的應用程式將不再具有你帳號的存取權限,必須更新為新的符記後才能繼續使用。 +quota.applies_to_user = 以下配額規則適用於您的帳號 +quota.applies_to_org = 以下配額規則適用於此組織 +quota.rule.exceeded.helper = 符合此規則的物件總大小已超出配額限制。 +quota.sizes.repos.public = 公開儲存庫 +quota.sizes.repos.private = 私有儲存庫 +quota.sizes.git.all = Git 內容 +quota.sizes.git.lfs = Git LFS +quota.sizes.assets.attachments.issues = 問題附件 +quota.sizes.assets.attachments.releases = 版本發布附件 +keep_pronouns_private = 僅向已驗證的使用者顯示代名詞 +keep_pronouns_private.description = 這將對未登入的訪客隱藏您的代名詞。 [repo] owner=所有者 @@ -1147,7 +1162,7 @@ forks=分叉 reactions_more=和其他 %d 個 unit_disabled=網站管理員已經停用這個儲存庫區域。 language_other=其他 -adopt_search=輸入帳號以搜尋未接管的儲存庫... (留白以查詢全部) +adopt_search=輸入帳號以搜尋未接管的儲存庫... (留白以查詢全部) adopt_preexisting_label=接管檔案 adopt_preexisting=接管既有的檔案 adopt_preexisting_content=從 %s 建立儲存庫 @@ -1362,7 +1377,7 @@ editor.filename_cannot_be_empty=檔案名稱不能為空。 editor.filename_is_invalid=檔名無效:「%s」。 editor.branch_does_not_exist=此儲存庫沒有名為「%s」的分支。 editor.branch_already_exists=此儲存庫已有名為「%s」的分支。 -editor.file_changed_while_editing=檔案內容在您編輯時已被更改。按一下此處來檢視被更改的地方或再次提交以覆蓋這些變更。 +editor.file_changed_while_editing=檔案內容自您開啟後已有變更。點此查看,或再次提交變更以覆寫原內容。 editor.file_already_exists=此儲存庫已有名為「%s」的檔案。 editor.commit_empty_file_header=提交空白檔案 editor.commit_empty_file_text=你準備提交的檔案是空白的,是否繼續? @@ -1581,7 +1596,7 @@ issues.commented_at=`已留言 %s` issues.delete_comment_confirm=您確定要刪除這則留言嗎? issues.context.copy_link=複製連結 issues.context.quote_reply=引用回覆 -issues.context.reference_issue=新增問題並參考 +issues.context.reference_issue=在新問題中引用 issues.context.edit=編輯 issues.context.delete=刪除 issues.close=關閉問題 @@ -1594,8 +1609,8 @@ issues.reopened_at=`重新開放了這個問題 %[2] issues.commit_ref_at=`在提交中關聯了這個問題 %[2]s` issues.ref_issue_from=`關聯了這個問題 %[4]s %[2]s` issues.ref_pull_from=`關聯了這個合併請求 %[4]s %[2]s` -issues.ref_closing_from=`關聯了合併請求 %[4]s 將關閉這個問題 %[2]s` -issues.ref_reopening_from=`關聯了合併請求 %[4]s 將重新開放這個問題 %[2]s` +issues.ref_closing_from=從將關閉此問題的拉取請求 %[4]s 中提及了此問題%[2]s +issues.ref_reopening_from=從將重新開啟此問題的拉取請求 %[4]s 中提及了此問題%[2]s issues.ref_closed_from=`關閉了這個問題 %[4]s %[2]s` issues.ref_reopened_from=`重新開放了這個問題 %[4]s %[2]s` issues.ref_from=`自 %[1]s` @@ -1644,8 +1659,8 @@ issues.unlock=解鎖對話 issues.lock.unknown_reason=由於未知的原因而無法鎖定問題。 issues.lock_duplicate=問題無法被鎖定兩次。 issues.unlock_error=無法解鎖未被鎖定的問題。 -issues.lock_with_reason=因為 %s 而鎖定,並將對話設為協作者限定 %s -issues.lock_no_reason=鎖定並將對話設為協作者限定 %s +issues.lock_with_reason=因為 %s 而鎖定,並將對話限制為協作者 %s +issues.lock_no_reason=鎖定並將對話限制為協作者 %s issues.unlock_comment=解鎖這個對話 %s issues.lock_confirm=鎖定 issues.unlock_confirm=解除鎖定 @@ -1739,8 +1754,8 @@ issues.review.left_comment=留下了回應 issues.review.content.empty=您必須留下訊息指出需要修正的地方。 issues.review.reject=請求了變更 %s issues.review.wait=被請求進行審核 %s -issues.review.add_review_request=請求了 %s 來審核 %s -issues.review.remove_review_request=移除了對 %s 的審核請求 %s +issues.review.add_review_request=請求 %[1]s 進行審查 %[2]s +issues.review.remove_review_request=移除了對 %[1]s 的審查請求 %[2]s issues.review.remove_review_request_self=拒絕了審核 %s issues.review.pending=待處理 issues.review.review=審核 @@ -1824,7 +1839,7 @@ pulls.reject_count_1=%d 個變更請求 pulls.reject_count_n=%d 個變更請求 pulls.waiting_count_1=%d 個正在等待審核 pulls.waiting_count_n=%d 個正在等待審核 -pulls.wrong_commit_id=提交 id 必須存在於目標分支上 +pulls.wrong_commit_id=提交 ID 必須存在於目標分支上 pulls.no_merge_desc=無法進行合併,因為所有儲存庫的合併選項已被停用。 pulls.no_merge_helper=在儲存庫設定啟用合併選項或手動合併該合併請求。 @@ -1840,13 +1855,13 @@ pulls.merge_commit_id=合併提交 ID pulls.require_signed_wont_sign=該分支需要經簽署的提交,但此合併將不會被簽署 pulls.invalid_merge_option=您無法對此合併請求使用這個合併選項。 -pulls.merge_conflict=合併失敗:合併時發生衝突。 提示:請嘗試不同的策略 +pulls.merge_conflict=合併失敗:合併時發生衝突。 提示:請嘗試其他的合併策略 pulls.merge_conflict_summary=錯誤訊息 -pulls.rebase_conflict=合併失敗:Rebase 提交時發生衝突:%[1]s。 提示:請嘗試不同的策略 +pulls.rebase_conflict=合併失敗:Rebase 提交時發生衝突:%[1]s。 提示:請嘗試其他的合併策略 pulls.rebase_conflict_summary=錯誤訊息 -pulls.unrelated_histories=合併失敗:要合併的 HEAD 和基底分支沒有共同的歷史。 提示:請嘗試不同的策略 +pulls.unrelated_histories=合併失敗:要合併的 HEAD 和基底分支沒有共同的歷史。 提示:請嘗試其他的合併策略 pulls.merge_out_of_date=合併失敗:產生合併時,基底已被更新。提示:再試一次。 -pulls.head_out_of_date=合併失敗:產生合併時,head 已被更新。提示:再試一次。 +pulls.head_out_of_date=合併失敗:產生合併時,HEAD 已被更新。提示:再試一次。 pulls.push_rejected=合併失敗:此推送被拒絕。請檢查此儲存庫的 Git Hook。 pulls.push_rejected_summary=完整的拒絕訊息 pulls.push_rejected_no_message=推送失敗:此推送被拒絕但未提供其他資訊。請檢查此儲存庫的 Git Hook @@ -2082,7 +2097,7 @@ settings.pulls.default_allow_edits_from_maintainers=預設允許維護者進行 settings.releases_desc=啟用儲存庫版本發佈 settings.packages_desc=啟用儲存庫軟體包註冊中心 settings.projects_desc=啟用儲存庫專案 -settings.actions_desc=啟用儲存庫 Actions +settings.actions_desc=啟用與 Forgejo Actions 整合的 CI/CD 流程 settings.admin_settings=管理員設定 settings.admin_enable_health_check=啟用儲存庫的健康檢查(git fsck) settings.admin_code_indexer=程式碼索引器 @@ -2181,7 +2196,7 @@ settings.webhook.payload=內容 settings.webhook.body=本體 settings.webhook.replay.description=再次執行此 Webhook。 settings.webhook.delivery.success=已將事件加入到傳送佇列,可能需要等待幾分鐘才會出現於傳送紀錄。 -settings.githooks_desc=Git Hook 是 Git 本身提供的功能。您可以在下方編輯 hook 檔案以設定自訂作業。 +settings.githooks_desc=Git hook 是 Git 本身提供的功能。您可以在下方編輯 hook 檔案以設定自訂作業。 settings.githook_edit_desc=如果 Hook 未啟動,則會顯示範例文件中的內容。如果想要刪除某個 Hook,則送出空白內容即可。 settings.githook_name=Hook 名稱 settings.githook_content=Hook 內容 @@ -2232,15 +2247,15 @@ settings.event_pull_request_desc=建立、編輯、關閉及重新開放合併 settings.event_pull_request_assign=指派 settings.event_pull_request_assign_desc=指派或取消指派合併請求。 settings.event_pull_request_label=標籤 -settings.event_pull_request_label_desc=更新或清除合併請求標籤。 +settings.event_pull_request_label_desc=已新增或移除合併請求的標籤。 settings.event_pull_request_milestone=里程碑 settings.event_pull_request_milestone_desc=里程碑已新增、已移除或已修改。 settings.event_pull_request_comment=評註 settings.event_pull_request_comment_desc=建立、編輯或刪除合併請求的留言。 settings.event_pull_request_review=審核 -settings.event_pull_request_review_desc=核准、退回或提出審核留言。 +settings.event_pull_request_review_desc=合併請求已被核准、拒絕,或已有審查留言新增。 settings.event_pull_request_sync=同步 -settings.event_pull_request_sync_desc=合併請求同步。 +settings.event_pull_request_sync_desc=分支已自動與目標分支同步更新。 settings.event_package=軟體包 settings.event_package_desc=已在儲存庫中建立或刪除軟體包。 settings.branch_filter=分支篩選 @@ -2352,7 +2367,7 @@ settings.choose_branch=選擇一個分支… settings.no_protected_branch=沒有受保護的分支。 settings.edit_protected_branch=編輯 settings.protected_branch_required_rule_name=必須填寫規則名稱 -settings.protected_branch_duplicate_rule_name=規則名稱已存在 +settings.protected_branch_duplicate_rule_name=已經存在一條針對這組分支的規則 settings.protected_branch_required_approvals_min=需要的核可數量不能為負數。 settings.tags=標籤 settings.tags.protection=標籤保護 @@ -2486,7 +2501,7 @@ release.tag_helper=新增或選擇現有的標籤。 release.tag_helper_new=新標籤,將在目標上建立此標籤。 release.tag_helper_existing=現有的標籤。 release.title_empty=標題不可為空。 -release.prerelease_desc=標記為 Pre-Release +release.prerelease_desc=標記為預發行 release.prerelease_helper=標記此版本不適合生產使用。 release.cancel=取消 release.publish=發佈發行 @@ -2629,10 +2644,10 @@ issues.filter_milestone_all = 所有里程碑 issues.author_helper = 此使用者是作者。 pulls.blocked_by_approvals = 此合併請求還沒有足夠的核可。已有 %d 個,總共需要 %d 個核可。 wiki.search = 搜尋百科 -settings.mirror_settings.docs.disabled_pull_mirror.instructions = 設定您的儲存庫以自動推送提交、標簽、和分支至另一個儲存庫。Pull 鏡像已被您的管理員停用。 +settings.mirror_settings.docs.disabled_pull_mirror.instructions = 設定你的專案使其自動將提交、標籤與分支推送到另一個儲存庫。您的網站管理員已停用拉取鏡像功能。 settings.mirror_settings.docs = 設定您的儲存庫以自動與另一個儲存庫同步提交、標籤、和分支。 settings.mirror_settings.docs.disabled_push_mirror.info = 推送鏡像已被您的網站管理員停用。 -settings.mirror_settings.docs.pull_mirror_instructions = 如需建立一個 pull 鏡像,請參閱: +settings.mirror_settings.docs.pull_mirror_instructions = 如需建立一個拉取鏡像,請參閱: blame.ignore_revs = .git-blame-ignore-revs 中的修訂已被忽略。點擊這裡來檢視一般的責任歸屬界面。 editor.file_is_a_symlink = `「%s」是一個符號連結。網頁編輯器無法編輯符號連結` issues.label_archive = 封存標籤 @@ -2644,7 +2659,7 @@ pulls.showing_only_single_commit = 只顯示提交 %[1]s 的變更 pulls.cmd_instruction_merge_desc = 合併更改並在 Forgejo 上更新。 signing.wont_sign.pubkey = 無法簽署該提交,因為您沒有與您帳號連結的公鑰。 signing.wont_sign.twofa = 您必須啟用兩步驟認證才能簽署提交。 -signing.wont_sign.basesigned = 因為 base 提交沒有被簽署,無法簽署該提交。 +signing.wont_sign.basesigned = 合併將不會被簽署,因為其基礎提交尚未簽署。 settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning = 目前,這個操作只能在「新的遷移」目錄完成。如需更多資訊,請參閱: settings.mirror_settings.docs.disabled_push_mirror.instructions = 設定您的儲存庫以自動從另一個儲存庫拉取提交、標籤和分支。 issues.role.contributor_helper = 該使用者過去曾經提交至此儲存庫。 @@ -2657,14 +2672,14 @@ signing.wont_sign.error = 在檢查是否能簽署提交時發生錯誤。 issues.num_participants_one = %d 位參與者 pulls.showing_specified_commit_range = 只顯示 %[1]s 至 %[2]s 之間的變更 blame.ignore_revs.failed = 忽略 .git-blame-ignore-revs 中的修訂失敗。 -issues.blocked_by_user = 因為您被該儲存庫的所有者封鎖,您不能提出一個新的問題。 +issues.blocked_by_user = 因為您已被儲存庫擁有者封鎖,您無法在此儲存庫中建立問題。 pulls.blocked_by_user = 因為您被這個儲存庫的所有者封鎖,您不能在這裡開啟一個合併請求。 pulls.has_merged = 失敗:該合併請求已被合併,您無法再合併一次或更改目標分支。 wiki.cancel = 取消 activity.navbar.code_frequency = 寫程式頻率 settings.mirror_settings.docs.no_new_mirrors = 您的儲存庫目前是另一個儲存庫的鏡像。請記住,您目前不能建立新的鏡像。 settings.mirror_settings.docs.can_still_use = 雖然您不能建立新的鏡像,您還是可以使用既有的鏡像。 -pulls.reopen_failed.base_branch = 因為這個合併請求的 base 分支不存在,它無法被開啟。 +pulls.reopen_failed.base_branch = 因為這個合併請求的基礎分支不存在,它無法被開啟。 issues.label_archive_tooltip = 在預設情況下,標籤搜尋時會排除被封存的標籤。 signing.wont_sign.approved = 因為合併請求沒有被核可,這個合併不會被簽署。 activity.navbar.recent_commits = 最近的提交 @@ -2679,7 +2694,7 @@ editor.push_out_of_date = 該推送似乎過期了。 issues.cancel_tracking_history = `已取消時間追蹤 %s` issues.due_date_not_writer = 您需要有寫入這個儲存庫的權限才能更新其問題的到期日。 pulls.commit_ref_at = `在提交 %[2]s 引用了這個合併請求` -pulls.cmd_instruction_checkout_desc = 從您的專案儲存庫 checkout 一個新的分支來測試這些更改。 +pulls.cmd_instruction_checkout_desc = 從您的專案儲存庫中,建立並切換到一個新分支以測試這些變更。 pulls.cmd_instruction_merge_title = 合併 pulls.ready_for_review = 可以開始審閱了嗎? pulls.cmd_instruction_hint = `檢視命令列指示` @@ -2704,7 +2719,7 @@ pulls.blocked_by_changed_protected_files_1 = 這個合併請求被暫止,因 pulls.blocked_by_changed_protected_files_n = 這個合併請求被暫止,因為它更改了這些受保護的檔案: pulls.status_checks_hide_all = 隱藏所有檢查 pulls.status_checks_show_all = 顯示所有檢查 -pulls.reopen_failed.head_branch = 因為這個合併請求的 head 分支不存在,它無法被開啟。 +pulls.reopen_failed.head_branch = 因為這個合併請求的 HEAD 分支不存在,無法重新開啟此合併請求。 activity.navbar.pulse = 動態 signing.will_sign = 將以金鑰「%s」簽署這個提交。 signing.wont_sign.headsigned = 因為 head 提交沒有被簽署,這個合併不會被簽署。 @@ -2721,7 +2736,7 @@ pulls.show_changes_since_your_last_review = 顯示自您上次審閱的變更 pulls.blocked_by_rejection = 這個合併請求有正式審閱者所請求的更改。 pulls.blocked_by_official_review_requests = 因為這個合併請求還缺少至少一個正式審閱者的核可,它已被暫止。 wiki.original_git_entry_tooltip = 與其使用友善連結,檢視原始 Git 檔案。 -settings.mirror_settings.docs.more_information_if_disabled = 您可以在這裡找到更多關於 push 和 pull 鏡像的資訊: +settings.mirror_settings.docs.more_information_if_disabled = 您可以在這裡找到更多關於推送和拉取鏡像的資訊: settings.mirror_settings.docs.doc_link_title = 如何建立儲存庫鏡像? settings.mirror_settings.docs.pulling_remote_title = 從遠端儲存庫拉取 issues.author.tooltip.pr = 此使用者是這個合併請求的作者。 @@ -2872,12 +2887,40 @@ settings.units.units = 功能 diff.git-notes.add = 增加註釋 diff.git-notes.remove-header = 移除註釋 settings.event_pull_request_enforcement = 執行 -sync_fork.branch_behind_few = 此分支落後 %[2]s %[1]d 次提交 +sync_fork.branch_behind_few = 此分支落後 %[2]s 共 %[1]d 次提交 sync_fork.button = 同步 sync_fork.branch_behind_one = 此分支落後 %[2]s %[1]d 次提交 +issues.review.remove_review_requests = 移除了對 %[1]s 的審查請求 %[2]s +issues.filter_no_results_placeholder = 嘗試調整您的搜尋篩選條件。 +issues.reaction.alt_add = 對留言添加 %[1]s 個反應。 +issues.reopen.blocked_by_user = 您無法重新開啟此問題,因為您已被儲存庫擁有者或此問題的發佈者封鎖。 +issues.filter_no_results = 沒有結果 +archive.nocomment = 您無法留言,因為此儲存庫已被封存。 +migrate.repo_desc_helper = 如欲匯入現有描述請留空 +comment.blocked_by_user = 您無法留言,因為您已被儲存庫擁有者或作者封鎖。 +pulls.editable_explanation = 此合併請求允許維護者進行編輯。您可以直接參與貢獻。 +pulls.agit_explanation = 此合併請求使用 AGit 工作流程建立。AGit 允許貢獻者透過「git push」提出變更,無需建立分支或分叉。 +issues.review.add_remove_review_requests = 請求 %[1]s 進行審查,並取消了對 %[2]s 的審查請求 %[3]s +pulls.cmd_instruction_merge_warning = 警告:此儲存庫尚未啟用「自動偵測手動合併」設定,您之後需要手動標記此合併請求為已合併。 +issues.review.add_review_requests = 請求 %[1]s 進行審查 %[2]s +settings.event_header_action = Action 執行事件 +settings.event_action_failure = 失敗 +settings.event_action_recover = 恢復 +settings.event_action_recover_desc = Action 執行成功,先前同一工作流程中的 Action 執行曾經失敗。 +settings.event_action_success = 成功 +settings.event_action_success_desc = Action 執行成功。 +settings.sourcehut_builds.manifest_path = 建置清單路徑 +settings.sourcehut_builds.visibility = 作業可見性 +settings.event_action_failure_desc = Action 執行以失敗結束。 +settings.protect_branch_name_pattern_desc = 受保護分支名稱的模式。請參閱文件說明以了解模式語法。範例:main、release/** +settings.sourcehut_builds.access_token_helper = 具有 JOBS:RW 權限的存取權杖。請在 meta.sr.ht 上產生一個 builds.sr.ht 權杖,或一個 具有機密存取權限的 builds.sr.ht 權杖。 +settings.matrix.room_id_helper = 可從 Element 網頁版客戶端取得 Room ID:進入房間設定 > 進階 > Internal room ID。範例:%s。 +settings.web_hook_name_sourcehut_builds = SourceHut Builds +diff.git-notes.remove-body = 此注釋將會被移除。 +settings.sourcehut_builds.secrets_helper = 授權此作業存取建置機密(需要 SECRETS:RO 權限) [graphs] -component_loading = %s載入中… +component_loading = 正在載入 %s… code_frequency.what = 寫程式頻率 recent_commits.what = 最近的提交 contributors.what = 貢獻 @@ -2920,7 +2963,7 @@ settings.permission=權限 settings.repoadminchangeteam=儲存庫管理者可增加與移除團隊權限 settings.visibility=瀏覽權限 settings.visibility.public=公開 -settings.visibility.limited=受限(只有已驗證的使用者才可以看到) +settings.visibility.limited=受限(僅對已登入使用者可見) settings.visibility.limited_shortname=受限 settings.visibility.private=私有(只有組織成員才能看到) settings.visibility.private_shortname=私有 @@ -3096,8 +3139,8 @@ dashboard.total_gc_time=總 GC 暫停時間 dashboard.total_gc_pause=總 GC 暫停時間 dashboard.last_gc_pause=上次 GC 暫停時間 dashboard.gc_times=GC 執行次數 -dashboard.delete_old_actions=從資料庫刪除所有舊行為 -dashboard.delete_old_actions.started=從資料庫刪除所有舊行為的任務已啟動。 +dashboard.delete_old_actions=從資料庫刪除所有舊操作紀錄 +dashboard.delete_old_actions.started=從資料庫刪除所有舊操作紀錄的任務已啟動。 dashboard.update_checker=更新檢查器 dashboard.delete_old_system_notices=從資料庫刪除所有舊系統提示 dashboard.gc_lfs=對 LFS meta objects 進行垃圾回收 @@ -3117,7 +3160,7 @@ users.repos=儲存庫數 users.created=建立時間 users.last_login=上次登入 users.never_login=從未登入 -users.send_register_notify=寄送使用者註冊通知 +users.send_register_notify=透過電子郵件傳送註冊通知 users.new_success=已建立新帳號「%s」。 users.edit=編輯 users.auth_source=認證來源 @@ -3133,7 +3176,7 @@ users.prohibit_login=已停權帳號 users.is_admin=管理員帳號 users.is_restricted=受限制的帳號 users.allow_git_hook=可以建立 Git Hook -users.allow_git_hook_tooltip=Git Hook 將以和 Forgejo 相同的作業系統使用者執行,並擁有同等的主機存取權限。因此擁有此特殊 Git Hook 權限的使用者可存取和修改所有的 Forgejo 儲存庫和 Forgejo 的資料庫。他們甚至能取得 Forgejo 的管理員權限。 +users.allow_git_hook_tooltip=Git hooks 會以執行 Forgejo 的作業系統使用者身分執行,並擁有相同等級的主機存取權限。因此,擁有此特殊 Git hook 權限的使用者,可以存取並修改所有 Forgejo 儲存庫,以及 Forgejo 使用的資料庫。換句話說,他們也具備取得 Forgejo 管理員權限的能力。 users.allow_import_local=可以匯入本地儲存庫 users.allow_create_organization=可以建立組織 users.update_profile=更新使用者帳號 @@ -3335,7 +3378,7 @@ config.custom_file_root_path=自訂檔案根路徑 config.domain=伺服器域名 config.offline_mode=本地模式 config.disable_router_log=停用路由日誌 -config.run_user=以使用者名稱執行 +config.run_user=以使用者執行 config.run_mode=執行模式 config.git_version=Git 版本 config.repo_root_path=儲存庫根路徑 @@ -3440,8 +3483,8 @@ config.enable_federated_avatar=啟用聯邦式大頭貼 config.git_config=Git 設定 config.git_disable_diff_highlight=停用比較語法高亮 -config.git_max_diff_lines=差異比較時顯示的最多行數 (單檔) -config.git_max_diff_line_characters=差異比較時顯示的最多字元數 (單行) +config.git_max_diff_lines=差異比較時顯示的最多行數 +config.git_max_diff_line_characters=差異比較時顯示的最多字元數 config.git_max_diff_files=差異比較時顯示的最多檔案數 config.git_gc_args=GC 參數 config.git_migrate_timeout=遷移逾時 @@ -3599,7 +3642,7 @@ mirror_sync_create=從鏡像同步了新參考 %[3]s%[3]s 刪除了參考 %[2]s approve_pull_request=`核可了 %[3]s#%[2]s` reject_pull_request=`提出了修改建議 %[3]s#%[2]s` -publish_release=`發布了 %[3]s "%[4]s" ` +publish_release=`已於 %[3]s 發佈 %[4]s` review_dismissed=`取消了 %[4]s%[3]s#%[2]s 的審核` review_dismissed_reason=原因: create_branch=在 %[4]s 中建立了分支 %[3]s @@ -3817,7 +3860,7 @@ arch.version.depends = 依賴 owner.settings.cargo.rebuild.no_index = 無法重建,未初始化任何索引。 cran.registry = 在你的 Rprofile.site 檔案中設定此註冊表: debian.repository.distributions = 發行版 -owner.settings.chef.keypair.description = 需要金鑰對才能向 Chef 註冊表進行身份驗證。如果你之前已經產生過金鑰對,產生新的金鑰對將會丟棄舊的金鑰對。 +owner.settings.chef.keypair.description = 送往 Chef 註冊表的請求必須經過加密簽署,以作為驗證身分的方式。在產生金鑰對時,只有公鑰會儲存在 Forgejo 上,私鑰則會提供給你用於 knife 工具。產生新的金鑰對將會覆蓋先前的金鑰。 owner.settings.cargo.initialize.description = 使用 Cargo 註冊表需要一個特殊的索引 Git 儲存庫。使用此選項將會(重新)建立儲存庫並自動配置它。 rpm.repository.multiple_groups = 此套件可以在多個群組中使用。 rpm.distros.suse = 在基於 SUSE 的發行版上 @@ -3839,6 +3882,7 @@ registry.documentation = 有關 %s 註冊表的更多資訊,請參閱讀取:查看並建立問題與留言。 +releases.write = 寫入:發布、編輯與刪除發行版本及其資源。 +wiki.read = 讀取:查看整合的 Wiki 及其歷史紀錄。 +code.write = 寫入:推送到儲存庫,建立分支與標籤。 +code.read = 讀取:存取並複製此儲存庫的原始碼。 +issues.write = 寫入:關閉問題,並管理標籤、里程碑、指派對象、到期日與依賴關係等中繼資料。 +pulls.read = 讀取:查看並建立合併請求。 +pulls.write = 寫入:關閉合併請求,並管理標籤、里程碑、指派對象、到期日與依賴關係等中繼資料。 +releases.read = 讀取:檢視並下載發行版本。 +wiki.write = 寫入:在整合的 Wiki 中建立、更新與刪除頁面。 +projects.read = 讀取:存取儲存庫的專案看板。 +packages.write = 寫入:發布與刪除指派給此儲存庫的套件。 +projects.write = 寫入:建立專案與欄位,並編輯它們。 +packages.read = 讀取:檢視並下載指派給此儲存庫的套件。 +actions.read = 讀取:查看整合的 CI/CD 流程及其紀錄。 +actions.write = 寫入:手動觸發、重新啟動、取消或核准待處理的 CI/CD 流程。 \ No newline at end of file diff --git a/options/locale_next/locale_bg.json b/options/locale_next/locale_bg.json index 02144c8b38..1faf269aba 100644 --- a/options/locale_next/locale_bg.json +++ b/options/locale_next/locale_bg.json @@ -1,10 +1,89 @@ { - "repo.pulls.merged_title_desc": { - "one": "сля %[1]d подаване от %[2]s в %[3]s %[4]s", - "other": "сля %[1]d подавания от %[2]s в %[3]s %[4]s" - }, - "repo.pulls.title_desc": { - "one": "иска да слее %[1]d подаване от %[2]s в %[3]s", - "other": "иска да слее %[1]d подавания от %[2]s в %[3]s" - } + "repo.pulls.merged_title_desc": { + "one": "сля %[1]d подаване от %[2]s в %[3]s %[4]s", + "other": "сля %[1]d подавания от %[2]s в %[3]s %[4]s" + }, + "repo.pulls.title_desc": { + "one": "иска да слее %[1]d подаване от %[2]s в %[3]s", + "other": "иска да слее %[1]d подавания от %[2]s в %[3]s" + }, + "mail.actions.run_info_ref": "Клон: %[1]s (%[2]s)", + "mail.actions.run_info_trigger": "Задействано поради: %[1]s от: %[2]s", + "meta.last_line": "В България расте най-старото дърво в страната, Байкушевата мура, на възраст над 1300 години.", + "relativetime.1day": "вчера", + "relativetime.2months": "преди два месеца", + "home.explore_repos": "Разглеждане на хранилища", + "home.explore_users": "Разглеждане на потребители", + "home.explore_orgs": "Разглеждане на организации", + "relativetime.mins": { + "one": "преди %d минута", + "other": "преди %d минути" + }, + "repo.form.cannot_create": "Всички пространства, в които можете да създавате хранилища, са достигнали лимита си на хранилища.", + "moderation.report_remarks": "Подробности", + "moderation.submit_report": "Изпращане на доклада", + "followers.incoming.list.self.none": "Никой не следва вашия профил.", + "followers.incoming.list.none": "Никой не следва този потребител.", + "relativetime.now": "сега", + "home.welcome.no_activity": "Няма дейност", + "relativetime.1year": "миналата година", + "moderation.abuse_category": "Категория", + "moderation.abuse_category.illegal_content": "Незаконно съдържание", + "home.welcome.activity_hint": "Все още няма нищо в емисията ви. Вашите действия и дейност от хранилищата, които наблюдавате, ще се появят тук.", + "stars.list.none": "Никой не е отбелязал това хранилище със звезда.", + "moderation.report_abuse_form.invalid": "Невалидни аргументи", + "moderation.report_abuse_form.already_reported": "Вече сте докладвали това съдържание", + "moderation.abuse_category.placeholder": "Изберете категория", + "moderation.abuse_category.spam": "Спам", + "repo.issue_indexer.title": "Индексатор на задачи", + "moderation.report_abuse_form.details": "Този формуляр трябва да се използва за докладване на потребители, които създават спам профили, хранилища, задачи, коментари или се държат неподходящо.", + "moderation.report_remarks.placeholder": "Моля, предоставете подробности относно злоупотребата, за която докладвате.", + "moderation.reporting_failed": "Не може да се изпрати новият доклад за злоупотреба: %v", + "moderation.reported_thank_you": "Благодарим ви за доклада. Администрацията е уведомена.", + "error.not_found.title": "Страницата не е намерена", + "incorrect_root_url": "Тази инстанция на Forgejo е конфигурирана да се сервира на \"%s\". В момента разглеждате Forgejo през друг URL адрес, което може да доведе до неправилно функциониране на части от приложението. Каноничният URL адрес се контролира от администраторите на Forgejo чрез настройката ROOT_URL в app.ini.", + "themes.names.forgejo-dark": "Forgejo тъмна", + "themes.names.forgejo-auto": "Forgejo (следване на системната тема)", + "themes.names.forgejo-light": "Forgejo светла", + "watch.list.none": "Никой не наблюдава това хранилище.", + "followers.outgoing.list.self.none": "Не следвате никого.", + "followers.outgoing.list.none": "%s не следва никого.", + "relativetime.future": "в бъдеще", + "relativetime.2days": "преди два дни", + "relativetime.1week": "миналата седмица", + "relativetime.2weeks": "преди две седмици", + "relativetime.1month": "миналия месец", + "relativetime.2years": "преди две години", + "moderation.report_abuse_form.header": "Докладване на злоупотреба до администратора", + "moderation.abuse_category.malware": "Зловреден софтуер", + "moderation.abuse_category.other_violations": "Други нарушения на правилата на платформата", + "alert.asset_load_failed": "Неуспешно зареждане на файлове с ресурси от {path}. Моля, уверете се, че файловете с ресурси са достъпни.", + "alert.range_error": " трябва да бъде число между %[1]s и %[2]s.", + "install.invalid_lfs_path": "Не може да се създаде LFS корен в посочения път: %[1]s", + "search.milestone_kind": "Търсене на етапи…", + "admin.config.moderation_config": "Конфигурация на модерацията", + "moderation.report_abuse": "Докладване на злоупотреба", + "moderation.report_content": "Докладване на съдържание", + "relativetime.hours": { + "one": "преди %d час", + "other": "преди %d часа" + }, + "relativetime.days": { + "one": "преди %d ден", + "other": "преди %d дни" + }, + "relativetime.weeks": { + "one": "преди %d седмица", + "other": "преди %d седмици" + }, + "relativetime.months": { + "one": "преди %d месец", + "other": "преди %d месеца" + }, + "relativetime.years": { + "one": "преди %d година", + "other": "преди %d години" + }, + "editor.textarea.tab_hint": "Редът вече е с отстъп. Натиснете отново Tab или Escape, за да излезете от редактора.", + "editor.textarea.shift_tab_hint": "Няма отстъп на този ред. Натиснете отново Shift + Tab или Escape, за да излезете от редактора." } diff --git a/options/locale_next/locale_cs-CZ.json b/options/locale_next/locale_cs-CZ.json index d70790c5d9..97a8536d4f 100644 --- a/options/locale_next/locale_cs-CZ.json +++ b/options/locale_next/locale_cs-CZ.json @@ -100,5 +100,10 @@ "stars.list.none": "Tento repozitář si nikdo nepřidal do oblíbených.", "followers.outgoing.list.self.none": "Nikoho nesledujete.", "editor.textarea.tab_hint": "Řádek je již odsazen. Pro opuštění editoru stiskněte znovu Tab nebo Escape.", - "editor.textarea.shift_tab_hint": "Na tomto řádku není žádné odsazení. Pro opuštění editoru stiskněte znovu Shift + Tab nebo Escape." + "editor.textarea.shift_tab_hint": "Na tomto řádku není žádné odsazení. Pro opuštění editoru stiskněte znovu Shift + Tab nebo Escape.", + "admin.dashboard.cleanup_offline_runners": "Vymazat offline runnery", + "settings.visibility.description": "Viditelnost profilu ovlivňuje možnost ostatních přistupovat k vašim veřejným repozitářům. Zjistit více", + "avatar.constraints_hint": "Velikost vlastního avataru nesmí překročit %[1]s nebo být větší než %[2]dx%[3]d pixelů", + "repo.diff.commit.next-short": "Další", + "repo.diff.commit.previous-short": "Předchozí" } diff --git a/options/locale_next/locale_da.json b/options/locale_next/locale_da.json index fe3c25ab2e..8315e06bcc 100644 --- a/options/locale_next/locale_da.json +++ b/options/locale_next/locale_da.json @@ -92,5 +92,10 @@ "followers.outgoing.list.self.none": "Du følger ikke nogen.", "followers.outgoing.list.none": "%s følger ikke nogen.", "editor.textarea.tab_hint": "Linjen er allerede indrykket. Tryk på Tab igen eller Escape for at forlade editoren.", - "editor.textarea.shift_tab_hint": "Ingen indrykning på denne linje. Tryk på Shift + Tab igen eller Escape for at forlade editoren." + "editor.textarea.shift_tab_hint": "Ingen indrykning på denne linje. Tryk på Shift + Tab igen eller Escape for at forlade editoren.", + "admin.dashboard.cleanup_offline_runners": "Ryd op offline runners", + "settings.visibility.description": "Profilsynlighed påvirker andres adgang til dine ikke-private depoter. Læs mere", + "avatar.constraints_hint": "Brugerdefineret avatar må ikke overstige %[1]s i størrelse eller være større end %[2]dx%[3]d pixels", + "repo.diff.commit.next-short": "Næste", + "repo.diff.commit.previous-short": "Forrige" } diff --git a/options/locale_next/locale_de-DE.json b/options/locale_next/locale_de-DE.json index 331f319a86..94ab12f180 100644 --- a/options/locale_next/locale_de-DE.json +++ b/options/locale_next/locale_de-DE.json @@ -92,5 +92,10 @@ "stars.list.none": "Niemand hat dieses Repo favorisiert.", "followers.incoming.list.none": "Niemand folgt diesem Benutzer.", "editor.textarea.tab_hint": "Zeile bereits eingerückt. Drücke nochmals Tab oder Escape um den Editor zu verlassen.", - "editor.textarea.shift_tab_hint": "Keine Einrückung auf dieser Zeile. Drücke nochmals Shift + Tab oder Escape um den Editor zu verlassen." + "editor.textarea.shift_tab_hint": "Keine Einrückung auf dieser Zeile. Drücke nochmals Shift + Tab oder Escape um den Editor zu verlassen.", + "admin.dashboard.cleanup_offline_runners": "Aufräumen der offline Runner", + "settings.visibility.description": "Die Profilsichtbarkeit beeinflusst die Möglichkeit anderer, auf deine nicht-privaten Repositorys zuzugreifen. Erfahre mehr", + "avatar.constraints_hint": "Individuelles Profilbild darf %[1]s in der Größe nicht überschreiten, und nicht größer als %[2]dx%[3]d Pixel sein", + "repo.diff.commit.next-short": "Nächste", + "repo.diff.commit.previous-short": "Vorherige" } diff --git a/options/locale_next/locale_en-US.json b/options/locale_next/locale_en-US.json index a377bf9af9..d82d7f0ed7 100644 --- a/options/locale_next/locale_en-US.json +++ b/options/locale_next/locale_en-US.json @@ -63,6 +63,11 @@ "alert.asset_load_failed": "Failed to load asset files from {path}. Please make sure the asset files can be accessed.", "alert.range_error": " must be a number between %[1]s and %[2]s.", "install.invalid_lfs_path": "Unable to create the LFS root at the specified path: %[1]s", + "profile.actions.tooltip": "More actions", + "profile.edit.link": "Edit profile", + "feed.atom.link": "Atom feed", + "keys.ssh.link": "SSH keys", + "keys.gpg.link": "GPG keys", "admin.config.moderation_config": "Moderation configuration", "moderation.report_abuse": "Report abuse", "moderation.report_content": "Report content", @@ -89,6 +94,8 @@ "mail.actions.run_info_previous_status": "Previous Run's Status: %[1]s", "mail.actions.run_info_ref": "Branch: %[1]s (%[2]s)", "mail.actions.run_info_trigger": "Triggered because: %[1]s by: %[2]s", + "repo.diff.commit.next-short": "Next", + "repo.diff.commit.previous-short": "Prev", "discussion.locked": "This discussion has been locked. Commenting is limited to contributors.", "editor.textarea.tab_hint": "Line already indented. Press Tab again or Escape to leave the editor.", "editor.textarea.shift_tab_hint": "No indentation on this line. Press Shift + Tab again or Escape to leave the editor.", @@ -98,5 +105,7 @@ "settings.theme_dark": "Dark", "settings.theme_auto": "Auto (per your browser)", "error.issue_not_found": "Issue not found", + "settings.visibility.description": "Profile visibility affects others' ability to access your non-private repositories. Learn more", + "avatar.constraints_hint": "Custom avatar may not exceed %[1]s in size or be larger than %[2]dx%[3]d pixels", "meta.last_line": "Thank you for translating Forgejo! This line isn't seen by the users but it serves other purposes in the translation management. You can place a fun fact in the translation instead of translating it." } diff --git a/options/locale_next/locale_fil.json b/options/locale_next/locale_fil.json index ae066d0663..1f1e535dad 100644 --- a/options/locale_next/locale_fil.json +++ b/options/locale_next/locale_fil.json @@ -92,5 +92,7 @@ "followers.outgoing.list.self.none": "Hindi ka sumusunod ng anumang tao.", "followers.outgoing.list.none": "Hindi sinusundan ni %s ang sinuman.", "editor.textarea.tab_hint": "Naka-indent na ang linya. Pindutin ulit ang Tab o Escape para umalis sa editor.", - "editor.textarea.shift_tab_hint": "Walang indentation sa linyang ito. Pindutin ang Shift + Tab ulit o Escape para umalis sa editor." + "editor.textarea.shift_tab_hint": "Walang indentation sa linyang ito. Pindutin ang Shift + Tab ulit o Escape para umalis sa editor.", + "admin.dashboard.cleanup_offline_runners": "Linisin ang mga offline na runner", + "settings.visibility.description": "Maaapektuhan ng visibility ng profile ang kakayahan ng iba na i-access ang iyong mga hindi pribadong repositoryo. Matuto pa" } diff --git a/options/locale_next/locale_fr-FR.json b/options/locale_next/locale_fr-FR.json index 0792804bf2..a9035f0848 100644 --- a/options/locale_next/locale_fr-FR.json +++ b/options/locale_next/locale_fr-FR.json @@ -97,5 +97,11 @@ "repo.issue_indexer.title": "Indexeur de problèmes", "stars.list.none": "Personne n'a mis d'étoiles sur ce dépôt.", "watch.list.none": "Personne ne consulte ce dépôt.", - "repo.form.cannot_create": "Tous les espaces dans lesquels vous pouvez créer des dépôts ont atteint la limite de dépôts." + "repo.form.cannot_create": "Tous les espaces dans lesquels vous pouvez créer des dépôts ont atteint la limite de dépôts.", + "admin.dashboard.cleanup_offline_runners": "Nettoyer les exécuteurs hors ligne", + "mail.actions.run_info_trigger": "Déclenché parce que : %[1]s par : %[2]s", + "settings.visibility.description": "La visibilité du profil affecte la capacité des autres à accéder à vos dépôts non-privés. Voir plus", + "editor.textarea.shift_tab_hint": "Pas d'indentation sur cette ligne. Appuyez sur Maj + Tab une nouvelle fois ou sur Échap pour quitter l'éditeur.", + "avatar.constraints_hint": "L'avatar personnalisé ne doit pas dépasser une taille de %[1]s ou être plus grand que %[2]dx%[3]d pixels", + "editor.textarea.tab_hint": "Ligne déjà indentée. Appuyez sur Tab une nouvelle fois ou sur Échap pour quitter l'éditeur." } diff --git a/options/locale_next/locale_lv-LV.json b/options/locale_next/locale_lv-LV.json index 1b03595ee3..f71cfa227d 100644 --- a/options/locale_next/locale_lv-LV.json +++ b/options/locale_next/locale_lv-LV.json @@ -100,5 +100,10 @@ "stars.list.none": "Neviens šo glabātavu nav atzīmējis ar zvaigzni.", "followers.incoming.list.self.none": "Neviens neseko Tavam profilam.", "editor.textarea.tab_hint": "Rinda jau ir ar atkāpi. Spied Tab vēlreiz vai Escape, lai izietu no redaktora!", - "editor.textarea.shift_tab_hint": "Šajā rindā nav atkāpes. Spied Shift + Tab vēlreiz vai Escape, lai izietu no redaktora!" + "editor.textarea.shift_tab_hint": "Šajā rindā nav atkāpes. Spied Shift + Tab vēlreiz vai Escape, lai izietu no redaktora!", + "admin.dashboard.cleanup_offline_runners": "Notīrīt bezsaistes izpildītājus", + "settings.visibility.description": "Profila redzamība ietekmē iespēju citiem piekļūt Tavām glabātavām, kas nav privātas. Uzzināt vairāk", + "avatar.constraints_hint": "Pielāgots profila attēls nevar pārsniegt %[1]s vai būt lielāks par %[2]dx%[3]d pikseļiem", + "repo.diff.commit.next-short": "Nāk.", + "repo.diff.commit.previous-short": "Iepr." } diff --git a/options/locale_next/locale_nds.json b/options/locale_next/locale_nds.json index 71225a720b..9a3884a87f 100644 --- a/options/locale_next/locale_nds.json +++ b/options/locale_next/locale_nds.json @@ -92,5 +92,10 @@ "followers.outgoing.list.self.none": "Du gahst nüms na.", "stars.list.none": "Nüms hett up deesem Repositorium eenen Steern sett.", "editor.textarea.tab_hint": "Rieg al inschuven. Drück weer Tab of Esc, um de Bewarker to verlaten.", - "editor.textarea.shift_tab_hint": "Keen Inschuuv in deeser Rieg. Drück weer Umschalt+Tab of Esc, um de Bewarker to verlaten." + "editor.textarea.shift_tab_hint": "Keen Inschuuv in deeser Rieg. Drück weer Umschalt+Tab of Esc, um de Bewarker to verlaten.", + "admin.dashboard.cleanup_offline_runners": "Nich verbunnen Lopers uprümen", + "settings.visibility.description": "De Profil-Sichtbaarkeid maakt daar wat an, of un wo anner Lüü diene nich-privaaten Repositoriums ankieken könen. Mehr unnerhören", + "avatar.constraints_hint": "Dat eegene Kontobill düür nich groter as %[1]s wesen of groter as %[2]d×%[3]d Billtüttels wesen", + "repo.diff.commit.next-short": "Anner", + "repo.diff.commit.previous-short": "Vörig" } diff --git a/options/locale_next/locale_pt-BR.json b/options/locale_next/locale_pt-BR.json index 909c9339d3..92e140878e 100644 --- a/options/locale_next/locale_pt-BR.json +++ b/options/locale_next/locale_pt-BR.json @@ -100,5 +100,8 @@ "stars.list.none": "Ninguém favoritou este repositório.", "followers.outgoing.list.self.none": "Você não está seguindo ninguém.", "editor.textarea.tab_hint": "Linha já indentada. Pressione Tab novamente ou Esc para sair do editor.", - "editor.textarea.shift_tab_hint": "Sem indentação nesta linha. Pressione Shift + Tab novamente ou Esc para sair do editor." + "editor.textarea.shift_tab_hint": "Sem indentação nesta linha. Pressione Shift + Tab novamente ou Esc para sair do editor.", + "admin.dashboard.cleanup_offline_runners": "Limpar runners desconectados", + "avatar.constraints_hint": "Imagem de perfil personalizada não pode exceder %[1]s em tamanho ou ser maior que %[2]dx%[3]d pixels", + "settings.visibility.description": "A visibilidade do perfil afeta a habilidade de acessarem seus repositórios não-privados. Saiba mais" } diff --git a/options/locale_next/locale_pt-PT.json b/options/locale_next/locale_pt-PT.json index e43f34c6da..78e6dc4493 100644 --- a/options/locale_next/locale_pt-PT.json +++ b/options/locale_next/locale_pt-PT.json @@ -100,5 +100,8 @@ "followers.outgoing.list.self.none": "Não está a seguir ninguém.", "editor.textarea.tab_hint": "Linha já indentada. Pressione Tab novamente ou Escape para sair do editor.", "editor.textarea.shift_tab_hint": "Sem indentação nesta linha. Pressione Shift + Tab novamente ou Escape para sair do editor.", - "stars.list.none": "Ninguém juntou este repositório aos favoritos." + "stars.list.none": "Ninguém juntou este repositório aos favoritos.", + "admin.dashboard.cleanup_offline_runners": "Limpeza de executores offline", + "settings.visibility.description": "A visibilidade do perfil afecta a capacidade de outros acederem aos seus repositórios não privados. Ler mais", + "avatar.constraints_hint": "O avatar personalizado não pode exceder %[1]s de tamanho ou ser maior do que %[2]dx%[3]d pixéis" } diff --git a/options/locale_next/locale_ru-RU.json b/options/locale_next/locale_ru-RU.json index 768c4b23ae..8992cc6abd 100644 --- a/options/locale_next/locale_ru-RU.json +++ b/options/locale_next/locale_ru-RU.json @@ -100,5 +100,10 @@ "moderation.report_abuse_form.invalid": "Невалидные аргументы", "moderation.report_abuse_form.header": "Жалоба администрации", "editor.textarea.tab_hint": "Отступ уже добавлен. Нажмите Tab снова или Escape, чтобы покинуть редактор.", - "editor.textarea.shift_tab_hint": "В строке нет отступов. Нажмите Shift + Tab снова или Escape, чтобы покинуть редактор." + "editor.textarea.shift_tab_hint": "В строке нет отступов. Нажмите Shift + Tab снова или Escape, чтобы покинуть редактор.", + "admin.dashboard.cleanup_offline_runners": "Удалить недоступных исполнителей", + "avatar.constraints_hint": "Изображение профиля не может быть более %[1]s и крупнее %[2]dx%[3]d пикселей", + "settings.visibility.description": "Видимость профиля влияет на доступ других до ваших не частных репозиториев. Подробнее", + "repo.diff.commit.previous-short": "Предыдущий", + "repo.diff.commit.next-short": "Далее" } diff --git a/options/locale_next/locale_sv-SE.json b/options/locale_next/locale_sv-SE.json index dc41e4d6e1..9a8762212c 100644 --- a/options/locale_next/locale_sv-SE.json +++ b/options/locale_next/locale_sv-SE.json @@ -1,5 +1,99 @@ { - "repo.pulls.merged_title_desc": "sammanfogade %[1]d incheckningar från %[2]s in i %[3]s %[4]s", - "repo.pulls.title_desc": "vill sammanfoga %[1]d incheckningar från s[2]s in i %[3]s", - "search.milestone_kind": "Sök milstolpar..." + "repo.pulls.merged_title_desc": { + "one": "sammanfogade %[1]d incheckning från %[2]s in i %[3]s %[4]s", + "other": "sammanfogade %[1]d incheckningar från %[2]s in i %[3]s %[4]s" + }, + "repo.pulls.title_desc": { + "one": "vill sammanfoga %[1]d incheckning från s[2]s in i %[3]s", + "other": "vill sammanfoga %[1]d incheckningar från s[2]s in i %[3]s" + }, + "search.milestone_kind": "Sök milstolpar…", + "mail.actions.not_successful_run_subject": "Arbetsflödet %[1]s misslyckades i förrådet %[2]s", + "discussion.locked": "Denna diskussion har låsts. Kommentarer är begränsade till bidragsgivare.", + "relativetime.now": "nu", + "relativetime.1day": "igår", + "relativetime.2days": "för två dagar sedan", + "relativetime.days": { + "one": "%d dag sedan", + "other": "%d dagar sedan" + }, + "admin.dashboard.cleanup_offline_runners": "Städa upp offline runners", + "repo.issue_indexer.title": "Indexerare för utgåvor", + "moderation.reported_thank_you": "Tack för din rapport. Administrationen har gjorts uppmärksam på det.", + "themes.names.forgejo-light": "Forgejo ljus", + "themes.names.forgejo-dark": "Forgejo mörk", + "themes.names.forgejo-auto": "Forgejo (följ systemets tema)", + "moderation.submit_report": "Skicka in rapport", + "moderation.reporting_failed": "Det gick inte att skicka in den nya övergreppsrapporten: %v", + "mail.actions.run_info_cur_status": "Status för denna körning: %[1]s (just uppdaterad från %[2]s)", + "mail.actions.run_info_previous_status": "Status för föregående körning: %[1]s", + "mail.actions.run_info_ref": "Gren: %[1]s (%[2]s)", + "mail.actions.run_info_trigger": "Utlöses på grund av: %[1]s av: %[2]s", + "alert.asset_load_failed": "Misslyckades med att läsa in resursfiler från {path}. Kontrollera att resursfilerna är åtkomliga.", + "install.invalid_lfs_path": "Det gick inte att skapa LFS-roten på den angivna sökvägen: %[1]s", + "alert.range_error": " måste vara ett tal mellan %[1]s och %[2]s.", + "stars.list.none": "Ingen har stjärnmarkerat detta förråd.", + "watch.list.none": "Ingen tittar på det här förrådet.", + "followers.incoming.list.self.none": "Ingen följer din profil.", + "followers.incoming.list.none": "Ingen följer den här användaren.", + "followers.outgoing.list.self.none": "Du följer inte någon.", + "followers.outgoing.list.none": "%s följer inte någon.", + "relativetime.future": "i framtiden", + "relativetime.1week": "förra veckan", + "relativetime.2weeks": "för två veckor sedan", + "relativetime.1month": "senaste månaden", + "relativetime.2months": "för två månader sedan", + "relativetime.1year": "förra året", + "relativetime.2years": "för två år sedan", + "repo.form.cannot_create": "Alla utrymmen där du kan skapa förråd har nått gränsen för antal förråd.", + "incorrect_root_url": "Denna Forgejo-instans är konfigurerad att serveras på \"%s\". Du tittar för närvarande på Forgejo via en annan URL, vilket kan leda till att delar av applikationen bryts. Den kanoniska webbadressen styrs av Forgejo-administratörer via inställningen ROOT_URL i app.ini.", + "error.not_found.title": "Sidan hittades inte", + "moderation.abuse_category.illegal_content": "Olagligt innehåll", + "moderation.abuse_category.other_violations": "Andra överträdelser av plattformsreglerna", + "moderation.report_remarks": "Anmärkningar", + "moderation.report_remarks.placeholder": "Ge några detaljer om det övergrepp som du rapporterar.", + "mail.actions.successful_run_after_failure_subject": "Arbetsflöde %[1]s återställdes i förrådet %[2]s", + "mail.actions.successful_run_after_failure": "Arbetsflöde %[1]s återställdes i förrådet %[2]s", + "mail.actions.not_successful_run": "Arbetsflödet %[1]s misslyckades i förrådet %[2]s", + "editor.textarea.shift_tab_hint": "Ingen indragning på den här raden. Tryck på Shift + Tab igen eller Escape för att lämna redigeringsläget.", + "meta.last_line": "Daniel Nylander heter jag och har översatt Forgejo. Mer information om mig på https://www.danielnylander.se", + "editor.textarea.tab_hint": "Raden är redan indragen. Tryck på Tab igen eller Escape för att lämna redigeringsläget.", + "home.welcome.no_activity": "Ingen aktivitet", + "home.welcome.activity_hint": "Det finns inget i ditt flöde ännu. Dina åtgärder och aktivitet från förråd som du bevakar kommer att visas här.", + "home.explore_repos": "Utforska förråd", + "home.explore_users": "Utforska användare", + "home.explore_orgs": "Utforska organisationer", + "relativetime.mins": { + "one": "%d minut sedan", + "other": "%d minuter sedan" + }, + "relativetime.hours": { + "one": "%d timme sedan", + "other": "%d timmar sedan" + }, + "relativetime.weeks": { + "one": "%d vecka sedan", + "other": "%d veckor sedan" + }, + "relativetime.months": { + "one": "%d månad sedan", + "other": "%d månader sedan" + }, + "relativetime.years": { + "one": "%d år sedan", + "other": "%d år sedan" + }, + "admin.config.moderation_config": "Konfiguration av moderering", + "moderation.report_abuse": "Rapportera missbruk", + "moderation.report_content": "Rapportera innehåll", + "moderation.report_abuse_form.header": "Rapportera missbruk till administratör", + "moderation.report_abuse_form.details": "Detta formulär ska användas för att rapportera användare som skapar skräpprofiler, förråd, problem, kommentarer eller beter sig olämpligt.", + "moderation.report_abuse_form.invalid": "Ogiltiga argument", + "moderation.report_abuse_form.already_reported": "Du har redan rapporterat detta innehåll", + "moderation.abuse_category": "Kategori", + "moderation.abuse_category.placeholder": "Välj en kategori", + "moderation.abuse_category.spam": "Skräppost", + "moderation.abuse_category.malware": "Skadlig kod", + "settings.visibility.description": "Profilens synlighet påverkar andras möjlighet att komma åt dina icke-privata förråd. Läs mer", + "avatar.constraints_hint": "Anpassade avatarer får inte vara större än %[1] eller %[2]dx%[3] bildpunkter" } diff --git a/options/locale_next/locale_uk-UA.json b/options/locale_next/locale_uk-UA.json index 998237c6b4..c3bcf5397c 100644 --- a/options/locale_next/locale_uk-UA.json +++ b/options/locale_next/locale_uk-UA.json @@ -88,10 +88,10 @@ "moderation.report_abuse_form.details": "Використовуйте цю форму, щоб повідомити про користувачів, які створюють спам-профілі, репозиторії, задачі, коментарі або поводяться неналежним чином.", "moderation.abuse_category": "Категорія", "moderation.abuse_category.other_violations": "Інші порушення правил платформи", - "moderation.reporting_failed": "Не вдалося надіслати нове повідомлення про порушення: %v", + "moderation.reporting_failed": "Не вдалося надіслати повідомлення про порушення: %v", "moderation.report_remarks": "Подробиці", "moderation.reported_thank_you": "Дякуємо за ваше повідомлення. Адміністрацію поінформовано про нього.", - "repo.form.cannot_create": "Усі простори, в яких ви можете створювати репозиторії, досягли максимальної кількості репозиторіїв.", + "repo.form.cannot_create": "У всіх просторах, де ви можете створювати репозиторії, досягнуто обмеження кількості репозиторіїв.", "repo.issue_indexer.title": "Індексатор задач", "followers.incoming.list.self.none": "Ніхто не стежить за вашим профілем.", "followers.incoming.list.none": "Ніхто не стежить за цим користувачем.", @@ -100,5 +100,10 @@ "stars.list.none": "Ніхто не додав цей репозиторій в обрані.", "watch.list.none": "Ніхто не спостерігає за цим репозиторієм.", "editor.textarea.tab_hint": "У рядку вже є відступ. Натисніть Tab ще раз або Esc, щоб вийти з редактора.", - "editor.textarea.shift_tab_hint": "У цьому рядку немає відступів. Натисніть Shift + Tab ще раз або Esc, щоб вийти з редактора." + "editor.textarea.shift_tab_hint": "У цьому рядку немає відступів. Натисніть Shift + Tab ще раз або Esc, щоб вийти з редактора.", + "admin.dashboard.cleanup_offline_runners": "Очистити неактивні раннери", + "settings.visibility.description": "Видимість профілю впливає на можливість інших користувачів отримати доступ до ваших неприватних репозиторіїв. Дізнатися більше", + "avatar.constraints_hint": "Розмір користувацького аватара не може перевищувати %[1]s або бути більшим за %[2]d×%[3]d пікселів", + "repo.diff.commit.next-short": "Наступний", + "repo.diff.commit.previous-short": "Попередній" } diff --git a/options/locale_next/locale_zh-CN.json b/options/locale_next/locale_zh-CN.json index 9a4874b22c..e7c1bad81e 100644 --- a/options/locale_next/locale_zh-CN.json +++ b/options/locale_next/locale_zh-CN.json @@ -68,5 +68,8 @@ "stars.list.none": "没有人点赞这个仓库。", "followers.incoming.list.self.none": "没有人关注你的个人资料。", "editor.textarea.tab_hint": "此行已缩进。再次按 Tab 或按 Escape 退出编辑器。", - "editor.textarea.shift_tab_hint": "此行无缩进。再次按 Shift + Tab 或按 Escape 退出编辑器。" + "editor.textarea.shift_tab_hint": "此行无缩进。再次按 Shift + Tab 或按 Escape 退出编辑器。", + "admin.dashboard.cleanup_offline_runners": "清理离线运行器", + "settings.visibility.description": "个人资料可见性设置会影响他人对您的非私有仓库的访问。了解更多", + "avatar.constraints_hint": "自定义头像大小不得超过 %[1]s,或大于 %[2]d×%[3]d 像素" } diff --git a/options/locale_next/locale_zh-TW.json b/options/locale_next/locale_zh-TW.json index 59933571db..5e3e43f66e 100644 --- a/options/locale_next/locale_zh-TW.json +++ b/options/locale_next/locale_zh-TW.json @@ -67,5 +67,6 @@ "moderation.report_abuse_form.header": "向管理員回報濫用", "moderation.report_abuse_form.details": "這個表單是用來檢舉用戶建立垃圾帳號、儲存庫、問題、留言,或其他不當行為。", "moderation.report_abuse_form.invalid": "無效參數", - "moderation.report_abuse_form.already_reported": "您已檢舉此內容" + "moderation.report_abuse_form.already_reported": "您已檢舉此內容", + "meta.last_line": "Rubi-chan? Hai! Nani ga suki? Choko minto yori mo a・na・ta♡ Ayumu-chan? Hai! Nani ga suki? Sutoroberii fureibaa yori mo a・na・ta♡ Shiki-chan! Hai! Nani ga suki? Kukkii and kuriimu yori mo a・na・ta♡ Minna? Hai! Nani ga suki? Mochiron daisuki AiScReam." } diff --git a/package-lock.json b/package-lock.json index 588a359444..9de06a8055 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,11 +12,12 @@ "@github/markdown-toolbar-element": "2.2.3", "@github/quote-selection": "2.1.0", "@github/text-expander-element": "2.8.0", + "@google/model-viewer": "4.1.0", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@primer/octicons": "19.14.0", "ansi_up": "6.0.5", "asciinema-player": "3.8.2", - "chart.js": "4.4.9", + "chart.js": "4.5.0", "chartjs-adapter-dayjs-4": "1.0.4", "chartjs-plugin-zoom": "2.2.0", "clippie": "4.1.7", @@ -33,13 +34,13 @@ "katex": "0.16.22", "mermaid": "11.6.0", "mini-css-extract-plugin": "2.9.2", - "minimatch": "10.0.1", + "minimatch": "10.0.3", "monaco-editor": "0.52.2", "monaco-editor-webpack-plugin": "7.1.0", "pdfobject": "2.3.0", - "postcss": "8.5.4", + "postcss": "8.5.5", "postcss-loader": "8.1.1", - "postcss-nesting": "13.0.1", + "postcss-nesting": "13.0.2", "pretty-ms": "9.0.0", "sortablejs": "1.15.6", "swagger-ui-dist": "5.17.14", @@ -64,11 +65,11 @@ "@eslint-community/eslint-plugin-eslint-comments": "4.5.0", "@playwright/test": "1.52.0", "@stoplight/spectral-cli": "6.15.0", - "@stylistic/eslint-plugin-js": "4.4.1", + "@stylistic/eslint-plugin": "4.4.1", "@stylistic/stylelint-plugin": "3.1.2", "@vitejs/plugin-vue": "5.2.4", "@vitest/coverage-v8": "3.2.3", - "@vitest/eslint-plugin": "1.2.1", + "@vitest/eslint-plugin": "1.2.2", "@vue/test-utils": "2.4.6", "eslint": "9.28.0", "eslint-import-resolver-typescript": "4.4.3", @@ -84,9 +85,9 @@ "eslint-plugin-vitest-globals": "1.5.0", "eslint-plugin-vue": "10.2.0", "eslint-plugin-vue-scoped-css": "2.10.0", - "eslint-plugin-wc": "2.2.1", + "eslint-plugin-wc": "3.0.1", "globals": "16.1.0", - "happy-dom": "17.6.3", + "happy-dom": "18.0.0", "license-checker-rseidelsohn": "4.4.2", "markdownlint-cli": "0.45.0", "postcss-html": "1.8.0", @@ -97,7 +98,7 @@ "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.2.0", "typescript": "5.8.3", - "typescript-eslint": "8.33.1", + "typescript-eslint": "8.34.0", "vite-string-plugin": "1.3.4", "vitest": "3.2.3" }, @@ -1020,9 +1021,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", + "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1034,17 +1035,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/@eslint/config-array/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1059,9 +1049,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", - "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", + "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1122,17 +1112,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -1190,19 +1169,32 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", - "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz", + "integrity": "sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.14.0", + "@eslint/core": "^0.15.0", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.0.tgz", + "integrity": "sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@github/combobox-nav": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.3.1.tgz", @@ -1231,6 +1223,22 @@ "dom-input-range": "^1.2.0" } }, + "node_modules/@google/model-viewer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@google/model-viewer/-/model-viewer-4.1.0.tgz", + "integrity": "sha512-7WB/jS6wfBfRl/tWhsUUvDMKFE1KlKME97coDLlZQfvJD0nCwjhES1lJ+k7wnmf7T3zMvCfn9mIjM/mgZapuig==", + "license": "Apache-2.0", + "dependencies": { + "@monogrid/gainmap-js": "^3.1.0", + "lit": "^3.2.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "three": "^0.172.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1748,6 +1756,27 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1992,6 +2021,21 @@ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", "license": "MIT" }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz", + "integrity": "sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.0.tgz", + "integrity": "sha512-L2qyoZSQClcBmq0qajBVbhYEcG6iK0XfLn66ifLe/RfC0/ihpc+pl0Wdn8bJ8o+hj38cG0fGXRgSS20MuXn7qA==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0" + } + }, "node_modules/@mcaptcha/core-glue": { "version": "0.1.0-alpha-5", "resolved": "https://registry.npmjs.org/@mcaptcha/core-glue/-/core-glue-0.1.0-alpha-5.tgz", @@ -2052,6 +2096,18 @@ "langium": "3.3.1" } }, + "node_modules/@monogrid/gainmap-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.1.0.tgz", + "integrity": "sha512-Obb0/gEd/HReTlg8ttaYk+0m62gQJmCblMOjHSMHRrBP2zdfKMHLCRbh/6ex9fSUJMKdjjIEiohwkbGD3wj2Nw==", + "license": "MIT", + "dependencies": { + "promise-worker-transferable": "^1.0.4" + }, + "peerDependencies": { + "three": ">= 0.159.0" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.11", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz", @@ -2213,9 +2269,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.42.0.tgz", - "integrity": "sha512-gldmAyS9hpj+H6LpRNlcjQWbuKUtb94lodB9uCz71Jm+7BxK1VIOo7y62tZZwxhA7j1ylv/yQz080L5WkS+LoQ==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.43.0.tgz", + "integrity": "sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==", "cpu": [ "arm" ], @@ -2227,9 +2283,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.42.0.tgz", - "integrity": "sha512-bpRipfTgmGFdCZDFLRvIkSNO1/3RGS74aWkJJTFJBH7h3MRV4UijkaEUeOMbi9wxtxYmtAbVcnMtHTPBhLEkaw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.43.0.tgz", + "integrity": "sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==", "cpu": [ "arm64" ], @@ -2241,9 +2297,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.42.0.tgz", - "integrity": "sha512-JxHtA081izPBVCHLKnl6GEA0w3920mlJPLh89NojpU2GsBSB6ypu4erFg/Wx1qbpUbepn0jY4dVWMGZM8gplgA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.43.0.tgz", + "integrity": "sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==", "cpu": [ "arm64" ], @@ -2255,9 +2311,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.42.0.tgz", - "integrity": "sha512-rv5UZaWVIJTDMyQ3dCEK+m0SAn6G7H3PRc2AZmExvbDvtaDc+qXkei0knQWcI3+c9tEs7iL/4I4pTQoPbNL2SA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.43.0.tgz", + "integrity": "sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==", "cpu": [ "x64" ], @@ -2269,9 +2325,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.42.0.tgz", - "integrity": "sha512-fJcN4uSGPWdpVmvLuMtALUFwCHgb2XiQjuECkHT3lWLZhSQ3MBQ9pq+WoWeJq2PrNxr9rPM1Qx+IjyGj8/c6zQ==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.43.0.tgz", + "integrity": "sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==", "cpu": [ "arm64" ], @@ -2283,9 +2339,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.42.0.tgz", - "integrity": "sha512-CziHfyzpp8hJpCVE/ZdTizw58gr+m7Y2Xq5VOuCSrZR++th2xWAz4Nqk52MoIIrV3JHtVBhbBsJcAxs6NammOQ==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.43.0.tgz", + "integrity": "sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==", "cpu": [ "x64" ], @@ -2297,9 +2353,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.42.0.tgz", - "integrity": "sha512-UsQD5fyLWm2Fe5CDM7VPYAo+UC7+2Px4Y+N3AcPh/LdZu23YcuGPegQly++XEVaC8XUTFVPscl5y5Cl1twEI4A==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.43.0.tgz", + "integrity": "sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==", "cpu": [ "arm" ], @@ -2311,9 +2367,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.42.0.tgz", - "integrity": "sha512-/i8NIrlgc/+4n1lnoWl1zgH7Uo0XK5xK3EDqVTf38KvyYgCU/Rm04+o1VvvzJZnVS5/cWSd07owkzcVasgfIkQ==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.43.0.tgz", + "integrity": "sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==", "cpu": [ "arm" ], @@ -2325,9 +2381,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.42.0.tgz", - "integrity": "sha512-eoujJFOvoIBjZEi9hJnXAbWg+Vo1Ov8n/0IKZZcPZ7JhBzxh2A+2NFyeMZIRkY9iwBvSjloKgcvnjTbGKHE44Q==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.43.0.tgz", + "integrity": "sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==", "cpu": [ "arm64" ], @@ -2339,9 +2395,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.42.0.tgz", - "integrity": "sha512-/3NrcOWFSR7RQUQIuZQChLND36aTU9IYE4j+TB40VU78S+RA0IiqHR30oSh6P1S9f9/wVOenHQnacs/Byb824g==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.43.0.tgz", + "integrity": "sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==", "cpu": [ "arm64" ], @@ -2353,9 +2409,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.42.0.tgz", - "integrity": "sha512-O8AplvIeavK5ABmZlKBq9/STdZlnQo7Sle0LLhVA7QT+CiGpNVe197/t8Aph9bhJqbDVGCHpY2i7QyfEDDStDg==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.43.0.tgz", + "integrity": "sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==", "cpu": [ "loong64" ], @@ -2367,9 +2423,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.42.0.tgz", - "integrity": "sha512-6Qb66tbKVN7VyQrekhEzbHRxXXFFD8QKiFAwX5v9Xt6FiJ3BnCVBuyBxa2fkFGqxOCSGGYNejxd8ht+q5SnmtA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.43.0.tgz", + "integrity": "sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==", "cpu": [ "ppc64" ], @@ -2381,9 +2437,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.42.0.tgz", - "integrity": "sha512-KQETDSEBamQFvg/d8jajtRwLNBlGc3aKpaGiP/LvEbnmVUKlFta1vqJqTrvPtsYsfbE/DLg5CC9zyXRX3fnBiA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.43.0.tgz", + "integrity": "sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==", "cpu": [ "riscv64" ], @@ -2395,9 +2451,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.42.0.tgz", - "integrity": "sha512-qMvnyjcU37sCo/tuC+JqeDKSuukGAd+pVlRl/oyDbkvPJ3awk6G6ua7tyum02O3lI+fio+eM5wsVd66X0jQtxw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.43.0.tgz", + "integrity": "sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==", "cpu": [ "riscv64" ], @@ -2409,9 +2465,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.42.0.tgz", - "integrity": "sha512-I2Y1ZUgTgU2RLddUHXTIgyrdOwljjkmcZ/VilvaEumtS3Fkuhbw4p4hgHc39Ypwvo2o7sBFNl2MquNvGCa55Iw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.43.0.tgz", + "integrity": "sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==", "cpu": [ "s390x" ], @@ -2423,9 +2479,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.42.0.tgz", - "integrity": "sha512-Gfm6cV6mj3hCUY8TqWa63DB8Mx3NADoFwiJrMpoZ1uESbK8FQV3LXkhfry+8bOniq9pqY1OdsjFWNsSbfjPugw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.43.0.tgz", + "integrity": "sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==", "cpu": [ "x64" ], @@ -2437,9 +2493,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.42.0.tgz", - "integrity": "sha512-g86PF8YZ9GRqkdi0VoGlcDUb4rYtQKyTD1IVtxxN4Hpe7YqLBShA7oHMKU6oKTCi3uxwW4VkIGnOaH/El8de3w==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.43.0.tgz", + "integrity": "sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==", "cpu": [ "x64" ], @@ -2451,9 +2507,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.42.0.tgz", - "integrity": "sha512-+axkdyDGSp6hjyzQ5m1pgcvQScfHnMCcsXkx8pTgy/6qBmWVhtRVlgxjWwDp67wEXXUr0x+vD6tp5W4x6V7u1A==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.43.0.tgz", + "integrity": "sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==", "cpu": [ "arm64" ], @@ -2465,9 +2521,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.42.0.tgz", - "integrity": "sha512-F+5J9pelstXKwRSDq92J0TEBXn2nfUrQGg+HK1+Tk7VOL09e0gBqUHugZv7SW4MGrYj41oNCUe3IKCDGVlis2g==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.43.0.tgz", + "integrity": "sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==", "cpu": [ "ia32" ], @@ -2479,9 +2535,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.42.0.tgz", - "integrity": "sha512-LpHiJRwkaVz/LqjHjK8LCi8osq7elmpwujwbXKNW88bM8eeGxavJIKKjkjpMHAh/2xfnrt1ZSnhTv41WYUHYmA==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.43.0.tgz", + "integrity": "sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==", "cpu": [ "x64" ], @@ -2701,17 +2757,6 @@ "node": "^12.20 || >=14.13" } }, - "node_modules/@stoplight/spectral-core/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/@stoplight/spectral-core/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3010,15 +3055,18 @@ "node": "^12.20 || >=14.13" } }, - "node_modules/@stylistic/eslint-plugin-js": { + "node_modules/@stylistic/eslint-plugin": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-4.4.1.tgz", - "integrity": "sha512-eLisyHvx7Sel8vcFZOEwDEBGmYsYM1SqDn81BWgmbqEXfXRf8oe6Rwp+ryM/8odNjlxtaaxp0Ihmt86CnLAxKg==", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.4.1.tgz", + "integrity": "sha512-CEigAk7eOLyHvdgmpZsKFwtiqS2wFwI1fn4j09IU9GmD4euFM4jEBAViWeCqaNLlbX2k2+A/Fq9cje4HQBXuJQ==", "dev": true, "license": "MIT", "dependencies": { + "@typescript-eslint/utils": "^8.32.1", "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0" + "espree": "^10.3.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3027,6 +3075,19 @@ "eslint": ">=9.0.0" } }, + "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@stylistic/stylelint-plugin": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-3.1.2.tgz", @@ -3448,9 +3509,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.15.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", - "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", + "version": "20.19.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.0.tgz", + "integrity": "sha512-hfrc+1tud1xcdVTABC2JiomZJEklMcXYNTVtZLAeqTVWD+qL5jkHKT+1lOtqDdGxt+mB53DTtiz673vfjU8D1Q==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -3476,8 +3537,7 @@ "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/@types/unist": { "version": "2.0.11", @@ -3493,18 +3553,25 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "dev": true, + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.1.tgz", - "integrity": "sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz", + "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/type-utils": "8.33.1", - "@typescript-eslint/utils": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/scope-manager": "8.34.0", + "@typescript-eslint/type-utils": "8.34.0", + "@typescript-eslint/utils": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -3518,7 +3585,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.33.1", + "@typescript-eslint/parser": "^8.34.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } @@ -3534,16 +3601,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.1.tgz", - "integrity": "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz", + "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/scope-manager": "8.34.0", + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/typescript-estree": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0", "debug": "^4.3.4" }, "engines": { @@ -3559,14 +3626,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.1.tgz", - "integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz", + "integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.33.1", - "@typescript-eslint/types": "^8.33.1", + "@typescript-eslint/tsconfig-utils": "^8.34.0", + "@typescript-eslint/types": "^8.34.0", "debug": "^4.3.4" }, "engines": { @@ -3581,14 +3648,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz", - "integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz", + "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1" + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3599,9 +3666,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz", - "integrity": "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz", + "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==", "dev": true, "license": "MIT", "engines": { @@ -3616,14 +3683,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.1.tgz", - "integrity": "sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz", + "integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.33.1", - "@typescript-eslint/utils": "8.33.1", + "@typescript-eslint/typescript-estree": "8.34.0", + "@typescript-eslint/utils": "8.34.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -3640,9 +3707,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz", - "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz", + "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==", "dev": true, "license": "MIT", "engines": { @@ -3654,16 +3721,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz", - "integrity": "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz", + "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.33.1", - "@typescript-eslint/tsconfig-utils": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/visitor-keys": "8.33.1", + "@typescript-eslint/project-service": "8.34.0", + "@typescript-eslint/tsconfig-utils": "8.34.0", + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/visitor-keys": "8.34.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3682,6 +3749,23 @@ "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -3699,16 +3783,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.1.tgz", - "integrity": "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz", + "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.33.1", - "@typescript-eslint/types": "8.33.1", - "@typescript-eslint/typescript-estree": "8.33.1" + "@typescript-eslint/scope-manager": "8.34.0", + "@typescript-eslint/types": "8.34.0", + "@typescript-eslint/typescript-estree": "8.34.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3723,13 +3807,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz", - "integrity": "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz", + "integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.33.1", + "@typescript-eslint/types": "8.34.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -3740,10 +3824,38 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.9.0.tgz", + "integrity": "sha512-h1T2c2Di49ekF2TE8ZCoJkb+jwETKUIPDJ/nO3tJBKlLFPu+fyd93f0rGP/BvArKx2k2HlRM4kqkNarj3dvZlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.9.0.tgz", + "integrity": "sha512-sG1NHtgXtX8owEkJ11yn34vt0Xqzi3k9TJ8zppDmyG8GZV4kVWw44FHwKwHeEFl07uKPeC4ZoyuQaGh5ruJYPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.11.tgz", - "integrity": "sha512-i3/wlWjQJXMh1uiGtiv7k1EYvrrS3L1hdwmWJJiz1D8jWy726YFYPIxQWbEIVPVAgrfRR0XNlLrTQwq17cuCGw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.0.tgz", + "integrity": "sha512-nJ9z47kfFnCxN1z/oYZS7HSNsFh43y2asePzTEZpEvK7kGyuShSl3RRXnm/1QaqFL+iP+BjMwuB+DYUymOkA5A==", "cpu": [ "arm64" ], @@ -3755,9 +3867,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.7.11.tgz", - "integrity": "sha512-8XXyFvc6w6kmMmi6VYchZhjd5CDcp+Lv6Cn1YmUme0ypsZ/0Kzd+9ESrWtDrWibKPTgSteDTxp75cvBOY64FQQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.0.tgz", + "integrity": "sha512-TK+UA1TTa0qS53rjWn7cVlEKVGz2B6JYe0C++TdQjvWYIyx83ruwh0wd4LRxYBM5HeuAzXcylA9BH2trARXJTw==", "cpu": [ "x64" ], @@ -3769,9 +3881,9 @@ ] }, "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.7.11.tgz", - "integrity": "sha512-0qJBYzP8Qk24CZ05RSWDQUjdiQUeIJGfqMMzbtXgCKl/a5xa6thfC0MQkGIr55LCLd6YmMyO640ifYUa53lybQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.0.tgz", + "integrity": "sha512-6uZwzMRFcD7CcCd0vz3Hp+9qIL2jseE/bx3ZjaLwn8t714nYGwiE84WpaMCYjU+IQET8Vu/+BNAGtYD7BG/0yA==", "cpu": [ "x64" ], @@ -3783,9 +3895,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.7.11.tgz", - "integrity": "sha512-1sGwpgvx+WZf0GFT6vkkOm6UJ+mlsVnjw+Yv9esK71idWeRAG3bbpkf3AoY8KIqKqmnzJExi0uKxXpakQ5Pcbg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.0.tgz", + "integrity": "sha512-bPUBksQfrgcfv2+mm+AZinaKq8LCFvt5PThYqRotqSuuZK1TVKkhbVMS/jvSRfYl7jr3AoZLYbDkItxgqMKRkg==", "cpu": [ "arm" ], @@ -3797,9 +3909,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.7.11.tgz", - "integrity": "sha512-D/1F/2lTe+XAl3ohkYj51NjniVly8sIqkA/n1aOND3ZMO418nl2JNU95iVa1/RtpzaKcWEsNTtHRogykrUflJg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.0.tgz", + "integrity": "sha512-uT6E7UBIrTdCsFQ+y0tQd3g5oudmrS/hds5pbU3h4s2t/1vsGWbbSKhBSCD9mcqaqkBwoqlECpUrRJCmldl8PA==", "cpu": [ "arm" ], @@ -3811,9 +3923,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.7.11.tgz", - "integrity": "sha512-7vFWHLCCNFLEQlmwKQfVy066ohLLArZl+AV/AdmrD1/pD1FlmqM+FKbtnONnIwbHtgetFUCV/SRi1q4D49aTlw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.0.tgz", + "integrity": "sha512-vdqBh911wc5awE2bX2zx3eflbyv8U9xbE/jVKAm425eRoOVv/VseGZsqi3A3SykckSpF4wSROkbQPvbQFn8EsA==", "cpu": [ "arm64" ], @@ -3825,9 +3937,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.7.11.tgz", - "integrity": "sha512-tYkGIx8hjWPh4zcn17jLEHU8YMmdP2obRTGkdaB3BguGHh31VCS3ywqC4QjTODjmhhNyZYkj/1Dz/+0kKvg9YA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.0.tgz", + "integrity": "sha512-/8JFZ/SnuDr1lLEVsxsuVwrsGquTvT51RZGvyDB/dOK3oYK2UqeXzgeyq6Otp8FZXQcEYqJwxb9v+gtdXn03eQ==", "cpu": [ "arm64" ], @@ -3839,9 +3951,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.7.11.tgz", - "integrity": "sha512-6F328QIUev29vcZeRX6v6oqKxfUoGwIIAhWGD8wSysnBYFY0nivp25jdWmAb1GildbCCaQvOKEhCok7YfWkj4Q==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.0.tgz", + "integrity": "sha512-FkJjybtrl+rajTw4loI3L6YqSOpeZfDls4SstL/5lsP2bka9TiHUjgMBjygeZEis1oC8LfJTS8FSgpKPaQx2tQ==", "cpu": [ "ppc64" ], @@ -3853,9 +3965,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.7.11.tgz", - "integrity": "sha512-NqhWmiGJGdzbZbeucPZIG9Iav4lyYLCarEnxAceguMx9qlpeEF7ENqYKOwB8Zqk7/CeuYMEcLYMaW2li6HyDzQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.0.tgz", + "integrity": "sha512-w/NZfHNeDusbqSZ8r/hp8iL4S39h4+vQMc9/vvzuIKMWKppyUGKm3IST0Qv0aOZ1rzIbl9SrDeIqK86ZpUK37w==", "cpu": [ "riscv64" ], @@ -3867,9 +3979,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.7.11.tgz", - "integrity": "sha512-J2RPIFKMdTrLtBdfR1cUMKl8Gcy05nlQ+bEs/6al7EdWLk9cs3tnDREHZ7mV9uGbeghpjo4i8neNZNx3PYUY9w==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.0.tgz", + "integrity": "sha512-bEPBosut8/8KQbUixPry8zg/fOzVOWyvwzOfz0C0Rw6dp+wIBseyiHKjkcSyZKv/98edrbMknBaMNJfA/UEdqw==", "cpu": [ "riscv64" ], @@ -3881,9 +3993,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.7.11.tgz", - "integrity": "sha512-bDpGRerHvvHdhun7MmFUNDpMiYcJSqWckwAVVRTJf8F+RyqYJOp/mx04PDc7DhpNPeWdnTMu91oZRMV+gGaVcQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.0.tgz", + "integrity": "sha512-LDtMT7moE3gK753gG4pc31AAqGUC86j3AplaFusc717EUGF9ZFJ356sdQzzZzkBk1XzMdxFyZ4f/i35NKM/lFA==", "cpu": [ "s390x" ], @@ -3895,9 +4007,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.7.11.tgz", - "integrity": "sha512-G9U7bVmylzRLma3cK39RBm3guoD1HOvY4o0NS4JNm37AD0lS7/xyMt7kn0JejYyc0Im8J+rH69/dXGM9DAJcSQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.0.tgz", + "integrity": "sha512-WmFd5KINHIXj8o1mPaT8QRjA9HgSXhN1gl9Da4IZihARihEnOylu4co7i/yeaIpcfsI6sYs33cNZKyHYDh0lrA==", "cpu": [ "x64" ], @@ -3909,9 +4021,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.7.11.tgz", - "integrity": "sha512-7qL20SBKomekSunm7M9Fe5L93bFbn+FbHiGJbfTlp0RKhPVoJDP73vOxf1QrmJHyDPECsGWPFnKa/f8fO2FsHw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.0.tgz", + "integrity": "sha512-CYuXbANW+WgzVRIl8/QvZmDaZxrqvOldOwlbUjIM4pQ46FJ0W5cinJ/Ghwa/Ng1ZPMJMk1VFdsD/XwmCGIXBWg==", "cpu": [ "x64" ], @@ -3923,9 +4035,9 @@ ] }, "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.7.11.tgz", - "integrity": "sha512-jisvIva8MidjI+B1lFRZZMfCPaCISePgTyR60wNT1MeQvIh5Ksa0G3gvI+Iqyj3jqYbvOHByenpa5eDGcSdoSg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.0.tgz", + "integrity": "sha512-6Rp2WH0OoitMYR57Z6VE8Y6corX8C6QEMWLgOV6qXiJIeZ1F9WGXY/yQ8yDC4iTraotyLOeJ2Asea0urWj2fKQ==", "cpu": [ "wasm32" ], @@ -3933,16 +4045,16 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.10" + "@napi-rs/wasm-runtime": "^0.2.11" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.7.11.tgz", - "integrity": "sha512-G+H5nQZ8sRZ8ebMY6mRGBBvTEzMYEcgVauLsNHpvTUavZoCCRVP1zWkCZgOju2dW3O22+8seTHniTdl1/uLz3g==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.0.tgz", + "integrity": "sha512-rknkrTRuvujprrbPmGeHi8wYWxmNVlBoNW8+4XF2hXUnASOjmuC9FNF1tGbDiRQWn264q9U/oGtixyO3BT8adQ==", "cpu": [ "arm64" ], @@ -3954,9 +4066,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.7.11.tgz", - "integrity": "sha512-Hfy46DBfFzyv0wgR0MMOwFFib2W2+Btc8oE5h4XlPhpelnSyA6nFxkVIyTgIXYGTdFaLoZFNn62fmqx3rjEg3A==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.0.tgz", + "integrity": "sha512-Ceymm+iBl+bgAICtgiHyMLz6hjxmLJKqBim8tDzpX61wpZOx2bPK6Gjuor7I2RiUynVjvvkoRIkrPyMwzBzF3A==", "cpu": [ "ia32" ], @@ -3968,9 +4080,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.7.11.tgz", - "integrity": "sha512-7L8NdsQlCJ8T106Gbz/AjzM4QKWVsoQbKpB9bMBGcIZswUuAnJMHpvbqGW3RBqLHCIwX4XZ5fxSBHEFcK2h9wA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.0.tgz", + "integrity": "sha512-k59o9ZyeyS0hAlcaKFezYSH2agQeRFEB7KoQLXl3Nb3rgkqT1NY9Vwy+SqODiLmYnEjxWJVRE/yq2jFVqdIxZw==", "cpu": [ "x64" ], @@ -4040,9 +4152,9 @@ } }, "node_modules/@vitest/eslint-plugin": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.2.1.tgz", - "integrity": "sha512-JQr1jdVcrsoS7Sdzn83h9sq4DvREf9Q/onTZbJCqTVlv/76qb+TZrLv/9VhjnjSMHweQH5FdpMDeCR6aDe2fgw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.2.2.tgz", + "integrity": "sha512-R8NwW+VxyKqVGcMfYsUbdThQyMbtNcoeg+jJeTgMHqWdFdcS0nrODAQXhkplvWzgd7jIJ+GQeydGqFLibsxMxg==", "dev": true, "license": "MIT", "dependencies": { @@ -4958,9 +5070,10 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -5012,14 +5125,23 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, + "node_modules/brace-expansion/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -5146,9 +5268,9 @@ } }, "node_modules/cacheable/node_modules/keyv": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.3.tgz", - "integrity": "sha512-Rwu4+nXI9fqcxiEHtbkvoes2X+QfkTRo1TMkPfwzipGsJlJO/z69vqB4FNl9xJ3xCpAcbkvmEabZfPzrwN3+gQ==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.3.4.tgz", + "integrity": "sha512-ypEvQvInNpUe+u+w8BIcPkQvEqXquyyibWE/1NB5T2BTzIpS5cGEV1LZskDzPSTvNAaT4+5FutvzlvnkxOSKlw==", "dev": true, "license": "MIT", "dependencies": { @@ -5224,9 +5346,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001721", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", - "integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==", + "version": "1.0.30001723", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz", + "integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==", "funding": [ { "type": "opencollective", @@ -5310,9 +5432,9 @@ } }, "node_modules/chart.js": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz", - "integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", + "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", "license": "MIT", "dependencies": { "@kurkle/color": "^0.3.0" @@ -5652,13 +5774,13 @@ } }, "node_modules/core-js-compat": { - "version": "3.42.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.42.0.tgz", - "integrity": "sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==", + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz", + "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.24.4" + "browserslist": "^4.25.0" }, "funding": { "type": "opencollective", @@ -6456,9 +6578,9 @@ } }, "node_modules/decode-named-character-reference": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz", - "integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", "dev": true, "license": "MIT", "dependencies": { @@ -6760,6 +6882,23 @@ "node": ">=14" } }, + "node_modules/editorconfig/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/editorconfig/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/editorconfig/node_modules/minimatch": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", @@ -6777,9 +6916,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.165", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz", - "integrity": "sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==", + "version": "1.5.167", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.167.tgz", + "integrity": "sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -7416,6 +7555,23 @@ "eslint": "^8.0.0 || ^9.0.0" } }, + "node_modules/eslint-plugin-sonarjs/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-sonarjs/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/eslint-plugin-sonarjs/node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -7586,23 +7742,23 @@ } }, "node_modules/eslint-plugin-wc": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-2.2.1.tgz", - "integrity": "sha512-KstLqGmyQz088DvFlDYHg0sHih+w2QeulreCi1D1ftr357klO2zqHdG/bbnNMmuQdVFDuNkopNIyNhmG0XCT/g==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-3.0.1.tgz", + "integrity": "sha512-0p1wkSlA2Ue3FA4qW+5LZ+15sy0p1nUyVl1eyBMLq4rtN1LtE9IdI49BXNWMz8N8bM/y7Ulx8SWGAni5f8XO5g==", "dev": true, "license": "MIT", "dependencies": { "is-valid-element-name": "^1.0.0", - "js-levenshtein-esm": "^1.2.0" + "js-levenshtein-esm": "^2.0.0" }, "peerDependencies": { "eslint": ">=8.40.0" } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -7617,9 +7773,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -7653,17 +7809,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/eslint/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -7685,15 +7830,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -8264,17 +8409,6 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "license": "BSD-2-Clause" }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/glob/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -8429,13 +8563,14 @@ } }, "node_modules/happy-dom": { - "version": "17.6.3", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-17.6.3.tgz", - "integrity": "sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-18.0.0.tgz", + "integrity": "sha512-o3p2Axi1EdIfMaOUulDzO/5yXzLLV0g/54eLPVrkt3u20r3yOuOenHpyp2clAJ0eHMc+HyE139ulQxl+8pEJIw==", "dev": true, "license": "MIT", "dependencies": { - "webidl-conversions": "^7.0.0", + "@types/node": "^20.0.0", + "@types/whatwg-mimetype": "^3.0.2", "whatwg-mimetype": "^3.0.0" }, "engines": { @@ -8676,6 +8811,12 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/immer": { "version": "9.0.21", "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", @@ -9215,6 +9356,12 @@ "dev": true, "license": "MIT" }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "license": "MIT" + }, "node_modules/is-proto-prop": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/is-proto-prop/-/is-proto-prop-3.0.1.tgz", @@ -9551,6 +9698,23 @@ "node": ">=14" } }, + "node_modules/js-beautify/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-beautify/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/js-beautify/node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -9599,9 +9763,9 @@ } }, "node_modules/js-levenshtein-esm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-1.2.0.tgz", - "integrity": "sha512-fzreKVq1eD7eGcQr7MtRpQH94f8gIfhdrc7yeih38xh684TNMK9v5aAu2wxfIRMk/GpAJRrzcirMAPIaSDaByQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-2.0.0.tgz", + "integrity": "sha512-1n4LEPOL4wRXY8rOQcuA7Iuaphe5xCMayvufCzlLAi+hRsnBRDbSS6XPuV58CBVJxj5D9ApFLyjQ7KzFToyHBw==", "dev": true, "license": "MIT" }, @@ -9914,6 +10078,15 @@ "npm": ">=8" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -9942,6 +10115,37 @@ "uc.micro": "^2.0.0" } }, + "node_modules/lit": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.0.tgz", + "integrity": "sha512-DGVsqsOIHBww2DqnuZzW7QsuCdahp50ojuDaBPC7jUDRpYoH0z7kHBBYZewRzer75FwtrkmkKk7iOAwSaWdBmw==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.0.tgz", + "integrity": "sha512-MGrXJVAI5x+Bfth/pU9Kst1iWID6GHDLEzFEnyULB/sFiRLgkd8NPK/PeeXxktA3T6EIIaq8U3KcbTU5XFcP2Q==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-html": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.0.tgz", + "integrity": "sha512-RHoswrFAxY2d8Cf2mm4OZ1DgzCoBKUKSPvA1fhtSELxUERq2aQQ2h05pO9j81gS1o7RIRJ+CePLogfyahwmynw==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -10199,15 +10403,15 @@ } }, "node_modules/markdownlint-cli/node_modules/glob": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz", - "integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", + "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", "dev": true, "license": "ISC", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.0.3", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" @@ -11004,12 +11208,12 @@ } }, "node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { "node": "20 || >=22" @@ -11828,9 +12032,9 @@ } }, "node_modules/postcss": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", - "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", + "version": "8.5.5", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.5.tgz", + "integrity": "sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==", "funding": [ { "type": "opencollective", @@ -12049,9 +12253,9 @@ } }, "node_modules/postcss-nesting": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.1.tgz", - "integrity": "sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz", + "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==", "funding": [ { "type": "github", @@ -12064,7 +12268,7 @@ ], "license": "MIT-0", "dependencies": { - "@csstools/selector-resolve-nested": "^3.0.0", + "@csstools/selector-resolve-nested": "^3.1.0", "@csstools/selector-specificity": "^5.0.0", "postcss-selector-parser": "^7.0.0" }, @@ -12254,6 +12458,16 @@ "dev": true, "license": "Unlicense" }, + "node_modules/promise-worker-transferable": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz", + "integrity": "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==", + "license": "Apache-2.0", + "dependencies": { + "is-promise": "^2.1.0", + "lie": "^3.0.2" + } + }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -12385,6 +12599,23 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/read-package-json/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/read-package-json/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/read-package-json/node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -13722,13 +13953,6 @@ "postcss-selector-parser": "^7.0.0" } }, - "node_modules/stylelint/node_modules/balanced-match": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", - "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", - "dev": true, - "license": "MIT" - }, "node_modules/stylelint/node_modules/file-entry-cache": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-10.1.1.tgz", @@ -13871,6 +14095,21 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/sucrase/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/sucrase/node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -14137,9 +14376,9 @@ } }, "node_modules/terser": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.41.0.tgz", - "integrity": "sha512-H406eLPXpZbAX14+B8psIuvIr8+3c+2hkuYzpMkoE0ij+NdsVATbA78vb8neA/eqrj7rywa2pIkdmWRsXW6wmw==", + "version": "5.42.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.42.0.tgz", + "integrity": "sha512-UYCvU9YQW2f/Vwl+P0GfhxJxbUGLwd+5QrrGgLajzWAtC/23AX0vcise32kkP7Eu0Wu9VlzzHAXkLObgjQfFlQ==", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -14209,6 +14448,23 @@ "node": ">=18" } }, + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/test-exclude/node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -14274,6 +14530,13 @@ "node": ">=0.8" } }, + "node_modules/three": { + "version": "0.172.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.172.0.tgz", + "integrity": "sha512-6HMgMlzU97MsV7D/tY8Va38b83kz8YJX+BefKjspMNAv0Vx6dxMogHOrnRl/sbMIs3BPUKijPqDqJ/+UwJbIow==", + "license": "MIT", + "peer": true + }, "node_modules/throttle-debounce": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.0.tgz", @@ -14320,9 +14583,9 @@ } }, "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", - "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "dev": true, "license": "MIT", "peerDependencies": { @@ -14609,15 +14872,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.33.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.33.1.tgz", - "integrity": "sha512-AgRnV4sKkWOiZ0Kjbnf5ytTJXMUZQ0qhSVdQtDNYLPLnjsATEYhaO94GlRQwi4t4gO8FfjM6NnikHeKjUm8D7A==", + "version": "8.34.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.0.tgz", + "integrity": "sha512-MRpfN7uYjTrTGigFCt8sRyNqJFhjN0WwZecldaqhWm+wy0gaRt8Edb/3cuUy0zdq2opJWT6iXINKAtewnDOltQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.33.1", - "@typescript-eslint/parser": "8.33.1", - "@typescript-eslint/utils": "8.33.1" + "@typescript-eslint/eslint-plugin": "8.34.0", + "@typescript-eslint/parser": "8.34.0", + "@typescript-eslint/utils": "8.34.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -14692,9 +14955,9 @@ } }, "node_modules/unrs-resolver": { - "version": "1.7.11", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.11.tgz", - "integrity": "sha512-OhuAzBImFPjKNgZ2JwHMfGFUA6NSbRegd1+BPjC1Y0E6X9Y/vJ4zKeGmIMqmlYboj6cMNEwKI+xQisrg4J0HaQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.0.tgz", + "integrity": "sha512-wqaRu4UnzBD2ABTC1kLfBjAqIDZ5YUTr/MLGa7By47JV1bJDSW7jq/ZSLigB7enLe7ubNaJhtnBXgrc/50cEhg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -14705,23 +14968,25 @@ "url": "https://opencollective.com/unrs-resolver" }, "optionalDependencies": { - "@unrs/resolver-binding-darwin-arm64": "1.7.11", - "@unrs/resolver-binding-darwin-x64": "1.7.11", - "@unrs/resolver-binding-freebsd-x64": "1.7.11", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.7.11", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.7.11", - "@unrs/resolver-binding-linux-arm64-gnu": "1.7.11", - "@unrs/resolver-binding-linux-arm64-musl": "1.7.11", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.7.11", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.7.11", - "@unrs/resolver-binding-linux-riscv64-musl": "1.7.11", - "@unrs/resolver-binding-linux-s390x-gnu": "1.7.11", - "@unrs/resolver-binding-linux-x64-gnu": "1.7.11", - "@unrs/resolver-binding-linux-x64-musl": "1.7.11", - "@unrs/resolver-binding-wasm32-wasi": "1.7.11", - "@unrs/resolver-binding-win32-arm64-msvc": "1.7.11", - "@unrs/resolver-binding-win32-ia32-msvc": "1.7.11", - "@unrs/resolver-binding-win32-x64-msvc": "1.7.11" + "@unrs/resolver-binding-android-arm-eabi": "1.9.0", + "@unrs/resolver-binding-android-arm64": "1.9.0", + "@unrs/resolver-binding-darwin-arm64": "1.9.0", + "@unrs/resolver-binding-darwin-x64": "1.9.0", + "@unrs/resolver-binding-freebsd-x64": "1.9.0", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.0", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.9.0", + "@unrs/resolver-binding-linux-arm64-gnu": "1.9.0", + "@unrs/resolver-binding-linux-arm64-musl": "1.9.0", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.9.0", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.9.0", + "@unrs/resolver-binding-linux-riscv64-musl": "1.9.0", + "@unrs/resolver-binding-linux-s390x-gnu": "1.9.0", + "@unrs/resolver-binding-linux-x64-gnu": "1.9.0", + "@unrs/resolver-binding-linux-x64-musl": "1.9.0", + "@unrs/resolver-binding-wasm32-wasi": "1.9.0", + "@unrs/resolver-binding-win32-arm64-msvc": "1.9.0", + "@unrs/resolver-binding-win32-ia32-msvc": "1.9.0", + "@unrs/resolver-binding-win32-x64-msvc": "1.9.0" } }, "node_modules/update-browserslist-db": { @@ -14940,9 +15205,9 @@ "license": "MIT" }, "node_modules/vite/node_modules/fdir": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz", - "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "dev": true, "license": "MIT", "peerDependencies": { @@ -14983,9 +15248,9 @@ } }, "node_modules/vite/node_modules/rollup": { - "version": "4.42.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.42.0.tgz", - "integrity": "sha512-LW+Vse3BJPyGJGAJt1j8pWDKPd73QM8cRXYK1IxOBgL2AGLu7Xd2YOW0M2sLUBCkF5MshXXtMApyEAEzMVMsnw==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.43.0.tgz", + "integrity": "sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==", "dev": true, "license": "MIT", "dependencies": { @@ -14999,26 +15264,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.42.0", - "@rollup/rollup-android-arm64": "4.42.0", - "@rollup/rollup-darwin-arm64": "4.42.0", - "@rollup/rollup-darwin-x64": "4.42.0", - "@rollup/rollup-freebsd-arm64": "4.42.0", - "@rollup/rollup-freebsd-x64": "4.42.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.42.0", - "@rollup/rollup-linux-arm-musleabihf": "4.42.0", - "@rollup/rollup-linux-arm64-gnu": "4.42.0", - "@rollup/rollup-linux-arm64-musl": "4.42.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.42.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.42.0", - "@rollup/rollup-linux-riscv64-gnu": "4.42.0", - "@rollup/rollup-linux-riscv64-musl": "4.42.0", - "@rollup/rollup-linux-s390x-gnu": "4.42.0", - "@rollup/rollup-linux-x64-gnu": "4.42.0", - "@rollup/rollup-linux-x64-musl": "4.42.0", - "@rollup/rollup-win32-arm64-msvc": "4.42.0", - "@rollup/rollup-win32-ia32-msvc": "4.42.0", - "@rollup/rollup-win32-x64-msvc": "4.42.0", + "@rollup/rollup-android-arm-eabi": "4.43.0", + "@rollup/rollup-android-arm64": "4.43.0", + "@rollup/rollup-darwin-arm64": "4.43.0", + "@rollup/rollup-darwin-x64": "4.43.0", + "@rollup/rollup-freebsd-arm64": "4.43.0", + "@rollup/rollup-freebsd-x64": "4.43.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.43.0", + "@rollup/rollup-linux-arm-musleabihf": "4.43.0", + "@rollup/rollup-linux-arm64-gnu": "4.43.0", + "@rollup/rollup-linux-arm64-musl": "4.43.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.43.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.43.0", + "@rollup/rollup-linux-riscv64-gnu": "4.43.0", + "@rollup/rollup-linux-riscv64-musl": "4.43.0", + "@rollup/rollup-linux-s390x-gnu": "4.43.0", + "@rollup/rollup-linux-x64-gnu": "4.43.0", + "@rollup/rollup-linux-x64-musl": "4.43.0", + "@rollup/rollup-win32-arm64-msvc": "4.43.0", + "@rollup/rollup-win32-ia32-msvc": "4.43.0", + "@rollup/rollup-win32-x64-msvc": "4.43.0", "fsevents": "~2.3.2" } }, @@ -15287,14 +15552,10 @@ } }, "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" }, "node_modules/webpack": { "version": "5.99.9", @@ -15475,12 +15736,6 @@ "webidl-conversions": "^3.0.0" } }, - "node_modules/whatwg-url/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index f30c334cdb..f7df1b3f38 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,12 @@ "@github/markdown-toolbar-element": "2.2.3", "@github/quote-selection": "2.1.0", "@github/text-expander-element": "2.8.0", + "@google/model-viewer": "4.1.0", "@mcaptcha/vanilla-glue": "0.1.0-alpha-3", "@primer/octicons": "19.14.0", "ansi_up": "6.0.5", "asciinema-player": "3.8.2", - "chart.js": "4.4.9", + "chart.js": "4.5.0", "chartjs-adapter-dayjs-4": "1.0.4", "chartjs-plugin-zoom": "2.2.0", "clippie": "4.1.7", @@ -32,13 +33,13 @@ "katex": "0.16.22", "mermaid": "11.6.0", "mini-css-extract-plugin": "2.9.2", - "minimatch": "10.0.1", + "minimatch": "10.0.3", "monaco-editor": "0.52.2", "monaco-editor-webpack-plugin": "7.1.0", "pdfobject": "2.3.0", - "postcss": "8.5.4", + "postcss": "8.5.5", "postcss-loader": "8.1.1", - "postcss-nesting": "13.0.1", + "postcss-nesting": "13.0.2", "pretty-ms": "9.0.0", "sortablejs": "1.15.6", "swagger-ui-dist": "5.17.14", @@ -63,11 +64,11 @@ "@eslint-community/eslint-plugin-eslint-comments": "4.5.0", "@playwright/test": "1.52.0", "@stoplight/spectral-cli": "6.15.0", - "@stylistic/eslint-plugin-js": "4.4.1", + "@stylistic/eslint-plugin": "4.4.1", "@stylistic/stylelint-plugin": "3.1.2", "@vitejs/plugin-vue": "5.2.4", "@vitest/coverage-v8": "3.2.3", - "@vitest/eslint-plugin": "1.2.1", + "@vitest/eslint-plugin": "1.2.2", "@vue/test-utils": "2.4.6", "eslint": "9.28.0", "eslint-import-resolver-typescript": "4.4.3", @@ -78,14 +79,14 @@ "eslint-plugin-playwright": "2.2.0", "eslint-plugin-regexp": "2.9.0", "eslint-plugin-sonarjs": "3.0.2", - "eslint-plugin-unicorn": "59.0.1", "eslint-plugin-toml": "0.12.0", + "eslint-plugin-unicorn": "59.0.1", "eslint-plugin-vitest-globals": "1.5.0", "eslint-plugin-vue": "10.2.0", "eslint-plugin-vue-scoped-css": "2.10.0", - "eslint-plugin-wc": "2.2.1", + "eslint-plugin-wc": "3.0.1", "globals": "16.1.0", - "happy-dom": "17.6.3", + "happy-dom": "18.0.0", "license-checker-rseidelsohn": "4.4.2", "markdownlint-cli": "0.45.0", "postcss-html": "1.8.0", @@ -96,7 +97,7 @@ "stylelint-value-no-unknown-custom-properties": "6.0.1", "svgo": "3.2.0", "typescript": "5.8.3", - "typescript-eslint": "8.33.1", + "typescript-eslint": "8.34.0", "vite-string-plugin": "1.3.4", "vitest": "3.2.3" }, diff --git a/release-notes-published/11.0.2.md b/release-notes-published/11.0.2.md new file mode 100644 index 0000000000..a1a8549984 --- /dev/null +++ b/release-notes-published/11.0.2.md @@ -0,0 +1,33 @@ + + + + +## Release notes + +- Features + - [PR](https://codeberg.org/forgejo/forgejo/pulls/7986) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7991)): feat: make Forgejo Actions server logs less noisy +- Bug fixes + - [PR](https://codeberg.org/forgejo/forgejo/pulls/8155) ([backported](https://codeberg.org/forgejo/forgejo/pulls/8167)): fix: do not fail when release or wiki is set in `/repos/migrate` API + - [PR](https://codeberg.org/forgejo/forgejo/pulls/7976) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7985)): fix: ignore expired artifacts for quota calculation + - [PR](https://codeberg.org/forgejo/forgejo/pulls/7979) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7983)): fix: pull request cross references + - [PR](https://codeberg.org/forgejo/forgejo/pulls/7883) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7886)): fix: quote reply in Chromium + - [PR](https://codeberg.org/forgejo/forgejo/pulls/7775) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7779)): fix: make hash pattern more strict +- Included for completeness but not worth a release note + - [PR](https://codeberg.org/forgejo/forgejo/pulls/8112) ([backported](https://codeberg.org/forgejo/forgejo/pulls/8120)): fix: remove download attribute from external assets + - [PR](https://codeberg.org/forgejo/forgejo/pulls/8110): Update bleve to v2.5.2 with changes made in backport of 2.5.0 + - [PR](https://codeberg.org/forgejo/forgejo/pulls/8094) ([backported](https://codeberg.org/forgejo/forgejo/pulls/8095)): fix: show membership of limited orgs + - [PR](https://codeberg.org/forgejo/forgejo/pulls/8059): Update dependency go to v1.24.3 (v11.0/forgejo) + - [PR](https://codeberg.org/forgejo/forgejo/pulls/8057): chore: drop unused `@typescript-eslint/parser` package + - [PR](https://codeberg.org/forgejo/forgejo/pulls/8021) ([backported](https://codeberg.org/forgejo/forgejo/pulls/8022)): chore(cleanup): suppress non actionable XORM warnings + - [PR](https://codeberg.org/forgejo/forgejo/pulls/7987) ([backported](https://codeberg.org/forgejo/forgejo/pulls/8000)): fix: aggregate deleted team as ghost team + - [PR](https://codeberg.org/forgejo/forgejo/pulls/7925) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7937)): fix(ui): center footer links + - [PR](https://codeberg.org/forgejo/forgejo/pulls/7894) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7903)): fix(ui): fix force-push compare line layout + - [PR](https://codeberg.org/forgejo/forgejo/pulls/7884) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7887)): fix: parse `change-id` in the git commit header + - [PR](https://codeberg.org/forgejo/forgejo/pulls/7885): Update module github.com/blevesearch/bleve/v2 to v2.5.1 (v11.0/forgejo) - abandoned + - [PR](https://codeberg.org/forgejo/forgejo/pulls/7746) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7871)): fix(ui): improve force-push compare line layout + - [PR](https://codeberg.org/forgejo/forgejo/pulls/7640) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7869)): fix: Remove "create branch" button on mirrored repos + - [PR](https://codeberg.org/forgejo/forgejo/pulls/7858): Update module github.com/msteinert/pam/v2 to v2.1.0 (v11.0/forgejo) + - [PR](https://codeberg.org/forgejo/forgejo/pulls/7817) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7821)): fix: replace ß with ss in normalizeUserName + - [PR](https://codeberg.org/forgejo/forgejo/pulls/7784) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7786)): fix(api): document `is_system_webhook` field + - [PR](https://codeberg.org/forgejo/forgejo/pulls/7773) ([backported](https://codeberg.org/forgejo/forgejo/pulls/7774)): fix: remove artificial delay for PR update + diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index e371ebb28b..bf08bdd249 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1310,6 +1310,7 @@ func Routes() *web.Route { m.Get("/refs", repo.GetGitAllRefs) m.Get("/refs/*", repo.GetGitRefs) m.Get("/trees/{sha}", repo.GetTree) + m.Get("/blobs", repo.GetBlobs) m.Get("/blobs/{sha}", repo.GetBlob) m.Get("/tags/{sha}", repo.GetAnnotatedTag) m.Group("/notes/{sha}", func() { diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go index 03089a18d3..dbc4933de6 100644 --- a/routers/api/v1/repo/action.go +++ b/routers/api/v1/repo/action.go @@ -748,7 +748,7 @@ func ListActionRuns(ctx *context.APIContext) { // type: string // responses: // "200": - // "$ref": "#/responses/RepoActionRunList" + // "$ref": "#/responses/ActionRunList" // "400": // "$ref": "#/responses/error" // "403": @@ -779,16 +779,16 @@ func ListActionRuns(ctx *context.APIContext) { return } - res := new(api.ListRepoActionRunResponse) + res := new(api.ListActionRunResponse) res.TotalCount = total - res.Entries = make([]*api.RepoActionRun, len(runs)) + res.Entries = make([]*api.ActionRun, len(runs)) for i, r := range runs { - cr, err := convert.ToRepoActionRun(ctx, r) - if err != nil { - ctx.Error(http.StatusInternalServerError, "ToActionRun", err) + if err := r.LoadAttributes(ctx); err != nil { + ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) return } + cr := convert.ToActionRun(ctx, r, ctx.Doer) res.Entries[i] = cr } @@ -821,7 +821,7 @@ func GetActionRun(ctx *context.APIContext) { // required: true // responses: // "200": - // "$ref": "#/responses/RepoActionRun" + // "$ref": "#/responses/ActionRun" // "400": // "$ref": "#/responses/error" // "403": @@ -839,16 +839,17 @@ func GetActionRun(ctx *context.APIContext) { return } + // Action runs lives in its own table, therefore we check that the + // run with the requested ID is owned by the repository if ctx.Repo.Repository.ID != run.RepoID { ctx.Error(http.StatusNotFound, "GetRunById", util.ErrNotExist) return } - res, err := convert.ToRepoActionRun(ctx, run) - if err != nil { - ctx.Error(http.StatusInternalServerError, "ToRepoActionRun", err) + if err := run.LoadAttributes(ctx); err != nil { + ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) return } - ctx.JSON(http.StatusOK, res) + ctx.JSON(http.StatusOK, convert.ToActionRun(ctx, run, ctx.Doer)) } diff --git a/routers/api/v1/repo/blob.go b/routers/api/v1/repo/blob.go index 8ed57d4787..63baec2025 100644 --- a/routers/api/v1/repo/blob.go +++ b/routers/api/v1/repo/blob.go @@ -5,11 +5,54 @@ package repo import ( "net/http" + "strings" "forgejo.org/services/context" files_service "forgejo.org/services/repository/files" ) +// GetBlobs gets multiple blobs of a repository. +func GetBlobs(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/git/blobs repository GetBlobs + // --- + // summary: Gets multiplbe blobs of a repository. + // 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 + // - name: shas + // in: query + // description: a comma separated list of blob-sha (mind the overall URL-length limit of ~2,083 chars) + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/GitBlobList" + // "400": + // "$ref": "#/responses/error" + + shas := ctx.FormString("shas") + if len(shas) == 0 { + ctx.Error(http.StatusBadRequest, "", "shas not provided") + return + } + + if blobs, err := files_service.GetBlobsBySHA(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, strings.Split(shas, ",")); err != nil { + ctx.Error(http.StatusBadRequest, "", err) + } else { + ctx.JSON(http.StatusOK, blobs) + } +} + // GetBlob get the blob of a repository file. func GetBlob(ctx *context.APIContext) { // swagger:operation GET /repos/{owner}/{repo}/git/blobs/{sha} repository GetBlob @@ -30,12 +73,12 @@ func GetBlob(ctx *context.APIContext) { // required: true // - name: sha // in: path - // description: sha of the commit + // description: sha of the blob to retrieve // type: string // required: true // responses: // "200": - // "$ref": "#/responses/GitBlobResponse" + // "$ref": "#/responses/GitBlob" // "400": // "$ref": "#/responses/error" // "404": diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index 3408f88dd1..549fe9fae0 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -480,6 +480,8 @@ func ChangeFiles(ctx *context.APIContext) { // "$ref": "#/responses/error" // "404": // "$ref": "#/responses/notFound" + // "409": + // "$ref": "#/responses/conflict" // "413": // "$ref": "#/responses/quotaExceeded" // "422": @@ -584,6 +586,8 @@ func CreateFile(ctx *context.APIContext) { // "$ref": "#/responses/error" // "404": // "$ref": "#/responses/notFound" + // "409": + // "$ref": "#/responses/conflict" // "413": // "$ref": "#/responses/quotaExceeded" // "422": @@ -684,6 +688,8 @@ func UpdateFile(ctx *context.APIContext) { // "$ref": "#/responses/error" // "404": // "$ref": "#/responses/notFound" + // "409": + // "$ref": "#/responses/conflict" // "413": // "$ref": "#/responses/quotaExceeded" // "422": @@ -757,11 +763,19 @@ func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) { ctx.Error(http.StatusForbidden, "Access", err) return } - if git_model.IsErrBranchAlreadyExists(err) || models.IsErrFilenameInvalid(err) || models.IsErrSHADoesNotMatch(err) || - models.IsErrFilePathInvalid(err) || models.IsErrRepoFileAlreadyExists(err) { + if git_model.IsErrBranchAlreadyExists(err) || + models.IsErrFilenameInvalid(err) || + models.IsErrSHAOrCommitIDNotProvided(err) || + models.IsErrFilePathInvalid(err) || + models.IsErrRepoFileAlreadyExists(err) { ctx.Error(http.StatusUnprocessableEntity, "Invalid", err) return } + if models.IsErrCommitIDDoesNotMatch(err) || + models.IsErrSHADoesNotMatch(err) { + ctx.Error(http.StatusConflict, "Conflict", err) + return + } if git_model.IsErrBranchNotExist(err) || git.IsErrBranchNotExist(err) { ctx.Error(http.StatusNotFound, "BranchDoesNotExist", err) return diff --git a/routers/api/v1/repo/migrate.go b/routers/api/v1/repo/migrate.go index 4ee7aa58e4..a848a950db 100644 --- a/routers/api/v1/repo/migrate.go +++ b/routers/api/v1/repo/migrate.go @@ -224,9 +224,8 @@ func Migrate(ctx *context.APIContext) { HasWiki: &opts.Wiki, } - // only enabling wiki could return an error - if err = updateRepoUnits(ctx, repoOpt); err != nil { - log.Error("Failed to enable wiki on %s/%s repo. %w", repoOwner.Name, form.RepoName, err) + if err = updateRepoUnits(ctx, repoOwner.Name, repo, repoOpt); err != nil { + log.Error("Failed to update units on %s/%s repo. %w", repoOwner.Name, form.RepoName, err) } } diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 75b4870e51..c9dda124de 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -71,7 +71,7 @@ func ListPullRequests(ctx *context.APIContext) { // in: query // description: Type of sort // type: string - // enum: [oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority] + // enum: [oldest, recentupdate, recentclose, leastupdate, mostcomment, leastcomment, priority] // - name: milestone // in: query // description: ID of the milestone diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 63100bca06..3d6a40e9ab 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -647,7 +647,7 @@ func Edit(ctx *context.APIContext) { return } - if err := updateRepoUnits(ctx, opts); err != nil { + if err := updateRepoUnits(ctx, ctx.Repo.Owner.Name, ctx.Repo.Repository, opts); err != nil { return } @@ -779,10 +779,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err } // updateRepoUnits updates repo units: Issue settings, Wiki settings, PR settings -func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { - owner := ctx.Repo.Owner - repo := ctx.Repo.Repository - +func updateRepoUnits(ctx *context.APIContext, owner string, repo *repo_model.Repository, opts api.EditRepoOption) error { var units []repo_model.RepoUnit var deleteUnitTypes []unit_model.Type @@ -1045,7 +1042,7 @@ func updateRepoUnits(ctx *context.APIContext, opts api.EditRepoOption) error { } } - log.Trace("Repository advanced settings updated: %s/%s", owner.Name, repo.Name) + log.Trace("Repository advanced settings updated: %s/%s", owner, repo.Name) return nil } diff --git a/routers/api/v1/swagger/repo.go b/routers/api/v1/swagger/repo.go index a27e94253b..cd4832e15f 100644 --- a/routers/api/v1/swagger/repo.go +++ b/routers/api/v1/swagger/repo.go @@ -231,11 +231,18 @@ type swaggerGitTreeResponse struct { Body api.GitTreeResponse `json:"body"` } -// GitBlobResponse -// swagger:response GitBlobResponse -type swaggerGitBlobResponse struct { +// GitBlob +// swagger:response GitBlob +type swaggerGitBlob struct { // in: body - Body api.GitBlobResponse `json:"body"` + Body api.GitBlob `json:"body"` +} + +// GitBlobList +// swagger:response GitBlobList +type swaggerGitBlobList struct { + // in: body + Body []api.GitBlob `json:"body"` } // Commit @@ -456,16 +463,16 @@ type swaggerSyncForkInfo struct { Body []api.SyncForkInfo `json:"body"` } -// RepoActionRunList -// swagger:response RepoActionRunList -type swaggerRepoActionRunList struct { +// ActionRunList +// swagger:response ActionRunList +type swaggerActionRunList struct { // in:body - Body api.ListRepoActionRunResponse `json:"body"` + Body api.ListActionRunResponse `json:"body"` } -// RepoActionRun -// swagger:response RepoActionRun -type swaggerRepoActionRun struct { +// ActionRun +// swagger:response ActionRun +type swaggerActionRun struct { // in:body - Body api.RepoActionRun `json:"body"` + Body api.ActionRun `json:"body"` } diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index 0188df17b4..964326291e 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -313,6 +313,9 @@ func editUserCommon(ctx *context.Context) { ctx.Data["DisableMigrations"] = setting.Repository.DisableMigrations ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice() ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx) + ctx.Data["MaxAvatarFileSize"] = setting.Avatar.MaxFileSize + ctx.Data["MaxAvatarWidth"] = setting.Avatar.MaxWidth + ctx.Data["MaxAvatarHeight"] = setting.Avatar.MaxHeight } // EditUser show editing user page diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index aa599bd252..e8e5d2c54b 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -225,7 +225,7 @@ func newAccessTokenResponse(ctx go_context.Context, grant *auth.OAuth2Grant, ser idToken := &oauth2.OIDCToken{ RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(expirationDate.AsTime()), - Issuer: setting.AppURL, + Issuer: strings.TrimSuffix(setting.AppURL, "/"), Audience: []string{app.ClientID}, Subject: fmt.Sprint(grant.UserID), }, @@ -409,7 +409,7 @@ func IntrospectOAuth(ctx *context.Context) { if err == nil && app != nil { response.Active = true response.Scope = grant.Scope - response.Issuer = setting.AppURL + response.Issuer = strings.TrimSuffix(setting.AppURL, "/") response.Audience = []string{app.ClientID} response.Subject = fmt.Sprint(grant.UserID) } @@ -669,6 +669,7 @@ func GrantApplicationOAuth(ctx *context.Context) { // OIDCWellKnown generates JSON so OIDC clients know Gitea's capabilities func OIDCWellKnown(ctx *context.Context) { ctx.Data["SigningKey"] = oauth2.DefaultSigningKey + ctx.Data["Issuer"] = strings.TrimSuffix(setting.AppURL, "/") ctx.JSONTemplate("user/auth/oidc_wellknown") } diff --git a/routers/web/auth/oauth_test.go b/routers/web/auth/oauth_test.go index 487b551d6c..9782711dd0 100644 --- a/routers/web/auth/oauth_test.go +++ b/routers/web/auth/oauth_test.go @@ -51,6 +51,7 @@ func TestNewAccessTokenResponse_OIDCToken(t *testing.T) { // Scopes: openid oidcToken := createAndParseToken(t, grants[0]) + assert.Equal(t, "https://try.gitea.io", oidcToken.RegisteredClaims.Issuer) assert.Empty(t, oidcToken.Name) assert.Empty(t, oidcToken.PreferredUsername) assert.Empty(t, oidcToken.Profile) @@ -67,6 +68,7 @@ func TestNewAccessTokenResponse_OIDCToken(t *testing.T) { // Scopes: openid profile email oidcToken = createAndParseToken(t, grants[0]) + assert.Equal(t, "https://try.gitea.io", oidcToken.RegisteredClaims.Issuer) assert.Equal(t, "User Five", oidcToken.Name) assert.Equal(t, "user5", oidcToken.PreferredUsername) assert.Equal(t, "https://try.gitea.io/user5", oidcToken.Profile) diff --git a/routers/web/org/home.go b/routers/web/org/home.go index a3823565ed..8f14f8899c 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -175,10 +175,12 @@ func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repositor return } - if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil { - log.Error("failed to GetBlobContent: %v", err) + if rc, _, err := profileReadme.NewTruncatedReader(setting.UI.MaxDisplayFileSize); err != nil { + log.Error("failed to NewTruncatedReader: %v", err) } else { - if profileContent, err := markdown.RenderString(&markup.RenderContext{ + defer rc.Close() + + if profileContent, err := markdown.RenderReader(&markup.RenderContext{ Ctx: ctx, GitRepo: profileGitRepo, Links: markup.Links{ @@ -188,7 +190,7 @@ func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repositor BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)), }, Metas: map[string]string{"mode": "document"}, - }, bytes); err != nil { + }, rc); err != nil { log.Error("failed to RenderString: %v", err) } else { ctx.Data["ProfileReadme"] = profileContent diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index 284f406413..c83242754b 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -50,6 +50,9 @@ func Settings(ctx *context.Context) { ctx.Data["RepoAdminChangeTeamAccess"] = ctx.Org.Organization.RepoAdminChangeTeamAccess ctx.Data["ContextUser"] = ctx.ContextUser ctx.Data["CooldownPeriod"] = setting.Service.UsernameCooldownPeriod + ctx.Data["MaxAvatarFileSize"] = setting.Avatar.MaxFileSize + ctx.Data["MaxAvatarWidth"] = setting.Avatar.MaxWidth + ctx.Data["MaxAvatarHeight"] = setting.Avatar.MaxHeight err := shared_user.LoadHeaderCount(ctx) if err != nil { diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index af8a838fc9..0fe52bfb48 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -70,11 +70,6 @@ func Branches(ctx *context.Context) { ctx.ServerError("LoadBranches", err) return } - if !ctx.Repo.CanRead(unit.TypeActions) { - for key := range commitStatuses { - git_model.CommitStatusesHideActionsURL(ctx, commitStatuses[key]) - } - } commitStatus := make(map[string]*git_model.CommitStatus) for commitID, cs := range commitStatuses { diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 89463d9d03..f3192266ad 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -16,7 +16,6 @@ import ( "forgejo.org/models/db" git_model "forgejo.org/models/git" repo_model "forgejo.org/models/repo" - unit_model "forgejo.org/models/unit" user_model "forgejo.org/models/user" "forgejo.org/modules/base" "forgejo.org/modules/charset" @@ -84,7 +83,7 @@ func Commits(ctx *context.Context) { ctx.ServerError("CommitsByRange", err) return } - ctx.Data["Commits"] = processGitCommits(ctx, commits) + ctx.Data["Commits"] = git_model.ParseCommitsWithStatus(ctx, commits, ctx.Repo.Repository) ctx.Data["Username"] = ctx.Repo.Owner.Name ctx.Data["Reponame"] = ctx.Repo.Repository.Name @@ -202,7 +201,7 @@ func SearchCommits(ctx *context.Context) { return } ctx.Data["CommitCount"] = len(commits) - ctx.Data["Commits"] = processGitCommits(ctx, commits) + ctx.Data["Commits"] = git_model.ParseCommitsWithStatus(ctx, commits, ctx.Repo.Repository) ctx.Data["Keyword"] = query if all { @@ -267,7 +266,7 @@ func FileHistory(ctx *context.Context) { } } - ctx.Data["Commits"] = processGitCommits(ctx, commits) + ctx.Data["Commits"] = git_model.ParseCommitsWithStatus(ctx, commits, ctx.Repo.Repository) ctx.Data["Username"] = ctx.Repo.Owner.Name ctx.Data["Reponame"] = ctx.Repo.Repository.Name @@ -375,9 +374,6 @@ func Diff(ctx *context.Context) { if err != nil { log.Error("GetLatestCommitStatus: %v", err) } - if !ctx.Repo.CanRead(unit_model.TypeActions) { - git_model.CommitStatusesHideActionsURL(ctx, statuses) - } ctx.Data["CommitStatus"] = git_model.CalcCommitStatus(statuses) ctx.Data["CommitStatuses"] = statuses @@ -456,20 +452,6 @@ func RawDiff(ctx *context.Context) { } } -func processGitCommits(ctx *context.Context, gitCommits []*git.Commit) []*git_model.SignCommitWithStatuses { - commits := git_model.ConvertFromGitCommit(ctx, gitCommits, ctx.Repo.Repository) - if !ctx.Repo.CanRead(unit_model.TypeActions) { - for _, commit := range commits { - if commit.Status == nil { - continue - } - commit.Status.HideActionsURL(ctx) - git_model.CommitStatusesHideActionsURL(ctx, commit.Statuses) - } - } - return commits -} - func SetCommitNotes(ctx *context.Context) { form := web.GetForm(ctx).(*forms.CommitNotesForm) diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index f5826cf249..de2e29ab9f 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -654,7 +654,7 @@ func PrepareCompareDiff( return false } - commits := processGitCommits(ctx, ci.CompareInfo.Commits) + commits := git_model.ParseCommitsWithStatus(ctx, ci.CompareInfo.Commits, ctx.Repo.Repository) ctx.Data["Commits"] = commits ctx.Data["CommitCount"] = len(commits) diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index b97c268ae2..5e228507c0 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -348,11 +348,6 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption opt ctx.ServerError("GetIssuesAllCommitStatus", err) return } - if !ctx.Repo.CanRead(unit.TypeActions) { - for key := range commitStatuses { - git_model.CommitStatusesHideActionsURL(ctx, commitStatuses[key]) - } - } if err := issues.LoadAttributes(ctx); err != nil { ctx.ServerError("issues.LoadAttributes", err) @@ -1313,7 +1308,7 @@ func roleDescriptor(ctx stdCtx.Context, repo *repo_model.Repository, poster *use } // Special user that can't have associated contributions and permissions in the repo. - if poster.IsGhost() || poster.IsActions() || poster.IsAPServerActor() { + if poster.IsSystem() || poster.IsAPServerActor() { return roleDescriptor, nil } @@ -1698,7 +1693,7 @@ func ViewIssue(ctx *context.Context) { return } ghostMilestone := &issues_model.Milestone{ - ID: -1, + ID: issues_model.GhostMilestoneID, Name: ctx.Locale.TrString("repo.issues.deleted_milestone"), } if comment.OldMilestoneID > 0 && comment.OldMilestone == nil { @@ -1799,15 +1794,6 @@ func ViewIssue(ctx *context.Context) { ctx.ServerError("LoadPushCommits", err) return } - if !ctx.Repo.CanRead(unit.TypeActions) { - for _, commit := range comment.Commits { - if commit.Status == nil { - continue - } - commit.Status.HideActionsURL(ctx) - git_model.CommitStatusesHideActionsURL(ctx, commit.Statuses) - } - } } else if comment.Type == issues_model.CommentTypeAddTimeManual || comment.Type == issues_model.CommentTypeStopTracking || comment.Type == issues_model.CommentTypeDeleteTimeManual { diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index bb89e30d54..fd18646211 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -10,13 +10,16 @@ import ( "errors" "fmt" "html" + "html/template" "net/http" "net/url" + "path" "strconv" "strings" "forgejo.org/models" activities_model "forgejo.org/models/activities" + asymkey_model "forgejo.org/models/asymkey" "forgejo.org/models/db" git_model "forgejo.org/models/git" issues_model "forgejo.org/models/issues" @@ -28,11 +31,13 @@ import ( "forgejo.org/models/unit" user_model "forgejo.org/models/user" "forgejo.org/modules/base" + "forgejo.org/modules/charset" "forgejo.org/modules/emoji" "forgejo.org/modules/git" "forgejo.org/modules/gitrepo" issue_template "forgejo.org/modules/issue/template" "forgejo.org/modules/log" + "forgejo.org/modules/markup" "forgejo.org/modules/optional" "forgejo.org/modules/setting" "forgejo.org/modules/structs" @@ -498,6 +503,7 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue) ctx.Data["IsPullRequestBroken"] = true ctx.Data["BaseTarget"] = pull.BaseBranch ctx.Data["NumCommits"] = 0 + ctx.Data["CommitIDs"] = map[string]bool{} ctx.Data["NumFiles"] = 0 return nil } @@ -508,6 +514,12 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue) ctx.Data["NumCommits"] = len(compareInfo.Commits) ctx.Data["NumFiles"] = compareInfo.NumFiles + commitIDs := map[string]bool{} + for _, commit := range compareInfo.Commits { + commitIDs[commit.ID.String()] = true + } + ctx.Data["CommitIDs"] = commitIDs + if len(compareInfo.Commits) != 0 { sha := compareInfo.Commits[0].ID.String() commitStatuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, sha, db.ListOptionsAll) @@ -515,9 +527,6 @@ func PrepareMergedViewPullInfo(ctx *context.Context, issue *issues_model.Issue) ctx.ServerError("GetLatestCommitStatus", err) return nil } - if !ctx.Repo.CanRead(unit.TypeActions) { - git_model.CommitStatusesHideActionsURL(ctx, commitStatuses) - } if len(commitStatuses) != 0 { ctx.Data["LatestCommitStatuses"] = commitStatuses @@ -581,9 +590,6 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C ctx.ServerError("GetLatestCommitStatus", err) return nil } - if !ctx.Repo.CanRead(unit.TypeActions) { - git_model.CommitStatusesHideActionsURL(ctx, commitStatuses) - } if len(commitStatuses) > 0 { ctx.Data["LatestCommitStatuses"] = commitStatuses @@ -597,6 +603,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C ctx.Data["IsPullRequestBroken"] = true ctx.Data["BaseTarget"] = pull.BaseBranch ctx.Data["NumCommits"] = 0 + ctx.Data["CommitIDs"] = map[string]bool{} ctx.Data["NumFiles"] = 0 return nil } @@ -607,6 +614,13 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C ctx.Data["NumCommits"] = len(compareInfo.Commits) ctx.Data["NumFiles"] = compareInfo.NumFiles + + commitIDs := map[string]bool{} + for _, commit := range compareInfo.Commits { + commitIDs[commit.ID.String()] = true + } + ctx.Data["CommitIDs"] = commitIDs + return compareInfo } @@ -665,6 +679,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C } ctx.Data["BaseTarget"] = pull.BaseBranch ctx.Data["NumCommits"] = 0 + ctx.Data["CommitIDs"] = map[string]bool{} ctx.Data["NumFiles"] = 0 return nil } @@ -677,9 +692,6 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C ctx.ServerError("GetLatestCommitStatus", err) return nil } - if !ctx.Repo.CanRead(unit.TypeActions) { - git_model.CommitStatusesHideActionsURL(ctx, commitStatuses) - } if len(commitStatuses) > 0 { ctx.Data["LatestCommitStatuses"] = commitStatuses @@ -745,6 +757,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C ctx.Data["IsPullRequestBroken"] = true ctx.Data["BaseTarget"] = pull.BaseBranch ctx.Data["NumCommits"] = 0 + ctx.Data["CommitIDs"] = map[string]bool{} ctx.Data["NumFiles"] = 0 return nil } @@ -769,6 +782,13 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C ctx.Data["NumCommits"] = len(compareInfo.Commits) ctx.Data["NumFiles"] = compareInfo.NumFiles + + commitIDs := map[string]bool{} + for _, commit := range compareInfo.Commits { + commitIDs[commit.ID.String()] = true + } + ctx.Data["CommitIDs"] = commitIDs + return compareInfo } @@ -847,7 +867,7 @@ func ViewPullCommits(ctx *context.Context) { ctx.Data["Username"] = ctx.Repo.Owner.Name ctx.Data["Reponame"] = ctx.Repo.Repository.Name - commits := processGitCommits(ctx, prInfo.Commits) + commits := git_model.ParseCommitsWithStatus(ctx, prInfo.Commits, ctx.Repo.Repository) ctx.Data["Commits"] = commits ctx.Data["CommitCount"] = len(commits) @@ -928,7 +948,78 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi ctx.Data["IsShowingOnlySingleCommit"] = willShowSpecifiedCommit - if willShowSpecifiedCommit || willShowSpecifiedCommitRange { + if willShowSpecifiedCommit { + commitID := specifiedEndCommit + + ctx.Data["CommitID"] = commitID + + var prevCommit, curCommit, nextCommit *git.Commit + + // Iterate in reverse to properly map "previous" and "next" buttons + for i := len(prInfo.Commits) - 1; i >= 0; i-- { + commit := prInfo.Commits[i] + + if curCommit != nil { + nextCommit = commit + break + } + + if commit.ID.String() == commitID { + curCommit = commit + } else { + prevCommit = commit + } + } + + if curCommit == nil { + ctx.ServerError("Repo.GitRepo.viewPullFiles", git.ErrNotExist{ID: commitID}) + return + } + + ctx.Data["Commit"] = curCommit + if prevCommit != nil { + ctx.Data["PrevCommitLink"] = path.Join(ctx.Repo.RepoLink, "pulls", strconv.FormatInt(issue.Index, 10), "commits", prevCommit.ID.String()) + } + if nextCommit != nil { + ctx.Data["NextCommitLink"] = path.Join(ctx.Repo.RepoLink, "pulls", strconv.FormatInt(issue.Index, 10), "commits", nextCommit.ID.String()) + } + + statuses, _, err := git_model.GetLatestCommitStatus(ctx, ctx.Repo.Repository.ID, commitID, db.ListOptionsAll) + if err != nil { + log.Error("GetLatestCommitStatus: %v", err) + } + + ctx.Data["CommitStatus"] = git_model.CalcCommitStatus(statuses) + ctx.Data["CommitStatuses"] = statuses + + verification := asymkey_model.ParseCommitWithSignature(ctx, curCommit) + ctx.Data["Verification"] = verification + ctx.Data["Author"] = user_model.ValidateCommitWithEmail(ctx, curCommit) + + note := &git.Note{} + err = git.GetNote(ctx, ctx.Repo.GitRepo, specifiedEndCommit, note) + if err == nil { + ctx.Data["NoteCommit"] = note.Commit + ctx.Data["NoteAuthor"] = user_model.ValidateCommitWithEmail(ctx, note.Commit) + ctx.Data["NoteRendered"], err = markup.RenderCommitMessage(&markup.RenderContext{ + Links: markup.Links{ + Base: ctx.Repo.RepoLink, + BranchPath: path.Join("commit", util.PathEscapeSegments(commitID)), + }, + Metas: ctx.Repo.Repository.ComposeMetas(ctx), + GitRepo: ctx.Repo.GitRepo, + Ctx: ctx, + }, template.HTMLEscapeString(string(charset.ToUTF8WithFallback(note.Message, charset.ConvertOpts{})))) + if err != nil { + ctx.ServerError("RenderCommitMessage", err) + return + } + } + + endCommitID = commitID + startCommitID = prInfo.MergeBase + ctx.Data["IsShowingAllCommits"] = false + } else if willShowSpecifiedCommitRange { if len(specifiedEndCommit) > 0 { endCommitID = specifiedEndCommit } else { @@ -939,6 +1030,7 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi } else { startCommitID = prInfo.MergeBase } + ctx.Data["IsShowingAllCommits"] = false } else { endCommitID = headCommitID @@ -946,10 +1038,10 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi ctx.Data["IsShowingAllCommits"] = true } - ctx.Data["Username"] = ctx.Repo.Owner.Name - ctx.Data["Reponame"] = ctx.Repo.Repository.Name ctx.Data["AfterCommitID"] = endCommitID ctx.Data["BeforeCommitID"] = startCommitID + ctx.Data["Username"] = ctx.Repo.Owner.Name + ctx.Data["Reponame"] = ctx.Repo.Repository.Name fileOnly := ctx.FormBool("file-only") diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 3c923c2c5e..493787ad8b 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -693,9 +693,6 @@ func SearchRepo(ctx *context.Context) { ctx.JSON(http.StatusInternalServerError, nil) return } - if !ctx.Repo.CanRead(unit.TypeActions) { - git_model.CommitStatusesHideActionsURL(ctx, latestCommitStatuses) - } results := make([]*repo_service.WebSearchRepository, len(repos)) for i, repo := range repos { diff --git a/routers/web/repo/setting/lfs.go b/routers/web/repo/setting/lfs.go index 2e9c34e8a7..b9cb86bd08 100644 --- a/routers/web/repo/setting/lfs.go +++ b/routers/web/repo/setting/lfs.go @@ -342,6 +342,20 @@ func LFSFileGet(ctx *context.Context) { ctx.Data["IsVideoFile"] = true case st.IsAudio(): ctx.Data["IsAudioFile"] = true + case st.Is3DModel(): + ctx.Data["Is3DModelFile"] = true + switch { + case st.IsGLB(): + ctx.Data["IsGLBFile"] = true + case st.IsSTL(): + ctx.Data["IsSTLFile"] = true + case st.IsGLTF(): + ctx.Data["IsGLTFFile"] = true + case st.IsOBJ(): + ctx.Data["IsOBJFile"] = true + case st.Is3MF(): + ctx.Data["Is3MFFile"] = true + } case st.IsImage() && (setting.UI.SVG.Enabled || !st.IsSvgImage()): ctx.Data["IsImageFile"] = true } diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index c59225ba49..6f35e19880 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -64,6 +64,9 @@ func SettingsCtxData(ctx *context.Context) { ctx.Data["DisableNewPushMirrors"] = setting.Mirror.DisableNewPush ctx.Data["DefaultMirrorInterval"] = setting.Mirror.DefaultInterval ctx.Data["MinimumMirrorInterval"] = setting.Mirror.MinInterval + ctx.Data["MaxAvatarFileSize"] = setting.Avatar.MaxFileSize + ctx.Data["MaxAvatarWidth"] = setting.Avatar.MaxWidth + ctx.Data["MaxAvatarHeight"] = setting.Avatar.MaxHeight signing, _ := asymkey_service.SigningKey(ctx, ctx.Repo.Repository.RepoPath()) ctx.Data["SigningKeyAvailable"] = len(signing) > 0 @@ -150,11 +153,9 @@ func UnitsPost(ctx *context.Context) { }) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeWiki) } else if form.EnableWiki && !form.EnableExternalWiki && !unit_model.TypeWiki.UnitGlobalDisabled() { - var wikiPermissions repo_model.UnitAccessMode + wikiPermissions := repo_model.UnitAccessModeUnset if form.GloballyWriteableWiki { wikiPermissions = repo_model.UnitAccessModeWrite - } else { - wikiPermissions = repo_model.UnitAccessModeRead } units = append(units, repo_model.RepoUnit{ RepoID: repo.ID, diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 58d2b09ee7..94b58da502 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -369,9 +369,6 @@ func loadLatestCommitData(ctx *context.Context, latestCommit *git.Commit) bool { if err != nil { log.Error("GetLatestCommitStatus: %v", err) } - if !ctx.Repo.CanRead(unit_model.TypeActions) { - git_model.CommitStatusesHideActionsURL(ctx, statuses) - } ctx.Data["LatestCommitStatus"] = git_model.CalcCommitStatus(statuses) ctx.Data["LatestCommitStatuses"] = statuses @@ -442,8 +439,8 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) { ctx.Data["FileError"] = ctx.Locale.Tr("actions.runs.invalid_workflow_helper", workFlowErr.Error()) } } else if slices.Contains([]string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"}, ctx.Repo.TreePath) { - if data, err := blob.GetBlobContent(setting.UI.MaxDisplayFileSize); err == nil { - _, warnings := issue_model.GetCodeOwnersFromContent(ctx, data) + if rc, size, err := blob.NewTruncatedReader(setting.UI.MaxDisplayFileSize); err == nil { + _, warnings := issue_model.GetCodeOwnersFromReader(ctx, rc, size > setting.UI.MaxDisplayFileSize) if len(warnings) > 0 { ctx.Data["FileWarning"] = strings.Join(warnings, "\n") } @@ -627,6 +624,20 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry) { ctx.Data["IsVideoFile"] = true case fInfo.st.IsAudio(): ctx.Data["IsAudioFile"] = true + case fInfo.st.Is3DModel(): + ctx.Data["Is3DModelFile"] = true + switch { + case fInfo.st.IsGLB(): + ctx.Data["IsGLBFile"] = true + case fInfo.st.IsSTL(): + ctx.Data["IsSTLFile"] = true + case fInfo.st.IsGLTF(): + ctx.Data["IsGLTFFile"] = true + case fInfo.st.IsOBJ(): + ctx.Data["IsOBJFile"] = true + case fInfo.st.Is3MF(): + ctx.Data["Is3MFFile"] = true + } case fInfo.st.IsImage() && (setting.UI.SVG.Enabled || !fInfo.st.IsSvgImage()): ctx.Data["IsImageFile"] = true ctx.Data["CanCopyContent"] = true diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index 9a21ac21a3..1b5265978a 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -393,7 +393,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) ctx.ServerError("CommitsByFileAndRange", err) return nil, nil } - ctx.Data["Commits"] = git_model.ConvertFromGitCommit(ctx, commitsHistory, ctx.Repo.Repository) + ctx.Data["Commits"] = git_model.ParseCommitsWithStatus(ctx, commitsHistory, ctx.Repo.Repository) pager := context.NewPagination(int(commitsCount), setting.Git.CommitsRangeSize, page, 5) pager.SetDefaultParams(ctx) diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 2a93221c8f..d980fa393a 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -16,7 +16,6 @@ import ( activities_model "forgejo.org/models/activities" asymkey_model "forgejo.org/models/asymkey" "forgejo.org/models/db" - git_model "forgejo.org/models/git" issues_model "forgejo.org/models/issues" "forgejo.org/models/organization" repo_model "forgejo.org/models/repo" @@ -611,11 +610,6 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { ctx.ServerError("GetIssuesLastCommitStatus", err) return } - if !ctx.Repo.CanRead(unit.TypeActions) { - for key := range commitStatuses { - git_model.CommitStatusesHideActionsURL(ctx, commitStatuses[key]) - } - } // ------------------------------- // Fill stats to post to ctx.Data. diff --git a/routers/web/user/notification.go b/routers/web/user/notification.go index 9fa71add57..fdca1a2fdd 100644 --- a/routers/web/user/notification.go +++ b/routers/web/user/notification.go @@ -13,10 +13,8 @@ import ( activities_model "forgejo.org/models/activities" "forgejo.org/models/db" - git_model "forgejo.org/models/git" issues_model "forgejo.org/models/issues" repo_model "forgejo.org/models/repo" - "forgejo.org/models/unit" "forgejo.org/modules/base" "forgejo.org/modules/log" "forgejo.org/modules/optional" @@ -311,11 +309,6 @@ func NotificationSubscriptions(ctx *context.Context) { ctx.ServerError("GetIssuesAllCommitStatus", err) return } - if !ctx.Repo.CanRead(unit.TypeActions) { - for key := range commitStatuses { - git_model.CommitStatusesHideActionsURL(ctx, commitStatuses[key]) - } - } ctx.Data["CommitLastStatus"] = lastStatus ctx.Data["CommitStatuses"] = commitStatuses ctx.Data["Issues"] = issues diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index 8068c1c6bc..78dd6c5e7c 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -70,17 +70,6 @@ func userProfile(ctx *context.Context) { ctx.Data["OpenGraphURL"] = ctx.ContextUser.HTMLURL() ctx.Data["OpenGraphDescription"] = ctx.ContextUser.Description - // prepare heatmap data - if setting.Service.EnableUserHeatmap { - data, err := activities_model.GetUserHeatmapDataByUser(ctx, ctx.ContextUser, ctx.Doer) - if err != nil { - ctx.ServerError("GetUserHeatmapDataByUser", err) - return - } - ctx.Data["HeatmapData"] = data - ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data) - } - profileDbRepo, profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer) defer profileClose() @@ -186,6 +175,17 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb ctx.Data["CardsNoneMsg"] = ctx.Tr("followers.outgoing.list.none", ctx.ContextUser.Name) } case "activity": + // prepare heatmap data + if setting.Service.EnableUserHeatmap { + data, err := activities_model.GetUserHeatmapDataByUser(ctx, ctx.ContextUser, ctx.Doer) + if err != nil { + ctx.ServerError("GetUserHeatmapDataByUser", err) + return + } + ctx.Data["HeatmapData"] = data + ctx.Data["HeatmapTotalContributions"] = activities_model.GetTotalContributionsInHeatmap(data) + } + date := ctx.FormString("date") pagingNum = setting.UI.FeedPagingNum items, count, err := activities_model.GetFeeds(ctx, activities_model.GetFeedsOptions{ @@ -264,10 +264,12 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb total = int(count) case "overview": - if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil { - log.Error("failed to GetBlobContent: %v", err) + if rc, _, err := profileReadme.NewTruncatedReader(setting.UI.MaxDisplayFileSize); err != nil { + log.Error("failed to NewTruncatedReader: %v", err) } else { - if profileContent, err := markdown.RenderString(&markup.RenderContext{ + defer rc.Close() + + if profileContent, err := markdown.RenderReader(&markup.RenderContext{ Ctx: ctx, GitRepo: profileGitRepo, Links: markup.Links{ @@ -280,7 +282,7 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb BranchPath: path.Join("branch", util.PathEscapeSegments(profileDbRepo.DefaultBranch)), }, Metas: map[string]string{"mode": "document"}, - }, bytes); err != nil { + }, rc); err != nil { log.Error("failed to RenderString: %v", err) } else { ctx.Data["ProfileReadme"] = profileContent diff --git a/routers/web/user/setting/profile.go b/routers/web/user/setting/profile.go index 464e99b55d..822aa8065f 100644 --- a/routers/web/user/setting/profile.go +++ b/routers/web/user/setting/profile.go @@ -52,6 +52,9 @@ func Profile(ctx *context.Context) { ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx) ctx.Data["CooldownPeriod"] = setting.Service.UsernameCooldownPeriod ctx.Data["CommonPronouns"] = commonPronouns + ctx.Data["MaxAvatarFileSize"] = setting.Avatar.MaxFileSize + ctx.Data["MaxAvatarWidth"] = setting.Avatar.MaxWidth + ctx.Data["MaxAvatarHeight"] = setting.Avatar.MaxHeight ctx.HTML(http.StatusOK, tplSettingsProfile) } @@ -64,6 +67,9 @@ func ProfilePost(ctx *context.Context) { ctx.Data["DisableGravatar"] = setting.Config().Picture.DisableGravatar.Value(ctx) ctx.Data["CooldownPeriod"] = setting.Service.UsernameCooldownPeriod ctx.Data["CommonPronouns"] = commonPronouns + ctx.Data["MaxAvatarFileSize"] = setting.Avatar.MaxFileSize + ctx.Data["MaxAvatarWidth"] = setting.Avatar.MaxWidth + ctx.Data["MaxAvatarHeight"] = setting.Avatar.MaxHeight if ctx.HasError() { ctx.HTML(http.StatusOK, tplSettingsProfile) diff --git a/routers/web/web.go b/routers/web/web.go index f8a13dab7e..4b39f22f7d 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1510,7 +1510,10 @@ func registerRoutes(m *web.Route) { m.Group("/commits", func() { m.Get("", context.RepoRef(), repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits) m.Get("/list", context.RepoRef(), repo.GetPullCommits) - m.Get("/{sha:[a-f0-9]{4,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit) + m.Group("/{sha:[a-f0-9]{4,40}}", func() { + m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit) + m.Post("/reviews/submit", context.RepoMustNotBeArchived(), web.Bind(forms.SubmitReviewForm{}), repo.SubmitReview) + }) }) m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), context.EnforceQuotaWeb(quota_model.LimitSubjectSizeGitAll, context.QuotaTargetRepo), repo.MergePullRequest) m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest) diff --git a/services/actions/notifier_helper.go b/services/actions/notifier_helper.go index 9654186fbb..e240c996b5 100644 --- a/services/actions/notifier_helper.go +++ b/services/actions/notifier_helper.go @@ -345,6 +345,14 @@ func handleWorkflows( Status: actions_model.StatusWaiting, } + if workflow, err := model.ReadWorkflow(bytes.NewReader(dwf.Content)); err == nil { + notifications, err := workflow.Notifications() + if err != nil { + log.Error("Notifications: %w", err) + } + run.NotifyEmail = notifications + } + need, err := ifNeedApproval(ctx, run, input.Repo, input.Doer) if err != nil { log.Error("check if need approval for repo %d with user %d: %v", input.Repo.ID, input.Doer.ID, err) diff --git a/services/actions/schedule_tasks.go b/services/actions/schedule_tasks.go index 3ec0807d5f..cf8b29ead7 100644 --- a/services/actions/schedule_tasks.go +++ b/services/actions/schedule_tasks.go @@ -4,6 +4,7 @@ package actions import ( + "bytes" "context" "errors" "fmt" @@ -18,6 +19,7 @@ import ( webhook_module "forgejo.org/modules/webhook" "github.com/nektos/act/pkg/jobparser" + act_model "github.com/nektos/act/pkg/model" "xorm.io/builder" ) @@ -140,6 +142,16 @@ func CreateScheduleTask(ctx context.Context, cron *actions_model.ActionSchedule) return err } + workflow, err := act_model.ReadWorkflow(bytes.NewReader(cron.Content)) + if err != nil { + return err + } + notifications, err := workflow.Notifications() + if err != nil { + return err + } + run.NotifyEmail = notifications + // Parse the workflow specification from the cron schedule workflows, err := jobparser.Parse(cron.Content, jobparser.WithVars(vars)) if err != nil { diff --git a/services/actions/schedule_tasks_test.go b/services/actions/schedule_tasks_test.go new file mode 100644 index 0000000000..7073985252 --- /dev/null +++ b/services/actions/schedule_tasks_test.go @@ -0,0 +1,121 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package actions + +import ( + "testing" + + actions_model "forgejo.org/models/actions" + repo_model "forgejo.org/models/repo" + "forgejo.org/models/unittest" + webhook_module "forgejo.org/modules/webhook" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCreateScheduleTask(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2, OwnerID: 2}) + + assertConstant := func(t *testing.T, cron *actions_model.ActionSchedule, run *actions_model.ActionRun) { + t.Helper() + assert.Equal(t, cron.Title, run.Title) + assert.Equal(t, cron.RepoID, run.RepoID) + assert.Equal(t, cron.OwnerID, run.OwnerID) + assert.Equal(t, cron.WorkflowID, run.WorkflowID) + assert.Equal(t, cron.TriggerUserID, run.TriggerUserID) + assert.Equal(t, cron.Ref, run.Ref) + assert.Equal(t, cron.CommitSHA, run.CommitSHA) + assert.Equal(t, cron.Event, run.Event) + assert.Equal(t, cron.EventPayload, run.EventPayload) + assert.Equal(t, cron.ID, run.ScheduleID) + assert.Equal(t, actions_model.StatusWaiting, run.Status) + } + + assertMutable := func(t *testing.T, expected, run *actions_model.ActionRun) { + t.Helper() + assert.Equal(t, expected.NotifyEmail, run.NotifyEmail) + } + + testCases := []struct { + name string + cron actions_model.ActionSchedule + want []actions_model.ActionRun + }{ + { + name: "simple", + cron: actions_model.ActionSchedule{ + Title: "scheduletitle1", + RepoID: repo.ID, + OwnerID: repo.OwnerID, + WorkflowID: "some.yml", + TriggerUserID: repo.OwnerID, + Ref: "branch", + CommitSHA: "fakeSHA", + Event: webhook_module.HookEventSchedule, + EventPayload: "fakepayload", + Content: []byte( + ` +name: test +on: push +jobs: + job2: + runs-on: ubuntu-latest + steps: + - run: true +`), + }, + want: []actions_model.ActionRun{ + { + Title: "scheduletitle1", + NotifyEmail: false, + }, + }, + }, + { + name: "enable-email-notifications is true", + cron: actions_model.ActionSchedule{ + Title: "scheduletitle2", + RepoID: repo.ID, + OwnerID: repo.OwnerID, + WorkflowID: "some.yml", + TriggerUserID: repo.OwnerID, + Ref: "branch", + CommitSHA: "fakeSHA", + Event: webhook_module.HookEventSchedule, + EventPayload: "fakepayload", + Content: []byte( + ` +name: test +enable-email-notifications: true +on: push +jobs: + job2: + runs-on: ubuntu-latest + steps: + - run: true +`), + }, + want: []actions_model.ActionRun{ + { + Title: "scheduletitle2", + NotifyEmail: true, + }, + }, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + require.NoError(t, CreateScheduleTask(t.Context(), &testCase.cron)) + require.Equal(t, len(testCase.want), unittest.GetCount(t, actions_model.ActionRun{RepoID: repo.ID})) + for _, expected := range testCase.want { + run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{Title: expected.Title}) + assertConstant(t, &testCase.cron, run) + assertMutable(t, &expected, run) + } + unittest.AssertSuccessfulDelete(t, actions_model.ActionRun{RepoID: repo.ID}) + }) + } +} diff --git a/services/actions/workflows.go b/services/actions/workflows.go index 7ec7c3abed..fbba3fd667 100644 --- a/services/actions/workflows.go +++ b/services/actions/workflows.go @@ -111,6 +111,11 @@ func (entry *Workflow) Dispatch(ctx context.Context, inputGetter InputValueGette return nil, nil, err } + notifications, err := wf.Notifications() + if err != nil { + return nil, nil, err + } + run := &actions_model.ActionRun{ Title: title, RepoID: repo.ID, @@ -125,6 +130,7 @@ func (entry *Workflow) Dispatch(ctx context.Context, inputGetter InputValueGette EventPayload: string(p), TriggerEvent: string(webhook.HookEventWorkflowDispatch), Status: actions_model.StatusWaiting, + NotifyEmail: notifications, } vars, err := actions_model.GetVariablesOfRun(ctx, run) diff --git a/services/automerge/automerge.go b/services/automerge/automerge.go index f183136907..cbfe3bd54e 100644 --- a/services/automerge/automerge.go +++ b/services/automerge/automerge.go @@ -107,6 +107,7 @@ func handlePullRequestAutoMerge(pullID int64, sha string) { return } if !exists { + log.Trace("GetScheduledMergeByPullID found nothing for PR %d", pullID) return } @@ -204,6 +205,10 @@ func handlePullRequestAutoMerge(pullID int64, sha string) { return } + if err := pull_model.DeleteScheduledAutoMerge(ctx, pr.ID); err != nil && !db.IsErrNotExist(err) { + log.Error("DeleteScheduledAutoMerge[%d]: %v", pr.ID, err) + } + if err := pull_service.Merge(ctx, pr, doer, baseGitRepo, scheduledPRM.MergeStyle, "", scheduledPRM.Message, true); err != nil { log.Error("pull_service.Merge: %v", err) // FIXME: if merge failed, we should display some error message to the pull request page. diff --git a/services/convert/action.go b/services/convert/action.go index 5e17172b45..703c1f1261 100644 --- a/services/convert/action.go +++ b/services/convert/action.go @@ -8,22 +8,17 @@ import ( actions_model "forgejo.org/models/actions" access_model "forgejo.org/models/perm/access" + user_model "forgejo.org/models/user" api "forgejo.org/modules/structs" ) // ToActionRun convert actions_model.User to api.ActionRun // the run needs all attributes loaded -func ToActionRun(ctx context.Context, run *actions_model.ActionRun) *api.ActionRun { +func ToActionRun(ctx context.Context, run *actions_model.ActionRun, doer *user_model.User) *api.ActionRun { if run == nil { return nil } - // The doer is the one whose perspective is used to view this ActionRun. - // In the best case we use the user that created the webhook. - // Unfortunately we don't know who that was. - // So instead we use the repo owner, who is able to create webhooks and allow others to do so by making them repo admins. - // This is pretty close to perfect. - doer := run.Repo.Owner permissionInRepo, _ := access_model.GetUserRepoPermission(ctx, run.Repo, doer) return &api.ActionRun{ diff --git a/services/convert/convert.go b/services/convert/convert.go index 48da9d7623..2ea24a1b51 100644 --- a/services/convert/convert.go +++ b/services/convert/convert.go @@ -222,29 +222,6 @@ func ToActionTask(ctx context.Context, t *actions_model.ActionTask) (*api.Action }, nil } -// ToRepoActionRun convert a actions_model.ActionRun to an api.RepoActionRun -func ToRepoActionRun(ctx context.Context, r *actions_model.ActionRun) (*api.RepoActionRun, error) { - if err := r.LoadAttributes(ctx); err != nil { - return nil, err - } - - url := strings.TrimSuffix(setting.AppURL, "/") + r.Link() - actor := ToUser(ctx, r.TriggerUser, nil) - - return &api.RepoActionRun{ - ID: r.ID, - Name: r.Title, - HeadBranch: r.PrettyRef(), - HeadSHA: r.CommitSHA, - RunNumber: r.Index, - Event: r.TriggerEvent, - Status: r.Status.String(), - WorkflowID: r.WorkflowID, - URL: url, - TriggeringActor: actor, - }, nil -} - // ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerification { verif := asymkey_model.ParseCommitWithSignature(ctx, c) diff --git a/services/federation/federation_service.go b/services/federation/federation_service.go index 174c175f86..a3b719d1a7 100644 --- a/services/federation/federation_service.go +++ b/services/federation/federation_service.go @@ -211,6 +211,11 @@ func CreateUserFromAP(ctx context.Context, personID fm.PersonID, federationHostI return nil, nil, err } + inbox, err := url.ParseRequestURI(person.Inbox.GetLink().String()) + if err != nil { + return nil, nil, err + } + newUser := user.User{ LowerName: strings.ToLower(name), Name: name, @@ -227,6 +232,7 @@ func CreateUserFromAP(ctx context.Context, personID fm.PersonID, federationHostI federatedUser := user.FederatedUser{ ExternalID: personID.ID, FederationHostID: federationHostID, + InboxPath: inbox.Path, NormalizedOriginalURL: personID.AsURI(), } diff --git a/services/feed/action.go b/services/feed/action.go index a2cd0551a3..7d179bd1c8 100644 --- a/services/feed/action.go +++ b/services/feed/action.go @@ -39,6 +39,24 @@ func NewNotifier() notify_service.Notifier { return &actionNotifier{} } +func notifyAll(ctx context.Context, action *activities_model.Action) error { + _, err := activities_model.NotifyWatchers(ctx, action) + if err != nil { + return err + } + return err + // return federation_service.NotifyActivityPubFollowers(ctx, out) +} + +func notifyAllActions(ctx context.Context, acts []*activities_model.Action) error { + _, err := activities_model.NotifyWatchersActions(ctx, acts) + if err != nil { + return err + } + return nil + // return federation_service.NotifyActivityPubFollowers(ctx, out) +} + func (a *actionNotifier) NewIssue(ctx context.Context, issue *issues_model.Issue, mentions []*user_model.User) { if err := issue.LoadPoster(ctx); err != nil { log.Error("issue.LoadPoster: %v", err) @@ -50,7 +68,7 @@ func (a *actionNotifier) NewIssue(ctx context.Context, issue *issues_model.Issue } repo := issue.Repo - if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{ + if err := notifyAll(ctx, &activities_model.Action{ ActUserID: issue.Poster.ID, ActUser: issue.Poster, OpType: activities_model.ActionCreateIssue, @@ -91,7 +109,7 @@ func (a *actionNotifier) IssueChangeStatus(ctx context.Context, doer *user_model } // Notify watchers for whatever action comes in, ignore if no action type. - if err := activities_model.NotifyWatchers(ctx, act); err != nil { + if err := notifyAll(ctx, act); err != nil { log.Error("NotifyWatchers: %v", err) } } @@ -127,7 +145,7 @@ func (a *actionNotifier) CreateIssueComment(ctx context.Context, doer *user_mode } // Notify watchers for whatever action comes in, ignore if no action type. - if err := activities_model.NotifyWatchers(ctx, act); err != nil { + if err := notifyAll(ctx, act); err != nil { log.Error("NotifyWatchers: %v", err) } } @@ -146,7 +164,7 @@ func (a *actionNotifier) NewPullRequest(ctx context.Context, pull *issues_model. return } - if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{ + if err := notifyAll(ctx, &activities_model.Action{ ActUserID: pull.Issue.Poster.ID, ActUser: pull.Issue.Poster, OpType: activities_model.ActionCreatePullRequest, @@ -160,7 +178,7 @@ func (a *actionNotifier) NewPullRequest(ctx context.Context, pull *issues_model. } func (a *actionNotifier) RenameRepository(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldRepoName string) { - if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{ + if err := notifyAll(ctx, &activities_model.Action{ ActUserID: doer.ID, ActUser: doer, OpType: activities_model.ActionRenameRepo, @@ -174,7 +192,7 @@ func (a *actionNotifier) RenameRepository(ctx context.Context, doer *user_model. } func (a *actionNotifier) TransferRepository(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldOwnerName string) { - if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{ + if err := notifyAll(ctx, &activities_model.Action{ ActUserID: doer.ID, ActUser: doer, OpType: activities_model.ActionTransferRepo, @@ -188,7 +206,7 @@ func (a *actionNotifier) TransferRepository(ctx context.Context, doer *user_mode } func (a *actionNotifier) CreateRepository(ctx context.Context, doer, u *user_model.User, repo *repo_model.Repository) { - if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{ + if err := notifyAll(ctx, &activities_model.Action{ ActUserID: doer.ID, ActUser: doer, OpType: activities_model.ActionCreateRepo, @@ -201,7 +219,7 @@ func (a *actionNotifier) CreateRepository(ctx context.Context, doer, u *user_mod } func (a *actionNotifier) ForkRepository(ctx context.Context, doer *user_model.User, oldRepo, repo *repo_model.Repository) { - if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{ + if err := notifyAll(ctx, &activities_model.Action{ ActUserID: doer.ID, ActUser: doer, OpType: activities_model.ActionCreateRepo, @@ -266,13 +284,13 @@ func (a *actionNotifier) PullRequestReview(ctx context.Context, pr *issues_model actions = append(actions, action) } - if err := activities_model.NotifyWatchersActions(ctx, actions); err != nil { + if err := notifyAllActions(ctx, actions); err != nil { log.Error("notify watchers '%d/%d': %v", review.Reviewer.ID, review.Issue.RepoID, err) } } func (*actionNotifier) MergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) { - if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{ + if err := notifyAll(ctx, &activities_model.Action{ ActUserID: doer.ID, ActUser: doer, OpType: activities_model.ActionMergePullRequest, @@ -286,7 +304,7 @@ func (*actionNotifier) MergePullRequest(ctx context.Context, doer *user_model.Us } func (*actionNotifier) AutoMergePullRequest(ctx context.Context, doer *user_model.User, pr *issues_model.PullRequest) { - if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{ + if err := notifyAll(ctx, &activities_model.Action{ ActUserID: doer.ID, ActUser: doer, OpType: activities_model.ActionAutoMergePullRequest, @@ -304,7 +322,7 @@ func (*actionNotifier) NotifyPullRevieweDismiss(ctx context.Context, doer *user_ if len(review.OriginalAuthor) > 0 { reviewerName = review.OriginalAuthor } - if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{ + if err := notifyAll(ctx, &activities_model.Action{ ActUserID: doer.ID, ActUser: doer, OpType: activities_model.ActionPullReviewDismissed, @@ -342,7 +360,7 @@ func (a *actionNotifier) PushCommits(ctx context.Context, pusher *user_model.Use opType = activities_model.ActionDeleteBranch } - if err = activities_model.NotifyWatchers(ctx, &activities_model.Action{ + if err = notifyAll(ctx, &activities_model.Action{ ActUserID: pusher.ID, ActUser: pusher, OpType: opType, @@ -362,7 +380,7 @@ func (a *actionNotifier) CreateRef(ctx context.Context, doer *user_model.User, r // has sent same action in `PushCommits`, so skip it. return } - if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{ + if err := notifyAll(ctx, &activities_model.Action{ ActUserID: doer.ID, ActUser: doer, OpType: opType, @@ -381,7 +399,7 @@ func (a *actionNotifier) DeleteRef(ctx context.Context, doer *user_model.User, r // has sent same action in `PushCommits`, so skip it. return } - if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{ + if err := notifyAll(ctx, &activities_model.Action{ ActUserID: doer.ID, ActUser: doer, OpType: opType, @@ -405,7 +423,7 @@ func (a *actionNotifier) SyncPushCommits(ctx context.Context, pusher *user_model return } - if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{ + if err := notifyAll(ctx, &activities_model.Action{ ActUserID: repo.OwnerID, ActUser: repo.MustOwner(ctx), OpType: activities_model.ActionMirrorSyncPush, @@ -420,7 +438,7 @@ func (a *actionNotifier) SyncPushCommits(ctx context.Context, pusher *user_model } func (a *actionNotifier) SyncCreateRef(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, refFullName git.RefName, refID string) { - if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{ + if err := notifyAll(ctx, &activities_model.Action{ ActUserID: repo.OwnerID, ActUser: repo.MustOwner(ctx), OpType: activities_model.ActionMirrorSyncCreate, @@ -434,7 +452,7 @@ func (a *actionNotifier) SyncCreateRef(ctx context.Context, doer *user_model.Use } func (a *actionNotifier) SyncDeleteRef(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, refFullName git.RefName) { - if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{ + if err := notifyAll(ctx, &activities_model.Action{ ActUserID: repo.OwnerID, ActUser: repo.MustOwner(ctx), OpType: activities_model.ActionMirrorSyncDelete, @@ -452,7 +470,7 @@ func (a *actionNotifier) NewRelease(ctx context.Context, rel *repo_model.Release log.Error("LoadAttributes: %v", err) return } - if err := activities_model.NotifyWatchers(ctx, &activities_model.Action{ + if err := notifyAll(ctx, &activities_model.Action{ ActUserID: rel.PublisherID, ActUser: rel.Publisher, OpType: activities_model.ActionPublishRelease, diff --git a/services/issue/pull.go b/services/issue/pull.go index b0a0c47d88..2eef1fbfa8 100644 --- a/services/issue/pull.go +++ b/services/issue/pull.go @@ -43,8 +43,6 @@ type ReviewRequestNotifier struct { } func PullRequestCodeOwnersReview(ctx context.Context, issue *issues_model.Issue, pr *issues_model.PullRequest) ([]*ReviewRequestNotifier, error) { - files := []string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"} - if pr.IsWorkInProgress(ctx) { return nil, nil } @@ -72,18 +70,17 @@ func PullRequestCodeOwnersReview(ctx context.Context, issue *issues_model.Issue, return nil, err } - var data string - for _, file := range files { + var rules []*issues_model.CodeOwnerRule + for _, file := range []string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"} { if blob, err := commit.GetBlobByPath(file); err == nil { - data, err = blob.GetBlobContent(setting.UI.MaxDisplayFileSize) + rc, size, err := blob.NewTruncatedReader(setting.UI.MaxDisplayFileSize) if err == nil { + rules, _ = issues_model.GetCodeOwnersFromReader(ctx, rc, size > setting.UI.MaxDisplayFileSize) break } } } - rules, _ := issues_model.GetCodeOwnersFromContent(ctx, data) - // get the mergebase mergeBase, err := getMergeBase(repo, pr, git.BranchPrefix+pr.BaseBranch, pr.GetGitRefName()) if err != nil { diff --git a/services/mailer/mail_actions.go b/services/mailer/mail_actions.go index 7c63603a98..09763e164e 100644 --- a/services/mailer/mail_actions.go +++ b/services/mailer/mail_actions.go @@ -23,19 +23,24 @@ func MailActionRun(run *actions_model.ActionRun, priorStatus actions_model.Statu return nil } - if run.TriggerUser.Email != "" && run.TriggerUser.EmailNotificationsPreference != user_model.EmailNotificationsDisabled { - if err := sendMailActionRun(run.TriggerUser, run, priorStatus, lastRun); err != nil { - return err - } + if !run.NotifyEmail { + return nil } - if run.Repo.Owner.Email != "" && run.Repo.Owner.Email != run.TriggerUser.Email && run.Repo.Owner.EmailNotificationsPreference != user_model.EmailNotificationsDisabled { - if err := sendMailActionRun(run.Repo.Owner, run, priorStatus, lastRun); err != nil { - return err - } + user := run.TriggerUser + // this happens e.g. when this is a scheduled run + if user.IsSystem() { + user = run.Repo.Owner + } + if user.IsSystem() || user.Email == "" { + return nil } - return nil + if user.EmailNotificationsPreference == user_model.EmailNotificationsDisabled { + return nil + } + + return sendMailActionRun(user, run, priorStatus, lastRun) } func sendMailActionRun(to *user_model.User, run *actions_model.ActionRun, priorStatus actions_model.Status, lastRun *actions_model.ActionRun) error { diff --git a/services/mailer/mail_actions_now_done_test.go b/services/mailer/mail_actions_now_done_test.go index 0d832f2b36..6a01ea7631 100644 --- a/services/mailer/mail_actions_now_done_test.go +++ b/services/mailer/mail_actions_now_done_test.go @@ -4,42 +4,53 @@ package mailer import ( + "slices" "testing" actions_model "forgejo.org/models/actions" "forgejo.org/models/db" + organization_model "forgejo.org/models/organization" repo_model "forgejo.org/models/repo" user_model "forgejo.org/models/user" + "forgejo.org/modules/optional" "forgejo.org/modules/setting" + "forgejo.org/modules/test" notify_service "forgejo.org/services/notify" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func getActionsNowDoneTestUsers(t *testing.T) []*user_model.User { +func getActionsNowDoneTestUser(t *testing.T, name, email, notifications string) *user_model.User { t.Helper() - newTriggerUser := new(user_model.User) - newTriggerUser.Name = "new_trigger_user" - newTriggerUser.Language = "en_US" - newTriggerUser.IsAdmin = false - newTriggerUser.Email = "new_trigger_user@example.com" - newTriggerUser.LastLoginUnix = 1693648327 - newTriggerUser.CreatedUnix = 1693648027 - newTriggerUser.EmailNotificationsPreference = user_model.EmailNotificationsEnabled - require.NoError(t, user_model.CreateUser(db.DefaultContext, newTriggerUser)) + user := new(user_model.User) + user.Name = name + user.Language = "en_US" + user.IsAdmin = false + user.Email = email + user.LastLoginUnix = 1693648327 + user.CreatedUnix = 1693648027 + opts := user_model.CreateUserOverwriteOptions{ + AllowCreateOrganization: optional.Some(true), + EmailNotificationsPreference: ¬ifications, + } + require.NoError(t, user_model.AdminCreateUser(db.DefaultContext, user, &opts)) + return user +} - newOwner := new(user_model.User) - newOwner.Name = "new_owner" - newOwner.Language = "en_US" - newOwner.IsAdmin = false - newOwner.Email = "new_owner@example.com" - newOwner.LastLoginUnix = 1693648329 - newOwner.CreatedUnix = 1693648029 - newOwner.EmailNotificationsPreference = user_model.EmailNotificationsEnabled - require.NoError(t, user_model.CreateUser(db.DefaultContext, newOwner)) - - return []*user_model.User{newTriggerUser, newOwner} +func getActionsNowDoneTestOrg(t *testing.T, name, email string, owner *user_model.User) *user_model.User { + t.Helper() + org := new(organization_model.Organization) + org.Name = name + org.Language = "en_US" + org.IsAdmin = false + // contact email for the organization, for display purposes but otherwise not used as of v12 + org.Email = email + org.LastLoginUnix = 1693648327 + org.CreatedUnix = 1693648027 + org.Email = email + require.NoError(t, organization_model.CreateOrganization(db.DefaultContext, org, owner)) + return (*user_model.User)(org) } func assertTranslatedLocaleMailActionsNowDone(t *testing.T, msgBody string) { @@ -49,98 +60,181 @@ func assertTranslatedLocaleMailActionsNowDone(t *testing.T, msgBody string) { func TestActionRunNowDoneNotificationMail(t *testing.T) { ctx := t.Context() - users := getActionsNowDoneTestUsers(t) - defer CleanUpUsers(ctx, users) - triggerUser := users[0] - ownerUser := users[1] + defer test.MockVariableValue(&setting.Admin.DisableRegularOrgCreation, false)() + + actionsUser := user_model.NewActionsUser() + require.NotEmpty(t, actionsUser.Email) repo := repo_model.Repository{ Name: "some repo", Description: "rockets are cool", - Owner: ownerUser, - OwnerID: ownerUser.ID, } // Do some funky stuff with the action run's ids: // The run with the larger ID finished first. // This is odd but something that must work. - run1 := &actions_model.ActionRun{ID: 2, Repo: &repo, RepoID: repo.ID, Title: "some workflow", TriggerUser: triggerUser, TriggerUserID: triggerUser.ID, Status: actions_model.StatusFailure, Stopped: 1745821796, TriggerEvent: "workflow_dispatch"} - run2 := &actions_model.ActionRun{ID: 1, Repo: &repo, RepoID: repo.ID, Title: "some workflow", TriggerUser: triggerUser, TriggerUserID: triggerUser.ID, Status: actions_model.StatusSuccess, Stopped: 1745822796, TriggerEvent: "push"} + run1 := &actions_model.ActionRun{ID: 2, Repo: &repo, RepoID: repo.ID, Title: "some workflow", Status: actions_model.StatusFailure, Stopped: 1745821796, TriggerEvent: "workflow_dispatch"} + run2 := &actions_model.ActionRun{ID: 1, Repo: &repo, RepoID: repo.ID, Title: "some workflow", Status: actions_model.StatusSuccess, Stopped: 1745822796, TriggerEvent: "push"} + + assignUsers := func(triggerUser, owner *user_model.User) { + for _, run := range []*actions_model.ActionRun{run1, run2} { + run.TriggerUser = triggerUser + run.TriggerUserID = triggerUser.ID + run.NotifyEmail = true + } + repo.Owner = owner + repo.OwnerID = owner.ID + } notify_service.RegisterNotifier(NewNotifier()) + orgOwner := getActionsNowDoneTestUser(t, "org_owner", "org_owner@example.com", "disabled") + defer CleanUpUsers(ctx, []*user_model.User{orgOwner}) + t.Run("DontSendNotificationEmailOnFirstActionSuccess", func(t *testing.T) { + user := getActionsNowDoneTestUser(t, "new_user", "new_user@example.com", "enabled") + defer CleanUpUsers(ctx, []*user_model.User{user}) + assignUsers(user, user) defer MockMailSettings(func(msgs ...*Message) { assert.Fail(t, "no mail should be sent") })() notify_service.ActionRunNowDone(ctx, run2, actions_model.StatusRunning, nil) }) - t.Run("SendNotificationEmailOnActionRunFailed", func(t *testing.T) { - mailSentToOwner := false - mailSentToTriggerUser := false + t.Run("WorkflowEnableEmailNotificationIsFalse", func(t *testing.T) { + user := getActionsNowDoneTestUser(t, "new_user1", "new_user1@example.com", "enabled") + defer CleanUpUsers(ctx, []*user_model.User{user}) + assignUsers(user, user) defer MockMailSettings(func(msgs ...*Message) { - assert.LessOrEqual(t, len(msgs), 2) - for _, msg := range msgs { - switch msg.To { - case triggerUser.EmailTo(): - assert.False(t, mailSentToTriggerUser, "sent mail twice") - mailSentToTriggerUser = true - case ownerUser.EmailTo(): - assert.False(t, mailSentToOwner, "sent mail twice") - mailSentToOwner = true - default: - assert.Fail(t, "sent mail to unknown sender", msg.To) - } - assert.Contains(t, msg.Body, triggerUser.HTMLURL()) - assert.Contains(t, msg.Body, triggerUser.Name) - // what happened - assert.Contains(t, msg.Body, "failed") - // new status of run - assert.Contains(t, msg.Body, "failure") - // prior status of this run - assert.Contains(t, msg.Body, "waiting") - assertTranslatedLocaleMailActionsNowDone(t, msg.Body) - } + assert.Fail(t, "no mail should be sent") })() - notify_service.ActionRunNowDone(ctx, run1, actions_model.StatusWaiting, nil) - assert.True(t, mailSentToOwner) - assert.True(t, mailSentToTriggerUser) + run2.NotifyEmail = false + notify_service.ActionRunNowDone(ctx, run2, actions_model.StatusRunning, nil) }) - t.Run("SendNotificationEmailOnActionRunRecovered", func(t *testing.T) { - mailSentToOwner := false - mailSentToTriggerUser := false - defer MockMailSettings(func(msgs ...*Message) { - assert.LessOrEqual(t, len(msgs), 2) - for _, msg := range msgs { - switch msg.To { - case triggerUser.EmailTo(): - assert.False(t, mailSentToTriggerUser, "sent mail twice") - mailSentToTriggerUser = true - case ownerUser.EmailTo(): - assert.False(t, mailSentToOwner, "sent mail twice") - mailSentToOwner = true - default: - assert.Fail(t, "sent mail to unknown sender", msg.To) - } - assert.Contains(t, msg.Body, triggerUser.HTMLURL()) - assert.Contains(t, msg.Body, triggerUser.Name) - // what happened - assert.Contains(t, msg.Body, "recovered") - // old status of run - assert.Contains(t, msg.Body, "failure") - // new status of run - assert.Contains(t, msg.Body, "success") - // prior status of this run - assert.Contains(t, msg.Body, "running") - assertTranslatedLocaleMailActionsNowDone(t, msg.Body) - } - })() - assert.NotNil(t, setting.MailService) + for _, testCase := range []struct { + name string + triggerUser *user_model.User + owner *user_model.User + expected string + expectMail bool + }{ + { + // if the action is assigned a trigger user in a repository + // owned by a regular user, the mail is sent to the trigger user + name: "RegularTriggerUser", + triggerUser: getActionsNowDoneTestUser(t, "new_trigger_user0", "new_trigger_user0@example.com", user_model.EmailNotificationsEnabled), + owner: getActionsNowDoneTestUser(t, "new_owner0", "new_owner0@example.com", user_model.EmailNotificationsEnabled), + expected: "trigger", + expectMail: true, + }, + { + // if the action is assigned to a system user (e.g. ActionsUser) + // in a repository owned by a regular user, the mail is sent to + // the user that owns the repository + name: "SystemTriggerUserAndRegularOwner", + triggerUser: actionsUser, + owner: getActionsNowDoneTestUser(t, "new_owner1", "new_owner1@example.com", user_model.EmailNotificationsEnabled), + expected: "owner", + expectMail: true, + }, + { + // if the action is assigned a trigger user with disabled notifications in a repository + // owned by a regular user, no mail is sent + name: "RegularTriggerUserNotificationsDisabled", + triggerUser: getActionsNowDoneTestUser(t, "new_trigger_user2", "new_trigger_user2@example.com", user_model.EmailNotificationsDisabled), + owner: getActionsNowDoneTestUser(t, "new_owner2", "new_owner2@example.com", user_model.EmailNotificationsEnabled), + expectMail: false, + }, + { + // if the action is assigned to a system user (e.g. ActionsUser) + // owned by a regular user with disabled notifications, no mail is sent + name: "SystemTriggerUserAndRegularOwnerNotificationsDisabled", + triggerUser: actionsUser, + owner: getActionsNowDoneTestUser(t, "new_owner3", "new_owner3@example.com", user_model.EmailNotificationsDisabled), + expectMail: false, + }, + { + // if the action is assigned to a system user (e.g. ActionsUser) + // in a repository owned by an organization with an email contact, the mail is sent to + // this email contact + name: "SystemTriggerUserAndOrgOwner", + triggerUser: actionsUser, + owner: getActionsNowDoneTestOrg(t, "new_org1", "new_org_owner0@example.com", orgOwner), + expected: "owner", + expectMail: true, + }, + { + // if the action is assigned to a system user (e.g. ActionsUser) + // in a repository owned by an organization without an email contact, no mail is sent + name: "SystemTriggerUserAndNoMailOrgOwner", + triggerUser: actionsUser, + owner: getActionsNowDoneTestOrg(t, "new_org2", "", orgOwner), + expectMail: false, + }, + } { + t.Run(testCase.name, func(t *testing.T) { + assignUsers(testCase.triggerUser, testCase.owner) + defer CleanUpUsers(ctx, slices.DeleteFunc([]*user_model.User{testCase.triggerUser, testCase.owner}, func(user *user_model.User) bool { + return user.IsSystem() + })) - notify_service.ActionRunNowDone(ctx, run2, actions_model.StatusRunning, run1) - assert.True(t, mailSentToOwner) - assert.True(t, mailSentToTriggerUser) - }) + t.Run("SendNotificationEmailOnActionRunFailed", func(t *testing.T) { + mailSent := false + defer MockMailSettings(func(msgs ...*Message) { + assert.Len(t, msgs, 1) + msg := msgs[0] + assert.False(t, mailSent, "sent mail twice") + expectedEmail := testCase.triggerUser.Email + if testCase.expected == "owner" { // otherwise "trigger" + expectedEmail = testCase.owner.Email + } + require.Contains(t, msg.To, expectedEmail, "sent mail to unknown sender") + mailSent = true + assert.Contains(t, msg.Body, testCase.triggerUser.HTMLURL()) + assert.Contains(t, msg.Body, testCase.triggerUser.Name) + // what happened + assert.Contains(t, msg.Body, "failed") + // new status of run + assert.Contains(t, msg.Body, "failure") + // prior status of this run + assert.Contains(t, msg.Body, "waiting") + assertTranslatedLocaleMailActionsNowDone(t, msg.Body) + })() + require.NotNil(t, setting.MailService) + + notify_service.ActionRunNowDone(ctx, run1, actions_model.StatusWaiting, nil) + assert.Equal(t, testCase.expectMail, mailSent) + }) + + t.Run("SendNotificationEmailOnActionRunRecovered", func(t *testing.T) { + mailSent := false + defer MockMailSettings(func(msgs ...*Message) { + assert.Len(t, msgs, 1) + msg := msgs[0] + assert.False(t, mailSent, "sent mail twice") + expectedEmail := testCase.triggerUser.Email + if testCase.expected == "owner" { // otherwise "trigger" + expectedEmail = testCase.owner.Email + } + require.Contains(t, msg.To, expectedEmail, "sent mail to unknown sender") + mailSent = true + assert.Contains(t, msg.Body, testCase.triggerUser.HTMLURL()) + assert.Contains(t, msg.Body, testCase.triggerUser.Name) + // what happened + assert.Contains(t, msg.Body, "recovered") + // old status of run + assert.Contains(t, msg.Body, "failure") + // new status of run + assert.Contains(t, msg.Body, "success") + // prior status of this run + assert.Contains(t, msg.Body, "running") + })() + require.NotNil(t, setting.MailService) + + notify_service.ActionRunNowDone(ctx, run2, actions_model.StatusRunning, run1) + assert.Equal(t, testCase.expectMail, mailSent) + }) + }) + } } diff --git a/services/mailer/main_test.go b/services/mailer/main_test.go index 47e5d5d175..5e9cbe3e99 100644 --- a/services/mailer/main_test.go +++ b/services/mailer/main_test.go @@ -8,6 +8,7 @@ import ( "testing" "forgejo.org/models/db" + organization_model "forgejo.org/models/organization" "forgejo.org/models/unittest" user_model "forgejo.org/models/user" "forgejo.org/modules/setting" @@ -51,6 +52,11 @@ func MockMailSettings(send func(msgs ...*Message)) func() { func CleanUpUsers(ctx context.Context, users []*user_model.User) { for _, u := range users { - db.DeleteByID[user_model.User](ctx, u.ID) + if u.IsOrganization() { + organization_model.DeleteOrganization(ctx, (*organization_model.Organization)(u)) + } else { + db.DeleteByID[user_model.User](ctx, u.ID) + db.DeleteByBean(ctx, &user_model.EmailAddress{UID: u.ID}) + } } } diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index 55adad9685..7887dacdb1 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -766,7 +766,7 @@ func (g *GiteaLocalUploader) newPullRequest(pr *base.PullRequest) (*issues_model issue := issues_model.Issue{ RepoID: g.repo.ID, Repo: g.repo, - Title: prTitle, + Title: util.TruncateRunes(prTitle, 255), Index: pr.Number, Content: pr.Content, MilestoneID: milestoneID, diff --git a/services/migrations/gitlab.go b/services/migrations/gitlab.go index c4014c903f..f54f682c47 100644 --- a/services/migrations/gitlab.go +++ b/services/migrations/gitlab.go @@ -99,6 +99,7 @@ func NewGitlabDownloader(ctx context.Context, baseURL, repoPath, username, passw // Only use basic auth if token is blank and password is NOT // Basic auth will fail with empty strings, but empty token will allow anonymous public API usage if token == "" && password != "" { + //nolint // SA1019 gitlab.NewBasicAuthClient is deprecated: GitLab recommends against using this authentication method gitlabClient, err = gitlab.NewBasicAuthClient(username, password, gitlab.WithBaseURL(baseURL), gitlab.WithHTTPClient(NewMigrationHTTPClient())) } diff --git a/services/pull/check.go b/services/pull/check.go index afeb7e675e..6002e2ae26 100644 --- a/services/pull/check.go +++ b/services/pull/check.go @@ -28,6 +28,7 @@ import ( "forgejo.org/modules/timeutil" asymkey_service "forgejo.org/services/asymkey" notify_service "forgejo.org/services/notify" + shared_automerge "forgejo.org/services/shared/automerge" ) // prPatchCheckerQueue represents a queue to handle update pull request tests @@ -170,7 +171,7 @@ func isSignedIfRequired(ctx context.Context, pr *issues_model.PullRequest, doer // checkAndUpdateStatus checks if pull request is possible to leaving checking status, // and set to be either conflict or mergeable. -func checkAndUpdateStatus(ctx context.Context, pr *issues_model.PullRequest) { +func checkAndUpdateStatus(ctx context.Context, pr *issues_model.PullRequest) bool { // If status has not been changed to conflict by testPatch then we are mergeable if pr.Status == issues_model.PullRequestStatusChecking { pr.Status = issues_model.PullRequestStatusMergeable @@ -184,12 +185,15 @@ func checkAndUpdateStatus(ctx context.Context, pr *issues_model.PullRequest) { if has { log.Trace("Not updating status for %-v as it is due to be rechecked", pr) - return + return false } if err := pr.UpdateColsIfNotMerged(ctx, "merge_base", "status", "conflicted_files", "changed_protected_files"); err != nil { log.Error("Update[%-v]: %v", pr, err) + return false } + + return true } // getMergeCommit checks if a pull request has been merged @@ -339,15 +343,22 @@ func handler(items ...string) []string { } func testPR(id int64) { - pullWorkingPool.CheckIn(fmt.Sprint(id)) - defer pullWorkingPool.CheckOut(fmt.Sprint(id)) ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("Test PR[%d] from patch checking queue", id)) defer finished() + if pr, updated := testPRProtected(ctx, id); pr != nil && updated { + shared_automerge.AddToQueueIfMergeable(ctx, pr) + } +} + +func testPRProtected(ctx context.Context, id int64) (*issues_model.PullRequest, bool) { + pullWorkingPool.CheckIn(fmt.Sprint(id)) + defer pullWorkingPool.CheckOut(fmt.Sprint(id)) + pr, err := issues_model.GetPullRequestByID(ctx, id) if err != nil { log.Error("Unable to GetPullRequestByID[%d] for testPR: %v", id, err) - return + return nil, false } log.Trace("Testing %-v", pr) @@ -357,12 +368,12 @@ func testPR(id int64) { if pr.HasMerged { log.Trace("%-v is already merged (status: %s, merge commit: %s)", pr, pr.Status, pr.MergedCommitID) - return + return nil, false } if manuallyMerged(ctx, pr) { log.Trace("%-v is manually merged (status: %s, merge commit: %s)", pr, pr.Status, pr.MergedCommitID) - return + return nil, false } if err := TestPatch(pr); err != nil { @@ -371,9 +382,10 @@ func testPR(id int64) { if err := pr.UpdateCols(ctx, "status"); err != nil { log.Error("update pr [%-v] status to PullRequestStatusError failed: %v", pr, err) } - return + return nil, false } - checkAndUpdateStatus(ctx, pr) + + return pr, checkAndUpdateStatus(ctx, pr) } // CheckPRsForBaseBranch check all pulls with baseBrannch diff --git a/services/repository/files/content.go b/services/repository/files/content.go index 5f7dd38303..3d2217df18 100644 --- a/services/repository/files/content.go +++ b/services/repository/files/content.go @@ -250,8 +250,25 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, treePath, ref return contentsResponse, nil } -// GetBlobBySHA get the GitBlobResponse of a repository using a sha hash. -func GetBlobBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, sha string) (*api.GitBlobResponse, error) { +// GetBlobsBySHA gets multiple GitBlobs of a repository by sha hash. +func GetBlobsBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, shas []string) ([]*api.GitBlob, error) { + if len(shas) > setting.API.MaxResponseItems { + shas = shas[:setting.API.MaxResponseItems] + } + + blobs := make([]*api.GitBlob, 0, len(shas)) + for _, sha := range shas { + blob, err := GetBlobBySHA(ctx, repo, gitRepo, sha) + if err != nil { + return nil, err + } + blobs = append(blobs, blob) + } + return blobs, nil +} + +// GetBlobBySHA get the GitBlob of a repository using a sha hash. +func GetBlobBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, sha string) (*api.GitBlob, error) { gitBlob, err := gitRepo.GetBlob(sha) if err != nil { return nil, err @@ -263,7 +280,7 @@ func GetBlobBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git return nil, err } } - return &api.GitBlobResponse{ + return &api.GitBlob{ SHA: gitBlob.ID.String(), URL: repo.APIURL() + "/git/blobs/" + url.PathEscape(gitBlob.ID.String()), Size: gitBlob.Size(), diff --git a/services/repository/files/content_test.go b/services/repository/files/content_test.go index e55b840660..8fc8f56b4f 100644 --- a/services/repository/files/content_test.go +++ b/services/repository/files/content_test.go @@ -192,7 +192,7 @@ func TestGetBlobBySHA(t *testing.T) { defer gitRepo.Close() gbr, err := GetBlobBySHA(db.DefaultContext, repo, gitRepo, "65f1bf27bc3bf70f64657658635e66094edbcb4d") - expectedGBR := &api.GitBlobResponse{ + expectedGBR := &api.GitBlob{ Content: "dHJlZSAyYTJmMWQ0NjcwNzI4YTJlMTAwNDllMzQ1YmQ3YTI3NjQ2OGJlYWI2CmF1dGhvciB1c2VyMSA8YWRkcmVzczFAZXhhbXBsZS5jb20+IDE0ODk5NTY0NzkgLTA0MDAKY29tbWl0dGVyIEV0aGFuIEtvZW5pZyA8ZXRoYW50a29lbmlnQGdtYWlsLmNvbT4gMTQ4OTk1NjQ3OSAtMDQwMAoKSW5pdGlhbCBjb21taXQK", Encoding: "base64", URL: "https://try.gitea.io/api/v1/repos/user2/repo1/git/blobs/65f1bf27bc3bf70f64657658635e66094edbcb4d", @@ -202,3 +202,43 @@ func TestGetBlobBySHA(t *testing.T) { require.NoError(t, err) assert.Equal(t, expectedGBR, gbr) } + +func TestGetBlobsBySHA(t *testing.T) { + unittest.PrepareTestEnv(t) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + + gitRepo, err := gitrepo.OpenRepository(db.DefaultContext, repo) + require.NoError(t, err) + defer gitRepo.Close() + + gbr, err := GetBlobsBySHA(db.DefaultContext, repo, gitRepo, []string{ + "ea82fc8777a24b07c26b3a4bf4e2742c03733eab", // Home.md + "6395b68e1feebb1e4c657b4f9f6ba2676a283c0b", // line.svg + "26f842bcad37fa40a1bb34cbb5ee219ee35d863d", // test.xml + }) + expectedGBR := []*api.GitBlob{ + { + Content: "IyBIb21lIHBhZ2UKClRoaXMgaXMgdGhlIGhvbWUgcGFnZSEK", + Encoding: "base64", + URL: "https://try.gitea.io/api/v1/repos/user2/repo2/git/blobs/ea82fc8777a24b07c26b3a4bf4e2742c03733eab", + SHA: "ea82fc8777a24b07c26b3a4bf4e2742c03733eab", + Size: 36, + }, + { + Content: "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZwogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHdpZHRoPSIxMjgiCiAgIGhlaWdodD0iMTI4IgogICB2aWV3Qm94PSIwIDAgMTI4IDEyOCI+CgogIDxsaW5lIHgxPSIwIiB5MT0iNyIgeDI9IjEwIiB5Mj0iNyIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KPC9zdmc+", + Encoding: "base64", + URL: "https://try.gitea.io/api/v1/repos/user2/repo2/git/blobs/6395b68e1feebb1e4c657b4f9f6ba2676a283c0b", + SHA: "6395b68e1feebb1e4c657b4f9f6ba2676a283c0b", + Size: 246, + }, + { + Content: "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHRlc3Q+VGhpcyBpcyBYTUw8L3Rlc3Q+Cg==", + Encoding: "base64", + URL: "https://try.gitea.io/api/v1/repos/user2/repo2/git/blobs/26f842bcad37fa40a1bb34cbb5ee219ee35d863d", + SHA: "26f842bcad37fa40a1bb34cbb5ee219ee35d863d", + Size: 64, + }, + } + require.NoError(t, err) + assert.Equal(t, expectedGBR, gbr) +} diff --git a/services/repository/files/update.go b/services/repository/files/update.go index 5e8834c6de..8fb9644fa4 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -193,28 +193,34 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use } if hasOldBranch { - // Get the commit of the original branch - commit, err := t.GetBranchCommit(opts.OldBranch) + // Get the current commit of the original branch + actualBaseCommit, err := t.GetBranchCommit(opts.OldBranch) if err != nil { return nil, err // Couldn't get a commit for the branch } - // Assigned LastCommitID in opts if it hasn't been set - if opts.LastCommitID == "" { - opts.LastCommitID = commit.ID.String() - } else { - lastCommitID, err := t.gitRepo.ConvertToGitID(opts.LastCommitID) + var lastKnownCommit git.ObjectID // when nil, the sha provided in the opts.Files must match the current blob-sha + if opts.OldBranch != opts.NewBranch { + // when creating a new branch, ignore if a file has been changed in the meantime + // (such changes will visible when doing the merge) + lastKnownCommit = actualBaseCommit.ID + } else if opts.LastCommitID != "" { + lastKnownCommit, err = t.gitRepo.ConvertToGitID(opts.LastCommitID) if err != nil { return nil, fmt.Errorf("ConvertToSHA1: Invalid last commit ID: %w", err) } - opts.LastCommitID = lastCommitID.String() } for _, file := range opts.Files { - if err := handleCheckErrors(file, commit, opts); err != nil { + if err := handleCheckErrors(file, actualBaseCommit, lastKnownCommit); err != nil { return nil, err } } + + if opts.LastCommitID == "" { + // needed for t.CommitTree + opts.LastCommitID = actualBaseCommit.ID.String() + } } contentStore := lfs.NewContentStore() @@ -277,9 +283,9 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use } // handles the check for various issues for ChangeRepoFiles -func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRepoFilesOptions) error { +func handleCheckErrors(file *ChangeRepoFile, actualBaseCommit *git.Commit, lastKnownCommit git.ObjectID) error { if file.Operation == "update" || file.Operation == "delete" { - fromEntry, err := commit.GetTreeEntryByPath(file.Options.fromTreePath) + fromEntry, err := actualBaseCommit.GetTreeEntryByPath(file.Options.fromTreePath) if err != nil { return err } @@ -292,22 +298,22 @@ func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRep CurrentSHA: fromEntry.ID.String(), } } - } else if opts.LastCommitID != "" { - // If a lastCommitID was given and it doesn't match the commitID of the head of the branch throw - // an error, but only if we aren't creating a new branch. - if commit.ID.String() != opts.LastCommitID && opts.OldBranch == opts.NewBranch { - if changed, err := commit.FileChangedSinceCommit(file.Options.treePath, opts.LastCommitID); err != nil { + } else if lastKnownCommit != nil { + if actualBaseCommit.ID.String() != lastKnownCommit.String() { + // If a lastKnownCommit was given and it doesn't match the actualBaseCommit, + // check if the file has been changed in between + if changed, err := actualBaseCommit.FileChangedSinceCommit(file.Options.treePath, lastKnownCommit.String()); err != nil { return err } else if changed { return models.ErrCommitIDDoesNotMatch{ - GivenCommitID: opts.LastCommitID, - CurrentCommitID: opts.LastCommitID, + GivenCommitID: lastKnownCommit.String(), + CurrentCommitID: actualBaseCommit.ID.String(), } } - // The file wasn't modified, so we are good to delete it + // The file wasn't modified, so we are good to update it } } else { - // When updating a file, a lastCommitID or SHA needs to be given to make sure other commits + // When updating a file, a lastKnownCommit or SHA needs to be given to make sure other commits // haven't been made. We throw an error if one wasn't provided. return models.ErrSHAOrCommitIDNotProvided{} } @@ -322,7 +328,7 @@ func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRep subTreePath := "" for index, part := range treePathParts { subTreePath = path.Join(subTreePath, part) - entry, err := commit.GetTreeEntryByPath(subTreePath) + entry, err := actualBaseCommit.GetTreeEntryByPath(subTreePath) if err != nil { if git.IsErrNotExist(err) { // Means there is no item with that name, so we're good diff --git a/services/shared/automerge/automerge.go b/services/shared/automerge/automerge.go index 1dc309f4b3..be7b2f6eb4 100644 --- a/services/shared/automerge/automerge.go +++ b/services/shared/automerge/automerge.go @@ -21,9 +21,9 @@ import ( var PRAutoMergeQueue *queue.WorkerPoolQueue[string] func addToQueue(pr *issues_model.PullRequest, sha string) { - log.Trace("Adding pullID: %d to the pull requests patch checking queue with sha %s", pr.ID, sha) + log.Trace("Adding pullID: %d to the automerge queue with sha %s", pr.ID, sha) if err := PRAutoMergeQueue.Push(fmt.Sprintf("%d_%s", pr.ID, sha)); err != nil { - log.Error("Error adding pullID: %d to the pull requests patch checking queue %v", pr.ID, err) + log.Error("Error adding pullID: %d to the automerge queue %v", pr.ID, err) } } @@ -43,32 +43,29 @@ func StartPRCheckAndAutoMergeBySHA(ctx context.Context, sha string, repo *repo_m return nil } -// StartPRCheckAndAutoMerge start an automerge check and auto merge task for a pull request func StartPRCheckAndAutoMerge(ctx context.Context, pull *issues_model.PullRequest) { if pull == nil || pull.HasMerged || !pull.CanAutoMerge() { return } - if err := pull.LoadBaseRepo(ctx); err != nil { - log.Error("LoadBaseRepo: %v", err) - return + commitID := pull.HeadCommitID + if commitID == "" { + commitID = getCommitIDFromRefName(ctx, pull) } - gitRepo, err := gitrepo.OpenRepository(ctx, pull.BaseRepo) - if err != nil { - log.Error("OpenRepository: %v", err) - return - } - defer gitRepo.Close() - commitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName()) - if err != nil { - log.Error("GetRefCommitID: %v", err) + if commitID == "" { return } addToQueue(pull, commitID) } +var AddToQueueIfMergeable = func(ctx context.Context, pull *issues_model.PullRequest) { + if pull.Status == issues_model.PullRequestStatusMergeable { + StartPRCheckAndAutoMerge(ctx, pull) + } +} + func getPullRequestsByHeadSHA(ctx context.Context, sha string, repo *repo_model.Repository, filter func(*issues_model.PullRequest) bool) (map[int64]*issues_model.PullRequest, error) { gitRepo, err := gitrepo.OpenRepository(ctx, repo) if err != nil { @@ -118,3 +115,24 @@ func getPullRequestsByHeadSHA(ctx context.Context, sha string, repo *repo_model. return pulls, nil } + +func getCommitIDFromRefName(ctx context.Context, pull *issues_model.PullRequest) string { + if err := pull.LoadBaseRepo(ctx); err != nil { + log.Error("LoadBaseRepo: %v", err) + return "" + } + + gitRepo, err := gitrepo.OpenRepository(ctx, pull.BaseRepo) + if err != nil { + log.Error("OpenRepository: %v", err) + return "" + } + defer gitRepo.Close() + commitID, err := gitRepo.GetRefCommitID(pull.GetGitRefName()) + if err != nil { + log.Error("GetRefCommitID: %v", err) + return "" + } + + return commitID +} diff --git a/services/webhook/notifier.go b/services/webhook/notifier.go index b3201e5d10..009efc994f 100644 --- a/services/webhook/notifier.go +++ b/services/webhook/notifier.go @@ -894,9 +894,16 @@ func (m *webhookNotifier) ActionRunNowDone(ctx context.Context, run *actions_mod Owner: run.TriggerUser, } + // The doer is the one whose perspective is used to view this ActionRun. + // In the best case we use the user that created the webhook. + // Unfortunately we don't know who that was. + // So instead we use the repo owner, who is able to create webhooks and allow others to do so by making them repo admins. + // This is pretty close to perfect. + doer := run.Repo.Owner + payload := &api.ActionPayload{ - Run: convert.ToActionRun(ctx, run), - LastRun: convert.ToActionRun(ctx, lastRun), + Run: convert.ToActionRun(ctx, run, doer), + LastRun: convert.ToActionRun(ctx, lastRun, doer), PriorStatus: priorStatus.String(), } diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000000..cfd555fa37 --- /dev/null +++ b/shell.nix @@ -0,0 +1,28 @@ +{ + pkgs ? import { }, +}: + +pkgs.mkShell { + name = "forgejo"; + nativeBuildInputs = with pkgs; [ + # generic + git + git-lfs + gnumake + gnused + gnutar + gzip + + # frontend + nodejs + + # backend + gofumpt + sqlite + go + gopls + + # tests + openssh + ]; +} diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl index 7d004dd903..a1ff3d4117 100644 --- a/templates/admin/user/edit.tmpl +++ b/templates/admin/user/edit.tmpl @@ -208,6 +208,7 @@
+
{{ctx.Locale.Tr "avatar.constraints_hint" (ctx.Locale.TrSize .MaxAvatarFileSize) .MaxAvatarWidth .MaxAvatarHeight}}
diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index 7ec2ac87b3..6357981549 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -25,7 +25,7 @@ {{template "base/head_style" .}} {{template "custom/header" .}} - + {{template "custom/body_outer_pre" .}}
diff --git a/templates/org/settings/options.tmpl b/templates/org/settings/options.tmpl index 2ef7031aef..562f3f3ae3 100644 --- a/templates/org/settings/options.tmpl +++ b/templates/org/settings/options.tmpl @@ -94,6 +94,7 @@
+
{{ctx.Locale.Tr "avatar.constraints_hint" (ctx.Locale.TrSize .MaxAvatarFileSize) .MaxAvatarWidth .MaxAvatarHeight}}
diff --git a/templates/repo/commit_header.tmpl b/templates/repo/commit_header.tmpl index 8a074e9545..9604daf2b0 100644 --- a/templates/repo/commit_header.tmpl +++ b/templates/repo/commit_header.tmpl @@ -14,10 +14,19 @@ {{end}} {{end}}
-
+

{{RenderCommitMessage $.Context .Commit.Message ($.Repository.ComposeMetas ctx)}}{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses}}

- {{if not $.PageIsWiki}} - {{end}} -
- {{end}} + {{end}} +
{{if IsMultilineCommitMessage .Commit.Message}}
{{RenderCommitBody $.Context .Commit.Message ($.Repository.ComposeMetas ctx)}}
@@ -185,9 +194,16 @@ {{end}}
{{ctx.Locale.Tr "repo.diff.commit"}} - - {{ShortSha .CommitID}} - + {{if .PageIsPullFiles}} + {{$commitShaLink := (printf "%s/commit/%s" $.RepoLink (PathEscape .CommitID))}} + + {{ShortSha .CommitID}} + + {{else}} + + {{ShortSha .CommitID}} + + {{end}}
diff --git a/templates/repo/commit_load_branches_and_tags.tmpl b/templates/repo/commit_load_branches_and_tags.tmpl index ffa0e530e8..25402ca2f4 100644 --- a/templates/repo/commit_load_branches_and_tags.tmpl +++ b/templates/repo/commit_load_branches_and_tags.tmpl @@ -1,4 +1,4 @@ -{{if not .PageIsWiki}} +{{if not (or .PageIsWiki .PageIsPullFiles)}}
{{if and (not $.Repository.IsArchived) (not .DiffNotAvailable)}} diff --git a/templates/repo/diff/new_review.tmpl b/templates/repo/diff/new_review.tmpl index 13d09babe1..ded7a6c5fc 100644 --- a/templates/repo/diff/new_review.tmpl +++ b/templates/repo/diff/new_review.tmpl @@ -1,17 +1,14 @@
-
- {{if $.IsShowingAllCommits}}
@@ -55,5 +52,4 @@
- {{end}}
diff --git a/templates/repo/issue/filter_list.tmpl b/templates/repo/issue/filter_list.tmpl index ae50ac4c46..84ba6e5358 100644 --- a/templates/repo/issue/filter_list.tmpl +++ b/templates/repo/issue/filter_list.tmpl @@ -127,7 +127,7 @@ {{svg "octicon-triangle-down" 14 "dropdown icon"}}